FormatJS: react-intl's monorepo, now three languages deep
The monorepo home to all of the FormatJS related libraries, most notably react-intl.
At a glance
- What is it?
- Fourteen thousand stars on a repository that publishes twenty scoped packages, writes some of them in Rust, and tests its parsers against test262.
- Who is it for?
- FormatJS is worth understanding as an infrastructure project rather than a library. The published surface is twenty scoped packages covering ICU message parsing, number and date formatting, segmenters and the framework bindings, and behind that sits a Rust and Python implementation of the same parsers, a native CLI with per-platform binaries, and a conformance suite that checks its output against test262.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 7, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The README is a package table, which is the point
The FormatJS README contains almost no prose. After three badges and a Slack link, it is a table with one row per published package: name, version, changelog and license. That is unusual, and it tells you how the project is organised. There is no single product here. There is a set of small packages that others depend on, and the table is the index.
The packages fall into recognisable groups. The formatting polyfills carry explicit names: `@formatjs/intl-datetimeformat`, `@formatjs/intl-numberformat`, `@formatjs/intl-pluralrules`, `@formatjs/intl-relativetimeformat`, `@formatjs/intl-listformat`, `@formatjs/intl-displaynames`, `@formatjs/intl-segmenter`, `@formatjs/intl-locale`, `@formatjs/intl-localematcher` and `@formatjs/intl-getcanonicallocales`. Below those sit the shared pieces, `@formatjs/intl` and `@formatjs/icu-messageformat-parser`, and the tooling, `@formatjs/cli`, `@formatjs/cli-lib` and `@formatjs/ts-transformer`, plus `babel-plugin-formatjs` outside the scope.
Each row links to a changelog inside the repository at `packages/<name>/CHANGELOG.md`, so the version history of each package is inspectable without leaving GitHub. Every one of those packages has its own version line and its own release cadence, which is the practical consequence of a monorepo with independent publishing.
Rust and Python crates inside a TypeScript monorepo
The tree contains `crates/` and `Cargo.toml` and `Cargo.lock` next to `pnpm-lock.yaml`, and the workspace manifest lists exactly what is in there:
[workspace]
resolver = "3"
[workspace.dependencies]
pretty_assertions = "1.4"
pyo3 = { version = "0.29.2", features = ["abi3-py312"] }
pythonize = "0.29.0"The member list names crates for an ICU skeleton parser, an ICU message format parser, ICU message format itself, Python bindings for each of those, a `formatjs_intl` pair, and two crates for the CLI plus a N-API layer, and it also pulls in an integration test package from inside the JavaScript packages directory. So the message parser exists in at least two implementations, one for JavaScript and one for Rust, and Python bindings on top of the Rust one via PyO3.
That is a deliberate performance bet. ICU message parsing is the part of i18n that runs on every render, and doing it in Rust behind a native module is a reasonable answer to that. The cost is a much heavier development story: you now need Bazel, a Rust toolchain and a Python toolchain to build the repository, rather than one language and one package manager.
Bazel, lefthook and a commit hook that runs everything
Build and test are both Bazel commands, and formatting and linting run through lefthook. The scripts in the root `package.json` are short enough to read in full:
"build": "bazel build //...",
"test": "lefthook run pre-commit --all-files && bazel test //..."The test script is the interesting one, because running the pre-commit hooks across all files before the test suite means lint, formatting and commit message checks are part of what you run to verify a change. `lefthook.yml` holds the hook definitions and `prepare` installs them, which is how a clone gets the same hooks a maintainer's machine has.
Around that there is a wide ring of infrastructure files, and reading them tells you what the project is worried about. There is `BUILD.bazel`, `MODULE.bazel` and `MODULE.bazel.lock` for Bazel module resolution, `buildbuddy.yaml` for remote caching, `renovate.json` for dependency updates, `release-please-config.json` and `.release-please-manifest.json` for versioning, `sgconfig.yml` and `codescythe.jsonc` for ast-grep rules, and `.oxlintrc.json` with `.oxfmtrc.json` for the oxlint and oxformatter tools. There are also `AGENTS.md`, `CLAUDE.md` and a `.agents/` directory at the root.
Correctness as a testing concern
Three directories in the tree exist purely to check that the implementation is right. `conformance-tests/` compares the implementations against each other and against the specification. `test262.BUILD` wires the JavaScript test262 suite into Bazel, which is the test suite the language itself uses. `fuzz/` is there for fuzzing the parsers, which makes sense once you consider that the input is arbitrary ICU message syntax coming from translators.
`benchmarks/` sits alongside them, and `knowledge-base/` and `docs/` hold the documentation the website is built from. The website script is `ibazel run //docs:serve`, so docs are Bazel targets and reload on change rather than a separate static site build.
There is also an `examples/` directory with three entries in the repository metadata: `rescripts/`, `strict-locale-type/` and `strict-message-types/`. Those names describe what they demonstrate. Strict message types is the compile-time guarantee that a message ID exists in your catalogue, which is the single most useful thing in the project for anyone who has shipped an untranslated string by accident.
Native binaries for the CLI, per platform
The `package.json` devDependencies include a long run of platform packages for the CLI: `@formatjs/cli-native-darwin-arm64`, `cli-native-linux-arm64`, `cli-native-linux-arm64-musl`, `cli-native-linux-x64`, `cli-native-linux-x64-musl` and `cli-native-win32-x64`. Every one of them is pinned as a `workspace:*` dependency, so they are built from this repository rather than downloaded.
That is the CLI shipping as a native binary per platform instead of a JavaScript bundle, and it connects back to the Rust crates named `formatjs_cli` and `formatjs_cli_napi` in the Cargo workspace. The practical effect for a user is that extracting messages or compiling them is a binary invocation rather than a Node startup plus a bundle load.
The rest of the workspace dependencies follow the same pattern, listing nearly every internal package as `workspace:*`. Combined with `pnpm-workspace.yaml`, that is what lets a single version bump propagate across the twenty packages, and it is why the release notes you see are often just a list of internal dependency bumps.
How the releases actually look
The three releases visible in the repository metadata are all dated 2026-09-19 and are all the same shape. `[email protected]`, `[email protected]` and `[email protected]`, published within seconds of each other. Each body is a dependency-only changelog: the previous line's workspace dependencies were updated, with `@formatjs/icu-messageformat-parser` bumped to 3.5.20 and `@formatjs/intl` to 6.1.2.
So the release process here is automated and boring in the good way. release-please handles versioning and notes per package, and a single change to a foundational package produces a coordinated set of releases across everything that depends on it. A reader who only looks at the top-level release feed will mostly see dependency bumps; the interesting changes are in the per-package changelogs the README table links to.
The repository is not archived, was pushed to on 2026-09-21, and reports 14,744 stars and 1,386 forks against only 2 open issues. That issue count is either very good triage or a low-traffic tracker compared with the star count, and the topics list, from globalization and i18n through translation and react, describes a project with a settled audience rather than one searching for adoption.
Editorial conclusion
FormatJS is worth understanding as an infrastructure project rather than a library. The published surface is twenty scoped packages covering ICU message parsing, number and date formatting, segmenters and the framework bindings, and behind that sits a Rust and Python implementation of the same parsers, a native CLI with per-platform binaries, and a conformance suite that checks its output against test262. For most teams the practical entry point stays react-intl, with the ICU message syntax as the thing to learn properly. Read the packages table in the README to see which piece you actually need, then read `crates/` if you are curious why the parser has its own language.
Frequently asked questions
What is FormatJS and what is react-intl's relationship to it?
FormatJS is the monorepo that contains react-intl and the libraries around it. react-intl is the React binding; the monorepo also publishes the ICU message format parser, polyfills for number, date, plural and list formatting, and tooling like a CLI. The repository description calls it the monorepo home to all of the FormatJS related libraries, most notably react-intl.
Which packages does the FormatJS monorepo publish?
Around twenty scoped packages. The formatting polyfills include `@formatjs/intl-numberformat`, `@formatjs/intl-datetimeformat`, `@formatjs/intl-pluralrules`, `@formatjs/intl-relativetimeformat`, `@formatjs/intl-listformat`, `@formatjs/intl-segmenter` and `@formatjs/intl-displaynames`. Shared pieces include `@formatjs/intl` and `@formatjs/icu-messageformat-parser`, and tooling includes `@formatjs/cli` and `@formatjs/ts-transformer`.
Why does FormatJS contain Rust and Python code?
The ICU message parser and skeleton parser are implemented in Rust under `crates/`, with Python bindings built through PyO3, and the root `Cargo.toml` lists those crates as workspace members. ICU message parsing runs on every render, so a native implementation is a performance decision, and the JavaScript packages call into it.
How does FormatJS verify that its formatting is correct?
The repository has three directories for this: `conformance-tests/` for comparing implementations, `test262.BUILD` for wiring the JavaScript test262 suite into Bazel, and `fuzz/` for fuzzing the parsers with arbitrary message syntax. There is also a `benchmarks/` directory. The root test script runs the lefthook pre-commit hooks across all files before running `bazel test //...`.
How do FormatJS releases work?
Each package is versioned and published independently using release-please, with a manifest and config file at the repository root. A change to a foundational package produces a coordinated set of releases: recent tags include `[email protected]`, `[email protected]` and `[email protected]`, all published on the same day with dependency-only changelogs.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/formatjs-formatjs)