fix(i18n): translate viewer + Transcribe panel controls so EN/ES locales do not show German labels #319
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
With locale set to EN (and presumably ES — not yet sampled), several controls inside the document viewer and Transcribe panel render with German labels instead of being translated.
Captured on 2026-04-24 during Phase B2 audit, structural snap at
/tmp/fa-audit/G2-snap.json:Zurück,WeiterVerkleinern,VergrößernFür Training vormerkenMenü öffnenOther controls in the same surfaces (
Read,Edit,Done,Close panel,Draw regions on the document to start transcribing.) are in English — so the surrounding i18n plumbing works; these specific strings just weren't wired up.Stakes: the product mission is "readable for a younger generation." That generation may prefer English (or Spanish, for family in Spain / Latin America). Mixed-locale UI undermines trust in the translation and fails WCAG 3.1.2 ("language of parts").
Non-goals
Implementation plan
1. Discover every violation
Also grep broader for suspiciously hardcoded German in viewer/Transcribe files:
Each hit becomes a Paraglide key.
2. Add message keys
In
frontend/messages/{de,en,es}.json— keys must exist in all three:Translation suggestions — confirm with a native speaker before merge. Spanish especially.
3. Replace string literals with
m.*()callsfrontend/src/lib/components/DocumentViewer.svelte(or its zoom/pager sub-components — verify the actual component names via the rg output)frontend/src/lib/components/TranscribePanel.svelte(or its footer component)frontend/src/routes/+layout.svelte(mobile drawer toggle button)4. Regression guard
Add a Vitest unit test that asserts the three JSON files have identical key sets:
Tests
/documents/<some-id>, open Transcribe panel, assert none ofZurück|Weiter|Verkleinern|Vergrößern|Für Training vormerken|Menü öffnenappear in the visible DOM.Verification
frontend/src/for any residual hardcoded German. Zero hits expected.Acceptance criteria
de/en/esJSONs have matching key sets (enforced by unit test)Critical files
👨💻 Felix Brandt — Senior Fullstack Developer
Observations
PdfControls.svelte(notDocumentViewer.svelteas the critical files list says). The four aria-labels (Zurück,Weiter,Verkleinern,Vergrößern) are on lines witharia-label="..."on icon-only buttons.DocumentViewer.svelteexists but is not where these strings are. Fix the critical files list before anyone starts implementing.AppNav.svelte:87confirmed:aria-label={mobileNavOpen ? 'Menü schließen' : 'Menü öffnen'}— both states are hardcoded.TranscriptionEditView.svelte:242confirmed:<p class="mb-2 font-sans text-xs font-medium text-ink-2">Für Training vormerken</p>— this is visible text, not just an aria-label.btn_backalready exists inde.jsonwith value "Zurück", but it carries navigation-back semantics. Do not reuse it for the PDF page-prev button —viewer_previous_pageis the right distinct key.rg --type sveltewill silently find nothing: ripgrep doesn't know thesveltefiletype by default. Userg -g '*.svelte'or pass file paths directly.Recommendations
DocumentViewer.sveltewithPdfControls.svelte.rg -g '*.svelte' -g '*.ts' "Zurück|Weiter|Verkleinern|Vergrößern|Für Training|Menü öffnen|Menü schließen" frontend/src/layout_menu_close("Menü schließen") is inAppNav.sveltebut is absent from the proposed key list. Add it — that's violation #7 and it's already in the rg grep pattern in the issue body.PdfControls.sveltealready importsmfrom$lib/paraglide/messages.jsand uses it for the annotation toggle (m.pdf_annotations_hide()/m.pdf_annotations_show()). The wiring pattern is already there — this is a mechanical substitution.🏛️ Markus Keller — Senior Application Architect
Observations
.sveltefiles and three JSON message files — as contained as it gets.viewer_*,layout_*) are consistent with the existing conventions (nav_*,btn_*,form_*,pdf_*). They scale correctly — a reader navigating the JSON file can group keys by prefix without a map.PdfControls.sveltealready has the Paraglide import and usesm.pdf_annotations_*()— this component knows how to use the message system. There is no architectural discovery needed; it's a mechanical wiring job.Recommendations
viewer_previous_pageandviewer_next_pageas distinct keys frombtn_back. They carry different semantics (document paging vs. UI navigation). Reusingbtn_backwould make a future rename or translation change ripple into an unrelated surface.src/lib/messages.spec.tsfile, not piggybacked onto an unrelated spec. The file doesn't yet exist; creating it is the right call.🔒 Nora "NullX" Steiner — Application Security Engineer
Observations
aria-labelattributes — matches the declared page language. When locale is EN,aria-label="Zurück"on a button is a conformance failure for AT users. The issue correctly identifies this.aria-labelattributes (PdfControls.svelte,AppNav.svelte) or a visible<p>(TranscriptionEditView.svelte). There is no auth surface, no session state, no server-side data exposure involved. Risk is purely accessibility compliance.Recommendations
e2e/accessibility.spec.ts— it already exists. If axe-core is already imported and configured there, extend that file rather than creating a new integration point.🧪 Sara Holt — Senior QA Engineer
Observations
e2e/lang.spec.tsalready demonstrates the locale-switching E2E pattern: click the EN button in the banner, verify the nav updates, usepage.context().clearCookies({ name: 'PARAGLIDE_LOCALE' })to reset. The new i18n test should reuse exactly this setup.e2e/fixtures/has such a fixture or that the test setup creates one — otherwise the test will be blocked by "no file attached" state.layout_menu_close("Menü schließen") is confirmed inAppNav.svelte:87but is absent from the acceptance criteria. It's violation #7. The "≥ 6 strings" criterion should cover it, but it should be named explicitly so the implementer doesn't miss it.Recommendations
Zurück,Weiter,Verkleinern,Vergrößern,Für Training vormerken,Menü öffnen,Menü schließen.getByRole('button', { name: 'Previous page' })rather than text matchers — this directly verifies the accessibility label is translated, not just that German text is absent.e2e/accessibility.spec.tsshould cover the EN and ES locale paths after the fix; confirm that file already sets the locale before running the axe scan.🎨 Leonie Voss — Senior UX Designer & Accessibility Strategist
Observations
Zurück,Weiter,Verkleinern,Vergrößern) arearia-labelvalues on icon-only buttons — invisible to sighted users but read by screen readers and shown as hover tooltips in most browsers. Translating them correctly is both a screen-reader fix and a tooltip fix.TranscriptionEditView.svelte:242is visible text rendered in a<p>tag — directly in the UI for all users. This one is the most user-visible of the violations.Recommendations
Previous page,Next page,Zoom out,Zoom in). Keep them as aria-labels — do not add visible text to these icon buttons.training_ocr_heading,ocr_stat_training_blocks). The EN translation should follow suit — "Mark for OCR training" rather than just "Mark for training" — otherwise users unfamiliar with the context won't know what "training" refers to.Open Decisions
🚀 Tobias Wendt — DevOps & Platform Engineer
Observations
*.sveltecomponents andmessages/*.jsonfiles. Fully self-contained infrontend/.npm run test— already part of CI.e2e/lang.spec.tsalready exercises the locale-switching flow successfully, so the test infrastructure is proven.Recommendations
Menü öffnen) needs a narrow-viewport Playwright context. Check that the Playwright config or the test itself setsviewport: { width: 375, height: 812 }for that specific assertion — otherwise the mobile button is never rendered and the test passes vacuously.📋 Elicit — Requirements Engineer
Observations
layout_menu_close("Menü schließen") appears inAppNav.svelte:87alongsidelayout_menu_open, but is not listed in the proposed keys, not in the acceptance criteria, and not in the verification steps. The issue body's own grep pattern includes it (Menü öffnen), but the open/close pair must both be translated together — an untranslated "Menü schließen" after the fix would leave 1 of 2 states broken.layout_menu_closeis confirmed as violation #7.DocumentViewer.svelteas the location for viewer violations, but the actual violations are inPdfControls.svelte. The implementer will grep first and discover this anyway, but correcting it removes ambiguity.Recommendations
layout_menu_close/ "Menü schließen" to: (a) the proposed key list, (b) the acceptance criteria, (c) the verification walk-through.DocumentViewer.svelte→PdfControls.sveltein the critical files list.rg --type sveltewhich is not a valid ripgrep built-in type. Suggest replacing withrg -g '*.svelte'to avoid a silent no-match.🗳️ Decision Queue — Action Required
1 decision needs your input before implementation starts.
UX / Translations
training_ocr_heading,ocr_stat_training_blocks,error_training_already_running). The issue proposes just "Mark for training" / "Mark for training" for EN — but without the "OCR" qualifier, younger non-technical users won't know what they're marking the document for. Option A: "Mark for OCR training" (consistent with the codebase, more context); Option B: "Mark for training" (shorter, matches the issue's suggestion, arguably cleaner in the UI). (Raised by: Leonie)