import { UnsupportedError } from '../errors.js'; /** * Given an input ICU NumberFormatter skeleton, does its best to construct a * corresponding `Intl.NumberFormat` options structure. * * @remarks * Some features depend on `Intl.NumberFormat` features defined in ES2020. * * @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': break; case 'precision-currency-standard': opt.trailingZeroDisplay = precision.trailingZero; 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': // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore https://github.com/microsoft/TypeScript/issues/46712 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'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore https://github.com/microsoft/TypeScript/issues/46712 opt.signDisplay = 'exceptZero'; break; } if (decimal === 'decimal-always') fail(decimal); if (roundingMode) fail(roundingMode); return opt; }