ADR 0023: i18n stack — nestjs-i18n (BE) + i18next (FE), Italian-first
ADR 0023: i18n stack — nestjs-i18n (BE) + i18next (FE), Italian-first
Section titled “ADR 0023: i18n stack — nestjs-i18n (BE) + i18next (FE), Italian-first”- Status: Accepted
- Date: 2026-04-16
- Deciders: Ideony team
Context
Section titled “Context”Ideony is Italian-first (primary market) with English as secondary. Consumers, pros, admins all receive UI copy + error messages + notifications in IT by default, EN on explicit language switch. Error strings historically hardcoded in English — needed replacement with translation keys. Auto-detection from device locale required on the client; Accept-Language / x-lang header respect required on the server.
Decision
Section titled “Decision”- Backend:
nestjs-i18nfor server-side translations. Request-scoped resolver viaAccept-Language/x-langheader. Translation files live inapps/api/src/i18n/{en,it}/*.json, organized by namespace (common,booking,credential, etc.). - Frontend:
i18next+react-i18next+expo-localization. Auto-detect locale fromLocalization.locale; user override persisted in Zustand. Translation files inapps/mobile/i18n/{en,it}/*.json. - Copy rule: Italian written first, English second. Regional specificity (“Milano, Zona 9”) preferred over generic “near you”.
A helper t(key, fallback, args?) wraps I18nContext.current() in BE services so tests (no i18n context) fall back to English string transparently.
Consequences
Section titled “Consequences”- Shared Zod-typed key surface between BE + FE via
packages/validators(future) keeps keys in sync - Tests pass without wiring i18n via English fallback — no mocking required
- Namespace separation prevents key collision as app grows
- Single source of truth per language in JSON files (not scattered in source) − Two libraries instead of one shared core — acceptable given different runtime needs (NestJS vs RN) − Translation-file drift between BE + EN; mitigate via CI lint (planned)
Alternatives considered
Section titled “Alternatives considered”- Single library (react-i18next everywhere) — breaks in NestJS DI; no request-scoped resolver for API handlers
- Lingui / FormatJS — more powerful ICU formatting but heavier; nestjs-i18n enough for Ideony’s needs
- Vendor-managed (Lokalise / Phrase) — cost + external ops burden; revisit post-MVP if translation volume grows
- Hardcoded
Accept-Languagein every service — violates DRY;@I18nLang()decorator in nestjs-i18n is cleaner
Related
Section titled “Related”- Translation files:
apps/api/src/i18n/{en,it}/common.json,apps/mobile/i18n/{en,it}/*.json ../glossary.md— terminology (Italian-first, regional specificity)../design-system.md— copy tone