Skip to content
fearchitect
Quality, Observability & Cross-Cutting

Internationalization (i18n)

Ship UI that works correctly in any locale without code changes.

By Abas TurabliReviewed

Summary

Internationalization separates locale-dependent content — strings, numbers, dates, plurals — from application logic so the same code path serves every market. The `Intl` API handles formatting natively; ICU MessageFormat handles plurals and gender; CSS logical properties handle RTL. Getting this wrong at the data layer causes string-concatenation bugs that are expensive to fix late.

Jump to the interview angle

Internationalization (i18n)

Internationalization means engineering an application so it can be adapted to any locale without source changes. Localization (l10n) is the act of adding a specific language.

The most common mistake is string concatenation: "You have " + count + " items" breaks the moment word order, plural rules, or grammatical gender differs — which is most languages. The fix is a message format that expresses the full sentence as a single translatable unit with embedded slots.

The browser's built-in Intl API handles numbers, dates, relative times, and plural categories. ICU MessageFormat (used by react-intl, @formatjs/intl, and Lit's msg) handles sentence-level messages with placeholders, select, and plural built in.

Rules that prevent i18n defects

  • Never concatenate translated strings — one message, one translatable unit.
  • Use ICU MessageFormat `{count, plural, one {# item} other {# items}}` for any quantity.
  • Format numbers and dates with `Intl.NumberFormat` and `Intl.DateTimeFormat` — never `toLocaleString()` without a locale argument.
  • Locale routing: serve `en-US` vs `fr-FR` via URL prefix (`/en/`) or `Accept-Language` negotiation at the edge.
  • RTL support requires `dir="rtl"` on the root element and CSS logical properties (`margin-inline-start`) instead of physical ones (`margin-left`).
  • Run pseudo-localization in CI — expands strings ~40% and adds RTL markers — to catch layout breaks before translation exists.

Intl API and ICU MessageFormat

Three Intl constructors cover the most common formatting needs. ICU MessageFormat (via @formatjs/intl) handles plurals and select — both compile to locale-aware output without any string concatenation.

Intl formatters and ICU plural messagets
// --- Intl: number, date, relative time, plural category ---
const locale = "de-DE";

const price = new Intl.NumberFormat(locale, {
  style: "currency",
  currency: "EUR",
}).format(1234.5);
// "1.234,50 €"

const date = new Intl.DateTimeFormat(locale, {
  dateStyle: "long",
}).format(new Date("2026-06-21"));
// "21. Juni 2026"

const rel = new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(
  -1,
  "day",
);
// "gestern"

// Which plural category does a number fall into for this locale?
const rules = new Intl.PluralRules("ar"); // Arabic has 6 plural forms
console.log(rules.select(2)); // "two"
console.log(rules.select(11)); // "many"

// --- ICU MessageFormat via @formatjs/intl ---
// Message defined in en.json:
// "cart": "{count, plural, =0 {No items} one {# item} other {# items}}"
//
// At runtime (react-intl / @formatjs/intl):
// intl.formatMessage({ id: "cart" }, { count: 3 })
// → "3 items"
//
// German translator writes:
// "cart": "{count, plural, one {# Artikel} other {# Artikel}}"
// Same call, correct output in every locale.

Pass the locale explicitly to every Intl constructor — never rely on toLocaleString() without one. ICU plural selects the right form per locale automatically; the translator owns the whole sentence.

RTL and CSS logical properties

Setting dir="rtl" on <html> flips text direction but physical CSS properties (margin-left, padding-right, border-left) do not flip. Replace every physical side property with its logical equivalent: margin-inline-start, padding-inline-end, border-inline-start. Logical properties are supported in all modern browsers and cost nothing at runtime.

Interview angle

Interviewers test whether you know why concatenation fails and how ICU MessageFormat fixes it. Know the Intl API surface and locale routing approaches.

Soundbite: "Concatenated translations break word order and plurals — one ICU message per sentence, formatted by Intl, is the only safe model."

Key terms

ICU MessageFormat
A message syntax with `{var}`, `{count, plural, …}`, and `{gender, select, …}` — one translatable unit per sentence.
Intl API
Browser-native namespace: `NumberFormat`, `DateTimeFormat`, `RelativeTimeFormat`, `PluralRules`, `Collator`, and more.
locale routing
Serving locale-specific content via URL prefix (`/fr/`) or `Accept-Language` header negotiation at the edge.
CSS logical properties
Flow-relative CSS like `margin-inline-start` that automatically mirrors for RTL without extra overrides.
pseudo-localization
Replacing strings with expanded, accented variants in CI to surface layout bugs before real translations exist.

Further reading

Search fearchitect

Jump to a topic, mode, or action.