Designing software so it can ship in any language, locale, currency, and writing direction without code changes. The hardest part isn't translation — it's everything around it: dates, numbers, plurals, sort order, layout direction.
← Back to Client Sideen-US, pt-BR, zh-Hant-TW. Drives all formatting decisions.checkout.placeOrder); a catalog maps it to text per locale.new Date().toLocaleString(), every fixed-width layout.UI labels, microcopy, errors, emails. The visible part — but only ~30% of the work.
1,234.56 (en-US) vs 1.234,56 (de-DE) vs 1 234,56 (fr-FR). Currency symbol position varies.
MM/DD/YYYY vs DD/MM/YYYY vs YYYY-MM-DD. 12h vs 24h. Different calendars (Gregorian, Hijri, Japanese era).
English has 2 forms (1, other). Russian has 4 (1, few, many, other). Arabic has 6.
Locale-aware collation. ä sorts after z in Swedish, with a in German.
RTL languages flip the entire UI — navigation, icons, progress bars, animations.
Family-name-first (Japan, Hungary). Postal-code formats. State/province vs prefecture vs canton.
Metric vs imperial. Culturally appropriate icons and stock photos. Avoid hand gestures and color taboos.
t('checkout.placeOrder') call into a source catalog (usually JSON or PO).réspéçt mode) and longest-string tests catch overflow.A pattern language Unicode standardized for handling plurals, gender, and selects without if-statements in code.
{count, plural,
=0 {No messages}
one {# new message}
other {# new messages}
}
Translators fill in each plural category for their language without the developer caring how many categories that is.
Modern JavaScript exposes ECMAScript Internationalization APIs natively. Use them before reaching for a library.
Intl.NumberFormat — currencies, units, scientific.Intl.DateTimeFormat — dates, times, calendars.Intl.RelativeTimeFormat — "3 days ago", "in 5 minutes".Intl.PluralRules — plural categories per locale.Intl.Collator — locale-aware sort & compare.Intl.ListFormat — "A, B, and C" / "A, B et C".Intl.Segmenter — word/sentence/grapheme boundaries (essential for CJK).<html dir="rtl"> — the browser flips inline direction automatically.margin-inline-start not margin-left; padding-block not padding-top).‎, ‏) to render correctly./en/about, /de/about) — most common; SEO-friendly.de.example.com) — clean separation; harder for SEO consolidation..de, .fr) — strongest local SEO signal; expensive to manage.hreflang tags so Google serves the right locale per region.Accept-Language header — never solely from IP geolocation.A TMS keeps a translation memory (every prior translation) and a glossary (brand terms, product names that must stay untranslated). New strings are pre-filled from memory; reviewers confirm. This is what keeps cost predictable as the catalog grows.
Modern TMSes route strings through GPT/Claude with the glossary, brand voice, and surrounding context as the system prompt. Output goes to a human reviewer for high-visibility surfaces (legal, marketing) and ships unchecked for low-stakes microcopy. Cost per word has dropped roughly 10× since 2023.
| Tool | Layer | Sweet spot |
|---|---|---|
| FormatJS / react-intl | Library | ICU MessageFormat in React; the most rigorous option. |
| i18next | Library | Framework-agnostic; huge plugin ecosystem; the JS default. |
| LinguiJS | Library | Macro-based extraction; great DX with TypeScript. |
| Vue i18n | Library | Official solution for the Vue ecosystem. |
| next-intl / next-i18next | Library | Next.js App Router-aware i18n. |
| Angular i18n / @angular/localize | Built-in | Angular's own pipeline, AOT-friendly. |
| Lokalise / Phrase / Crowdin | TMS | Hosted translation management, integrations with Git/CI. |
| Transifex / Smartling | TMS | Enterprise-grade workflows, big translator marketplaces. |
| Globalize, date-fns/locale, Luxon | Formatting | For projects targeting older browsers without full Intl. |
Use Intl.* APIs from day one. Extract strings into a catalog file even if there's only one. The retrofit cost is huge.
i18next or react-intl + a TMS like Lokalise. JSON catalogs in the repo, sync via CI.
FormatJS for ICU rigor, dedicated TMS, dedicated localization PM, pseudo-loc in CI, LLM pre-translation.
Logical CSS properties everywhere, mirrored design system, RTL visual regression tests.
"You have " + n + " items" — impossible to translate cleanly. Use a single message with a placeholder.n === 1 ? 'item' : 'items' breaks in Russian, Arabic, Polish.Intl.DateTimeFormat.Asia/Tokyo is not the same as ja-JP."👨👩👧".length === 8; use Intl.Segmenter.