intl-messageformat and messageformat are both JavaScript libraries that implement the ICU MessageFormat standard for internationalizing user-facing text. They enable developers to handle complex translation scenarios involving variables, plurals, gender selection, and number/date formatting in a locale-aware way. intl-messageformat relies on the native Intl API available in modern JavaScript environments and parses message patterns at runtime, while messageformat compiles ICU message strings into optimized JavaScript functions during build time, offering greater runtime performance and support for custom formatters.
When building multilingual web applications, handling complex translation strings with variables, plurals, and gender agreement is a common challenge. Both intl-messageformat and messageformat aim to solve this using the ICU MessageFormat standard, but they differ significantly in architecture, runtime requirements, and developer experience. Let’s compare them in depth.
intl-messageformat parses ICU messages at runtime using JavaScript. It relies on the built-in Intl APIs (like Intl.NumberFormat and Intl.DateTimeFormat) for formatting values, making it lightweight in bundle size but requiring parsing overhead on each render.
// intl-messageformat: Parse and format at runtime
import IntlMessageFormat from 'intl-messageformat';
const message = new IntlMessageFormat('Hello, {name}! You have {unreadCount, plural, one {# message} other {# messages}}.');
const output = message.format({ name: 'Alice', unreadCount: 5 });
// Output: "Hello, Alice! You have 5 messages."
messageformat compiles ICU messages into pure JavaScript functions ahead of time (typically during build). This eliminates runtime parsing, resulting in faster execution but larger bundle sizes due to generated code.
// messageformat: Precompile messages into functions
import MessageFormat from 'messageformat';
const mf = new MessageFormat('en');
const compileFn = mf.compile('Hello, {name}! You have {unreadCount, plural, one {# message} other {# messages}}.');
const output = compileFn({ name: 'Alice', unreadCount: 5 });
// Output: "Hello, Alice! You have 5 messages."
intl-messageformat depends directly on the browser's or Node.js environment's Intl implementation. This means:
Intl implementations differ.Intl provides.// intl-messageformat uses native Intl under the hood
const msg = new IntlMessageFormat('{date, date, long}', 'en-US');
msg.format({ date: new Date() }); // Uses Intl.DateTimeFormat
messageformat includes its own locale data and formatting logic, independent of the host environment's Intl. This gives you consistent behavior everywhere but increases bundle size.
// messageformat bundles its own locale data
import MessageFormat from 'messageformat';
// Locale data must be explicitly loaded if not using default
MessageFormat.locale.en = require('messageformat-formatters');
const mf = new MessageFormat('en');
intl-messageformat is typically used as a runtime-only library. There’s no official build tooling to extract or precompile messages, so all parsing happens in the browser or server.
messageformat offers strong build-time tooling support. The messageformat-cli package can extract messages from source files and compile them into optimized JavaScript modules, which integrates well with bundlers like Webpack or Rollup.
# Example: Precompile messages with messageformat-cli
npx messageformat --locale=en --output-dir ./i18n ./src/messages.json
This results in importable modules:
// Generated by messageformat-cli
import messages from './i18n/en/messages.js';
const greeting = messages.welcome({ name: 'Bob' });
Both libraries support the core ICU MessageFormat syntax, including:
{name}){count, plural, one {...} other {...}}){gender, select, male {...} female {...} other {...}})However, messageformat supports nested message structures and custom formatters more flexibly:
// messageformat: Custom formatter example
const mf = new MessageFormat('en');
mf.addFormatters({
upcase: (v) => String(v).toUpperCase()
});
const fn = mf.compile('Hello, {name, upcase}!');
fn({ name: 'alice' }); // "Hello, ALICE!"
intl-messageformat does not support custom formatters. All formatting must go through standard Intl types (number, date, time) or be handled externally before passing values to .format().
Because intl-messageformat parses messages on every use (unless you cache the IntlMessageFormat instance), it incurs CPU cost during rendering. However, the library itself is small since it delegates to native Intl.
messageformat shifts that cost to build time. The compiled functions execute instantly, but each message becomes a small JavaScript function in your bundle. For apps with hundreds of messages, this can noticeably increase bundle size.
💡 Best practice with
intl-messageformat: Cache parsed message instances.
// Cache to avoid reparsing
const messageCache = new Map();
function formatMessage(pattern, locale) {
const key = `${locale}:${pattern}`;
if (!messageCache.has(key)) {
messageCache.set(key, new IntlMessageFormat(pattern, locale));
}
return messageCache.get(key);
}
intl-messageformat throws clear errors for malformed ICU syntax at parse time, which helps catch mistakes early during development.
messageformat also validates syntax during compilation, but if you use runtime compilation (not recommended in production), errors may surface later.
Both provide decent error messages, but messageformat’s build-time compilation means syntax errors are caught before deployment.
messageformatintl-messageformatIntl reduce overhead, especially if message count is low.messageformatmessageformat allows registering custom formatting functions within the ICU string.intl-messageformat if you trust Node.js’s Intl support (v13+ has full ICU).messageformat if you need guaranteed consistency across Node versions or want to avoid Intl polyfills.| Feature | intl-messageformat | messageformat |
|---|---|---|
| Parsing | Runtime | Build-time (precompiled functions) |
| Intl Dependency | Yes (uses native Intl) | No (bundles its own locale data) |
| Custom Formatters | ❌ Not supported | ✅ Supported |
| Bundle Impact | Small library, CPU cost per render | Larger bundles, zero runtime parse |
| Build Tooling | Minimal | Rich (CLI, bundler plugins) |
| Error Detection | Runtime (unless cached) | Build-time (if precompiled) |
Choose intl-messageformat if you:
Intl supportChoose messageformat if you:
Both are mature, well-maintained solutions — the right pick depends on whether you prioritize bundle size (intl-messageformat) or runtime performance and flexibility (messageformat).
Choose messageformat if you need maximum runtime performance through precompiled message functions, require custom formatting logic (like uppercase or markdown) directly within ICU strings, or want to guarantee consistent internationalization behavior across all JavaScript environments—including older ones—by bundling locale data yourself.
Choose intl-messageformat if you're building an application that targets modern browsers or Node.js environments with full Intl support, and you prefer a smaller bundle footprint over runtime parsing overhead. It’s ideal when you don’t need custom formatters inside your translation strings and are comfortable caching parsed message instances to avoid repeated parsing costs.
This library provides a formatter and other tools for Unicode MessageFormat 2.0 (MF2), the new standard for localization developed by the MessageFormat Working Group.
This includes a formatter that can be used as a polyfill for the proposed ECMA-402 Intl.MessageFormat formatter.
The API provided by this library is current as of the LDML 48 (October 2025) version of the MF2 specification.
npm install --save messageformat
import { MessageFormat } from 'messageformat';
import { DraftFunctions } from 'messageformat/functions';
const msg = 'Today is {$today :datetime dateStyle=medium}';
const mf = new MessageFormat('en', msg, { functions: DraftFunctions });
mf.format({ today: new Date('2022-02-02') });
// 'Today is Feb 2, 2022'
The library also provides a number of other tools and utilities for MF2, such as:
MF2 data model conversion tools
import { parseMessage, stringifyMessage } from 'messageformat';
MF2 data model validation and transformation tools
import { validate, visit } from 'messageformat';
Concreate Syntax Tree (CST) tools for MF2
import { parseCST, messageFromCST, stringifyCST } from 'messageformat/cst';
Utilities for building custom function handlers for MF2
import {
DraftFunctions,
asPositiveInteger,
asString
} from 'messageformat/functions';
In addition to supporting MF2 syntax, compilers and formatting function runtimes are also provided for ICU MessageFormat and Fluent messages:
For more information on the types and functions provided by this package, see the documentation site.
[!IMPORTANT] The v4 release of the
messageformatpackage has an entirely different API compared to its earlier major releases, which were built on top of ICU MessageFormat, aka "MF1". For that, please see@messageformat/coreinstead.