Version | 46.1 (draft) |
---|---|
Editors | Addison Phillips and other CLDR committee members |
For the full header, summary, and status, see Part 1: Core.
This specification defines the data model, syntax, processing, and conformance requirements for the next generation of dynamic messages.
This is a partial document, describing only those parts of the LDML that are relevant for message format. For the other parts of the LDML see the main LDML document and the links above.
This is a draft document which may be updated, replaced, or superseded by other documents at any time. Publication does not imply endorsement by the Unicode Consortium. This is not a stable document; it is inappropriate to cite this document as other than a work in progress.
A Unicode Technical Standard (UTS) is an independent specification. Conformance to the Unicode Standard does not imply conformance to any UTS.
Please submit corrigenda and other comments with the CLDR bug reporting form [Bugs]. Related information that is useful in understanding this document is found in the References. For the latest version of the Unicode Standard see [Unicode]. For more information see About Unicode Technical Reports and the Specifications FAQ. Unicode Technical Reports are governed by the Unicode Terms of Use.
The LDML specification is divided into the following parts:
One of the challenges in adapting software to work for users with different languages and cultures is the need for dynamic messages. Whenever a user interface needs to present data as part of a larger string, that data needs to be formatted (and the message may need to be altered) to make it culturally accepted and grammatically correct.
For example, if your US English (
en-US
) interface has a message like:Your item had 1,023 views on April 3, 2023
You want the translated message to be appropriately formatted into French:
Votre article a eu 1 023 vues le 3 avril 2023
Or Japanese:
あなたのアイテムは 2023 年 4 月 3 日に 1,023 回閲覧されました。
This specification defines the data model, syntax, processing, and conformance requirements for the next generation of dynamic messages. It is intended for adoption by programming languages and APIs. This will enable the integration of existing internationalization APIs (such as the date and number formats shown above), grammatical matching (such as plurals or genders), as well as user-defined formats and message selectors.
The document is the successor to ICU MessageFormat, henceforth called ICU MessageFormat 1.0.
Everything in this specification is normative except for: sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
A term looks like this when it is defined in this specification.
A reference to a term looks like this.
Examples are non-normative and styled like this.
Important
Text marked "Important" like this are normative.
Note
Notes are non-normative.
Important
The provisions of the stability policy are not in effect until the conclusion of the technical preview and adoption of this specification.
Updates to this specification will not make any valid message invalid.
Updates to this specification will not remove any syntax provided in this version.
Updates to this specification will not specify an error for any message that previously did not specify an error.
Updates to this specification will not specify the use of a fallback value for any message that previously did not specify a fallback value.
Updates to this specification will not change the syntactical meaning of any syntax defined in this specification.
Updates to this specification will not remove any functions defined in the default function registry.
Updates to this specification will not remove any options or option values defined in the default function registry.
Note
The foregoing policies are not a guarantee that the results of formatting will never change. Even when this specification or its implementation do not change, the function handlers for date formatting, number formatting and so on can change their results over time or behave differently due to local runtime differences in implementation or changes to locale data (such as due to the release of new CLDR versions).
Updates to this specification will only reserve, define, or require function identifiers and function option identifiers which satisfy either of the following two requirements:
.
, U+002D HYPHEN-MINUS -
, and U+005F LOW LINE _
.All other identifiers in these categories are reserved for the use of implementations or users.
Note
Users defining custom identifiers SHOULD include at least one character outside these ranges to ensure that they will be compatible with future versions of this specification. They SHOULD also use the namespace feature to avoid collisions with other implementations.
Future versions of this specification will not introduce changes to the data model that would result in a data model representation based on this version being invalid.
For example, existing interfaces or fields will not be removed.
Important
This stability policy allows any of the following, non-exhaustive list, of changes in future versions of this specification:
This section defines the formal grammar describing the syntax of a single message.
This section is non-normative.
The design goals of the syntax specification are as follows:
The syntax should leverage the familiarity with ICU MessageFormat 1.0 in order to lower the barrier to entry and increase the chance of adoption. At the same time, the syntax should fix the pain points of ICU MessageFormat 1.0.
The syntax inside translatable content should be easy to understand for humans. This includes making it clear which parts of the message body are translatable content, which parts inside it are placeholders for expressions, as well as making the selection logic predictable and easy to reason about.
The syntax surrounding translatable content should be easy to write and edit for developers, localization engineers, and easy to parse by machines.
The syntax should make a single message easily embeddable inside many container formats:
.properties
, YAML, XML, inlined as string literals in programming languages, etc.
This includes a future MessageResource specification.
\n
, \012
, \x0A
, \u000A
,
\U0000000A
, 

, 

, %0A
, <LF>
, or something else entirely).This section is non-normative.
The syntax specification takes into account the following design restrictions:
Whitespace outside the translatable content should be insignificant. It should be possible to define a message entirely on a single line with no ambiguity, as well as to format it over multiple lines for clarity.
The syntax should define as few special characters and sigils as possible. Note that this necessitates extra care when presenting messages for human consumption, because they may contain invisible characters such as U+200B ZERO WIDTH SPACE, control characters such as U+0000 NULL and U+0009 TAB, permanently reserved noncharacters (U+FDD0 through U+FDEF and U+nFFFE and U+nFFFF where n is 0x0 through 0x10), private-use code points (U+E000 through U+F8FF, U+F0000 through U+FFFFD, and U+100000 through U+10FFFD), unassigned code points, unpaired surrogates (U+D800 through U+DFFF), and other potentially confusing content.
The purpose of MessageFormat is to allow content to vary at runtime. This variation might be due to placing a value into the content or it might be due to selecting a different bit of content based on some data value or it might be due to a combination of the two.
MessageFormat calls the template for a given formatting operation a message.
The values passed in at runtime (which are to be placed into the content or used to select between different content items) are called external variables. The author of a message can also assign local variables, including variables that modify external variables.
This part of the MessageFormat specification defines the syntax for a message, along with the concepts and terminology needed when processing a message during the formatting of a message at runtime.
The complete formal syntax of a message is described by the ABNF.
A message is well-formed if it satisfies all the rules of the grammar. Attempting to parse a message that is not well-formed will result in a Syntax Error.
A message is valid if it is well-formed and also meets the additional content restrictions and semantic requirements about its structure defined below for declarations, matcher, and options. Attempting to parse a message that is not valid will result in a Data Model Error.
A message is the complete template for a specific message formatting request.
A variable is a name associated to a resolved value.
An external variable is a variable whose name and initial value are supplied by the caller to MessageFormat or available in the formatting context. Only an external variable can appear as an operand in an input declaration.
A local variable is a variable created as the result of a local declaration.
Note
This syntax is designed to be embeddable into many different programming languages and formats. As such, it avoids constructs, such as character escapes, that are specific to any given file format or processor. In particular, it avoids using quote characters common to many file formats and formal languages so that these do not need to be escaped in the body of a message.
Note
Text and quoted literals allow unpaired surrogate code points
(U+D800
to U+DFFF
).
This is for compatibility with formats or data structures
that use the UTF-16 encoding
and do not check for unpaired surrogates.
(Strings in Java or JavaScript are examples of this.)
These code points SHOULD NOT be used in a message.
Unpaired surrogate code points are likely an indication of mistakes
or errors in the creation, serialization, or processing of the message.
Many processes will convert them to
� U+FFFD REPLACEMENT CHARACTER
during processing or display.
Implementations not based on UTF-16 might not be able to represent
a message containing such code points.
Note
In general (and except where required by the syntax), whitespace carries no meaning in the structure of a message. While many of the examples in this spec are written on multiple lines, the formatting shown is primarily for readability.
Example This message:
.local $foo = { |horse| } {{You have a {$foo}!}}
Can also be written as:
.local $foo={|horse|}{{You have a {$foo}!}}
An exception to this is: whitespace inside a pattern is always significant.
Note
The MessageFormat 2 syntax assumes that each message will be displayed with a left-to-right display order and be processed in the logical character order. The syntax permits the use of right-to-left characters in identifiers, literals, and other values. This can result in confusion when viewing the message or users might incorrectly insert bidi controls or marks that negatively affect the output of the message.
To assist with this, the syntax permits the use of various controls and strongly-directional markers in both optional and required whitespace in a message, as well was encouraging the use of isolating controls with expressions and quoted patterns. See: whitespace (below) for more information.
A message can be a simple message or it can be a complex message.
message = simple-message / complex-message
A simple message contains a single pattern, with restrictions on its first non-whitespace character. An empty string is a valid simple message.
Whitespace at the start or end of a simple message is significant, and a part of the text of the message.
simple-message = o [simple-start pattern]
simple-start = simple-start-char / escaped-char / placeholder
A complex message is any message that contains declarations,
a matcher, or both.
A complex message always begins with either a keyword that has a .
prefix or a quoted pattern
and consists of:
Whitespace at the start or end of a complex message is not significant, and does not affect the processing of the message.
complex-message = o *(declaration o) complex-body o
A declaration binds a variable identifier to a value within the scope of a message. This variable can then be used in other expressions within the same message. Declarations are optional: many messages will not contain any declarations.
An input-declaration binds a variable to an external input value. The variable-expression of an input-declaration MAY include a function that is applied to the external value.
A local-declaration binds a variable to the resolved value of an expression.
declaration = input-declaration / local-declaration
input-declaration = input o variable-expression
local-declaration = local s variable o "=" o expression
Variables, once declared, MUST NOT be redeclared. A message that does any of the following is not valid and will produce a Duplicate Declaration error during processing:
A local-declaration MAY overwrite an external input value as long as the external input value does not appear in a previous declaration.
Note
These restrictions only apply to declarations. A placeholder can apply a different function to a variable than one applied to the same variable named in a declaration. For example, this message is valid:
.input {$var :number maximumFractionDigits=0}
.local $var2 = {$var :number maximumFractionDigits=2}
.match $var2
0 {{The selector can apply a different function to {$var} for the purposes of selection}}
* {{A placeholder in a pattern can apply a different function to {$var :number maximumFractionDigits=3}}}
(See the Errors section for examples of invalid messages)
The complex body of a complex message is the part that will be formatted. The complex body consists of either a quoted pattern or a matcher.
complex-body = quoted-pattern / matcher
A pattern contains a sequence of text and placeholders to be formatted as a unit. Unless there is an error, resolving a message always results in the formatting of a single pattern.
pattern = *(text-char / escaped-char / placeholder)
A pattern MAY be empty.
A pattern MAY contain an arbitrary number of placeholders to be evaluated during the formatting process.
A quoted pattern is a pattern that is "quoted" to prevent
interference with other parts of the message.
A quoted pattern starts with a sequence of two U+007B LEFT CURLY BRACKET {{
and ends with a sequence of two U+007D RIGHT CURLY BRACKET }}
.
quoted-pattern = "{{" pattern "}}"
A quoted pattern MAY be empty.
An empty quoted pattern:
{{}}
text is the translateable content of a pattern. Any Unicode code point is allowed, except for U+0000 NULL.
The characters U+005C REVERSE SOLIDUS \
,
U+007B LEFT CURLY BRACKET {
, and U+007D RIGHT CURLY BRACKET }
MUST be escaped as \\
, \{
, and \}
respectively.
In the ABNF, text is represented by non-empty sequences of
simple-start-char
, text-char
, escaped-char
, and s
.
The production simple-start-char
represents the first non-whitespace in a simple message
and matches text-char
except for not allowing U+002E FULL STOP .
.
The ABNF uses content-char
as a shared base for text and quoted literal characters.
Whitespace in text, including tabs, spaces, and newlines is significant and MUST be preserved during formatting.
simple-start-char = content-char / "@" / "|"
text-char = content-char / ws / "." / "@" / "|"
quoted-char = content-char / ws / "." / "@" / "{" / "}"
content-char = %x01-08 ; omit NULL (%x00), HTAB (%x09) and LF (%x0A)
/ %x0B-0C ; omit CR (%x0D)
/ %x0E-1F ; omit SP (%x20)
/ %x21-2D ; omit . (%x2E)
/ %x2F-3F ; omit @ (%x40)
/ %x41-5B ; omit \ (%x5C)
/ %x5D-7A ; omit { | } (%x7B-7D)
/ %x7E-2FFF ; omit IDEOGRAPHIC SPACE (%x3000)
/ %x3001-10FFFF ; allowing surrogates is intentional
Note
Unpaired surrogate code points (U+D800
through U+DFFF
inclusive)
are allowed for compatibility with UTF-16 based implementations
that do not check for this encoding error.
When a pattern is quoted by embedding the pattern in curly brackets, the resulting message can be embedded into various formats regardless of the container's whitespace trimming rules. Otherwise, care must be taken to ensure that pattern-significant whitespace is preserved.
Example In a Java
.properties
file, the valueshello
andhello2
both contain an identical message which consists of a single pattern. This pattern consists of text with exactly three spaces before and after the word "Hello":hello = {{ Hello }} hello2=\ Hello \
A placeholder is an expression or markup that appears inside of a pattern and which will be replaced during the formatting of a message.
placeholder = expression / markup
A matcher is the complex body of a message that allows runtime selection of the pattern to use for formatting. This allows the form or content of a message to vary based on values determined at runtime.
A matcher consists of the keyword .match
followed by at least one selector
and at least one variant.
When the matcher is processed, the result will be a single pattern that serves as the template for the formatting process.
A message can only be considered valid if the following requirements are satisfied; otherwise, a corresponding Data Model Error will be produced during processing:
*
.matcher = match-statement s variant *(o variant)
match-statement = match 1*(s selector)
A message with a matcher:
.input {$count :number} .match $count one {{You have {$count} notification.}} * {{You have {$count} notifications.}}
A message containing a matcher formatted on a single line:
.local $os = {:platform} .match $os windows {{Settings}} * {{Preferences}}
A selector is a variable whose resolved value ranks or excludes the variants based on the value of the corresponding key in each variant. The combination of selectors in a matcher thus determines which pattern will be used during formatting.
selector = variable
There MUST be at least one selector in a matcher. There MAY be any number of additional selectors.
A message with a single selector that uses a custom function
:hasCase
which is a selector that allows the message to choose a pattern based on grammatical case:.local $hasCase = {$userName :hasCase} .match $hasCase vocative {{Hello, {$userName :person case=vocative}!}} accusative {{Please welcome {$userName :person case=accusative}!}} * {{Hello!}}
A message with two selectors:
.input {$numLikes :integer} .input {$numShares :integer} .match $numLikes $numShares 0 0 {{Your item has no likes and has not been shared.}} 0 one {{Your item has no likes and has been shared {$numShares} time.}} 0 * {{Your item has no likes and has been shared {$numShares} times.}} one 0 {{Your item has {$numLikes} like and has not been shared.}} one one {{Your item has {$numLikes} like and has been shared {$numShares} time.}} one * {{Your item has {$numLikes} like and has been shared {$numShares} times.}} * 0 {{Your item has {$numLikes} likes and has not been shared.}} * one {{Your item has {$numLikes} likes and has been shared {$numShares} time.}} * * {{Your item has {$numLikes} likes and has been shared {$numShares} times.}}
A variant is a quoted pattern associated with a list of keys in a matcher. Each variant MUST begin with a sequence of keys, and terminate with a valid quoted pattern. The number of keys in each variant MUST match the number of selectors in the matcher.
Each key is separated from each other by whitespace. Whitespace is permitted but not required between the last key and the quoted pattern.
variant = key *(s key) o quoted-pattern
key = literal / "*"
A key is a value in a variant for use by a selector when ranking
or excluding variants during the matcher process.
A key can be either a literal value or the "catch-all" key *
.
The catch-all key is a special key, represented by *
,
that matches all values for a given selector.
The value of each key MUST be treated as if it were in Unicode Normalization Form C ("NFC"). Two keys are considered equal if they are canonically equivalent strings, that is, if they consist of the same sequence of Unicode code points after Unicode Normalization Form C has been applied to both.
An expression is a part of a message that will be determined during the message's formatting.
An expression MUST begin with U+007B LEFT CURLY BRACKET {
and end with U+007D RIGHT CURLY BRACKET }
.
An expression MUST NOT be empty.
An expression cannot contain another expression.
An expression MAY contain one more attributes.
A literal-expression contains a literal, optionally followed by a function.
A variable-expression contains a variable, optionally followed by a function.
A function-expression contains a function without an operand.
expression = literal-expression
/ variable-expression
/ function-expression
literal-expression = "{" o literal [s function] *(s attribute) o "}"
variable-expression = "{" o variable [s function] *(s attribute) o "}"
function-expression = "{" o function *(s attribute) o "}"
There are several types of expression that can appear in a message. All expressions share a common syntax. The types of expression are:
Additionally, an input-declaration can contain a variable-expression.
Examples of different types of expression
Declarations:
.input {$x :function option=value} .local $y = {|This is an expression|}
Placeholders:
This placeholder contains a literal expression: {|literal|} This placeholder contains a variable expression: {$variable} This placeholder references a function on a variable: {$variable :function with=options} This placeholder contains a function expression with a variable-valued option: {:function option=$variable}
An operand is the literal of a literal-expression or the variable of a variable-expression.
A function is named functionality in an expression. Functions are used to evaluate, format, select, or otherwise process data values during formatting.
A function can appear in an expression by itself or following a single operand. When following an operand, the operand serves as input to the function.
Each function is defined by the runtime's function registry. A function's entry in the function registry will define whether the function is a selector or formatter (or both), whether an operand is required, what form the values of an operand can take, what options and option values are acceptable, and what outputs might result. See function registry for more information.
A function starts with a prefix sigil :
followed by an identifier.
The identifier MAY be followed by one or more options.
Options are not required.
function = ":" identifier *(s option)
A message with a function operating on the variable
$now
:It is now {$now :datetime}.
An option is a key-value pair containing a named argument that is passed to a function.
An option has an identifier and a value.
The identifier is separated from the value by an U+003D EQUALS SIGN =
along with
optional whitespace.
The value of an option can be either a literal or a variable.
Multiple options are permitted in a function. Options are separated from the preceding function identifier and from each other by whitespace. Each option's identifier MUST be unique within the function: a function with duplicate option identifiers is not valid and will produce a Duplicate Option Name error during processing.
The order of options is not significant.
option = identifier o "=" o (literal / variable)
Examples of functions with options
A message using the
:datetime
function. The optionweekday
has the literallong
as its value:Today is {$date :datetime weekday=long}!
A message using the
:datetime
function. The optionweekday
has a variable$dateStyle
as its value:Today is {$date :datetime weekday=$dateStyle}!
Markup placeholders are pattern parts that can be used to represent non-language parts of a message, such as inline elements or styling that should apply to a span of parts.
Markup MUST begin with U+007B LEFT CURLY BRACKET {
and end with U+007D RIGHT CURLY BRACKET }
.
Markup MAY contain one more attributes.
Markup comes in three forms:
Markup-open starts with U+0023 NUMBER SIGN #
and
represents an opening element within the message,
such as markup used to start a span.
It MAY include options.
Markup-standalone starts with U+0023 NUMBER SIGN #
and has a U+002F SOLIDUS /
immediately before its closing }
representing a self-closing or standalone element within the message.
It MAY include options.
Markup-close starts with U+002F SOLIDUS /
and
is a pattern part ending a span.
markup = "{" o "#" identifier *(s option) *(s attribute) o ["/"] "}" ; open and standalone
/ "{" o "/" identifier *(s option) *(s attribute) o "}" ; close
A message with one
button
markup span and a standaloneimg
markup element:{#button}Submit{/button} or {#img alt=|Cancel| /}.
A message containing markup that uses options to pair two closing markup placeholders to the one open markup placeholder:
{#ansi attr=|bold,italic|}Bold and italic{/ansi attr=|bold|} italic only {/ansi attr=|italic|} no formatting.}
A markup-open can appear without a corresponding markup-close. A markup-close can appear without a corresponding markup-open. Markup placeholders can appear in any order without making the message invalid. However, specifications or implementations defining markup might impose requirements on the pairing, ordering, or contents of markup during formatting.
An attribute is an identifier with an optional value that appears in an expression or in markup. During formatting, attributes have no effect, and they can be treated as code comments.
Attributes are prefixed by a U+0040 COMMERCIAL AT @
sign,
followed by an identifier.
An attribute MAY have a literal value which is separated from the identifier
by an U+003D EQUALS SIGN =
along with optional whitespace.
Multiple attributes are permitted in an expression or markup. Each attribute is separated by whitespace.
Each attribute's identifier SHOULD be unique within the expression or markup: all but the last attribute with the same identifier are ignored. The order of attributes is not otherwise significant.
attribute = "@" identifier [o "=" o literal]
Examples of expressions and markup with attributes:
A message including a literal that should not be translated:
In French, "{|bonjour| @translate=no}" is a greeting
A message with markup that should not be copied:
Have a {#span @can-copy}great and wonderful{/span @can-copy} birthday!
This section defines common elements used to construct messages.
A keyword is a reserved token that has a unique meaning in the message syntax.
The following three keywords are defined: .input
, .local
, and .match
.
Keywords are always lowercase and start with U+002E FULL STOP .
.
input = %s".input"
local = %s".local"
match = %s".match"
A literal is a character sequence that appears outside of text in various parts of a message. A literal can appear as a key value, as the operand of a literal-expression, or in the value of an option. A literal MAY include any Unicode code point except for U+0000 NULL.
All code points are preserved.
Important
Most text, including that produced by common keyboards and input methods, is already encoded in the canonical form known as Unicode Normalization Form C ("NFC"). A few languages, legacy character encoding conversions, or operating environments can result in literal values that are not in this form. Some uses of literals in MessageFormat, notably as the value of keys, apply NFC to the literal value during processing or comparison. While there is no requirement that the literal value actually be entered in a normalized form, users are cautioned to employ the same character sequences for equivalent values and, whenever possible, ensure literals are in NFC.
A quoted literal begins and ends with U+005E VERTICAL BAR |
.
The characters \
and |
within a quoted literal MUST be
escaped as \\
and \|
.
Note
Unpaired surrogate code points (U+D800
through U+DFFF
inclusive)
are allowed in quoted literals for compatibility with UTF-16 based
implementations that do not check for this encoding error.
An unquoted literal is a literal that does not require the |
quotes around it to be distinct from the rest of the message syntax.
An unquoted literal MAY be used when the content of the literal
contains no whitespace and otherwise matches the unquoted
production.
Implementations MUST NOT distinguish between quoted literals and unquoted literals
that have the same sequence of code points.
Unquoted literals can contain a name or consist of a number-literal. A number-literal uses the same syntax as JSON and is intended for the encoding of number values in operands or options, or as keys for variants.
literal = quoted-literal / unquoted-literal
quoted-literal = "|" *(quoted-char / escaped-char) "|"
unquoted-literal = name / number-literal
number-literal = ["-"] (%x30 / (%x31-39 *DIGIT)) ["." 1*DIGIT] [%i"e" ["-" / "+"] 1*DIGIT]
A name is a character sequence used in an identifier or as the name for a variable or the value of an unquoted literal.
A name can be preceded or followed by bidirectional marks or isolating controls to aid in presenting names that contain right-to-left or neutral characters. These characters are not part of the value of the name and MUST be treated as if they were not present when matching name or identifier strings or unquoted literal values.
Variable names are prefixed with $
.
Two names are considered equal if they are canonically equivalent strings, that is, if they consist of the same sequence of Unicode code points after Unicode Normalization Form C ("NFC") has been applied to both.
Note
Implementations are not required to normalize all names. Comparisons of name values only need be done "as-if" normalization has occured. Since most text in the wild is already in NFC and since checking for NFC is fast and efficient, implementations can often substitute checking for actually applying normalization to name values.
Valid content for names is based on Namespaces in XML 1.0's
NCName.
This is different from XML's Name
in that it MUST NOT contain a U+003A COLON :
.
Otherwise, the set of characters allowed in a name is large.
Note
External variables can be passed in that are not valid names. Such variables cannot be referenced in a message, but are not otherwise errors.
An identifier is a character sequence that
identifies a function, markup, or option.
Each identifier consists of a name optionally preceeded by
a namespace.
When present, the namespace is separated from the name by a
U+003A COLON :
.
Built-in functions and their options do not have a namespace identifier.
The namespace u
(U+0075 LATIN SMALL LETTER U)
is reserved for future standardization.
Function identifiers are prefixed with :
.
Markup identifiers are prefixed with #
or /
.
Option identifiers have no prefix.
Examples:
A variable:
This has a {$variable}
A function:
This has a {:function}
An add-on function from the
icu
namespace:This has a {:icu:function}
An option and an add-on option:
This has {:options option=value icu:option=add_on}
Support for namespaces and their interpretation is implementation-defined in this release.
variable = "$" name
option = identifier o "=" o (literal / variable)
identifier = [namespace ":"] name
namespace = name
name = [bidi] name-start *name-char [bidi]
name-start = ALPHA / "_"
/ %xC0-D6 / %xD8-F6 / %xF8-2FF
/ %x370-37D / %x37F-61B / %x61D-1FFF / %x200C-200D
/ %x2070-218F / %x2C00-2FEF / %x3001-D7FF
/ %xF900-FDCF / %xFDF0-FFFC / %x10000-EFFFF
name-char = name-start / DIGIT / "-" / "."
/ %xB7 / %x300-36F / %x203F-2040
An escape sequence is a two-character sequence starting with
U+005C REVERSE SOLIDUS \
.
An escape sequence allows the appearance of lexically meaningful characters
in the body of text or quoted literal sequences.
Each escape sequence represents the literal character immediately following the initial \
.
escaped-char = backslash ( backslash / "{" / "|" / "}" )
backslash = %x5C ; U+005C REVERSE SOLIDUS "\"
Note
The escaped-char
rule allows escaping some characters in places where
they do not need to be escaped, such as braces in a quoted literal.
For example, |foo {bar}|
and |foo \{bar\}|
are synonymous.
When writing or generating a message, escape sequences SHOULD NOT be used
unless required by the syntax.
That is, inside literals only escape |
and inside patterns only escape {
and }
.
The syntax limits whitespace characters outside of a pattern to the following:
U+0009 CHARACTER TABULATION
(tab),
U+000A LINE FEED
(new line),
U+000D CARRIAGE RETURN
,
U+3000 IDEOGRAPHIC SPACE
,
or U+0020 SPACE
.
Inside patterns and quoted literals, whitespace is part of the content and is recorded and stored verbatim. Whitespace is not significant outside translatable text, except where required by the syntax.
There are two whitespace productions in the syntax. Optional whitespace is whitespace that is not required by the syntax, but which users might want to include to increase the readability of a message. Required whitespace is whitespace that is required by the syntax.
Both types of whitespace optionally permit the use of the bidirectional isolate controls and certain strongly directional marks. These can assist users in presenting messages that contain right-to-left text, literals, or names (including those for functions, options, option values, and keys)
Messages that contain right-to-left (aka RTL) characters SHOULD use one of the following mechanisms to make messages display intelligibly in plain-text editors:
U+2066 LEFT-TO-RIGHT ISOLATE
("LRI")
and U+2069 POP DIRECTIONAL ISOLATE
("PDI") as permitted by the ABNF around
parts of any message containing RTL characters:{
and }
{{
and }}
<LRI>$var</PDI>
or <LRI>:ns:name</PDI>
)U+061C ARABIC LETTER MARK
, U+200E LEFT-TO-RIGHT MARK
or
U+200F RIGHT-TO-LEFT MARK
as permitted by the ABNF before or after identifiers,
names, unquoted literals, or option values,
especially when the values contain a mix of neutral, weakly directional, and
strongly directional characters.Important
Always take care not to add bidirectional controls or marks where they would be semantically significant or where they would unintentionally become part of the message's output:
<LRM>|...|<LRM>
)<LRI>{{...}}<PDI>
){<LRI>$foo :number<PDI>}
)Controls placed inside literal quotes or quoted patterns are part of the literal or pattern. Controls in a pattern will appear in the output of the message. Controls inside literal quotes are part of the literal and will be considered in operations such as matching a key to a selector.
Note
Users cannot be expected to create or manage bidirectional controls or marks in messages, since the characters are invisible and can be difficult to manage. Tools (such as resource editors or translation editors) and other implementations of MessageFormat 2 serialization are strongly encouraged to provide paired isolates around any right-to-left syntax as described above so that messages display appropriately as plain text.
These definitions of whitespace implement UAX#31 Requirement R3a-2. It is a profile of R3a-1 in that specification because:
U+000B FORM FEED
,
U+000C VERTICAL TABULATION
,
U+0085 NEXT LINE
,
U+2028 LINE SEPARATOR
and
U+2029 PARAGRAPH SEPARATOR
.U+3000 IDEOGRAPHIC SPACE
is interpreted as whitespace.U+061C ARABIC LETTER MARK
,
U+200E LEFT-TO-RIGHT MARK
,
U+200F RIGHT-TO-LEFT MARK
,
U+2066 LEFT-TO-RIGHT ISOLATE
,
U+2067 RIGHT-TO-LEFT ISOLATE
,
U+2068 FIRST STRONG ISOLATE
,
and U+2069 POP DIRECTIONAL ISOLATE
.
(The character U+061C
is an addition according to R3a.)Note
The character U+3000 IDEOGRAPHIC SPACE is included in whitespace for compatibility with certain East Asian keyboards and input methods, in which users might accidentally create these characters in a message.
; Required whitespace
s = *bidi ws o
; Optional whitespace
o = *(s / bidi)
; Bidirectional marks and isolates
; ALM / LRM / RLM / LRI, RLI, FSI & PDI
bidi = %x061C / %x200E / %x200F / %x2066-2069
; Whitespace characters
ws = SP / HTAB / CR / LF / %x3000
The grammar is formally defined in message.abnf
using the ABNF notation [STD68],
including the modifications found in RFC 7405.
RFC7405 defines a variation of ABNF that is case-sensitive.
Some ABNF tools are only compatible with the specification found in
RFC 5234.
To make message.abnf
compatible with that version of ABNF, replace
the rules of the same name with this block:
input = %x2E.69.6E.70.75.74 ; ".input"
local = %x2E.6C.6F.63.61.6C ; ".local"
match = %x2E.6D.61.74.63.68 ; ".match"
message.abnf
message = simple-message / complex-message
simple-message = o [simple-start pattern]
simple-start = simple-start-char / escaped-char / placeholder
pattern = *(text-char / escaped-char / placeholder)
placeholder = expression / markup
complex-message = o *(declaration o) complex-body o
declaration = input-declaration / local-declaration
complex-body = quoted-pattern / matcher
input-declaration = input o variable-expression
local-declaration = local s variable o "=" o expression
quoted-pattern = "{{" pattern "}}"
matcher = match-statement s variant *(o variant)
match-statement = match 1*(s selector)
selector = variable
variant = key *(s key) o quoted-pattern
key = literal / "*"
; Expressions
expression = literal-expression
/ variable-expression
/ function-expression
literal-expression = "{" o literal [s function] *(s attribute) o "}"
variable-expression = "{" o variable [s function] *(s attribute) o "}"
function-expression = "{" o function *(s attribute) o "}"
markup = "{" o "#" identifier *(s option) *(s attribute) o ["/"] "}" ; open and standalone
/ "{" o "/" identifier *(s option) *(s attribute) o "}" ; close
; Expression and literal parts
function = ":" identifier *(s option)
option = identifier o "=" o (literal / variable)
attribute = "@" identifier [o "=" o literal]
variable = "$" name
literal = quoted-literal / unquoted-literal
quoted-literal = "|" *(quoted-char / escaped-char) "|"
unquoted-literal = name / number-literal
; number-literal matches JSON number (https://www.rfc-editor.org/rfc/rfc8259#section-6)
number-literal = ["-"] (%x30 / (%x31-39 *DIGIT)) ["." 1*DIGIT] [%i"e" ["-" / "+"] 1*DIGIT]
; Keywords; Note that these are case-sensitive
input = %s".input"
local = %s".local"
match = %s".match"
; Names and identifiers
; identifier matches https://www.w3.org/TR/REC-xml-names/#NT-QName
; name matches https://www.w3.org/TR/REC-xml-names/#NT-NCName but excludes U+FFFD and U+061C
identifier = [namespace ":"] name
namespace = name
name = [bidi] name-start *name-char [bidi]
name-start = ALPHA / "_"
/ %xC0-D6 / %xD8-F6 / %xF8-2FF
/ %x370-37D / %x37F-61B / %x61D-1FFF / %x200C-200D
/ %x2070-218F / %x2C00-2FEF / %x3001-D7FF
/ %xF900-FDCF / %xFDF0-FFFC / %x10000-EFFFF
name-char = name-start / DIGIT / "-" / "."
/ %xB7 / %x300-36F / %x203F-2040
; Restrictions on characters in various contexts
simple-start-char = content-char / "@" / "|"
text-char = content-char / ws / "." / "@" / "|"
quoted-char = content-char / ws / "." / "@" / "{" / "}"
content-char = %x01-08 ; omit NULL (%x00), HTAB (%x09) and LF (%x0A)
/ %x0B-0C ; omit CR (%x0D)
/ %x0E-1F ; omit SP (%x20)
/ %x21-2D ; omit . (%x2E)
/ %x2F-3F ; omit @ (%x40)
/ %x41-5B ; omit \ (%x5C)
/ %x5D-7A ; omit { | } (%x7B-7D)
/ %x7E-2FFF ; omit IDEOGRAPHIC SPACE (%x3000)
/ %x3001-10FFFF ; allowing surrogates is intentional
; Character escapes
escaped-char = backslash ( backslash / "{" / "|" / "}" )
backslash = %x5C ; U+005C REVERSE SOLIDUS "\"
; Required whitespace
s = *bidi ws o
; Optional whitespace
o = *(ws / bidi)
; Bidirectional marks and isolates
; ALM / LRM / RLM / LRI, RLI, FSI & PDI
bidi = %x061C / %x200E / %x200F / %x2066-2069
; Whitespace characters
ws = SP / HTAB / CR / LF / %x3000
This section defines the behavior of a MessageFormat 2.0 implementation when formatting a message for display in a user interface, or for some later processing.
To start, we presume that a message has either been parsed from its syntax or created from a data model description. If the resulting message is not well-formed, a Syntax Error is emitted. If the resulting message is well-formed but is not valid, a Data Model Error is emitted.
The formatting of a message is defined by the following operations:
Pattern Selection determines which of a message's patterns is formatted. For a message with no selectors, this is simple as there is only one pattern. With selectors, this will depend on their resolution.
Formatting takes the resolved values of the text and placeholder parts of the selected pattern, and produces the formatted result for the message. Depending on the implementation, this result could be a single concatenated string, an array of objects, an attributed string, or some other locally appropriate data type.
Expression and Markup Resolution determines the value of an expression or markup, with reference to the current formatting context. This can include multiple steps, such as looking up the value of a variable and calling formatting functions. The form of the resolved value is implementation defined and the value might not be evaluated or formatted yet. However, it needs to be "formattable", i.e. it contains everything required by the eventual formatting.
The resolution of text is rather straightforward, and is detailed under literal resolution.
Implementations are not required to expose the expression resolution and pattern selection operations to their users, or even use them in their internal processing, as long as the final formatting result is made available to users and the observable behavior of the formatting matches that described here.
Attributes MUST NOT have any effect on the formatted output of a message, nor be made available to function handlers.
Important
This specification does not require either eager or lazy expression resolution of message parts; do not construe any requirement in this document as requiring either.
Implementations are not required to evaluate all parts of a message when parsing, processing, or formatting. In particular, an implementation MAY choose not to evaluate or resolve the value of a given expression until it is actually used by a selection or formatting process. However, when an expression is resolved, it MUST behave as if all preceding declarations affecting variables referenced by that expression have already been evaluated in the order in which the relevant declarations appear in the message. An implementation MUST ensure that every expression in a message is evaluated at most once.
Important
Implementations with lazy evaluation MUST NOT use a call-by-name evaluation strategy. Instead, they must evaluate expressions at most once ("call-by-need"). This is to prevent expressions from having different values when used in different parts of a given message. Function handlers are not necessarily pure: they can access external mutable state such as the current system clock time. Thus, evaluating the same expression more than once could yield different results. That behavior violates this specification.
Important
Implementations and users SHOULD NOT create function handlers that mutate external program state, particularly since such a function handler can present a remote execution hazard.
A message's formatting context represents the data and procedures that are required for the message's expression resolution, pattern selection and formatting.
At a minimum, it includes:
Information on the current locale, potentially including a fallback chain of locales. This will be passed on to formatting functions.
Information on the base directionality of the message and its text tokens. This will be used by strategies for bidirectional isolation, and can be used to set the base direction of the message upon display.
An input mapping of string identifiers to values, defining variable values that are available during variable resolution. This is often determined by a user-provided argument of a formatting function call.
The function registry, providing the function handlers of the functions referred to by message functions.
Optionally, a fallback string to use for the message if it is not valid.
Implementations MAY include additional fields in their formatting context.
A resolved value is the result of resolving a text, literal, variable, expression, or markup. The resolved value is determined using the formatting context. The form of the resolved value is implementation-defined.
In a declaration, the resolved value of an expression is bound to a variable, which makes it available for use in later expressions and markup options.
For example, in
.input {$a :number minimumFractionDigits=3} .local $b = {$a :integer notation=compact} .match $a 0 {{The value is zero.}} * {{In compact form, the value {$a} is rendered as {$b}.}}
the resolved value bound to
$a
is used as the operand of the:integer
function when resolving the value of the variable$b
, as a selector in the.match
statement, as well as for formatting the placeholder{$a}
.
In an input-declaration, the variable operand of the variable-expression identifies not only the name of the external input value, but also the variable to which the resolved value of the variable-expression is bound.
In a pattern, the resolved value of an expression or markup is used in its formatting.
The form that resolved values take is implementation-dependent, and different implementations MAY choose to perform different levels of resolution.
While this specification does not require it, a resolved value could be implemented by requiring each function handler to return a value matching the following interface:
interface MessageValue { formatToString(): string formatToX(): X // where X is an implementation-defined type getValue(): unknown resolvedOptions(): { [key: string]: MessageValue } selectKeys(keys: string[]): string[] }
With this approach:
- An expression could be used as a placeholder if calling the
formatToString()
orformatToX()
method of its resolved value did not emit an error.- A variable could be used as a selector if calling the
selectKeys(keys)
method of its resolved value did not emit an error.- Using a variable, the resolved value of an expression could be used as an operand or option value if calling the
getValue()
method of its resolved value did not emit an error. In this use case, theresolvedOptions()
method could also provide a set of option values that could be taken into account by the called function.Extensions of the base
MessageValue
interface could be provided for different data types, such as numbers or strings, for which theunknown
return type ofgetValue()
and the genericMessageValue
type used inresolvedOptions()
could be narrowed appropriately. An implementation could also allowMessageValue
values to be passed in as input variables, or automatically wrap each variable as aMessageValue
to provide a uniform interface for custom functions.
Expressions are used in declarations and patterns. Markup is only used in patterns.
Depending on the presence or absence of a variable or literal operand and a function, the resolved value of the expression is determined as follows:
If the expression contains a function, its resolved value is defined by function resolution.
Else, if the expression consists of a variable, its resolved value is defined by variable resolution. An implementation MAY perform additional processing when resolving the value of an expression that consists only of a variable.
For example, it could apply function resolution using a function and a set of options chosen based on the value or type of the variable. So, given a message like this:
Today is {$date}
If the value passed in the variable were a date object, such as a JavaScript
Date
or a Javajava.util.Date
orjava.time.Temporal
, the implementation could interpret the placeholder{$date}
as if the pattern included the function:datetime
with some set of default options.
Else, the expression consists of a literal. Its resolved value is defined by literal resolution.
Note
This means that a literal value with no function is always treated as a string. To represent values that are not strings as a literal, a function needs to be provided:
.local $aNumber = {1234 :number}
.local $aDate = {|2023-08-30| :datetime}
.local $aFoo = {|some foo| :foo}
{{You have {42 :number}}}
The resolved value of a text or a literal contains the character sequence of the text or literal after any character escape has been converted to the escaped character.
When a literal is used as an operand or on the right-hand side of an option, the formatting function MUST treat its resolved value the same whether its value was originally a quoted literal or an unquoted literal.
For example, the option
foo=42
and the optionfoo=|42|
are treated as identical.
For example, in a JavaScript formatter, the resolved value of a text or a literal could have the following implementation:
class MessageLiteral implements MessageValue { constructor(value: string) { this.formatToString = () => value; this.getValue = () => value; } resolvedOptions: () => ({}); selectKeys(_keys: string[]) { throw Error("Selection on unannotated literals is not supported"); } }
To resolve the value of a variable, its name is used to identify either a local variable or an input variable. If a declaration exists for the variable, its resolved value is used. Otherwise, the variable is an implicit reference to an input value, and its value is looked up from the formatting context input mapping.
The resolution of a variable fails if no value is identified for its name. If this happens, an Unresolved Variable error is emitted and a fallback value is used as the resolved value of the variable.
If the resolved value identified for the variable name is a fallback value, a fallback value is used as the resolved value of the variable.
The fallback value representation of a variable has a string representation
consisting of the U+0024 DOLLAR SIGN $
followed by the name of the variable.
To resolve an expression with a function, the following steps are taken:
If the expression includes an operand, resolve its value. If this is a fallback value, return a fallback value as the resolved value of the expression.
Resolve the identifier of the function and find the appropriate function handler to call. If the implementation cannot find the function handler, or if the identifier includes a namespace that the implementation does not support, emit an Unknown Function error and return a fallback value as the resolved value of the expression.
Implementations are not required to implement namespaces or installable function registries.
Perform option resolution.
Determine the function context for calling the function handler.
The function context contains the context necessary for the function handler to resolve the expression. This includes:
If the resolved mapping of options includes any u:
options
supported by the implementation, process them as specified.
Such u:
options MAY be removed from the resolved mapping of options.
Call the function handler with the following arguments:
The form that resolved operand and option values take is implementation-defined.
An implementation MAY pass additional arguments to the function handler, as long as reasonable precautions are taken to keep the function interface simple and minimal, and avoid introducing potential security vulnerabilities.
If the call succeeds, resolve the value of the expression as the result of that function call.
If the call fails or does not return a valid value, emit the appropriate Message Function Error for the failure.
Implementations MAY provide a mechanism for the function handler to provide additional detail about internal failures. Specifically, if the cause of the failure was that the datatype, value, or format of the operand did not match that expected by the function, the function SHOULD cause a Bad Operand error to be emitted.
In all failure cases, return a fallback value as the resolved value of the expression.
A function handler is an implementation-defined process such as a function or method which accepts a set of arguments and returns a resolved value. A function handler is required to resolve a function.
An implementation MAY define its own functions and their handlers. An implementation MAY allow custom functions to be defined by users.
Implementations that provide a means for defining custom functions MUST provide a means for function handlers to return resolved values that contain enough information to be used as operands or option values in subsequent expressions.
The resolved value returned by a function handler MAY be different from the value of the operand of the function. It MAY be an implementation specified type. It is not required to be the same type as the operand.
A function handler MAY include resolved options in its resolved value. The resolved options MAY be different from the options of the function.
A function handler SHOULD emit a Bad Operand error for operands whose resolved value or type is not supported.
Function handler access to the formatting context MUST be minimal and read-only, and execution time SHOULD be limited.
Implementation-defined functions SHOULD use an implementation-defined namespace.
Option resolution is the process of computing the options for a given expression. Option resolution results in a mapping of string identifiers to values. The order of options MUST NOT be significant.
For example, the following message treats both both placeholders identically:
{$x :function option1=foo option2=bar} {$x :function option2=bar option1=foo}
For each option:
res
be a new empty mapping.id
be the string value of the identifier of the option.rv
be the resolved value of the option value.rv
is a fallback value:res[id]
to be rv
.res
.The result of option resolution MUST be a (possibly empty) mapping of string identifiers to values; that is, errors MAY be emitted, but such errors MUST NOT be fatal. This mapping can be empty.
Note
The resolved value of a function operand can also include resolved option values. These are not included in the option resolution result, and need to be processed separately by a function handler.
Unlike functions, the resolution of markup is not customizable.
The resolved value of markup includes the following fields:
If the resolved mapping of options includes any u:
options
supported by the implementation, process them as specified.
Such u:
options MAY be removed from the resolved mapping of options.
The resolution of markup MUST always succeed.
A fallback value is the resolved value for an expression or variable when that expression or variable fails to resolve. It contains a string representation that is used for its formatting, and no option values.
The resolved value of text, literal, and markup MUST NOT be a fallback value.
A variable fails to resolve when no value is identified for its name.
The string representation of its fallback value is
U+0024 DOLLAR SIGN $
followed by the name of the variable.
An expression fails to resolve when:
The string representation of the fallback value of an expression depends on its contents:
expression with a literal operand (either quoted or unquoted):
U+007C VERTICAL LINE |
followed by the value of the literal
with escaping applied to U+005C REVERSE SOLIDUS \
and U+007C VERTICAL LINE |
,
and then by U+007C VERTICAL LINE |
.
Examples: In a context where
:func
fails to resolve,{42 :func}
resolves to a fallback value with a string representation|42|
and{|C:\\| :func}
resolves to a fallback value with a string representation|C:\\|
.
expression with variable operand:
the fallback value representation of that variable,
U+0024 DOLLAR SIGN $
followed by the name of the variable
Examples: In a context where
$var
fails to resolve,{$var}
and{$var :number}
both resolve to a fallback value with a string representation$var
(even if:number
fails to resolve).In a context where
:func
fails to resolve, the placeholder in.local $var = {|val| :func} {{{$var}}}
resolves to a fallback value with a string representation$var
.In a context where either
:now
or:pretty
fails to resolve, the placeholder in.local $time = {:now format=iso8601} {{{$time :pretty}}}
resolves to a fallback value with a string representation
$time
.
function expression with no operand:
U+003A COLON :
followed by the function identifier
Examples: In a context where
:func
fails to resolve,{:func}
resolves to a fallback value with a string representation:func
. In a context where:ns:func
fails to resolve,{:ns:func}
resolves to a fallback value with a string representation:ns:func
.
Otherwise: the U+FFFD REPLACEMENT CHARACTER �
This is not currently used by any expression, but may apply in future revisions.
Options and attributes are not included in the fallback value.
Pattern selection is not supported for fallback values.
For example, in a JavaScript formatter the fallback value could have the following implementation, where
source
is one of the above-defined strings:class MessageFallback implements MessageValue { constructor(source: string) { this.formatToString = () => `{${source}}`; this.getValue = () => undefined; } resolvedOptions: () => ({}); selectKeys(_keys: string[]) { throw Error("Selection on fallback values is not supported"); } }
If the message being formatted is not well-formed and valid,
the result of pattern selection is a pattern consisting of a single fallback value
using the message's fallback string defined in the formatting context
or if this is not available or empty, the U+FFFD REPLACEMENT CHARACTER �
.
If the message being formatted does not contain a matcher, the result of pattern selection is its pattern value.
When a message contains a matcher with one or more selectors, the implementation needs to determine which variant will be used to provide the pattern for the formatting operation. This is done by ordering and filtering the available variant statements according to their key values and selecting the first one.
Note
At least one variant is required to have all of its keys consist of
the fallback value *
.
Some selectors might be implemented in a way that the key value *
cannot be selected in a valid message.
In other cases, this key value might be unreachable only in certain locales.
This could result in the need in some locales to create
one or more variants that do not make sense grammatically for that language.
For example, in the
pl
(Polish) locale, this message cannot reach the*
variant:.input {$num :integer} .match $num 0 {{ }} one {{ }} few {{ }} many {{ }} * {{Only used by fractions in Polish.}}
During the Final Candidate review period, feedback from users and implementers is desired about whether to relax the requirement that such a "fallback variant" appear in every message, versus the potential for a message to fail at runtime because no matching variant is available.
The number of keys in each variant MUST equal the number of selectors.
Each key corresponds to a selector by its position in the variant.
For example, in this message:
.input {$one :number} .input {$two :number} .input {$three :number} .match $one $two $three 1 2 3 {{ ... }}
The first key
1
corresponds to the first selector ($one
), the second key2
to the second selector ($two
), and the third key3
to the third selector ($three
).
To determine which variant best matches a given set of inputs, each selector is used in turn to order and filter the list of variants.
Each variant with a key that does not match its corresponding selector is omitted from the list of variants. The remaining variants are sorted according to the selector's key-ordering preference. Earlier selectors in the matcher's list of selectors have a higher priority than later ones.
When all of the selectors have been processed, the earliest-sorted variant in the remaining list of variants is selected.
This selection method is defined in more detail below. An implementation MAY use any pattern selection method, as long as its observable behavior matches the results of the method defined here.
First, resolve the values of each selector:
res
be a new empty list of resolved values that support selection.sel
, in source order,rv
be the resolved value of sel
.rv
:rv
as the last element of the list res
.nomatch
be a resolved value for which selection always fails.nomatch
as the last element of the list res
.The form of the resolved values is determined by each implementation, along with the manner of determining their support for selection.
Next, using res
, resolve the preferential order for all message keys:
pref
be a new empty list of lists of strings.i
in res
:keys
be a new empty list of strings.var
of the message:key
be the var
key at position i
.key
is not the catch-all key '*'
:key
is a literal.ks
be the resolved value of key
in Unicode Normalization Form C.ks
as the last element of the list keys
.rv
be the resolved value at index i
of res
.matches
be the result of calling the method MatchSelectorKeys(rv
, keys
)matches
as the last element of the list pref
.The method MatchSelectorKeys is determined by the implementation.
It takes as arguments a resolved selector value rv
and a list of string keys keys
,
and returns a list of string keys in preferential order.
The returned list MUST contain only unique elements of the input list keys
.
The returned list MAY be empty.
The most-preferred key is first,
with each successive key appearing in order by decreasing preference.
The resolved value of each key MUST be in Unicode Normalization Form C ("NFC"), even if the literal for the key is not.
If calling MatchSelectorKeys encounters any error, a Bad Selector error is emitted and an empty list is returned.
Then, using the preferential key orders pref
,
filter the list of variants to the ones that match with some preference:
vars
be a new empty list of variants.var
of the message:i
in pref
:key
be the var
key at position i
.key
is the catch-all key '*'
:pref
.key
is a literal.ks
be the resolved value of key
.matches
be the list of strings at index i
of pref
.matches
includes ks
:pref
.var
as the last element of the list vars
.Finally, sort the list of variants vars
and select the pattern:
sortable
be a new empty list of (integer, variant) tuples.var
of vars
:tuple
be a new tuple (-1, var
).tuple
as the last element of the list sortable
.len
be the integer count of items in pref
.i
be len
- 1.i
>= 0:matches
be the list of strings at index i
of pref
.minpref
be the integer count of items in matches
.tuple
of sortable
:matchpref
be an integer with the value minpref
.key
be the tuple
variant key at position i
.key
is not the catch-all key '*'
:key
is a literal.ks
be the resolved value of key
.matchpref
be the integer position of ks
in matches
.tuple
integer value as matchpref
.sortable
to be the result of calling the method SortVariants(sortable)
.i
to be i
- 1.var
be the variant element of the first element of sortable
.var
.SortVariants
is a method whose single argument is
a list of (integer, variant) tuples.
It returns a list of (integer, variant) tuples.
Any implementation of SortVariants
is acceptable
as long as it satisfies the following requirements:
sortable
be an arbitrary list of (integer, variant) tuples.sorted
be SortVariants(sortable)
.sorted
is the result of sorting sortable
using the following comparator:(i1, v1)
<= (i2, v2)
if and only if i1 <= i2
.sortable
that are equal
in their first element have the same relative order in sorted
).This section is non-normative.
Presuming a minimal implementation which only supports :string
function
which matches keys by using string comparison,
and a formatting context in which
the variable reference $foo
resolves to the string 'foo'
and
the variable reference $bar
resolves to the string 'bar'
,
pattern selection proceeds as follows for this message:
.input {$foo :string}
.input {$bar :string}
.match $foo $bar
bar bar {{All bar}}
foo foo {{All foo}}
* * {{Otherwise}}
For the first selector:
The value of the selector is resolved to be 'foo'
.
The available keys « 'bar'
, 'foo'
» are compared to 'foo'
,
resulting in a list « 'foo'
» of matching keys.
For the second selector:
The value of the selector is resolved to be 'bar'
.
The available keys « 'bar'
, 'foo'
» are compared to 'bar'
,
resulting in a list « 'bar'
» of matching keys.
Creating the list vars
of variants matching all keys:
The first variant bar bar
is discarded as its first key does not match the first selector.
The second variant foo foo
is discarded as its second key does not match the second selector.
The catch-all keys of the third variant * *
always match, and this is added to vars
,
resulting in a list « * *
» of variants.
As the list vars
only has one entry, it does not need to be sorted.
The pattern Otherwise
of the third variant is selected.
Alternatively, with the same implementation and formatting context as in Example 1, pattern selection would proceed as follows for this message:
.input {$foo :string}
.input {$bar :string}
.match $foo $bar
* bar {{Any and bar}}
foo * {{Foo and any}}
foo bar {{Foo and bar}}
* * {{Otherwise}}
For the first selector:
The value of the selector is resolved to be 'foo'
.
The available keys « 'foo'
» are compared to 'foo'
,
resulting in a list « 'foo'
» of matching keys.
For the second selector:
The value of the selector is resolved to be 'bar'
.
The available keys « 'bar'
» are compared to 'bar'
,
resulting in a list « 'bar'
» of matching keys.
Creating the list vars
of variants matching all keys:
The keys of all variants either match each selector exactly, or via the catch-all key,
resulting in a list « * bar
, foo *
, foo bar
, * *
» of variants.
Sorting the variants:
The list sortable
is first set with the variants in their source order
and scores determined by the second selector:
« ( 0, * bar
), ( 1, foo *
), ( 0, foo bar
), ( 1, * *
) »
This is then sorted as:
« ( 0, * bar
), ( 0, foo bar
), ( 1, foo *
), ( 1, * *
) ».
To sort according to the first selector, the scores are updated to:
« ( 1, * bar
), ( 0, foo bar
), ( 0, foo *
), ( 1, * *
) ».
This is then sorted as:
« ( 0, foo bar
), ( 0, foo *
), ( 1, * bar
), ( 1, * *
) ».
The pattern Foo and bar
of the most preferred foo bar
variant is selected.
A more-complex example is the matching found in selection APIs
such as ICU's PluralFormat
.
Suppose that this API is represented here by the function :number
.
This :number
function can match a given numeric value to a specific number literal
and also to a plural category (zero
, one
, two
, few
, many
, other
)
according to locale rules defined in CLDR.
Given a variable reference $count
whose value resolves to the number 1
and an en
(English) locale,
the pattern selection proceeds as follows for this message:
.input {$count :number}
.match $count
one {{Category match for {$count}}}
1 {{Exact match for {$count}}}
* {{Other match for {$count}}}
For the selector:
The value of the selector is resolved to an implementation-defined value
that is capable of performing English plural category selection on the value 1
.
The available keys « 'one'
, '1'
» are passed to
the implementation's MatchSelectorKeys method,
resulting in a list « '1'
, 'one'
» of matching keys.
Creating the list vars
of variants matching all keys:
The keys of all variants are included in the list of matching keys, or use the catch-all key,
resulting in a list « one
, 1
, *
» of variants.
Sorting the variants:
The list sortable
is first set with the variants in their source order
and scores determined by the selector key order:
« ( 1, one
), ( 0, 1
), ( 2, *
) »
This is then sorted as:
« ( 0, 1
), ( 1, one
), ( 2, *
) »
The pattern Exact match for {$count}
of the most preferred 1
variant is selected.
After pattern selection, each text and placeholder part of the selected pattern is resolved and formatted.
Resolved values cannot always be formatted by a given implementation. When such an error occurs during formatting, an appropriate Message Function Error is emitted and a fallback value is used for the placeholder with the error.
Implementations MAY represent the result of formatting using the most appropriate data type or structure. Some examples of these include:
Implementations SHOULD provide formatting result types that match user needs, including situations that require further processing of formatted messages. Implementations SHOULD encourage users to consider a formatted localised string as an opaque data structure, suitable only for presentation.
When formatting to a string, the default representation of all markup MUST be an empty string. Implementations MAY offer functionality for customizing this, such as by emitting XML-ish tags for each markup.
This section is non-normative.
An implementation might choose to return an interstitial object
so that the caller can "decorate" portions of the formatted value.
In ICU4J, the NumberFormatter
class returns a FormattedNumber
object,
so a pattern such as This is my number {42 :number}
might return
the character sequence This is my number
followed by a FormattedNumber
object representing the value 42
in the current locale.
A formatter in a web browser could format a message as a DOM fragment rather than as a representation of its HTML source.
If the resolved pattern includes any fallback values
and the formatting result is a concatenated string or a sequence of strings,
the string representation of each fallback value MUST be the concatenation of
a U+007B LEFT CURLY BRACKET {
,
the fallback value as a string,
and a U+007D RIGHT CURLY BRACKET }
.
For example, a message that is not well-formed would format to a string as
{�}
, unless a fallback string is defined in the formatting context, in which case that string would be used instead.
Messages contain text. Any text can be bidirectional text. That is, the text can can consist of a mixture of left-to-right and right-to-left spans of text. The display of bidirectional text is defined by the Unicode Bidirectional Algorithm [UAX9].
The directionality of the formatted message as a whole is provided by the formatting context.
Note
Keep in mind the difference between the formatted output of a message, which is the topic of this section, and the syntax of message prior to formatting. The processing of a message depends on the logical sequence of Unicode code points, not on the presentation of the message. Affordances to allow users appropriate control over the appearance of the message's syntax have been provided.
When a message is formatted, placeholders are replaced with their formatted representation. Applying the Unicode Bidirectional Algorithm to the text of a formatted message (including its formatted parts) can result in unexpected or undesirable spillover effects. Applying bidi isolation to each affected formatted value helps avoid this spillover in a formatted message.
Note that both the message and, separately, each placeholder need to have direction metadata for this to work. If an implementation supports formatting to something other than a string (such as a sequence of parts), the directionality of each formatted placeholder needs to be available to the caller.
If a formatted expression itself contains spans with differing directionality, its formatter SHOULD perform any necessary processing, such as inserting controls or isolating such parts to ensure that the formatted value displays correctly in a plain text context.
For example, an implementation could provide a
:currency
formatting function which inserts strongly directional characters, such as U+200F RIGHT-TO-LEFT MARK (RLM), U+200E LEFT-TO-RIGHT MARK (LRM), or U+061C ARABIC LETTER MARKER (ALM), to coerce proper display of the sign and currency symbol next to a formatted number. An example of this is formatting the value-1234.56
as the currencyAED
in thear-AE
locale. The formatted value appears like this:-1,234.56 د.إ.
The code point sequence for this string, as produced by the ICU4J
NumberFormat
function, includes U+200F U+200E at the start and U+200F at the end of the string. If it did not do this, the same string would appear like this instead:
A bidirectional isolation strategy is functionality in the formatter's processing of a message that produces bidirectional output text that is ready for display.
The Default Bidi Strategy is a bidirectional isolation strategy that uses isolating Unicode control characters around placeholder's formatted values. It is primarily intended for use in plain-text strings, where markup or other mechanisms are not available. Implementations MUST provide the Default Bidi Strategy as one of the bidirectional isolation strategies.
Implementations MAY provide other bidirectional isolation strategies.
Implementations MAY supply a bidirectional isolation strategy that performs no processing.
The Default Bidi Strategy is defined as follows:
msgdir
be the directionality of the whole message,
one of « 'LTR'
, 'RTL'
, 'unknown'
».
These correspond to the message having left-to-right directionality,
right-to-left directionality, and to the message's directionality not being known.exp
in pattern:fmt
be the formatted string representation of the resolved value of exp
.dir
be the directionality of fmt
,
one of « 'LTR'
, 'RTL'
, 'unknown'
», with the same meanings as for msgdir
.isolate
be
True if the u:dir
option of the resolved value of exp
has a value other than 'inherit'
,
or False otherwise.dir
is 'LTR'
:msgdir
is 'LTR'
in the formatted output
and isolate
is False,
let fmt
be itselffmt
with U+2066 LEFT-TO-RIGHT ISOLATE
and postfix it with U+2069 POP DIRECTIONAL ISOLATE.dir
is 'RTL'
:fmt
with U+2067 RIGHT-TO-LEFT ISOLATE
and postfix it with U+2069 POP DIRECTIONAL ISOLATE.fmt
with U+2068 FIRST STRONG ISOLATE
and postfix it with U+2069 POP DIRECTIONAL ISOLATE.Errors can occur during the processing of a message. Some errors can be detected statically, such as those due to problems with message syntax, violations of requirements in the data model, or requirements defined by a function. Other errors might be detected during selection or formatting of a given message. Where available, the use of validation tools is recommended, as early detection of errors makes their correction easier.
Syntax Errors and Data Model Errors apply to all message processors, and MUST be emitted as soon as possible. The other error categories are only emitted during formatting, but it might be possible to detect them with validation tools.
During selection and formatting, expression handlers MUST only emit Message Function Errors.
Implementations do not have to check for or emit Resolution Errors or Message Function Errors in expressions that are not otherwise used by the message, such as placeholders in unselected patterns or declarations that are never referenced during formatting.
When formatting a message with one or more errors, an implementation MUST provide a mechanism to discover and identify at least one of the errors. The exact form of error signaling is implementation defined. Some examples include throwing an exception, returning an error code, or providing a function or method for enumerating any errors.
For all valid messages, an implementation MUST enable a user to get a formatted result. The formatted result might include fallback values such as when a placeholder's expression produced an error during formatting.
The two above requirements MAY be fulfilled by a single formatting method, or separately by more than one such method.
When a message contains more than one error, or contains some error which leads to further errors, an implementation which does not emit all of the errors SHOULD prioritise Syntax Errors and Data Model Errors over others.
When an error occurs while resolving a selector
or calling MatchSelectorKeys with its resolved value,
the selector MUST NOT match any variant key other than the catch-all *
and a Bad Selector error MUST be emitted.
Syntax Errors occur when the syntax representation of a message is not well-formed.
Example invalid messages resulting in a Syntax Error:
{{Missing end braces
{{Missing one end brace}
Unknown {{expression}}
.local $var = {|no message body|}
Data Model Errors occur when a message is not valid due to violating one of the semantic requirements on its structure.
A Variant Key Mismatch occurs when the number of keys on a variant does not equal the number of selectors.
Example invalid messages resulting in a Variant Key Mismatch error:
.input {$one :func} .match $one 1 2 {{Too many}} * {{Otherwise}}
.input {$one :func} .input {$two :func} .match $one $two 1 2 {{Two keys}} * {{Missing a key}} * * {{Otherwise}}
A Missing Fallback Variant error occurs when the message does not include a variant with only catch-all keys.
Example invalid messages resulting in a Missing Fallback Variant error:
.input {$one :func} .match $one 1 {{Value is one}} 2 {{Value is two}}
.input {$one :func} .input {$two :func} .match $one $two 1 * {{First is one}} * 1 {{Second is one}}
A Missing Selector Annotation error occurs when the message contains a selector that does not directly or indirectly reference a declaration with a function.
Examples of invalid messages resulting in a Missing Selector Annotation error:
.match $one 1 {{Value is one}} * {{Value is not one}}
.local $one = {|The one|} .match $one 1 {{Value is one}} * {{Value is not one}}
.input {$one} .match $one 1 {{Value is one}} * {{Value is not one}}
A Duplicate Declaration error occurs when a variable is declared more than once. Note that an input variable is implicitly declared when it is first used, so explicitly declaring it after such use is also an error.
Examples of invalid messages resulting in a Duplicate Declaration error:
.input {$var :number maximumFractionDigits=0} .input {$var :number minimumFractionDigits=0} {{Redeclaration of the same variable}} .local $var = {$ext :number maximumFractionDigits=0} .input {$var :number minimumFractionDigits=0} {{Redeclaration of a local variable}} .input {$var :number minimumFractionDigits=0} .local $var = {$ext :number maximumFractionDigits=0} {{Redeclaration of an input variable}} .input {$var :number minimumFractionDigits=$var2} .input {$var2 :number} {{Redeclaration of the implicit input variable $var2}} .local $var = {$ext :someFunction} .local $var = {$error} .local $var2 = {$var2 :error} {{{$var} cannot be redefined. {$var2} cannot refer to itself}}
A Duplicate Option Name error occurs when the same identifier appears on the left-hand side of more than one option in the same expression.
Examples of invalid messages resulting in a Duplicate Option Name error:
Value is {42 :number style=percent style=decimal}
.local $foo = {horse :func one=1 two=2 one=1} {{This is {$foo}}}
A Duplicate Variant error occurs when the same list of keys is used for more than one variant.
Examples of invalid messages resulting in a Duplicate Variant error:
.input {$var :string} .match $var * {{The first default}} * {{The second default}}
.input {$x :string} .input {$y :string} .match $x $y * foo {{The first "foo" variant}} bar * {{The "bar" variant}} * |foo| {{The second "foo" variant}} * * {{The default variant}}
Resolution Errors occur when the runtime value of a part of a message cannot be determined.
An Unresolved Variable error occurs when a variable reference cannot be resolved.
For example, attempting to format either of the following messages would result in an Unresolved Variable error if done within a context that does not provide for the variable reference
$var
to be successfully resolved:The value is {$var}.
.input {$var :func} .match $var 1 {{The value is one.}} * {{The value is not one.}}
An Unknown Function error occurs when an expression includes a reference to a function which cannot be resolved.
For example, attempting to format either of the following messages would result in an Unknown Function error if done within a context that does not provide for the function
:func
to be successfully resolved:The value is {horse :func}.
.local $horse = {|horse| :func} .match $horse 1 {{The value is one.}} * {{The value is not one.}}
A Bad Selector error occurs when a message includes a selector with a resolved value which does not support selection.
For example, attempting to format this message would result in a Bad Selector error:
.local $day = {|2024-05-01| :date} .match $day * {{The due date is {$day}}}
A Message Function Error is any error that occurs when calling a function handler or which depends on validation associated with a specific function.
Implementations SHOULD provide a way for function handlers to emit (or cause to be emitted) any of the types of error defined in this section. Implementations MAY also provide implementation-defined Message Function Error types.
For example, attempting to format any of the following messages might result in a Message Function Error if done within a context that
- Provides for the variable reference
$user
to resolve to an object{ name: 'Kat', id: 1234 }
,- Provides for the variable reference
$field
to resolve to a string'address'
, and- Uses a
:get
message function which requires its argument to be an object and an optionfield
to be provided with a string value.The exact type of Message Function Error is determined by the function handler.
Hello, {horse :get field=name}!
Hello, {$user :get}!
.local $id = {$user :get field=id} {{Hello, {$id :get field=name}!}}
Your {$field} is {$id :get field=$field}
A Bad Operand error is any error that occurs due to the content or format of the operand, such as when the operand provided to a function during function resolution does not match one of the expected implementation-defined types for that function; or in which a literal operand value does not have the required format and thus cannot be processed into one of the expected implementation-defined types for that specific function.
For example, the following messages each produce a Bad Operand error because the literal
|horse|
does not match thenumber-literal
production, which is a requirement of the function:number
for its operand:.local $horse = {|horse| :number} {{You have a {$horse}.}}
.local $horse = {|horse| :number} .match $horse 1 {{The value is one.}} * {{The value is not one.}}
A Bad Option error is an error that occurs when there is an implementation-defined error with an option or its value. These might include:
For example, the following message might produce a Bad Option error because the literal
foo
does not match the productiondigit-size-option
, which is a requirement of the function:number
for itsminimumFractionDigits
option:The answer is {42 :number minimumFractionDigits=foo}.
A Bad Variant Key error is an error that occurs when a variant key does not match the expected implementation-defined format.
For example, the following message produces a Bad Variant Key error because
horse
is not a recognized plural category and does not match thenumber-literal
production, which is a requirement of the:number
function:.local $answer = {42 :number} .match $answer 1 {{The value is one.}} horse {{The value is a horse.}} * {{The value is not one.}}
A Unsupported Operation error is an implementation-specific error that occurs when a given option, option value, operand value, or some combination of these are incompatible or not supported by a given function or its function handler.
This section defines the REQUIRED functions which are REQUIRED for conformance with this specification, along with RECOMMENDED functions that SHOULD be implemented to support additional functionality.
To accept a function means that an implementation MUST NOT emit an Unknown Function error for that function's identifier. To accept an option means that a function handler MUST NOT emit a Bad Option error for that option's identifier when used with the function it is defined for and MUST NOT emit a Bad Option error for any of the option values defined for that option. Accepting a function or its options does not mean that a particular output is produced. Implementations MAY emit an Unsupported Operation error for options or option values that they cannot support.
Functions can define options. An option can be REQUIRED or RECOMMENDED.
Implementations MUST accept each REQUIRED function and MUST accept all options defined as REQUIRED for those functions.
Implementations SHOULD accept each RECOMMENDED function. For each such function, the implementation MUST accept all options listed as REQUIRED for that function.
Implementations SHOULD accept options that are marked as RECOMMENDED.
Implementations MAY accept functions not defined in this specification. In addition, implementations SHOULD provide mechanisms for users to register and use user-defined functions and their associated functional handlers. Functions not defined by any version of this specification SHOULD use an implementation-defined or user-defined namespace.
Implementations MAY implement additional options not defined by any version of this specification for REQUIRED and RECOMMENDED functions. Such options MUST use an implementation-specific namespace.
Implementations MAY accept, for options defined in this specification, option values which are not defined in this specification. However, such values might become defined with a different meaning in the future, including with a different, incompatible name or using an incompatible value space. Supporting implementation-specific option values for REQUIRED or RECOMMENDED functions is NOT RECOMMENDED.
Implementations MAY accept, for operands or options defined in this specification, values with implementation-defined types. Such values can be useful to users in cases where local usage and support exists (including cases in which details vary from those defined by Unicode and CLDR).
For example:
- Implementations are encouraged to accept some native representation for currency amounts as the operand in the function
:currency
.- A Java implementation might accept a
java.time.chrono.Chronology
object as a value for the date/time override optioncalendar
- ICU4J's implementation might accept a
com.ibm.icu.text.NumberingSystem
object instead of using a Unicode Numbering System Identifier for the optionnumberingSystem
in functions such as:number
or:integer
.
Future versions of this specification MAY define additional options and option values, subject to the rules in the Stability Policy, for functions found in this specification. As implementations are permitted to ignore options that they do not support, it is possible to write messages using options not defined below which currently format with no error, but which could produce errors when formatted with a later edition of this specification. Therefore, using options not explicitly defined here is NOT RECOMMENDED.
:string
functionThe function :string
provides string selection and formatting.
The operand of :string
is either any implementation-defined type
that is a string or for which conversion to a string is supported,
or any literal value.
All other values produce a Bad Operand error.
For example, in Java, implementations of the
java.lang.CharSequence
interface (such asjava.lang.String
orjava.lang.StringBuilder
), the typechar
, or the classjava.lang.Character
might be considered as the "implementation-defined types". Such an implementation might also support other classes via the methodtoString()
. This might be used to enable selection of aenum
value by name, for example.Other programming languages would define string and character sequence types or classes according to their local needs, including, where appropriate, coercion to string.
The function :string
has no options.
Note
While :string
has no built- in options,
options in the u:
namespace can be used.
For example:
{$s :string u:dir=ltr u:locale=fr-CA}
The resolved value of an expression with a :string
function
contains the string value of the operand of the annotated expression,
together with its resolved locale and directionality.
None of the options set on the expression are part of the resolved value.
When implementing MatchSelectorKeys(resolvedSelector, keys)
where resolvedSelector
is the resolved value of a selector
and keys
is a list of strings,
the :string
selector function performs as described below.
compare
be the string value of resolvedSelector
in Unicode Normalization Form C (NFC) [UAX#15]result
be a new empty list of strings.key
in keys
:key
and compare
consist of the same sequence of Unicode code points, thenkey
as the last element of the list result
.result
.Note
Unquoted string literals in a variant do not include spaces.
If users wish to match strings that include whitespace
(including U+3000 IDEOGRAPHIC SPACE
)
to a key, the key
needs to be quoted.
For example:
.input {$string :string}
.match $string
| space key | {{Matches the string " space key "}}
* {{Matches the string "space key"}}
The :string
function returns the string value of the resolved value of the operand.
Note
The function :string
does not perform Unicode Normalization of its formatted output.
Users SHOULD encode messages and their parts in Unicode Normalization Form C (NFC)
unless there is a very good reason not to.
:number
functionThe function :number
is a selector and formatter for numeric values.
The function :number
requires a Number Operand as its operand.
Some options do not have default values defined in this specification. The defaults for these options are implementation-dependent. In general, the default values for such options depend on the locale, the value of other options, or both.
Note
The names of options and their values were derived from the
options
in JavaScript's Intl.NumberFormat
.
The following options and their values are required to be available on the function :number
:
select
plural
(default; see Default Value of select
Option below)ordinal
exact
compactDisplay
(this option only has meaning when combined with the option notation=compact
)short
(default)long
notation
standard
(default)scientific
engineering
compact
numberingSystem
signDisplay
auto
(default)always
exceptZero
negative
never
style
decimal
(default)percent
(see Percent Style below)useGrouping
auto
(default)always
never
min2
minimumIntegerDigits
1
)minimumFractionDigits
maximumFractionDigits
minimumSignificantDigits
maximumSignificantDigits
trailingZeroDisplay
auto
(default)stripIfInteger
roundingPriority
auto
(default)morePrecision
lessPrecision
roundingIncrement
roundingMode
ceil
floor
expand
trunc
halfCeil
halfFloor
halfExpand
(default)halfTrunc
halfEven
If the operand of the expression is an implementation-defined type,
such as the resolved value of an expression with a :number
or :integer
annotation,
it can include option values.
These are included in the resolved option values of the expression,
with options on the expression taking priority over any option values of the operand.
For example, the placeholder in this message:
.input {$n :number notation=scientific minimumFractionDigits=2} {{{$n :number minimumFractionDigits=1}}}
would be formatted with the resolved options
{ notation: 'scientific', minimumFractionDigits: '1' }
.
select
OptionThe value plural
is the default for the option select
because it is the most common use case for numeric selection.
It can be used for exact value matches but also allows for the grammatical needs of
languages using CLDR's plural rules.
This might not be noticeable in the source language (particularly English),
but can cause problems in target locales that the original developer is not considering.
For example, a naive developer might use a special message for the value
1
without considering a locale's need for aone
plural:.input {$var :number} .match $var 1 {{You have one last chance}} one {{You have {$var} chance remaining}} * {{You have {$var} chances remaining}}
The
one
variant is needed by languages such as Polish or Russian. Such locales typically also require other keywords such astwo
,few
, andmany
.
When implementing style=percent
, the numeric value of the operand
MUST be multiplied by 100 for the purposes of formatting.
For example,
The total was {0.5 :number style=percent}.
should format in a manner similar to:
The total was 50%.
The resolved value of an expression with a :number
function
contains an implementation-defined numerical value
of the operand of the annotated expression,
together with the resolved options' values.
The function :number
performs selection as described in Number Selection below.
:integer
functionThe function :integer
is a selector and formatter for matching or formatting numeric
values as integers.
The function :integer
requires a Number Operand as its operand.
Some options do not have default values defined in this specification. The defaults for these options are implementation-dependent. In general, the default values for such options depend on the locale, the value of other options, or both.
Note
The names of options and their values were derived from the
options
in JavaScript's Intl.NumberFormat
.
The following options and their values are required in the default registry to be available on the
function :integer
:
select
plural
(default)ordinal
exact
numberingSystem
signDisplay
auto
(default)always
exceptZero
negative
never
style
decimal
(default)percent
(see Percent Style below)useGrouping
auto
(default)always
never
min2
minimumIntegerDigits
1
)maximumSignificantDigits
If the operand of the expression is an implementation-defined type,
such as the resolved value of an expression with a :number
or :integer
annotation,
it can include option values.
In general, these are included in the resolved option values of the expression,
with options on the expression taking priority over any option values of the operand.
Option values with the following names are however discarded if included in the operand:
compactDisplay
notation
minimumFractionDigits
maximumFractionDigits
minimumSignificantDigits
select
OptionThe value plural
is the default for the option select
because it is the most common use case for numeric selection.
It can be used for exact value matches but also allows for the grammatical needs of
languages using CLDR's plural rules.
This might not be noticeable in the source language (particularly English),
but can cause problems in target locales that the original developer is not considering.
For example, a naive developer might use a special message for the value
1
without considering a locale's need for aone
plural:.input {$var :integer} .match $var 1 {{You have one last chance}} one {{You have {$var} chance remaining}} * {{You have {$var} chances remaining}}
The
one
variant is needed by languages such as Polish or Russian. Such locales typically also require other keywords such astwo
,few
, andmany
.
When implementing style=percent
, the numeric value of the operand
MUST be multiplied by 100 for the purposes of formatting.
For example,
The total was {0.5 :number style=percent}.
should format in a manner similar to:
The total was 50%.
The resolved value of an expression with an :integer
function
contains the implementation-defined integer value
of the operand of the annotated expression,
together with the resolved options' values.
The function :integer
performs selection as described in Number Selection below.
:math
functionThe function :math
is a selector and formatter for matching or formatting
numeric values to which a mathematical operation has been applied.
This function is useful for selection and formatting of values that differ from the input value by a specified amount. For example, it can be used in a message such as this:
.input {$like_count :integer} .local $others_count = {$like_count :math subtract=1} .match $like_count $others_count 0 * {{Your post has no likes.}} 1 * {{{$name} liked your post.}} * one {{{$name} and {$others_count} other user liked your post.}} * * {{{$name} and {$others_count} other users liked your post.}}
The function :math
requires a Number Operand as its operand.
The options on :math
are exclusive with each other,
and exactly one option is always required.
The options do not have default values.
The following options and their values are
required in the default registry to be available on the function :math
:
add
subtract
If no options or more than one option is set, or if an option value is not a digit size option, a Bad Option error is emitted and a fallback value used as the resolved value of the expression.
The resolved value of an expression with a :math
function
contains the implementation-defined numeric value
of the operand of the annotated expression.
If the add
option is set,
the numeric value of the resolved value is formed by incrementing
the numeric value of the operand by the integer value of the digit size option value.
If the subtract
option is set,
the numeric value of the resolved value is formed by decrementing
the numeric value of the operand by the integer value of the digit size option value.
If the operand of the expression is an implementation-defined numeric type,
such as the resolved value of an expression with a :number
or :integer
annotation,
it can include option values.
These are included in the resolved option values of the expression.
The :math
options are not included in the resolved option values.
Note
Implementations can encounter practical limits with :math
expressions,
such as the result of adding two integers exceeding
the storage or precision of some implementation-defined number type.
In such cases, implementations can emit an Unsupported Operation error
or they might just silently overflow the underlying data value.
The function :math
performs selection as described in Number Selection below.
:currency
functionThe function :currency
is a selector and formatter for currency values,
which are a specialized form of numeric selection and formatting.
The operand of the :currency
function can be one of any number of
implementation-defined types,
each of which contains a numerical value
and a currency
;
or it can be a Number Operand, as long as the option
currency
is provided.
The option currency
MUST NOT be used to override the currency of an implementation-defined type.
Using this option in such a case results in a Bad Option error.
The value of the operand's currency
MUST be either a string containing a
well-formed Unicode Currency Identifier
or an implementation-defined currency type.
Although currency codes are expected to be uppercase,
implementations SHOULD treat them in a case-insensitive manner.
A well-formed Unicode Currency Identifier matches the production currency_code
in this ABNF:
currency_code = 3ALPHA
A Number Operand without a currency
option results in a Bad Operand error.
Note
For example, in ICU4J, the type com.ibm.icu.util.CurrencyAmount
can be used
to set the amount and currency.
Note
The currency
is only required to be well-formed rather than checked for validity.
This allows new currency codes to be defined
(there are many recent examples of this occuring).
It also avoids requiring implementations to check currency codes for validity,
although implementations are permitted to emit Bad Option or Bad Operand for invalid codes.
Note
For runtime environments that do not provide a ready-made data structure, class, or type for currency values, the implementation ought to provide a data structure, convenience function, or documentation on how to encode the value and currency code for formatting. For example, such an implementation might define a "currency operand" to include a key-value structure with specific keys to be the local currency operand, which might look like the following:
{
"value": 123.45,
"currency": "EUR"
}
Some options do not have default values defined in this specification. The defaults for these options are implementation-dependent. In general, the default values for such options depend on the locale, the currency, the value of other options, or all of these.
Fraction digits for currency values behave differently than for other numeric formatters.
The number of fraction digits displayed is usually set by the currency used.
For example, USD uses 2 fraction digits, while JPY uses none.
Setting some other number of fractionDigits
allows greater precision display
(such as when performing currency conversions or other specialized operations)
or disabling fraction digits if set to 0
.
The option trailingZeroDisplay
has a value stripIfInteger
that is useful
for displaying currencies with their fraction digits removed when the fraction
part of the operand is zero.
This is sometimes used in messages to make the displayed value omit the fraction part
automatically.
For example, this message:
The special price is {$price :currency trailingZeroDisplay=stripIfInteger}.
When used with the value
5.00 USD
in theen-US
locale displays as:The special price is $5.
But like this when when value is
5.01 USD
:The special price is $5.01.
Implementations MAY internally alias option values that they do not have data or a backing implementation for.
Notably, the currencyDisplay
option has a rich set of values that mirrors developments in CLDR data.
Some implementations might not be able to produce all of these formats for every currency.
Note
Except where noted otherwise, the names of options and their values were derived from the
options
in JavaScript's Intl.NumberFormat
.
Note
The option select
does not accept the value ordinal
because selecting
currency values using ordinal rules makes no sense.
The following options and their values are required to be available on the function :currency
:
select
plural
(default)exact
currency
compactDisplay
(this option only has meaning when combined with the option notation=compact
)short
(default)long
notation
standard
(default)compact
numberingSystem
currencySign
accounting
standard
(default)currencyDisplay
narrowSymbol
symbol
(default)name
code
formalSymbol
never
(this is called hidden
in ICU)useGrouping
auto
(default)always
never
min2
minimumIntegerDigits
1
)fractionDigits
(unlike number/integer formats, the fraction digits for currency formatting are fixed)auto
(default) (the number of digits used by the currency)minimumSignificantDigits
maximumSignificantDigits
trailingZeroDisplay
auto
(default)stripIfInteger
roundingPriority
auto
(default)morePrecision
lessPrecision
roundingIncrement
roundingMode
ceil
floor
expand
trunc
halfCeil
halfFloor
halfExpand
(default)halfTrunc
halfEven
If the operand of the expression is an implementation-defined type,
such as the resolved value of an expression with a :currency
annotation,
it can include option values.
These are included in the resolved option values of the expression,
with options on the expression taking priority over any option values of the operand.
For example, the placeholder in this message:
.input {$n :currency currency=USD trailingZeroDisplay=stripIfInteger} {{{$n :currency currencySign=accounting}}}
would be formatted with the resolved options
{ currencySign: 'accounting', trailingZeroDisplay: 'stripIfInteger', currency: 'USD' }
.
The resolved value of an expression with a :currency
function
contains an implementation-defined currency value
of the operand of the annotated expression,
together with the resolved options' values.
The function :currency
performs selection as described in Number Selection below.
:unit
functionThe function :unit
is Proposed for inclusion in the next release of this specification but has not yet been finalized.
The function :unit
is proposed to be a RECOMMENDED selector and formatter for unitized values,
that is, for numeric values associated with a unit of measurement.
This is a specialized form of numeric selection and formatting.
The operand of the :unit
function can be one of any number of
implementation-defined types,
each of which contains a numerical value
plus a unit
or it can be a Number Operand, as long as the option
unit
is provided.
The value of the operand's unit
SHOULD be either a string containing a
valid Unit Identifier
or an implementation-defined unit type.
A Number Operand without a unit
option results in a Bad Operand error.
Note
For example, in ICU4J, the type com.ibm.icu.util.Measure
might be used
as an operand for :unit
because it contains the value
and unit
.
Note
For runtime environments that do not provide a ready-made data structure, class, or type for unit values, the implementation ought to provide a data structure, convenience function, or documentation on how to encode the value and unit for formatting. For example, such an implementation might define a "unit operand" to include a key-value structure with specific keys to be the local unit operand, which might look like the following:
{
"value": 123.45,
"unit": "kilometer-per-hour"
}
Some options do not have default values defined in this specification. The defaults for these options are implementation-dependent. In general, the default values for such options depend on the locale, the unit, the value of other options, or all of these.
Note
The option select
does not accept the value ordinal
because selecting
unit values using ordinal rules makes no sense.
The following options and their values are required to be available on the function :unit
:
select
plural
(default)exact
unit
usage
[RECOMMENDED]unitDisplay
short
(default)narrow
long
compactDisplay
(this option only has meaning when combined with the option notation=compact
)short
(default)long
notation
standard
(default)compact
numberingSystem
signDisplay
auto
(default)always
exceptZero
negative
never
useGrouping
auto
(default)always
never
min2
minimumIntegerDigits
1
)minimumFractionDigits
maximumFractionDigits
minimumSignificantDigits
maximumSignificantDigits
roundingPriority
auto
(default)morePrecision
lessPrecision
roundingIncrement
roundingMode
ceil
floor
expand
trunc
halfCeil
halfFloor
halfExpand
(default)halfTrunc
halfEven
If the operand of the expression is an implementation-defined type,
such as the resolved value of an expression with a :unit
annotation,
it can include option values.
These are included in the resolved option values of the expression,
with options on the expression taking priority over any option values of the operand.
For example, the placeholder in this message:
.input {$n :unit unit=furlong minimumFractionDigits=2} {{{$n :unit minimumIntegerDigits=1}}}
would have the resolved options:
{ unit: 'furlong', minimumFractionDigits: '2', minimumIntegerDigits: '1' }
.
The resolved value of an expression with a :unit
function
consist of an implementation-defined unit value
of the operand of the annotated expression,
together with the resolved options and their resolved values.
The function :unit
performs selection as described in Number Selection below.
Implementations MAY support conversion to the locale's preferred units via the usage
option.
Implementing this option is optional.
Not all usage
values are compatible with a given unit.
Implementations SHOULD emit an Unsupported Operation error if the requested conversion is not supported.
For example, trying to convert a
length
unit (such as "meters") to avolume
usage (which might be a unit akin to "liters" or "gallons", depending on the locale) could produce an Unsupported Operation error.
Implementations MUST NOT substitute the unit without performing the associated conversion.
For example, consider the value:
{ "value": 123.5, "unit": "meter" }
The following message might convert the formatted result to U.S. customary units in the
en-US
locale:You have {$v :unit usage=road maximumFractionDigits=0} to go.
This can produce "You have 405 feet to go."
The operand of a number function is either an implementation-defined type or
a literal whose contents match the number-literal
production in the ABNF.
All other values produce a Bad Operand error.
For example, in Java, any subclass of
java.lang.Number
plus the primitive types (byte
,short
,int
,long
,float
,double
, etc.) might be considered as the "implementation-defined numeric types". Implementations in other programming languages would define different types or classes according to their local needs.
Note
String values passed as variables in the formatting context's
input mapping can be formatted as numeric values as long as their
contents match the number-literal
production in the ABNF.
For example, if the value of the variable num
were the string
-1234.567
, it would behave identically to the local
variable in this example:
.local $example = {|-1234.567| :number}
{{{$num :number} == {$example}}}
Note
Implementations are encouraged to provide support for compound types or data structures
that provide additional semantic meaning to the formatting of number-like values.
For example, in ICU4J, the type com.ibm.icu.util.Measure
can be used to communicate
a value that includes a unit
or the type com.ibm.icu.util.CurrencyAmount
can be used to set the currency and related
options (such as the number of fraction digits).
Some options of number functions are defined to take a "digit size option". The function handlers for number functions use these options to control aspects of numeric display such as the number of fraction, integer, or significant digits.
A "digit size option" is an option value that the function interprets as a small integer value greater than or equal to zero. Implementations MAY define an upper limit on the resolved value of a digit size option option consistent with that implementation's practical limits.
In most cases, the value of a digit size option will be a string that encodes the value as a non-negative integer. Implementations MAY also accept implementation-defined types as the value. When provided as a string, the representation of a digit size option matches the following ABNF:
digit-size-option = "0" / (("1"-"9") [DIGIT])
If the value of a digit size option does not evaluate as a non-negative integer, or if the value exceeds any implementation-defined upper limit or any option-specific lower limit, a Bad Option Error is emitted.
Number selection has three modes:
exact
selection matches the operand to explicit numeric keys exactlyplural
selection matches the operand to explicit numeric keys exactly
followed by a plural rule category if there is no explicit matchordinal
selection matches the operand to explicit numeric keys exactly
followed by an ordinal rule category if there is no explicit matchWhen implementing MatchSelectorKeys(resolvedSelector, keys)
where resolvedSelector
is the resolved value of a selector
and keys
is a list of strings,
numeric selectors perform as described below.
exact
be the serialized representation of the numeric value of resolvedSelector
.
(See Exact Literal Match Serialization for details)keyword
be a string which is the result of rule selection on resolvedSelector
.resultExact
be a new empty list of strings.resultKeyword
be a new empty list of strings.key
in keys
:key
matches the production number-literal
, thenkey
and exact
consist of the same sequence of Unicode code points, thenkey
as the last element of the list resultExact
.key
is one of the keywords zero
, one
, two
, few
, many
, or other
, thenkey
and keyword
consist of the same sequence of Unicode code points, thenkey
as the last element of the list resultKeyword
.resultExact
followed by the elements (in order) of resultKeyword
.Note
Implementations are not required to implement this exactly as written. However, the observed behavior must be consistent with what is described here.
Rule selection is intended to support the grammatical matching needs of different languages/locales in order to support plural or ordinal numeric values.
If the option select
is set to exact
, rule-based selection is not used.
Otherwise rule selection matches the operand, as modified by function options, to exactly one of these keywords:
zero
, one
, two
, few
, many
, or other
.
The keyword other
is the default.
Note
Since valid keys cannot be the empty string in a numeric expression, returning the empty string disables keyword selection.
The meaning of the keywords is locale-dependent and implementation-defined.
A key that matches the rule-selected keyword is a stronger match than the fallback key *
but a weaker match than any exact match key value.
The rules for a given locale might not produce all of the keywords. A given operand value might produce different keywords depending on the locale.
Apply the rules to the resolved value of the operand and the relevant function options,
and return the resulting keyword.
If no rules match, return other
.
If the option select
is set to plural
, the rules applied to selection SHOULD be
the CLDR plural rule data of type cardinal
.
See charts
for examples.
If the option select
is set to ordinal
, the rules applied to selection SHOULD be
the CLDR plural rule data of type ordinal
.
See charts
for examples.
Example. In CLDR 44, the Czech (
cs
) plural rule set can be found here.A message in Czech might be:
.input {$numDays :number} .match $numDays one {{{$numDays} den}} few {{{$numDays} dny}} many {{{$numDays} dne}} * {{{$numDays} dní}}
Using the rules found above, the results of various operand values might look like:
Operand value Keyword Formatted Message 1 one
1 den 2 few
2 dny 5 other
5 dní 22 few
22 dny 27 other
27 dní 2.4 many
2,4 dne
If the numeric value of resolvedSelector
is an integer
and none of the following options are set for resolvedSelector
,
the serialized form of the numeric value MUST match the ABNF defined below for integer
,
representing its decimal value:
minimumFractionDigits
minimumIntegerDigits
minimumSignificantDigits
maximumSignificantDigits
notation
style
integer = "0" / ["-"] ("1"-"9") *DIGIT
Otherwise, the serialized form of the numeric value is implementation-defined.
Important
The exact behavior of exact literal match is only well defined for integer values without leading zeros. Functions that use fraction digits or significant digits might work in specific implementation-defined ways. Users should avoid depending on these types of keys in message selection.
This subsection describes the functions and options for date/time formatting. Selection based on date and time values is not required in this release.
Note
Selection based on date/time types is not required by MF2.
Implementations should use care when defining selectors based on date/time types.
The types of queries found in implementations such as java.time.TemporalAccessor
are complex and user expectations may be inconsistent with good I18N practices.
:datetime
functionThe function :datetime
is used to format date/time values, including
the ability to compose user-specified combinations of fields.
If no options are specified, this function defaults to the following:
{$d :datetime}
is the same as {$d :datetime dateStyle=medium timeStyle=short}
Note
The default formatting behavior of :datetime
is inconsistent with Intl.DateTimeFormat
in JavaScript and with {d,date}
in ICU MessageFormat 1.0.
This is because, unlike those implementations, :datetime
is distinct from :date
and :time
.
The operand of the :datetime
function is either
an implementation-defined date/time type
or a date/time literal value, as defined in Date and Time Operand.
All other operand values produce a Bad Operand error.
The :datetime
function can use either the appropriate style options
or can use a collection of field options (but not both) to control the formatted
output.
Date/time override options can be combined with either style options or field options.
If both style options and field options are specified, a Bad Option error is emitted and a fallback value used as the resolved value of the expression.
If the operand of the expression is an implementation-defined date/time type, it can include style options, field options, or other option values. These are included in the resolved option values of the expression, with options on the expression taking priority over any option values of the operand.
Note
The names of options and their values were derived from the
options
in JavaScript's Intl.DateTimeFormat
.
The function :datetime
has these style options.
dateStyle
full
long
medium
short
timeStyle
full
long
medium
short
Field options describe which fields to include in the formatted output and what format to use for that field.
Note
Field options do not have default values because they are only to be used to compose the formatter.
The field options are defined as follows:
Important
The value 2-digit
for some field options MUST be quoted
in the MessageFormat syntax because it starts with a digit
but does not match the number-literal
production in the ABNF.
.local $correct = {$someDate :datetime year=|2-digit|}
.local $syntaxError = {$someDate :datetime year=2-digit}
The function :datetime
has the following options:
weekday
long
short
narrow
era
long
short
narrow
year
numeric
2-digit
month
numeric
2-digit
long
short
narrow
day
numeric
2-digit
hour
numeric
2-digit
minute
numeric
2-digit
second
numeric
2-digit
fractionalSecondDigits
1
2
3
timeZoneName
long
short
shortOffset
longOffset
shortGeneric
longGeneric
The resolved value of an expression with a :datetime
function
contains an implementation-defined date/time value
of the operand of the annotated expression,
together with the resolved options values.
:date
functionThe function :date
is used to format the date portion of date/time values.
If no options are specified, this function defaults to the following:
{$d :date}
is the same as {$d :date style=medium}
The operand of the :date
function is either
an implementation-defined date/time type
or a date/time literal value, as defined in Date and Time Operand.
All other operand values produce a Bad Operand error.
The function :date
has these options:
style
full
long
medium
(default)short
If the operand of the expression is an implementation-defined date/time type,
it can include other option values.
Any operand option values matching the :datetime
style options or field options are ignored,
as is any style
option.
The resolved value of an expression with a :date
function
is implementation-defined.
An implementation MAY emit a Bad Operand or Bad Option error (as appropriate)
when a variable annotated directly or indirectly by a :date
annotation
is used as an operand or an option value.
:time
functionThe function :time
is used to format the time portion of date/time values.
If no options are specified, this function defaults to the following:
{$t :time}
is the same as {$t :time style=short}
The operand of the :time
function is either
an implementation-defined date/time type
or a date/time literal value, as defined in Date and Time Operand.
All other operand values produce a Bad Operand error.
The function :time
has these options:
style
full
long
medium
short
(default)If the operand of the expression is an implementation-defined date/time type,
it can include other option values.
Any operand option values matching the :datetime
style options or field options are ignored,
as is any style
option.
The resolved value of an expression with a :time
function
is implementation-defined.
An implementation MAY emit a Bad Operand or Bad Option error (as appropriate)
when a variable annotated directly or indirectly by a :time
annotation
is used as an operand or an option value.
The operand of a date/time function is either an implementation-defined date/time type or a date/time literal value, as defined below. All other operand values produce a Bad Operand error.
A date/time literal value is a non-empty string consisting of an ISO 8601 date, or an ISO 8601 datetime optionally followed by a timezone offset. As implementations differ slightly in their parsing of such strings, ISO 8601 date and datetime values not matching the following regular expression MAY also be supported. Furthermore, matching this regular expression does not guarantee validity, given the variable number of days in each month.
(?!0000)[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])(T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]{1,3})?(Z|[+-]((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?
When the time is not present, implementations SHOULD use 00:00:00
as the time.
When the offset is not present, implementations SHOULD use a floating time type
(such as Java's java.time.LocalDateTime
) to represent the time value.
For more information, see Working with Timezones.
Important
The ABNF and syntax of MF2 do not formally define date/time literals. This means that a message can be syntactically valid but produce a Bad Operand error at runtime.
Note
String values passed as variables in the formatting context's input mapping can be formatted as date/time values as long as their contents are date/time literals.
For example, if the value of the variable now
were the string
2024-02-06T16:40:00Z
, it would behave identically to the local
variable in this example:
.local $example = {|2024-02-06T16:40:00Z| :datetime}
{{{$now :datetime} == {$example}}}
Note
True time zone support in serializations is expected to coincide with the adoption of Temporal in JavaScript. The form of these serializations is known and is a de facto standard. Support for these extensions is expected to be required in the post-tech preview. See: https://datatracker.ietf.org/doc/draft-ietf-sedate-datetime-extended/
Date/time override options are options that allow an expression to override values set by the current locale, or provided by the formatting context (such as the default time zone), or embedded in an implementation-defined date/time operand value.
Note
These options do not have default values because they are only to be used as overrides for locale-and-value dependent implementation-defined defaults.
The following option and its values are REQUIRED to be available on
the functions :datetime
and :time
:
hour12
true
false
The following options and their values are RECOMMENDED to be available on
the functions :datetime
, :date
, and :time
.
calendar
numberingSystem
The following option and its values are Proposed for
inclusion in the next release of this specification but have not yet been
finalized.
If accepted, implementations could be REQUIRED to make this option
available in the functions :datetime
, :date
, and :time
.
Note
The value local
permits a message to convert a date/time value
into a floating time value
(sometimes called a plain or local time value) by removing
the association with a specific time zone.
The u:
namespace is reserved for the definition of options
which affect the function context of the specific expressions
in which they appear,
or for the definition of options that are universally applicable
rather than function-specific.
It might also be used to define functions in a future release.
The CLDR Technical Committee of the Unicode Consortium
manages the specification for this namespace, hence the namespace u:
.
This section describes common u:
options which each implementation SHOULD support
for all functions and markup.
u:id
A string value that is included as an id
or other suitable value
in the formatted parts for the placeholder,
or any other structured formatted results.
Ignored when formatting a message to a string.
The value of the u:id
option MUST be a literal or a
variable whose resolved value is either a string
or can be resolved to a string without error.
For other values, a Bad Option error is emitted
and the u:id
option is ignored.
u:locale
Replaces the locale defined in the function context for this expression.
A comma-delimited list consisting of well-formed BCP 47 language tags, or an implementation-defined list of such tags.
If this option is set on markup, a Bad Option error is emitted
and the value of the u:locale
option is ignored.
During processing, the u:locale
option
MUST be removed from the resolved mapping of options
before calling the function handler.
Values matching the following ABNF are always accepted:
u-locale-option = unicode_bcp47_locale_id *(o "," o unicode_bcp47_locale_id)
using unicode_bcp47_locale_id
as defined for
Unicode Locale Identifier.
Implementations MAY support additional language tags,
such as private-use or grandfathered tags,
or tags using _
instead of -
as a separator.
When the value of u:locale
is set by a variable,
implementations MAY support non-string values otherwise representing locales.
Implementations MAY emit a Bad Option error
and MAY ignore the value of the u:locale
option as a whole
or any of the entries in the list of language tags.
This might be because the locale specified is not supported
or because the language tag is not well-formed,
not valid, or some other reason.
u:dir
Replaces the base directionality defined in the function context for this expression and applies bidirectional isolation to it.
If this option is set on markup, a Bad Option error is emitted
and the value of the u:dir
option is ignored.
During processing, the u:dir
option
MUST be removed from the resolved mapping of options
before calling the function handler.
Its value is retained in the resolved value of the expression.
The value of the u:dir
option MUST be one of the following literal values
or a variable whose resolved value is one of these literals:
ltr
: left-to-right directionalityrtl
: right-to-left directionalityauto
: directionality determined from expression contentsinherit
(default): directionality inherited from the message
or from the resolved value of the operand without
requiring isolation of the expression value.For other values, a Bad Option error is emitted
and the value of the u:dir
option is ignored.
This section defines a data model representation of MessageFormat 2 messages.
Implementations are not required to use this data model for their internal representation of messages. Neither are they required to provide an interface that accepts or produces representations of this data model.
The major reason this specification provides a data model is to allow interchange of the logical representation of a message between different implementations. This includes mapping legacy formatting syntaxes (such as MessageFormat 1) to a MessageFormat 2 implementation. Another use would be in converting to or from translation formats without the need to continually parse and serialize all or part of a message.
Implementations that expose APIs supporting the production, consumption, or transformation of a message as a data structure are encouraged to use this data model.
This data model provides these capabilities:
This data model might also be used to:
To ensure compatibility across all platforms, this interchange data model is defined here using TypeScript notation. Two equivalent definitions of the data model are also provided:
message.json
is a JSON Schema definition,
for use with message data encoded as JSON or compatible formats, such as YAML.message.dtd
is a document type definition (DTD),
for use with message data encoded as XML.Note that while the data model description below is the canonical one, the JSON and DTD definitions are intended for interchange between systems and processors. To that end, they relax some aspects of the data model, such as allowing declarations, options, and attributes to be optional rather than required properties.
Note
Users relying on XML representations of messages should note that XML 1.0 does not allow for the representation of all C0 control characters (U+0000-U+001F). Except for U+0000 NULL , these characters are allowed in MessageFormat 2 messages, so systems and users relying on this XML representation for interchange might need to supply an alternate escape mechanism to support messages that contain these characters.
Important
The data model uses the field name name
to denote various interface identifiers.
In the MessageFormat 2 syntax, the source for these name
fields
sometimes uses the production identifier
.
This happens when the named item, such as a function, supports namespacing.
A SelectMessage
corresponds to a syntax message that includes selectors.
A message without selectors and with a single pattern is represented by a PatternMessage
.
In the syntax,
a PatternMessage
may be represented either as a simple message or as a complex message,
depending on whether it has declarations and if its pattern
is allowed in a simple message.
type Message = PatternMessage | SelectMessage;
interface PatternMessage {
type: "message";
declarations: Declaration[];
pattern: Pattern;
}
interface SelectMessage {
type: "select";
declarations: Declaration[];
selectors: VariableRef[];
variants: Variant[];
}
Each message declaration is represented by a Declaration
,
which connects the name
of a variable
with its expression value
.
The name
does not include the initial $
of the variable.
The name
of an InputDeclaration
MUST be the same
as the name
in the VariableRef
of its VariableExpression
value
.
type Declaration = InputDeclaration | LocalDeclaration;
interface InputDeclaration {
type: "input";
name: string;
value: VariableExpression;
}
interface LocalDeclaration {
type: "local";
name: string;
value: Expression;
}
In a SelectMessage
,
the keys
and value
of each variant are represented as an array of Variant
.
For the CatchallKey
, a string value
may be provided to retain an identifier.
This is always '*'
in MessageFormat 2 syntax, but may vary in other formats.
interface Variant {
keys: Array<Literal | CatchallKey>;
value: Pattern;
}
interface CatchallKey {
type: "*";
value?: string;
}
Each Pattern
contains a linear sequence of text and placeholders corresponding to potential output of a message.
Each element of the Pattern
MUST either be a non-empty string, an Expression
, or a Markup
object.
String values represent literal text.
String values include all processing of the underlying text values,
including escape sequence processing.
Expression
wraps each of the potential expression shapes.
Markup
wraps each of the potential markup shapes.
Implementations MUST NOT rely on the set of Expression
and
Markup
interfaces defined in this document being exhaustive.
Future versions of this specification might define additional
expressions or markup.
type Pattern = Array<string | Expression | Markup>;
type Expression =
| LiteralExpression
| VariableExpression
| FunctionExpression;
interface LiteralExpression {
type: "expression";
arg: Literal;
function?: FunctionRef;
attributes: Attributes;
}
interface VariableExpression {
type: "expression";
arg: VariableRef;
function?: FunctionRef;
attributes: Attributes;
}
interface FunctionExpression {
type: "expression";
arg?: never;
function: FunctionRef;
attributes: Attributes;
}
The Literal
and VariableRef
correspond to the the literal and variable syntax rules.
When they are used as the body
of an Expression
,
they represent expression values with no function.
Literal
represents all literal values, both quoted literal and unquoted literal.
The presence or absence of quotes is not preserved by the data model.
The value
of Literal
is the "cooked" value (i.e. escape sequences are processed).
In a VariableRef
, the name
does not include the initial $
of the variable.
interface Literal {
type: "literal";
value: string;
}
interface VariableRef {
type: "variable";
name: string;
}
A FunctionRef
represents a function.
The name
does not include the :
starting sigil.
Options
is a key-value mapping containing options,
and is used to represent the function and markup options.
interface FunctionRef {
type: "function";
name: string;
options: Options;
}
type Options = Map<string, Literal | VariableRef>;
A Markup
object has a kind
of either "open"
, "standalone"
, or "close"
,
each corresponding to open, standalone, and close markup.
The name
in these does not include the starting sigils #
and /
or the ending sigil /
.
The options
for markup use the same key-value mapping as FunctionRef
.
interface Markup {
type: "markup";
kind: "open" | "standalone" | "close";
name: string;
options: Options;
attributes: Attributes;
}
Attributes
is a key-value mapping
used to represent the expression and markup attributes.
Attributes with no value are represented by true
here.
type Attributes = Map<string, Literal | true>;
Implementations MAY extend this data model with additional interfaces,
as well as adding new fields to existing interfaces.
When encountering an unfamiliar field, an implementation MUST ignore it.
For example, an implementation could include a span
field on all interfaces
encoding the corresponding start and end positions in its source syntax.
In general, implementations MUST NOT extend the sets of values for any defined field or type when representing a valid message. However, when using this data model to represent an invalid message, an implementation MAY do so. This is intended to allow for the representation of "junk" or invalid content within messages.
MessageFormat 2.0 patterns are meant to allow a message to include any string value which users might normally wish to use in their environment. Programming languages and other environments vary in what characters are permitted to appear in a valid string. In many cases, certain types of characters, such as invisible control characters, require escaping by these host formats. In other cases, strings are not permitted to contain certain characters at all. Since messages are subject to the restrictions and limitations of their host environments, their serializations and resource formats, that might be sufficient to prevent most problems. However, MessageFormat itself does not supply such a restriction.
MessageFormat messages permit nearly all Unicode code points to appear in literals, including the text portions of a pattern. This means that it can be possible for a message to contain invisible characters (such as bidirectional controls, ASCII control characters in the range U+0000 to U+001F, or characters that might be interpreted as escapes or syntax in the host format) that abnormally affect the display of the message when viewed as source code, or in resource formats or translation tools, but do not generate errors from MessageFormat parsers or processing APIs.
Bidirectional text containing right-to-left characters (such as used for Arabic or Hebrew) also poses a potential source of confusion for users. Since MessageFormat 2.0's syntax makes use of keywords and symbols that are left-to-right or consist of neutral characters (including characters subject to mirroring under the Unicode Bidirectional Algorithm), it is possible to create messages that, when displayed in source code, or in resource formats or translation tools, have a misleading appearance or are difficult to parse visually.
For more information, see [UTS#55] Unicode Source Code Handling.
MessageFormat 2.0 implementations might allow end-users to install selectors, functions, or markup from third-party sources. Such functionality can be a vector for various exploits, including buffer overflow, code injection, user tracking, fingerprinting, and other types of bad behavior. Any installed code needs to be appropriately sandboxed. In addition, end-users need to be aware of the risks involved.
Special thanks to the following people for their contributions to making MessageFormat v2. The following people contributed to our github repo and are listed in order by contribution size:
Addison Phillips, Eemeli Aro, Romulo Cintra, Stanisław Małolepszy, Tim Chevalier, Elango Cheran, Richard Gibson, Mihai Niță, Mark Davis, Steven R. Loomis, Shane F. Carr, Matt Radbourne, Caleb Maclennan, David Filip, Daniel Minor, Christopher Dieringer, Bruno Haible, Danny Gleckler, George Rhoten, Ujjwal Sharma, Daniel Ehrenberg, Markus Scherer, Zibi Braniecki, Lionel Rowe, Luca Casonato, and Rafael Xavier de Souza.
Addison Phillips was chair of the working group from January 2023. Prior to 2023, the group was governed by a chair group, consisting of Romulo Cintra, Elango Cheran, Mihai Niță, David Filip, Nicolas Bouvrette, Stanisław Małolepszy, Rafael Xavier de Souza, Addison Phillips, and Daniel Minor. Romulo Cintra chaired the chair group.
© 2024–2024 Unicode, Inc. This publication is protected by copyright, and permission must be obtained from Unicode, Inc. prior to any reproduction, modification, or other use not permitted by the Terms of Use. Specifically, you may make copies of this publication and may annotate and translate it solely for personal or internal business purposes and not for public distribution, provided that any such permitted copies and modifications fully reproduce all copyright and other legal notices contained in the original. You may not make copies of or modifications to this publication for public distribution, or incorporate it in whole or in part into any product or publication without the express written permission of Unicode.
Use of all Unicode Products, including this publication, is governed by the Unicode Terms of Use. The authors, contributors, and publishers have taken care in the preparation of this publication, but make no express or implied representation or warranty of any kind and assume no responsibility or liability for errors or omissions or for consequential or incidental damages that may arise therefrom. This publication is provided “AS-IS” without charge as a convenience to users.
Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the United States and other countries.