@universis/number-format
Version:
Universis library for converting number to text
161 lines (153 loc) • 6.13 kB
text/typescript
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;
}
}