--- name: i18n-localization description: Internationalization and localization for global applications. Use when adding multi-language support, handling regional formats, or preparing apps for global markets. --- # Internationalization & Localization Comprehensive guide for building globally-ready applications. ## Key Concepts ### Terminology ``` i18n (Internationalization): - Engineering for multiple languages/regions - Building the infrastructure - Happens once, during development L10n (Localization): - Adapting for specific locale - Translation, formatting, content - Happens per locale, ongoing Locale: - Language + Region combination - Format: language-REGION (e.g., en-US, pt-BR) - Determines formatting rules ``` ### What Needs Localization? | Category | Examples | | ------------- | ---------------------------- | | **Text** | UI labels, messages, errors | | **Numbers** | 1,234.56 vs 1.234,56 | | **Dates** | MM/DD/YYYY vs DD/MM/YYYY | | **Times** | 12-hour vs 24-hour | | **Currency** | $1,234.56 vs €1.234,56 | | **Plurals** | 1 item vs 2 items vs 5 items | | **Direction** | LTR vs RTL | | **Images** | Cultural appropriateness | | **Colors** | Cultural significance | | **Names** | Order, formality | --- ## Text & Messages ### Externalize All Strings ```typescript // DON'T: Hardcoded strings const message = "Welcome back, " + username; // DO: Externalized, translatable const message = t('welcome_back', { name: username }); // Translation file (en.json) { "welcome_back": "Welcome back, {{name}}" } // Translation file (es.json) { "welcome_back": "Bienvenido de nuevo, {{name}}" } ``` ### Message Format ```typescript // ICU Message Format (recommended) // Handles plurals, select, dates, numbers // Simple "greeting": "Hello, {name}!" // Plural "items_count": "{count, plural, =0 {No items} one {# item} other {# items}}" // Select (gender, etc.) "notification": "{gender, select, male {He} female {She} other {They}} liked your post" // Nested "cart": "{itemCount, plural, =0 {Your cart is empty} one {You have # item in your cart} other {You have # items in your cart} }" ``` ### Pluralization ``` Languages have different plural rules: English: 1 (one), 2+ (other) French: 0-1 (one), 2+ (other) Russian: Complex rules for 1, 2-4, 5-20, 21, etc. Arabic: 6 plural forms! Chinese: No plural forms Always use library pluralization, never DIY: - react-intl / formatjs - i18next - Intl.PluralRules API ``` --- ## Date & Time ### JavaScript Intl API ```typescript // Date formatting const date = new Date("2024-03-15"); new Intl.DateTimeFormat("en-US").format(date); // "3/15/2024" new Intl.DateTimeFormat("de-DE").format(date); // "15.3.2024" new Intl.DateTimeFormat("ja-JP").format(date); // "2024/3/15" // With options new Intl.DateTimeFormat("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", }).format(date); // "Friday, March 15, 2024" ``` ### Time Zones ```typescript // Always store in UTC const utcDate = new Date().toISOString(); // "2024-03-15T14:30:00.000Z" // Display in user's timezone new Intl.DateTimeFormat("en-US", { timeZone: "America/New_York", dateStyle: "full", timeStyle: "long", }).format(new Date(utcDate)); // "Friday, March 15, 2024 at 10:30:00 AM EDT" // Get user's timezone const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; ``` ### Relative Time ```typescript const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); rtf.format(-1, "day"); // "yesterday" rtf.format(1, "day"); // "tomorrow" rtf.format(-3, "hour"); // "3 hours ago" rtf.format(2, "week"); // "in 2 weeks" // For automatic unit selection, use a library like date-fns import { formatDistanceToNow } from "date-fns"; formatDistanceToNow(date, { addSuffix: true }); // "3 days ago" ``` --- ## Numbers & Currency ### Number Formatting ```typescript const number = 1234567.89; new Intl.NumberFormat("en-US").format(number); // "1,234,567.89" new Intl.NumberFormat("de-DE").format(number); // "1.234.567,89" new Intl.NumberFormat("fr-FR").format(number); // "1 234 567,89" // Percentages new Intl.NumberFormat("en-US", { style: "percent", minimumFractionDigits: 1, }).format(0.256); // "25.6%" ``` ### Currency Formatting ```typescript const amount = 1234.56; new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(amount); // "$1,234.56" new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR", }).format(amount); // "1.234,56 €" new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY", }).format(amount); // "¥1,235" (no decimal for JPY) ``` ### Currency Best Practices ```typescript // Store amount + currency code interface Money { amount: number; // In smallest unit (cents) currency: string; // ISO 4217 code (USD, EUR, etc.) } // Format for display function formatMoney(money: Money, locale: string): string { return new Intl.NumberFormat(locale, { style: "currency", currency: money.currency, }).format(money.amount / 100); // Convert cents to units } // Handle exchange rates on backend // Never convert currencies client-side ``` --- ## RTL (Right-to-Left) Support ### RTL Languages ``` RTL Languages: - Arabic (ar) - Hebrew (he) - Persian/Farsi (fa) - Urdu (ur) Layout considerations: ┌─────────────────────────────┐ ┌─────────────────────────────┐ │ Logo Menu Settings │ │ Settings Menu Logo │ │ ← Back │ │ Back → │ │ [Icon] Label │ │ Label [Icon] │ │ Next → │ │ ← Next │ └─────────────────────────────┘ └─────────────────────────────┘ LTR Layout RTL Layout ``` ### CSS for RTL ```css /* Use logical properties */ /* DON'T: Physical properties */ .element { margin-left: 10px; padding-right: 20px; text-align: left; float: left; } /* DO: Logical properties */ .element { margin-inline-start: 10px; padding-inline-end: 20px; text-align: start; float: inline-start; } /* Set direction based on locale */ html[dir="rtl"] { direction: rtl; } /* Or use :dir() pseudo-class */ .icon:dir(rtl) { transform: scaleX(-1); /* Mirror icons */ } /* Flexbox auto-reverses in RTL */ .container { display: flex; /* No need to change for RTL */ } ``` ### HTML Direction ```html
The word مرحبا means "hello".
``` --- ## Implementation (React) ### react-intl Setup ```typescript // src/i18n/index.ts import { createIntl, createIntlCache } from '@formatjs/intl'; const cache = createIntlCache(); const messages = { en: () => import('./locales/en.json'), es: () => import('./locales/es.json'), fr: () => import('./locales/fr.json'), }; export async function loadMessages(locale: string) { const loader = messages[locale] || messages.en; return (await loader()).default; } // App.tsx import { IntlProvider } from 'react-intl'; function App() { const [locale, setLocale] = useState('en'); const [messages, setMessages] = useState({}); useEffect(() => { loadMessages(locale).then(setMessages); }, [locale]); return (