Skip to content

Commit 9a0f3de

Browse files
fix(mf2): Always use locale set in function context
1 parent bb16ec8 commit 9a0f3de

File tree

5 files changed

+30
-68
lines changed

5 files changed

+30
-68
lines changed

mf2/messageformat/src/functions/datetime.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@ import { MessageResolutionError } from '../errors.js';
33
import type { MessageExpressionPart } from '../formatted-parts.js';
44
import type { MessageValue } from '../message-value.js';
55
import type { MessageFunctionContext } from '../resolve/function-context.js';
6-
import {
7-
asBoolean,
8-
asPositiveInteger,
9-
asString,
10-
mergeLocales
11-
} from './utils.js';
6+
import { asBoolean, asPositiveInteger, asString } from './utils.js';
127

138
/** @beta */
149
export interface MessageDateTime extends MessageValue {
@@ -51,7 +46,7 @@ export const datetime = (
5146
options: Record<string, unknown>,
5247
input?: unknown
5348
): MessageDateTime =>
54-
dateTimeImplementation(ctx, options, input, res => {
49+
dateTimeImplementation(ctx, input, res => {
5550
for (const [name, value] of Object.entries(options)) {
5651
if (value === undefined) continue;
5752
try {
@@ -91,7 +86,7 @@ export const date = (
9186
options: Record<string, unknown>,
9287
input?: unknown
9388
): MessageDateTime =>
94-
dateTimeImplementation(ctx, options, input, res => {
89+
dateTimeImplementation(ctx, input, res => {
9590
const ds = options.style ?? res.dateStyle ?? 'medium';
9691
for (const name of Object.keys(res)) {
9792
if (!localeOptions.includes(name)) delete res[name];
@@ -115,7 +110,7 @@ export const time = (
115110
options: Record<string, unknown>,
116111
input?: unknown
117112
): MessageDateTime =>
118-
dateTimeImplementation(ctx, options, input, res => {
113+
dateTimeImplementation(ctx, input, res => {
119114
const ts = options.style ?? res.timeStyle ?? 'short';
120115
for (const name of Object.keys(res)) {
121116
if (!localeOptions.includes(name)) delete res[name];
@@ -130,12 +125,10 @@ export const time = (
130125

131126
function dateTimeImplementation(
132127
ctx: MessageFunctionContext,
133-
options: Record<string, unknown>,
134128
input: unknown,
135129
parseOptions: (res: Record<string, unknown>) => void
136130
): MessageDateTime {
137131
const { localeMatcher, locales, source } = ctx;
138-
const lc = mergeLocales(locales, input, options);
139132
const opt: Intl.DateTimeFormatOptions = { localeMatcher };
140133
if (input && typeof input === 'object') {
141134
if (input && 'options' in input) Object.assign(opt, input.options);
@@ -174,13 +167,16 @@ function dateTimeImplementation(
174167
return dir;
175168
},
176169
get locale() {
177-
return (locale ??= Intl.DateTimeFormat.supportedLocalesOf(lc, opt)[0]);
170+
return (locale ??= Intl.DateTimeFormat.supportedLocalesOf(
171+
locales,
172+
opt
173+
)[0]);
178174
},
179175
get options() {
180176
return { ...opt };
181177
},
182178
toParts() {
183-
dtf ??= new Intl.DateTimeFormat(lc, opt);
179+
dtf ??= new Intl.DateTimeFormat(locales, opt);
184180
const parts = dtf.formatToParts(date);
185181
locale ??= dtf.resolvedOptions().locale;
186182
dir ??= getLocaleDir(locale);
@@ -189,7 +185,7 @@ function dateTimeImplementation(
189185
: [{ type: 'datetime', source, locale, parts }];
190186
},
191187
toString() {
192-
dtf ??= new Intl.DateTimeFormat(lc, opt);
188+
dtf ??= new Intl.DateTimeFormat(locales, opt);
193189
str ??= dtf.format(date);
194190
return str;
195191
},

mf2/messageformat/src/functions/number.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { MessageResolutionError } from '../errors.js';
33
import type { MessageExpressionPart } from '../formatted-parts.js';
44
import type { MessageValue } from '../message-value.js';
55
import type { MessageFunctionContext } from '../resolve/function-context.js';
6-
import { asPositiveInteger, asString, mergeLocales } from './utils.js';
6+
import { asPositiveInteger, asString } from './utils.js';
77

88
/** @beta */
99
export interface MessageNumber extends MessageValue {
@@ -80,7 +80,7 @@ export function number(
8080
exprOpt: Record<string | symbol, unknown>,
8181
operand?: unknown
8282
): MessageNumber {
83-
const { source } = ctx;
83+
const { locales, source } = ctx;
8484
const options: Intl.NumberFormatOptions &
8585
Intl.PluralRulesOptions & { select?: 'exact' | 'cardinal' | 'ordinal' } = {
8686
localeMatcher: ctx.localeMatcher
@@ -126,7 +126,6 @@ export function number(
126126
? Math.round(value as number)
127127
: value;
128128

129-
const lc = mergeLocales(ctx.locales, operand, exprOpt);
130129
let locale: string | undefined;
131130
let dir = ctx.dir;
132131
let nf: Intl.NumberFormat | undefined;
@@ -140,7 +139,10 @@ export function number(
140139
return dir;
141140
},
142141
get locale() {
143-
return (locale ??= Intl.NumberFormat.supportedLocalesOf(lc, options)[0]);
142+
return (locale ??= Intl.NumberFormat.supportedLocalesOf(
143+
locales,
144+
options
145+
)[0]);
144146
},
145147
get options() {
146148
return { ...options };
@@ -153,11 +155,11 @@ export function number(
153155
? { ...options, select: undefined, type: options.select }
154156
: options;
155157
// Intl.PluralRules needs a number, not bigint
156-
cat ??= new Intl.PluralRules(lc, pluralOpt).select(Number(num));
158+
cat ??= new Intl.PluralRules(locales, pluralOpt).select(Number(num));
157159
return keys.has(cat) ? cat : null;
158160
},
159161
toParts() {
160-
nf ??= new Intl.NumberFormat(lc, options);
162+
nf ??= new Intl.NumberFormat(locales, options);
161163
const parts = nf.formatToParts(num);
162164
locale ??= nf.resolvedOptions().locale;
163165
dir ??= getLocaleDir(locale);
@@ -166,7 +168,7 @@ export function number(
166168
: [{ type: 'number', source, locale, parts }];
167169
},
168170
toString() {
169-
nf ??= new Intl.NumberFormat(lc, options);
171+
nf ??= new Intl.NumberFormat(locales, options);
170172
str ??= nf.format(num);
171173
return str;
172174
},

mf2/messageformat/src/functions/string.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { MessageExpressionPart } from '../formatted-parts.js';
22
import type { MessageValue } from '../message-value.js';
33
import type { MessageFunctionContext } from '../resolve/function-context.js';
4-
import { mergeLocales } from './utils.js';
54

65
/** @beta */
76
export interface MessageString extends MessageValue {
@@ -31,12 +30,12 @@ export interface MessageStringPart extends MessageExpressionPart {
3130
* @beta */
3231
export function string(
3332
ctx: Pick<MessageFunctionContext, 'dir' | 'locales' | 'source'>,
34-
options: Record<string, unknown>,
33+
_options: Record<string, unknown>,
3534
input?: unknown
3635
): MessageString {
3736
const str = input === undefined ? '' : String(input);
3837
const selStr = str.normalize();
39-
const [locale] = mergeLocales(ctx.locales, input, options);
38+
const locale = ctx.locales[0];
4039
return {
4140
type: 'string',
4241
source: ctx.source,

mf2/messageformat/src/functions/utils.ts

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function asBoolean(value: unknown): boolean {
2020

2121
/**
2222
* Utility function for custom functions.
23-
* Cast a value as a positive integer,
23+
* Cast a value as a non-negative integer,
2424
* unwrapping objects using their `valueOf()` methods.
2525
* Also accepts JSON string reprentations of integers.
2626
* Throws a `RangeError` for invalid inputs.
@@ -53,39 +53,3 @@ export function asString(value: unknown): string {
5353
if (value && typeof value === 'object') return String(value);
5454
throw new RangeError('Not a string');
5555
}
56-
57-
/**
58-
* Utility function for custom functions.
59-
* Merge the locales set for the message,
60-
* an `options` property on the input,
61-
* and the `locale` option of the expression.
62-
*
63-
* @beta
64-
*/
65-
export function mergeLocales(
66-
locales: string[],
67-
input: unknown,
68-
options: Record<string, unknown> | null
69-
): string[] {
70-
// Message locales are always included, but have the lowest precedence
71-
let lc = locales;
72-
73-
// Next, use options from input object
74-
if (input && typeof input === 'object' && 'locale' in input) {
75-
if (typeof input.locale === 'string') {
76-
lc = [input.locale, ...lc];
77-
} else if (
78-
Array.isArray(input.locale) &&
79-
input.locale.every(lc => typeof lc === 'string')
80-
) {
81-
lc = [...input.locale, ...lc];
82-
}
83-
}
84-
85-
// Explicit locale in expression options is preferred over all others
86-
if (options?.locale) {
87-
lc = [...asString(options.locale).split(','), ...lc];
88-
}
89-
90-
return lc;
91-
}

mf2/messageformat/src/resolve/function-ref.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,18 @@ describe('inputs with options', () => {
6262
expect(parts).toEqual(nf.formatToParts(12345678));
6363
});
6464

65-
test('MessageValue locales take precedence', () => {
65+
test('u:locale value take precedence', () => {
6666
const mf = new MessageFormat(
6767
'en',
68-
'{$val :number minimumFractionDigits=2}'
68+
'{$val :number minimumFractionDigits=2 u:locale=ar}'
6969
);
70-
const val = Object.assign(new Number(12345), { locale: 'fi' });
71-
const msg = mf.formatToParts({ val });
72-
const { parts } = msg[0] as MessageNumberPart;
70+
const msg = mf.formatToParts({ val: 12345 });
71+
const { parts } = msg[1] as MessageNumberPart;
7372

74-
const nf = new Intl.NumberFormat('fi', { minimumFractionDigits: 2 });
75-
expect(parts).toEqual(nf.formatToParts(12345));
73+
const ar = new Intl.NumberFormat('ar', { minimumFractionDigits: 2 });
74+
const en = new Intl.NumberFormat('en', { minimumFractionDigits: 2 });
75+
expect(parts).toEqual(ar.formatToParts(12345));
76+
expect(parts).not.toEqual(en.formatToParts(12345));
7677
});
7778
});
7879

0 commit comments

Comments
 (0)