Functions
When you write an annotation in your MessageFormat message, then underneath the hood, the formatter calls a function. These could either be built-in functions that aim to assist you in performing common i18n operations like formatting common data types or custom functions that are registered by the user in the function registry. The syntax for annotations is as follows, with the operand followed by the function name and finally the options.
Today is {$date :datetime weekday=long}.
{ "date": "2024-06-06" }
The annotation {$date :datetime weekday=long}
can be read as: "Take the value
of the variable $date
and format it as a date, using the long
format for the
weekday part of the date." If :datetime
is passed something that can't be
parsed as a date, a formatting error occurs:
Today is {$date :datetime weekday=long}.
{ "date": "foo" }
This error message says that the string "foo" is not a valid date.
The set of meaningful options and their values is specific to each function, as is the rule for determining which operands are valid.
There are two types of functions: formatting functions (formatters) and selector
functions. The same function can be both a formatter and selector: for example,
the built-in :number
function.
Formatting functions Jump to heading
A formatting function takes an operand and produces a value from it. This value
can either be formatted immediately, as when using the result in a pattern (like
in the :datetime
example above), or it can be named and referred to in other
expressions. For example:
.local $date1 = {$date :datetime weekday=long}
{{Today is {$date1}.}}
{ "date": "2024-06-06" }
It would be equivalent to write:
.input {$date :datetime weekday=long}
{{Today is {$date}.}}
{ "date": "2024-06-06" }
Selector functions Jump to heading
Selector functions are used to customize the behavior of a matcher. The
:string
and :number
selector functions are built in, but you can also write
your own custom selector functions. To understand how selectors work, look at
this example:
.local $val = {foo :string}
.match $val
foo {{Foo}}
bar {{Bar}}
* {{No match}}
In the annotation {foo :string}
, the operand is the literal foo
. Underneath
the hood, the string
function is passed in both its operand, and a list of the
keys for all the variants: in this case, ["foo", "bar"]
. It returns a list of
keys sorted by preference. string
does exact matching, so in this case it
would only return ["foo"]
. The key *
is special and is used when the
selector doesn't return any matching keys.
Built-in functions Jump to heading
The number
and integer
functions Jump to heading
The number
and integer
functions both format and select on numbers,
including literals (strings) that can be parsed as numbers. The integer
function treats its operand as an integer, for example:
{3.14 :integer}
The formatted result when annotating 3.14
with :integer
is 3
. Otherwise,
number
and integer
behave similarly.
Number formatting Jump to heading
The number
function has many different options to control formatting, some of
which also work for integer
.
The following example shows how some of the options work.
.local $n1 = {$num :number notation=scientific minimumSignificantDigits=5}
.local $n2 = {$num :number style=percent}
.local $n3 = {$num :number minimumIntegerDigits=4 minimumFractionDigits=4}
.local $n4 = {$num :number maximumSignificantDigits=6}
{{The number in scientific notation is {$n1}. As a percentage, it's {$n2}.
Formatted with at least four integer digits and four fractional digits, it's
{$n3}. Formatted with at most six significant digits, it's {$n4}.}}
{ "num": 3.1415927 }
For the most part, these options can be combined arbitrarily.
Number selection Jump to heading
When used as a selector in a .match
, the default behavior of number
is to
find the CLDR plural category of the operand, and compare the variants against
that category. This behavior can be customized using options.
Revisiting the Czech example from the quick start guide:
.input {$numDays :number}
.match $numDays
one {{{$numDays} den}}
few {{{$numDays} dny}}
many {{{$numDays} dne}}
* {{{$numDays} dní}}
{ "numDays": 2 }
Notice that the value of the operand $numDays
is 2, but it matches with the
key few
. The number
function uses CLDR data for the cs-CZ
locale to
determine that the plural category of 2 is "few".
However, number
can also do exact matching:
.input {$numDays :number select=exact}
.match $numDays
one {{{$numDays} den}}
few {{{$numDays} dny}}
many {{{$numDays} dne}}
* {{{$numDays} dní}}
{ "numDays": 2 }
With this option, since "2" is not literally equal to any of the other keys, the
*
variant matches.
Options Jump to heading
The following table includes all the options that can be passed to :number
,
according to
the specification.
A description of each option follows.
Option name | Allowed values | Default value | Used with :integer ? |
---|---|---|---|
select |
plural , ordinal , exact |
plural |
yes |
compactDisplay |
short , long |
short |
no |
notation |
standard , scientific , engineering , compact |
standard |
no |
numberingSystem |
a valid Unicode Number System Identifier | Locale-dependent | yes |
signDisplay |
auto , always , exceptZero , negative , never |
auto |
yes |
style |
decimal , percent |
decimal |
yes |
useGrouping |
auto , always , never , min2 |
auto |
yes |
minimumIntegerDigits |
Digit size option (see below) | 1 |
yes |
minimumFractionDigits |
Digit size option (see below) | No default | no |
maximumFractionDigits |
Digit size option (see below) | No default | no |
minimumSignificantDigits |
Digit size option (see below) | No default | no |
maximumSignificantDigits |
Digit size option (see below) | No defualt | yes |
All the same options can be passed to :integer
, except where noted in the rightmost
column of the table.
Many of the options are similar to the JavaScript Intl.NumberFormat
API.
The select
option
The select
option controls how selectors are matched against keys
when an expression annotated with :number
is used as a selector.
When plural
(the default value) is provided as the value of this option,
matching uses CLDR plural rules for cardinal numbers.
When ordinal
is provided, values are matched based on CLDR plural rules
for ordinal numbers.
When exact
is provided, values are matched based on their exact contents.
Compare the following three examples:
.input {$num :number}
.match $num
one {{one!}}
* {{many!}}
{ "num": 2 }
.input {$num :number select=ordinal}
.match $num
one {{You are {$num}st}}
two {{You are {$num}nd}}
few {{You are {$num}rd}}
* {{You are {$num}th}}
{ "num": 2 }
.input {$num :number select=exact}
.match $num
1 {{one!}}
2 {{two!}}
3 {{three!}}
* {{other!}}
{ "num": 2 }
The compactDisplay
option
The compactDisplay
option only has meaning when combined with the notation
option.
The meaning is locale-dependent, but the following examples show how it works
for the en-US
locale:
short: {$num :number compactDisplay=short notation=compact}
long: {$num :number compactDisplay=long notation=compact}
{ "num": 10001 }
With this option, numbers are rounded to the nearest thousand,
million, or billion, which is then either represented as
K
/M
/B
or spelled out.
The notation
option
The notation
option has the following effects, depending on its value:
standard
: Plain number formatting.scientific
: Order of magnitude.engineering
: Return the exponent of ten when divisible by three.compact
Number is abbreviated as explained previously.
standard: {$num :number notation=standard}
scientific: {$num :number notation=scientific}
engineering: {$num :number notation=engineering}
compact: {$num :number notation=compact}
{ "num": 3001 }
The numberingSystem
option
The numberingSystem
option specifies which numbering system to use
for formatting the number. The list of possible values
is specified by Unicode.
{$num :number numberingSystem=roman} or
{$num :number numberingSystem=arab} or
{$num :number numberingSystem=thai}
{ "num": 3001 }
The signDisplay
option
The signDisplay
option specifies how to format the sign of a number.
The possible values are:
auto
: Display the sign for negative numbers only.always
: Always display the sign.exceptZero
: Display the sign for any non-zero number.negative
: Display the sign for negative numbers, excluding negative zero.never
: Never display the sign.
The following example shows how negative 0 is processed:
auto: {$num :number signDisplay=auto}
always: {$num :number signDisplay=always}
exceptZero: {$num :number signDisplay=exceptZero}
negative: {$num :number signDisplay=negative}
never: {$num :number signDisplay=never}
{ "num": -0 }
The style
option
The style
option specifies whether to format a number as a decimal
or as a percentage.
decimal: {$num :number style=decimal}
percent: {$num :number style=percent}
{ "num": 42 }
Passing percent
causes the formatted number to be the input
number, multiplied by 100.
The useGrouping
option
The useGrouping
option controls whether to use grouping
separators, such as thousands separators in the en-US
locale.
The possible values are:
auto
: Display grouping separators based on locale.always
: Always display grouping separators.never
: Never display grouping separators.min2
: Display grouping separators only when there are at least 2 digits in a group.
(Note: a bug in the messageformat package prevents inclusion of an interactive example.)
Digit size options
The five remaining options all take a "digit size" option, which is a small integer value greater than or equal to zero, or a string that can be parsed as one.
The options are:
minimumIntegerDigits
: The minimum number of integer digits to use in formatting the number. The number will be left-padded with zeros as much as necessary.minimumFractionDigits
: The minimum number of fraction digits to use in formatting the number.maximumFractionDigits
: The maximum number of fraction digits to use in formatting the number.minimumSignificantDigits
: The minimum number of significant digits to use.maximumSignificantDigits
: The maximum number of significant digits to use.
The following example combines all five options and shows how two different numbers are formatted:
first: {$num1 :number minimumIntegerDigits=2 minimumFractionDigits=3 maximumFractionDigits=5 minimumSignificantDigits=1 maximumSignificantDigits=5}
second: {$num2 :number minimumIntegerDigits=2 minimumFractionDigits=3 maximumFractionDigits=5 minimumSignificantDigits=1 maximumSignificantDigits=5}
{ "num1": 3.1415927,
"num2": 42.01 }
The string
function Jump to heading
The string
function is both a selector and a formatter function. It works on
strings and on any operands that can be converted to a string.
String selection Jump to heading
When used as a selector, string
matches strings exactly.
.input {$operand :string}
.match $operand
1 {{Number 1}}
one {{String "one"}}
* {{Something else}}
{ "operand": "1" }
This may not seem very useful, but anything used as a selector in a .match
must have an annotation, so string
can be used to express your intention to
match on a value exactly.
String formatting Jump to heading
When used as a formatter, string
returns a string representation of its
operand.
{$operand :string}
{ "operand": { "name": "days", "value": 5 } }
The exact details of how to stringify an operand are implementation-dependent,
as you can see in this example (in JavaScript, the default stringification
behavior of objects is to print them as [object Object]
).
Options Jump to heading
The string
function has no options.
The date
, time
, and datetime
functions Jump to heading
The date
, time
, and datetime
functions are formatting functions that
format dates. date
is a version of datetime
that ignores the time component
of a date, while time
is a version of datetime
that ignores the date
component.
Today is {$date :datetime weekday=long}.
The time is {$date :time style=full}.
The date is {$date :date style=short}.
{ "date": "2006-01-02T15:04:06" }
If supplied as a string, the operand has to be in ISO 8601 format, followed by an optional timezone offset. Some implementations may also accept other date types, such as an integer timestamp:
Today is {$date :datetime weekday=long}.
The time is {$date :time style=full}.
The date is {$date :date style=short}.
{ "date": 1722746637000 }
datetime
has two kinds of options: style options and field options. In the
above example, weekday
is an example of a field option. There are also field
options for each part of a date/time. Their meanings are
implementation-dependent. The names of all the options can be found in the
spec.
The style options are either dateStyle
or timeStyle
, whose values can be
full
, long
, medium
, or short
. The meaning of these values is also
implementation-dependent.
Today is {$date :datetime dateStyle=full timeStyle=long}.
Or: today is {$date :datetime dateStyle=long timeStyle=short}.
Or: today is {$date :datetime dateStyle=medium timeStyle=full}.
Or: today is {$date :datetime dateStyle=short}.
Or: today is {$date :datetime timeStyle=medium}.
{ "date": "2006-01-02T15:04:06" }
date
and time
both only have one option: style
. The values are the same as
for :datetime
.
Today is {$date :date style=full}.
Or: today is {$date :date style=short}.
The time is {$date :time style=medium}.
Or: the time is {$date :time style=long}.
{ "date": "2006-01-02T15:04:06" }
Options Jump to heading
Options for :datetime
:
Option name | Allowed values | Default value |
---|---|---|
dateStyle |
full , long , medium , short |
medium |
timeStyle |
full , long , medium , short |
short |
weekday |
long , short , narrow |
none |
era |
long , short , narrow |
none |
year |
numeric , 2-digit |
none |
month |
numeric , 2-digit , long , short , narrow |
none |
day |
numeric , 2-digit |
none |
hour |
numeric , 2-digit |
none |
minute |
numeric , 2-digit |
none |
second |
numeric , 2-digit |
none |
fractionalSecondDigits |
1 , 2 , 3 |
none |
hourCycle |
h11 , h12 , h23 , h24 |
locale-specific |
timeZoneName |
long , short , shortOffset , longOffset , shortGeneric , longGeneric |
none |
All the options other than dateStyle
and timeStyle
are called "field options".
Field options do not have defaults, as they're intended to override locale-
and implementation-dependent default values.
Field options cannot be specified if either dateStyle
or timeStyle
(or both)
is specified.
Options for :date
and :time
The :date
and :time
functions only have one option: style
.
Like the dateStyle
and timeStyle
options to :datetime
, its values
can be full
, long
, medium
, or short
. For :date
, the default
is medium, and for :time
, the default is :short
.
The names of these options are largely derived from the
JavaScript Intl.DateTimeFormat
API
and they can be expected to have the same meaning as in Intl.DateTimeFormat
.
Style options
The meanings of the style options (dateStyle
/timeStyle
/style
) are
locale-dependent. The following interactive example can be used to see
how these options work.
Short: {$date :datetime dateStyle=short timeStyle=short}.
Medium: {$date :datetime dateStyle=medium timeStyle=medium}.
Long: {$date :datetime dateStyle=long timeStyle=long}.
Full: {$date :datetime dateStyle=full timeStyle=full}.
{ "date": "2024-10-25T14:05:06" }
Field options
The meanings of the weekday
, era
, year
, month
, day
, hour
, minute
, second
,
fractionalSecondDigits
, and timeZoneName
options depend on
the available representations for dates and times in the current locale.
The hourCycle
option specifies the timekeeping convention
to use in formatting the time:
h12
: Hour system using 1-12 (12-hour clock with midnight at 12:00 AM).h23
: Hour system using 0-23 (24-hour clock with midnight at 0:00).h11
: Hour system using 0-11 (12-hour clock with midnight at 0:00 AM).h24
: Hour system using 0-24 (24-hour click with midnight at 24:00).
When field options are used, only the explicitly-specified fields are present in the output. In the following example, the formatted date only contains the year and weekday.
{$date :datetime weekday=long year=|2-digit|}.
{ "date": "2024-10-25T14:05:06" }
It's up to the developer to use a combination of field options that makes sense for the desired locale.
Custom functions Jump to heading
Here are some examples of what you can do with custom functions. The examples are not interactive, because the playground doesn't currently support supplying a custom function registry. The details on how to write and register these functions are implementation-dependent.
Examples: custom formatters Jump to heading
Text transformations Jump to heading
A custom formatter function could stringify its argument and apply a text transformation to it, like converting it to uppercase:
Check out {MessageFormat :uppercase}.
Result: Check out MESSAGEFORMAT
List formatting Jump to heading
In an implementation with a list type (such as the JavaScript implementation), a custom formatter function could format lists:
I know how to program in {$languages :list type=AND}
Parameters: { "languages": ["JavaScript", "C++", "Python"] }
Result: I know how to program in JavaScript, C++, and Python
Examples: custom selectors Jump to heading
Selecting a field Jump to heading
In an implementation with an object type (such as the JavaScript implementation), custom selectors could extract a field from an operand and match on that:
.local $pronoun = {$person :pronoun}
.match $pronoun
he {{{$person :name} won his game}}
she {{{$person :name} won her game}}
they {{{$person :name} won their game}}
* {{{$person :name} won the game}}
Parameters: { "person": { "name": "Alice", "pronoun": "she", "age": 42 } }
Result: Alice won her game
In this example, pronoun
is a custom selector that extracts the pronoun
field from its operand if supplied an object, and matches it against keys name
is a custom formatter that extracts and formats a name
field from its operand.
Range matching Jump to heading
.local $range = {$name :range}
.match $range
|A-J| {{{$name} is in the first group}}
|J-P| {{{$name} is in the second group}}
|Q-Z| {{{$name} is in the third group}}
* {{Should be unreachable}}
Parameters: {"name": "Kim"}
Result: Kim is in the second group
In this example, range
is a custom selector that compares the first letter of
its string operand against alphabetic ranges. It could be extended to support
things like time and date ranges.