FormatJS

formatjs_icu_messageformat is the low-level Rust runtime for ICU MessageFormat. It parses each message once and accepts a locale for every formatting call, so one compiled message can serve requests in many locales.

Installation

[dependencies]
formatjs_icu_messageformat = "0.1"

Format a message

use formatjs_icu_messageformat::{IcuMessageFormat, Value, Values};
use std::collections::HashMap;

fn main() -> Result<(), formatjs_icu_messageformat::Error> {
    let message = IcuMessageFormat::try_new(
        "Hello, {name}. You have {count, plural, one {# task} other {# tasks}}.",
    )?;
    let values: Values = HashMap::from([
        ("name".to_owned(), Value::from("Ada")),
        ("count".to_owned(), Value::from(2_i64)),
    ]);

    assert_eq!(
        message.format_to_string("en-US", &values)?,
        "Hello, Ada. You have 2 tasks."
    );
    Ok(())
}

Supply locale per request

Locale is not part of IcuMessageFormat::try_new. Keep the compiled message in shared application state, then pass the request locale to format, format_to_parts, or format_to_string:

let english = message.format_to_string("en-US", &values)?;
let french = message.format_to_string("fr-FR", &values)?;

IcuMessageFormat is Send + Sync, so it can be shared through Arc in a multi-request server.

Supported message features

  • arguments, select, plural, selectordinal, plural offsets, and #
  • locale-aware numbers, dates, times, and plural rules through ICU4X
  • named number, date, and time formats
  • ICU number and date/time skeletons
  • rich-text tags through Value::Tag
  • pre-parsed ASTs through IcuMessageFormat::from_ast

Use format_to_string for text-only output. Use format or format_to_parts when rich values must survive formatting.

Custom formatters

Options::formatters accepts an implementation of the Formatters trait. Use it when application-specific number or date/time behavior is required. ICU4X currently does not provide full ECMA-402 parity for currency, unit, and time-zone formatting.

See the complete formatjs_icu_messageformat API.

Weekday-only skeletons (EEE, EEEE, EEEEE) render stand-alone abbreviated, wide, and narrow names without adding date fields. Python bindings share this behavior. Mixed date/time skeleton matching remains limited to the existing ICU4X fieldsets.

External ICU data

The default compiled_data feature preserves the existing constructors. Disable it to supply ICU4X data yourself:

[dependencies]
formatjs_icu_messageformat = { version = "0.2", default-features = false }
icu_provider_blob = { version = "2.1", features = ["alloc"] }
use formatjs_icu_messageformat::{IcuMessageFormat, Options, ProviderFormatters};
use icu_provider_blob::BlobDataProvider;
use std::sync::Arc;

let provider = BlobDataProvider::try_new_from_blob(bytes.into_boxed_slice())?;
let options = Options::with_formatters(Arc::new(ProviderFormatters::new(provider)));
let message = IcuMessageFormat::try_new_with_options("{count, number}", options)?;

Use the same options with from_ast_with_options for precompiled messages. Options::default_parser_options() returns the parser settings these options use, for parsing or precompiling messages without building formatters. Generate a compatible blob with ICU4X icu4x-datagen, including the locales and markers needed for numbers, cardinal/ordinal plurals, calendars, dates, and times. The provider must support the requested locales or implement locale fallback (for example, through ICU4X's LocaleFallbackProvider). Missing data returns a formatter error; it does not silently use compiled data. Deserialization support must match the buffer format; icu_provider_blob enables Postcard support.

Data errors name the formatter and locale and keep the ICU4X error as source(). Call Formatters::check_locale at startup for each locale you format with; it builds every formatter and returns the first such error. Behind LocaleFallbackProvider, a locale missing from the provider resolves to root (und) data and passes this check; formatjs_intl's IntlContext::check_catalog also rejects that case.

Without compiled_data, DefaultFormatters, Options::default(), and the constructors that use default options are unavailable. Explicit-options APIs remain available. Other dependencies can re-enable ICU compiled data through Cargo feature unification; check the final application's dependency graph.