n2words 1.24.0 → 3.0.0
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/CHANGELOG.md +49 -0
- package/README.md +183 -156
- package/dist/languages/am-Latn.js +3 -0
- package/dist/languages/am-Latn.js.map +1 -0
- package/dist/languages/am.js +3 -0
- package/dist/languages/am.js.map +1 -0
- package/dist/languages/ar.js +3 -2
- package/dist/languages/ar.js.map +1 -1
- package/dist/languages/az.js +3 -2
- package/dist/languages/az.js.map +1 -1
- package/dist/languages/bn.js +3 -2
- package/dist/languages/bn.js.map +1 -1
- package/dist/languages/cs.js +3 -2
- package/dist/languages/cs.js.map +1 -1
- package/dist/languages/da.js +3 -2
- package/dist/languages/da.js.map +1 -1
- package/dist/languages/de.js +3 -2
- package/dist/languages/de.js.map +1 -1
- package/dist/languages/el.js +3 -2
- package/dist/languages/el.js.map +1 -1
- package/dist/languages/en.js +3 -2
- package/dist/languages/en.js.map +1 -1
- package/dist/languages/es.js +3 -2
- package/dist/languages/es.js.map +1 -1
- package/dist/languages/fa.js +3 -2
- package/dist/languages/fa.js.map +1 -1
- package/dist/languages/fi.js +3 -0
- package/dist/languages/fi.js.map +1 -0
- package/dist/languages/fil.js +3 -2
- package/dist/languages/fil.js.map +1 -1
- package/dist/languages/fr-BE.js +3 -2
- package/dist/languages/fr-BE.js.map +1 -1
- package/dist/languages/fr.js +3 -2
- package/dist/languages/fr.js.map +1 -1
- package/dist/languages/gu.js +3 -2
- package/dist/languages/gu.js.map +1 -1
- package/dist/languages/ha.js +3 -0
- package/dist/languages/ha.js.map +1 -0
- package/dist/languages/hbo.js +3 -0
- package/dist/languages/hbo.js.map +1 -0
- package/dist/languages/he.js +3 -2
- package/dist/languages/he.js.map +1 -1
- package/dist/languages/hi.js +3 -2
- package/dist/languages/hi.js.map +1 -1
- package/dist/languages/hr.js +3 -2
- package/dist/languages/hr.js.map +1 -1
- package/dist/languages/hu.js +3 -2
- package/dist/languages/hu.js.map +1 -1
- package/dist/languages/id.js +3 -2
- package/dist/languages/id.js.map +1 -1
- package/dist/languages/it.js +3 -2
- package/dist/languages/it.js.map +1 -1
- package/dist/languages/ja.js +3 -2
- package/dist/languages/ja.js.map +1 -1
- package/dist/languages/kn.js +3 -2
- package/dist/languages/kn.js.map +1 -1
- package/dist/languages/ko.js +3 -2
- package/dist/languages/ko.js.map +1 -1
- package/dist/languages/lt.js +3 -2
- package/dist/languages/lt.js.map +1 -1
- package/dist/languages/lv.js +3 -2
- package/dist/languages/lv.js.map +1 -1
- package/dist/languages/mr.js +3 -2
- package/dist/languages/mr.js.map +1 -1
- package/dist/languages/ms.js +3 -2
- package/dist/languages/ms.js.map +1 -1
- package/dist/languages/nb.js +3 -2
- package/dist/languages/nb.js.map +1 -1
- package/dist/languages/nl.js +3 -2
- package/dist/languages/nl.js.map +1 -1
- package/dist/languages/pa.js +3 -0
- package/dist/languages/pa.js.map +1 -0
- package/dist/languages/pl.js +3 -2
- package/dist/languages/pl.js.map +1 -1
- package/dist/languages/pt.js +3 -2
- package/dist/languages/pt.js.map +1 -1
- package/dist/languages/ro.js +3 -2
- package/dist/languages/ro.js.map +1 -1
- package/dist/languages/ru.js +3 -2
- package/dist/languages/ru.js.map +1 -1
- package/dist/languages/sr-Cyrl.js +3 -0
- package/dist/languages/sr-Cyrl.js.map +1 -0
- package/dist/languages/sr-Latn.js +3 -2
- package/dist/languages/sr-Latn.js.map +1 -1
- package/dist/languages/sv.js +3 -2
- package/dist/languages/sv.js.map +1 -1
- package/dist/languages/sw.js +3 -2
- package/dist/languages/sw.js.map +1 -1
- package/dist/languages/ta.js +3 -2
- package/dist/languages/ta.js.map +1 -1
- package/dist/languages/te.js +3 -2
- package/dist/languages/te.js.map +1 -1
- package/dist/languages/th.js +3 -2
- package/dist/languages/th.js.map +1 -1
- package/dist/languages/tr.js +3 -2
- package/dist/languages/tr.js.map +1 -1
- package/dist/languages/uk.js +3 -2
- package/dist/languages/uk.js.map +1 -1
- package/dist/languages/ur.js +3 -2
- package/dist/languages/ur.js.map +1 -1
- package/dist/languages/vi.js +3 -2
- package/dist/languages/vi.js.map +1 -1
- package/dist/languages/zh-Hans.js +3 -2
- package/dist/languages/zh-Hans.js.map +1 -1
- package/dist/languages/zh-Hant.js +3 -0
- package/dist/languages/zh-Hant.js.map +1 -0
- package/dist/n2words.js +3 -2
- package/dist/n2words.js.map +1 -1
- package/lib/languages/am-Latn.d.ts +7 -0
- package/lib/languages/am-Latn.js +164 -0
- package/lib/languages/am.d.ts +7 -0
- package/lib/languages/am.js +164 -0
- package/lib/languages/ar.d.ts +17 -0
- package/lib/languages/ar.js +171 -209
- package/lib/languages/az.d.ts +7 -0
- package/lib/languages/az.js +167 -49
- package/lib/languages/bn.d.ts +7 -0
- package/lib/languages/bn.js +142 -123
- package/lib/languages/cs.d.ts +18 -0
- package/lib/languages/cs.js +303 -176
- package/lib/languages/da.d.ts +14 -0
- package/lib/languages/da.js +267 -139
- package/lib/languages/de.d.ts +17 -0
- package/lib/languages/de.js +310 -113
- package/lib/languages/el.d.ts +14 -0
- package/lib/languages/el.js +225 -98
- package/lib/languages/en.d.ts +17 -0
- package/lib/languages/en.js +235 -102
- package/lib/languages/es.d.ts +21 -0
- package/lib/languages/es.js +307 -125
- package/lib/languages/fa.d.ts +7 -0
- package/lib/languages/fa.js +115 -108
- package/lib/languages/fi.d.ts +14 -0
- package/lib/languages/fi.js +245 -0
- package/lib/languages/fil.d.ts +7 -0
- package/lib/languages/fil.js +199 -139
- package/lib/languages/fr-BE.d.ts +11 -0
- package/lib/languages/fr-BE.js +287 -48
- package/lib/languages/fr.d.ts +21 -0
- package/lib/languages/fr.js +343 -119
- package/lib/languages/gu.d.ts +7 -0
- package/lib/languages/gu.js +125 -144
- package/lib/languages/ha.d.ts +7 -0
- package/lib/languages/ha.js +230 -0
- package/lib/languages/hbo.d.ts +13 -0
- package/lib/languages/hbo.js +300 -0
- package/lib/languages/he.d.ts +13 -0
- package/lib/languages/he.js +230 -283
- package/lib/languages/hi.d.ts +7 -0
- package/lib/languages/hi.js +142 -123
- package/lib/languages/hr.d.ts +11 -0
- package/lib/languages/hr.js +190 -129
- package/lib/languages/hu.d.ts +7 -0
- package/lib/languages/hu.js +194 -133
- package/lib/languages/id.d.ts +7 -0
- package/lib/languages/id.js +167 -140
- package/lib/languages/it.d.ts +19 -0
- package/lib/languages/it.js +337 -108
- package/lib/languages/ja.d.ts +17 -0
- package/lib/languages/ja.js +224 -155
- package/lib/languages/kn.d.ts +7 -0
- package/lib/languages/kn.js +128 -62
- package/lib/languages/ko.d.ts +14 -0
- package/lib/languages/ko.js +250 -70
- package/lib/languages/lt.d.ts +18 -0
- package/lib/languages/lt.js +287 -148
- package/lib/languages/lv.d.ts +18 -0
- package/lib/languages/lv.js +291 -123
- package/lib/languages/mr.d.ts +7 -0
- package/lib/languages/mr.js +125 -144
- package/lib/languages/ms.d.ts +7 -0
- package/lib/languages/ms.js +171 -112
- package/lib/languages/nb.d.ts +14 -0
- package/lib/languages/nb.js +275 -100
- package/lib/languages/nl.d.ts +26 -0
- package/lib/languages/nl.js +307 -174
- package/lib/languages/pa.d.ts +7 -0
- package/lib/languages/pa.js +163 -0
- package/lib/languages/pl.d.ts +22 -0
- package/lib/languages/pl.js +299 -158
- package/lib/languages/pt.d.ts +17 -0
- package/lib/languages/pt.js +279 -120
- package/lib/languages/ro.d.ts +18 -0
- package/lib/languages/ro.js +214 -337
- package/lib/languages/ru.d.ts +11 -0
- package/lib/languages/ru.js +219 -95
- package/lib/languages/sr-Cyrl.d.ts +11 -0
- package/lib/languages/sr-Cyrl.js +215 -0
- package/lib/languages/sr-Latn.d.ts +11 -0
- package/lib/languages/sr-Latn.js +190 -132
- package/lib/languages/sv.d.ts +14 -0
- package/lib/languages/sv.js +280 -103
- package/lib/languages/sw.d.ts +7 -0
- package/lib/languages/sw.js +135 -103
- package/lib/languages/ta.d.ts +7 -0
- package/lib/languages/ta.js +133 -205
- package/lib/languages/te.d.ts +7 -0
- package/lib/languages/te.js +148 -213
- package/lib/languages/th.d.ts +7 -0
- package/lib/languages/th.js +139 -101
- package/lib/languages/tr.d.ts +18 -0
- package/lib/languages/tr.js +246 -66
- package/lib/languages/uk.d.ts +11 -0
- package/lib/languages/uk.js +197 -101
- package/lib/languages/ur.d.ts +7 -0
- package/lib/languages/ur.js +160 -123
- package/lib/languages/vi.d.ts +17 -0
- package/lib/languages/vi.js +287 -164
- package/lib/languages/zh-Hans.d.ts +11 -0
- package/lib/languages/zh-Hans.js +159 -142
- package/lib/languages/zh-Hant.d.ts +11 -0
- package/lib/languages/zh-Hant.js +202 -0
- package/lib/n2words.d.ts +53 -0
- package/lib/n2words.js +91 -227
- package/lib/utils/is-plain-object.d.ts +13 -0
- package/lib/utils/is-plain-object.js +17 -0
- package/lib/utils/parse-numeric.d.ts +17 -0
- package/lib/utils/parse-numeric.js +108 -0
- package/lib/utils/validate-options.d.ts +8 -0
- package/lib/utils/validate-options.js +16 -0
- package/package.json +118 -67
- package/dist/languages/pa-Guru.js +0 -2
- package/dist/languages/pa-Guru.js.map +0 -1
- package/lib/classes/abstract-language.js +0 -261
- package/lib/classes/greedy-scale-language.js +0 -195
- package/lib/classes/slavic-language.js +0 -251
- package/lib/classes/south-asian-language.js +0 -161
- package/lib/classes/turkic-language.js +0 -63
- package/lib/languages/pa-Guru.js +0 -126
- package/typings/classes/abstract-language.d.ts +0 -144
- package/typings/classes/greedy-scale-language.d.ts +0 -148
- package/typings/classes/slavic-language.d.ts +0 -145
- package/typings/classes/south-asian-language.d.ts +0 -101
- package/typings/classes/turkic-language.d.ts +0 -42
- package/typings/languages/ar.d.ts +0 -93
- package/typings/languages/az.d.ts +0 -25
- package/typings/languages/bn.d.ts +0 -1
- package/typings/languages/cs.d.ts +0 -120
- package/typings/languages/da.d.ts +0 -53
- package/typings/languages/de.d.ts +0 -26
- package/typings/languages/el.d.ts +0 -11
- package/typings/languages/en.d.ts +0 -30
- package/typings/languages/es.d.ts +0 -43
- package/typings/languages/fa.d.ts +0 -81
- package/typings/languages/fil.d.ts +0 -12
- package/typings/languages/fr-BE.d.ts +0 -41
- package/typings/languages/fr.d.ts +0 -43
- package/typings/languages/gu.d.ts +0 -12
- package/typings/languages/he.d.ts +0 -197
- package/typings/languages/hi.d.ts +0 -1
- package/typings/languages/hr.d.ts +0 -110
- package/typings/languages/hu.d.ts +0 -37
- package/typings/languages/id.d.ts +0 -69
- package/typings/languages/it.d.ts +0 -51
- package/typings/languages/ja.d.ts +0 -58
- package/typings/languages/kn.d.ts +0 -11
- package/typings/languages/ko.d.ts +0 -25
- package/typings/languages/lt.d.ts +0 -110
- package/typings/languages/lv.d.ts +0 -99
- package/typings/languages/mr.d.ts +0 -12
- package/typings/languages/ms.d.ts +0 -37
- package/typings/languages/nb.d.ts +0 -27
- package/typings/languages/nl.d.ts +0 -65
- package/typings/languages/pa-Guru.d.ts +0 -1
- package/typings/languages/pl.d.ts +0 -116
- package/typings/languages/pt.d.ts +0 -39
- package/typings/languages/ro.d.ts +0 -229
- package/typings/languages/ru.d.ts +0 -108
- package/typings/languages/sr-Latn.d.ts +0 -98
- package/typings/languages/sv.d.ts +0 -30
- package/typings/languages/sw.d.ts +0 -1
- package/typings/languages/ta.d.ts +0 -1
- package/typings/languages/te.d.ts +0 -1
- package/typings/languages/th.d.ts +0 -1
- package/typings/languages/tr.d.ts +0 -46
- package/typings/languages/uk.d.ts +0 -117
- package/typings/languages/ur.d.ts +0 -1
- package/typings/languages/vi.d.ts +0 -116
- package/typings/languages/zh-Hans.d.ts +0 -57
- package/typings/n2words.d.ts +0 -177
package/lib/languages/pl.js
CHANGED
|
@@ -1,189 +1,330 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Polish language converter - Functional Implementation
|
|
3
|
+
*
|
|
4
|
+
* A performance-optimized number-to-words converter using precomputed lookup tables.
|
|
5
|
+
* Self-contained module with its own input validation, ready for subpath exports.
|
|
6
|
+
*
|
|
7
|
+
* Key optimization: Precompute all segment values (0-999) at module load.
|
|
8
|
+
* This eliminates all per-call string manipulation for segment conversion.
|
|
9
|
+
*
|
|
10
|
+
* Polish-specific rules (handled in precomputation):
|
|
11
|
+
* - Three-form pluralization: 1 = singular, 2-4 = few, 5+ = many
|
|
12
|
+
* - Gender agreement (masculine/feminine for numbers < 1000)
|
|
13
|
+
* - Omit "jeden" before scale words (tysiąc, milion, etc.)
|
|
14
|
+
* - Irregular hundreds: dwieście, trzysta, czterysta, pięćset...
|
|
15
|
+
* - Long scale with -ard forms: miliard, biliard, tryliard
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
19
|
+
import { validateOptions } from '../utils/validate-options.js'
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Vocabulary (module-level constants)
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
const ONES_MASC = ['', 'jeden', 'dwa', 'trzy', 'cztery', 'pięć', 'sześć', 'siedem', 'osiem', 'dziewięć']
|
|
26
|
+
const ONES_FEM = ['', 'jedna', 'dwie', 'trzy', 'cztery', 'pięć', 'sześć', 'siedem', 'osiem', 'dziewięć']
|
|
27
|
+
|
|
28
|
+
const TEENS = ['dziesięć', 'jedenaście', 'dwanaście', 'trzynaście', 'czternaście', 'piętnaście', 'szesnaście', 'siedemnaście', 'osiemnaście', 'dziewiętnaście']
|
|
29
|
+
|
|
30
|
+
const TENS = ['', '', 'dwadzieścia', 'trzydzieści', 'czterdzieści', 'pięćdziesiąt', 'sześćdziesiąt', 'siedemdziesiąt', 'osiemdziesiąt', 'dziewięćdziesiąt']
|
|
31
|
+
|
|
32
|
+
// Irregular hundreds
|
|
33
|
+
const HUNDREDS = ['', 'sto', 'dwieście', 'trzysta', 'czterysta', 'pięćset', 'sześćset', 'siedemset', 'osiemset', 'dziewięćset']
|
|
34
|
+
|
|
35
|
+
// Scale words: [singular, few (2-4), many (5+)]
|
|
36
|
+
const PLURAL_FORMS = {
|
|
37
|
+
1: ['tysiąc', 'tysiące', 'tysięcy'],
|
|
38
|
+
2: ['milion', 'miliony', 'milionów'],
|
|
39
|
+
3: ['miliard', 'miliardy', 'miliardów'],
|
|
40
|
+
4: ['bilion', 'biliony', 'bilionów'],
|
|
41
|
+
5: ['biliard', 'biliardy', 'biliardów'],
|
|
42
|
+
6: ['trylion', 'tryliony', 'trylionów'],
|
|
43
|
+
7: ['tryliard', 'tryliardy', 'tryliardów'],
|
|
44
|
+
8: ['kwadrylion', 'kwadryliony', 'kwadrylionów'],
|
|
45
|
+
9: ['kwaryliard', 'kwadryliardy', 'kwadryliardów'],
|
|
46
|
+
10: ['kwintylion', 'kwintyliony', 'kwintylionów']
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ZERO = 'zero'
|
|
50
|
+
const NEGATIVE = 'minus'
|
|
51
|
+
const DECIMAL_SEP = 'przecinek'
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Precomputed Lookup Tables (built once at module load)
|
|
55
|
+
// ============================================================================
|
|
2
56
|
|
|
3
57
|
/**
|
|
4
|
-
*
|
|
5
|
-
* @
|
|
58
|
+
* Builds segment word for 0-999 (masculine form).
|
|
59
|
+
* @param {number} n - Segment value
|
|
60
|
+
* @returns {string} Polish word
|
|
6
61
|
*/
|
|
62
|
+
function buildSegment (n) {
|
|
63
|
+
if (n === 0) return ''
|
|
64
|
+
|
|
65
|
+
const ones = n % 10
|
|
66
|
+
const tens = Math.floor(n / 10) % 10
|
|
67
|
+
const hundreds = Math.floor(n / 100)
|
|
68
|
+
|
|
69
|
+
const parts = []
|
|
70
|
+
|
|
71
|
+
// Hundreds
|
|
72
|
+
if (hundreds > 0) {
|
|
73
|
+
parts.push(HUNDREDS[hundreds])
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Tens and ones
|
|
77
|
+
if (tens === 1) {
|
|
78
|
+
// Teens (10-19)
|
|
79
|
+
parts.push(TEENS[ones])
|
|
80
|
+
} else {
|
|
81
|
+
if (tens >= 2) {
|
|
82
|
+
parts.push(TENS[tens])
|
|
83
|
+
}
|
|
84
|
+
if (ones > 0) {
|
|
85
|
+
parts.push(ONES_MASC[ones])
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return parts.join(' ')
|
|
90
|
+
}
|
|
7
91
|
|
|
8
92
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* - Polish number words (jeden, dwa, trzy, cztery, pięć...)
|
|
13
|
-
* - Complex Slavic three-form pluralization (tysiąc/tysiące/tysięcy)
|
|
14
|
-
* - Polish-specific declension patterns
|
|
15
|
-
* - Distinctive Polish phonology and orthography
|
|
16
|
-
*
|
|
17
|
-
* Key Features:
|
|
18
|
-
* - Three-form pluralization system shared across Slavic languages
|
|
19
|
-
* * Form 1 (singular): 1 (e.g., "tysiąc")
|
|
20
|
-
* * Form 2 (few): 2-4, 22-24, 32-34... excluding teens (e.g., "tysiące")
|
|
21
|
-
* * Form 3 (many): all other numbers (e.g., "tysięcy")
|
|
22
|
-
* - Chunk-based decomposition (splits into groups of 3 digits: ones, thousands, millions, etc.)
|
|
23
|
-
* - Large number handling via thousands[] array with indexed [singular, few, many] forms
|
|
24
|
-
*
|
|
25
|
-
* Features:
|
|
26
|
-
* - Polish diacritical marks (ą, ć, ę, ł, ń, ś, ź, ż)
|
|
27
|
-
* - Gender and case agreement
|
|
28
|
-
* - Polish-specific number word endings
|
|
29
|
-
*
|
|
30
|
-
* Inherits from SlavicLanguage for complex pluralization algorithms.
|
|
93
|
+
* Builds segment word for 0-999 (feminine form - only differs in ones).
|
|
94
|
+
* @param {number} n - Segment value
|
|
95
|
+
* @returns {string} Polish word
|
|
31
96
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
8: 'osiem',
|
|
45
|
-
9: 'dziewięć'
|
|
97
|
+
function buildSegmentFeminine (n) {
|
|
98
|
+
if (n === 0) return ''
|
|
99
|
+
|
|
100
|
+
const ones = n % 10
|
|
101
|
+
const tens = Math.floor(n / 10) % 10
|
|
102
|
+
const hundreds = Math.floor(n / 100)
|
|
103
|
+
|
|
104
|
+
const parts = []
|
|
105
|
+
|
|
106
|
+
// Hundreds
|
|
107
|
+
if (hundreds > 0) {
|
|
108
|
+
parts.push(HUNDREDS[hundreds])
|
|
46
109
|
}
|
|
47
110
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
9: 'dziewiętnaście'
|
|
111
|
+
// Tens and ones - feminine for ones only
|
|
112
|
+
if (tens === 1) {
|
|
113
|
+
parts.push(TEENS[ones])
|
|
114
|
+
} else {
|
|
115
|
+
if (tens >= 2) {
|
|
116
|
+
parts.push(TENS[tens])
|
|
117
|
+
}
|
|
118
|
+
if (ones > 0) {
|
|
119
|
+
parts.push(ONES_FEM[ones])
|
|
120
|
+
}
|
|
59
121
|
}
|
|
60
122
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
123
|
+
return parts.join(' ')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Precompute all 1000 segment words (0-999)
|
|
127
|
+
const SEGMENTS_MASC = new Array(1000)
|
|
128
|
+
const SEGMENTS_FEM = new Array(1000)
|
|
129
|
+
for (let i = 0; i < 1000; i++) {
|
|
130
|
+
SEGMENTS_MASC[i] = buildSegment(i)
|
|
131
|
+
SEGMENTS_FEM[i] = buildSegmentFeminine(i)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// Helper Functions
|
|
136
|
+
// ============================================================================
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Polish pluralization: 1 = singular, 2-4 = few, else = many.
|
|
140
|
+
* Special case: 11-19 always use many form.
|
|
141
|
+
*
|
|
142
|
+
* @param {bigint} n - Number to pluralize
|
|
143
|
+
* @param {string[]} forms - [singular, few, many]
|
|
144
|
+
* @returns {string} Correct plural form
|
|
145
|
+
*/
|
|
146
|
+
function pluralize (n, forms) {
|
|
147
|
+
if (n === 1n) {
|
|
148
|
+
return forms[0]
|
|
70
149
|
}
|
|
71
150
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
7: 'siedemset',
|
|
80
|
-
8: 'osiemset',
|
|
81
|
-
9: 'dziewięćset'
|
|
151
|
+
const lastDigit = n % 10n
|
|
152
|
+
const lastTwoDigits = n % 100n
|
|
153
|
+
|
|
154
|
+
// Teens (11-19) always use many form
|
|
155
|
+
// 2-4 use few form (but not 12-14)
|
|
156
|
+
if (lastDigit >= 2n && lastDigit <= 4n && (lastTwoDigits < 10n || lastTwoDigits > 20n)) {
|
|
157
|
+
return forms[1]
|
|
82
158
|
}
|
|
83
159
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
160
|
+
return forms[2]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// Conversion Functions
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Converts a non-negative integer to Polish words.
|
|
169
|
+
*
|
|
170
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
171
|
+
* @param {Object} options - Conversion options
|
|
172
|
+
* @returns {string} Polish words
|
|
173
|
+
*/
|
|
174
|
+
function integerToWords (n, options = {}) {
|
|
175
|
+
if (n === 0n) return ZERO
|
|
176
|
+
|
|
177
|
+
// Fast path: numbers < 1000 (direct lookup)
|
|
178
|
+
if (n < 1000n) {
|
|
179
|
+
const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
|
|
180
|
+
return segments[Number(n)]
|
|
95
181
|
}
|
|
96
182
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
* - Form 3 (many): all other numbers (e.g., "tysięcy")
|
|
104
|
-
*
|
|
105
|
-
* @param {bigint} n The number to classify.
|
|
106
|
-
* @param {Array<string>} forms Array of [singular, few, many] word forms.
|
|
107
|
-
* @returns {string} The appropriate form for the number n.
|
|
108
|
-
*/
|
|
109
|
-
pluralize (n, forms) {
|
|
110
|
-
if (n === 1n) {
|
|
111
|
-
return forms[0]
|
|
112
|
-
}
|
|
183
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
184
|
+
if (n < 1_000_000n) {
|
|
185
|
+
const thousands = Number(n / 1000n)
|
|
186
|
+
const remainder = Number(n % 1000n)
|
|
187
|
+
|
|
188
|
+
const scaleWord = pluralize(BigInt(thousands), PLURAL_FORMS[1])
|
|
113
189
|
|
|
114
|
-
|
|
115
|
-
|
|
190
|
+
let result
|
|
191
|
+
if (thousands === 1) {
|
|
192
|
+
// Omit "jeden" before tysiąc
|
|
193
|
+
result = scaleWord
|
|
194
|
+
} else {
|
|
195
|
+
result = SEGMENTS_MASC[thousands] + ' ' + scaleWord
|
|
196
|
+
}
|
|
116
197
|
|
|
117
|
-
if (
|
|
118
|
-
|
|
198
|
+
if (remainder > 0) {
|
|
199
|
+
result += ' ' + SEGMENTS_MASC[remainder]
|
|
119
200
|
}
|
|
120
201
|
|
|
121
|
-
return
|
|
202
|
+
return result
|
|
122
203
|
}
|
|
123
204
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
205
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
206
|
+
return buildLargeNumberWords(n, options)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Builds words for numbers >= 1,000,000.
|
|
211
|
+
* Uses BigInt division for faster segment extraction.
|
|
212
|
+
*
|
|
213
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
214
|
+
* @param {Object} options - Conversion options
|
|
215
|
+
* @returns {string} Polish words
|
|
216
|
+
*/
|
|
217
|
+
function buildLargeNumberWords (n, options) {
|
|
218
|
+
// Extract segments using BigInt division (faster than string slicing)
|
|
219
|
+
// Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
|
|
220
|
+
const segmentValues = []
|
|
221
|
+
let temp = n
|
|
222
|
+
while (temp > 0n) {
|
|
223
|
+
segmentValues.push(temp % 1000n)
|
|
224
|
+
temp = temp / 1000n
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Build result string directly
|
|
228
|
+
let result = ''
|
|
229
|
+
|
|
230
|
+
for (let i = segmentValues.length - 1; i >= 0; i--) {
|
|
231
|
+
const segment = segmentValues[i]
|
|
232
|
+
if (segment === 0n) continue
|
|
233
|
+
|
|
234
|
+
const segmentWord = SEGMENTS_MASC[Number(segment)]
|
|
235
|
+
|
|
236
|
+
if (result) result += ' '
|
|
237
|
+
|
|
238
|
+
if (i === 0) {
|
|
239
|
+
// Units segment
|
|
240
|
+
result += segmentWord
|
|
241
|
+
} else {
|
|
242
|
+
// Scale word needed
|
|
243
|
+
const forms = PLURAL_FORMS[i]
|
|
244
|
+
if (forms) {
|
|
245
|
+
const scaleWord = pluralize(segment, forms)
|
|
246
|
+
|
|
247
|
+
if (segment === 1n) {
|
|
248
|
+
// Omit "jeden" before scale words
|
|
249
|
+
result += scaleWord
|
|
250
|
+
} else {
|
|
251
|
+
result += segmentWord + ' ' + scaleWord
|
|
252
|
+
}
|
|
172
253
|
}
|
|
173
254
|
}
|
|
174
|
-
return words.join(' ')
|
|
175
255
|
}
|
|
256
|
+
|
|
257
|
+
return result
|
|
176
258
|
}
|
|
177
259
|
|
|
178
260
|
/**
|
|
179
|
-
* Converts
|
|
261
|
+
* Converts decimal digits to Polish words.
|
|
180
262
|
*
|
|
181
|
-
* @param {
|
|
182
|
-
* @param {
|
|
183
|
-
* @returns {string}
|
|
184
|
-
* @throws {TypeError} If value is NaN or invalid type.
|
|
185
|
-
* @throws {Error} If value is an invalid number string.
|
|
263
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
264
|
+
* @param {Object} options - Conversion options
|
|
265
|
+
* @returns {string} Polish words for decimal part
|
|
186
266
|
*/
|
|
187
|
-
|
|
188
|
-
|
|
267
|
+
function decimalPartToWords (decimalPart, options) {
|
|
268
|
+
let result = ''
|
|
269
|
+
|
|
270
|
+
// Handle leading zeros
|
|
271
|
+
let i = 0
|
|
272
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
273
|
+
if (result) result += ' '
|
|
274
|
+
result += ZERO
|
|
275
|
+
i++
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Convert remainder as a single number
|
|
279
|
+
const remainder = decimalPart.slice(i)
|
|
280
|
+
if (remainder) {
|
|
281
|
+
if (result) result += ' '
|
|
282
|
+
result += integerToWords(BigInt(remainder), options)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return result
|
|
189
286
|
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Converts a numeric value to Polish words.
|
|
290
|
+
*
|
|
291
|
+
* This is the main public API. It accepts any valid numeric input
|
|
292
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
293
|
+
*
|
|
294
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
295
|
+
* @param {Object} [options] - Conversion options
|
|
296
|
+
* @param {string} [options.gender='masculine'] - Gender for numbers < 1000
|
|
297
|
+
* @returns {string} The number in Polish words
|
|
298
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
299
|
+
* @throws {Error} If value is not a valid number format
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* toWords(1) // 'jeden'
|
|
303
|
+
* toWords(1, { gender: 'feminine' }) // 'jedna'
|
|
304
|
+
* toWords(1000) // 'tysiąc'
|
|
305
|
+
* toWords(2000) // 'dwa tysiące'
|
|
306
|
+
*/
|
|
307
|
+
function toWords (value, options) {
|
|
308
|
+
options = validateOptions(options)
|
|
309
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
310
|
+
|
|
311
|
+
let result = ''
|
|
312
|
+
|
|
313
|
+
if (isNegative) {
|
|
314
|
+
result = NEGATIVE + ' '
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
result += integerToWords(integerPart, options)
|
|
318
|
+
|
|
319
|
+
if (decimalPart) {
|
|
320
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, options)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return result
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ============================================================================
|
|
327
|
+
// Public API
|
|
328
|
+
// ============================================================================
|
|
329
|
+
|
|
330
|
+
export { toWords }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a numeric value to Portuguese words.
|
|
3
|
+
*
|
|
4
|
+
* This is the main public API. It accepts any valid numeric input
|
|
5
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
6
|
+
*
|
|
7
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
8
|
+
* @returns {string} The number in Portuguese words
|
|
9
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
10
|
+
* @throws {Error} If value is not a valid number format
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* toWords(21) // 'vinte e um'
|
|
14
|
+
* toWords(100) // 'cem'
|
|
15
|
+
* toWords(1000000) // 'um milhão'
|
|
16
|
+
*/
|
|
17
|
+
export function toWords(value: number | string | bigint): string;
|