Quick Start

MessageFormat 2.0 is designed to enable localization of dynamic messages across different human languages. This page walks through the syntax of Message Format 2 messages, giving an overview of what Message Format 2 is capable of.

To use Message Format 2.0 in a project, follow these guides to set up Message Format 2:

You can also try out Message Format 2 in the online playground:

Try in Playground

Basic Syntax Jump to heading

Text Jump to heading

A simple message is just plain text. All Unicode characters can be used in text. The only special characters are curly braces {} — they need to be escaped. Messages can also not start with the . character.

Hello, World!

Escapes Jump to heading

Escape special characters with a backslash \. In text, only curly braces need to be escaped. In literal quotes, | may also be escaped.

Curly braces: \{ and \}

Placeholders Jump to heading

Placeholders are used to dynamically insert values into messages. Placeholders are enclosed in curly braces {}.

To insert a variable into a message, use the variable name inside curly braces, preceded by a dollar sign $.

Hello, {$name}!
{ "name": "World" }

The values for variables are provided by the invoker of the message. They could be numbers, strings, dates, or even lists.

Functions Jump to heading

How placeholders behave can be modified with functions. Functions are prefixed with a colon :. Functions are often used to format values in particular ways.

It is the {$today :datetime} today.
{ "today": "2024-07-01T12:00:00Z" }

Message Format 2 has multiple built in functions. These allow you, for example, to format numbers in a locale appropriate way. See the full list of built-in functions.

Function Options Jump to heading

Functions can have options (arguments). Options are key-value pairs separated by an equal sign =. Options are separated by spaces.

It is the {$today :datetime dateStyle=long timeStyle=long} today.
{ "today": "2024-07-01T12:00:00Z" }

Options can be used to modify the behaviour of functions, for example changing whether time formatting should use AM/PM, or the 24 hour clock.

Literals Jump to heading

In addition to variables, placeholders can contain literals. Literals are also used as the values of options. Literals can be text or numbers.

Number Jump to heading

Number literals can represent any integer or decimal number at arbitrary precision.

I eat {1.5} bananas.

Number literals are often useful when combined with the built-in :number or :integer functions, enabling formatting in a locale aware way:

The total was {0.5 :number style=percent}.

Decimals, and scientific notation are also supported in number literals:

{1.3e-10 :number notation=engineering}

Unquoted Text Jump to heading

Unquoted text literals are simple strings. They can contain all characters except whitespace or special characters.

Hello, {world}!

This is most often useful when passing a simple string as the value to a function option - here it is h12:

It is {$now :datetime hourCycle=h12}
{ "now": "2024-07-01T12:00:00Z" }

There is no boolean literal in Message Format 2. Options with boolean values usually use the text literals true and false to represent the two boolean states.

Quoted Text Jump to heading

Text literals that need to contain spaces or special characters like { or @ can be wrapped in |, the quote character in Message Format 2 syntax.

My name is {|John Doe|}.

Quoted text can also contain escapes, just like in simple messages. In quoted text, only | must be escaped.

This is the {|pipe \| character|}.

Markup Jump to heading

Markup tags can be used to add rich text formatting to messages. There are three types of markup tags: opening, closing, and self-closing.

This is {#bold}bold{/bold}.
This is a star: {#star-icon /}

Message Format 2 does not define any specific markup tags. Instead, it is up to the application to define the tags that are used in messages. There is also no requirement that opening and closing tags must match, or that they are used in a hierarchical way.

Good to know: Because the meaning of markup tags is defined by the application, they enable portable rich text formatting across different platforms. For example, a {#bold} tag could be rendered as a <b> tag in HTML, as a bold font style in a terminal application, or as a bold text node in a mobile app.

Markup Options Jump to heading

Markup tags can have options, just like functions. Options are key-value pairs separated by an equal sign =. Options are separated by spaces. Options can be used to modify the behaviour of markup tags, for example changing the target of a link.

This is a {#link to="home"}link{/link}.

It is best practice to not mix styles into messages too much - instead, use markup tags to add semantic meaning and selectors to text, and let the application decide how to render the message. This makes it easier to change the styling of messages across an application without having to modify all translations of all messages in the application.

This is a {#error}critical{/error} message.

Matchers Jump to heading

Matchers are used to select different variants of a message based on a value.

.match {$count :integer}
one {{You have {$count} notification.}}
*   {{You have {$count} notifications.}}
{ "count": 1 }

Matchers start with a .match keyword, followed by the value to match on, in curly braces {}. The value can be a literal or variable. An annotation is required to specify the type of the value to match on.

After the match value, there is a block of variants. Each variant starts with the value to match on, followed by a message enclosed in double curly braces {{}}. The special value * is used to match any value.

String Matching Jump to heading

The match behaviour for a given value is based on the type of the value. For strings, a literal case-sensitive match is used. This is useful for matching on enum-like values, like pronouns in English:

.match {$pronoun :string}
he  {{He is a good person.}}
she {{She is a good person.}}
*   {{They are a good person.}}
{ "pronoun": "they" }

Values in selectors are literals. This means to have a selector that matches on a space or special character, it must be quoted.

.match {$char :string}
| |  {{You entered a space character.}}
|\|| {{You entered a pipe character.}}
*    {{You entered something else.}}
{ "char": "|" }

Number Matching Jump to heading

Other values can have more complex match behaviours. Numbers can match on exact values, but also on locale specific plural categories:

.match {$count :number}
one {{You have {$count} notification.}}
*   {{You have {$count} notifications.}}
{ "count": 1 }

This is specifically useful for languages with complex plural rules, like Czech:

.match {$numDays :number}
one  {{{$numDays} den}}
few  {{{$numDays} dny}}
many {{{$numDays} dne}}
*    {{{$numDays} dní}}
{ "numDays": 2 }

In addition to plural rules, numbers can also match on ordinal rules, or just on exact values:

.match {$count :number select=ordinal}
one {{You are {$count}st.}}
two {{You are {$count}nd.}}
few {{You are {$count}rd.}}
*   {{You are {$count}th.}}
{ "count": 3 }

Muli-Value Matching Jump to heading

Matchers can also match on multiple values. This is useful when a message needs to be selected based on multiple different values:

.match {$pronoun :string} {$count :number}
he one   {{He has {$count} notification.}}
he *     {{He has {$count} notifications.}}
she one  {{She has {$count} notification.}}
she *    {{She has {$count} notifications.}}
* one    {{They have {$count} notification.}}
* *      {{They have {$count} notifications.}}
{ "pronoun": "she", "count": 3 }

When specifying multiple values to match on, the values are separated by spaces. Selectors also need to be separated by spaces.

Only a single matcher can be used in a message. Matchers can contain multiple values to match against, and can contain multiple variants.

Local Declarations Jump to heading

A local declaration binds a variable to the value of an expression. .local is like const in JavaScript. Think of it as a convenient way to reuse the result of an operation without repeating it.

.local $x = {|This is an expression|}
.local $y = {$now :datetime dateStyle=long}
{{{$x} from {$y}}}
{"now": "2021-04-03"}

An interesting case that demonstrates the usefulness of .local is .match statements with multiple variants where you need to reuse a value.

.local $date = {$dt :datetime dateStyle=long}
.match {$pronouns :string}
he  {{He joined on {$date}.}}
she {{She joined on {$date}.}}
*   {{They joined on {$date}.}}
{"dt": "2021-04-03", "pronouns": "she"}

Input Declarations Jump to heading

An input declaration binds a variable to an external input value. It's not required in order to use external input but is a declarative way to verify the presence of (and even the type of) any input.

.input {$count :number}
{{{$count} Mississippi}}
{"count": 5}

Furthermore, it's really useful when you want to apply certain formatting options onto an input value everytime it's used within the message.

.input {$x :number style=percent}
{{{$x}}}
{"x": 0.42}