Part V β The Craft Β· Lesson 10.6
Multi Language Support
In one lineKeys not sentences, one locale bundle at a time, and a layout that survives text growing 35%.
Where you've seen it"Zahlungsmethode hinzufΓΌgen" overflowing a button sized for "Add payment".
Diagram
THE PIPELINE
source code runtime
ββββββββββββββββββββ ββββββββββββββββββββββββββ
β t('cart.empty') β β detect locale β
ββββββββββ¬ββββββββββ β URL /de/ > cookie > β
β extract β Accept-Language β
βΌ βββββββββββββ¬βββββββββββββ
ββββββββββββββββββββ β lazy-load
β en.json (source) β βΌ
β de.json β TMS β ββββββββββββββββββββββββββ
β hi.json β TMS β ββββββββββΊ β de.json only (~20KB) β
ββββββββββββββββββββ β NOT all 30 locales β
βββββββββββββ¬βββββββββββββ
βΌ
t('cart.empty')
β "Ihr Warenkorb ist leer"WHAT CHANGES BESIDES THE WORDS
text length EN β DE +35%, EN β FI +40% β never fix widths
direction ar, he, fa β RTL β logical CSS properties
numbers 1,234.56 vs 1.234,56 vs 1,23,456 (en-IN)
dates 03/04 = Mar 4 (US) or 3 Apr (most of the world)
currency βΉ1,234 $1,234.00 1.234,00 β¬
sorting Γ€ sorts differently per locale
names/addresses field order and required fields differ
images/icons hand gestures, colours, flags β languagesPLURALS AND INTERPOLATION β never concatenate
β t('you have') + ' ' + n + ' ' + t('items')
word order is language-specific; this breaks everywhere
β t('cart.count', { count: n })
en: { one: "{{count}} item", other: "{{count}} items" }
pl: { one: β¦, few: β¦, many: β¦, other: β¦ } β 4 forms
ar: 6 formsHow it works
- Key by meaning (
cart.empty), never by the English sentence β source text changes, keys shouldn't. - Lazy-load one locale bundle per session and split large namespaces per route (5.7).
- Use
Intlfor dates, numbers, currency, plural rules, and relative time β it's built in and correct. - Use logical CSS (
margin-inline-start, notmargin-left) so RTL works by settingdir="rtl"rather than rewriting styles. - Set
langanddiron<html>β screen readers switch pronunciation on it (8.3).
// interpolation + plurals handled by the library
<p>{t('cart.count', { count: items.length })}</p>
// formatting handled by the platform
new Intl.NumberFormat(locale, { style: 'currency', currency: 'INR' })
.format(1234.5); // "βΉ1,234.50"Trade-offs
| Decision | Consequence |
|---|---|
| Lazy-load per locale | Small bundles; a flash before strings load |
| Ship all locales | No flash; large bundle |
| Server-render the locale | Best LCP + SEO per language |
| Translate at build time | Fast, but a release per copy change |
Pseudo-localisation is the cheap test: render [!!! Δ€Γ©ΔΌΔΌΓΆ Ε΄ΓΆΕΔΌΓ° !!!] β 40% longer, accented, bracketed. It surfaces hardcoded strings and fragile layouts before any translator is paid.
Interview angle
"How would you add multi-language support to an existing app?"
- Extract to keys with a source bundle, wire
Intlfor all formatting, and lazy-load one locale at a time. - Fix layout for text expansion and switch to logical CSS properties so RTL is a
dirattribute, not a second stylesheet. - Decide URL strategy early β
/de/pathis best for SEO and shareability, and it makes locale a routing concern rather than hidden state.
Recap
- Keys by meaning, plurals by library, formatting by
Intl. - Design for +35% text and RTL from the start; retrofitting either is expensive.
- Pseudo-localise early β it finds the bugs before translation does.