messageformat
Advanced tools
Comparing version
@@ -20,3 +20,4 @@ "use strict"; | ||
(cc >= 0x370 && cc <= 0x37d) || | ||
(cc >= 0x37f && cc <= 0x1fff) || | ||
(cc >= 0x37f && cc <= 0x61b) || | ||
(cc >= 0x61d && cc <= 0x1fff) || | ||
(cc >= 0x200c && cc <= 0x200d) || | ||
@@ -53,6 +54,6 @@ (cc >= 0x2070 && cc <= 0x2187) || | ||
cc = src.charCodeAt(++pos); | ||
const name = src.substring(nameStart, pos); | ||
const value = src.substring(nameStart, pos).normalize(); | ||
if (bidiChars.has(cc)) | ||
pos += 1; | ||
return { value: name, end: pos }; | ||
return { value, end: pos }; | ||
} | ||
@@ -59,0 +60,0 @@ exports.parseNameValue = parseNameValue; |
@@ -116,5 +116,10 @@ "use strict"; | ||
ctx.onError('missing-syntax', pos, "' '"); | ||
const key = ch === '*' | ||
? ({ type: '*', start: pos, end: pos + 1 }) | ||
: (0, values_js_1.parseLiteral)(ctx, pos, true); | ||
let key; | ||
if (ch === '*') { | ||
key = { type: '*', start: pos, end: pos + 1 }; | ||
} | ||
else { | ||
key = (0, values_js_1.parseLiteral)(ctx, pos, true); | ||
key.value = key.value.normalize(); | ||
} | ||
if (key.end === pos) | ||
@@ -121,0 +126,0 @@ break; // error; reported in pattern.errors |
@@ -53,3 +53,3 @@ "use strict"; | ||
function stringifyPattern({ body, braces }, braced) { | ||
let str = braced ? braces?.[0]?.value ?? '{{' : ''; | ||
let str = braced ? (braces?.[0]?.value ?? '{{') : ''; | ||
for (const el of body) { | ||
@@ -56,0 +56,0 @@ str += |
@@ -73,3 +73,5 @@ "use strict"; | ||
else { | ||
keys.push(literal(true)); | ||
const key = literal(true); | ||
key.value = key.value.normalize(); | ||
keys.push(key); | ||
} | ||
@@ -76,0 +78,0 @@ } |
@@ -37,3 +37,3 @@ import { type MessageNode } from './data-model/types.js'; | ||
export declare class MessageResolutionError extends MessageError { | ||
type: 'bad-function-result' | 'bad-operand' | 'bad-option' | 'unresolved-variable'; | ||
type: 'bad-function-result' | 'bad-operand' | 'bad-option' | 'unresolved-variable' | 'unsupported-operation'; | ||
source: string; | ||
@@ -40,0 +40,0 @@ constructor(type: typeof MessageResolutionError.prototype.type, message: string, source: string); |
@@ -1,2 +0,2 @@ | ||
import type { MessageValue } from './functions/index.js'; | ||
import type { MessageValue } from './message-value.js'; | ||
import type { MessageFunctions } from './messageformat.js'; | ||
@@ -7,3 +7,3 @@ export interface Context { | ||
localeMatcher: 'best fit' | 'lookup'; | ||
locales: string[]; | ||
locales: Intl.Locale[]; | ||
/** Cache for local variables */ | ||
@@ -10,0 +10,0 @@ localVars: WeakSet<MessageValue>; |
@@ -7,6 +7,13 @@ export type { MessageDateTimePart } from './functions/datetime.js'; | ||
/** @beta */ | ||
export interface MessageBiDiIsolationPart { | ||
type: 'bidiIsolation'; | ||
value: '\u2066' | '\u2067' | '\u2068' | '\u2069'; | ||
} | ||
/** @beta */ | ||
export interface MessageExpressionPart { | ||
type: string; | ||
source: string; | ||
dir?: 'ltr' | 'rtl'; | ||
locale?: string; | ||
id?: string; | ||
parts?: Array<{ | ||
@@ -30,2 +37,3 @@ type: string; | ||
name: string; | ||
id?: string; | ||
options?: { | ||
@@ -36,2 +44,2 @@ [key: string]: unknown; | ||
/** @beta */ | ||
export type MessagePart = MessageExpressionPart | MessageLiteralPart | MessageMarkupPart; | ||
export type MessagePart = MessageBiDiIsolationPart | MessageExpressionPart | MessageLiteralPart | MessageMarkupPart; |
import type { MessageExpressionPart } from '../formatted-parts.js'; | ||
import type { MessageFunctionContext, MessageValue } from './index.js'; | ||
import type { MessageValue } from '../message-value.js'; | ||
import type { MessageFunctionContext } from '../resolve/function-context.js'; | ||
/** @beta */ | ||
@@ -7,3 +8,3 @@ export interface MessageDateTime extends MessageValue { | ||
readonly source: string; | ||
readonly locale: string; | ||
readonly dir: 'ltr' | 'rtl' | 'auto'; | ||
readonly options: Readonly<Intl.DateTimeFormatOptions>; | ||
@@ -10,0 +11,0 @@ toParts(): [MessageDateTimePart]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.time = exports.date = exports.datetime = void 0; | ||
const dir_utils_js_1 = require("../dir-utils.js"); | ||
const errors_js_1 = require("../errors.js"); | ||
const utils_js_1 = require("./utils.js"); | ||
const localeOptions = [ | ||
'calendar', | ||
'localeMatcher', | ||
'hour12', | ||
'hourCycle', | ||
'numberingSystem', | ||
'timeZone' | ||
]; | ||
const styleOptions = new Set(['dateStyle', 'timeStyle']); | ||
const fieldOptions = new Set([ | ||
'weekday', | ||
'era', | ||
'year', | ||
'month', | ||
'day', | ||
'hour', | ||
'minute', | ||
'second', | ||
'fractionalSecondDigits', | ||
'timeZoneName' | ||
]); | ||
/** | ||
@@ -21,3 +27,5 @@ * `datetime` accepts a Date, number or string as its input | ||
*/ | ||
const datetime = (ctx, options, input) => dateTimeImplementation(ctx, options, input, res => { | ||
const datetime = (ctx, options, input) => dateTimeImplementation(ctx, input, res => { | ||
let hasStyle = false; | ||
let hasFields = false; | ||
for (const [name, value] of Object.entries(options)) { | ||
@@ -32,2 +40,3 @@ if (value === undefined) | ||
res[name] = (0, utils_js_1.asPositiveInteger)(value); | ||
hasFields = true; | ||
break; | ||
@@ -39,14 +48,21 @@ case 'hour12': | ||
res[name] = (0, utils_js_1.asString)(value); | ||
if (!hasStyle && styleOptions.has(name)) | ||
hasStyle = true; | ||
if (!hasFields && fieldOptions.has(name)) | ||
hasFields = true; | ||
} | ||
} | ||
catch { | ||
const msg = `Value ${value} is not valid for :datetime option ${name}`; | ||
throw new errors_js_1.MessageResolutionError('bad-option', msg, ctx.source); | ||
const msg = `Value ${value} is not valid for :datetime ${name} option`; | ||
ctx.onError(new errors_js_1.MessageResolutionError('bad-option', msg, ctx.source)); | ||
} | ||
} | ||
// Set defaults if localeMatcher is the only option | ||
if (Object.keys(res).length <= 1) { | ||
if (!hasStyle && !hasFields) { | ||
res.dateStyle = 'medium'; | ||
res.timeStyle = 'short'; | ||
} | ||
else if (hasStyle && hasFields) { | ||
const msg = 'Style and field options cannot be both set for :datetime'; | ||
throw new errors_js_1.MessageResolutionError('bad-option', msg, ctx.source); | ||
} | ||
}); | ||
@@ -60,15 +76,30 @@ exports.datetime = datetime; | ||
*/ | ||
const date = (ctx, options, input) => dateTimeImplementation(ctx, options, input, res => { | ||
const ds = options.style ?? res.dateStyle ?? 'medium'; | ||
const date = (ctx, options, input) => dateTimeImplementation(ctx, input, res => { | ||
for (const name of Object.keys(res)) { | ||
if (!localeOptions.includes(name)) | ||
if (styleOptions.has(name) || fieldOptions.has(name)) | ||
delete res[name]; | ||
} | ||
try { | ||
res.dateStyle = (0, utils_js_1.asString)(ds); | ||
for (const [name, value] of Object.entries(options)) { | ||
if (value === undefined) | ||
continue; | ||
try { | ||
switch (name) { | ||
case 'style': | ||
res.dateStyle = (0, utils_js_1.asString)(value); | ||
break; | ||
case 'hour12': | ||
res[name] = (0, utils_js_1.asBoolean)(value); | ||
break; | ||
case 'calendar': | ||
case 'numberingSystem': | ||
case 'timeZone': | ||
res[name] = (0, utils_js_1.asString)(value); | ||
} | ||
} | ||
catch { | ||
const msg = `Value ${value} is not valid for :date ${name} option`; | ||
ctx.onError(new errors_js_1.MessageResolutionError('bad-option', msg, ctx.source)); | ||
} | ||
} | ||
catch { | ||
const msg = `Value ${ds} is not valid for :date style option`; | ||
throw new errors_js_1.MessageResolutionError('bad-option', msg, ctx.source); | ||
} | ||
res.dateStyle ??= 'medium'; | ||
}); | ||
@@ -82,19 +113,34 @@ exports.date = date; | ||
*/ | ||
const time = (ctx, options, input) => dateTimeImplementation(ctx, options, input, res => { | ||
const ts = options.style ?? res.timeStyle ?? 'short'; | ||
const time = (ctx, options, input) => dateTimeImplementation(ctx, input, res => { | ||
for (const name of Object.keys(res)) { | ||
if (!localeOptions.includes(name)) | ||
if (styleOptions.has(name) || fieldOptions.has(name)) | ||
delete res[name]; | ||
} | ||
try { | ||
res.timeStyle = (0, utils_js_1.asString)(ts); | ||
for (const [name, value] of Object.entries(options)) { | ||
if (value === undefined) | ||
continue; | ||
try { | ||
switch (name) { | ||
case 'style': | ||
res.timeStyle = (0, utils_js_1.asString)(value); | ||
break; | ||
case 'hour12': | ||
res[name] = (0, utils_js_1.asBoolean)(value); | ||
break; | ||
case 'calendar': | ||
case 'numberingSystem': | ||
case 'timeZone': | ||
res[name] = (0, utils_js_1.asString)(value); | ||
} | ||
} | ||
catch { | ||
const msg = `Value ${value} is not valid for :time ${name} option`; | ||
ctx.onError(new errors_js_1.MessageResolutionError('bad-option', msg, ctx.source)); | ||
} | ||
} | ||
catch { | ||
const msg = `Value ${ts} is not valid for :time style option`; | ||
throw new errors_js_1.MessageResolutionError('bad-option', msg, ctx.source); | ||
} | ||
res.timeStyle ??= 'short'; | ||
}); | ||
exports.time = time; | ||
function dateTimeImplementation({ localeMatcher, locales, source }, options, input, parseOptions) { | ||
const lc = (0, utils_js_1.mergeLocales)(locales, input, options); | ||
function dateTimeImplementation(ctx, input, parseOptions) { | ||
const { localeMatcher, locales, source } = ctx; | ||
const opt = { localeMatcher }; | ||
@@ -125,2 +171,3 @@ if (input && typeof input === 'object') { | ||
let locale; | ||
let dir = ctx.dir; | ||
let dtf; | ||
@@ -131,4 +178,8 @@ let str; | ||
source, | ||
get locale() { | ||
return (locale ??= Intl.DateTimeFormat.supportedLocalesOf(lc, opt)[0]); | ||
get dir() { | ||
if (dir == null) { | ||
locale ??= Intl.DateTimeFormat.supportedLocalesOf(locales, opt)[0]; | ||
dir = (0, dir_utils_js_1.getLocaleDir)(locale); | ||
} | ||
return dir; | ||
}, | ||
@@ -139,9 +190,12 @@ get options() { | ||
toParts() { | ||
dtf ??= new Intl.DateTimeFormat(lc, opt); | ||
dtf ??= new Intl.DateTimeFormat(locales, opt); | ||
const parts = dtf.formatToParts(date); | ||
locale ??= dtf.resolvedOptions().locale; | ||
return [{ type: 'datetime', source, locale, parts }]; | ||
dir ??= (0, dir_utils_js_1.getLocaleDir)(locale); | ||
return dir === 'ltr' || dir === 'rtl' | ||
? [{ type: 'datetime', source, dir, locale, parts }] | ||
: [{ type: 'datetime', source, locale, parts }]; | ||
}, | ||
toString() { | ||
dtf ??= new Intl.DateTimeFormat(lc, opt); | ||
dtf ??= new Intl.DateTimeFormat(locales, opt); | ||
str ??= dtf.format(date); | ||
@@ -148,0 +202,0 @@ return str; |
import type { MessageExpressionPart } from '../formatted-parts.js'; | ||
import type { MessageValue } from './index.js'; | ||
import type { MessageValue } from '../message-value.js'; | ||
/** | ||
@@ -11,3 +11,2 @@ * Used to represent runtime/formatting errors. | ||
readonly source: string; | ||
readonly locale: 'und'; | ||
toParts(): [MessageFallbackPart]; | ||
@@ -14,0 +13,0 @@ toString(): string; |
@@ -6,3 +6,2 @@ "use strict"; | ||
type: 'fallback', | ||
locale: 'und', | ||
source, | ||
@@ -9,0 +8,0 @@ toParts: () => [{ type: 'fallback', source }], |
@@ -1,17 +0,9 @@ | ||
import type { MessageExpressionPart } from '../formatted-parts.js'; | ||
export type { MessageFunctionContext } from '../data-model/function-context.js'; | ||
export { type MessageDateTime, date, datetime, time } from './datetime.js'; | ||
export { type MessageFallback, fallback } from './fallback.js'; | ||
export { type MessageNumber, integer, number } from './number.js'; | ||
export { type MessageString, string } from './string.js'; | ||
export { type MessageUnknownValue, unknown } from './unknown.js'; | ||
export interface MessageValue { | ||
readonly type: string; | ||
readonly locale: string; | ||
readonly source: string; | ||
readonly options?: Readonly<object>; | ||
selectKey?: (keys: Set<string>) => string | null; | ||
toParts?: () => MessageExpressionPart[]; | ||
toString?: () => string; | ||
valueOf?: () => unknown; | ||
} | ||
export type { MessageValue } from '../message-value.js'; | ||
export type { MessageFunctionContext } from '../resolve/function-context.js'; | ||
export { currency } from './currency.js'; | ||
export { date, datetime, time, type MessageDateTime } from './datetime.js'; | ||
export { fallback, type MessageFallback } from './fallback.js'; | ||
export { math } from './math.js'; | ||
export { integer, number, type MessageNumber } from './number.js'; | ||
export { string, type MessageString } from './string.js'; | ||
export { unknown, type MessageUnknownValue } from './unknown.js'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.unknown = exports.string = exports.number = exports.integer = exports.fallback = exports.time = exports.datetime = exports.date = void 0; | ||
exports.unknown = exports.string = exports.number = exports.integer = exports.math = exports.fallback = exports.time = exports.datetime = exports.date = exports.currency = void 0; | ||
var currency_js_1 = require("./currency.js"); | ||
Object.defineProperty(exports, "currency", { enumerable: true, get: function () { return currency_js_1.currency; } }); | ||
var datetime_js_1 = require("./datetime.js"); | ||
@@ -10,2 +12,4 @@ Object.defineProperty(exports, "date", { enumerable: true, get: function () { return datetime_js_1.date; } }); | ||
Object.defineProperty(exports, "fallback", { enumerable: true, get: function () { return fallback_js_1.fallback; } }); | ||
var math_js_1 = require("./math.js"); | ||
Object.defineProperty(exports, "math", { enumerable: true, get: function () { return math_js_1.math; } }); | ||
var number_js_1 = require("./number.js"); | ||
@@ -12,0 +16,0 @@ Object.defineProperty(exports, "integer", { enumerable: true, get: function () { return number_js_1.integer; } }); |
import type { MessageExpressionPart } from '../formatted-parts.js'; | ||
import type { MessageFunctionContext, MessageValue } from './index.js'; | ||
import type { MessageValue } from '../message-value.js'; | ||
import type { MessageFunctionContext } from '../resolve/function-context.js'; | ||
/** @beta */ | ||
@@ -7,3 +8,3 @@ export interface MessageNumber extends MessageValue { | ||
readonly source: string; | ||
readonly locale: string; | ||
readonly dir: 'ltr' | 'rtl' | 'auto'; | ||
readonly options: Readonly<Intl.NumberFormatOptions & Intl.PluralRulesOptions>; | ||
@@ -32,2 +33,6 @@ /** | ||
} | ||
export declare function readNumericOperand(value: unknown, source: string): { | ||
value: number | bigint; | ||
options: unknown; | ||
}; | ||
/** | ||
@@ -42,3 +47,3 @@ * `number` accepts a number, BigInt or string representing a JSON number as input | ||
*/ | ||
export declare function number({ localeMatcher, locales, source }: MessageFunctionContext, options: Record<string | symbol, unknown>, input?: unknown): MessageNumber; | ||
export declare function number(ctx: MessageFunctionContext, exprOpt: Record<string | symbol, unknown>, operand?: unknown): MessageNumber; | ||
/** | ||
@@ -45,0 +50,0 @@ * `integer` accepts a number, BigInt or string representing a JSON number as input |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.integer = exports.number = void 0; | ||
exports.integer = exports.number = exports.readNumericOperand = void 0; | ||
const dir_utils_js_1 = require("../dir-utils.js"); | ||
const errors_js_1 = require("../errors.js"); | ||
const utils_js_1 = require("./utils.js"); | ||
const INT = Symbol('INT'); | ||
/** | ||
* `number` accepts a number, BigInt or string representing a JSON number as input | ||
* and formats it with the same options as | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat | Intl.NumberFormat}. | ||
* It also supports plural category selection via | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules | Intl.PluralRules}. | ||
* | ||
* @beta | ||
*/ | ||
function number({ localeMatcher, locales, source }, options, input) { | ||
const opt = { | ||
localeMatcher | ||
}; | ||
let value = input; | ||
function readNumericOperand(value, source) { | ||
let options = undefined; | ||
if (typeof value === 'object') { | ||
const valueOf = value?.valueOf; | ||
if (typeof valueOf === 'function') { | ||
Object.assign(opt, value.options); | ||
options = value.options; | ||
value = valueOf.call(value); | ||
@@ -40,3 +29,23 @@ } | ||
} | ||
for (const [name, optval] of Object.entries(options)) { | ||
return { value, options }; | ||
} | ||
exports.readNumericOperand = readNumericOperand; | ||
/** | ||
* `number` accepts a number, BigInt or string representing a JSON number as input | ||
* and formats it with the same options as | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat | Intl.NumberFormat}. | ||
* It also supports plural category selection via | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules | Intl.PluralRules}. | ||
* | ||
* @beta | ||
*/ | ||
function number(ctx, exprOpt, operand) { | ||
const { locales, source } = ctx; | ||
const options = { | ||
localeMatcher: ctx.localeMatcher | ||
}; | ||
const input = readNumericOperand(operand, source); | ||
const value = input.value; | ||
Object.assign(options, input.options); | ||
for (const [name, optval] of Object.entries(exprOpt)) { | ||
if (optval === undefined) | ||
@@ -56,10 +65,13 @@ continue; | ||
// @ts-expect-error TS types don't know about roundingIncrement | ||
opt[name] = (0, utils_js_1.asPositiveInteger)(optval); | ||
options[name] = (0, utils_js_1.asPositiveInteger)(optval); | ||
break; | ||
case 'useGrouping': | ||
opt[name] = (0, utils_js_1.asBoolean)(optval); | ||
case 'useGrouping': { | ||
const strval = (0, utils_js_1.asString)(optval); | ||
// @ts-expect-error TS type is wrong | ||
options[name] = strval === 'never' ? false : strval; | ||
break; | ||
} | ||
default: | ||
// @ts-expect-error Unknown options will be ignored | ||
opt[name] = (0, utils_js_1.asString)(optval); | ||
options[name] = (0, utils_js_1.asString)(optval); | ||
} | ||
@@ -69,10 +81,10 @@ } | ||
const msg = `Value ${optval} is not valid for :number option ${name}`; | ||
throw new errors_js_1.MessageResolutionError('bad-option', msg, source); | ||
ctx.onError(new errors_js_1.MessageResolutionError('bad-option', msg, source)); | ||
} | ||
} | ||
const num = Number.isFinite(value) && options[INT] | ||
const num = Number.isFinite(value) && exprOpt[INT] | ||
? Math.round(value) | ||
: value; | ||
const lc = (0, utils_js_1.mergeLocales)(locales, input, options); | ||
let locale; | ||
let dir = ctx.dir; | ||
let nf; | ||
@@ -84,7 +96,11 @@ let cat; | ||
source, | ||
get locale() { | ||
return (locale ??= Intl.NumberFormat.supportedLocalesOf(lc, opt)[0]); | ||
get dir() { | ||
if (dir == null) { | ||
locale ??= Intl.NumberFormat.supportedLocalesOf(locales, options)[0]; | ||
dir = (0, dir_utils_js_1.getLocaleDir)(locale); | ||
} | ||
return dir; | ||
}, | ||
get options() { | ||
return { ...opt }; | ||
return { ...options }; | ||
}, | ||
@@ -95,19 +111,22 @@ selectKey(keys) { | ||
return str; | ||
if (opt.select === 'exact') | ||
if (options.select === 'exact') | ||
return null; | ||
const pluralOpt = opt.select | ||
? { ...opt, select: undefined, type: opt.select } | ||
: opt; | ||
const pluralOpt = options.select | ||
? { ...options, select: undefined, type: options.select } | ||
: options; | ||
// Intl.PluralRules needs a number, not bigint | ||
cat ??= new Intl.PluralRules(lc, pluralOpt).select(Number(num)); | ||
cat ??= new Intl.PluralRules(locales, pluralOpt).select(Number(num)); | ||
return keys.has(cat) ? cat : null; | ||
}, | ||
toParts() { | ||
nf ??= new Intl.NumberFormat(lc, opt); | ||
nf ??= new Intl.NumberFormat(locales, options); | ||
const parts = nf.formatToParts(num); | ||
locale ??= nf.resolvedOptions().locale; | ||
return [{ type: 'number', source, locale, parts }]; | ||
dir ??= (0, dir_utils_js_1.getLocaleDir)(locale); | ||
return dir === 'ltr' || dir === 'rtl' | ||
? [{ type: 'number', source, dir, locale, parts }] | ||
: [{ type: 'number', source, locale, parts }]; | ||
}, | ||
toString() { | ||
nf ??= new Intl.NumberFormat(lc, opt); | ||
nf ??= new Intl.NumberFormat(locales, options); | ||
str ??= nf.format(num); | ||
@@ -114,0 +133,0 @@ return str; |
import type { MessageExpressionPart } from '../formatted-parts.js'; | ||
import type { MessageFunctionContext, MessageValue } from './index.js'; | ||
import type { MessageValue } from '../message-value.js'; | ||
import type { MessageFunctionContext } from '../resolve/function-context.js'; | ||
/** @beta */ | ||
@@ -7,3 +8,3 @@ export interface MessageString extends MessageValue { | ||
readonly source: string; | ||
readonly locale: string; | ||
readonly dir: 'ltr' | 'rtl' | 'auto'; | ||
selectKey(keys: Set<string>): string | null; | ||
@@ -27,2 +28,2 @@ toParts(): [MessageStringPart]; | ||
* @beta */ | ||
export declare function string({ locales, source }: Pick<MessageFunctionContext, 'locales' | 'source'>, options: Record<string, unknown>, input?: unknown): MessageString; | ||
export declare function string(ctx: Pick<MessageFunctionContext, 'dir' | 'locales' | 'source'>, _options: Record<string, unknown>, input?: unknown): MessageString; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.string = void 0; | ||
const utils_js_1 = require("./utils.js"); | ||
/** | ||
@@ -11,11 +10,17 @@ * Accepts any input, and parses any non-string value using `String()`. | ||
* @beta */ | ||
function string({ locales, source }, options, input) { | ||
function string(ctx, _options, input) { | ||
const str = input === undefined ? '' : String(input); | ||
const [locale] = (0, utils_js_1.mergeLocales)(locales, input, options); | ||
const selStr = str.normalize(); | ||
return { | ||
type: 'string', | ||
source, | ||
locale, | ||
selectKey: keys => (keys.has(str) ? str : null), | ||
toParts: () => [{ type: 'string', source, locale, value: str }], | ||
source: ctx.source, | ||
dir: ctx.dir ?? 'auto', | ||
selectKey: keys => (keys.has(selStr) ? selStr : null), | ||
toParts() { | ||
const { dir, source } = ctx; | ||
const locale = ctx.locales[0]; | ||
return dir === 'ltr' || dir === 'rtl' | ||
? [{ type: 'string', source, dir, locale, value: str }] | ||
: [{ type: 'string', source, locale, value: str }]; | ||
}, | ||
toString: () => str, | ||
@@ -22,0 +27,0 @@ valueOf: () => str |
import type { MessageExpressionPart } from '../formatted-parts.js'; | ||
import type { MessageValue } from './index.js'; | ||
import type { MessageValue } from '../message-value.js'; | ||
/** @beta */ | ||
@@ -7,3 +7,3 @@ export interface MessageUnknownValue extends MessageValue { | ||
readonly source: string; | ||
readonly locale: 'und'; | ||
readonly dir: 'auto'; | ||
toParts(): [MessageUnknownPart]; | ||
@@ -10,0 +10,0 @@ toString(): string; |
@@ -8,3 +8,3 @@ "use strict"; | ||
source, | ||
locale: 'und', | ||
dir: 'auto', | ||
toParts: () => [{ type: 'unknown', source, value: input }], | ||
@@ -11,0 +11,0 @@ toString: () => String(input), |
@@ -0,1 +1,2 @@ | ||
export { getLocaleDir } from '../dir-utils.js'; | ||
/** | ||
@@ -13,3 +14,3 @@ * Utility function for custom functions. | ||
* Utility function for custom functions. | ||
* Cast a value as a positive integer, | ||
* Cast a value as a non-negative integer, | ||
* unwrapping objects using their `valueOf()` methods. | ||
@@ -31,10 +32,1 @@ * Also accepts JSON string reprentations of integers. | ||
export declare function asString(value: unknown): string; | ||
/** | ||
* Utility function for custom functions. | ||
* Merge the locales set for the message, | ||
* an `options` property on the input, | ||
* and the `locale` option of the expression. | ||
* | ||
* @beta | ||
*/ | ||
export declare function mergeLocales(locales: string[], input: unknown, options: Record<string, unknown> | null): string[]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.mergeLocales = exports.asString = exports.asPositiveInteger = exports.asBoolean = void 0; | ||
exports.asString = exports.asPositiveInteger = exports.asBoolean = exports.getLocaleDir = void 0; | ||
var dir_utils_js_1 = require("../dir-utils.js"); | ||
Object.defineProperty(exports, "getLocaleDir", { enumerable: true, get: function () { return dir_utils_js_1.getLocaleDir; } }); | ||
/** | ||
@@ -14,3 +16,3 @@ * Utility function for custom functions. | ||
function asBoolean(value) { | ||
if (value instanceof Boolean) | ||
if (value && typeof value === 'object') | ||
value = value.valueOf(); | ||
@@ -30,3 +32,3 @@ if (typeof value === 'boolean') | ||
* Utility function for custom functions. | ||
* Cast a value as a positive integer, | ||
* Cast a value as a non-negative integer, | ||
* unwrapping objects using their `valueOf()` methods. | ||
@@ -39,5 +41,5 @@ * Also accepts JSON string reprentations of integers. | ||
function asPositiveInteger(value) { | ||
if (value instanceof Number) | ||
value = Number(value); | ||
if (typeof value === 'object' && value) | ||
if (value && typeof value === 'object') | ||
value = value.valueOf(); | ||
if (value && typeof value === 'object') | ||
value = String(value); | ||
@@ -62,2 +64,4 @@ if (typeof value === 'string' && /^(0|[1-9][0-9]*)$/.test(value)) { | ||
function asString(value) { | ||
if (value && typeof value === 'object') | ||
value = value.valueOf(); | ||
if (typeof value === 'string') | ||
@@ -70,29 +74,1 @@ return value; | ||
exports.asString = asString; | ||
/** | ||
* Utility function for custom functions. | ||
* Merge the locales set for the message, | ||
* an `options` property on the input, | ||
* and the `locale` option of the expression. | ||
* | ||
* @beta | ||
*/ | ||
function mergeLocales(locales, input, options) { | ||
// Message locales are always included, but have the lowest precedence | ||
let lc = locales; | ||
// Next, use options from input object | ||
if (input && typeof input === 'object' && 'locale' in input) { | ||
if (typeof input.locale === 'string') { | ||
lc = [input.locale, ...lc]; | ||
} | ||
else if (Array.isArray(input.locale) && | ||
input.locale.every(lc => typeof lc === 'string')) { | ||
lc = [...input.locale, ...lc]; | ||
} | ||
} | ||
// Explicit locale in expression options is preferred over all others | ||
if (options?.locale) { | ||
lc = [...asString(options.locale).split(','), ...lc]; | ||
} | ||
return lc; | ||
} | ||
exports.mergeLocales = mergeLocales; |
@@ -1,5 +0,5 @@ | ||
import type { MessageFunctionContext } from './data-model/function-context.js'; | ||
import type { Message } from './data-model/types.js'; | ||
import type { MessagePart } from './formatted-parts.js'; | ||
import { MessageValue } from './functions/index.js'; | ||
import { type MessageValue } from './message-value.js'; | ||
import type { MessageFunctionContext } from './resolve/function-context.js'; | ||
/** | ||
@@ -16,2 +16,17 @@ * The runtime function registry available when resolving {@link FunctionRef} elements. | ||
/** | ||
* The bidi isolation strategy for messages, | ||
* i.e. how parts with different or unknown directionalities are isolated from each other. | ||
* | ||
* The default `'default'` strategy isolates all placeholders, | ||
* except when both the message and the placeholder are known to be left-to-right. | ||
* | ||
* The `'none'` strategy applies no isolation at all. | ||
*/ | ||
bidiIsolation?: 'default' | 'none'; | ||
/** | ||
* Explicitly set the message's base direction. | ||
* If not set, the direction is detected from the primary locale. | ||
*/ | ||
dir?: 'ltr' | 'rtl' | 'auto'; | ||
/** | ||
* If given multiple locales, | ||
@@ -42,2 +57,4 @@ * determines which algorithm to use when selecting between them; | ||
resolvedOptions(): { | ||
bidiIsolation: boolean; | ||
dir: "auto" | "ltr" | "rtl"; | ||
functions: Readonly<Readonly<MessageFunctions>>; | ||
@@ -44,0 +61,0 @@ localeMatcher: "lookup" | "best fit"; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.MessageFormat = void 0; | ||
const format_markup_js_1 = require("./data-model/format-markup.js"); | ||
const resolve_expression_js_1 = require("./data-model/resolve-expression.js"); | ||
const resolve_variable_js_1 = require("./data-model/resolve-variable.js"); | ||
const parse_js_1 = require("./data-model/parse.js"); | ||
const validate_js_1 = require("./data-model/validate.js"); | ||
const dir_utils_js_1 = require("./dir-utils.js"); | ||
const errors_js_1 = require("./errors.js"); | ||
const index_js_1 = require("./functions/index.js"); | ||
const parse_js_1 = require("./data-model/parse.js"); | ||
const message_value_js_1 = require("./message-value.js"); | ||
const format_markup_js_1 = require("./resolve/format-markup.js"); | ||
const resolve_expression_js_1 = require("./resolve/resolve-expression.js"); | ||
const resolve_variable_js_1 = require("./resolve/resolve-variable.js"); | ||
const select_pattern_js_1 = require("./select-pattern.js"); | ||
const defaultFunctions = Object.freeze({ | ||
currency: index_js_1.currency, | ||
date: index_js_1.date, | ||
datetime: index_js_1.datetime, | ||
integer: index_js_1.integer, | ||
math: index_js_1.math, | ||
number: index_js_1.number, | ||
@@ -26,2 +30,4 @@ string: index_js_1.string, | ||
class MessageFormat { | ||
#bidiIsolation; | ||
#dir; | ||
#localeMatcher; | ||
@@ -32,8 +38,10 @@ #locales; | ||
constructor(locales, source, options) { | ||
this.#bidiIsolation = options?.bidiIsolation !== 'none'; | ||
this.#localeMatcher = options?.localeMatcher ?? 'best fit'; | ||
this.#locales = Array.isArray(locales) | ||
? locales.slice() | ||
? locales.map(lc => new Intl.Locale(lc)) | ||
: locales | ||
? [locales] | ||
? [new Intl.Locale(locales)] | ||
: []; | ||
this.#dir = options?.dir ?? (0, dir_utils_js_1.getLocaleDir)(this.#locales[0]); | ||
this.#message = typeof source === 'string' ? (0, parse_js_1.parseMessage)(source) : source; | ||
@@ -54,3 +62,7 @@ (0, validate_js_1.validate)(this.#message, (type, node) => { | ||
} | ||
else if (elem.type !== 'markup') { | ||
else if (elem.type === 'markup') { | ||
// Handle errors, but discard results | ||
(0, format_markup_js_1.formatMarkup)(ctx, elem); | ||
} | ||
else { | ||
let mv; | ||
@@ -60,3 +72,10 @@ try { | ||
if (typeof mv.toString === 'function') { | ||
res += mv.toString(); | ||
if (this.#bidiIsolation && | ||
(this.#dir !== 'ltr' || mv.dir !== 'ltr' || mv[message_value_js_1.BIDI_ISOLATE])) { | ||
const pre = mv.dir === 'ltr' ? dir_utils_js_1.LRI : mv.dir === 'rtl' ? dir_utils_js_1.RLI : dir_utils_js_1.FSI; | ||
res += pre + mv.toString() + dir_utils_js_1.PDI; | ||
} | ||
else { | ||
res += mv.toString(); | ||
} | ||
} | ||
@@ -70,3 +89,4 @@ else { | ||
ctx.onError(error); | ||
res += `{${mv?.source ?? '�'}}`; | ||
const errStr = `{${mv?.source ?? '�'}}`; | ||
res += this.#bidiIsolation ? dir_utils_js_1.FSI + errStr + dir_utils_js_1.PDI : errStr; | ||
} | ||
@@ -92,3 +112,14 @@ } | ||
if (typeof mv.toParts === 'function') { | ||
parts.push(...mv.toParts()); | ||
const mp = mv.toParts(); | ||
if (this.#bidiIsolation && | ||
(this.#dir !== 'ltr' || mv.dir !== 'ltr' || mv[message_value_js_1.BIDI_ISOLATE])) { | ||
const pre = mv.dir === 'ltr' ? dir_utils_js_1.LRI : mv.dir === 'rtl' ? dir_utils_js_1.RLI : dir_utils_js_1.FSI; | ||
parts.push({ type: 'bidiIsolation', value: pre }, ...mp, { | ||
type: 'bidiIsolation', | ||
value: dir_utils_js_1.PDI | ||
}); | ||
} | ||
else { | ||
parts.push(...mp); | ||
} | ||
} | ||
@@ -102,3 +133,12 @@ else { | ||
ctx.onError(error); | ||
parts.push({ type: 'fallback', source: mv?.source ?? '�' }); | ||
const fb = { type: 'fallback', source: mv?.source ?? '�' }; | ||
if (this.#bidiIsolation) { | ||
parts.push({ type: 'bidiIsolation', value: dir_utils_js_1.FSI }, fb, { | ||
type: 'bidiIsolation', | ||
value: dir_utils_js_1.PDI | ||
}); | ||
} | ||
else { | ||
parts.push(fb); | ||
} | ||
} | ||
@@ -111,2 +151,4 @@ } | ||
return { | ||
bidiIsolation: this.#bidiIsolation, | ||
dir: this.#dir, | ||
functions: Object.freeze(this.#functions), | ||
@@ -127,3 +169,3 @@ localeMatcher: this.#localeMatcher | ||
for (const decl of this.#message.declarations) { | ||
scope[decl.name] = new resolve_variable_js_1.UnresolvedExpression(decl.value, decl.type === 'input' ? msgParams ?? {} : undefined); | ||
scope[decl.name] = new resolve_variable_js_1.UnresolvedExpression(decl.value, decl.type === 'input' ? (msgParams ?? {}) : undefined); | ||
} | ||
@@ -130,0 +172,0 @@ const ctx = { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.selectPattern = void 0; | ||
const resolve_variable_js_1 = require("./data-model/resolve-variable.js"); | ||
const errors_js_1 = require("./errors.js"); | ||
const resolve_variable_js_1 = require("./resolve/resolve-variable.js"); | ||
function selectPattern(context, message) { | ||
@@ -7,0 +7,0 @@ switch (message.type) { |
{ | ||
"name": "messageformat", | ||
"version": "4.0.0-8", | ||
"version": "4.0.0-9", | ||
"description": "Intl.MessageFormat / Unicode MessageFormat 2 parser, runtime and polyfill", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
161186
11.97%81
10.96%4116
10.32%