{
"cart.items": "{count, plural, =0 {Your cart is empty} one {# item} other {# items}}",
"greeting": "Hello, {name}!"
}
Keep all text in translation catalogues using ICU messages, lazy load each language, format with Intl, and use CSS logical properties for RTL.
Messages: all user-facing strings live in catalogues, keyed by id. They use ICU MessageFormat, a standard syntax for messages with variables. It handles plurals, gender and select rules per language. Never build strings by concatenation. Translators work in a translation management system. CI (the automatic checks on each change) extracts new keys. It fails when keys are missing for required locales (languages and regions).
Loading: each locale's catalogue is split per route or feature and lazy loaded. So users download only their own language. The locale is chosen in this order:
- the URL path, which is good for SEO and sharing
- then a saved preference
- then the
Accept-Languageheader
The page is rendered on the server in that locale, to avoid a flash of the wrong language.
Formatting: dates, numbers, currencies, relative times and lists use the Intl APIs with the active locale. Time zones are explicit.
RTL (right-to-left languages like Arabic): set dir on html. Use CSS logical properties, such as margin-inline-start and inset-inline-end, so layouts mirror automatically. Directional icons, like arrows, flip. Logos and media do not.
Layout: allow for text that grows 30 to 40 percent longer after translation. Test with pseudo-localisation. It replaces the text with longer, accented fake text, to find layout bugs early.
- Use ICU messages with plural rules, never concatenation.
- Lazy load one catalogue per locale.
- Put the locale in the URL and render it on the server.
- Use
Intlfor dates, numbers and currency. - Use the
dirattribute and CSS logical properties for RTL.
- MDN: Intl (https://developer.mozilla.org/en-US/docs/Web/API/Intl)developer.mozilla.org