All posts

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-lattakia should 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.