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:

  1. 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.
  2. defaults.all is the catch-all. Every locale resolves to a branch even before the app has a preference — no missing-branch runtime errors.
  3. Per-locale defaults inherit down the dialect chain. Arabic defaults to warm politeness; 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.