Part 7 · 2 chapters · ~15 min

Internationalisation

What a locale decides and the Intl API that already knows: numbers, money as minor units, dates as instants, plural categories, ICU messages and collation; then right-to-left with logical properties and mirroring rules, and the extraction, translation and gating pipeline that ships languages without a release each.

15

Locale, Intl and plurals

refuse to hand-format anything
  1. Numbers through Intl.NumberFormat: grouping, decimals, digits, compact and units differ by locale.
  2. Money as integer minor units plus an ISO currency code; the locale decides presentation only.
  3. Dates: store UTC instants, carry the time zone, format at the edge; calendar dates are not instants.
  4. Plurals through Intl.PluralRules: two forms in English, six in Arabic, one in Yoruba.
  5. Messages as whole ICU sentences with branches; never concatenate fragments.
  6. Collation and length: Intl.Collator for sort and search; layouts that flex 30 to 40%.
code
const n = 1234567.891;
new Intl.NumberFormat('en-IN').format(n);            // "12,34,567.891"
new Intl.NumberFormat('en-NG', { style: 'currency', currency: 'NGN' }).format(150000 / 100);
                                                     // "₦1,500.00"
new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium', timeZone: 'Africa/Lagos' })
  .format(new Date('2026-10-06T09:00:00Z'));          // "6 Oct 2026"
new Intl.RelativeTimeFormat('fr', { numeric: 'auto' }).format(-1, 'day'); // "hier"
new Intl.PluralRules('ar').select(3);                // "few"
['Äpfel', 'Zebra', 'Apfel'].sort(new Intl.Collator('sv').compare);
                                                     // ["Apfel", "Zebra", "Äpfel"]

// ICU message: one sentence, the translator reorders freely
// "{count, plural, =0 {No new messages} one {# new message} other {# new messages}}"
the Nigerian case
An en-NG product still meets plurals and money rules: naira with kobo minor units, the ₦ symbol, Africa/Lagos time, and, if Yoruba, Hausa or Igbo are added, three new plural rule sets and longer strings. Building on Intl from day one is what makes the second locale a translation job rather than a rewrite.
LOCALE, INTL AND PLURALS
one value, many correct renderings: what a locale decides and the built-in API that already knows
swipe the figure sideways, or tap expand for full screen
1/6
numbers
Numbers: 1234567.891 is "1,234,567.891" in en-US, "1.234.567,891" in de-DE, "12,34,567.891" in en-IN (lakh grouping), "١٬٢٣٤٬٥٦٧٫٨٩١" in ar-EG with Arabic-Indic digits. Intl.NumberFormat(locale).format(n) gets every one right; toFixed and a regex for commas get exactly one right. Compact ("1.2M", "12 lakh"), percent and units ("5 km/h") are options on the same formatter.
16

RTL and the translation pipeline

mirroring for free, translating without a release per language
  1. lang and dir on the root from the locale; dir="auto" or <bdi> for user content.
  2. Logical properties everywhere for inline layout, enforced by lint.
  3. Mirroring rules decided once in the design system: directional icons flip; time, media, numbers and logos do not.
  4. Extraction with stable ids and descriptions; keys are ids, not English.
  5. The loop through a TMS with context; one lazily loaded catalogue chunk per locale.
  6. Gates: pseudo-localisation, missing keys, placeholder parity, ICU syntax, visual tests in ar and de.
code
/* physical: breaks in RTL */          /* logical: mirrors with dir */
.card { margin-left: 16px;            .card { margin-inline-start: 16px;
        padding-right: 8px;                   padding-inline-end: 8px;
        text-align: left;                     text-align: start;
        border-left: 3px solid; }             border-inline-start: 3px solid; }

/* the icon component, once */
[dir="rtl"] .icon[data-directional] { transform: scaleX(-1); }
the exercise
Set dir="rtl" on your app's root in DevTools and click through the main flow. Every element that did not move is a physical property; every arrow pointing the wrong way is a missing directional flag. Then turn on pseudo-localisation and count the strings that stay plain.
RTL AND THE TRANSLATION PIPELINE
mirroring a layout with logical properties, and getting strings from code to translators and back without a release per language
swipe the figure sideways, or tap expand for full screen
1/6
dir and lang
dir and lang: sets the base direction for the document; the browser flips the inline axis, text alignment defaults to the start, and the bidi algorithm orders mixed text. Set both from the active locale, on the root, and on any element whose content is in a different direction (a user name in Hebrew inside English UI: dir="auto" or ).