An ICU MessageFormat parser and renderer, with no runtime dependencies.

Parsing and rendering are two separate steps. parse turns a message into a flat array of plain objects, and createRenderer builds a renderer that applies those tokens to a set of variables. Because the parsed result is a plain array, it can be cached or serialized — you can parse once at startup (or ahead of time) and render many times.

This package deliberately contains no date or number formatting of its own. You pass in the formatters you want, so it can sit on top of Intl, Globalize, or whatever your application already uses. If you don’t already have formatters, @onigoetz/intl-formatters provides Intl-based ones.

Usage

import { parse, createRenderer } from "@onigoetz/messageformat";
import {
  dateFormatter,
  numberFormatter,
  pluralGenerator,
} from "@onigoetz/intl-formatters";

// Parse the message once; the result is cacheable
const parsed = parse("{test, plural, offset:3 one{one test} other {# test} }");

// Create a renderer bound to a locale and a set of formatters
const render = createRenderer(
  "en",
  (locale, type) => pluralGenerator(locale, { type }),
  (locale, options, value: number) => numberFormatter(locale, options)(value),
  (locale, options, value: Date) => dateFormatter(locale, options)(value)
);

render(parsed, { test: 4 }); // => "one test"
render(parsed, { test: 7 }); // => "4 test"

Supported syntax

Syntax Example
Simple argument {name}
Plural {count, plural, one {book} other {books}}
Plural with offset {count, plural, offset:1 one {book} other {books}}
Exact match {count, plural, =0 {none} other {#}}
Ordinal {rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}
Select {gender, select, female {her} other {their}}
Number {price, number, percent}
Date / time {when, date, short}, {when, time, full}, {when, datetime, long}

Inside a plural or selectordinal block, # renders the argument value minus the offset. Plural selectors are zero, one, two, few, many, other, or an exact =N match.

number, date, time and datetime are handed to the formatters you supplied. Any other argument type is passed through as an unknown formatter and renders as an empty string, rather than throwing.

Module format

This package is ESM-only. It requires Node.js >= 22.12, where CommonJS consumers can still load it with require() — no dynamic import() needed.