Unicode Messageformat
Use when reading, writing, or debugging Unicode MessageFormat 2 (MF2) message syntax — the Unicode Consortium's standard for localizable messages with placeholders, plural/selection matching, and formatting functions. Independent of any library; see [[sveltia-i18n]] for a Svelte library built on it and [[locale]] for broader i18n/l10n concepts.
MessageFormat 2 (MF2) is the Unicode Consortium's standard message syntax for localizable, dynamic messages — the successor to the older ICU MessageFormat ("MessageFormat 1"). It standardizes how a message interpolates values, applies locale-aware formatting, and selects among grammatical variants (plurals, gender, ordinals) — solving the same problem Locale describes generally (don't hand-roll plural logic or string concatenation across a translated phrase), but as a concrete, parseable syntax rather than a set of principles.
Simple messages
A message with no placeholders is just text:
Hello, world!
Two characters are special and need escaping if used literally: {, }, and \.
Placeholders, variables, and functions
A placeholder is {$variableName}; a function transforms it with a :functionName annotation and optional key=value options:
Today is {$date :datetime weekday=long}.
Built-in functions include :number, :integer, :percent, :currency, :string, :date, :time, :datetime — each accepts options like style=long or minimumFractionDigits=2 that control formatting, the same way Intl.NumberFormat/Intl.DateTimeFormat options do (see Locale for the underlying Intl APIs).
Complex messages: .input and .local
A message that needs to declare or compute values before the message body uses a complex message form — a . declaration section followed by the message text in {{...}}:
.input {$date :datetime weekday=long month=medium day=short}
.local $numPigs = {$pigs :integer}
{{On {$date} you had this many pigs: {$numPigs}}}
.input {$var :function options}declares an external variable and annotates it with a function up front, so the annotation isn't repeated everywhere the variable is used..local $name = {expression}binds a computed value to a local variable for reuse in the message body.
Matching: .match
.match selects among message variants based on one or more values — this is how MF2 expresses plurals, gender, and other selection logic without a hand-written count === 1 ? ... : ... ternary:
.input {$count :integer}
.match $count
0 {{You have no notifications.}}
one {{You have {$count} notification.}}
* {{You have {$count} notifications.}}
.match supports:
- Plural/ordinal categories — CLDR's
zero,one,two,few,many,other(a locale only uses the subset that applies to it — see Locale's pluralization section for why English's two-category system doesn't generalize), plus exact-number matches like literal0above. - String/enum matching — case-sensitive matching against literal string values (e.g. a
$gendervariable matched againstfemale,male, and a catch-all). - Multiple selectors — matching on more than one variable at once, with one variant key per selector per line.
- **The catch-all
*** — required as the final variant so every possible value has somewhere to fall through to.
Markup
MF2 also defines application-defined markup tags for rich formatting inside a message, e.g. {#bold}text{/bold} — the tags themselves carry no built-in meaning; the consuming application decides what #bold maps to (bold text, a <strong> element, a terminal escape code, etc.).
Common pitfalls
- Writing ICU MessageFormat 1 syntax and expecting it to work as MF2.
{count, plural, one {# item} other {# items}}is ICU MF1 syntax, not valid MF2 — MF2 needs.input {$count :integer}/.match $countwithone {{...}}/* {{...}}variants instead. - Referencing a variable without the
$sigil.{count}is not a valid MF2 placeholder; it's{$count}. - **Omitting the
*catch-all variant** in a.matchblock — every.matchneeds a default fallback, since real-world plural category sets vary by locale and an incomplete variant list will fail to match on some locale/value combination. - Assuming every locale needs all six plural categories. Most languages use two or three; over-specifying variants a locale never selects is harmless but unnecessary, while under-specifying variants a locale does select breaks that locale.
Learn more
- MessageFormat 2 quick start
- MF2 specification (Unicode's working group repository)
- Sveltia I18n for a Svelte library that uses MF2 as its message syntax.
- Locale for broader i18n/l10n concepts (BCP 47, CLDR, RTL) independent of MF2.