dorfteich/packages/shared/i18n/de/errors.json
Claude Opus 4.8 4755c18ef5
All checks were successful
CD / Build and push images (push) Successful in 4m9s
CI / Lint, typecheck, test (push) Successful in 2m50s
CI / Auth e2e pack (push) Successful in 3m44s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m15s
CD / Promote to Int (push) Successful in 11s
Add conversion job queue and pandoc sidecar integration (#62)
Import/export conversions run asynchronously against an internal pandoc-server
sidecar with limits and graceful failure (ADR 0009). This is the plumbing;
the import (#63) and export (#65) features enqueue jobs onto it.

Sidecar & config:
- pandoc/core:3.6 in HTTP server mode added to the Compose stack, internal
  network only, with a wget healthcheck on /version; the api depends on it
  healthy and reaches it via the new PANDOC_URL env (default http://pandoc:3030).
- readyz gains a warning-level `converter` check: an unreachable sidecar
  degrades import/export but never flips the instance to unready (new `warn`
  status on ReadinessCheck).

Conversion flow (apps/api/src/import-export/):
- ConversionJob table (per-request work queue, distinct from the name-keyed
  maintenance Job table): owner, formats, input/result bytes, status, attempts,
  lockedAt. Migration + owner cascade.
- PandocConverter (abstract) + PandocServerConverter: POST / with
  {text,from,to,standalone}; binary input formats (docx/odt/…) are base64-encoded
  in `text`; 60 s AbortController timeout; input/output size caps. Failures map
  to distinct localized codes — converter_unavailable / converter_timeout
  (retryable) and conversion_failed (final).
- ConversionWorker: claims one job at a time with `FOR UPDATE SKIP LOCKED`
  (safe against overlapping sweeps and a second process), recovers a stale
  RUNNING lock, retries transient failures up to 3 attempts then fails. A 2 s
  sweep plus wake-on-enqueue means a queued job survives an API restart.
- ConversionJobService.enqueue (size-limited) + owner-scoped GET /jobs/:id
  (poll) and GET /jobs/:id/result (stream the output); a foreign/unknown id is
  404. ConversionJobView in @dorfteich/shared.

Tests:
- conversion-job.e2e.db.test.ts (fake converter injected via a new createTestApp
  override hook): enqueue→convert→poll→result; foreign/unknown job 404; a
  persisted PENDING job picked up by a fresh app's worker (restart survival);
  sidecar-down fails after 3 retries while the API stays healthy.
- pandoc.converter.test.ts: success, non-200→conversion_failed, refused→
  converter_unavailable, and a delay-injecting server→converter_timeout.
- Verified locally against a real pandoc/core:3.6 container: markdown→html,
  markdown→docx (valid PK/OOXML bytes), and a docx→markdown round-trip.

Local: typecheck, lint, i18n:check, build all green; api 193 tests
(9 new), shared 121, web 50.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-10 04:06:27 +02:00

75 lines
4.7 KiB
JSON

{
"bad_request": "Die Anfrage ist ungültig.",
"unauthorized": "Bitte melde dich an, um fortzufahren.",
"forbidden": "Dir fehlt die Berechtigung für diese Aktion.",
"not_found": "Die angeforderte Ressource existiert nicht.",
"conflict": "Die Anfrage steht im Widerspruch zum aktuellen Zustand.",
"gone": "Diese Ressource ist nicht mehr verfügbar.",
"payload_too_large": "Die übermittelten Daten sind zu groß.",
"rate_limited": "Zu viele Anfragen — bitte versuche es später erneut.",
"internal_error": "Interner Serverfehler.",
"registration_closed": "Die Registrierung ist auf dieser Instanz derzeit geschlossen.",
"token_invalid": "Dieser Link ist ungültig oder abgelaufen.",
"login_failed": "Benutzername/E-Mail oder Passwort ist falsch.",
"login_backoff": "Zu viele Fehlversuche — bitte warte ein paar Minuten.",
"email_unverified": "Bitte bestätige zuerst deine E-Mail-Adresse.",
"account_disabled": "Dieses Konto wurde deaktiviert.",
"password_incorrect": "Das aktuelle Passwort ist falsch.",
"csrf_origin_mismatch": "Die Anfrage kam von einer unerwarteten Herkunft.",
"cannot_revoke_current_session": "Beende deine aktuelle Sitzung über die Abmeldung.",
"personal_pond_undeletable": "Der persönliche Teich kann nicht gelöscht werden.",
"quota_exceeded": "Das Kontingent ist erreicht (Limit: {{limit}}).",
"slug_taken": "Dieser Adressname ist in diesem Teich bereits vergeben.",
"page_document_too_large": "Die Seite ist zu groß (Limit: {{limitBytes}} Bytes).",
"invalid_page_state": "Der übermittelte Seiteninhalt ist ungültig.",
"page_trashed": "Diese Seite wurde in den Papierkorb verschoben.",
"label_name_taken": "Ein Label mit diesem Namen existiert auf dieser Ebene bereits.",
"label_cycle": "Ein Label kann nicht in seinen eigenen Teilbaum verschoben werden.",
"label_depth_exceeded": "Labels lassen sich höchstens {{max}} Ebenen tief verschachteln.",
"label_has_pages": "Diesem Label sind noch Seiten zugeordnet; bitte bestätigen, um sie zu lösen.",
"label_wrong_pond": "Dieses Label gehört zu einem anderen Teich.",
"unsupported_file_type": "Dieser Dateityp wird nicht unterstützt.",
"upload_type_not_allowed": "Dieser Dateityp ist auf dieser Instanz nicht erlaubt.",
"file_too_large": "Die Datei ist zu groß (Limit: {{limitBytes}} Bytes).",
"converter_unavailable": "Der Dokument-Konverter ist derzeit nicht verfügbar. Bitte versuche es später erneut.",
"converter_timeout": "Die Konvertierung hat zu lange gedauert und wurde abgebrochen.",
"conversion_failed": "Dieses Dokument konnte nicht konvertiert werden.",
"network": "Der Server war nicht erreichbar.",
"grant_exists": "Diese Berechtigung existiert bereits.",
"grant_pond_admin_scope": "Eine Teich-Admin-Berechtigung muss für den ganzen Teich und eine bestimmte Person gelten.",
"grant_pond_admin_personal_pond": "Der einzige Administrator eines persönlichen Teichs ist dessen Eigentümer.",
"grant_subject_id_mismatch": "Das Subjekt der Berechtigung ist widersprüchlich.",
"grant_scope_id_mismatch": "Der Geltungsbereich der Berechtigung ist widersprüchlich.",
"grant_scope_not_found": "Das Label oder die Seite, auf die sich diese Berechtigung bezieht, existiert in diesem Teich nicht.",
"grant_subject_not_found": "Diese Person existiert nicht.",
"grant_last_admin": "Die letzte Teich-Admin-Berechtigung kann nicht entfernt werden.",
"member_not_found": "Es existiert keine Person mit diesem Benutzernamen oder dieser E-Mail.",
"member_exists": "Diese Person ist bereits Mitglied dieses Teichs.",
"member_not_a_member": "Diese Person ist kein Mitglied dieses Teichs.",
"member_is_owner": "Die Mitgliedschaft des Teich-Eigentümers kann hier nicht geändert werden.",
"cannot_modify_self": "Du kannst diese Aktion nicht auf dein eigenes Konto anwenden.",
"last_site_admin": "Der letzte Site-Admin kann nicht entfernt werden.",
"validation": {
"required": "Dieses Feld ist erforderlich.",
"taken": "Dieser Wert ist bereits vergeben.",
"username": {
"tooShort": "Der Benutzername braucht mindestens 3 Zeichen.",
"tooLong": "Der Benutzername darf höchstens 32 Zeichen haben.",
"charset": "Erlaubt sind nur Buchstaben, Ziffern und Bindestriche."
},
"password": {
"tooShort": "Das Passwort braucht mindestens 10 Zeichen.",
"tooLong": "Das Passwort darf höchstens 128 Zeichen haben.",
"tooCommon": "Dieses Passwort ist zu verbreitet."
},
"email": {
"invalid": "Bitte gib eine gültige E-Mail-Adresse ein."
},
"displayName": {
"required": "Bitte gib einen Anzeigenamen ein."
},
"labelColor": "Bitte gib eine Farbe wie #a1b2c3 ein.",
"tooLong": "Die Eingabe ist zu lang."
}
}