UNPKG

@universis/number-format

Version:

Universis library for converting number to text

161 lines (153 loc) 6.13 kB
import {LOCALES} from './locales'; const registeredLocales: any = { el: LOCALES.el, en: LOCALES.en }; export class NumberFormatter { /** * Registers the given local configuration * @param locale * @param configuration */ static registerLocale(locale: string, configuration: any) { Object.defineProperty(registeredLocales, locale, { configurable: true, enumerable: true, writable: true, value: configuration }) } /** * Creates a new instance of NumberFormatter class */ static create(): NumberFormatter { return new NumberFormatter(); } protected formatIntegerPart(number: number, locale: string) { // format number const currentLocale = registeredLocales[locale]; let result = ''; let nextValue = number; const keys = currentLocale.values.map( (key) => { return key[0]; }); // if nextValue is zero return zero string if (nextValue === 0) { const findZero = currentLocale.values.find( (key) => { return key[0] === '0'; }); if (findZero) { result = findZero[1]; } return result; } // enumerate formatter keys for (let index = 0; index < keys.length; index++) { const key = keys[index]; // convert property value to number const divider = parseFloat(key); // divide object const nextResult = Math.floor(nextValue / divider); if (nextResult >= 1) { if (nextResult > 1) { result += " "; result += this.formatIntegerPart(nextResult, locale); } const value = currentLocale.values[index][1]; if (typeof value === 'string') { result += " "; result += value; } else if (typeof value === 'object') { result += " "; if (nextResult === 1 && nextValue > divider && Object.prototype.hasOwnProperty.call(value, 'more')) { result += value.more; } else { result += nextResult === 1 ? value.one : value.many; } } else { throw new Error('Invalid number formatter provider value.'); } } // get next value nextValue = nextValue % divider; // if value is zero break and exit if (nextValue === 0) { break; } // otherwise continue } // trim result return result.trim(); } protected formatFractionalPart(number: number, locale: string, fractionDigits: number): string { if (number < 0) { throw new Error('Fractional part of a number cannot be less than zero'); } // if number is zero if (number === 0) { // do nothing return ''; } const currentLocale = registeredLocales[locale]; // get values lower than 1 const values = currentLocale.values.map( (key) => { return parseFloat(key[0]); }); let result; // enumerate locale values for (let i = 0; i < values.length; i++) { let value = values[i]; // test value that it is lower than 1 e.g. 0.01 if (value > 0 && value < 1) { let valueText = currentLocale.values[i][0]; // if result is greater or equal to 1 if (valueText.length - 2 === fractionDigits) { // get key value let keyValue = currentLocale.values[i]; // format number e.g. 45 => a cardinal number result = this.formatIntegerPart(number, locale); result += " "; // and get key value description if (typeof keyValue === 'string') { result += keyValue[1]; } else if (typeof keyValue === 'object') { result += number === 1 ? keyValue[1].one : keyValue[1].many; } else { throw new Error('Invalid number formatter provider value.'); } break; } } } return result; } format(number: number, locale: string, fractionDigits?: number) { if (Object.prototype.hasOwnProperty.call(registeredLocales, locale) === false) { throw new Error(`The specified locale is missing for registered locales.`) } // format number const currentLocale = registeredLocales[locale]; // get integer part const numberString = fractionDigits? number.toFixed(fractionDigits) : number.toString(); const integerPart = parseInt(/^(\d+)\.?/.exec(numberString)[0]); let result = this.formatIntegerPart(integerPart, locale); const fractionalMatch = /\.(\d+)$/.exec(numberString); if (fractionalMatch) { const fractionalPart = Math.round(Number((parseFloat(fractionalMatch[0]) * Math.pow(10, fractionalMatch[1].length)).toFixed(0))); const fractionalResult = this.formatFractionalPart(fractionalPart, locale,fractionDigits ? fractionDigits : fractionalMatch[1].length ); if (fractionalResult && fractionalResult.length) { result += " "; if (currentLocale.decimalSeparator && currentLocale.decimalSeparator.length) { result += currentLocale.decimalSeparator; result += " "; } result += fractionalResult; } } if (typeof currentLocale.spellcheck === 'function') { result = currentLocale.spellcheck(result); } return result; } }