2
0
mirror of https://github.com/tenrok/numeralize-ru.git synced 2026-05-15 11:59:43 +03:00
Files
2023-11-11 00:27:36 -03:00

310 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export enum Gender {
Masculine = 0,
Feminine = 1,
Neuter = 2
}
export enum Case {
Nominative = 0,
Genitive = 1,
Dative = 2,
Accusative = 3,
Instrumental = 4,
Prepositional = 5
}
const MINORS = [
['ноль', 'нуля', 'нулю', 'ноль', 'нулём', 'нуле'],
[
['один', 'одна', 'одно'],
['одного', 'одной', 'одного'],
['одному', 'одной', 'одному'],
[['одного', 'один'], 'одну', 'одно'],
['одним', 'одной', 'одним'],
['одном', 'одной', 'одном']
],
[
['два', 'две', 'два'],
'двух',
'двум',
[['двух', 'два'], ['двух', 'две'], 'два'],
'двумя',
'двух'
],
[
'три',
'трёх',
'трём',
[['трёх', 'три'], ['трёх', 'три'], 'три'],
'тремя',
'трёх'
],
[
'четыре',
'четырёх',
'четырём',
[['четырёх', 'четыре'], ['четырёх', 'четыре'], 'четыре'],
'четырьмя',
'четырёх'
],
['пять', 'пяти', 'пяти', 'пять', 'пятью', 'пяти'],
['шесть', 'шести', 'шести', 'шесть', 'шестью', 'шести'],
['семь', 'семи', 'семи', 'семь', 'семью', 'семи'],
['восемь', 'восьми', 'восьми', 'восемь', 'восемью', 'восьми'],
['девять', 'девяти', 'девяти', 'девять', 'девятью', 'девяти'],
['десять', 'десяти', 'десяти', 'десять', 'десятью', 'десяти'],
...['один', 'две', 'три', 'четыр', 'пят', 'шест', 'сем', 'восем', 'девят'].map((prefix) =>
['надцать', 'надцати', 'надцати', 'надцать', 'надцатью', 'надцати'].map((suffix) =>
`${prefix}${suffix}`
)
)
] as const;
const TENS = [
false,
false,
['двадцать', 'двадцати', 'двадцати', 'двадцать', 'двадцатью', 'двадцати'],
['тридцать', 'тридцати', 'тридцати', 'тридцать', 'тридцатью', 'тридцати'],
['сорок', 'сорока', 'сорока', 'сорок', 'сорока', 'сорока'],
['пятьдесят', 'пятидесяти', 'пятидесяти', 'пятьдесят', 'пятьюдесятью', 'пятидесяти'],
['шестьдесят', 'шестидесяти', 'шестидесяти', 'шестьдесят', 'шестьюдесятью', 'шестидесяти'],
['семьдесят', 'семидесяти', 'семидесяти', 'семьдесят', 'семьюдесятью', 'семидесяти'],
['восемьдесят', 'восьмидесяти', 'восьмидесяти', 'восемьдесят', 'восемьюдесятью', 'восьмидесяти'],
['девяносто', 'девяноста', 'девяноста', 'девяносто', 'девяноста', 'девяноста']
] as const;
const HUNDREDS = [
false,
['сто', 'ста', 'ста', 'сто', 'ста', 'ста'],
['двести', 'двухсот', 'двумстам', 'двести', 'двумястами', 'двухстах'],
['триста', 'трёхсот', 'трёмстам', 'триста', 'тремястами', 'трёхстах'],
['четыреста', 'четырёхсот', 'четырёмстам', 'четыреста', 'четырьмястами', 'четырёхстах'],
['пятьсот', 'пятисот', 'пятистам', 'пятьсот', 'пятьюстами', 'пятистах'],
['шестьсот', 'шестисот', 'шестистам', 'шестьсот', 'шестьюстами', 'шестистах'],
['семьсот', 'семисот', 'семистам', 'семьсот', 'семьюстами', 'семистах'],
['восемьсот', 'восьмисот', 'восьмистам', 'восемьсот', 'восемьюстами', 'восьмистах'],
['девятьсот', 'девятисот', 'девятистам', 'девятьсот', 'девятьюстами', 'девятистах']
] as const;
const LARGES = [
false,
[
Gender.Feminine,
['тысяча', 'тысячи', 'тысяч'],
['тысячи', 'тысяч', 'тысяч'],
['тысяче', 'тысячам', 'тысячам'],
['тысячу', 'тысячи', 'тысяч'],
['тысячей', 'тысячами', 'тысячами'],
['тысяче', 'тысячах', 'тысячах']
],
...['миллион', 'миллиард', 'триллион'].map<[Gender, ...string[][]]>((base) =>
[
Gender.Masculine,
...[
['', 'а', 'ов'],
['а', 'ов', 'ов'],
['у', 'ам', 'ам'],
['', 'а', 'ов'],
['ом', 'ами', 'ами'],
['е', 'ах', 'ах']
].map((kase) => kase.map((suffix) => `${base}${suffix}`))
]
)
] as const;
function small(number: number, gender: Gender, kase: Case, animate: boolean): string {
// Zero
if (0 === number) {
return '';
}
// Collect chunks
const result: string[] = [];
// Hundreds
const hundreds = HUNDREDS[Math.floor(number / 100)];
if (hundreds) {
result.push(hundreds[kase]);
}
// Tens
const tens = TENS[Math.floor(number % 100 / 10)];
if (tens) {
result.push(tens[kase]);
}
// Minors
let minors = number % 100;
if (minors >= MINORS.length) {
minors = number % 10;
}
if (minors) {
let part;
if (
((part = MINORS[minors][kase]) && typeof part === 'string')
|| ((part = MINORS[minors][kase][gender]) && typeof part === 'string')
|| (part = MINORS[minors][kase][gender][animate ? 0 : 1])
) {
result.push(part)
}
}
// Return
return result.join(" ");
}
/**
* Выбирает нужную форму существительного в зависимости от количества.
*
* @param {number} count Количество
* @param {string} one Форма существительного для одного предмета, например, «рубль»;
* @param {string} two Форма существительного для двух предметов, например, «рубля»;
* @param {string} five Форма существительного для пяти предмета, например, «рублей»;
*
* @example
* // 'рублей'
* pluralize(0, 'рубль', 'рубля', 'рублей');
*
* @example
* // 'рубль'
* pluralize(1, 'рубль', 'рубля', 'рублей');
*
* @example
* // 'рубля'
* pluralize(2, 'рубль', 'рубля', 'рублей');
*
* @example
* // 'рублей'
* pluralize(5, 'рубль', 'рубля', 'рублей');
*
* @example
* // 'рублей'
* pluralize(11, 'рубль', 'рубля', 'рублей');
*
* @example
* // 'рубль'
* pluralize(21, 'рубль', 'рубля', 'рублей');
*
* @example
* // 'рубля'
* pluralize(22, 'рубль', 'рубля', 'рублей');
*/
export function pluralize(count: number, one: string, two: string, five: string): string {
count = Math.floor(Math.abs(count)) % 100;
if (count > 10 && count < 20) {
return five;
}
count = count % 10;
if (1 === count) {
return one;
}
if (count >= 2 && count <= 4) {
return two;
}
return five;
}
/**
* Возвращает числительное, соответствующее числу
*
* @param {number} number Целое число, для которого надо записать числительное
*
* @param {Gender} [gender=Gender.Masculine] Пол:
* - {@link Gender.Masculine} мужской (по умолчанию);
* - {@link Gender.Feminine} женский;
* - {@link Gender.Neuter} средний.
*
* @param {Case} [kase=Case.Nominative] Падеж
* (`case` является ключевым словом, поэтому не может быть использован в качестве имени переменной):
* - {@link Case.Nominative} — именительный (по умолчанию);
* - {@link Case.Genitive} — родительный;
* - {@link Case.Dative} — дательный;
* - {@link Case.Accusative} — винительный;
* - {@link Case.Instrumental} — творительный;
* - {@link Case.Prepositional} — предложный.
*
* @param {boolean} [animate=false] Являются ли перечисляемые предметы одушевлёнными
* (влияет на форму винительного падежа некоторых числительных)
*
* @example
* // мужской род, 'пять миллиардов сто двадцать два миллиона девятьсот восемьдесят одна тысяча сто двадцать один'
* numeralize(5122981121);
*
* @example
* // женский род, 'пять миллиардов сто двадцать два миллиона девятьсот восемьдесят одна тысяча сто двадцать одна'
* numeralize(5122981121, Gender.Feminine);
*
* @example
* // средний род, 'пять миллиардов сто двадцать два миллиона девятьсот восемьдесят одна тысяча сто двадцать одно'
* numeralize(5122981121, Gender.Neuter);
*
* @example
* // мужской род, именительный падеж, 'пять миллиардов сто двадцать два миллиона девятьсот восемьдесят одна тысяча сто двадцать один'
* numeralize(5122981121, Gender.Masculine, Case.Nominative);
*
* @example
* // мужской род, родительный падеж, 'пяти миллиардов ста двадцати двух миллионов девятисот восьмидесяти одной тысячи ста двадцати одного'
* numeralize(5122981121, Gender.Masculine, Case.Genitive);
*
* @example
* // мужской род, дательный падеж, 'пяти миллиардам ста двадцати двум миллионам девятистам восьмидесяти одной тысяче ста двадцати одному'
* numeralize(5122981121, Gender.Masculine, Case.Dative);
*
* @example
* // мужской род, винительный падеж, 'пять миллиардов сто двадцать два миллиона девятьсот восемьдесят одну тысячу сто двадцать один'
* numeralize(5122981121, Gender.Masculine, Case.Accusative);
*
* @example
* // мужской род, винительный падеж, одушевлённые предметы, 'пять миллиардов сто двадцать два миллиона девятьсот восемьдесят одну тысячу сто двадцать одного'
* numeralize(5122981121, Gender.Masculine, Case.Accusative, true);
*
* @example
* // мужской род, творительный падеж, 'пятью миллиардами ста двадцатью двумя миллионами девятьюстами восемьюдесятью одной тысячей ста двадцатью одним'
* numeralize(5122981121, Gender.Masculine, Case.Instrumental);
*
* @example
* // мужской род, творительный падеж, 'пяти миллиардах ста двадцати двух миллионах девятистах восьмидесяти одной тысяче ста двадцати одном'
* numeralize(5122981121, Gender.Masculine, Case.Prepositional);
*/
export function numeralize(
number: number,
gender: Gender = Gender.Masculine,
kase: Case = Case.Nominative,
animate: boolean = false
): string {
// Normalize params
number = Math.abs(parseInt(String(number), 10));
// Collect chunks
const result: string[] = [];
// Descend known powers of thousand
for (let l = LARGES.length, i = l; i >= 0; i--) {
const base = Math.pow(10, i * 3);
const current = Math.floor(number / base);
number = number % base;
if (current) {
const large = i ? LARGES[i] : null;
const numeral = small(current, large ? large[0] : gender, kase, large ? false : animate);
if (numeral) {
result.push(numeral);
if (large) {
const [, ...forms] = large
const plural = pluralize(current, forms[kase][0], forms[kase][1], forms[kase][2]);
result.push(plural);
}
}
}
}
// Zero
if (!result.length) {
result.push(MINORS[0][kase]);
}
// Return
return result.join(' ');
}