Authoring Translations
ICU Select & Plural
messageFormat: 'icu-mf1'. Params are derived from the message source per locale and unioned across locales — a param required by any locale is required at every call site.
Interpolation
"greeting": "Hello, {name}!"
renders with t('greeting', { name }). Rendering values: Translating & Values.
Select (enum branches)
The select argument's case labels become the literal union. Always include a catch-all other — it is the required fallback branch.
"greetingNote": "We address you {politeness, select, polite {respectfully} warm {warmly} other {…}}."
Context params (gender/politeness/verbosity) appear in messages as ordinary select arguments, but they are declared in config so the app supplies them once and call sites don't repeat them. Declaring them: stift.config.ts. Supplying them at runtime (setParam, <LocaleScope>): Translating & Values.
Plural and selectordinal
"items": "{count, plural, one {# item} other {# items}}"
{count, plural, …} gives the param type number. Always include other.
Rich text and markdown
Use tags for inline emphasis/links and let the UI render them:
"rich.demo": "This message is <strong>rich</strong> — try the <a>inline link</a>."
Rendered with t.rich('rich.demo', { strong: <strong>…</strong>, a: <a>…</a> }). Markdown messages render via t.markdown(...). Author the message in the locale files; wire the tag components in the UI. Full rendering API: Translating & Values.
Rules that bite
- Removing
otherfrom aselect/plural→ runtime falls back / missing branch. - Renaming a param in one locale but not the default → union rule makes it required everywhere.
- Translating selector names or param names → breaks every other locale; translate only the strings inside branches.
- Branching a message on a unit system (
{unitSystem, select, metric {…} imperial {…}}) → wrong tool. Store the value in base units and format it; see Units.
Next
- Per-dialect value overrides → Dialects
- Declaring context params and units in config → stift.config.ts