release: WRNexusJS 0.8.0
This commit is contained in:
@@ -73,7 +73,11 @@ export interface LocaleFormatter {
|
||||
list(values: string[], options?: Intl.ListFormatOptions): string;
|
||||
}
|
||||
|
||||
export function createLocaleFormatter(locale: string, timeZone?: string): LocaleFormatter {
|
||||
export function createLocaleFormatter(
|
||||
locale: string,
|
||||
timeZone?: string,
|
||||
calendar?: string,
|
||||
): LocaleFormatter {
|
||||
const numberCache = new Map<string, Intl.NumberFormat>();
|
||||
const dateCache = new Map<string, Intl.DateTimeFormat>();
|
||||
const key = (value: unknown) => JSON.stringify(value ?? {});
|
||||
@@ -93,7 +97,11 @@ export function createLocaleFormatter(locale: string, timeZone?: string): Locale
|
||||
);
|
||||
},
|
||||
date(value, options = { dateStyle: "medium" }) {
|
||||
const resolved = { ...options, ...(timeZone ? { timeZone } : {}) };
|
||||
const resolved = {
|
||||
...options,
|
||||
...(timeZone ? { timeZone } : {}),
|
||||
...(calendar ? { calendar } : {}),
|
||||
};
|
||||
const cacheKey = key(resolved);
|
||||
let format = dateCache.get(cacheKey);
|
||||
if (!format) {
|
||||
@@ -108,18 +116,60 @@ export function createLocaleFormatter(locale: string, timeZone?: string): Locale
|
||||
};
|
||||
}
|
||||
|
||||
/** Lightweight plural templates: `{count, plural, one {# item} other {# items}}`. */
|
||||
function complexExpression(template: string, start: number) {
|
||||
const header = /^\{(\w+),\s*(plural|select),\s*/.exec(template.slice(start));
|
||||
if (!header) return null;
|
||||
let cursor = start + header[0].length;
|
||||
const choices: Record<string, string> = {};
|
||||
while (cursor < template.length) {
|
||||
while (/\s/.test(template[cursor] ?? "")) cursor++;
|
||||
if (template[cursor] === "}")
|
||||
return { name: header[1]!, kind: header[2]!, choices, end: cursor + 1 };
|
||||
const key = /^(=?[\w-]+)/.exec(template.slice(cursor));
|
||||
if (!key) return null;
|
||||
cursor += key[0].length;
|
||||
while (/\s/.test(template[cursor] ?? "")) cursor++;
|
||||
if (template[cursor] !== "{") return null;
|
||||
const bodyStart = ++cursor;
|
||||
let depth = 1;
|
||||
while (cursor < template.length && depth) {
|
||||
if (template[cursor] === "{") depth++;
|
||||
else if (template[cursor] === "}") depth--;
|
||||
cursor++;
|
||||
}
|
||||
if (depth) return null;
|
||||
choices[key[0]] = template.slice(bodyStart, cursor - 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** ICU-style plural/select templates with exact values and recursive interpolation. */
|
||||
export function formatMessage(
|
||||
template: string,
|
||||
params: Record<string, string | number>,
|
||||
locale: string,
|
||||
): string {
|
||||
const plural = /\{(\w+),\s*plural,\s*one\s*\{([^{}]*)\}\s*other\s*\{([^{}]*)\}\s*\}/g;
|
||||
let result = template.replace(plural, (_match, name: string, one: string, other: string) => {
|
||||
const value = Number(params[name] ?? 0);
|
||||
const selected = new Intl.PluralRules(locale).select(value) === "one" ? one : other;
|
||||
return selected.replace(/#/g, String(value));
|
||||
});
|
||||
let result = "";
|
||||
for (let cursor = 0; cursor < template.length;) {
|
||||
const expression = template[cursor] === "{" ? complexExpression(template, cursor) : null;
|
||||
if (!expression) {
|
||||
result += template[cursor++]!;
|
||||
continue;
|
||||
}
|
||||
const raw = params[expression.name];
|
||||
const selector = expression.kind === "plural" ? `=${Number(raw ?? 0)}` : String(raw ?? "other");
|
||||
const category =
|
||||
expression.kind === "plural"
|
||||
? new Intl.PluralRules(locale).select(Number(raw ?? 0))
|
||||
: selector;
|
||||
const selected =
|
||||
expression.choices[selector] ??
|
||||
expression.choices[category] ??
|
||||
expression.choices.other ??
|
||||
"";
|
||||
result += formatMessage(selected.replace(/#/g, String(raw ?? 0)), params, locale);
|
||||
cursor = expression.end;
|
||||
}
|
||||
result = result.replace(/\{(\w+)\}/g, (_match, name: string) =>
|
||||
name in params ? String(params[name]) : `{${name}}`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user