Skip to content

Repository files navigation

Rust MF2

Unicode MessageFormat 2 (MF2) for Rust applications, including Leptos, native CLI tools and Ratatui TUIs: the full specification, one small binary catalog per language loaded when it is needed, and a wasm that contains none of the text.

#[component]
pub fn Overview(name: String, count: RwSignal<u32>) -> impl IntoView {
    view! {
        <h1>{tr!("signature.text", name = name)}</h1>
        <input type="search" placeholder=tr!("search.placeholder") />
        // A signal as an argument: the text follows the count and the language.
        <p role="status">{tr!("cart.items", count = count)}</p>
        // Markup in the message becomes elements, wherever the translation puts them.
        <p>
            {tr!(
                "terms.accept",
                link = |c: AnyView| view! { <a href="/terms">{c}</a> },
                strong = |c: AnyView| view! { <strong>{c}</strong> },
            )}
        </p>
    }
}

One macro works in every position: text, attributes, props, strings and const tables. Each call is checked against the messages when it compiles. (This sample is compiled too: it belongs to the library that call sites builds.)

  • Server-rendered and hydrated is the default. The server renders each page in the reader's language, and the browser hydrates it and switches language live, without a reload. The page stays correct and readable before the wasm arrives, and if it never does.
  • Islands give the smallest download. A server-only component costs the wasm nothing, however many messages it uses: the code section was 185,925 bytes with and without one full of call sites (cargo xtask islands-zero, 2026-09-23).
  • Client-only applications and lazy routes are supported too.
  • Native CLI and Ratatui apps use the same messages and the same tr!, through mf2's native feature: the catalogs embedded in the executable or shipped beside it, the system's language and time zone. With ratatui, a message is Ratatui text, its MF2 markup drawn by a theme set once.
  • No locale data in the wasm: no message text, ids, argument names or plural rules. A translation edit leaves the wasm byte-for-byte the same, so readers keep their cached copy. The library's client code costs 25,875 bytes gzipped, plus 8.4 bytes a call site: 41,466 bytes for an application with 1,860 call sites (cargo xtask size, 2026-09-25).
  • The whole specification: the MF2 working group's test suite passes at every layer, from the parser to the browser, and every normative statement has a test (conformance/REPORT.md, conformance/COVERAGE.md).
  • WCAG 2.2 AA: <html lang dir> follows the language, the switcher is a labelled form that applies a choice on a button, and bidi isolation is on where a person reads the text.

Status: 1.0.0 is on crates.io for all sixteen crates of the web family. 1.1.0 was not published and will not be: its fixes ship in 2.0.0, the next release, where the native and terminal support are mf2's native and ratatui features. What each release contains, what it measures and its known limitations are in CHANGELOG.md; what 2.x promises is in docs/versioning.md.

Install

Rust 1.88 or later and the mf2 command, plus the browser target for a web application:

cargo install mf2-cli             # the `mf2` command
rustup target add wasm32-unknown-unknown

A Leptos application is one crate too: its messages sit in locales/, its build script is mf2_build::run(), and it names mf2 with leptos and the functions its messages call, forwarding its ssr feature to mf2/ssr and mf2/axum, and hydrate to mf2/hydrate:

cargo add mf2 --features leptos,fn-number   # leptos-0-8 on Leptos 0.8
cargo add --build mf2-build

mf2 init --ssr (or --islands, --csr) writes a complete one, and Getting started builds one step by step.

A native application is the same shape: its messages sit in locales/, its build script is mf2_build::run(), and it names mf2 with native — or ratatui, for a terminal UI — and the functions its messages call:

cargo add mf2 --features native,fn-number   # or ratatui,fn-number
cargo add --build mf2-build

mf2 init --cli or mf2 init --tui writes a complete one; Native CLI and Ratatui apps explains every file. Both are on crates.io at 2.0.0.

Documentation

The Rust MF2 book explains the ecosystem, crate roles, application workflows and integrations. Its source and chapter links are in docs/. Start with getting started, then choose a path: how the crates fit together, call sites, delivery modes, switching language, accessibility, native CLI and TUI apps, migrating from leptos-fluent, upgrading from 1.x and versions; what changed, CHANGELOG.md. mdbook build renders the book; cargo xtask docs compiles its application examples for native and browser targets. Each crate's API reference remains in rustdoc.

The web examples: examples/demo-ssr (server-rendered, with a lazy route), examples/demo-islands and examples/demo-csr. The native example — a CLI with a Ratatui mode — is the application Native CLI and Ratatui apps builds, compiled by cargo xtask docs.

Developing

  • Plans and decisions: plans/. Start with plans/00-master-plan.md.
  • Current work order: plans/18-phase-10-work-order.md.
  • Vendored, pinned inputs (read-only): third_party/. The MF2 specification text is not among them: its license does not allow public redistribution, so cargo xtask spec-sync fetches it, at the pinned commit and checked against recorded digests, into target/xtask-cache/. The conformance tests need it; run the command once after cloning. UTS #35 Part 1, whose language-matching rules the locale matcher follows, is fetched the same way by cargo xtask uts35-sync; nothing builds from it.
cargo xtask spec-sync          # fetch the MF2 specification text (once)
cargo check --workspace        # the toolchain is pinned in rust-toolchain.toml
cargo xtask ci                 # everything the `ci` job runs, locally
cargo xtask docs               # compile every sample in docs/

License

MIT — see LICENSE. mf2 and mf2-locale-data ship data from Unicode's CLDR, so they are MIT AND Unicode-3.0 (each has its LICENSE-UNICODE). Vendored material under third_party/ keeps its own license (Unicode License v3), stated in each directory.

About

A MessageFormat 2 implementation for Rust.

Resources

Accessibility

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages