feat(i18n): localize client-side JS strings via window.__lcI18n catalog (LC-361) #373

Merged
longjacksonle merged 2 commits from fix/lc-361-js-i18n into main 2026-06-19 14:21:35 +02:00

The last MED finding from the UI audit. just test + just test-saas green.

Problem

~45 user-facing strings in the client-side JS were hardcoded English and never localized - the call (call.js), voice-channel (voice.js), upload + voice-recording (composer.html), offline-outbox (outbox.js), reminder (reminders/picker.html), and device-label (devices.js) flows. So exactly the high-stakes moments (a call failing, an upload erroring, offline queueing) stayed English for es users, even though the static template strings are fully translated.

Approach

  • base.html renders a window.__lcI18n object (each value via {{ "js-..."|t }}) plus a window.__lcS(key, fallback) helper, synchronously before the deferred asset scripts.
  • Each call site passes the English literal as the fallback, so a missing/old catalog key degrades to English instead of breaking the flow (calls/uploads have no JS test harness, so this safety net matters).
  • New locales/en/js.ftl + locales/es/js.ftl carry the keys with Spanish translations, glob-loaded alongside the existing catalogs and kept in en/es lockstep for the i18n_catalog gate.
  • %name% / %status% / %n% / %size% are literal placeholders the JS substitutes; filename substitutions use a function replacer to avoid $-in-replacement-string pitfalls.

Tests

  • New render test: a full page (through base.html) emits the catalog object + helper with a known English value.
  • i18n_catalog confirms every key exists in en and en/es parity holds.

Closes LC-361.

The last MED finding from the UI audit. `just test` + `just test-saas` green. ## Problem ~45 user-facing strings in the client-side JS were hardcoded English and never localized - the call (`call.js`), voice-channel (`voice.js`), upload + voice-recording (`composer.html`), offline-outbox (`outbox.js`), reminder (`reminders/picker.html`), and device-label (`devices.js`) flows. So exactly the high-stakes moments (a call failing, an upload erroring, offline queueing) stayed English for `es` users, even though the static template strings are fully translated. ## Approach - `base.html` renders a `window.__lcI18n` object (each value via `{{ "js-..."|t }}`) plus a `window.__lcS(key, fallback)` helper, synchronously before the deferred asset scripts. - Each call site passes the English literal as the fallback, so a missing/old catalog key degrades to English instead of breaking the flow (calls/uploads have no JS test harness, so this safety net matters). - New `locales/en/js.ftl` + `locales/es/js.ftl` carry the keys with Spanish translations, glob-loaded alongside the existing catalogs and kept in en/es lockstep for the `i18n_catalog` gate. - `%name%` / `%status%` / `%n%` / `%size%` are literal placeholders the JS substitutes; filename substitutions use a function replacer to avoid `$`-in-replacement-string pitfalls. ## Tests - New render test: a full page (through `base.html`) emits the catalog object + helper with a known English value. - `i18n_catalog` confirms every key exists in en and en/es parity holds. Closes LC-361.
feat(i18n): localize client-side JS strings via a window.__lcI18n catalog (LC-361)
All checks were successful
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 4s
check-secrets / Kingfisher (push) Successful in 5s
check-secrets / Nosey parker (pull_request) Successful in 8s
check-secrets / TruffleHog (pull_request) Successful in 9s
Check / clippy + fmt + tests (pull_request) Successful in 7m3s
check-secrets / TruffleHog (push) Successful in 5s
56f0d9188c
~45 user-facing strings in the call (call.js), voice-channel (voice.js), upload + voice-recording (composer.html), offline-outbox (outbox.js), reminder (reminders/picker.html), and device-label (devices.js) flows were hardcoded English, so they never localized - exactly the high-stakes flows (a call failing, an upload erroring, offline queueing) stayed English for es users.

base.html now renders a window.__lcI18n object (each value via `{{ "js-..."|t }}`) plus a window.__lcS(key, fallback) helper, synchronously before the deferred asset scripts. Each call site passes the English literal as the fallback, so a missing/old catalog key degrades to English rather than breaking the flow. New en/js.ftl + es/js.ftl carry the keys (Spanish translations included), glob-loaded alongside the existing catalogs and kept in en/es lockstep for the i18n_catalog gate. `%name%`/`%status%`/`%n%`/`%size%` are literal placeholders the JS substitutes (filename substitutions use a function replacer to avoid `$`-in-replacement pitfalls).

Test: a full page render emits the catalog object + helper with a known English value; i18n_catalog confirms key existence + en/es parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(i18n): localize the remaining outbox/devices/recorder JS strings (LC-361)
All checks were successful
check-secrets / Kingfisher (push) Successful in 4s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 5s
check-secrets / Nosey parker (push) Successful in 8s
check-secrets / TruffleHog (push) Successful in 9s
Check / clippy + fmt + tests (pull_request) Successful in 3m37s
check-secrets / Kingfisher (pull_request) Successful in 9s
Create release / Create release from merged PR (pull_request) Has been skipped
f6f8991f57
Completes the JS i18n sweep: the offline-outbox banner (You are offline, Delivering/queued counts, Failed/Retry/Discard), the call-devices modal (title, Close, permission hint, Show device names, System default), and the voice-recorder controls (the Recording indicator, Stop, Cancel) were still hardcoded English. Route them through the same window.__lcS catalog with English fallbacks, and add the keys to en/js.ftl + es/js.ftl in lockstep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch fix/lc-361-js-i18n 2026-06-19 14:21:35 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!373
No description provided.