Skip to content

Translate the remaining German UI text into English - #22

Merged
Krypt0ph0ne merged 1 commit into
mainfrom
full-bilingual-ui
Aug 3, 2026
Merged

Translate the remaining German UI text into English#22
Krypt0ph0ne merged 1 commit into
mainfrom
full-bilingual-ui

Conversation

@Krypt0ph0ne

Copy link
Copy Markdown
Owner

Switching the app to English left large parts of the interface German — settings and section headings, action titles and descriptions, status and error text. Five parallel audits swept every view, model, store, service and both action-catalogue JSON files.

Root cause

Two localization mechanisms coexist and both had gaps:

  1. AppLanguage.text(de, en) for plain Strings.
  2. Localizable.strings, keyed on the German source, for LocalizedStringKey positions.

I verified empirically (rendering Text under each locale) that .environment(\.locale, …) really does drive the strings table, and that a missing de.lproj correctly falls back to the German keys rather than to English. The mechanism was sound; only its coverage was missing.

The biggest single gap was the action catalogues: CodexActions.json and ClaudeActions.json carry German description, category and availabilityNote values that were rendered raw.

Changes

  • +230 entries in en.lproj/Localizable.strings, covering every German value in both catalogue JSON files, including gesture shortcuts like ⌘F17 halten.
  • CodexActionCatalog translates on read, not at decode time — the catalogue is built once in AppState.init, so decode-time translation would have needed an app restart to follow a language switch. The JSON stays the German source of truth.
  • String-typed text wrapped in AppLanguage.text: view models, diagnostics entries, device and bridge status, permission notifications, and the errors surfaced through lastError.
  • Interpolated Text rewritten to AppLanguage.textLocalizedStringKey interpolation does not resolve reliably.

Two latent bugs found along the way

  • OnboardingAssignmentDemo.sampleThreads and the LED colour presets were stored static lets. A stored static initialises lazily exactly once, so their text froze at whatever language was active on first access and never followed a later switch. Now computed.
  • control.title prefix strips searched for "Taste ", which silently stopped matching once the title was English.

Deliberately not translated

  • Accessibility strings matched against Claude's own German UI ("menü", "aufwand", effort/model keywords). Translating these would break the automation.
  • Persisted labels stay German on disk and are looked up at render time via KeyboardAction.displayLabel, so saved profiles and the legacy Pet anzeigen migration keep working.
  • Three strings reachable only from code no view reads today (PendingApproval.summary, lastResolvedAction).

Each of these was traced before being left alone; a verification script re-checks that the remaining German literals are exactly this set.

Verification

  • swift build clean; 117 tests pass.
  • New regression test testEnglishLocalizationResolvesForTableAndActionCatalog asserts the strings table is reachable, that unknown keys fall back to German rather than an empty string, and that no catalogue field still contains German characters under English. This is what would have caught the original bug.
  • build_and_run.sh now fails loudly when en.lproj is missing instead of silently shipping a German-only app.

Not verified: I did not launch the built .app for a visual pass, since rebuilding can void the Accessibility grant.

🤖 Generated with Claude Code

Switching the app to English left large parts of the interface German:
settings and section headings, action titles and descriptions, status and
error text. Two mechanisms coexist — `AppLanguage.text(de, en)` for plain
`String`s and `Localizable.strings` keyed on the German source for
`LocalizedStringKey` positions — and both had gaps.

Verified first that `.environment(\.locale, …)` really does drive the
strings table (it does, and a missing `de.lproj` correctly falls back to
the German keys rather than to English), so the mechanism was sound and
only its coverage was missing.

- Add 230 entries to `en.lproj/Localizable.strings`, including every
  German `title`, `description`, `category`, `availabilityNote` and
  gesture `shortcut` carried in the two action catalogue JSON files.
- Translate the catalogue on read in `CodexActionCatalog` rather than at
  decode time, so a language switch takes effect without a restart. The
  JSON stays the German source of truth.
- Wrap the String-typed text that never passes through
  `LocalizedStringKey`: view models, diagnostics entries, device and
  bridge status, permission notifications, and the errors surfaced via
  `lastError`.
- Rewrite interpolated `Text("… \(x) …")` to `AppLanguage.text`, which
  `LocalizedStringKey` interpolation does not resolve reliably.

Persisted labels stay German on disk and are looked up at render time
via `KeyboardAction.displayLabel`, so saved profiles and the legacy
migration keep working. Accessibility strings matched against Claude's
own German UI are deliberately untouched — translating those would break
the automation.

Two latent bugs surfaced along the way: `sampleThreads` and the LED
colour presets were stored statics, so their text froze at whatever
language was active on first access; and `control.title` prefix strips
looked for "Taste " even in English.

Also fail the build loudly when `en.lproj` is missing instead of
silently shipping a German-only app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Krypt0ph0ne
Krypt0ph0ne merged commit 88bb1f2 into main Aug 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant