ICU MessageFormat
The ICU plural format
ICU MessageFormat puts the whole plural inside one message:
{count, plural, one {…} other {…}}. The
branches are named after CLDR categories, the runtime picks the
right one for the locale and the number, and # stands for the count
itself. Because the value is self contained, the plural survives any tool that
moves the file, which made ICU the de facto interchange encoding.
This page covers the syntax, where it came from, and the same plural written in JSON, YAML and properties files.
Examples
The same plural, three languages
The message grows with the language: English needs two branches, Polish four, Arabic six. Switch the file format to see the encoding is identical everywhere; only the quoting changes.
"items": "{count, plural, one {# item} other {# items}}" "items": "{count, plural,
one {# artykuł}
few {# artykuły}
many {# artykułów}
other {# artykułu}}" "items": "{count, plural,
zero {لا عناصر} one {عنصر واحد}
two {عنصران} few {# عناصر}
many {# عنصرًا} other {# عنصر}}" items: "{count, plural, one {# item} other {# items}}" items: "{count, plural,
one {# artykuł} few {# artykuły}
many {# artykułów} other {# artykułu}}" The braces mean the value must be quoted in YAML; a bare brace would start a map.
cart.items={count, plural, one {# item} other {# items}} cart.items={count, plural, one {# artykuł} \\
few {# artykuły} many {# artykułów} other {# artykułu}} How it works
Selection happens at runtime, by rule
The library formatting the message asks CLDR which category the number belongs
to in the current locale, then renders that branch. Polish 22 is
few, Polish 25 is many, and the translator never
writes an if statement: the categories in the message are the whole contract.
Beyond plural, the same syntax offers select for
branching on things like grammatical gender, selectordinal for
1st/2nd/3rd style ordinals, and explicit matches like =0 for
special casing exact values.
Origin
Where it came from
The lineage starts with Java’s MessageFormat, which shipped
with JDK 1.1 in 1997 and could already interpolate and branch on numbers.
IBM’s ICU project (International Components for Unicode)
carried the idea further and grounded plural selection in CLDR’s category
data, replacing per language special cases with one rule set. JavaScript
implementations, FormatJS and react-intl among them, brought the syntax to the
web, and Flutter adopted it for ARB messages.
Trade offs
Strengths and limits
Self contained
The plural lives in the value, so any pipeline that can move a string can move the whole plural without understanding it.
More than plurals
select, selectordinal and nested arguments cover gender, ordinals and combinations no key convention can express.
Braces are hostile to humans
A translator editing raw ICU can break a message with one brace. Good tooling shows per form fields and reassembles the string.
In locamorph
How locamorph treats ICU
ICU is the default plural format on export. On import, ICU plurals decompose
into per form fields for properties and
ARB files; in JSON and
YAML the message imports whole, every form intact in
one string. Messages using select, offset: or
explicit =N matches always travel whole, because splitting them
would drop meaning.
Further reading
- ICU MessageFormat documentation
- CLDR plural rules, the category definitions
- FormatJS ICU syntax guide, the web dialect
Related
Keep going
Edit forms, not braces
Import ICU messages and translators get one field per form; exports rebuild the message exactly.