A product that supports one language has strings. A product that supports ten has a supply chain. I’ve worked on a centralised translation service shared by many front ends, and on products with right-to-left scripts and per-market formatting, and the lesson is the same each time: the problems aren’t linguistic, they’re engineering.
Keys, not sentences
Code should reference transfer.review.fee_label, never the English sentence. Sentences change; keys are stable. Keys also carry context for translators — the same English word (“Send”, “Order”, “Close”) often needs different words in other languages depending on where it appears. A key per use, with a description and a screenshot, saves more translator time than any tool.
Plurals and grammar belong in the message
count + " items" breaks in most languages. English has two plural forms; Arabic has six (zero, one, two, few, many, other); some languages have none. Gender and case change words around the number too. Use ICU MessageFormat or an equivalent, so the translator controls the whole sentence:
{count, plural,
=0 {No transfers yet}
one {# transfer}
other {# transfers}
}
Never build sentences by concatenating fragments. Word order is the translator’s job, not the template’s.
Fallbacks are a chain, and they should be visible
Requests ask for ar-AE; you have ar; you definitely have en. Resolve along a declared chain per locale, at build or load time, and record when a fallback was used. A silent fallback to English is how a market launches half-translated and nobody notices until customers do.
Version translations with the release
If translations live in a database that anyone can edit at any time, the text in production is not the text you tested. Treat translation bundles as artifacts: pulled from the translation system at build, versioned with the release, cached at the edge with long TTLs, and rolled back with the code. Hot-fixing a typo is a release, just a small one — and it can be automated.
Test what humans won’t
- Pseudo-localisation in CI: render every screen with expanded, accented strings (
[!! Šéñd ţŕåñšƒéŕ !!]). It instantly reveals truncation, hard-coded strings that didn’t go through the system, and layouts that assume English lengths. - Right-to-left is not a mirror flag. Use logical CSS properties (
margin-inline-start, notmargin-left), check icons that imply direction, and keep numbers, phone numbers and code left-to-right inside RTL text. - Formatting — dates, numbers, currencies — comes from the locale (
Intlin JavaScript), never from string templates. - Missing keys fail the build, not the user.
Internationalisation done this way stops being a project you do before entering a market and becomes a property of the platform. Adding the eleventh language is a translator’s job, not an engineer’s.