Frontend Internationalization (i18n)
Purpose
Deliver multi-locale frontends with proper locale detection, resource loading, ICU message formatting, pluralization, date/number formatting, and full RTL layout support. The right library is chosen per framework. Translation files are kept separate from application logic.
Agent Protocol
Trigger
Exact phrases: "i18n", "internationalization", "localization", "locale switching", "RTL", "i18next", "react-intl", "vue-i18n", "translation", "language selector", "formatjs", "pluralization", "bidirectional text", "locale detection".
Input Context
- Framework (React, Vue, Angular, Svelte, vanilla)
- Locales required (e.g., en, fr, ar, he)
- Existing i18n library or preference
- SSR vs client-only rendering
- Dynamic vs build-time locale loading
- RTL support needed
Output Artifact
Complete i18n setup: library config, resource structure, locale switching mechanism, RTL styles, and usage patterns.
Response Format
## Strategy
<library, locale-loading, RTL-approach>
## Setup
<config, resource-imports>
## Usage
<components, hooks, format-patterns>
## RTL
<layout-switch, css-logical-properties>
—
Compression footer: frontend-i18n/v1 | locales: <count> | lib: <selected> | rtl: <bool>
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.
Completion Criteria
- Locale resources loaded (dynamic or static) without affecting TTFB
- Locale switching works without full page reload
- ICU message format used for pluralization and interpolation
- Dates, numbers, currencies formatted per locale
- RTL layout switches correctly with CSS logical properties
- Language direction attribute updates on the
<html>element - Translation key fallback chain configured (locale → fallback locale)
- SSR locale detection sends correct language to client
Max Response Length
4096 tokens
Workflow
1. Library Selection
| Framework | Library | Notes |
|---|---|---|
| React | react-i18next (i18next) | Most mature, full feature set |
| React | react-intl (FormatJS) | ICU message syntax, smaller bundle |
| Vue 2/3 | vue-i18n | Official Vue i18n solution |
| Angular | @angular/localize | Built-in Angular i18n |
| Svelte | svelte-i18n | Lightweight, reactive |
| Vanilla | i18next | Framework-agnostic |
Prefer i18next for cross-framework projects or when full feature set is needed. Prefer FormatJS for React projects that need ICU message syntax and smaller bundle size.
2. Resource Structure
locales/
├── en/
│ ├── common.json
│ ├── auth.json
│ └── errors.json
├── fr/
│ ├── common.json
│ ├── auth.json
│ └── errors.json
└── ar/
├── common.json
├── auth.json
└── errors.json
Namespaced translation files. Lazy-load namespaces on demand. Each namespace is a flat JSON file with dot-separated keys.
3. Locale Detection & Persistence
// Priority: URL > localStorage > cookie > navigator.language > fallback
import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n.use(LanguageDetector).init({
detection: {
order: ['localStorage', 'navigator', 'htmlTag'],
caches: ['localStorage'],
},
fallbackLng: 'en',
interpolation: { escapeValue: false },
})
4. Translation Usage
// Key-based translation with interpolation
t('auth.welcome', { name: 'Alice' })
// → "Welcome, Alice!"
// Pluralization
t('common.items', { count: items.length })
// count=0 → "No items"
// count=1 → "1 item"
// count=5 → "5 items"
// With context
t('common.status', { context: status })
// status='online' → "User is online"
// status='offline' → "User is offline"
5. ICU Message Format (FormatJS)
import { FormattedMessage, useIntl } from 'react-intl'
// Component
<FormattedMessage
defaultMessage="{name} has {numPhotos, plural, =0 {no photos} =1 {one photo} other {# photos}}"
values={{ name: 'Alice', numPhotos: 3 }}
/>
// Hook
const { formatMessage } = useIntl()
formatMessage({ defaultMessage: 'Hello {name}' }, { name: 'Bob' })
6. RTL Support
// Detect direction from locale
const isRTL = ['ar', 'he', 'fa', 'ur'].includes(locale)
document.documentElement.dir = isRTL ? 'rtl' : 'ltr'
document.documentElement.lang = locale
Prefer CSS logical properties over hardcoded left/right:
/* NOT THIS */
.sidebar { margin-left: 16px; text-align: left; }
/* THIS */
.sidebar { margin-inline-start: 16px; text-align: start; }
Logical properties automatically flip in RTL mode: margin-inline-start → right margin in RTL, padding-inline-end → left padding in RTL, border-inline-start → right border in RTL, inset-inline-start → right positioning in RTL.
7. Date & Number Formatting
// i18next
new Date().toLocaleDateString(locale, { dateStyle: 'long' })
new Intl.NumberFormat(locale, { style: 'currency', currency: 'EUR' }).format(amount)
// FormatJS
import { FormattedDate, FormattedNumber } from 'react-intl'
<FormattedDate value={date} dateStyle="long" />
<FormattedNumber value={price} style="currency" currency="USD" />
<FormattedNumber value={0.9} style="percent" />
8. SSR Locale Detection
// Next.js i18n (simpler approach — use middleware)
// Or accept-language header parsing
import acceptLanguage from 'accept-language'
acceptLanguage.languages(['en', 'fr', 'ar'])
export function getLocaleFromHeaders(request: Request): string {
const acceptLang = request.headers.get('accept-language') || ''
return acceptLanguage.parse(acceptLang)[0] || 'en'
}
9. Lazy Loading
i18n.use(initReactI18next).init({
resources: {}, // start empty
partialBundledLanguages: true,
})
// Load namespace on demand
async function loadNamespace(locale: string, ns: string) {
const resources = await import(`./locales/${locale}/${ns}.json`)
i18n.addResourceBundle(locale, ns, resources)
}
10. Testing with i18n
import { render } from '@testing-library/react'
import { I18nextProvider } from 'react-i18next'
import i18n from './i18n-test-config' // test-only config
function renderWithI18n(ui: React.ReactElement) {
return render(<I18nextProvider i18n={i18n}>{ui}</I18nextProvider>)
}
Rules
- Never embed user-facing strings directly in components — always use translation keys.
- Translation keys follow a namespaced dot notation:
namespace.key.subkey. - Use ICU message format for pluralization, select, and ordinal rules.
- Never use CSS
left/rightproperties — use CSS logical properties (inline-start/inline-end). - HTML dir attribute and lang attribute are always in sync with current locale.
- Lazy-load translation files — never bundle all locales in the main chunk.
- Fallback chain always configured: target locale → fallback locale → key itself.
- Locale detection never relies solely on browser
navigator.language— persist user choice. - Numbers, dates, and currencies are formatted with
Intl— never hardcoded. - Translation interpolation escapes HTML by default to prevent XSS.
References
references/i18n-libraries.md— i18next, react-intl, vue-i18n, FormatJS, Angular i18n, setup patterns, lazy loadingreferences/rtl-support.md— CSS logical properties, dir attribute, RTL testing, layout adaptation
Handoff
No artifact produced unless requested.
Next skill: frontend-accessibility — RTL a11y overlaps with i18n, pass locale direction config.
Carry forward: locale list, RTL requirement, i18n library, lazy loading strategy.
Source: j4flmao/agent-skills — distributed by TomeVault.