messageformat-number-skeleton
Version:
A parser & formatter for ICU NumberFormat skeleton strings & patterns
212 lines (211 loc) • 6.95 kB
JavaScript
import { UnsupportedError } from '../errors.js';
/**
* Given an input ICU NumberFormatter skeleton, does its best to construct a
* corresponding `Intl.NumberFormat` options structure.
*
* @remarks
* In addition to standard `Intl.NumberFormat` options, some features make use
* of the {@link https://github.com/tc39/proposal-unified-intl-numberformat |
* Unified API Proposal}, which has limited support.
*
* @internal
* @param onUnsupported - If defined, called when encountering unsupported (but
* valid) tokens, such as `decimal-always` or `permille`. The error `source`
* may specify the source of an unsupported option.
*
* @example
* ```js
* import {
* getNumberFormatOptions,
* parseNumberSkeleton
* } from 'messageformat-number-skeleton'
*
* const src = 'currency/CAD unit-width-narrow'
* const skeleton = parseNumberSkeleton(src, console.error)
* // {
* // unit: { style: 'currency', currency: 'CAD' },
* // unitWidth: 'unit-width-narrow'
* // }
*
* getNumberFormatOptions(skeleton, console.error)
* // {
* // style: 'currency',
* // currency: 'CAD',
* // currencyDisplay: 'narrowSymbol',
* // unitDisplay: 'narrow'
* // }
*
* const sk2 = parseNumberSkeleton('group-min2')
* // { group: 'group-min2' }
*
* getNumberFormatOptions(sk2, console.error)
* // Error: The stem group-min2 is not supported
* // at UnsupportedError.NumberFormatError ... {
* // code: 'UNSUPPORTED',
* // stem: 'group-min2'
* // }
* // {}
* ```
*/
export function getNumberFormatOptions(skeleton, onUnsupported) {
const { decimal, group, integerWidth, notation, precision, roundingMode, sign, unit, unitPer, unitWidth } = skeleton;
const fail = (stem, source) => {
if (onUnsupported)
onUnsupported(new UnsupportedError(stem, source));
};
const opt = {};
if (unit) {
switch (unit.style) {
case 'base-unit':
opt.style = 'decimal';
break;
case 'currency':
opt.style = 'currency';
opt.currency = unit.currency;
break;
case 'measure-unit':
opt.style = 'unit';
opt.unit = unit.unit.replace(/.*-/, '');
if (unitPer)
opt.unit += '-per-' + unitPer.replace(/.*-/, '');
break;
case 'percent':
opt.style = 'percent';
break;
case 'permille':
fail('permille');
break;
}
}
switch (unitWidth) {
case 'unit-width-full-name':
opt.currencyDisplay = 'name';
opt.unitDisplay = 'long';
break;
case 'unit-width-hidden':
fail(unitWidth);
break;
case 'unit-width-iso-code':
opt.currencyDisplay = 'code';
break;
case 'unit-width-narrow':
opt.currencyDisplay = 'narrowSymbol';
opt.unitDisplay = 'narrow';
break;
case 'unit-width-short':
opt.currencyDisplay = 'symbol';
opt.unitDisplay = 'short';
break;
}
switch (group) {
case 'group-off':
opt.useGrouping = false;
break;
case 'group-auto':
opt.useGrouping = true;
break;
case 'group-min2':
case 'group-on-aligned':
case 'group-thousands':
fail(group);
opt.useGrouping = true;
break;
}
if (precision) {
switch (precision.style) {
case 'precision-fraction': {
const { minFraction: minF, maxFraction: maxF, minSignificant: minS, maxSignificant: maxS, source } = precision;
if (typeof minF === 'number') {
opt.minimumFractionDigits = minF;
if (typeof minS === 'number')
fail('precision-fraction', source);
}
if (typeof maxF === 'number')
opt.maximumFractionDigits = maxF;
if (typeof minS === 'number')
opt.minimumSignificantDigits = minS;
if (typeof maxS === 'number')
opt.maximumSignificantDigits = maxS;
break;
}
case 'precision-integer':
opt.maximumFractionDigits = 0;
break;
case 'precision-unlimited':
opt.maximumFractionDigits = 20;
break;
case 'precision-increment':
case 'precision-currency-standard':
break;
case 'precision-currency-cash':
fail(precision.style);
break;
}
}
if (notation) {
switch (notation.style) {
case 'compact-short':
opt.notation = 'compact';
opt.compactDisplay = 'short';
break;
case 'compact-long':
opt.notation = 'compact';
opt.compactDisplay = 'long';
break;
case 'notation-simple':
opt.notation = 'standard';
break;
case 'scientific':
case 'engineering': {
const { expDigits, expSign, source, style } = notation;
opt.notation = style;
if ((expDigits && expDigits > 1) ||
(expSign && expSign !== 'sign-auto'))
fail(style, source);
break;
}
}
}
if (integerWidth) {
const { min, max, source } = integerWidth;
if (min > 0)
opt.minimumIntegerDigits = min;
if (Number(max) > 0) {
const hasExp = opt.notation === 'engineering' || opt.notation === 'scientific';
if (max === 3 && hasExp)
opt.notation = 'engineering';
else
fail('integer-width', source);
}
}
switch (sign) {
case 'sign-auto':
opt.signDisplay = 'auto';
break;
case 'sign-always':
opt.signDisplay = 'always';
break;
case 'sign-except-zero':
opt.signDisplay = 'exceptZero';
break;
case 'sign-never':
opt.signDisplay = 'never';
break;
case 'sign-accounting':
opt.currencySign = 'accounting';
break;
case 'sign-accounting-always':
opt.currencySign = 'accounting';
opt.signDisplay = 'always';
break;
case 'sign-accounting-except-zero':
opt.currencySign = 'accounting';
opt.signDisplay = 'exceptZero';
break;
}
if (decimal === 'decimal-always')
fail(decimal);
if (roundingMode)
fail(roundingMode);
return opt;
}