0) Problem Restatement
Adobe asked: design a localization system for a product released in dozens of countries. Every user-facing string must resolve by locale at runtime, support variables ("Hello, {name}"), follow language-specific plural rules ("1 file" / "2 files", and languages with more plural forms like Polish or Arabic), fall back safely when a translation is missing, and allow updating translations without redeploying the app.
1) Core Concepts
- Message keys: code never contains English text directly. It uses keys:
t("files.deleted", { count: 3 }). - Catalogs: per locale, a map
key → message:
en: files.deleted = "{count, plural, one {# file deleted} other {# files deleted}}"
pl: files.deleted = "{count, plural, one {Usunięto # plik} few {Usunięto # pliki} many {Usunięto # plików} other {Usunięto # pliku}}"
These use ICU MessageFormat, the standard syntax for variables, plurals, gender and selects. The plural categories (one, few, many, other) come from CLDR data per language.
- Formatting: dates, numbers and currencies use locale-aware formatters (
Intl.DateTimeFormat,Intl.NumberFormat), never string concatenation.
2) Fallback Chain
When a key is missing in the user's locale, try: fr-CA → fr → en (the default). If it's still missing (a bug), show the key or the English text and log it (monitoring missing translations), never a blank.
3) Architecture
Architecture Diagram
flowchart LR
DEV["Developers - add keys in code"] --> EXT["Extraction in CI - new/changed keys"]
EXT --> TMS["Translation management system"]
TMS --> TR["Translators / vendors + review"]
TR --> TMS
TMS -->|"publish"| BLD["Bundle builder - per locale, validated"]
BLD --> CDN[("CDN - versioned bundles")]
APP["Apps - web, mobile, desktop"] -->|"fetch bundle for locale"| CDN
APP --> LIB["i18n runtime - ICU format + fallback"]- Extraction: CI scans the code for keys and source strings, and sends new or changed ones (with context screenshots and comments) to the translation management system.
- Bundles: per locale (and per app module, to keep them small), with a content hash in the name. The published manifest lists the latest bundle per locale.
- Runtime loading: the app ships with a built-in default bundle (so it works offline and at first launch), then fetches the latest bundle version from the CDN, caches it, and uses it on the next screen or launch. That's how translations update without redeploying.
- Validation before publish: ICU syntax is valid, variables match the source (a translation mustn't drop
{name}), and length limits are respected for UI elements.
4) Quality and UX
- Pseudo-localization in testing: replace strings with accented, longer text ("[Ŝéţţîñĝš !!!]") to catch hard-coded strings and layout overflow early.
- Right-to-left languages (Arabic, Hebrew): mirror the layouts (CSS logical properties), and test them.
- Locale detection: the user's setting first, then the OS or browser language, then region. Keep the locale separate from the country (prices and legal content may depend on the country).
- Performance: load only the needed locale and modules, and cache compiled message formatters.
5) Wrap-Up
Replace UI text with message keys and resolve them at runtime from per-locale catalogs written in ICU MessageFormat (variables, CLDR plural and gender rules), with locale-aware date and number formatting and a fr-CA → fr → en fallback chain that logs misses. Run a CI extraction to translation management to validated bundle pipeline, publish versioned bundles to a CDN that apps fetch and cache (with a built-in default), and ensure quality with pseudo-localization, RTL support and variable checks.