All checks were successful
CD / Build and push images (push) Successful in 4m3s
CI / Lint, typecheck, test (push) Successful in 3m5s
CI / Auth e2e pack (push) Successful in 4m7s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m17s
CD / Promote to Int (push) Successful in 12s
Server-side PDF export for reading/sharing (ADR 0009), rendered by a new internal Gotenberg (headless Chromium) sidecar. - Sidecar: `gotenberg/gotenberg:8` in the compose stack (internal, pinned, healthcheck); api `GOTENBERG_URL` env; a `renderer` readyz check at warning-level (mirrors the converter) so PDF export degrades gracefully when Gotenberg is down without failing readyz. - Export HTML: `buildPdfHtml` renders a self-contained document (no app chrome) — the page's content with images inlined as data URIs, the pond's fonts inlined as base64 `@font-face` + applied via CSS variables (ADR 0016), print CSS (A4, page-break rules, a title header), and page numbers from Gotenberg's footer. Plugin-block fallbacks are a marked TODO(#79) for M7. - Fonts in the api image: the api Dockerfile now bakes the font catalog in (`build-fonts.mjs` with FONTS_OUT) so the exporter can read a pond's chosen WOFF2 and inline them; a missing file falls back to the system stack. - Job flow: `POST /pages/:id/export {format: pdf}` builds the HTML (read permission checked by the guard) and enqueues an `export_pdf` job on the #62 queue with the HTML as input; the worker branches `to === 'pdf'` to the `GotenbergRenderer` (html → pdf) instead of pandoc, retrying an unreachable sidecar and failing a refused render (`renderer_unavailable`/`render_failed`, de+en). The client polls and downloads `GET /jobs/:id/result`. - Frontend: the page-menu PDF button is now a real export (PDF added to EXPORT_FORMATS; the disabled placeholder removed). - Tests: export.service.db PDF cases (HTML has title/font-variable/inlined image; renderer-down fails with `render_failed`); e2e PDF export self-skips without a Gotenberg sidecar (like the .docx case). Verified locally against real Gotenberg — a valid PDF with the pond font embedded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
78 lines
5.0 KiB
JSON
78 lines
5.0 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.",
|
|
"renderer_unavailable": "Der PDF-Renderer ist derzeit nicht verfügbar. Bitte versuche es später erneut.",
|
|
"render_failed": "Diese Seite konnte nicht als PDF gerendert werden.",
|
|
"import_unsupported_format": "Nur Word- (.docx) und OpenDocument-Dokumente (.odt) können importiert 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."
|
|
}
|
|
}
|