n2words
Version:
n2words converts a numerical number into a written one, supports 28 languages and has zero dependencies.
199 lines (166 loc) • 4.86 kB
JavaScript
import AbstractLanguage from '../classes/abstract-language.js';
export class N2WordsRU extends AbstractLanguage {
/** @type {boolean} */
feminine;
/** @type {object} */
ones = {
1: 'один',
2: 'два',
3: 'три',
4: 'четыре',
5: 'пять',
6: 'шесть',
7: 'семь',
8: 'восемь',
9: 'девять'
};
/** @type {object} */
onesFeminine = {
1: 'одна',
2: 'две',
3: 'три',
4: 'четыре',
5: 'пять',
6: 'шесть',
7: 'семь',
8: 'восемь',
9: 'девять'
};
/** @type {object} */
tens = {
0: 'десять',
1: 'одиннадцать',
2: 'двенадцать',
3: 'тринадцать',
4: 'четырнадцать',
5: 'пятнадцать',
6: 'шестнадцать',
7: 'семнадцать',
8: 'восемнадцать',
9: 'девятнадцать'
};
/** @type {object} */
twenties = {
2: 'двадцать',
3: 'тридцать',
4: 'сорок',
5: 'пятьдесят',
6: 'шестьдесят',
7: 'семьдесят',
8: 'восемьдесят',
9: 'девяносто'
};
/** @type {object} */
hundreds = {
1: 'сто',
2: 'двести',
3: 'триста',
4: 'четыреста',
5: 'пятьсот',
6: 'шестьсот',
7: 'семьсот',
8: 'восемьсот',
9: 'девятьсот'
};
/** @type {object} */
thousands = {
1: ['тысяча', 'тысячи', 'тысяч'], // 10^ 3
2: ['миллион', 'миллиона', 'миллионов'], // 10^ 6
3: ['миллиард', 'миллиарда', 'миллиардов'], // 10^ 9
4: ['триллион', 'триллиона', 'триллионов'], // 10^ 12
5: ['квадриллион', 'квадриллиона', 'квадриллионов'], // 10^ 15
6: ['квинтиллион', 'квинтиллиона', 'квинтиллионов'], // 10^ 18
7: ['секстиллион', 'секстиллиона', 'секстиллионов'], // 10^ 21
8: ['септиллион', 'септиллиона', 'септиллионов'], // 10^ 24
9: ['октиллион', 'октиллиона', 'октиллионов'], // 10^ 27
10: ['нониллион', 'нониллиона', 'нониллионов'], // 10^ 30
};
constructor(options) {
options = Object.assign({
negativeWord: 'минус',
separatorWord: 'запятая',
zero: 'ноль',
feminine: false
}, options);
super(options);
this.feminine = options.feminine;
}
toCardinal(number) {
if (number == 0) {
return this.zero;
}
const words = [];
const chunks = this.splitByX(number.toString(), 3);
let index = chunks.length;
for (const x of chunks) {
let ones = [];
index = index - 1;
if (x == 0) {
continue;
}
const [n1, n2, n3] = this.getDigits(x);
if (n3 > 0) {
words.push(this.hundreds[n3]);
}
if (n2 > 1) {
words.push(this.twenties[n2]);
}
if (n2 == 1) {
words.push(this.tens[n1]);
} else if (n1 > 0) {
ones = (index == 1 || this.feminine && index == 0) ? this.onesFeminine : this.ones;
words.push(ones[n1]);
}
if (index > 0) {
words.push(this.pluralize(x, this.thousands[index]));
}
}
return words.join(' ');
}
splitByX(n, x) {
const results = [];
const l = n.length;
let result;
if (l > x) {
const start = l % x;
if (start > 0) {
result = n.slice(0, start);
results.push(BigInt(result));
}
for (let index = start; index < l; index += x) {
result = n.slice(index, index + x);
results.push(BigInt(result));
}
} else {
results.push(BigInt(n));
}
return results;
}
getDigits(value) {
// TODO ESLint suggestion
// eslint-disable-next-line unicorn/no-useless-spread
const a = [...value.toString().padStart(3, '0').slice(-3)].toReversed();
return a.map(BigInt);
}
pluralize(n, forms) {
let form = 2;
if ((n % 100n < 10n) || (n % 100n > 20n)) {
if (n % 10n == 1n) {
form = 0;
} else if ((n % 10n < 5n) && (n % 10n > 1n)) {
form = 1;
}
}
return forms[form];
}
}
/**
* Converts a value to cardinal (written) form.
* @param {number|string|bigint} value Number to be convert.
* @param {object} [options] Options for class.
* @returns {string} Value in cardinal (written) format.
* @throws {Error} Value cannot be invalid.
*/
export default function floatToCardinal (value, options = {}) {
return new N2WordsRU(options).floatToCardinal(value);
}