@messageformat/icu-messageformat-1
Version:
Compile ICU MessageFormat 1 sources into MessageFormat 2 formatters
146 lines (145 loc) • 4.93 kB
JavaScript
import { MessageResolutionError } from 'messageformat';
import { DefaultFunctions, DraftFunctions } from 'messageformat/functions';
function checkArgStyle(ctx, options) {
const argStyle = options['mf1:argStyle'];
if (argStyle) {
const msg = `Unsupported MF1 number argStyle: ${argStyle}`;
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
}
}
function currency(ctx, options, operand) {
checkArgStyle(ctx, options);
const scale = Number(options['mf1:scale']);
if (scale && scale !== 1)
operand = scale * Number(operand);
return DraftFunctions.currency(ctx, options, operand);
}
function datetime(ctx, options, operand) {
checkArgStyle(ctx, options);
return DraftFunctions.datetime(ctx, options, operand);
}
function duration(ctx, _options, operand) {
let value = Number(operand);
let str;
if (!isFinite(value)) {
str = String(value);
}
else {
let sign = '';
if (value < 0) {
sign = '-';
value = Math.abs(value);
}
const sec = value % 60;
const parts = [Math.round(sec) === sec ? sec : sec.toFixed(3)];
if (value < 60) {
parts.unshift(0); // at least one : is required
}
else {
value = Math.round((value - Number(parts[0])) / 60);
parts.unshift(value % 60); // minutes
if (value >= 60) {
value = Math.round((value - Number(parts[0])) / 60);
parts.unshift(value); // hours
}
}
const first = parts.shift();
str =
sign +
first +
':' +
parts.map(n => (Number(n) < 10 ? '0' + String(n) : String(n))).join(':');
}
const { source } = ctx;
return {
type: 'mf1:duration',
source,
toParts: () => [{ type: 'mf1:duration', source, value: str }],
toString: () => str,
valueOf: () => value
};
}
function number(ctx, options, operand) {
checkArgStyle(ctx, options);
const scale = Number(options['mf1:scale']);
if (scale && scale !== 1)
operand = scale * Number(operand);
return DefaultFunctions.number(ctx, options, operand);
}
function plural(ctx, options, operand) {
const origNum = typeof operand === 'bigint' ? operand : Number(operand);
let num = origNum;
const offset = Number(options.offset);
if (Number.isInteger(offset)) {
if (typeof num === 'number')
num -= offset;
else if (typeof num === 'bigint')
num -= BigInt(offset);
}
else {
const msg = `Plural offset must be an integer: ${options.offset}`;
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
}
const mv = DefaultFunctions.number(ctx, {}, num);
mv.selectKey = keys => {
const str = String(origNum);
if (keys.has(str))
return str;
// Intl.PluralRules needs a number, not bigint
const cat = new Intl.PluralRules(ctx.locales, {
localeMatcher: ctx.localeMatcher,
type: options.select === 'ordinal' ? 'ordinal' : 'cardinal'
}).select(Number(num));
return keys.has(cat) ? cat : null;
};
return mv;
}
function unit(ctx, options, operand) {
checkArgStyle(ctx, options);
const scale = Number(options['mf1:scale']);
if (scale && scale !== 1)
operand = scale * Number(operand);
return DraftFunctions.unit(ctx, options, operand);
}
/**
* Function handlers for ICU MessageFormat 1.
*
* Used by {@link mf1ToMessage}.
*/
export let MF1Functions = {
/**
* A wrapper around {@link DraftFunctions.currency},
* used for formatting a `number, currency` and `number, ::currency` placeholder.
*/
'mf1:currency': currency,
/**
* A wrapper around {@link DraftFunctions.datetime},
* used for formatting `date` and `time` placeholders.
*/
'mf1:datetime': datetime,
/**
* Formats a duration expressed as seconds.
*
* The formatted value includes one or two `:` separators,
* matching the pattern `hhhh:mm:ss`,
* possibly with a leading `-` for negative values and a trailing `.sss` part for non-integer input.
*/
'mf1:duration': duration,
/**
* A wrapper around {@link DefaultFunctions.number},
* used for formatting most `number` placeholders.
*/
'mf1:number': number,
/**
* A plural or selectordinal selector that includes an `offset`.
*
* Selectors without an offset are represented by `:number`.
*/
'mf1:plural': plural,
/**
* A wrapper around {@link DraftFunctions.unit},
* used for formatting `number, percent`, `number, ::unit`, and other placeholders.
*/
'mf1:unit': unit
};
MF1Functions = Object.freeze(Object.assign(Object.create(null), MF1Functions));