@singleton-i18n/js-core-sdk-server
Version:
A JavaScript Singleton client library for internationalization and localization that leverage data from Singleton service. The library works both for the browser and as a Node.js module.
287 lines (286 loc) • 12.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FormatterFactory = void 0;
/*
* Copyright 2019-2023 VMware, Inc.
* SPDX-License-Identifier: EPL-2.0
*/
const decimal_js_light_1 = require("decimal.js-light");
const number_format_util_1 = require("./number.format.util");
const utils_1 = require("../utils");
const DECIMAL_SEP = '.';
const ZERO_CHAR = '0';
const GROUP_SEP = ',';
const DIGIT_CHAR = '#';
const PATTERN_SEP = ';';
class Formatter {
decimal(data) {
const decimalFormats = data.numberFormats.decimalFormats, symbol = data.numberSymbols;
let formatsInfo = this.parseFormats(decimalFormats);
return (value, min, max) => {
if ((0, utils_1.isDefined)(max) || (0, utils_1.isDefined)(min)) {
formatsInfo = this.resetFormats(formatsInfo, min, max, number_format_util_1.RoundingMode.ROUND_HALF_EVEN);
}
return this.resetString(false, formatsInfo, symbol, value, max, min);
};
}
currencies(data) {
const currencyFormats = data.currencyFormats, symbol = data.numberSymbols;
return (value, currencyCode, min, max) => {
let formatsInfo = this.parseFormats(currencyFormats);
formatsInfo = this.resetCurrencyFormatsInfo(formatsInfo, data, currencyCode, min, max);
const res = this.resetString(true, formatsInfo, symbol, value, max, min);
const currencySymbol = data.currencySymbols[currencyCode] && data.currencySymbols[currencyCode].symbol
? data.currencySymbols[currencyCode].symbol
: currencyCode;
return res.replace(/\u00A4/g, currencySymbol);
};
}
percent(data) {
const percentFormats = data.numberFormats.percentFormats, symbol = data.numberSymbols;
let formatsInfo = this.parseFormats(percentFormats);
return (value, min, max) => {
if ((0, utils_1.isDefined)(max) || (0, utils_1.isDefined)(min)) {
formatsInfo = this.resetFormats(formatsInfo, min, max, number_format_util_1.RoundingMode.ROUND_HALF_EVEN);
}
value = +this.resetPercentNumber(value);
return this.resetString(false, formatsInfo, symbol, value, max, min);
};
}
plural(data) {
const decimalFormats = data.numberFormats.decimalFormats;
let formatsInfo = this.parseFormats(decimalFormats);
return (value, min, max) => {
if ((0, utils_1.isDefined)(max) || (0, utils_1.isDefined)(min)) {
formatsInfo = this.resetFormats(formatsInfo, min, max, number_format_util_1.RoundingMode.ROUND_HALF_EVEN);
}
return this.roundingNumber(value, formatsInfo.minFrac, formatsInfo.maxFrac, formatsInfo.round);
};
}
resetFormats(formats, minFracDigit, maxFracDigit, round) {
let minFraction = formats.minFrac;
let maxFraction = formats.maxFrac;
if ((0, utils_1.isDefined)(minFracDigit)) {
minFraction = Math.ceil(minFracDigit) ? minFracDigit : minFraction;
}
if ((0, utils_1.isDefined)(maxFracDigit)) {
maxFraction = Math.ceil(maxFracDigit) ? maxFracDigit : minFraction;
}
if ((0, utils_1.isDefined)(maxFracDigit) && minFraction > maxFraction) {
maxFraction = minFraction;
}
formats.minFrac = minFraction;
formats.maxFrac = maxFraction;
if ((0, utils_1.isDefined)(round)) {
formats.round = round;
}
return formats;
}
/**
* Get info from the formats
* eg: ¤#,##0.00
* return: { gSize: 3, lgSize: 3, maxFrac: 2, minFrac: 2, minInt: 1, negPre: "-¤", posPre: "¤" }
*/
parseFormats(format, minusSign = '-') {
const patternInfo = {
'minInt': 1,
'minFrac': 0,
'maxFrac': 0,
'posPre': '',
'posSuf': '',
'negPre': '',
'negSuf': '',
'gSize': 0,
'lgSize': 0,
'round': number_format_util_1.RoundingMode.ROUND_HALF_EVEN
};
const patternParts = format.split(PATTERN_SEP);
const positive = patternParts[0];
const negative = patternParts[1];
const positiveParts = positive.indexOf(DECIMAL_SEP) !== -1
? positive.split(DECIMAL_SEP)
: [
positive.substring(0, positive.lastIndexOf(ZERO_CHAR) + 1),
positive.substring(positive.lastIndexOf(ZERO_CHAR) + 1)
];
const integer = positiveParts[0], fraction = positiveParts[1] || '';
patternInfo.posPre = integer.substr(0, integer.indexOf(DIGIT_CHAR));
for (let i = 0; i < fraction.length; i++) {
const ch = fraction.charAt(i);
if (ch === ZERO_CHAR) {
patternInfo.minFrac = patternInfo.maxFrac = i + 1;
}
else if (ch === DIGIT_CHAR) {
patternInfo.maxFrac = i + 1;
}
else {
patternInfo.posSuf += ch;
}
}
const groups = integer.split(GROUP_SEP);
patternInfo.gSize = groups[1] ? groups[1].length : 0;
patternInfo.lgSize = (groups[2] || groups[1]) ? (groups[2] || groups[1]).length : 0;
if (negative) {
const trunkLen = positive.length - patternInfo.posPre.length - patternInfo.posSuf.length, pos = negative.indexOf(DIGIT_CHAR);
patternInfo.negPre = negative.substr(0, pos).replace(/'/g, '');
patternInfo.negSuf = negative.substr(pos + trunkLen).replace(/'/g, '');
}
else {
patternInfo.negPre = minusSign + patternInfo.posPre;
patternInfo.negSuf = patternInfo.posSuf;
}
return patternInfo;
}
parseNumber(numStr) {
const digits = [];
let numberOfIntegerDigits;
let i;
// Decimal point?
if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) {
numStr = numStr.replace(DECIMAL_SEP, '');
}
if (numberOfIntegerDigits < 0) {
// There was no decimal point or exponent so it is an integer.
numberOfIntegerDigits = numStr.length;
}
for (i = 0; i < numStr.length; i++) {
digits.push(+numStr.charAt(i));
}
return {
digits: digits,
integerLen: numberOfIntegerDigits
};
}
/**
* rounding number
*/
roundingNumber(number, minFrac, maxFrac, mode) {
// TODO exponent
const digists = number.toString().replace(DECIMAL_SEP, '').length;
const decimalIndex = number.toString().indexOf(DECIMAL_SEP);
const numberOfIntegerDigits = decimalIndex > -1 ? decimalIndex : digists;
const fractionLen = digists - numberOfIntegerDigits;
if (minFrac > maxFrac) {
throw new Error(`The minimum number of digits after fraction (${minFrac}) is higher than the maximum (${maxFrac}).`);
}
const newDecimal = new decimal_js_light_1.Decimal(number);
const fractionSize = Math.min(Math.max(minFrac, fractionLen), maxFrac);
const roundedNum = newDecimal.toFixed(fractionSize, mode);
return roundedNum;
}
resetCurrencyFormatsInfo(formatsInfo, data, currencyCode, min, max) {
if (!data.fractions[currencyCode]) {
return formatsInfo;
}
const minFrac = data.fractions[currencyCode][number_format_util_1.CurrenciesDataType.DIGIST] || formatsInfo.minFrac;
formatsInfo.minFrac = (0, utils_1.isDefined)(min) ? min : minFrac;
formatsInfo.maxFrac = (0, utils_1.isDefined)(max) ? max : formatsInfo.minFrac;
const rounding = data.fractions[currencyCode][number_format_util_1.CurrenciesDataType.ROUNDING];
formatsInfo.round = !rounding || rounding === '0' ? formatsInfo.round : rounding;
return formatsInfo;
}
resetPercentNumber(num) {
return new decimal_js_light_1.Decimal(num).times(100).valueOf();
}
resetString(isCurrency, formatsInfo, symbol, value, minFracDigit, maxFracDigit) {
let formattedText;
let minFraction = formatsInfo.minFrac;
let maxFraction = formatsInfo.maxFrac;
if (minFracDigit != null) {
minFraction = Math.ceil(minFracDigit) ? minFracDigit : minFraction;
}
if (maxFracDigit != null) {
maxFraction = Math.ceil(maxFracDigit) ? maxFracDigit : minFraction;
}
else if (maxFracDigit != null && minFraction > maxFraction) {
maxFraction = minFraction;
}
let numberStr = this.roundingNumber(Math.abs(value), minFraction, maxFraction, formatsInfo.round);
if (!isCurrency) {
numberStr = String(+numberStr);
}
const parsedNumber = this.parseNumber(numberStr);
let digits = parsedNumber.digits;
const integerLen = parsedNumber.integerLen;
let decimals = [];
// extract decimals digits
if (integerLen > 0) {
decimals = digits.splice(integerLen, digits.length);
}
else {
decimals = digits;
digits = [0];
}
// format the integer digits with grouping separators
const groups = [];
if (digits.length >= formatsInfo.lgSize) {
groups.unshift(digits.splice(-formatsInfo.lgSize, digits.length).join(''));
}
while (digits.length > formatsInfo.gSize) {
groups.unshift(digits.splice(-formatsInfo.gSize, digits.length).join(''));
}
if (digits.length) {
groups.unshift(digits.join(''));
}
formattedText = groups.join(symbol.group);
// append the decimal digits
if (decimals.length) {
formattedText += symbol.decimal + decimals.join('');
}
if (value < 0) {
return formatsInfo.negPre + formattedText + formatsInfo.negSuf;
}
else {
return formatsInfo.posPre + formattedText + formatsInfo.posSuf;
}
}
}
class FormatterFactory {
constructor() {
this.mapping = new Map();
this.formatter = new Formatter();
}
getFormatter(locale, type) {
if (!this.mapping.get(locale)) {
this.mapping.set(locale, new Map());
}
let formatter;
if (this.mapping.get(locale) && this.mapping.get(locale).get(type)) {
formatter = this.mapping.get(locale) && this.mapping.get(locale).get(type);
}
return formatter;
}
currencies(data, locale) {
let formatter = this.getFormatter(locale, number_format_util_1.NumberFormatTypes.CURRENCIES);
if (!formatter) {
formatter = this.formatter.currencies(data);
this.mapping.get(locale).set(number_format_util_1.NumberFormatTypes.CURRENCIES, formatter);
}
return formatter;
}
percent(data, locale) {
let formatter = this.getFormatter(locale, number_format_util_1.NumberFormatTypes.PERCENT);
if (!formatter) {
formatter = this.formatter.percent(data);
this.mapping.get(locale).set(number_format_util_1.NumberFormatTypes.PERCENT, formatter);
}
return formatter;
}
decimal(data, locale) {
let formatter = this.getFormatter(locale, number_format_util_1.NumberFormatTypes.DECIMAL);
if (!formatter) {
formatter = this.formatter.decimal(data);
this.mapping.get(locale).set(number_format_util_1.NumberFormatTypes.DECIMAL, formatter);
}
return formatter;
}
roundNumberForPlural(data, locale) {
let formatter = this.getFormatter(locale, number_format_util_1.NumberFormatTypes.PLURAL);
if (!formatter) {
formatter = this.formatter.plural(data);
this.mapping.get(locale).set(number_format_util_1.NumberFormatTypes.PLURAL, formatter);
}
return formatter;
}
}
exports.FormatterFactory = FormatterFactory;