numberstring 0.1.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 -22
- package/index.js +700 -156
- 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/.codeclimate.yml +0 -34
- package/.npmignore +0 -31
- package/.travis.yml +0 -32
- package/codecov.yml +0 -4
- package/npm-shrinkwrap.json +0 -1466
- package/test/test.js +0 -123
package/languages/ja.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Japanese number-to-words converter
|
|
3
|
+
* Uses the man (万) system for grouping by 10,000
|
|
4
|
+
* @module languages/ja
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const JA_DIGITS = Object.freeze(['', '一', '二', '三', '四', '五', '六', '七', '八', '九']);
|
|
8
|
+
const JA_SCALES = Object.freeze(['', '万', '億', '兆', '京', '垓', '𥝱', '穣', '溝']);
|
|
9
|
+
|
|
10
|
+
/** Maximum supported value (10^36 - 1, up to 溝) */
|
|
11
|
+
const MAX_VALUE = 10n ** 36n - 1n;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert a 4-digit group (0-9999) to Japanese
|
|
16
|
+
* @param {number} grp - The group value (0-9999)
|
|
17
|
+
* @returns {string} The Japanese representation
|
|
18
|
+
*/
|
|
19
|
+
const groupToJa = (grp) => {
|
|
20
|
+
if (grp === 0) return '';
|
|
21
|
+
|
|
22
|
+
const thousands = Math.floor(grp / 1000);
|
|
23
|
+
const hundreds = Math.floor((grp % 1000) / 100);
|
|
24
|
+
const tens = Math.floor((grp % 100) / 10);
|
|
25
|
+
const ones = grp % 10;
|
|
26
|
+
|
|
27
|
+
let result = '';
|
|
28
|
+
|
|
29
|
+
// Thousands: 1 before 千 is omitted
|
|
30
|
+
if (thousands > 0) {
|
|
31
|
+
if (thousands === 1) {
|
|
32
|
+
result += '千';
|
|
33
|
+
} else {
|
|
34
|
+
result += JA_DIGITS[thousands] + '千';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Hundreds: 1 before 百 is omitted
|
|
39
|
+
if (hundreds > 0) {
|
|
40
|
+
if (hundreds === 1) {
|
|
41
|
+
result += '百';
|
|
42
|
+
} else {
|
|
43
|
+
result += JA_DIGITS[hundreds] + '百';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Tens: 1 before 十 is omitted
|
|
48
|
+
if (tens > 0) {
|
|
49
|
+
if (tens === 1) {
|
|
50
|
+
result += '十';
|
|
51
|
+
} else {
|
|
52
|
+
result += JA_DIGITS[tens] + '十';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Ones
|
|
57
|
+
if (ones > 0) {
|
|
58
|
+
result += JA_DIGITS[ones];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Convert a number to Japanese words
|
|
66
|
+
* @param {number|bigint} n - The number to convert
|
|
67
|
+
* @returns {string|false} The Japanese word representation
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* japanese(42) // '四十二'
|
|
71
|
+
* japanese(1000) // '千'
|
|
72
|
+
* japanese(10000) // '一万'
|
|
73
|
+
*/
|
|
74
|
+
const japanese = (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 4 from the right
|
|
94
|
+
const groups = [];
|
|
95
|
+
for (let i = len; i > 0; i -= 4) {
|
|
96
|
+
const start = Math.max(0, i - 4);
|
|
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 = groupToJa(grp);
|
|
109
|
+
|
|
110
|
+
// 1 before 万 and above IS included (handled naturally by groupToJa
|
|
111
|
+
// since grp=1 produces '一' for the ones digit in the group)
|
|
112
|
+
result += grpStr;
|
|
113
|
+
if (grpIdx > 0) {
|
|
114
|
+
result += JA_SCALES[grpIdx];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default japanese;
|
|
122
|
+
export { japanese, JA_DIGITS, JA_SCALES, MAX_VALUE };
|
package/languages/ko.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Korean number-to-words converter (Sino-Korean)
|
|
3
|
+
* Uses the man (만) system for grouping by 10,000
|
|
4
|
+
* @module languages/ko
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const KO_DIGITS = Object.freeze(['', '일', '이', '삼', '사', '오', '육', '칠', '팔', '구']);
|
|
8
|
+
const KO_SCALES = Object.freeze(['', '만', '억', '조', '경', '해', '자', '양', '구']);
|
|
9
|
+
|
|
10
|
+
/** Maximum supported value (10^36 - 1) */
|
|
11
|
+
const MAX_VALUE = 10n ** 36n - 1n;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert a 4-digit group (0-9999) to Sino-Korean
|
|
16
|
+
* @param {number} grp - The group value (0-9999)
|
|
17
|
+
* @returns {string} The Korean representation
|
|
18
|
+
*/
|
|
19
|
+
const groupToKo = (grp) => {
|
|
20
|
+
if (grp === 0) return '';
|
|
21
|
+
|
|
22
|
+
const thousands = Math.floor(grp / 1000);
|
|
23
|
+
const hundreds = Math.floor((grp % 1000) / 100);
|
|
24
|
+
const tens = Math.floor((grp % 100) / 10);
|
|
25
|
+
const ones = grp % 10;
|
|
26
|
+
|
|
27
|
+
let result = '';
|
|
28
|
+
|
|
29
|
+
// Thousands: 1 before 천 is omitted
|
|
30
|
+
if (thousands > 0) {
|
|
31
|
+
if (thousands === 1) {
|
|
32
|
+
result += '천';
|
|
33
|
+
} else {
|
|
34
|
+
result += KO_DIGITS[thousands] + '천';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Hundreds: 1 before 백 is omitted
|
|
39
|
+
if (hundreds > 0) {
|
|
40
|
+
if (hundreds === 1) {
|
|
41
|
+
result += '백';
|
|
42
|
+
} else {
|
|
43
|
+
result += KO_DIGITS[hundreds] + '백';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Tens: 1 before 십 is omitted
|
|
48
|
+
if (tens > 0) {
|
|
49
|
+
if (tens === 1) {
|
|
50
|
+
result += '십';
|
|
51
|
+
} else {
|
|
52
|
+
result += KO_DIGITS[tens] + '십';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Ones
|
|
57
|
+
if (ones > 0) {
|
|
58
|
+
result += KO_DIGITS[ones];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Convert a number to Korean words (Sino-Korean system)
|
|
66
|
+
* @param {number|bigint} n - The number to convert
|
|
67
|
+
* @returns {string|false} The Korean word representation
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* korean(42) // '사십이'
|
|
71
|
+
* korean(1000) // '천'
|
|
72
|
+
* korean(10000) // '일만'
|
|
73
|
+
*/
|
|
74
|
+
const korean = (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 4 from the right
|
|
94
|
+
const groups = [];
|
|
95
|
+
for (let i = len; i > 0; i -= 4) {
|
|
96
|
+
const start = Math.max(0, i - 4);
|
|
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 = groupToKo(grp);
|
|
109
|
+
|
|
110
|
+
// 1 before 만 and above IS included (handled naturally by groupToKo
|
|
111
|
+
// since grp=1 produces '일' for the ones digit in the group)
|
|
112
|
+
result += grpStr;
|
|
113
|
+
if (grpIdx > 0) {
|
|
114
|
+
result += KO_SCALES[grpIdx];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default korean;
|
|
122
|
+
export { korean, KO_DIGITS, KO_SCALES, MAX_VALUE };
|
package/languages/nl.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dutch number-to-words converter
|
|
3
|
+
* Dutch reverses ones and tens (eenentwintig = one-and-twenty)
|
|
4
|
+
* @module languages/nl
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const NL_ONES = Object.freeze(['', 'een', 'twee', 'drie', 'vier', 'vijf', 'zes', 'zeven', 'acht', 'negen']);
|
|
8
|
+
const NL_TENS = Object.freeze(['', '', 'twintig', 'dertig', 'veertig', 'vijftig', 'zestig', 'zeventig', 'tachtig', 'negentig']);
|
|
9
|
+
const NL_TEENS = Object.freeze(['tien', 'elf', 'twaalf', 'dertien', 'veertien', 'vijftien', 'zestien', 'zeventien', 'achttien', 'negentien']);
|
|
10
|
+
const NL_ILLIONS = Object.freeze(['', 'duizend', 'miljoen', 'miljard', 'biljoen', 'biljard', 'triljoen', 'triljard', 'quadriljoen', 'quadriljard', 'quintiljoen', 'quintiljard']);
|
|
11
|
+
const NL_ILLIONS_PLURAL = Object.freeze(['', 'duizend', 'miljoen', 'miljard', 'biljoen', 'biljard', 'triljoen', 'triljard', 'quadriljoen', 'quadriljard', 'quintiljoen', 'quintiljard']);
|
|
12
|
+
|
|
13
|
+
/** Maximum supported value (10^36 - 1, up to decillions) */
|
|
14
|
+
const MAX_VALUE = 10n ** 36n - 1n;
|
|
15
|
+
|
|
16
|
+
const group = (n) => Math.ceil(n.toString().length / 3) - 1;
|
|
17
|
+
const power = (g) => 10n ** BigInt(g * 3);
|
|
18
|
+
const segment = (n, g) => n % power(g + 1);
|
|
19
|
+
const hundment = (n, g) => Number(segment(n, g) / power(g));
|
|
20
|
+
const tenment = (n, g) => hundment(n, g) % 100;
|
|
21
|
+
|
|
22
|
+
const tenNl = (n) => {
|
|
23
|
+
if (n === 0) return '';
|
|
24
|
+
if (n < 10) return NL_ONES[n];
|
|
25
|
+
if (n < 20) return NL_TEENS[n - 10];
|
|
26
|
+
const onesDigit = n % 10;
|
|
27
|
+
const tensDigit = Math.floor(n / 10);
|
|
28
|
+
if (onesDigit === 0) return NL_TENS[tensDigit];
|
|
29
|
+
// Use "en" connector between ones and tens
|
|
30
|
+
// Use "ën" after words ending in vowel-e (twee, drie)
|
|
31
|
+
const connector = (onesDigit === 2 || onesDigit === 3) ? 'ën' : 'en';
|
|
32
|
+
return `${NL_ONES[onesDigit]}${connector}${NL_TENS[tensDigit]}`;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const hundredNl = (n) => {
|
|
36
|
+
if (n < 100 || n >= 1000) return '';
|
|
37
|
+
const h = Math.floor(n / 100);
|
|
38
|
+
// 100 = "honderd" (not "eenhonderd")
|
|
39
|
+
if (h === 1) return 'honderd';
|
|
40
|
+
return `${NL_ONES[h]}honderd`;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Convert a number to Dutch words
|
|
45
|
+
* @param {number|bigint} n - The number to convert
|
|
46
|
+
* @returns {string|false} The Dutch word representation
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* dutch(42) // 'tweeënveertig'
|
|
50
|
+
* dutch(1000) // 'duizend'
|
|
51
|
+
* dutch(21) // 'eenentwintig'
|
|
52
|
+
*/
|
|
53
|
+
const dutch = (n) => {
|
|
54
|
+
let num;
|
|
55
|
+
|
|
56
|
+
if (typeof n === 'bigint') {
|
|
57
|
+
if (n < 0n || n > MAX_VALUE) return false;
|
|
58
|
+
num = n;
|
|
59
|
+
} else if (typeof n === 'number') {
|
|
60
|
+
if (isNaN(n) || n < 0 || !Number.isInteger(n)) return false;
|
|
61
|
+
if (n > Number.MAX_SAFE_INTEGER) return false;
|
|
62
|
+
num = BigInt(n);
|
|
63
|
+
} else {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (num === 0n) return 'nul';
|
|
68
|
+
|
|
69
|
+
let s = '';
|
|
70
|
+
for (let i = group(num); i >= 0; i--) {
|
|
71
|
+
const h = hundment(num, i);
|
|
72
|
+
if (h > 0) {
|
|
73
|
+
if (i === 0) {
|
|
74
|
+
// Units group
|
|
75
|
+
if (h >= 100) s += hundredNl(h);
|
|
76
|
+
const t = tenment(num, i);
|
|
77
|
+
if (t > 0) s += tenNl(t);
|
|
78
|
+
} else if (i === 1) {
|
|
79
|
+
// Thousands: 1000 = "duizend" (not "eenduizend")
|
|
80
|
+
if (h === 1) {
|
|
81
|
+
s += 'duizend';
|
|
82
|
+
} else {
|
|
83
|
+
if (h >= 100) s += hundredNl(h);
|
|
84
|
+
const t = tenment(num, i);
|
|
85
|
+
if (t > 0) s += tenNl(t);
|
|
86
|
+
s += 'duizend';
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// Millions and above: separated by spaces
|
|
90
|
+
// 1 million = "een miljoen", 2 million = "twee miljoen"
|
|
91
|
+
if (h >= 100) s += hundredNl(h);
|
|
92
|
+
const t = tenment(num, i);
|
|
93
|
+
if (t > 0) {
|
|
94
|
+
if (t === 1) {
|
|
95
|
+
s += 'een ';
|
|
96
|
+
} else {
|
|
97
|
+
s += tenNl(t) + ' ';
|
|
98
|
+
}
|
|
99
|
+
} else if (h < 100 && h === 1) {
|
|
100
|
+
s += 'een ';
|
|
101
|
+
}
|
|
102
|
+
const illionWord = h === 1 ? NL_ILLIONS[i] : NL_ILLIONS_PLURAL[i];
|
|
103
|
+
s += `${illionWord} `;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return s.trim();
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default dutch;
|
|
112
|
+
export { dutch };
|
package/languages/no.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Norwegian (Bokmål) number-to-words converter
|
|
3
|
+
* Norwegian uses long scale like Danish but with simpler tens
|
|
4
|
+
* @module languages/no
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const NO_ONES = Object.freeze(['', 'en', 'to', 'tre', 'fire', 'fem', 'seks', 'sju', 'åtte', 'ni']);
|
|
8
|
+
const NO_TENS = Object.freeze(['', '', 'tjue', 'tretti', 'førti', 'femti', 'seksti', 'sytti', 'åtti', 'nitti']);
|
|
9
|
+
const NO_TEENS = Object.freeze(['ti', 'elleve', 'tolv', 'tretten', 'fjorten', 'femten', 'seksten', 'sytten', 'atten', 'nitten']);
|
|
10
|
+
const NO_ILLIONS = Object.freeze(['', 'tusen', 'million', 'milliard', 'billion', 'billiard', 'trillion', 'trilliard', 'kvadrillion', 'kvadrilliard', 'kvintillion', 'kvintilliard']);
|
|
11
|
+
const NO_ILLIONS_PLURAL = Object.freeze(['', 'tusen', 'millioner', 'milliarder', 'billioner', 'billiarder', 'trillioner', 'trilliarder', 'kvadrillioner', 'kvadrilliarder', 'kvintillioner', 'kvintilliarder']);
|
|
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 tenNo = (n) => {
|
|
22
|
+
if (n === 0) return '';
|
|
23
|
+
if (n < 10) return NO_ONES[n];
|
|
24
|
+
if (n < 20) return NO_TEENS[n - 10];
|
|
25
|
+
const onesDigit = n % 10;
|
|
26
|
+
const tensDigit = Math.floor(n / 10);
|
|
27
|
+
if (onesDigit === 0) return NO_TENS[tensDigit];
|
|
28
|
+
return `${NO_TENS[tensDigit]}${NO_ONES[onesDigit]}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const hundredNo = (n) => {
|
|
32
|
+
if (n < 100 || n >= 1000) return '';
|
|
33
|
+
const h = Math.floor(n / 100);
|
|
34
|
+
if (h === 1) return 'etthundre';
|
|
35
|
+
return `${NO_ONES[h]}hundre`;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convert a number to Norwegian words
|
|
40
|
+
* @param {number|bigint} n - The number to convert
|
|
41
|
+
* @returns {string|false} The Norwegian word representation
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* norwegian(42) // 'førtito'
|
|
45
|
+
* norwegian(1000) // 'ettusen'
|
|
46
|
+
*/
|
|
47
|
+
const norwegian = (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 'null';
|
|
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 += hundredNo(h) + ' ';
|
|
70
|
+
const t = tenment(num, i);
|
|
71
|
+
if (t > 0) s += tenNo(t);
|
|
72
|
+
} else if (i === 1) {
|
|
73
|
+
if (h === 1) {
|
|
74
|
+
s += 'ettusen ';
|
|
75
|
+
} else {
|
|
76
|
+
if (h >= 100) s += hundredNo(h) + ' ';
|
|
77
|
+
const t = tenment(num, i);
|
|
78
|
+
if (t > 0) s += tenNo(t);
|
|
79
|
+
s += 'tusen ';
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
if (h >= 100) s += hundredNo(h) + ' ';
|
|
83
|
+
const t = tenment(num, i);
|
|
84
|
+
if (t > 0) {
|
|
85
|
+
if (t === 1) {
|
|
86
|
+
s += 'en ';
|
|
87
|
+
} else {
|
|
88
|
+
s += tenNo(t) + ' ';
|
|
89
|
+
}
|
|
90
|
+
} else if (h < 100 && h >= 1 && h === 1) {
|
|
91
|
+
s += 'en ';
|
|
92
|
+
}
|
|
93
|
+
const illionWord = h === 1 ? NO_ILLIONS[i] : NO_ILLIONS_PLURAL[i];
|
|
94
|
+
s += `${illionWord} `;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return s.trim();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export default norwegian;
|
|
103
|
+
export { norwegian };
|
package/languages/pl.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polish number-to-words converter
|
|
3
|
+
* Handles Polish plural forms (singular, 2-4, 5+)
|
|
4
|
+
* @module languages/pl
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const PL_ONES = Object.freeze(['', 'jeden', 'dwa', 'trzy', 'cztery', 'pięć', 'sześć', 'siedem', 'osiem', 'dziewięć']);
|
|
8
|
+
const PL_TENS = Object.freeze(['', '', 'dwadzieścia', 'trzydzieści', 'czterdzieści', 'pięćdziesiąt', 'sześćdziesiąt', 'siedemdziesiąt', 'osiemdziesiąt', 'dziewięćdziesiąt']);
|
|
9
|
+
const PL_TEENS = Object.freeze(['dziesięć', 'jedenaście', 'dwanaście', 'trzynaście', 'czternaście', 'piętnaście', 'szesnaście', 'siedemnaście', 'osiemnaście', 'dziewiętnaście']);
|
|
10
|
+
const PL_HUNDREDS = Object.freeze(['', 'sto', 'dwieście', 'trzysta', 'czterysta', 'pięćset', 'sześćset', 'siedemset', 'osiemset', 'dziewięćset']);
|
|
11
|
+
const PL_ILLIONS = Object.freeze([
|
|
12
|
+
['', '', ''], // ones
|
|
13
|
+
['tysiąc', 'tysiące', 'tysięcy'], // thousands
|
|
14
|
+
['milion', 'miliony', 'milionów'], // millions
|
|
15
|
+
['miliard', 'miliardy', 'miliardów'], // billions
|
|
16
|
+
['bilion', 'biliony', 'bilionów'], // trillions
|
|
17
|
+
['biliard', 'biliardy', 'biliardów'], // quadrillions
|
|
18
|
+
['trylion', 'tryliony', 'trylionów'], // quintillions
|
|
19
|
+
['tryliard', 'tryliardy', 'tryliardów'], // sextillions
|
|
20
|
+
['kwadrylion', 'kwadryliony', 'kwadrylionów'], // septillions
|
|
21
|
+
['kwadryliard', 'kwadryliardy', 'kwadryliardów'], // octillions
|
|
22
|
+
['kwintylion', 'kwintyliony', 'kwintylionów'], // nonillions
|
|
23
|
+
['kwintyliard', 'kwintyliardy', 'kwintyliardów'] // decillions
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/** Maximum supported value (10^36 - 1, up to decillions) */
|
|
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 Polish plural form based on number
|
|
37
|
+
* Polish has 3 forms: singular (1), plural 2-4, plural 5+
|
|
38
|
+
* @param {number} n - The number to check
|
|
39
|
+
* @param {string[]} forms - [singular, plural2_4, plural5_plus]
|
|
40
|
+
* @returns {string} The correct plural form
|
|
41
|
+
*/
|
|
42
|
+
const getPlPlural = (n, forms) => {
|
|
43
|
+
if (n === 1) return forms[0];
|
|
44
|
+
const lastTwo = n % 100;
|
|
45
|
+
const lastOne = n % 10;
|
|
46
|
+
if (lastTwo >= 12 && lastTwo <= 14) return forms[2];
|
|
47
|
+
if (lastOne >= 2 && lastOne <= 4) return forms[1];
|
|
48
|
+
return forms[2];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const hundredPl = (n) => {
|
|
52
|
+
if (n < 100 || n >= 1000) return '';
|
|
53
|
+
return PL_HUNDREDS[Math.floor(n / 100)];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const tenPl = (n) => {
|
|
57
|
+
if (n === 0) return '';
|
|
58
|
+
if (n < 10) return PL_ONES[n];
|
|
59
|
+
if (n < 20) return PL_TEENS[n - 10];
|
|
60
|
+
const onesDigit = n % 10;
|
|
61
|
+
const tensDigit = Math.floor(n / 10);
|
|
62
|
+
if (onesDigit === 0) return PL_TENS[tensDigit];
|
|
63
|
+
return `${PL_TENS[tensDigit]} ${PL_ONES[onesDigit]}`;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Convert a number to Polish words
|
|
68
|
+
* @param {number|bigint} n - The number to convert
|
|
69
|
+
* @returns {string|false} The Polish word representation
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* polish(42) // 'czterdzieści dwa'
|
|
73
|
+
* polish(1000) // 'tysiąc'
|
|
74
|
+
* polish(2000) // 'dwa tysiące'
|
|
75
|
+
* polish(5000) // 'pięć tysięcy'
|
|
76
|
+
*/
|
|
77
|
+
const polish = (n) => {
|
|
78
|
+
let num;
|
|
79
|
+
|
|
80
|
+
if (typeof n === 'bigint') {
|
|
81
|
+
if (n < 0n || n > MAX_VALUE) return false;
|
|
82
|
+
num = n;
|
|
83
|
+
} else if (typeof n === 'number') {
|
|
84
|
+
if (isNaN(n) || n < 0 || !Number.isInteger(n)) return false;
|
|
85
|
+
if (n > Number.MAX_SAFE_INTEGER) return false;
|
|
86
|
+
num = BigInt(n);
|
|
87
|
+
} else {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (num === 0n) return 'zero';
|
|
92
|
+
|
|
93
|
+
let s = '';
|
|
94
|
+
for (let i = group(num); i >= 0; i--) {
|
|
95
|
+
const h = hundment(num, i);
|
|
96
|
+
if (h > 0) {
|
|
97
|
+
const hund = hundredPl(h);
|
|
98
|
+
if (hund) s += hund + ' ';
|
|
99
|
+
|
|
100
|
+
const t = tenment(num, i);
|
|
101
|
+
|
|
102
|
+
if (i === 0) {
|
|
103
|
+
// Units group: just tens
|
|
104
|
+
const tenWord = tenPl(t);
|
|
105
|
+
if (tenWord) s += tenWord + ' ';
|
|
106
|
+
} else if (i === 1) {
|
|
107
|
+
// Thousands
|
|
108
|
+
if (h === 1 && t === 0) {
|
|
109
|
+
// Just "tysiąc" for exactly 1 thousand (with possible hundreds)
|
|
110
|
+
s += 'tysiąc ';
|
|
111
|
+
} else {
|
|
112
|
+
if (t > 0) {
|
|
113
|
+
// Skip "jeden" before "tysiąc" when it's just 1
|
|
114
|
+
if (t === 1) {
|
|
115
|
+
s += 'tysiąc ';
|
|
116
|
+
} else {
|
|
117
|
+
s += tenPl(t) + ' ';
|
|
118
|
+
s += getPlPlural(t, PL_ILLIONS[i]) + ' ';
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
// h >= 100 with no tens portion, use h for plural
|
|
122
|
+
s += getPlPlural(h, PL_ILLIONS[i]) + ' ';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
// Millions and above
|
|
127
|
+
if (t > 0) {
|
|
128
|
+
if (t === 1) {
|
|
129
|
+
s += 'jeden ';
|
|
130
|
+
} else {
|
|
131
|
+
s += tenPl(t) + ' ';
|
|
132
|
+
}
|
|
133
|
+
} else if (h < 100 && h === 1) {
|
|
134
|
+
s += 'jeden ';
|
|
135
|
+
}
|
|
136
|
+
const illionWord = getPlPlural(t || h, PL_ILLIONS[i]);
|
|
137
|
+
s += illionWord + ' ';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return s.trim().replace(/\s+/g, ' ');
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default polish;
|
|
146
|
+
export { polish };
|