Internationalization (i18n) and Localization (l10n) for Global Products

Architecting Next.js and React Native apps to support multiple languages, currencies, and cultural conventions at scale.
Building for a global audience is one of the highest-leverage growth strategies available to software products. The English-speaking market represents less than 25% of internet users. Products that localize for Spanish, Mandarin, Hindi, Portuguese, or Arabic open dramatically larger addressable markets. But internationalization (i18n) must be architected from the start—retrofitting an English-only codebase for multiple languages is painful and expensive. ## i18n vs l10n: Understanding the Distinction **Internationalization (i18n)** is making your code capable of supporting multiple languages and cultural conventions—separating text from code, handling bidirectional text, respecting locale-specific date and number formats. **Localization (l10n)** is the process of adapting the internationalized product for a specific locale: translating strings, adapting imagery, adjusting legal text, and ensuring cultural appropriateness. You implement i18n once; l10n is a continuous process as you add and maintain locale support. ## Setting Up i18n in Next.js Next.js has built-in i18n routing that handles locale detection and URL structure: ```javascript // next.config.js module.exports = { i18n: { locales: ['en', 'es', 'fr', 'de', 'ja', 'ar'], defaultLocale: 'en', localeDetection: true, }, }; ``` With this configuration, Next.js automatically routes `/es/about` to the Spanish version of the about page and `/ja/about` to the Japanese version. The `useRouter().locale` hook provides the current locale in components. For string management, `next-intl` is the most ergonomic library for Next.js, supporting server components, client components, and the App Router: ```typescript // messages/en.json { "home": { "hero_title": "Build products people love", "hero_subtitle": "Forgeora helps teams ship faster.", "cta": "Get started for free" } } // Component import { useTranslations } from 'next-intl'; export function HeroSection() { const t = useTranslations('home'); return ( <section> <h1>{t('hero_title')}</h1> <p>{t('hero_subtitle')}</p> <button>{t('cta')}</button> </section> ); } ``` ## Handling Pluralization and Interpolation Different languages have different pluralization rules. English has singular/plural. Arabic has six forms. Russian has four. Use ICU message format for pluralization: ```json { "items_count": "{count, plural, =0 {No items} one {# item} other {# items}}" } ``` For string interpolation (inserting variables into translated strings), always use placeholder syntax rather than string concatenation. Never do `t('hello') + username`—word order differs between languages and concatenation produces grammatically incorrect results. ## Date, Time, Number, and Currency Formatting Use the `Intl` API for locale-aware formatting—never hardcode date or number formats: ```typescript // Date formatting const date = new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long', day: 'numeric' }).format(new Date()); // en-US: "July 28, 2025" // de-DE: "28. Juli 2025" // ja-JP: "2025年7月28日" // Number formatting const price = new Intl.NumberFormat(locale, { style: 'currency', currency: currencyCode, }).format(amount); // en-US: "$1,234.56" // de-DE: "1.234,56 €" // ja-JP: "¥1,235" ``` ## Bidirectional Text (RTL Support) Arabic, Hebrew, Persian, and Urdu are written right-to-left. RTL support requires: **CSS logical properties**: Use `margin-inline-start` instead of `margin-left`, `padding-inline-end` instead of `padding-right`. Logical properties automatically flip in RTL without separate CSS overrides. **`dir="rtl"` on the HTML element**: Setting `dir="rtl"` on `<html>` (or a container element) flips block layout, text alignment, and many UI conventions. The browser handles most of this automatically. **Bidirectional text (BIDI)**: When mixing LTR and RTL text in the same string (e.g., an Arabic sentence containing an English product name), use Unicode BIDI control characters or the `<bdi>` HTML element to ensure correct rendering. **Mirror icons**: Directional icons (arrows, chevrons, forward/back navigation) should be mirrored in RTL layouts. Non-directional icons (magnifying glass, heart, settings) should not. ## Translation Workflow Managing translations for multiple languages requires a workflow: 1. **Extract strings**: Tools like `i18next-parser` or `next-intl`'s extraction script automatically find all translation keys used in your code and generate source JSON files. 2. **Translation management platform**: Use Crowdin, Lokalise, or Phrase to manage translations. These platforms provide translation memory (reuse of previously translated strings), machine translation suggestions, and glossaries for consistent terminology. 3. **Automated synchronization**: Connect your repository to the TMS via CI. New strings are pushed to the platform; completed translations are pulled back via PR. 4. **Pseudo-localization**: Before real translations, test with pseudo-localized strings that simulate double-byte characters, accented characters, and 40% longer strings (German and Finnish text is often significantly longer than English equivalents). This reveals truncation bugs and layout issues before they reach production. ## React Native i18n For React Native, `react-i18next` or `expo-localization` + `i18next` provide similar capabilities. Access the device locale via `Localization.locale` (Expo) and use it to determine the active language. Platform-specific formatting uses the same `Intl` API available in the Hermes runtime.
