Intl.MessageFormat
TC39 Stage 1 Update
Eemeli Aro, Mozilla / OpenJS Foundation
Outline
interface MessageFormatOptions {
bidiIsolation?:� 'compatibility' | '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?: Record<string, unknown>,
onError?: (error: Error) => void
): string;
formatToParts(
values?: Record<string, unknown>,
onError?: (error: Error) => void
): MessagePart[];
resolvedOptions():
ResolvedMessageFormatOptions;
}
Changes since first presentation in March 2022
interface MessageFormatOptions {
bidiIsolation?:� 'compatibility' | '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?: Record<string, unknown>,
onError?: (error: Error) => void
): string;
formatToParts(
values?: Record<string, unknown>,
onError?: (error: Error) => void
): MessagePart[];
resolvedOptions():
ResolvedMessageFormatOptions;
}
interface MessageFormatOptions {
bidiIsolation?:� 'compatibility' | '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?: Record<string, unknown>,
onError?: (error: Error) => void
): string;
formatToParts(
values?: Record<string, unknown>,
onError?: (error: Error) => void
): MessagePart[];
resolvedOptions():
ResolvedMessageFormatOptions;
}
interface MessageFormatOptions {
bidiIsolation?:� 'compatibility' | '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?: Record<string, unknown>,
onError?: (error: Error) => void
): string;
formatToParts(
values?: Record<string, unknown>,
onError?: (error: Error) => void
): MessagePart[];
resolvedOptions():
ResolvedMessageFormatOptions;
}
interface MessageFormatOptions {
bidiIsolation?:� 'compatibility' | '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?: Record<string, unknown>,
onError?: (error: Error) => void
): string;
formatToParts(
values?: Record<string, unknown>,
onError?: (error: Error) => void
): MessagePart[];
resolvedOptions():
ResolvedMessageFormatOptions;
}
interface MessageFormatOptions {
bidiIsolation?:� 'compatibility' | '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?: Record<string, unknown>,
onError?: (error: Error) => void
): string;
formatToParts(
values?: Record<string, unknown>,
onError?: (error: Error) => void
): MessagePart[];
resolvedOptions():
ResolvedMessageFormatOptions;
}
const src = '{Hello {$place}!}';
const mf = new Intl.MessageFormat(src, 'en');
mf.format({ place: 'world' }); // 'Hello world!'
mf.formatToParts({ place: 'world' });
/* [
{ type: 'literal', value: 'Hello ' },
{ type: 'string', source: '$place', value: 'world' },
{ type: 'literal', value: '!' }
] */
const src = `
input {$count :number}
match {$count}
when 0 {You have no new notifications}
when one {You have {$count} new notification}
when * {You have {$count} new notifications}`;
const mf = new Intl.MessageFormat(src, 'en');
mf.format({ count: 0 }); // 'You have no new notifications'
mf.format({ count: 1 }); // 'You have 1 new notification'
How to format compound values?
How to format compound values?
const src = '{Your total is {$price :number style=currency}}';
const mf = new Intl.MessageFormat(src, 'en');
const price = new Number(42);
price.options = { currency: 'EUR' };
mf.format({ price }) // 'Your total is €42.00'
How to format to parts?
How to format to parts?
mf.formatToParts({ price }) /* [
{ type: 'literal', value: 'Your total is ' },
{ type: 'number', source: '$price', parts: [
{ type: 'currency', value: '€' },
{ type: 'integer', value: '42' },
{ type: 'decimal', value: '.' },
{ type: 'fraction', value: '00' } ] }
] */
How to format to parts?
const src = '{Have a {cow.png :image alt=Cow}}';
const mf = new Intl.MessageFormat(src, 'en',
{ functions: { image: … } });
mf.formatToParts() /* [
{ type: 'literal', value: 'Have a ' },
{ type: 'image', source: 'cow.png',
value: <img src="cow.png" alt="Cow"> }
] */
How to write custom formatters?
How to write custom formatters?
interface MessageValue {
type: string;
locale: string;
dir: 'ltr' | 'rtl' | 'auto';
source: string;
options?: { [key: string]: unknown };
selectKeys?: (keys: string[]) => string[];
toParts?: () => MessagePart[];
toString?: () => string;
valueOf?: () => unknown;
}
type MessageFunction = (
source: string,
locales: string[],
options: { [key: string]: unknown },
input?: unknown
) => MessageValue;
How to write custom formatters?
interface MessageValue {
type: string;
locale: string;
dir: 'ltr' | 'rtl' | 'auto';
source: string;
options?: { [key: string]: unknown };
selectKeys?: (keys: string[]) => string[];
toParts?: () => MessagePart[];
toString?: () => string;
valueOf?: () => unknown;
}
Selector
Format to parts
Format to string
How to write custom formatters?
interface MessageValue {
type: string;
locale: string;
dir: 'ltr' | 'rtl' | 'auto';
source: string;
options?: { [key: string]: unknown };
selectKeys?: (keys: string[]) => string[];
toParts?: () => MessagePart[];
toString?: () => string;
valueOf?: () => unknown;
}
To use as input/option value, MessageValue needs to match the “compound value” interface.
Incubator call TBD
Help me write the spec text for this?
Links
Why is this so difficult?
* Including, but not limited to: formatter options, cardinal & ordinal plurals, custom formatters, custom selectors, markup elements, composite values, formatting to parts.
Why is this so difficult?