Authoring locales — dos, don'ts, and esoteric languages
After watching a lot of locale files rot, here are the rules this toolkit enforces — and the judgment calls it leaves to you.
The dos
- Do treat the default locale as the schema. Add, rename, and delete keys there only. The generator propagates every change and re-serializes all locale files in schema key order — a key sits on the same line in every locale, so diffs are pure values.
- Do write sparse dialects.
ar-SY-x-lattakiashould contain only what actually differs from Syrian Arabic, which contains only what differs from MSA. The fallback chain does the rest. - Do use rich objects for translator context.
{ "value": "Delete?", "description": "Destructive action", "maxLength": 40 }— the description shows up in tooling, the maxLength in UI review.
The don'ts
- Don't add a key to a non-default locale only. It becomes an orphan: warned, untyped, and refilled by the next completion pass.
- Don't translate selector or param names.
{gender, select, …}— translate the branches, never the grammar. - Don't branch messages on units. Units change how numbers render (
formatLength), not which string is chosen. Context params select strings. Mixing the two is the most common config mistake we see.
Esoteric languages
Some languages don't fit the one-plural, no-gender mold — and ICU already knows this. Intl.PluralRules handles zero/one/two/few/many systems (Arabic has six forms) out of the box; write the branches and the engine picks. What ICU doesn't model — politeness registers (Japanese, Javanese speech levels), noun classes beyond gender (Bantu), animacy hierarchies — is exactly what context params are for: declare register or nounClass in config, branch in messages with select, supply once at runtime.
What we deliberately don't address: RTL layout (use CSS logical properties), transliteration, and locale-aware SEO routing beyond URL prefixes. Those are app concerns, and pretending otherwise is how Stift libraries become frameworks.