Skip to content

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

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.

  • Backend: nestjs-i18n for server-side translations. Request-scoped resolver via Accept-Language / x-lang header. Translation files live in apps/api/src/i18n/{en,it}/*.json, organized by namespace (common, booking, credential, etc.).
  • Frontend: i18next + react-i18next + expo-localization. Auto-detect locale from Localization.locale; user override persisted in Zustand. Translation files in apps/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.

  • 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)
  • 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-Language in every service — violates DRY; @I18nLang() decorator in nestjs-i18n is cleaner
  • 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