i18n: dependency-free message runtime, catalog checks, plugin strings, native strings

- runtime/i18n.ts: ICU-lite messages (placeholders as text, Intl.PluralRules plurals with exact
  forms, selectordinal, select, number/date/time), fallback chains (pt-BR -> pt -> en, ar-x-fm ->
  ar), X and legacy code aliases, RTL by locale, lazy catalogs, live onChange. Apostrophes are
  literal except right before { } # or another apostrophe.
- runtime/i18nCheck.ts: rules for contributed catalogs (shape, keys, syntax, placeholder parity,
  select options, markup/control characters, lengths). An argument English only uses to choose a
  plural form may be dropped; missing plural forms are warnings; meta "allowEmpty" fragments.
- runtime/pluginI18n.ts: api.i18n bound per plugin (register/t/locale/dir/onChange/number/date/
  relative/list); the UI kit's own words follow the app's language. pageLang.ts reports the
  site's <html lang>. shell/messages.ts: shell-model words.
- waybetter-host i18n.rs: NativeStrings (validated plain text, caps, one-pass {name} fill, page
  payload by prefix), system_locales via sys-locale; TabState.lang.
- JSON Schemas for catalogs and translator notes; npm exports waybetter/i18n, /i18n/check,
  /i18n/plugin.
This commit is contained in:
CRTkafa 2026-09-29 09:53:48 +07:00
parent c404dd3dd0
commit 2b8161fedc
18 changed files with 1461 additions and 27 deletions

View file

@ -71,6 +71,9 @@ pub struct TabState {
/// A deck column's unread count (capped at [`MAX_UNREAD`]); only ever a number about this tab.
#[serde(skip_serializing_if = "Option::is_none")]
pub unread: Option<u16>,
/// The language the site's own UI is in (its `<html lang>`), a validated tag; site tabs only.
#[serde(skip_serializing_if = "Option::is_none")]
pub lang: Option<String>,
}
impl TabState {
@ -89,6 +92,7 @@ impl TabState {
focused,
loading: None,
unread: None,
lang: None,
}
}
}