feat(ui): locale persistence + i18n completeness guards (F3)
Tests · UI / test (push) Waiting to run
Tests · UI / test (pull_request) Successful in 2m11s

An audit found the client already i18n-first: one hard-coded UI string
(the battle-scene aria-label, now keyed) and en/ru already share an
identical 692-key set.

- Persist the locale: i18n.setLocale writes localStorage (galaxy-locale)
  and the store boots from stored > browser detection > default, so a
  language switch survives reloads.
- tests/i18n-completeness.test.ts: en/ru key-set parity, non-empty
  values, and locale persistence.
- Docs: ui/docs/i18n.md; mark F3 done in ui/PLAN-finalize.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-22 08:48:13 +02:00
parent c56050f5dd
commit 1e62837c68
7 changed files with 107 additions and 10 deletions
+10 -6
View File
@@ -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.