Why traditional monolithic i18n architectures hit a wall
Kilobytes of RAM/Flash, no full OS, battery-critical
Strict app download size & startup latency budgets
Download bundle size & cross-platform consistency
High concurrency, low memory per request, 100s of locales
Imagine sending something like this to a central service:
中|ワ|況|写|イノナ|開|億|が|ゃ|へ|者|43|俳|寺|式|7|沢|暮|ル|材|年|る|あん|移|酔|むぎ|え|す|写|情|主|逃|69|引|ぎ|う|ぱ|何|81|昇|フネイマ|本|因|ぱ|不|真|え|断|候|ら|。|吉|群|コクチ|賀|伝|ツエ|別|写|ユリキ|施|見|ら|力|1|取|目|れ|づ|ぱ|ぞ|然|野|テ|倍|展|ぴ|る|て|け|京|答|週|ネコ|入|整|料|勉|ら|げ|。
Dead-code & data elimination for unused components
Dynamic language packs from disk or over the network
From no_std wearables & MCUs to browsers and cloud servers
One shared implementation across ecosystems via zero-cost FFI
Fast startup, tiny memory footprint, private, and offline-ready
Modular, portable, and memory-safe internationalization
Portable by Design
no_std to cloud, with C, C++, JS/WASM, and Dart bindings via Diplomat.
Lightweight
Compile-time dead-code elimination & data slicing (50–90% smaller than ICU4C).
Secure & Fast
Memory-safe Rust with zero-copy data (ZeroVec) and zero-alloc formatting (Writeable).
ICU4X-TC)Official Technical Committee (ICU4X-TC) under the Unicode Consortium
Founded and actively developed by engineers at Google and Mozilla
40+ contributors in the past year across industry, open source, and GSoC students
icu_segmenter) & Pixel Watch (no_std).
via WebAssembly
via no_std Rust
via Rust & C++
Text Segmentation (icu_segmenter) & Temporal Calendars (icu_calendar)
ECMA-262 Temporal (temporal_rs + icu_calendar + zoneinfo64)
Bionic C Library & Gboard Case Mapping (icu_casemap, 82 KiB static)
rustc diagnostic lists & locale fallback (icu_list & icu_locale_core)
ty — Python linter & type checkerIntl & Temporal)text-splitter — LLM/RAG text chunkingintl4x) — Official pub.dev SDKleptos_i18n — Rust SSR/WASM web appsresvg / usvg — Pure-Rust SVG <text>url & idna (UTS #46 — 100M+ monthly downloads):Pydantic v2 · uv · Deno · Reqwest · Next.js / SWC · Cloudflare quiche · Wasmtime · rust-analyzer · Zed · Taurirecord_use)icu_capi) — Single source of truth for all FFI bindings
Building Your First App Using ICU4X (~25 Minutes)
You can complete the hands-on exercise in either:
cargo new)icu4x.unicode.org/2_3/tutorials/date-pickerA CLI app that takes a locale and formats today's date & time
icu::locale::Localeicu::datetime::DateTimeFormattericu::datetime::fieldsets::YMD & YMDTicu::calendar::Date, icu::time::{DateTime, Time}docs.rs/icu/2.3A web UI with a date/time picker and interactive locale selector
Locale (Locale.fromString)DateFormatter / DateTimeFormatterIsoDate, Timeicu4x.unicode.org/2_3/tsdoc/cargo add icu@2.3 or open the JS CodePen template)LocaleDE-CH or ES-419 → watch it normalize to de-CH and es-419!Date with YMD::medium()en, en-GB, fr, th, ja-u-ca-japanese, en-u-ca-hebrewDateTime (YMDT)Consistent, type-safe APIs across Rust, JS/WASM, C++, and Dart.
BCP-47 canonicalization, semantic skeletons (YMD, YMDT), and Unicode extensions (-u-ca-hebrew).
Ships with compiled_data covering hundreds of locales out of the box.
Walkthrough & Demo: Custom Data Packs and Static Slicing (~15 Minutes)
ccp) Exampleccp (Chakma, spoken in Bangladesh and India) into DateTimeFormatter:
ccp2026 M10 20 (Root fallback!)compiled_data includes hundreds of high-coverage locales, not every long-tail locale.icu4x.unicode.org/2_3/tutorials/date-picker-dataDataProvider & DataMarker under the hoodccp.blob with icu4x-datagenccp.blob dynamically at runtimeccp.blob: 5.4 MB → 13.7 KB → 4.7 KB!DataProvider TraitDataProvider<M: DataMarker> underlies all ICU4X data loading.DataMarker is one atomic unit of data (e.g., DatetimeNamesMonthGregorianV1)._unstable constructor declares its exact required markers as Rust trait bounds:// impl<FSet> DateTimeFormatter<FSet>
pub fn try_new_unstable<P>(
provider: &P,
prefs: DateTimeFormatterPreferences,
field_set: FSet,
) -> Result<Self, DateTimeFormatterLoadError>
where
P: DataProvider<DatetimeNamesMonthGregorianV1>
+ DataProvider<DatetimePatternsDateGregorianV1>
+ DataProvider<DecimalSymbolsV1>
+ /* ... */;
icu_provider_baked): Compiled into Rust const tables; zero deserialization cost.icu_provider_blob): Portable binary pack loaded from files, HTTP, or memory.icu_provider_adapters)fr-CA → fr → und)ccp.blob!icu4x-datagenicu_provider_export in build.rs):cargo install icu4x-datagen
ccp) into a binary blob:icu4x-datagen \
--markers all \
--locales ccp \
--format blob \
--out ccp.blob
ccp.blob at Runtimetry_new_with_buffer_provider)// cargo add icu --features serde
// cargo add icu_provider_blob --features alloc
use icu_provider_blob::BlobDataProvider;
let blob = std::fs::read("ccp.blob")?;
let provider =
BlobDataProvider::try_new_from_blob(
blob.into_boxed_slice()
)?;
let dtf = DateTimeFormatter::try_new_with_buffer_provider(
&provider,
locale!("ccp").into(),
YMD::medium(),
)?;
DataProvider.fromByteSlice)import { DataProvider, DateFormatter, Locale } from "icu";
const response = await fetch("./ccp.blob");
const bytes = new Uint8Array(
await response.arrayBuffer()
);
const provider = DataProvider.fromByteSlice(bytes);
const df = DateFormatter.createYmdWithProvider(
provider,
Locale.fromString("ccp"),
/* options */
);
ccp: 𑄸𑄶 𑄃𑄧𑄇𑄴𑄑𑄮𑄝𑄧𑄢𑄴, 𑄸𑄶𑄸𑄼 (Native Chakma month name & digits!)
--markers-for-bin--markers all exported every component in ICU4X (5.4 MB).icu4x-datagen at the compiled binary (.exe or .wasm) to extract only used markers:icu4x-datagen \
--markers-for-bin target/release/tutorial \
--locales ccp \
--format blob \
--overwrite \
--out ccp.blob
5,448,603 bytes (5.4 MB) ➔ 13,711 bytes (13.7 KB)! (99.7% reduction)
DateTimeFormatter supports all 15+ calendars (13.7 KB).FixedCalendarDateTimeFormatter<Gregorian, ...>:use icu::calendar::Gregorian;
use icu::datetime::FixedCalendarDateTimeFormatter;
let dtf = FixedCalendarDateTimeFormatter::<Gregorian, _>::try_new_with_buffer_provider(
&provider, locale.into(), YMD::medium()
)?;
println!("{}", dtf.format(&iso_date.to_calendar(Gregorian))); // input must be Gregorian
--markers-for-bin now detects only 5 Gregorian & decimal markers:13,711 bytes (13.7 KB) ➔ 4,711 bytes (4.7 KB)!
Lean compiled defaults + on-demand BlobDataProvider language packs.
--markers-for-bin inspects your .exe or .wasm to extract only used DataMarkers.
Specific types (FixedCalendarDateTimeFormatter, try_new_tiny) shrink both code and data.
2025–2026 Project Update & Call for Feedback (~20 Minutes)
unstable APIs!unstable Cargo feature (or icu_experimental).Temporal → V8 C++ → temporal_rs (via Diplomat FFI)icu_calendar: 15+ calendars, zero heap allocationszoneinfo64: Zero-copy Rust parser sharing ICU4C's zoneinfo64.resTemporal → SpiderMonkey C++icu_calendar (via Diplomat FFI) for all calendar arithmetic & non-Gregorian calendarsrecord_use)build.dart & link.dart) integrate with record_use.dart run / flutter build fetches or builds ICU4X automatically.icu_segmenter) (1/2)*_neo_*) unstableCompiles upstream Unicode state machines directly into a compact DFA (no hand-maintained TOML rules!):
Parses upstream ucd/auxiliary/*Break*.txt state machines directly via unicodeset_parse.
Compiles CLDR tailorings and CSS line-break options (lb=loose/strict, lw=phrase) into symbol map overrides.
Fuses Thai, Lao, Khmer, Myanmar, and CJ directly into DFA symbols—no secondary lookup!
*_17_* constructors) unstable.icu_segmenter) (2/2)radaboost) & Thai (thadaboost).CnnSegmenter).components/segmenter/examples/ (not yet in the main crate API).
icu_locale::names unstable (+ C++, JS & Dart FFI): Language, Region, Script & Variant display names.| Display + Length | en-GB |
az |
zh-Hans |
|---|---|---|---|
| Dialect + Medium (default) | "British English" | "Azerbaijani" | "Simplified Chinese" |
| Dialect + Short | "UK English" | "Azeri" | "Simplified Chinese" |
| Dialect + Long | "British English" | "Azerbaijani" | "Simplified Mandarin Chinese" |
| Standard + Medium | "English (United Kingdom)" | "Azerbaijani" | "Chinese (Simplified)" |
| Menu + Short | "English (UK)" | "Azeri" | "Chinese, Mandarin (Simplified)" |
TryWriteable): Unknown subtags fall back to raw codes with Part::ERROR — it-Qabc-150 ➔ "Italian (Qabc, Europe)".
try_new_tiny)
core + basic
fr, en), official regions (FR, CA, BE, CH), own script (Latn).
try_new_light)
+ moderate
IN, …), major scripts (Arab, Cyrl, Hans).
try_new_heavy)
+ modern
az, scn, br), 49+ scripts (Deva, Xsux), all BCP-47 variants.
Tier (fr locale) |
fr-CA |
zh-Hans |
hi-Deva-IN |
|---|---|---|---|
Tiny (try_new_tiny) |
✅ "français (Canada)" | ❌ "zh (Hans)" | ❌ "hi (Deva, IN)" |
Light (try_new_light) |
✅ "français canadien" | ✅ "chinois simplifié" | ❌ "hindi (Deva, Inde)" |
Heavy (try_new_heavy) |
✅ "français canadien" | ✅ "chinois simplifié" | ✅ "hindi (dévanagari, Inde)" |
icu_calendar 2.2 & 2.3)Month::new(5) vs. Month::leap(5) (no more "M05L" strings!)Date::try_from_fields):Overflow (Constrain / Reject) & missing-field strategiestry_add & try_until across all 15+ calendars (75% faster diffing)fa ➔ Persian · th ➔ Buddhist (via likely subtags)AnyCalendar::Julian for historical datesicu_datetime)DateRangeFormatter) unstable:Oct 20 – 23, 2026 (collapses if start == end)MD in ja uses 10月20日 (MMM) instead of 10/20B):DateTimeFieldBag) 2.4 unstable:icu_experimental::currency) unstable$12,345.67 · USD 12,345.67$12K · ($12,345.67)CHF-12'345.67 (de-CH)try_new_no_currency):12,345.67 for USD, 12,346 for JPY)ZeroTrieZeroTrierecord_use & FFI provenance🌐 Documentation, Tutorials & Live Demo
icu4x.unicode.org · icu4x.unicode.org/2_3/demo
🧪 Try Our unstable APIs & Send Feedback!
icu_locale::names (Tiny / Light / Heavy display names)icu_segmenter V2 (_neo_ Unicode state machine segmenter)icu_datetime (DateRangeFormatter; DateTimeFieldBag coming in 2.4)icu_experimental::currency🤝 Get Involved!
We love mentoring new contributors—reach out at shane@unicode.org or on GitHub Discussions.
icu4x-announceconsole.assert(new Intl.NumberFormat("es-ES", { notation: "compact" }).format(1_900_000) === "1,9\u00a0M"); console.assert(new Intl.NumberFormat("es-ES", { notation: "compact" }).format(346_000_000) === "346\u00a0M"); console.assert(new Intl.RelativeTimeFormat("es", { numeric: "always" }).format(-4, "month") === "hace 4 meses"); console.assert(new Intl.NumberFormat("ja-JP", { notation: "compact" }).format(1_910_000) === "191万"); console.assert(new Intl.NumberFormat("ja-JP", { notation: "compact", maximumFractionDigits: 1 }).format(340_000_000) === "3.4億"); console.assert(new Intl.RelativeTimeFormat("ja", { numeric: "always" }).format(-4, "month") === "4 か月前");
const d = new Date("2026-10-20T00:00:00Z"); console.assert(new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeZone: "UTC" }).format(d) === "Oct 20, 2026"); console.assert(new Intl.DateTimeFormat("fr-FR", { dateStyle: "medium", timeZone: "UTC" }).format(d) === "20 oct. 2026"); console.assert(new Intl.DateTimeFormat("he-u-ca-hebrew", { dateStyle: "medium", timeZone: "UTC" }).format(d) === "ט׳ בחשוון תשפ״ז"); console.assert(new Intl.DateTimeFormat("en-u-ca-hebrew", { dateStyle: "medium", timeZone: "UTC" }).format(d) === "9 Heshvan 5787"); console.assert(new Intl.DateTimeFormat("ar-SA-u-ca-islamic-umalqura", { dateStyle: "medium", timeZone: "UTC" }).format(d) === "٩ جمادى الأولى ١٤٤٨ هـ"); console.assert(new Intl.DateTimeFormat("zh-u-ca-chinese", { dateStyle: "long", timeZone: "UTC" }).format(d) === "2026丙午年九月十一"); console.assert(new Intl.DateTimeFormat("ja-u-ca-japanese", { dateStyle: "medium", timeZone: "UTC" }).format(d) === "令和8年10月20日"); const d1 = new Date("2026-10-20T00:00:00Z"), d2 = new Date("2026-10-23T00:00:00Z"); console.assert(new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeZone: "UTC" }).formatRange(d1, d2) === "Oct 20\u2009–\u200923, 2026"); console.assert(new Intl.DateTimeFormat("fr-FR", { dateStyle: "medium", timeZone: "UTC" }).formatRange(d1, d2) === "20–23 oct. 2026"); console.assert(new Intl.DateTimeFormat("ja-JP", { dateStyle: "medium", timeZone: "UTC" }).formatRange(d1, d2) === "2026/10/20~2026/10/23"); console.assert(new Intl.DisplayNames("en", { type: "language", languageDisplay: "dialect" }).of("fr-CA") === "Canadian French"); console.assert(new Intl.DisplayNames("en", { type: "language", languageDisplay: "standard" }).of("fr-CA") === "French (Canada)"); console.assert(new Intl.DisplayNames("fr", { type: "language", languageDisplay: "dialect" }).of("fr-CA") === "français canadien"); console.assert(new Intl.DisplayNames("zh-Hant", { type: "language" }).of("fr-CA") === "法文(加拿大)"); console.assert([...new Intl.Segmenter("ja", { granularity: "word" }).segment("国際化ライブラリを使う")].map(s => s.segment).join("|") === "国際|化|ライブラリ|を|使う"); console.assert([...new Intl.Segmenter("th", { granularity: "word" }).segment("การทำให้เป็นสากล")].map(s => s.segment).join("|") === "การ|ทำให้|เป็น|สากล");
console.assert(new Intl.Locale("zh-TW").maximize().toString() === "zh-Hant-TW"); console.assert(new Intl.NumberFormat("de-CH", { style: "currency", currency: "CHF" }).format(12345.67) === "CHF\u00a012'345.67"); console.assert(new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY", notation: "compact" }).format(100000) === "¥10万"); console.assert(new Intl.NumberFormat("en-US", { style: "unit", unit: "kilometer", unitDisplay: "short" }).format(5) === "5 km"); console.assert(new Intl.NumberFormat("en-US", { style: "unit", unit: "mile", unitDisplay: "short" }).format(3.1) === "3.1 mi"); console.assert(new Intl.DateTimeFormat("ja-u-ca-japanese", { era: "long", year: "numeric", month: "long", day: "numeric", weekday: "long", hour: "2-digit", minute: "2-digit", timeZoneName: "long", timeZone: "Europe/Paris" }).format(new Date("2026-10-20T14:30:00+02:00")) === "令和8年10月20日火曜日 14:30 中央ヨーロッパ夏時間"); console.assert(new Intl.DateTimeFormat("fr-FR", { dateStyle: "medium", timeZone: "UTC" }).formatRange(new Date("2026-10-20T00:00:00Z"), new Date("2026-10-23T00:00:00Z")) === "20–23 oct. 2026"); console.assert(new Intl.RelativeTimeFormat("en").format(3, "day") === "in 3 days"); console.assert(new Intl.RelativeTimeFormat("fr").format(3, "day") === "dans 3 jours"); console.assert(new Intl.Collator("sv").compare("ä", "z") > 0); console.assert(new Intl.Collator("de").compare("ä", "b") < 0); console.assert(new Intl.ListFormat("en", { type: "disjunction" }).format(["A", "B", "C"]) === "A, B, or C"); console.assert(new Intl.DisplayNames("en", { type: "region", style: "long" }).of("GB") === "United Kingdom"); console.assert(new Intl.DisplayNames("en", { type: "region", style: "short" }).of("GB") === "UK"); // ICU4X: // LanguageIdentifierDisplayName::try_new_long_light(locale!("en").into(), langid!("zh-Hans"), Default::default()) => "Simplified Mandarin Chinese" // WordSegmenter::new_auto(Default::default()).segment_str("ユニコードは、符号化文字集合") => "ユニコード|は|、|符号|化|文字|集合"
s1 = "中ワ況写イノナ開億がゃへ者43俳寺式7沢暮ル材年るあん移酔むぎえす写情主逃69引ぎうぱ何81昇フネイマ本因ぱ不真え断候ら。吉群コクチ賀伝ツエ別写ユリキ施見ら力1取目れづぱぞ然野テ倍展ぴるてけ京答週ネコ入整料勉らげ。"; s2 = "中|ワ|況|写|イノナ|開|億|が|ゃ|へ|者|43|俳|寺|式|7|沢|暮|ル|材|年|る|あん|移|酔|むぎ|え|す|写|情|主|逃|69|引|ぎ|う|ぱ|何|81|昇|フネイマ|本|因|ぱ|不|真|え|断|候|ら|。|吉|群|コクチ|賀|伝|ツエ|別|写|ユリキ|施|見|ら|力|1|取|目|れ|づ|ぱ|ぞ|然|野|テ|倍|展|ぴ|る|て|け|京|答|週|ネコ|入|整|料|勉|ら|げ|。"; console.assert([...new Intl.Segmenter("ja", { granularity: "word" }).segment(s1)].map((v) => v.segment).join("|") === s2);
Curved milestone callout arrow from 2020 to 2026
Chart 1: DateTimeFormatter Performance (sec, lower is better)
Chart 2: DateTimeFormatter Memory Use (log scale bytes, lower is better)
Chart 3: Basic Number Format: Code Size (kB, lower is better)
console.assert(new Intl.Locale("DE-CH").toString() === "de-CH"); console.assert(new Intl.Locale("ES-419").toString() === "es-419"); console.assert(new Intl.DateTimeFormat("ja-u-ca-japanese", { dateStyle: "medium", timeStyle: "short", timeZone: "UTC" }).format(new Date("2026-10-20T14:30:00Z")) === "令和8年10月20日 14:30"); // Rust solution: use icu::calendar::Date; use icu::datetime::{fieldsets::YMD, DateTimeFormatter}; use icu::locale::Locale; use icu::time::{DateTime, Time}; let locale: Locale = "ja-u-ca-japanese".parse()?; let dtf = DateTimeFormatter::try_new( locale.into(), YMD::medium().with_time_hm(), // YMDT )?; let dt = DateTime { date: Date::try_new_iso(2026, 10, 20)?, time: Time::try_new(14, 30, 0, 0)?, }; println!("{}", dtf.format(&dt)); // => 令和8年10月20日 14:30
ICU4X compiled_data root fallback: DateTimeFormatter::try_new(locale!("ccp").into(), YMD::medium()).unwrap().format(&Date::try_new_iso(2026, 10, 20).unwrap()) // => "2026 M10 20" https://github.com/unicode-org/icu4x/blob/main/tutorials/data-provider-runtime.md
console.assert(new Intl.DateTimeFormat("ccp", { dateStyle: "medium", timeZone: "UTC" }).format(new Date("2026-10-20T00:00:00Z")) === "𑄸𑄶 𑄃𑄧𑄇𑄴𑄑𑄮𑄝𑄧𑄢𑄴, 𑄸𑄶𑄸𑄼");
console.assert(["en-GB", "az", "zh-Hans"].map(c => new Intl.DisplayNames("en", { type: "language", languageDisplay: "dialect", style: "long" }).of(c)).join(" | ") === "British English | Azerbaijani | Simplified Chinese"); console.assert(["en-GB", "az", "zh-Hans"].map(c => new Intl.DisplayNames("en", { type: "language", languageDisplay: "dialect", style: "short" }).of(c)).join(" | ") === "UK English | Azeri | Simplified Chinese"); console.assert(["en-GB", "az", "zh-Hans"].map(c => new Intl.DisplayNames("en", { type: "language", languageDisplay: "standard", style: "long" }).of(c)).join(" | ") === "English (United Kingdom) | Azerbaijani | Chinese (Simplified)"); // ICU4X Long, Menu & TryWriteable fallback tests: // https://github.com/unicode-org/icu4x/blob/main/components/locale/src/names/language.rs#L57-L181 // https://github.com/unicode-org/icu4x/blob/main/components/locale/src/names/language.rs#L2080-L2117
console.assert(["fr-CA", "zh-Hans", "hi-Deva-IN"].map(c => new Intl.DisplayNames("fr", { type: "language", languageDisplay: "dialect" }).of(c)).join(" | ") === "français canadien | chinois simplifié | hindi (dévanagari, Inde)"); // ICU4X Tiny / Light / Heavy tiers (locale!("fr") with [langid!("fr-CA"), langid!("zh-Hans"), langid!("hi-Deva-IN")]): // - try_new_tiny: [Ok("français (Canada)"), Err("zh (Hans)"), Err("hi (Deva, IN)")] // - try_new_light: [Ok("français canadien"), Ok("chinois simplifié"), Err("hindi (Deva, Inde)")] // - try_new_heavy: [Ok("français canadien"), Ok("chinois simplifié"), Ok("hindi (dévanagari, Inde)")] // https://github.com/unicode-org/icu4x/blob/main/components/locale/src/names/language.rs#L429-L460
console.assert(new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeZone: "UTC" }).formatRange(new Date("2026-10-20T00:00:00Z"), new Date("2026-10-23T00:00:00Z")) === "Oct 20\u2009–\u200923, 2026"); console.assert(new Intl.DateTimeFormat("en-US", { dayPeriod: "long", timeZone: "UTC" }).format(new Date("2026-10-20T09:00:00Z")) === "in the morning"); console.assert(new Intl.DateTimeFormat("en-US", { dayPeriod: "long", timeZone: "UTC" }).format(new Date("2026-10-20T23:00:00Z")) === "at night"); console.assert(new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(12345.67) === "$12,345.67"); console.assert(new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", currencyDisplay: "code" }).format(12345.67) === "USD\u00a012,345.67"); console.assert(new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", notation: "compact" }).format(12345.67) === "$12K"); console.assert(new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", currencySign: "accounting" }).format(-12345.67) === "($12,345.67)"); console.assert(new Intl.NumberFormat("de-CH", { style: "currency", currency: "CHF" }).format(-12345.67) === "CHF-12'345.67"); // ICU4X currency tests (including try_new_no_currency): // https://github.com/unicode-org/icu4x/blob/main/components/experimental/src/dimension/currency/format.rs