Browse docs

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 in ar/ar-SY/ar-SY-x-lattakia too, 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.ts narrows 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/orphanKeys surface 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