Expand description
I18n capability trait. See plan/ecosystem/02-capabilities.md §I18n.
An I18nPlugin turns a translation key plus a locale plus a bag of
variables into a localized string. The trait is shaped so that the
same surface covers three very different backends: static JSON
files (@bext/i18n-static), Mozilla Fluent (@bext/i18n-fluent),
and ICU MessageFormat (@bext/i18n-icu). A project swaps between
them in bext.config.toml without touching code.
§Design notes
-
Sync, object-safe. Like every other capability in this crate,
I18nPluginisSend + Sync, has no generics on the trait, and is callable from WASM, QuickJS, and native host code alike. -
Owned
Stringreturn, notCow<'_, str>. The plan sketch usesCow<'_, str>, which would let a backend return a borrowed slice of a statically-interned key when no interpolation happened. That borrow would have to live as long as the backend, which forces either a lifetime on the trait or an internal self-referential mess across the FFI boundary. Owning the string costs one allocation per call and makes every other concern (WASM guests, host-fn marshalling, async bridging, handle storage) trivial. -
Missing keys never error.
translatereturnsResult<String, I18nError>, but theOkvalue is a best-effort rendering — if the key is unknown, the backend returnsOk("{key}")(or whatever its configured missing-key policy is). A missing translation should never take down a UI. Errors are reserved for genuine backend failures: a locale that was promised insupported_locales()but failed to load, a Fluent syntax error in a bundle, an ICU formatter that could not format a variable. The caller branches onErrto decide whether to retry, fall back to a hardcoded English string, or surface a 500. -
TransVarsis a flatHashMap<String, I18nValue>. Passing&HashMap<String, String>would be ABI-flat but would force backends to re-parse numeric and date arguments from strings, which is exactly the kind of bug that ICU MessageFormat exists to prevent. Instead we carry a tagged enum of the small set of variable types every major i18n library supports: string, i64, f64, bool. Plurals are driven byi64andf64naturally;boolcovers Fluent’smale/female/otherselector shape. -
supported_localesreturns owned strings. A borrowed&[String]would be fine today but would bind the slice to the lifetime of the plugin, which crosses badly through dyn-trait host-fn dispatch. OwnedVec<String>is one allocation on what is an infrequently-called, boot-time query. -
fallback_localeis a single-locale answer, not a chain. BCP 47 negotiation (asking forfr-CAand gettingfr) is the caller’s job — specifically, it belongs in theI18nPlugin::negotiatehelper, which takes a requested locale and walks backward to the broadest tag the plugin supports, finally falling back tofallback_locale. Backends do not need to implement negotiation themselves; they only declare what they support.
Enums§
- I18n
Error - Why a translate call failed.
Ok(rendered)is the overwhelming majority path — every method here is reserved for a real backend failure the caller needs to decide about. - I18n
Value - A variable passed to
translatefor interpolation.
Traits§
- I18n
Plugin - A plugin that translates keys into localized strings.
Type Aliases§
- Trans
Vars - A bag of variables passed to
translate.