Frontend6 min read
Six locales without an i18n library
Six languages on two production platforms, and not one i18n package in either. What that costs at build time, and what it buys at three in the morning.
Why no library
An i18n library solves three problems: where the locale comes from, how a string is looked up, and what happens when the string is missing. On both platforms I could answer all three in a page of code, and the answers were boring. What the library would have added on top was a runtime, a plugin for the router, and a second opinion about my URLs.
So the locales live in the route: a segment at the top of the tree, six of them, rendered on the server. The URL is the source of truth, and nothing has to be hydrated before the page knows what language it is.
Where the locale is decided
Exactly one place decides, and it never guesses twice. A stored choice wins, because a person who picked a language meant it. Failing that, the browser gets a say through its accept-language header. Failing that, the country the request came from. Failing everything, English.
const locale =
fromCookie(request) ??
fromAcceptLanguage(request) ??
fromCountry(request) ??
'en';Written as a cascade, the rule reads in one breath and can be argued about by someone who does not write code. That mattered more than any feature I gave up.
Dictionaries that refuse to be incomplete
The dictionaries are plain typed objects. One interface describes the shape, and every locale has to satisfy it, so a missing key is not a blank space on a page at runtime: it is a type error on my machine, before anything ships.
The compiler is a better translator than a fallback string. It cannot write Italian, but it can refuse to let me forget that Italian exists.
Content that can be half translated
Interface copy is mine, so the compiler can insist. Database content is not: a product arrives in English on Monday and gets its German on Thursday, and the site has to be correct on Tuesday. That content is typed as localised text with a required default, resolved with an English fallback.
A missing translation therefore never leaves a hole. It leaves English, in the right place, on time, which is what a buyer in a sixth market actually needs.
The honest trade
Put the locale in the URL and resolve it once. Make the type system responsible for completeness and the fallback responsible for reality. And be honest about the trade: no library means no plural rules or date formats for free, so use the ones the platform already ships and stop there.