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/nl.js
CHANGED
|
@@ -1,206 +1,339 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Dutch 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
|
+
* Dutch-specific rules (handled in precomputation):
|
|
11
|
+
* - Inverted tens-ones: eenentwintig (one-and-twenty)
|
|
12
|
+
* - "ën" connector when ones ends in 'e' (twee, drie)
|
|
13
|
+
* - Compound words without spaces
|
|
14
|
+
* - Hundred pairing for 1100-9999 (elfhonderd style)
|
|
15
|
+
* - "één" vs "een" (accentOne option)
|
|
16
|
+
* - Optional "en" separator (includeOptionalAnd option)
|
|
17
|
+
* - Long scale with -ard forms
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
21
|
+
import { validateOptions } from '../utils/validate-options.js'
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Vocabulary (module-level constants)
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
const ONES = ['', 'een', 'twee', 'drie', 'vier', 'vijf', 'zes', 'zeven', 'acht', 'negen']
|
|
28
|
+
const TEENS = ['tien', 'elf', 'twaalf', 'dertien', 'veertien', 'vijftien', 'zestien', 'zeventien', 'achttien', 'negentien']
|
|
29
|
+
const TENS = ['', '', 'twintig', 'dertig', 'veertig', 'vijftig', 'zestig', 'zeventig', 'tachtig', 'negentig']
|
|
30
|
+
|
|
31
|
+
const HUNDRED = 'honderd'
|
|
32
|
+
|
|
33
|
+
// Scale words (long scale with -ard forms)
|
|
34
|
+
const SCALES = ['duizend', 'miljoen', 'miljard', 'biljoen', 'biljard', 'triljoen', 'triljard', 'quadriljoen', 'quadriljard']
|
|
35
|
+
|
|
36
|
+
const ZERO = 'nul'
|
|
37
|
+
const NEGATIVE = 'min'
|
|
38
|
+
const DECIMAL_SEP = 'komma'
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Precomputed Lookup Tables (built once at module load)
|
|
42
|
+
// ============================================================================
|
|
2
43
|
|
|
3
44
|
/**
|
|
4
|
-
*
|
|
5
|
-
* @
|
|
6
|
-
* @
|
|
7
|
-
* @
|
|
45
|
+
* Builds segment word for 0-999.
|
|
46
|
+
* @param {number} n - Segment value
|
|
47
|
+
* @param {boolean} withAnd - Include "en" for values < 13 after hundreds
|
|
48
|
+
* @returns {string} Dutch word (compound, no spaces)
|
|
8
49
|
*/
|
|
50
|
+
function buildSegment (n, withAnd) {
|
|
51
|
+
if (n === 0) return ''
|
|
52
|
+
|
|
53
|
+
const ones = n % 10
|
|
54
|
+
const tens = Math.floor(n / 10) % 10
|
|
55
|
+
const hundreds = Math.floor(n / 100)
|
|
56
|
+
const tensOnes = n % 100
|
|
57
|
+
|
|
58
|
+
let result = ''
|
|
59
|
+
|
|
60
|
+
// Hundreds
|
|
61
|
+
if (hundreds > 0) {
|
|
62
|
+
if (hundreds === 1) {
|
|
63
|
+
result = HUNDRED
|
|
64
|
+
} else {
|
|
65
|
+
result = ONES[hundreds] + HUNDRED
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Tens and ones
|
|
70
|
+
if (tensOnes === 0) {
|
|
71
|
+
// Just hundreds
|
|
72
|
+
} else if (tensOnes < 10) {
|
|
73
|
+
// Single digit - add "en" if withAnd and after hundreds
|
|
74
|
+
if (hundreds > 0 && withAnd) {
|
|
75
|
+
result += 'en' + ONES[tensOnes]
|
|
76
|
+
} else {
|
|
77
|
+
result += ONES[tensOnes]
|
|
78
|
+
}
|
|
79
|
+
} else if (tensOnes < 20) {
|
|
80
|
+
// Teens - add "en" if withAnd and after hundreds and < 13
|
|
81
|
+
if (hundreds > 0 && withAnd && tensOnes < 13) {
|
|
82
|
+
result += 'en' + TEENS[ones]
|
|
83
|
+
} else {
|
|
84
|
+
result += TEENS[ones]
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
// 20-99: Dutch inverts with connector
|
|
88
|
+
if (ones === 0) {
|
|
89
|
+
result += TENS[tens]
|
|
90
|
+
} else {
|
|
91
|
+
// "ën" if ones ends in 'e' (twee, drie)
|
|
92
|
+
const onesWord = ONES[ones]
|
|
93
|
+
const connector = onesWord.endsWith('e') ? 'ën' : 'en'
|
|
94
|
+
result += onesWord + connector + TENS[tens]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Precompute all 1000 segment words (0-999) - standard form
|
|
102
|
+
const SEGMENTS = new Array(1000)
|
|
103
|
+
for (let i = 0; i < 1000; i++) {
|
|
104
|
+
SEGMENTS[i] = buildSegment(i, false)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Precompute all 1000 segment words (0-999) - with optional "en"
|
|
108
|
+
const SEGMENTS_WITH_AND = new Array(1000)
|
|
109
|
+
for (let i = 0; i < 1000; i++) {
|
|
110
|
+
SEGMENTS_WITH_AND[i] = buildSegment(i, true)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// Conversion Functions
|
|
115
|
+
// ============================================================================
|
|
9
116
|
|
|
10
117
|
/**
|
|
11
|
-
* Dutch
|
|
118
|
+
* Converts a non-negative integer to Dutch words.
|
|
12
119
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* - Compound word formation without hyphenation
|
|
120
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
121
|
+
* @param {Object} options - Conversion options
|
|
122
|
+
* @returns {string} Dutch words
|
|
17
123
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
[1_000_000_000_000n, 'biljoen'],
|
|
29
|
-
[1_000_000_000n, 'miljard'],
|
|
30
|
-
[1_000_000n, 'miljoen'],
|
|
31
|
-
[1000n, 'duizend'],
|
|
32
|
-
[100n, 'honderd'],
|
|
33
|
-
[90n, 'negentig'],
|
|
34
|
-
[80n, 'tachtig'],
|
|
35
|
-
[70n, 'zeventig'],
|
|
36
|
-
[60n, 'zestig'],
|
|
37
|
-
[50n, 'vijftig'],
|
|
38
|
-
[40n, 'veertig'],
|
|
39
|
-
[30n, 'dertig'],
|
|
40
|
-
[20n, 'twintig'],
|
|
41
|
-
[19n, 'negentien'],
|
|
42
|
-
[18n, 'achttien'],
|
|
43
|
-
[17n, 'zeventien'],
|
|
44
|
-
[16n, 'zestien'],
|
|
45
|
-
[15n, 'vijftien'],
|
|
46
|
-
[14n, 'veertien'],
|
|
47
|
-
[13n, 'dertien'],
|
|
48
|
-
[12n, 'twaalf'],
|
|
49
|
-
[11n, 'elf'],
|
|
50
|
-
[10n, 'tien'],
|
|
51
|
-
[9n, 'negen'],
|
|
52
|
-
[8n, 'acht'],
|
|
53
|
-
[7n, 'zeven'],
|
|
54
|
-
[6n, 'zes'],
|
|
55
|
-
[5n, 'vijf'],
|
|
56
|
-
[4n, 'vier'],
|
|
57
|
-
[3n, 'drie'],
|
|
58
|
-
[2n, 'twee'],
|
|
59
|
-
[1n, 'één'],
|
|
60
|
-
[0n, 'nul']
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Initializes the Dutch converter with language-specific options.
|
|
65
|
-
*
|
|
66
|
-
* @param {DutchOptions} [options={}] Configuration options.
|
|
67
|
-
*/
|
|
68
|
-
constructor ({ includeOptionalAnd = false, noHundredPairs = false, accentOne = true } = {}) {
|
|
69
|
-
super()
|
|
70
|
-
|
|
71
|
-
this.includeOptionalAnd = includeOptionalAnd
|
|
72
|
-
|
|
73
|
-
this.noHundredPairs = noHundredPairs
|
|
74
|
-
|
|
75
|
-
this.accentOne = accentOne
|
|
76
|
-
if (!this.accentOne) {
|
|
77
|
-
this.scaleWordPairs[this.scaleWordPairs.length - 2][1] = 'een'
|
|
124
|
+
function integerToWords (n, options) {
|
|
125
|
+
if (n === 0n) return ZERO
|
|
126
|
+
|
|
127
|
+
const { accentOne, includeOptionalAnd, noHundredPairing } = options
|
|
128
|
+
const segments = includeOptionalAnd ? SEGMENTS_WITH_AND : SEGMENTS
|
|
129
|
+
|
|
130
|
+
// Apply één/een replacement
|
|
131
|
+
const applyAccent = (word) => {
|
|
132
|
+
if (accentOne) {
|
|
133
|
+
return word.replace(/\been\b/g, 'één')
|
|
78
134
|
}
|
|
135
|
+
return word
|
|
79
136
|
}
|
|
80
137
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
* - Implicit "één": `mergeScales({ 'één': 1n }, { 'duizend': 1000n })` → `{ 'duizend': 1000n }`
|
|
86
|
-
* - Reversed digit order for tens + units: `mergeScales({ 'twintig': 20n }, { 'één': 1n })` → `{ 'ééntwintig': 21n }`
|
|
87
|
-
* - Optional "en" separator based on includeOptionalAnd option
|
|
88
|
-
* - Compound words without separators for most combinations
|
|
89
|
-
* - Converts 'één' to 'een' in compound words (no accent in compounds)
|
|
90
|
-
* - Space separators for large numbers (millions+)
|
|
91
|
-
*
|
|
92
|
-
* @param {Object} current The left operand as `{ word: BigInt }`.
|
|
93
|
-
* @param {Object} next The right operand as `{ word: BigInt }`.
|
|
94
|
-
* @returns {Object} Merged pair with combined word and resulting numeric value.
|
|
95
|
-
*
|
|
96
|
-
* @example
|
|
97
|
-
* mergeScales({ 'één': 1n }, { 'duizend': 1000n }); // { 'duizend': 1000n }
|
|
98
|
-
* mergeScales({ 'twintig': 20n }, { 'drie': 3n }); // { 'drieentwintig': 23n }
|
|
99
|
-
*/
|
|
100
|
-
mergeScales (current, next) {
|
|
101
|
-
let cText = Object.keys(current)[0]
|
|
102
|
-
let nText = Object.keys(next)[0]
|
|
103
|
-
const cNumber = Object.values(current)[0] // BigInt
|
|
104
|
-
const nNumber = Object.values(next)[0] // BigInt
|
|
105
|
-
|
|
106
|
-
// Implicit "een": omit before large multipliers ("miljoen" not "een miljoen")
|
|
107
|
-
if (cNumber === 1n) {
|
|
108
|
-
if (nNumber < 1_000_000n) {
|
|
109
|
-
return next
|
|
110
|
-
}
|
|
111
|
-
cText = this.accentOne ? 'één' : 'een'
|
|
112
|
-
}
|
|
138
|
+
// Fast path: numbers < 1000 (direct lookup)
|
|
139
|
+
if (n < 1000n) {
|
|
140
|
+
return applyAccent(segments[Number(n)])
|
|
141
|
+
}
|
|
113
142
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
nText = nText.replace(/één/g, 'een')
|
|
143
|
+
// Hundred pairing for 1100-9999
|
|
144
|
+
if (!noHundredPairing && n >= 1100n && n < 10000n) {
|
|
145
|
+
const high = Number(n / 100n)
|
|
146
|
+
const low = Number(n % 100n)
|
|
147
|
+
|
|
148
|
+
// Only use pairing when high is not a multiple of 10
|
|
149
|
+
if (high % 10 !== 0) {
|
|
150
|
+
let result = segments[high] + HUNDRED
|
|
151
|
+
if (low > 0) {
|
|
152
|
+
const lowWord = segments[low]
|
|
153
|
+
if (includeOptionalAnd && low < 13) {
|
|
154
|
+
result += ' en ' + lowWord
|
|
155
|
+
} else {
|
|
156
|
+
result += ' ' + lowWord
|
|
157
|
+
}
|
|
130
158
|
}
|
|
131
|
-
return
|
|
159
|
+
return applyAccent(result)
|
|
132
160
|
}
|
|
161
|
+
}
|
|
133
162
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
hasSpace = true
|
|
147
|
-
} else if (cNumber >= 1_000_000n) {
|
|
148
|
-
cText += ' '
|
|
149
|
-
hasSpace = true
|
|
150
|
-
} else if (cNumber === 1000n) {
|
|
151
|
-
cText += ' '
|
|
152
|
-
hasSpace = true
|
|
163
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
164
|
+
if (n < 1_000_000n) {
|
|
165
|
+
const thousands = Number(n / 1000n)
|
|
166
|
+
const remainder = Number(n % 1000n)
|
|
167
|
+
|
|
168
|
+
let result
|
|
169
|
+
if (thousands === 1) {
|
|
170
|
+
// "duizend" not "eenduizend"
|
|
171
|
+
result = SCALES[0]
|
|
172
|
+
} else {
|
|
173
|
+
// Compound: "vijfduizend"
|
|
174
|
+
result = segments[thousands] + SCALES[0]
|
|
153
175
|
}
|
|
154
176
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
nText = nText.replace(/één/g, 'een')
|
|
177
|
+
if (remainder > 0) {
|
|
178
|
+
const remainderWord = segments[remainder]
|
|
179
|
+
if (includeOptionalAnd && remainder < 13) {
|
|
180
|
+
result += ' en ' + remainderWord
|
|
181
|
+
} else {
|
|
182
|
+
result += ' ' + remainderWord
|
|
183
|
+
}
|
|
163
184
|
}
|
|
164
185
|
|
|
165
|
-
return
|
|
186
|
+
return applyAccent(result)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
190
|
+
return applyAccent(buildLargeNumberWords(n, options))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Builds words for numbers >= 1,000,000.
|
|
195
|
+
* Uses BigInt division for faster segment extraction (4x faster than string slicing).
|
|
196
|
+
*
|
|
197
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
198
|
+
* @param {Object} options - Conversion options
|
|
199
|
+
* @returns {string} Dutch words
|
|
200
|
+
*/
|
|
201
|
+
function buildLargeNumberWords (n, options) {
|
|
202
|
+
const { includeOptionalAnd } = options
|
|
203
|
+
const segmentLookup = includeOptionalAnd ? SEGMENTS_WITH_AND : SEGMENTS
|
|
204
|
+
|
|
205
|
+
// Extract segments using BigInt division (faster than string slicing)
|
|
206
|
+
// Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
|
|
207
|
+
const segmentValues = []
|
|
208
|
+
let temp = n
|
|
209
|
+
while (temp > 0n) {
|
|
210
|
+
segmentValues.push(Number(temp % 1000n))
|
|
211
|
+
temp = temp / 1000n
|
|
166
212
|
}
|
|
167
213
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
214
|
+
// Build result string directly (avoids object allocation and join)
|
|
215
|
+
let result = ''
|
|
216
|
+
let prevWasScale = false
|
|
217
|
+
|
|
218
|
+
for (let i = segmentValues.length - 1; i >= 0; i--) {
|
|
219
|
+
const segment = segmentValues[i]
|
|
220
|
+
if (segment === 0) continue
|
|
221
|
+
|
|
222
|
+
if (i === 0) {
|
|
223
|
+
// Units segment
|
|
224
|
+
const word = segmentLookup[segment]
|
|
225
|
+
if (result) {
|
|
226
|
+
if (prevWasScale && includeOptionalAnd && segment < 13) {
|
|
227
|
+
result += ' en ' + word
|
|
228
|
+
} else {
|
|
229
|
+
result += ' ' + word
|
|
177
230
|
}
|
|
178
|
-
|
|
231
|
+
} else {
|
|
232
|
+
result = word
|
|
233
|
+
}
|
|
234
|
+
prevWasScale = false
|
|
235
|
+
} else if (i === 1) {
|
|
236
|
+
// Thousands - compound
|
|
237
|
+
if (result) result += ' '
|
|
238
|
+
if (segment === 1) {
|
|
239
|
+
result += SCALES[0]
|
|
240
|
+
} else {
|
|
241
|
+
result += segmentLookup[segment] + SCALES[0]
|
|
179
242
|
}
|
|
243
|
+
prevWasScale = true
|
|
244
|
+
} else {
|
|
245
|
+
// Million and above - space around scale
|
|
246
|
+
const scaleWord = SCALES[i - 1]
|
|
247
|
+
if (result) result += ' '
|
|
248
|
+
if (segment === 1) {
|
|
249
|
+
result += 'een ' + scaleWord
|
|
250
|
+
} else {
|
|
251
|
+
result += segmentLookup[segment] + ' ' + scaleWord
|
|
252
|
+
}
|
|
253
|
+
prevWasScale = true
|
|
180
254
|
}
|
|
181
|
-
return super.convertWholePart(value)
|
|
182
255
|
}
|
|
256
|
+
|
|
257
|
+
return result
|
|
183
258
|
}
|
|
184
259
|
|
|
185
260
|
/**
|
|
186
|
-
* Converts
|
|
261
|
+
* Converts decimal digits to Dutch words.
|
|
187
262
|
*
|
|
188
|
-
* @param {
|
|
189
|
-
* @param {Object}
|
|
190
|
-
* @
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
263
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
264
|
+
* @param {Object} options - Conversion options
|
|
265
|
+
* @returns {string} Dutch words for decimal part
|
|
266
|
+
*/
|
|
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
|
+
const word = integerToWords(BigInt(remainder), { ...options, noHundredPairing: true })
|
|
283
|
+
result += word
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return result
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Converts a numeric value to Dutch words.
|
|
291
|
+
*
|
|
292
|
+
* This is the main public API. It accepts any valid numeric input
|
|
293
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
294
|
+
*
|
|
295
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
296
|
+
* @param {Object} [options] - Optional configuration
|
|
297
|
+
* @param {boolean} [options.accentOne=true] - Use "één" instead of "een"
|
|
298
|
+
* @param {boolean} [options.includeOptionalAnd=false] - Include "en" before small numbers
|
|
299
|
+
* @param {boolean} [options.noHundredPairing=false] - Disable hundred pairing (1104→duizend honderdvier)
|
|
300
|
+
* @returns {string} The number in Dutch words
|
|
301
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
302
|
+
* @throws {Error} If value is not a valid number format
|
|
196
303
|
*
|
|
197
304
|
* @example
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* convertToWords('1.5'); // 'één komma vijf'
|
|
305
|
+
* toWords(21) // 'eenentwintig'
|
|
306
|
+
* toWords(1) // 'één'
|
|
307
|
+
* toWords(1, {accentOne: false}) // 'een'
|
|
308
|
+
* toWords(1104) // 'elfhonderd vier'
|
|
203
309
|
*/
|
|
204
|
-
|
|
205
|
-
|
|
310
|
+
function toWords (value, options) {
|
|
311
|
+
options = validateOptions(options)
|
|
312
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
313
|
+
|
|
314
|
+
const opts = {
|
|
315
|
+
accentOne: options.accentOne !== false, // default true
|
|
316
|
+
includeOptionalAnd: options.includeOptionalAnd || false,
|
|
317
|
+
noHundredPairing: options.noHundredPairing || false
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let result = ''
|
|
321
|
+
|
|
322
|
+
if (isNegative) {
|
|
323
|
+
result = NEGATIVE + ' '
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
result += integerToWords(integerPart, opts)
|
|
327
|
+
|
|
328
|
+
if (decimalPart) {
|
|
329
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, opts)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return result
|
|
206
333
|
}
|
|
334
|
+
|
|
335
|
+
// ============================================================================
|
|
336
|
+
// Public API
|
|
337
|
+
// ============================================================================
|
|
338
|
+
|
|
339
|
+
export { toWords }
|