# Messageformat: two message format standards in one JavaScript monorepo

> The messageformat monorepo ships a JavaScript implementation of ICU MessageFormat 1, where messages are transpiled into functions, and of Unicode MessageFormat 2, where they are parsed at runtime, plus converters and bundler plugins for both.

**messageformat/messageformat** — ICU MessageFormat and Unicode Messageformat for Javascript

- Repository: https://github.com/messageformat/messageformat
- Website: https://messageformat.github.io
- Stars: 1,772 · Forks: 162
- Language: TypeScript
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/messageformat-messageformat

## What MessageFormat is for

The premise of the project is that the experience and subtlety of your program's text can be important, and that Messageformat is a mechanism for handling both pluralization and gender in your applications.

The second claim in the README is the stronger one: it can lead to much better translations, because it is designed to support all the languages included in the Unicode CLDR. That is the difference between a library that offers a plural rule for a handful of languages and one that implements the CLDR plural rules, which is a large and irregular set. A language with two plural forms is simple, one with six is not, and the naive pluralisation helper in a codebase is usually wrong for the languages nobody on the team speaks.

The problem being solved is the one every application hits the moment it leaves one language. A message like you have {count} items has one form in English and several in Polish, Russian or Arabic, and a gender inflection for a noun changes the shape of the sentence rather than just a word. Building that by hand means either a per-language branch in the code, which translators cannot see, or a message format that translators can work with directly.

The project is an OpenJS Foundation project and follows the OpenJS Code of Conduct. The homepage is messageformat.github.io, the licence is MIT, the last push was on 2026-10-12, and the recent releases span several packages at once, with messageformat at 4.1.0, @messageformat/resource at 0.1.0 and @messageformat/core at 3.4.1.

## MF1 compiles messages into functions, MF2 parses them at runtime

The monorepo holds two implementations of two different standards, and the difference in approach is the interesting part.

The MF2 side is the messageformat package itself, described as the MF2 parser, runtime, and polyfill for Intl.MessageFormat. So MF2 messages are parsed and interpreted, and the package also provides a polyfill so that an environment without Intl.MessageFormat can still run them. Around it sit @messageformat/fluent, which compiles Fluent sources into MF2 resources, @messageformat/icu-messageformat-1, which compiles MF1 sources into MF2 formatters, @messageformat/resource, a Message resource file parser, and @messageformat/xliff, marked as ALPHA, which converts between XLIFF 2 and MF2.

The MF1 side is the older and more complete toolchain. @messageformat/core is described as the core library that transpiles MessageFormat strings into JavaScript functions, and @messageformat/parser parses MessageFormat source strings into an AST, which is the first step of that transpilation. @messageformat/runtime holds the runtime dependencies of the compiled message modules, which is what lets the generated functions stay small and shareable.

So the two families differ in when the work happens. MF1 parses and compiles at build time into JavaScript functions, and the runtime is small. MF2 parses and evaluates at runtime, which is more flexible and pays for it in runtime cost and in the need for a polyfill.

The bridge between them is @messageformat/icu-messageformat-1. An application that has thousands of existing ICU messages does not rewrite them to move to MF2, it compiles them, which is the migration path the package set is built around.

## The packages that plug into a build

Most of the MF1 packages exist to fit into a bundler pipeline, and the two plugin packages are the entry point for most projects.

@messageformat/loader is a webpack loader for JSON, YAML and .properties message files, and rollup-plugin-messageformat is the Rollup equivalent for the same three formats. Between them, they cover the two bundlers most JavaScript projects use, and both accept the file formats that translation tools tend to produce.

@messageformat/react provides React hooks and other bindings for messages, so a component can read a message without reaching for a global. @messageformat/cli is a command-line client for the library, which is the tool for one-off conversions and for inspecting what the compiler produces.

@messageformat/convert is the package that handles the outside world, converting other localization formats into ICU MessageFormat. That is the piece that matters when a translation agency or an existing TMS is in the loop, because it is what lets a translator keep working in the format they already use.

Two smaller packages handle the ICU skeletons. @messageformat/date-skeleton and @messageformat/number-skeleton are tools for working with ICU DateFormat skeletons and ICU NumberFormat skeletons respectively, which is how you express a date or number format once and let the locale decide the output, rather than hard-coding a format per language.

The getting started section points to per-scenario guides for the command line, Rollup, Webpack and React, and then to the examples directory and the API documentation for anything more involved. The Format Guide covers the ICU MessageFormat syntax and the Usage Guide covers further options for integrating messageformat into a workflow around UI texts and translations.

## The build and test setup of a sixteen-package monorepo

The workspace list in the root package.json enumerates sixteen packages, ten for MF1 and five for MF2, plus a shared test/utils package. The directories are named mf1/packages/* and mf2/*, so the two standards are physically separated in the tree, and a change to one does not touch the other.

The build is npm workspaces, with build running across all of them and a build:mf2 target that builds only the five MF2 packages. That split exists because the two halves have different toolchains: MF1 has its own build steps and the MF2 packages use typedoc for documentation, so the docs:mf1 target runs api-documenter to produce markdown into docs/api, and the MF2 docs use typedoc with --emit none.

The documentation pipeline has some interesting steps. A postdocs:mf1 script runs set-api-docs-frontmatter over the generated markdown and then add-mf2-redirects.mjs, which adds redirects from the MF1 API pages to the MF2 ones. That is the same migration concern as the compiler bridge, handled at the documentation level: the published API reference moves, and old links keep working.

The tests run on vitest, with vitest run as the default and a separate test:browsers target using a different config file, .config/vitest.browsers.config.mts. The browser run is what verifies the polyfill path, since a polyfill that only works in Node is not much of a polyfill.

There is a pretest hook that builds the ESM version of the MF1 runtime workspace before the tests run, so the runtime modules under test are the ones the tests are supposed to exercise. Linting is eslint and formatting is prettier, with the prettier configuration in the root package.json setting arrowParens to avoid, singleQuote to true and trailingComma to none.

The repository also carries a SECURITY.md, a CONTRIBUTING.md, a .gitmodules file, and a top-level test directory alongside the per-package tests.

## The gaps you should know about before committing

The first limitation is the alpha on the XLIFF converter. @messageformat/xliff is marked ALPHA in the package list, and it is the package that converts between XLIFF 2 and MF2. If your translation workflow depends on that conversion, you are depending on the least settled part of the project.

The second limitation is the two-standard split, which is a real decision cost. MF1 and MF2 are not the same thing with different names, and both are supported, which means the docs, the tooling and the community all have to talk about two things. A team that picks the wrong one migrates later, and the bridge package exists precisely because that migration is not free.

The third is that the MF1 toolchain is the one with the complete story. The compiler, the parser, the runtime, the CLI, the bundler plugins, the React bindings and the format converters are all MF1. On the MF2 side the pieces are the parser and runtime with its polyfill, the Fluent compiler, the MF1 bridge, the resource parser and the alpha XLIFF converter. If you want the whole toolchain today, MF1 is it.

The fourth is the format scope itself. MessageFormat handles pluralisation and gender, and that is the feature set. It does not replace a translation memory or a term base. The claim about better translations depends on the translator working with the format, which is a process change and not just a library change.

On maintenance, the repository is not archived, the last push was on 2026-10-12, and the release activity is spread across the packages with @messageformat/resource at 0.1.0, which is a very early version for a package that others depend on.

## Conclusion

The messageformat monorepo fits a JavaScript team that needs correct pluralisation and gender inflection across many locales, and that has to interoperate with translators working in XLIFF, since the MF1 to MF2 converter and the XLIFF 2 conversion cover the round trip. It is a poor fit if your product has one locale and simple strings, because the tooling cost is real. Before adopting, decide which standard you are committing to, MF1 is the established one and MF2 is where the work is going, and read the Format Guide and the Usage Guide, since the syntax and the integration workflow are separate decisions.

## FAQ

### What is the messageformat project?

It is a monorepo providing JavaScript packages for both Unicode MessageFormat 2 and ICU MessageFormat 1. It handles pluralization and gender in application text, and is designed against the Unicode CLDR plural rules so translations behave correctly across many languages.

### What is the difference between the MF1 and MF2 packages?

The MF1 packages include @messageformat/core, which transpiles MessageFormat strings into JavaScript functions, with the parser and runtime supporting it. The MF2 packages include messageformat itself, which is an MF2 parser, runtime and polyfill for Intl.MessageFormat, and @messageformat/icu-messageformat-1 compiles MF1 sources into MF2 formatters as a bridge.

### How do I integrate messageformat with a bundler?

Use @messageformat/loader for webpack or rollup-plugin-messageformat for Rollup, both of which handle JSON, YAML and .properties message files. The getting started guides cover the command line, Rollup, Webpack and React separately, and @messageformat/react provides React hooks for messages.

### How do I convert messages from another localization format?

The @messageformat/convert package converts other localization formats into ICU MessageFormat, and the @messageformat/cli is a command-line client for the library. For XLIFF 2, @messageformat/xliff converts between XLIFF 2 and MF2, but that package is marked as alpha.

### How does the messageformat monorepo run its tests?

Tests run with vitest run, with a separate test:browsers target using .config/vitest.browsers.config.mts, which is what checks the polyfill path in a real browser. A pretest hook builds the ESM version of the MF1 runtime workspace first, and the lint and format commands are eslint and prettier.

## Sources

- [License: MIT](https://github.com/messageformat/messageformat/blob/main/LICENSE)
- [messageformat/messageformat on GitHub](https://github.com/messageformat/messageformat)
- [Project website](https://messageformat.github.io)
- [README](https://github.com/messageformat/messageformat/blob/main/README.md)
- [Releases](https://github.com/messageformat/messageformat/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/messageformat-messageformat
