Internationalization (i18n) Conventions
General Rules
- Never hardcode user-facing strings in source code. Always use translation keys.
- Never concatenate translated strings. Use interpolation:
t('greeting', name: user.name).
- Fallback locale: English (
en) is the default fallback for all missing translations.
- Locale detection: Server reads
Accept-Language header. Mobile reads device locale via react-native-localize.
Rails i18n
File Organization
- One YAML file per domain per locale:
backend/config/locales/{locale}/{domain}.{locale}.yml
- Domains:
models, errors, notifications, mailers, api
- Devise translations in
backend/config/locales/defaults/devise.{locale}.yml
Key Naming
- Dot-separated, hierarchical:
errors.not_found, activerecord.models.user
- Use lazy lookup in controllers:
t('.success') resolves from controller namespace
- Avoid deeply nested keys (max 4 levels)
Pluralization
- Use Rails CLDR pluralization rules per locale
- Arabic requires all six plural forms:
zero, one, two, few, many, other
- Always provide at minimum
one and other forms
Formatting
- Dates:
I18n.l(date, format: :short) — never strftime directly
- Numbers:
number_to_currency, number_with_delimiter — never manual formatting
- Define format patterns in locale YAML files, not in code
React Native i18n
File Organization
- JSON locale files in
mobile/src/i18n/locales/{locale}.json
- Flat namespaced keys:
"orders.title", "auth.login"
- Initialization in
mobile/src/i18n/index.ts
Key Naming
- camelCase within namespaces:
"auth.forgotPassword"
- Plurals use
_one / _other suffixes (i18next convention)
- Context variants use
_male / _female suffixes
RTL Support
- Use
start/end instead of left/right for layout properties
- Flip directional icons for RTL locales
- Test all screens in both LTR and RTL modes
- Use
I18nManager.isRTL for conditional logic
Web SPA i18n (Vite + react-i18next)
Setup
- Use
react-i18next (same library as React Native, without react-native-localize).
- Locale detection via
navigator.language (browser API) instead of device locale.
- JSON locale files in
web/src/i18n/locales/{locale}.json — same key format as React Native.
- Initialization in
web/src/i18n/index.ts.
Locale Detection
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n.use(LanguageDetector).use(initReactI18next).init({
resources: { en: { translation: en }, ar: { translation: ar } },
fallbackLng: 'en',
detection: { order: ['navigator', 'htmlTag'] },
});
Web RTL Support
- Use CSS logical properties (
margin-inline-start instead of margin-left).
- Tailwind CSS
rtl: variant for directional overrides.
- Set
dir="rtl" on <html> element when locale is RTL.
- No
I18nManager on web — use CSS-only RTL approach.
Next.js i18n
Server Component i18n
- Use
next-intl or server-compatible i18next setup.
- Locale detection via
middleware.ts reading Accept-Language header.
- Pass locale to Server Components via layout params or headers.
- Do NOT use
useTranslation hook in Server Components — use a server-side t() function.
Client Component i18n
- Use
useTranslation from react-i18next in Client Components.
- Locale files bundled or loaded dynamically per route.
Locale Routing
- Use Next.js middleware for locale-based routing (e.g.,
/en/orders, /ar/orders).
- Or use a single locale with
Accept-Language header detection (simpler setup).
CI Validation
- Run locale key parity check: all locale files must have the same keys as
en
- Missing translations must fail CI in production builds
- Development builds show raw keys for easy identification of missing translations
1---2name: std-i18n3description: Internationalization standards — locale files, key naming, pluralization, RTL, CSS logical properties across Rails/RN/Vite/Next. Use when adding user-facing strings or locales.4---56# Internationalization (i18n) Conventions78## General Rules910- **Never hardcode user-facing strings** in source code. Always use translation keys.11- **Never concatenate** translated strings. Use interpolation: `t('greeting', name: user.name)`.12- **Fallback locale**: English (`en`) is the default fallback for all missing translations.13- **Locale detection**: Server reads `Accept-Language` header. Mobile reads device locale via `react-native-localize`.1415## Rails i18n1617### File Organization18- One YAML file per domain per locale: `backend/config/locales/{locale}/{domain}.{locale}.yml`19- Domains: `models`, `errors`, `notifications`, `mailers`, `api`20- Devise translations in `backend/config/locales/defaults/devise.{locale}.yml`2122### Key Naming23- Dot-separated, hierarchical: `errors.not_found`, `activerecord.models.user`24- Use lazy lookup in controllers: `t('.success')` resolves from controller namespace25- Avoid deeply nested keys (max 4 levels)2627### Pluralization28- Use Rails CLDR pluralization rules per locale29- Arabic requires all six plural forms: `zero`, `one`, `two`, `few`, `many`, `other`30- Always provide at minimum `one` and `other` forms3132### Formatting33- Dates: `I18n.l(date, format: :short)` — never `strftime` directly34- Numbers: `number_to_currency`, `number_with_delimiter` — never manual formatting35- Define format patterns in locale YAML files, not in code3637## React Native i18n3839### File Organization40- JSON locale files in `mobile/src/i18n/locales/{locale}.json`41- Flat namespaced keys: `"orders.title"`, `"auth.login"`42- Initialization in `mobile/src/i18n/index.ts`4344### Key Naming45- camelCase within namespaces: `"auth.forgotPassword"`46- Plurals use `_one` / `_other` suffixes (i18next convention)47- Context variants use `_male` / `_female` suffixes4849### RTL Support50- Use `start`/`end` instead of `left`/`right` for layout properties51- Flip directional icons for RTL locales52- Test all screens in both LTR and RTL modes53- Use `I18nManager.isRTL` for conditional logic5455## Web SPA i18n (Vite + react-i18next)5657### Setup58- Use `react-i18next` (same library as React Native, without `react-native-localize`).59- Locale detection via `navigator.language` (browser API) instead of device locale.60- JSON locale files in `web/src/i18n/locales/{locale}.json` — same key format as React Native.61- Initialization in `web/src/i18n/index.ts`.6263### Locale Detection64```typescript65import i18n from 'i18next';66import { initReactI18next } from 'react-i18next';67import LanguageDetector from 'i18next-browser-languagedetector';6869i18n.use(LanguageDetector).use(initReactI18next).init({70 resources: { en: { translation: en }, ar: { translation: ar } },71 fallbackLng: 'en',72 detection: { order: ['navigator', 'htmlTag'] },73});74```7576### Web RTL Support77- Use CSS logical properties (`margin-inline-start` instead of `margin-left`).78- Tailwind CSS `rtl:` variant for directional overrides.79- Set `dir="rtl"` on `<html>` element when locale is RTL.80- No `I18nManager` on web — use CSS-only RTL approach.8182## Next.js i18n8384### Server Component i18n85- Use `next-intl` or server-compatible i18next setup.86- Locale detection via `middleware.ts` reading `Accept-Language` header.87- Pass locale to Server Components via layout params or headers.88- Do NOT use `useTranslation` hook in Server Components — use a server-side `t()` function.8990### Client Component i18n91- Use `useTranslation` from `react-i18next` in Client Components.92- Locale files bundled or loaded dynamically per route.9394### Locale Routing95- Use Next.js middleware for locale-based routing (e.g., `/en/orders`, `/ar/orders`).96- Or use a single locale with `Accept-Language` header detection (simpler setup).9798## CI Validation99100- Run locale key parity check: all locale files must have the same keys as `en`101- Missing translations must fail CI in production builds102- Development builds show raw keys for easy identification of missing translations