GitHub
09/07/2026, 12:03 PMsrc/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/osctrlGitHub
09/07/2026, 12:08 PM