<#1037 Add i18n infrastructure with per-user langu...
# osctrl
g
#1037 Add i18n infrastructure with per-user language selection (en/es/fr/de) Pull request opened by javuto Add i18n infrastructure with per-user language selection (en/es/fr/de) Problem The frontend had no internationalization — all UI strings were hardcoded English across ~258 component files, with no way for a user to pick another language. Change • i18n core (
src/i18n/
): i18next + ICU via react-i18next. Language resolution mirrors the existing theme pattern:
localStorage["osctrl.language"]
→
navigator.languages
→ English fallback, resolved synchronously at boot. • Performance: English (source/fallback) is bundled in the main chunk so there is no flash of wrong language; es/fr/de are lazy Vite chunks (~1.5 KB gz each), fetched once per browser session. Native
Intl
handles date/number formatting (zero bundle cost), cached per locale via
useLocale
. • Language selector: new
LanguageMenu
(Radix dropdown, reuses the
DropdownMenu
primitive) next to
ThemeToggle
in the TopBar and on the login page. The previous language stays rendered until the new catalog chunk loads — no key/English flash.
document.lang
is kept in sync. • Translated surfaces: SideNav, TopBar, UserMenu, ThemeToggle, and the full LoginPage (incl. MFA enrollment/verification and recovery-code flows). • Type-safe catalogs: every language implements the English key contract; a missing or renamed key fails
tsc
. A catalog-parity test enforces the same guarantee for non-English bundles. Languages: English, Spanish, French, German. Portuguese intentionally skipped per decision. Security/operational impact • Frontend-only; no backend, auth, or API contract changes. Self-hosted chunks only — CSP (
script-src 'self'
) unaffected. • Preference is client-side (localStorage), same trust model as the theme. Validation •
npm run check
— clean •
npm test
— 349 passed (13 new i18n tests: resolution, switch/persist, Intl formatters, catalog completeness) •
npm run build
— production build confirms per-locale lazy chunks Follow-ups (later PRs) • Migrate remaining feature pages and shared components • Replace hardcoded
en-US
in
lib/time.ts
and
chart-formatters.ts
with
useLocale
• Optional Phase 2: backend
PreferredLanguage
on the user profile for cross-device sync jmpsec/osctrl