messageformat vs intl-messageformat
ICU MessageFormat Libraries for JavaScript Internationalization
messageformatintl-messageformatSimilar Packages:

ICU MessageFormat Libraries for JavaScript Internationalization

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
messageformat312,0081,761172 kB194 months agoApache-2.0
intl-messageformat014,684105 kB21a month agoBSD-3-Clause

Internationalization Message Formatting: intl-messageformat vs messageformat

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.

🧩 Core Architecture: Runtime Parser vs Precompiled Functions

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."

🌍 Locale Support and Intl Dependency

intl-messageformat depends directly on the browser's or Node.js environment's Intl implementation. This means:

  • No extra polyfills needed if targeting modern environments.
  • Behavior may vary slightly across browsers if Intl implementations differ.
  • Cannot customize number/date formatting logic beyond what 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');

⚙️ Build-Time Integration and Tooling

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' });

🔁 Message Syntax and ICU Compliance

Both libraries support the core ICU MessageFormat syntax, including:

  • Simple placeholders ({name})
  • Pluralization ({count, plural, one {...} other {...}})
  • Select (gender/enum-based choices: {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().

📦 Bundle Size and Performance Trade-offs

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);
}

🛠️ Error Handling and Debugging

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.

🌐 Real-World Usage Scenarios

Scenario 1: Large-Scale Application with Hundreds of Messages

  • Best choice: messageformat
  • Why? Precompilation ensures consistent performance regardless of message count, and build-time validation catches errors early.

Scenario 2: Lightweight App Targeting Modern Browsers

  • Best choice: intl-messageformat
  • Why? Smaller bundle size and reliance on native Intl reduce overhead, especially if message count is low.

Scenario 3: Need Custom Formatters (e.g., markdown, truncation)

  • Best choice: messageformat
  • Why? Only messageformat allows registering custom formatting functions within the ICU string.

Scenario 4: Server-Side Rendering with Node.js

  • Both work, but:
    • Use intl-messageformat if you trust Node.js’s Intl support (v13+ has full ICU).
    • Use messageformat if you need guaranteed consistency across Node versions or want to avoid Intl polyfills.

📌 Summary Table

Featureintl-messageformatmessageformat
ParsingRuntimeBuild-time (precompiled functions)
Intl DependencyYes (uses native Intl)No (bundles its own locale data)
Custom Formatters❌ Not supported✅ Supported
Bundle ImpactSmall library, CPU cost per renderLarger bundles, zero runtime parse
Build ToolingMinimalRich (CLI, bundler plugins)
Error DetectionRuntime (unless cached)Build-time (if precompiled)

💡 Final Recommendation

Choose intl-messageformat if you:

  • Target modern environments with solid Intl support
  • Prefer smaller bundles over runtime CPU cost
  • Don’t need custom formatters inside messages

Choose messageformat if you:

  • Want maximum runtime performance via precompilation
  • Need custom formatting logic within ICU strings
  • Prefer catching message syntax errors at build time
  • Require consistent behavior across all JavaScript environments

Both are mature, well-maintained solutions — the right pick depends on whether you prioritize bundle size (intl-messageformat) or runtime performance and flexibility (messageformat).

How to Choose: messageformat vs intl-messageformat

  • 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.

  • intl-messageformat:

    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.

README for messageformat

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 messageformat package 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/core instead.