diff --git a/ui/PLAN-finalize.md b/ui/PLAN-finalize.md index 2e1dfed..efd3e37 100644 --- a/ui/PLAN-finalize.md +++ b/ui/PLAN-finalize.md @@ -69,7 +69,17 @@ axe-core scan; full keyboard reachability with visible focus. Tests: axe-core integration tests on every top-level view; Playwright keyboard-only navigation. -## F3 — Localisation completeness +## F3 — Localisation completeness — done + +An audit found the client already i18n-first (the single hard-coded UI +string, the battle-scene `aria-label`, is now keyed); en/ru already have +identical key sets (692 keys). Added locale **persistence** to the i18n +store (`localStorage` `galaxy-locale`: stored choice > browser detection +> default) so a switch survives reloads, and +`tests/i18n-completeness.test.ts` enforcing en/ru key-set parity, +non-empty values, and persistence. A noisy literal-text lint was +deliberately skipped — the structural parity test plus the i18n-first +convention cover drift. Docs: `ui/docs/i18n.md`. (From Phase 35.) Goal: every visible string is translated (en + ru). diff --git a/ui/docs/i18n.md b/ui/docs/i18n.md index 7cfe9e4..069b6c9 100644 --- a/ui/docs/i18n.md +++ b/ui/docs/i18n.md @@ -82,12 +82,13 @@ Native wrappers (Wails, Capacitor) will pass their system locale once the desktop/mobile targets land (see ../ROADMAP.md); the helper is platform-agnostic by design. -The detection runs once at module load — there is no asynchronous -init step. Callers that mutate the locale (e.g. the language picker -on `/login`) call `i18n.setLocale(next)` directly. The choice is -**not** persisted between page reloads; the next visit re-runs -detection. Persistence is deferred to the finalization plan -(../Plan-finalize.md). +The boot locale resolves once at module load (no async init): +an explicit stored choice wins, otherwise browser/system detection, +otherwise `DEFAULT_LOCALE`. Callers that mutate the locale (the language +pickers on `/login` and in the account menu) call `i18n.setLocale(next)`, +which **persists** the choice to `localStorage` (key `galaxy-locale`) so +it survives reloads. An unrecognised stored value is ignored and falls +back to detection. ## Forwarding the locale to the gateway @@ -135,6 +136,9 @@ at challenge issuance and replayed from the challenge row. - `tests/i18n.test.ts` covers `detectInitialLocale`, `i18n.setLocale`, parameter interpolation, and the unknown-key fallback. +- `tests/i18n-completeness.test.ts` enforces en/ru key-set parity (no + key present in one locale but missing in the other), non-empty values, + and locale persistence (a stored choice is restored on the next load). - `tests/login-page.test.ts` asserts the language picker renders with native names, switching the locale re-renders the form text, and `sendEmailCode` receives the active locale. diff --git a/ui/frontend/src/lib/battle-player/battle-scene.svelte b/ui/frontend/src/lib/battle-player/battle-scene.svelte index bcc2292..55e8a9e 100644 --- a/ui/frontend/src/lib/battle-player/battle-scene.svelte +++ b/ui/frontend/src/lib/battle-player/battle-scene.svelte @@ -23,6 +23,7 @@ combat filter, so a shot never collapses to a single visual node.