All posts
Politeness, gender, and the case for context params
"Welcome back, cher utilisateur" and "Welcome back, chère utilisatrice" are not two translations. They are one translation that agrees with a piece of context about the reader.
The common workarounds are all bad in their own way: thread a gender prop through every component that renders text; branch in code (isFemale ? t('welcomeF') : t('welcomeM')); or duplicate keys with suffixes and hope the convention holds.
Secundus Stift makes agreement a declaration:
params: {
context: [
{ name: 'gender', values: ['male', 'female', 'other'], defaults: { all: 'other' } },
{ name: 'politeness', values: ['polite', 'warm'], defaults: { all: 'polite', ar: 'warm' } },
],
}
Three rules make this scale:
- Declared params are optional at call sites. The app supplies them once — from the user profile via
setParam, or lexically via<LocaleScope context={…}>. Messages that need them get them; messages that don't, don't. defaults.allis the catch-all. Every locale resolves to a branch even before the app has a preference — no missing-branch runtime errors.- Per-locale defaults inherit down the dialect chain. Arabic defaults to
warmpoliteness; Syrian Arabic and the Lattakia dialect inherit that unless they override.
And because the param values are in config, they're in the typegen: passing gender: 'mal' is a compile error, not a silent fallback to other.