1 of 20

Intl.MessageFormat

I have some questions.

Eemeli Aro, Mozilla / OpenJS Foundation

2 of 20

interface MessageFormatOptions {

bidiIsolation?: 'default' | 'none';

dir?: 'ltr' | 'rtl' | 'auto';

functions?:� { [key: string]: MessageFunction };

localeMatcher?: 'best fit' | 'lookup';

}

��type MessageFunction = (

source: string,

locales: string[],

options: { [key: string]: unknown },

input?: unknown

) => MessageValue;

class Intl.MessageFormat {

constructor(

source: MessageData | string,

locales?: string | string[],

options?: MessageFormatOptions

);

format(

values?: { [key: string]: unknown },

onError?: (error: Error) => void

): string;

formatToParts(

values?: { [key: string]: unknown },

onError?: (error: Error) => void

): MessagePart[];

resolvedOptions():

ResolvedMessageFormatOptions;

}

3 of 20

const msg = 'Total price: {$price :number style=currency currency=EUR}';

const mf = new Intl.MessageFormat(msg, 'en');

mf.format({ price: 42 }); // 'Total price: €42.00'

mf.formatToParts({ price: 42 });

/* [

{ type: 'literal', value: 'Total price: ' },

{ type: 'number', source: '$price',

parts: [

{ type: 'currency', value: '€' }, { type: 'integer', value: '42' },

{ type: 'decimal', value: '.' }, { type: 'fraction', value: '00' }

] }

] */

4 of 20

Developments since the 2023.09 update presentation

  • The Unicode MessageFormat 2 (aka “MF2”) specification has reached a feature freeze for its “tech preview” release in CLDR/LDML 45.
  • Syntax and semantics are almost completely described by formal spec language. Missing pieces:
    • Bidirectional isolation.
    • Definitions for built-in functions beyond :number and :string.
  • Polyfill updated to match current spec.
  • No JS API changes.

5 of 20

Q1: When can we consider a MessageFormat 2 parser in JS?

Option A

When the MF2 specification is finalized in Unicode CLDR, and has a sufficient stability guarantee.

Option B

When the stability of the MF2 specification has been proven by years of experience with it.

6 of 20

Timeline

2013.04 First strawman proposal presented & discussed

2016.07 Discussion re-started under tc39/ecma402#92

2019.07 MessageFormat WG first organised under TC39 TG2

2020.01 WG reorganised as a subgroup of the Unicode CLDR-TC

2022.03 Intl.MessageFormat accepted for Stage 1

2022.11 Message resources spun off as a separate proposal

2023.09 Stage 1 update, presenting current API

2023.10 Follow-up incubator call confirming no API changes

7 of 20

If we build it, they will come.

  • A more capable format is needed than the existing ICU MessageFormat, fbt, L20n/Fluent, printf, gettext, …
  • Localization on the web suffers from the lack of a good solution.
  • MF2 is that solution, built for Intl.MessageFormat.
  • MF2 is universal: It can represent any message in any other format.

8 of 20

If we build it, they will come.

“For some time I know thought about the next version of i18next [...] but with the current uncertainty what the decision here is - I better do nothing before picking the one that gets not adopted by browsers. [...] If there is a chance that one format gets the defacto standard for web [...] my bet is the tooling will improve. Currently, as a vendor, you just have too many formats for web you need to support (and the web is only one piece of what you need to support)”

– Jan Mühlemann, i18next maintainer, 2019

9 of 20

Motivation, as presented for Stage 1 in 2022.03

  • Make it easier to localize the web, increasing the openness and accessibility of the web for speakers of all languages.
  • Introduce a native parser and message formatter for MessageFormat 2, a spec currently being developed under the Unicode Consortium.
  • Build on and enhance existing workflows and systems, providing a shared message formatting runtime for all users.

10 of 20

The MessageFormat 2 Stability Policy

  • Updates to this specification will not change the syntactical meaning, the runtime output, or other behaviour of valid messages written for earlier versions of this specification that only use functions and expression attributes defined in this specification.
  • Later specification versions MAY make previously invalid messages valid.

11 of 20

Q1: When can we consider a MessageFormat 2 parser in JS?

Option A

When the MF2 specification is finalized in Unicode CLDR, and has a sufficient stability guarantee.

Option B

When the stability of the MF2 specification has been proven by years of experience with it.

12 of 20

Pause for discussion

13 of 20

What if we left out the MF2 syntax parser?

class Intl.MessageFormat {

constructor(

source: MessageData,

locales?: string | string[],

options?: MessageFormatOptions

);

...

}

class Intl.MessageFormat {

constructor(

source: MessageData | string,

locales?: string | string[],

options?: MessageFormatOptions

);

...

}

14 of 20

Messages as Data

ICU MessageFormat: 'Total price: {price, number, currency}' →

{ type: 'message',

pattern: [

'Total price: ',

{ type: 'expression',

arg: { type: 'variable', name: 'price' },

annotation: { type: 'function', name: 'number', options: [

{ name: 'style', value: { type: 'literal', value: 'currency' } }

] } }

] }

15 of 20

Messages as Data

Fluent: 'Total price: { NUMBER($price, style: "currency") }' →

{ type: 'message',

pattern: [

'Total price: ',

{ type: 'expression',

arg: { type: 'variable', name: 'price' },

annotation: { type: 'function', name: 'number', options: [

{ name: 'style', value: { type: 'literal', value: 'currency' } }

] } }

] }

16 of 20

Messages as Data

MessageFormat 2: 'Total price: {$price :number style=currency}' →

{ type: 'message',

pattern: [

'Total price: ',

{ type: 'expression',

arg: { type: 'variable', name: 'price' },

annotation: { type: 'function', name: 'number', options: [

{ name: 'style', value: { type: 'literal', value: 'currency' } }

] } }

] }

17 of 20

type Message =

| PatternMessage

| SelectMessage

interface PatternMessage {

type: "message"

declarations?: Declaration[]

pattern: Pattern

}

interface SelectMessage {

type: "select"

declarations?: Declaration[]

selectors: Expression[]

variants: Variant[]

}

interface Declaration {

type: "input" | "local"

name: string

value: Expression

}

interface Variant {

keys: (Literal | CatchallKey)[]

value: Pattern

}

interface CatchallKey {

type: "*"

}

18 of 20

interface Literal {

type: "literal"

value: string

}

interface VariableRef {

type: "variable"

name: string

}

interface FunctionAnnotation {

type: "function"

name: string

options?: Option[]

}

interface Option {

name: string

value: Literal | VariableRef

}

type Pattern =

(string | Expression | Markup)[]

interface Expression {

type: "expression"

arg?: Literal | VariableRef

annotation?: FunctionAnnotation

}

interface Markup {

type: "markup"

kind: "open" | "standalone" | "close"

name: string

options?: Option[]

}

19 of 20

Q2: Without syntax, is what remains motivated enough� for inclusion in the language?

The proposal includes answers to the following questions:

  • How to express a message in source form?
  • How to work with a message as a parsed data structure?
  • How to format a message?
  • How to define a custom message function?

20 of 20

Links

npm i messageformat@next