mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-05 18:20:09 +00:00
Collator::new takes sv_SE.UTF-8 and sr_RS@latin. Folding maps every script's decimal digits to ASCII and, ignoring accents, Persian keheh, Farsi yeh, alef maksura, teh marbuta and heh with hamza to the Arabic letters. The collation data grows 4.8 KB (465 bytes gzipped).
5.5 KiB
5.5 KiB
craft-i18n
Translations for the Craft apps: lookup with fallback, plurals, language names and the system locale, over the catalogs of craft-i18n (the data repository, same name).
The catalogs are a fully optional build input. An app's build.rs embeds them from
CRAFT_I18N_DIR as a static CRAFT_I18N: &[(locale, shared catalog, app catalog)], empty when the
variable is unset; the recipe is in craft-i18n's
docs/integration.md.
This crate never reads files.
Wiring an app
use std::cell::Cell;
use std::sync::LazyLock;
include!(concat!(env!("OUT_DIR"), "/craft_i18n.rs")); // pub static CRAFT_I18N: &craft_i18n::Embedded
pub static I18N: LazyLock<craft_i18n::I18n> = LazyLock::new(|| {
let offered: Vec<&str> = LANGUAGES.iter().map(|l| l.code).collect(); // the app's own list
let (i18n, problems) = craft_i18n::I18n::new(CRAFT_I18N, &offered);
problems.iter().for_each(|p| log::warn!("translation catalog: {p}; that string shows in English"));
i18n
});
thread_local! { static CURRENT: Cell<craft_i18n::Lang> = const { Cell::new(craft_i18n::Lang::EN) }; }
macro_rules! tl { ($s:expr) => { $crate::I18N.t($crate::CURRENT.get(), $s) }; }
The app keeps its tl! macro, its language setting ("auto" → I18N.system_lang(), otherwise
I18N.from_code) and its picker (I18N.languages(), I18N.name(lang)).
Behaviour
- Offered locales:
I18n::newtakes the codes the app offers (its own language list), so an app never offers a language its interface isn't ready for (Arabic without right-to-left layout, say).languages(),from_code,match_tagandsystem_langonly return offered locales; an embedded locale that isn't offered still serves as a fallback (ptfor an offeredpt-br). - Fallback: locale by locale along the chain,
pt-br→pt→ the English source text. In each locale, app rows override shared ones, andtc/tidtry their context or@idrow before the plain row, sopt-br's plain row beatspt'smenurow. Only a region falls back (pt-br→pt,zh-hant-hk→zh-hant);sr-latnnever falls back to Cyrillicsr. A missing translation never errors. - Lookups:
t(plain),tc(with a context such asmenuor@msg, else plain),tid(an@idcommand id, else the English label),trn(a count,@pluralrows,{n}filled),get(one context along the locale chain, sopt-brcan return aptrow, but never the plain row or English: a key cap's name underkey, or "is there a translation?"), andfmtfor{name}placeholders, filled in one pass. - Bad rows (malformed, an unknown
@context, a wrong plural form count, a row defined twice, of which the first is kept) and bad locales (en,pseudo, a code embedded twice in any case) are skipped and returned byI18n::newfor the app to log; craft-i18n's CI rejects them before they ship. - Plurals: forms are listed in CLDR order (zero, one, two, few, many, other) for the categories
whole numbers 0 to 1000 reach, from
intl_pluralrules; a count in any other category takes the last form. CLDR'sptis Brazilian, soptusespt-PT's rules (0 is plural) andpt-brpt's.intl_pluralrulesis pinned to the version craft-i18n's checker uses, andplural_form_countsholds the same counts. - Names: a language's name comes from its shared catalog's
@id language.namerow, else its code; English is "English". - Right to left:
rtlis true forar,ckb,dv,fa,he,ps,sd,ug,urandyi; putting text into display order is the app's job. - System locale:
system_langmatches the system's list once and caches it: on desktop UnixLANGUAGE,LC_ALL,LC_MESSAGESandLANG, read raw; elsewheresys-locale's (macOS and Windows preferred languages, the browser's on the web).fr_CA.UTF-8→fr,zh-TW→zh-hant,pt-PT→pt-brwhen that is the only Portuguese,C/POSIX→ English. A script is never crossed:sr_RS@latinandsr-Latn-RSpicksr-latn, neversr, whilesr-Cyrl-RSpickssr. - Pseudo locale (
pseudo, debug builds only, never inlanguages()): every lookup,getincluded, comes back as⟦…⟧, line by line, so text drawn without the marks never went through translation.looked_up()lists the source texts looked up so far, for an audit against a complete catalog. - Search (
Folder):Folder::new(locale).fold(text)gives a key for case- and accent-insensitive matching (keep_accents()makes accents count) and a map back to the original text, soFolded::findreturns the byte range to highlight. Compatibility forms fold to their plain letters (fi, full-widthA),ßtoss,ςtoσ, digits of every script to ASCII (Layer ١٢findslayer 12); ignoring accents, Persian and Arabic letter forms match (کandك,یandي,ةandه,أandا); invisible format characters (soft hyphens, joiners, bidi isolates) are dropped. Turkish and Azerbaijani foldItoıwhen accents count; ignoring accents,I,i,İandıall match. - Sorting (
Collator, featurecollate): ICU4X collation for the locales craft-i18n ships, Persian and Hindi, with digits compared by value (Layer 2beforeLayer 10) and-u-co-pinyin/-u-co-strokefor Chinese. The data (754 KB) is regenerated bydata/generate-collation.sh; the feature adds about 1 MB to a release binary or wasm module (320 KB gzipped).