numberstring 0.2.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +49 -0
- package/LICENSE +21 -21
- package/README.md +349 -27
- package/index.js +700 -196
- package/languages/ar.js +128 -0
- package/languages/da.js +103 -0
- package/languages/de.js +103 -0
- package/languages/en.js +73 -0
- package/languages/es.js +106 -0
- package/languages/fi.js +103 -0
- package/languages/fr.js +99 -0
- package/languages/hi.js +124 -0
- package/languages/id.js +105 -0
- package/languages/index.js +131 -0
- package/languages/is.js +103 -0
- package/languages/it.js +129 -0
- package/languages/ja.js +122 -0
- package/languages/ko.js +122 -0
- package/languages/nl.js +112 -0
- package/languages/no.js +103 -0
- package/languages/pl.js +146 -0
- package/languages/pt.js +145 -0
- package/languages/ru.js +125 -0
- package/languages/sv.js +103 -0
- package/languages/th.js +123 -0
- package/languages/tr.js +102 -0
- package/languages/zh.js +116 -0
- package/package.json +27 -11
- package/.npmignore +0 -42
- package/npm-shrinkwrap.json +0 -1466
package/languages/pt.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portuguese number-to-words converter
|
|
3
|
+
* @module languages/pt
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const PT_ONES = Object.freeze(['', 'um', 'dois', 'três', 'quatro', 'cinco', 'seis', 'sete', 'oito', 'nove']);
|
|
7
|
+
const PT_TENS = Object.freeze(['', '', 'vinte', 'trinta', 'quarenta', 'cinquenta', 'sessenta', 'setenta', 'oitenta', 'noventa']);
|
|
8
|
+
const PT_TEENS = Object.freeze(['dez', 'onze', 'doze', 'treze', 'catorze', 'quinze', 'dezesseis', 'dezessete', 'dezoito', 'dezenove']);
|
|
9
|
+
const PT_HUNDREDS = Object.freeze(['', 'cento', 'duzentos', 'trezentos', 'quatrocentos', 'quinhentos', 'seiscentos', 'setecentos', 'oitocentos', 'novecentos']);
|
|
10
|
+
const PT_ILLIONS = Object.freeze(['', 'mil', 'milhão', 'bilhão', 'trilhão', 'quatrilhão', 'quintilhão', 'sextilhão', 'septilhão', 'octilhão', 'nonilhão', 'decilhão']);
|
|
11
|
+
const PT_ILLIONS_PLURAL = Object.freeze(['', 'mil', 'milhões', 'bilhões', 'trilhões', 'quatrilhões', 'quintilhões', 'sextilhões', 'septilhões', 'octilhões', 'nonilhões', 'decilhões']);
|
|
12
|
+
|
|
13
|
+
const MAX_VALUE = 10n ** 36n - 1n;
|
|
14
|
+
|
|
15
|
+
const group = (n) => Math.ceil(n.toString().length / 3) - 1;
|
|
16
|
+
const power = (g) => 10n ** BigInt(g * 3);
|
|
17
|
+
const segment = (n, g) => n % power(g + 1);
|
|
18
|
+
const hundment = (n, g) => Number(segment(n, g) / power(g));
|
|
19
|
+
|
|
20
|
+
const hundredPt = (n) => {
|
|
21
|
+
if (n < 100 || n >= 1000) return '';
|
|
22
|
+
const h = Math.floor(n / 100);
|
|
23
|
+
const remainder = n % 100;
|
|
24
|
+
// "cem" when exactly 100, "cento" otherwise
|
|
25
|
+
if (h === 1 && remainder === 0) return 'cem';
|
|
26
|
+
return PT_HUNDREDS[h];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const tenPt = (n) => {
|
|
30
|
+
if (n === 0) return '';
|
|
31
|
+
if (n < 10) return PT_ONES[n];
|
|
32
|
+
if (n < 20) return PT_TEENS[n - 10];
|
|
33
|
+
const onesDigit = n % 10;
|
|
34
|
+
if (onesDigit) return `${PT_TENS[Math.floor(n / 10)]} e ${PT_ONES[onesDigit]}`;
|
|
35
|
+
return PT_TENS[Math.floor(n / 10)];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const cap = (str, style) => {
|
|
39
|
+
switch (style) {
|
|
40
|
+
case 'title':
|
|
41
|
+
return str.replace(/\w([^-\s]*)/g, (txt) =>
|
|
42
|
+
txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase()
|
|
43
|
+
);
|
|
44
|
+
case 'upper': return str.toUpperCase();
|
|
45
|
+
case 'lower': return str.toLowerCase();
|
|
46
|
+
default: return str;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Convert a number to Portuguese words
|
|
52
|
+
* @param {number|bigint} n - The number to convert
|
|
53
|
+
* @param {Object} [opt] - Options object
|
|
54
|
+
* @returns {string|false} The Portuguese word representation
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* portuguese(42) // 'quarenta e dois'
|
|
58
|
+
* portuguese(1000) // 'mil'
|
|
59
|
+
*/
|
|
60
|
+
const portuguese = (n, opt) => {
|
|
61
|
+
let num;
|
|
62
|
+
|
|
63
|
+
if (typeof n === 'bigint') {
|
|
64
|
+
if (n < 0n || n > MAX_VALUE) return false;
|
|
65
|
+
num = n;
|
|
66
|
+
} else if (typeof n === 'number') {
|
|
67
|
+
if (isNaN(n) || n < 0 || !Number.isInteger(n)) return false;
|
|
68
|
+
if (n > Number.MAX_SAFE_INTEGER) return false;
|
|
69
|
+
num = BigInt(n);
|
|
70
|
+
} else {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (num === 0n) return 'zero';
|
|
75
|
+
if (num === 1n) return 'um';
|
|
76
|
+
|
|
77
|
+
const parts = [];
|
|
78
|
+
|
|
79
|
+
for (let i = group(num); i >= 0; i--) {
|
|
80
|
+
const h = hundment(num, i);
|
|
81
|
+
if (h > 0) {
|
|
82
|
+
let part = '';
|
|
83
|
+
|
|
84
|
+
const hundreds = Math.floor(h / 100);
|
|
85
|
+
const tens = h % 100;
|
|
86
|
+
|
|
87
|
+
if (hundreds > 0) {
|
|
88
|
+
part += hundredPt(h);
|
|
89
|
+
if (tens > 0) part += ' e ';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (tens > 0) {
|
|
93
|
+
part += tenPt(tens);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (i > 0) {
|
|
97
|
+
// Add scale word (mil, milhão, etc.)
|
|
98
|
+
if (i === 1) {
|
|
99
|
+
// "mil" doesn't change for plural and doesn't need "um" before it
|
|
100
|
+
if (h === 1) {
|
|
101
|
+
part = 'mil';
|
|
102
|
+
} else {
|
|
103
|
+
part += ' mil';
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// milhão, bilhão, etc. - use singular for 1, plural for others
|
|
107
|
+
const scaleWord = h === 1 ? PT_ILLIONS[i] : PT_ILLIONS_PLURAL[i];
|
|
108
|
+
part += ` ${scaleWord}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
parts.push(part);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Join parts with appropriate connectors
|
|
117
|
+
let result = '';
|
|
118
|
+
for (let i = 0; i < parts.length; i++) {
|
|
119
|
+
if (i > 0) {
|
|
120
|
+
const prevPart = parts[i - 1];
|
|
121
|
+
const currPart = parts[i];
|
|
122
|
+
|
|
123
|
+
// Use "e" (and) for connecting in Portuguese when:
|
|
124
|
+
// 1. Current part has no scale word (final small numbers)
|
|
125
|
+
// 2. Previous part has a higher scale (milhão+) and current part has "mil"
|
|
126
|
+
const prevHasHigherScale = prevPart && (prevPart.includes('ilhão') || prevPart.includes('ilhões'));
|
|
127
|
+
const currHasMil = currPart && currPart.includes('mil');
|
|
128
|
+
const currHasNoScale = currPart && !currHasMil && !currPart.includes('ilhão') && !currPart.includes('ilhões');
|
|
129
|
+
|
|
130
|
+
if (currHasNoScale || (prevHasHigherScale && currHasMil)) {
|
|
131
|
+
result += ' e ';
|
|
132
|
+
} else {
|
|
133
|
+
result += ' ';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
result += parts[i];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
result = result.trim();
|
|
140
|
+
if (opt?.cap) result = cap(result, opt.cap);
|
|
141
|
+
return result;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default portuguese;
|
|
145
|
+
export { portuguese };
|
package/languages/ru.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Russian number-to-words converter
|
|
3
|
+
* Handles Russian plural forms (1, 2-4, 5-20/0)
|
|
4
|
+
* @module languages/ru
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const RU_ONES = Object.freeze(['', 'один', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять']);
|
|
8
|
+
const RU_ONES_FEM = Object.freeze(['', 'одна', 'две', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять']);
|
|
9
|
+
const RU_TENS = Object.freeze(['', '', 'двадцать', 'тридцать', 'сорок', 'пятьдесят', 'шестьдесят', 'семьдесят', 'восемьдесят', 'девяносто']);
|
|
10
|
+
const RU_TEENS = Object.freeze(['десять', 'одиннадцать', 'двенадцать', 'тринадцать', 'четырнадцать', 'пятнадцать', 'шестнадцать', 'семнадцать', 'восемнадцать', 'девятнадцать']);
|
|
11
|
+
const RU_HUNDREDS = Object.freeze(['', 'сто', 'двести', 'триста', 'четыреста', 'пятьсот', 'шестьсот', 'семьсот', 'восемьсот', 'девятьсот']);
|
|
12
|
+
const RU_ILLIONS = Object.freeze([
|
|
13
|
+
['', '', ''], // ones
|
|
14
|
+
['тысяча', 'тысячи', 'тысяч'], // thousands (feminine)
|
|
15
|
+
['миллион', 'миллиона', 'миллионов'], // millions
|
|
16
|
+
['миллиард', 'миллиарда', 'миллиардов'], // billions
|
|
17
|
+
['триллион', 'триллиона', 'триллионов'], // trillions
|
|
18
|
+
['квадриллион', 'квадриллиона', 'квадриллионов'], // quadrillions
|
|
19
|
+
['квинтиллион', 'квинтиллиона', 'квинтиллионов'], // quintillions
|
|
20
|
+
['секстиллион', 'секстиллиона', 'секстиллионов'], // sextillions
|
|
21
|
+
['септиллион', 'септиллиона', 'септиллионов'], // septillions
|
|
22
|
+
['октиллион', 'октиллиона', 'октиллионов'], // octillions
|
|
23
|
+
['нониллион', 'нониллиона', 'нониллионов'], // nonillions
|
|
24
|
+
['дециллион', 'дециллиона', 'дециллионов'] // decillions
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
const MAX_VALUE = 10n ** 36n - 1n;
|
|
28
|
+
|
|
29
|
+
const group = (n) => Math.ceil(n.toString().length / 3) - 1;
|
|
30
|
+
const power = (g) => 10n ** BigInt(g * 3);
|
|
31
|
+
const segment = (n, g) => n % power(g + 1);
|
|
32
|
+
const hundment = (n, g) => Number(segment(n, g) / power(g));
|
|
33
|
+
const tenment = (n, g) => hundment(n, g) % 100;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the correct Russian plural form based on number
|
|
37
|
+
* Russian has 3 forms: 1, 2-4, 5-20/0
|
|
38
|
+
*/
|
|
39
|
+
const getRuPlural = (n, forms) => {
|
|
40
|
+
const abs = Math.abs(n);
|
|
41
|
+
const lastTwo = abs % 100;
|
|
42
|
+
const lastOne = abs % 10;
|
|
43
|
+
|
|
44
|
+
if (lastTwo >= 11 && lastTwo <= 19) {
|
|
45
|
+
return forms[2];
|
|
46
|
+
}
|
|
47
|
+
if (lastOne === 1) {
|
|
48
|
+
return forms[0];
|
|
49
|
+
}
|
|
50
|
+
if (lastOne >= 2 && lastOne <= 4) {
|
|
51
|
+
return forms[1];
|
|
52
|
+
}
|
|
53
|
+
return forms[2];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const tenRu = (n, feminine = false) => {
|
|
57
|
+
if (n === 0) return '';
|
|
58
|
+
if (n < 10) return feminine ? RU_ONES_FEM[n] : RU_ONES[n];
|
|
59
|
+
if (n < 20) return RU_TEENS[n - 10];
|
|
60
|
+
const onesDigit = n % 10;
|
|
61
|
+
const tensDigit = Math.floor(n / 10);
|
|
62
|
+
if (onesDigit === 0) return RU_TENS[tensDigit];
|
|
63
|
+
const oneWord = feminine ? RU_ONES_FEM[onesDigit] : RU_ONES[onesDigit];
|
|
64
|
+
return `${RU_TENS[tensDigit]} ${oneWord}`;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const hundredRu = (n) => {
|
|
68
|
+
if (n < 100 || n >= 1000) return '';
|
|
69
|
+
return RU_HUNDREDS[Math.floor(n / 100)];
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Convert a number to Russian words
|
|
74
|
+
* @param {number|bigint} n - The number to convert
|
|
75
|
+
* @returns {string|false} The Russian word representation
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* russian(42) // 'сорок два'
|
|
79
|
+
* russian(1000) // 'одна тысяча'
|
|
80
|
+
* russian(2000) // 'две тысячи'
|
|
81
|
+
*/
|
|
82
|
+
const russian = (n) => {
|
|
83
|
+
let num;
|
|
84
|
+
|
|
85
|
+
if (typeof n === 'bigint') {
|
|
86
|
+
if (n < 0n || n > MAX_VALUE) return false;
|
|
87
|
+
num = n;
|
|
88
|
+
} else if (typeof n === 'number') {
|
|
89
|
+
if (isNaN(n) || n < 0 || !Number.isInteger(n)) return false;
|
|
90
|
+
if (n > Number.MAX_SAFE_INTEGER) return false;
|
|
91
|
+
num = BigInt(n);
|
|
92
|
+
} else {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (num === 0n) return 'ноль';
|
|
97
|
+
|
|
98
|
+
let s = '';
|
|
99
|
+
for (let i = group(num); i >= 0; i--) {
|
|
100
|
+
const h = hundment(num, i);
|
|
101
|
+
if (h > 0) {
|
|
102
|
+
const isFeminine = i === 1;
|
|
103
|
+
|
|
104
|
+
const hund = hundredRu(h);
|
|
105
|
+
if (hund) s += hund + ' ';
|
|
106
|
+
|
|
107
|
+
const t = tenment(num, i);
|
|
108
|
+
const tenWord = tenRu(t, isFeminine);
|
|
109
|
+
if (tenWord) s += tenWord + ' ';
|
|
110
|
+
|
|
111
|
+
if (i > 0) {
|
|
112
|
+
const illionForms = RU_ILLIONS[i];
|
|
113
|
+
if (illionForms && illionForms[0]) {
|
|
114
|
+
const illion = getRuPlural(t || h, illionForms);
|
|
115
|
+
s += illion + ' ';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return s.trim().replace(/\s+/g, ' ');
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export default russian;
|
|
125
|
+
export { russian };
|
package/languages/sv.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swedish number-to-words converter
|
|
3
|
+
* Swedish uses long scale (miljon, miljard, biljon, etc.)
|
|
4
|
+
* @module languages/sv
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const SV_ONES = Object.freeze(['', 'en', 'två', 'tre', 'fyra', 'fem', 'sex', 'sju', 'åtta', 'nio']);
|
|
8
|
+
const SV_TENS = Object.freeze(['', '', 'tjugo', 'trettio', 'fyrtio', 'femtio', 'sextio', 'sjuttio', 'åttio', 'nittio']);
|
|
9
|
+
const SV_TEENS = Object.freeze(['tio', 'elva', 'tolv', 'tretton', 'fjorton', 'femton', 'sexton', 'sjutton', 'arton', 'nitton']);
|
|
10
|
+
const SV_ILLIONS = Object.freeze(['', 'tusen', 'miljon', 'miljard', 'biljon', 'biljard', 'triljon', 'triljard', 'kvadriljon', 'kvadriljard', 'kvintiljon', 'kvintiljard']);
|
|
11
|
+
const SV_ILLIONS_PLURAL = Object.freeze(['', 'tusen', 'miljoner', 'miljarder', 'biljoner', 'biljarder', 'triljoner', 'triljarder', 'kvadriljoner', 'kvadriljarder', 'kvintiljoner', 'kvintiljarder']);
|
|
12
|
+
|
|
13
|
+
const MAX_VALUE = 10n ** 36n - 1n;
|
|
14
|
+
|
|
15
|
+
const group = (n) => Math.ceil(n.toString().length / 3) - 1;
|
|
16
|
+
const power = (g) => 10n ** BigInt(g * 3);
|
|
17
|
+
const segment = (n, g) => n % power(g + 1);
|
|
18
|
+
const hundment = (n, g) => Number(segment(n, g) / power(g));
|
|
19
|
+
const tenment = (n, g) => hundment(n, g) % 100;
|
|
20
|
+
|
|
21
|
+
const tenSv = (n) => {
|
|
22
|
+
if (n === 0) return '';
|
|
23
|
+
if (n < 10) return SV_ONES[n];
|
|
24
|
+
if (n < 20) return SV_TEENS[n - 10];
|
|
25
|
+
const onesDigit = n % 10;
|
|
26
|
+
const tensDigit = Math.floor(n / 10);
|
|
27
|
+
if (onesDigit === 0) return SV_TENS[tensDigit];
|
|
28
|
+
return `${SV_TENS[tensDigit]}${SV_ONES[onesDigit]}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const hundredSv = (n) => {
|
|
32
|
+
if (n < 100 || n >= 1000) return '';
|
|
33
|
+
const h = Math.floor(n / 100);
|
|
34
|
+
if (h === 1) return 'etthundra';
|
|
35
|
+
return `${SV_ONES[h]}hundra`;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convert a number to Swedish words
|
|
40
|
+
* @param {number|bigint} n - The number to convert
|
|
41
|
+
* @returns {string|false} The Swedish word representation
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* swedish(42) // 'fyrtiotvå'
|
|
45
|
+
* swedish(1000) // 'ettusen'
|
|
46
|
+
*/
|
|
47
|
+
const swedish = (n) => {
|
|
48
|
+
let num;
|
|
49
|
+
|
|
50
|
+
if (typeof n === 'bigint') {
|
|
51
|
+
if (n < 0n || n > MAX_VALUE) return false;
|
|
52
|
+
num = n;
|
|
53
|
+
} else if (typeof n === 'number') {
|
|
54
|
+
if (isNaN(n) || n < 0 || !Number.isInteger(n)) return false;
|
|
55
|
+
if (n > Number.MAX_SAFE_INTEGER) return false;
|
|
56
|
+
num = BigInt(n);
|
|
57
|
+
} else {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (num === 0n) return 'noll';
|
|
62
|
+
if (num === 1n) return 'en';
|
|
63
|
+
|
|
64
|
+
let s = '';
|
|
65
|
+
for (let i = group(num); i >= 0; i--) {
|
|
66
|
+
const h = hundment(num, i);
|
|
67
|
+
if (h > 0) {
|
|
68
|
+
if (i === 0) {
|
|
69
|
+
if (h >= 100) s += hundredSv(h) + ' ';
|
|
70
|
+
const t = tenment(num, i);
|
|
71
|
+
if (t > 0) s += tenSv(t);
|
|
72
|
+
} else if (i === 1) {
|
|
73
|
+
if (h === 1) {
|
|
74
|
+
s += 'ettusen ';
|
|
75
|
+
} else {
|
|
76
|
+
if (h >= 100) s += hundredSv(h) + ' ';
|
|
77
|
+
const t = tenment(num, i);
|
|
78
|
+
if (t > 0) s += tenSv(t);
|
|
79
|
+
s += 'tusen ';
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
if (h >= 100) s += hundredSv(h) + ' ';
|
|
83
|
+
const t = tenment(num, i);
|
|
84
|
+
if (t > 0) {
|
|
85
|
+
if (t === 1) {
|
|
86
|
+
s += 'en ';
|
|
87
|
+
} else {
|
|
88
|
+
s += tenSv(t) + ' ';
|
|
89
|
+
}
|
|
90
|
+
} else if (h < 100 && h >= 1 && h === 1) {
|
|
91
|
+
s += 'en ';
|
|
92
|
+
}
|
|
93
|
+
const illionWord = h === 1 ? SV_ILLIONS[i] : SV_ILLIONS_PLURAL[i];
|
|
94
|
+
s += `${illionWord} `;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return s.trim();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export default swedish;
|
|
103
|
+
export { swedish };
|
package/languages/th.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thai number-to-words converter
|
|
3
|
+
* Uses the lan (ล้าน) system for grouping by 1,000,000
|
|
4
|
+
* @module languages/th
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const TH_ONES = Object.freeze(['', 'หนึ่ง', 'สอง', 'สาม', 'สี่', 'ห้า', 'หก', 'เจ็ด', 'แปด', 'เก้า']);
|
|
8
|
+
const TH_POSITIONS = Object.freeze(['', 'สิบ', 'ร้อย', 'พัน', 'หมื่น', 'แสน']);
|
|
9
|
+
|
|
10
|
+
/** Maximum supported value (10^36 - 1) */
|
|
11
|
+
const MAX_VALUE = 10n ** 36n - 1n;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert a group of up to 6 digits (0-999999) to Thai
|
|
16
|
+
* @param {number} grp - The group value (0-999999)
|
|
17
|
+
* @returns {string} The Thai representation
|
|
18
|
+
*/
|
|
19
|
+
const groupToTh = (grp) => {
|
|
20
|
+
if (grp === 0) return '';
|
|
21
|
+
|
|
22
|
+
let result = '';
|
|
23
|
+
const digits = [];
|
|
24
|
+
|
|
25
|
+
// Extract digits from right to left (position 0 = ones, 5 = hundred-thousands)
|
|
26
|
+
let temp = grp;
|
|
27
|
+
for (let i = 0; i < 6; i++) {
|
|
28
|
+
digits[i] = temp % 10;
|
|
29
|
+
temp = Math.floor(temp / 10);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Process from highest position to lowest
|
|
33
|
+
for (let pos = 5; pos >= 0; pos--) {
|
|
34
|
+
const d = digits[pos];
|
|
35
|
+
if (d === 0) continue;
|
|
36
|
+
|
|
37
|
+
if (pos === 0) {
|
|
38
|
+
// Ones place: use เอ็ด when tens digit or higher exists
|
|
39
|
+
if (d === 1 && grp > 1) {
|
|
40
|
+
result += 'เอ็ด';
|
|
41
|
+
} else {
|
|
42
|
+
result += TH_ONES[d];
|
|
43
|
+
}
|
|
44
|
+
} else if (pos === 1) {
|
|
45
|
+
// Tens place
|
|
46
|
+
if (d === 1) {
|
|
47
|
+
// 1 in tens place: just สิบ, not หนึ่งสิบ
|
|
48
|
+
result += 'สิบ';
|
|
49
|
+
} else if (d === 2) {
|
|
50
|
+
// 2 in tens place: ยี่สิบ, not สองสิบ
|
|
51
|
+
result += 'ยี่สิบ';
|
|
52
|
+
} else {
|
|
53
|
+
result += TH_ONES[d] + 'สิบ';
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
// Hundreds, thousands, ten-thousands, hundred-thousands
|
|
57
|
+
result += TH_ONES[d] + TH_POSITIONS[pos];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Convert a number to Thai words
|
|
66
|
+
* @param {number|bigint} n - The number to convert
|
|
67
|
+
* @returns {string|false} The Thai word representation
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* thai(42) // 'สี่สิบสอง'
|
|
71
|
+
* thai(11) // 'สิบเอ็ด'
|
|
72
|
+
* thai(1000000) // 'หนึ่งล้าน'
|
|
73
|
+
*/
|
|
74
|
+
const thai = (n) => {
|
|
75
|
+
let num;
|
|
76
|
+
|
|
77
|
+
if (typeof n === 'bigint') {
|
|
78
|
+
if (n < 0n || n > MAX_VALUE) return false;
|
|
79
|
+
num = n;
|
|
80
|
+
} else if (typeof n === 'number') {
|
|
81
|
+
if (isNaN(n) || n < 0 || !Number.isInteger(n)) return false;
|
|
82
|
+
if (n > Number.MAX_SAFE_INTEGER) return false;
|
|
83
|
+
num = BigInt(n);
|
|
84
|
+
} else {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (num === 0n) return 'ศูนย์';
|
|
89
|
+
|
|
90
|
+
const str = num.toString();
|
|
91
|
+
const len = str.length;
|
|
92
|
+
|
|
93
|
+
// Split into groups of 6 from the right (ล้าน = million grouping)
|
|
94
|
+
const groups = [];
|
|
95
|
+
for (let i = len; i > 0; i -= 6) {
|
|
96
|
+
const start = Math.max(0, i - 6);
|
|
97
|
+
groups.unshift(str.slice(start, i));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let result = '';
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < groups.length; i++) {
|
|
103
|
+
const grp = parseInt(groups[i], 10);
|
|
104
|
+
const grpIdx = groups.length - 1 - i;
|
|
105
|
+
|
|
106
|
+
if (grp === 0) continue;
|
|
107
|
+
|
|
108
|
+
const grpStr = groupToTh(grp);
|
|
109
|
+
result += grpStr;
|
|
110
|
+
|
|
111
|
+
// Append ล้าน scale for each group above the lowest
|
|
112
|
+
if (grpIdx > 0) {
|
|
113
|
+
for (let j = 0; j < grpIdx; j++) {
|
|
114
|
+
result += 'ล้าน';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return result;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export default thai;
|
|
123
|
+
export { thai, TH_ONES, TH_POSITIONS, MAX_VALUE };
|
package/languages/tr.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turkish number-to-words converter
|
|
3
|
+
* Turkish is very regular with simple concatenation rules
|
|
4
|
+
* @module languages/tr
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const TR_ONES = Object.freeze(['', 'bir', 'iki', 'üç', 'dört', 'beş', 'altı', 'yedi', 'sekiz', 'dokuz']);
|
|
8
|
+
const TR_TENS = Object.freeze(['', 'on', 'yirmi', 'otuz', 'kırk', 'elli', 'altmış', 'yetmiş', 'seksen', 'doksan']);
|
|
9
|
+
const TR_ILLIONS = Object.freeze(['', 'bin', 'milyon', 'milyar', 'trilyon', 'katrilyon', 'kentilyon', 'sekstilyon', 'septilyon', 'oktilyon', 'nonilyon', 'desilyon']);
|
|
10
|
+
|
|
11
|
+
/** Maximum supported value (10^36 - 1, up to decillions) */
|
|
12
|
+
const MAX_VALUE = 10n ** 36n - 1n;
|
|
13
|
+
|
|
14
|
+
const group = (n) => Math.ceil(n.toString().length / 3) - 1;
|
|
15
|
+
const power = (g) => 10n ** BigInt(g * 3);
|
|
16
|
+
const segment = (n, g) => n % power(g + 1);
|
|
17
|
+
const hundment = (n, g) => Number(segment(n, g) / power(g));
|
|
18
|
+
const tenment = (n, g) => hundment(n, g) % 100;
|
|
19
|
+
|
|
20
|
+
const hundredTr = (n) => {
|
|
21
|
+
if (n < 100 || n >= 1000) return '';
|
|
22
|
+
const h = Math.floor(n / 100);
|
|
23
|
+
// 1 before yüz is omitted: just "yüz", not "bir yüz"
|
|
24
|
+
if (h === 1) return 'yüz';
|
|
25
|
+
return `${TR_ONES[h]} yüz`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const tenTr = (n) => {
|
|
29
|
+
if (n === 0) return '';
|
|
30
|
+
if (n < 10) return TR_ONES[n];
|
|
31
|
+
const onesDigit = n % 10;
|
|
32
|
+
const tensDigit = Math.floor(n / 10);
|
|
33
|
+
if (onesDigit === 0) return TR_TENS[tensDigit];
|
|
34
|
+
return `${TR_TENS[tensDigit]} ${TR_ONES[onesDigit]}`;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Convert a number to Turkish words
|
|
39
|
+
* @param {number|bigint} n - The number to convert
|
|
40
|
+
* @returns {string|false} The Turkish word representation
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* turkish(42) // 'kırk iki'
|
|
44
|
+
* turkish(1000) // 'bin'
|
|
45
|
+
* turkish(1000000) // 'bir milyon'
|
|
46
|
+
*/
|
|
47
|
+
const turkish = (n) => {
|
|
48
|
+
let num;
|
|
49
|
+
|
|
50
|
+
if (typeof n === 'bigint') {
|
|
51
|
+
if (n < 0n || n > MAX_VALUE) return false;
|
|
52
|
+
num = n;
|
|
53
|
+
} else if (typeof n === 'number') {
|
|
54
|
+
if (isNaN(n) || n < 0 || !Number.isInteger(n)) return false;
|
|
55
|
+
if (n > Number.MAX_SAFE_INTEGER) return false;
|
|
56
|
+
num = BigInt(n);
|
|
57
|
+
} else {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (num === 0n) return 'sıfır';
|
|
62
|
+
|
|
63
|
+
let s = '';
|
|
64
|
+
for (let i = group(num); i >= 0; i--) {
|
|
65
|
+
const h = hundment(num, i);
|
|
66
|
+
if (h === 0) continue;
|
|
67
|
+
|
|
68
|
+
if (i === 0) {
|
|
69
|
+
// Ones group: no scale word
|
|
70
|
+
const hund = hundredTr(h);
|
|
71
|
+
if (hund) s += hund + ' ';
|
|
72
|
+
const t = tenment(num, i);
|
|
73
|
+
const tenWord = tenTr(t);
|
|
74
|
+
if (tenWord) s += tenWord + ' ';
|
|
75
|
+
} else if (i === 1) {
|
|
76
|
+
// Thousands: 1 before bin is omitted
|
|
77
|
+
if (h === 1) {
|
|
78
|
+
s += 'bin ';
|
|
79
|
+
} else {
|
|
80
|
+
const hund = hundredTr(h);
|
|
81
|
+
if (hund) s += hund + ' ';
|
|
82
|
+
const t = tenment(num, i);
|
|
83
|
+
const tenWord = tenTr(t);
|
|
84
|
+
if (tenWord) s += tenWord + ' ';
|
|
85
|
+
s += 'bin ';
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
// Millions and above: 1 IS included ("bir milyon")
|
|
89
|
+
const hund = hundredTr(h);
|
|
90
|
+
if (hund) s += hund + ' ';
|
|
91
|
+
const t = tenment(num, i);
|
|
92
|
+
const tenWord = tenTr(t);
|
|
93
|
+
if (tenWord) s += tenWord + ' ';
|
|
94
|
+
s += `${TR_ILLIONS[i]} `;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return s.trim();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default turkish;
|
|
102
|
+
export { turkish, TR_ONES, TR_TENS, TR_ILLIONS, MAX_VALUE };
|