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.