ADR-013: Frontend i18n (i18next) and locale strategy
Status: Accepted
Date: 2026-03-24
Deciders: Engineering
Related: ADR-004 (React SPA), Frontend i18n docs, Locale tooling suite
Context
CepatEdge ships a React SPA with multiple user-facing surfaces (layouts, navigation, domain pages, router error states). We need:
- Consistent, maintainable user-visible copy without scattering English literals.
- User language driven by
/me(andGET /me/settings)settings.language, with English as the canonical fallback. - Room to grow: backend
STRINGS/ error codes mapped undererrors.codes, not raw API prose. - Alignment between filesystem domains (
src/domains/shared,auth,hod, …) and translation key hierarchy.
Decision
- Use i18next + react-i18next with a single default namespace (
translation) and static locale bundles (apps/web/locales/*.json) for predictable builds. - Fallback language is always English (
en); unknownlanguagevalues from the API are normalized toeninnormalizeLanguageTag()(apps/web/src/i18n/config.ts). - Runtime language sync:
LanguageSync(apps/web/src/i18n/sync.tsx) appliesuser.settings.languageafter auth hydration; the Settings page applies language after save/load. - Key naming follows a stable hierarchy (see
docs/frontend/i18n/README.mdand linked tooling docs forgenerate/sync/test):common.*— shared actions and labelslayouts.*— shell chrome (header, footer, auth layout)navigation.*— sidebar sections and nav items (machine-orientedlabelKeys, not English prose in data)pages.domain.<area>.<feature>.*— route-level screenserrors.codes.*— reserved for backend code → message mapping
- TypeScript key prefixes live in
apps/web/src/i18n/keys.tsto reduce typos when composingt(\${PREFIX}.field`)`.
Consequences
- Positive: One place to add languages (config + JSON +
resourcesini18n/index.ts); SEO-friendlydocument.documentElement.langupdates viasetAppLanguage. - Positive: Navigation and recents can use
labelKeyso stored recents stay stable when copy changes (display is always translated at render). - Trade-off: Adding a language requires updating
SUPPORTED_LANGUAGES, locale files, andresources(acceptable until many locales justify lazy-loading split).
Implementation references
| Area | Location |
|---|---|
Init + setAppLanguage | apps/web/src/i18n/index.ts |
| Supported langs + normalization | apps/web/src/i18n/config.ts |
| Key prefix constants | apps/web/src/i18n/keys.ts |
| Locale tooling | docs/frontend/i18n/*.md, apps/web/scripts/locales/ |
| Auth language sync | apps/web/src/i18n/sync.tsx |
| App bootstrap import | apps/web/src/main.tsx (import './i18n') |