Custom Formatters
Custom formatters are an extension of the ICU MessageFormat spec that’s specific to this library.
In MessageFormat source, a formatter function is called with the syntax
{var, name, arg}
where var
is a variable name, name
is the formatter name, and arg
is an optional string argument.
As a further extension of the spec, the arg
value itself may contain any MessageFormat syntax, such as references to other variables.
In JavaScript, a custom formatter may be defined as a function that is called with three arguments:
- The
value
of the variable; this can be of any user-defined type - The current
locale
code as a string - The
arg
value, ornull
if it is not set
To define and add your own formatter, use the customFormatters
option of the MessageFormat constructor:
import MessageFormat from '@messageformat/core';
const mf = new MessageFormat('en-GB', {
customFormatters: {
locale: (_, lc) => lc,
upcase: v => v.toUpperCase()
}
});
const msg = mf.compile('This is {VAR, upcase} in {_, locale}.');
msg({ VAR: 'big' }); // 'This is BIG in en-GB.'
For compiled modules (e.g. when using with the Webpack loader or Rollup plugin), formatters may also be defined as an object with the following properties:
formatter: CustomFormatter
– The formatter function, for live useid: string
andmodule
should both be defined if either is, to provide for importing the formatter asid
frommodule
in the compiled code. This is intended to allow for third-party formatters to be more easily used, and to work around the limitations of stringified functions needing to be completely independent of their surrounding context.module
may either be astring
, or a function(locale: string) => string
if the import path has a locale dependency.arg
defines shape in which any argument object will get passed to the formatter, using one of the following values:'string'
(default) will pass any argument as a trimmedstring
.'raw'
provides anArray
of values, which will bestring
for literals, but may include e.g. runtime variable values.'options'
requires its value to be literal, but will then parse that as aRecord<string, string | number | boolean | null>
shape, using a very simple parser that first splits the string on each,
and for each pair considers trimmed content before the first:
as the key and the rest as the (also trimmed) value. If the value matchestrue
,false
,null
, or the JSON representation of a number, it will be parsed as the corresponding type. No form of escaping is provided for.
Putting all that together, if we had this module available:
// '@messageformat/upcase'
export function formatter(value, locale, override) {
const lc = (override && override.locale) || locale;
return String(value).toLocaleUpperCase(lc);
}
export const upcase = {
formatter,
arg: 'options',
id: 'formatter',
module: '@messageformat/upcase'
};
We can use it like this:
import MessageFormat from '@messageformat/core';
import compileModule from '@messageformat/core/compile-module';
import { upcase } from '@messageformat/upcase';
const mf = new MessageFormat('en', { customFormatters: { upcase } });
const msgSet = {
here: 'This is {x, upcase} here',
fin: 'This is {x, upcase, locale: fi} in Finnish'
};
compileModule(mf, msgSet);
Resulting in this compiled module:
import { formatter as upcase } from '@messageformat/upcase';
export default {
here: d => 'This is ' + upcase(d.x, 'en') + ' here',
fin: d => 'This is ' + upcase(d.x, 'en', { locale: 'fi' }) + ' in Finnish'
};
In the prior example, the formatting module upcase
had to handle every locale under compilation. Formatting modules may also be authored on a per-locale basis. This may be useful in various scenarios, for example when bundling or compiling code along the locale dimension.