i18next for Internationalization
Applications often need to support multiple languages for their user interface.
Context
Applications often need to support multiple languages for their user interface. A standardized approach to internationalization (i18n) is needed to translate all UI strings, persist language preferences across sessions, and ensure type-safe translation keys. For applications that need offline availability or fast startup, translations should be bundled with the application rather than loaded remotely, ensuring synchronous access.
The separation between Connected and Presentational components creates a natural boundary for i18n: Connected components handle translation lookups and pass translated strings to Presentational components as props, keeping Presentational components pure and testable without i18n dependencies.
Decision
Use i18next with react-i18next as the internationalization library for the frontend.
- Bundled translations: JSON translation files are statically imported (no HTTP backend), ensuring synchronous availability.
- Type-safe translation keys: Use
CustomTypeOptionsaugmentation to provide type-safet()keys. - Connected/Presentational pattern: Connected components call
useTranslation()and build typedlabelsobjects that are passed to Presentational components via alabelsprop. - No hardcoded strings: Presentational components must not contain hardcoded user-facing strings in JSX; all text comes through the
labelsprop. - Single namespace: All strings live in one
translationnamespace per language undersrc/i18n/locales/{lang}/translation.json. - Pluralization: Use
_one/_otherkey suffixes with thecountparameter. - Interpolation: Use
{{variable}}syntax for dynamic values. - Presentational pluralization: Pass pluralized strings as function props (e.g.,
formatCount: (count: number) => string) from Connected components.
Do's and Don'ts
Do
- Call
useTranslation()only in Connected components. - Pass all translated strings as typed
labelsprops to Presentational components. - Use factory functions for validation schemas that need translated messages.
- Use
i18n.t()(direct import) for code outside React components (utilities, module-level). - Use
_one/_otherkey suffixes for pluralization. - Add new translation keys to all language files simultaneously.
- Provide English literal strings in Storybook story
labelsargs.
Don't
- Call
useTranslation()in Presentational components. - Hardcode user-facing strings in Presentational component JSX.
- Use
t()at module scope in non-factory contexts (translations may not be initialized). - Create multiple namespaces; use the single
translationnamespace. - Use HTTP backends for loading translations; bundle them statically.
- Introduce alternative i18n libraries (react-intl, lingui, etc.).
Consequences
Positive
- Full type safety for translation keys via TypeScript augmentation.
- Synchronous initialization with no loading states needed for translations.
- Clean separation: translations flow through the Connected/Presentational boundary via typed
labelsprops. - Persistent language preference via localStorage survives app restarts.
- Bundled translations work reliably offline.
Negative
- Connected components become more verbose, building complete
labelsobjects for each Presentational child. - All language files must be kept in sync when adding new translation keys.
- Factory function overhead for schemas and module-level constants that reference translations.