2
0
mirror of https://github.com/tenrok/numeralize-ru.git synced 2026-06-17 19:21:31 +03:00

Rewritten to Typescript, added support for different module systems

This commit is contained in:
anotherpit
2023-11-10 21:21:33 -03:00
parent 8785d5ee0a
commit 558659e6f8
19 changed files with 3907 additions and 1507 deletions
+1029
View File
File diff suppressed because it is too large Load Diff
+216
View File
@@ -0,0 +1,216 @@
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;
/**
* Pluralize noun according to count, e.g. `pluralize(42, 'рубль', 'рубля', 'рублей')`
*/
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;
}
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(" ");
}
/**
* Numeralize number
*/
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(' ');
}