Guide for Translators

MessageFormat 2 (MF2) is designed to enable localization of dynamic messages across different human languages. The syntax of MF2 messages is designed to make it clear which parts of a message needs to be translated, and which parts are code that doesn't need to be translated.

This guide is aimed at translators; there is also developer documentation.

Simple messages Jump to heading

Messages use curly brackets -- { and } -- to separate code from text. Take a look at the following message:

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

The text Hello, {$name}! is the message. The text { "name": "World" } is not part of the message; in this interactive demonstration, you can edit it to experiment with how the message looks depending on runtime information.

The text {$name} is an example of a placeholder, which should not be translated. The word "name" here is an example of a variable, which is used by code invoking the message formatter to specify the value of some piece of data that is only known after the user has provided some input.

Because the text "Hello, " is outside the curly braces, it's not a placeholder and it should be translated.

When translating a message, it may be necessary to move a placeholder to make a grammatically correct result. It would be perfectly fine to edit the message Hello, {$name} to be {$name}, hello!.

When translating a message, it's important to make sure the curly braces still match.

Placeholders with functions Jump to heading

A placeholder can also contain a function name, which also shouldn't be translated. A function name begins with a colon, :.

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

In this example, {$today :datetime} is a placeholder with the function :datetime. The names of functions are non-translatable.

Functions can also have options, which are also non-translatable content.

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

In both of these examples, only the text "It is the" and the text "today" should be translated.

Matchers and variants Jump to heading

A more complicated kind of message, called a matcher, uses variables to select a variant of a message. One of the most common forms of matching is to match on the plural category of a word. For example:

.input {$numDays :number}
.match $numDays
one  {{{$numDays} day}}
*    {{{$numDays} days}}
{ "numDays": 2 }

The text inside the double curly braces needs to be translated, except for any placeholders contained in this text. In this example, {$numDays} is a placeholder. The text "day" and "days" needs to be translated.

Be sure to keep all of the curly braces matching! If the placeholder {$numDays} needs to be moved in order to translate the message, the curly braces need to move with it.

Each of the variants begins with one or more keys. In this example, there are two variants. The first one begins with the string one. The second one begins with the string *. * is a special key that matches anything. So this message is saying: "If the value of $numDays is singular, use the pattern {{{$numDays} day}}. If it's plural, use the pattern {{{$numDays} days}}.

Translating a message to another language might involve adding new variants. For example, if you were to translate this message into Czech, you would need to both translate the existing variants and add two new ones:

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

Notice that the few and many variants have been added. When looking at a matcher, it's important to ask if more (or fewer) variants are needed in the target language. The CLDR Plural Rules page includes a list of plural categories for each language.

Literals Jump to heading

Sometimes, text appears inside paired pipe (|) characters. This text is called a literal.

My name is {|John Doe|}.

Usually, text inside literals has a special meaning and shouldn't be translated, but it depends on the developer's intentions. A more common use of literals is inside option values:

The year is '{$today :datetime year=|2-digit|} today.
{ "today": "2024-07-01T12:00:00Z" }

In this case, the string 2-digit is code; it's part of a call to the function :datetime. It shouldn't be translated.

Markup Jump to heading

MF2 allows for markup, which changes the appearance of text. Markup should not be translated:

This is {#bold}bold text{/bold}.

The text "bold" inside the curly braces is code. Markup tags begin with a # or /, so it's pretty easy to spot them and know that they shouldn't be translated. The text "bold text" is not markup (though it's preceded and followed by markup) and should be translated.

Declarations Jump to heading

Messages can be prefixed with some lines of code beginning with either .local or .input, such as:

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

These lines of code are called "declarations". When declarations are present, the translatable part of the message is enclosed in a double set of curly braces: {{ / }}. Only the text inside the double curly braces should be translated.

Escape characters Jump to heading

As we explained, single sets of curly braces are special: they enclose text that shouldn't be translated. However, sometimes a message might need to include a curly brace in text presented to a user. In that case, an "escape character" is used.

A left curly brace is \{. A right curly brace is \}.

Putting a backslash (\) in front of a curly brace says that the curly brace is just a character in the text and shouldn't be given special meaning.

On this page