Authoring Translations
Messages & Keys
ReactVanilla
The core rule: the default locale is the schema. Its files define every namespace's key set and key order. Every other locale is values-only — it holds translations, never structure.
Adding a key — edit the default locale only
// locales/en/common.json (default locale)
{
"greeting": "Hello, {name}!" // ← add here
}
On the next dev refresh the generator auto-completes the new key into every supported locale:
- A missing key inherits its value from the nearest registered chain ancestor (
ar-SY-x-lattakia→ar-SY→ar→ default), so"greeting": "Hello, {name}!"lands inar/ar-SY/ar-SY-x-lattakiatoo, and you translate each one. Chain semantics: Dialects. - Files are re-serialized in the default locale's key order (one key per line), so a key sits on the same line number in every locale's file.
- Shape is preserved through the rewrite: both the plain string and the rich object form.
Never add a key to a non-default locale directly — a key present in a locale but absent from the schema is an orphan: appended after the schema keys, reported as a warning, and given no typegen entry.
Translating into a non-default locale
// locales/ar/common.json — translate the value, keep the placeholders
{
"greeting": "مرحباً، {name}!"
}
- A locale file may be partial or absent; completion fills the rest from the chain. You only ever write what differs.
- Keep
{param}placeholders and the full ICU structure intact — translate the strings inside the branches, never the selector names or param names. ICU details: ICU Select & Plural. - Keep the rich object shape identical to the default locale's:
// en: "confirm": { "value": "Delete?", "description": "Destructive action", "maxLength": 40 }
// ar: "confirm": { "value": "حذف؟", "description": "إجراء لا يمكن التراجع عنه", "maxLength": 40 }
Message shapes
Each value is either a plain string or a rich object { value, description?, maxLength? } — description is translator context, maxLength a UI constraint. Both drive tooling, not runtime.
Deleting and renaming keys
- Delete/rename in the default locale. The key disappears everywhere; renamed keys behave as add + remove. A stale key lingering in a non-default locale is reported as an orphan.
- Don't delete from non-default locale files to "hide" a string — the schema still owns the key and completion will refill it.
Verification
- The generated
stift.gen.d.tsnarrows keys/params/select categories to your real messages — compile errors are the fastest authoring feedback. - The devtools panel (Devtools) shows the cross-locale table and missing-key/param logs; a missing key/param logs
{ code: 'missing-key' | 'missing-param', … }through the runtime hooks. filledKeys/orphanKeyssurface in generator warnings — treat orphan warnings as bugs.
Common mistakes
- Adding a key to a non-default locale only → orphan + warning, no typegen entry.
- Translating the default locale (it's the schema — always English/source).
- Reordering keys by hand in a locale file → the generator rewrites in default order; keep diffs to values only.
- Renaming a param in one locale but not the default → the union rule makes it required everywhere (see ICU Select & Plural).
Next
- Branching messages on gender, count, politeness → ICU Select & Plural
- Overriding values per dialect → Dialects
- Rendering messages with values, rich text, markdown → Translating & Values