Internationalisation & RTL
In one line
Internationalisation is mostly the parts that are not translation — formatting, plural rules, layout direction, and leaving room for text that is 30% longer.
What it is
The platform does formatting. Intl.NumberFormat handles currency, grouping separators and decimal marks; Intl.DateTimeFormat handles date order, month names and calendars; Intl.RelativeTimeFormat produces "3 days ago" correctly per locale; Intl.PluralRules and Intl.ListFormat cover the grammar. Hand-rolled formatting is both wrong and unnecessary — n === 1 ? 'item' : 'items' breaks in every language with more than two plural categories, and there are several.
Interpolation must not be concatenation. "You have " + n + " messages" cannot be translated, because word order differs. Use a message format with named placeholders — ICU MessageFormat is the standard — so translators can reorder and can express plural and gender selection.
RTL is a layout concern, not a text one. Arabic, Hebrew, Persian and Urdu mirror the entire interface: navigation, icons that imply direction, progress bars, sliders. CSS logical properties are the mechanism — margin-inline-start rather than margin-left, padding-block, inset-inline, text-align: start — so one stylesheet works in both directions. dir="rtl" on the root then flips everything. Icons that indicate direction (back arrows, chevrons) need mirroring; icons that do not (a clock, a checkmark) must not be.
Text expansion is a design constraint. German runs roughly 30% longer than English, and some languages more. A layout designed to exactly fit its English label will break, so components need to tolerate wrapping and avoid fixed widths on text containers. Pseudo-localisation — rendering with accented, lengthened placeholder text — surfaces those failures before translation exists.
Typography varies too: line height that suits Latin is too tight for Thai or Devanagari, CJK does not word-wrap the same way, and font stacks need per-script fallbacks.
Then the operational parts. Locale detection should follow Accept-Language or a stored preference, with an explicit switcher, and locale belongs in the URL for shareability and SEO. Load only the active locale's messages — shipping every language to every user is a bundle problem that grows linearly. And date and number input are as locale-dependent as output: a date picker assuming month/day order will collect wrong data.
Why it matters
The target market here is US and Europe, where multilingual is the default expectation rather than a later phase, and RTL support appears in JDs for products with Middle East reach.
Retrofitting is expensive: hard-coded strings, concatenated sentences, and physical CSS properties are spread through everything by the time anyone asks.
Key points
- Use the
IntlAPIs for dates, numbers, currency, relative time, plurals and lists. - Never concatenate sentences — use ICU-style messages with named placeholders.
- Logical properties make one stylesheet work in both directions;
dir="rtl"does the rest. - Mirror directional icons only; leave non-directional ones alone.
- Expect ~30% text expansion and test with pseudo-localisation before translations exist.
- Line height, wrapping, and font fallbacks vary by script.
- Detect locale from headers or preference, put it in the URL, and load only the active locale's messages.