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.
Files changed (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,206 +1,339 @@
1
- import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
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
- * @typedef {Object} DutchOptions
5
- * @property {boolean} [includeOptionalAnd=false] Include optional "en" separator.
6
- * @property {boolean} [noHundredPairs=false] Disable comma before hundreds.
7
- * @property {boolean} [accentOne=true] Use accented "één" for one.
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 language converter.
118
+ * Converts a non-negative integer to Dutch words.
12
119
  *
13
- * Features:
14
- * - Optional "en" (and) separator for tens (includeOptionalAnd)
15
- * - Optional comma before hundreds (noHundredPairs)
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
- export class Dutch extends GreedyScaleLanguage {
19
- negativeWord = 'min'
20
- decimalSeparatorWord = 'komma'
21
- zeroWord = 'nul'
22
- scaleWordPairs = [
23
- [1_000_000_000_000_000_000_000_000_000n, 'quadriljard'],
24
- [1_000_000_000_000_000_000_000_000n, 'quadriljoen'],
25
- [1_000_000_000_000_000_000_000n, 'triljard'],
26
- [1_000_000_000_000_000_000n, 'triljoen'],
27
- [1_000_000_000_000_000n, 'biljard'],
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
- * Merges two adjacent word-number pairs according to Dutch grammar rules.
83
- *
84
- * Dutch-specific rules:
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
- // Handle multiplication and spacing
115
- if (nNumber > cNumber) {
116
- let hadSpace = false
117
- // Large scale words (millions+): add space before multiplier
118
- if (nNumber >= 1_000_000n) {
119
- cText += ' '
120
- hadSpace = true
121
- } else if (nNumber > 100n) {
122
- // Hundreds and above: add space after multiplier
123
- nText += ' '
124
- hadSpace = true
125
- }
126
- // Convert 'één' to 'een' in compounds (when no space or accentOne disabled)
127
- if (!hadSpace || !this.accentOne) {
128
- cText = cText.replace(/één/g, 'een')
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 { [`${cText}${nText}`]: cNumber * nNumber }
159
+ return applyAccent(result)
132
160
  }
161
+ }
133
162
 
134
- // Track if we're adding a space (which keeps words separate)
135
- let hasSpace = false
136
-
137
- if (nNumber < 10n && cNumber > 10n && cNumber < 100n) {
138
- const temporary = nText
139
- nText = cText
140
- const andTxt = temporary.endsWith('e') ? 'ën' : 'en'
141
- cText = `${temporary}${andTxt}`
142
- } else if (nNumber < 13n && cNumber < 1000n && this.includeOptionalAnd) {
143
- cText = `${cText}en`
144
- } else if (nNumber < 13n && cNumber >= 1000n && this.includeOptionalAnd) {
145
- nText = ` en ${nText}`
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
- // Convert 'één' to 'een' in direct compounds (no space)
156
- // Keep 'één' only if there's a space AND accentOne=true
157
- if (!hasSpace) {
158
- cText = cText.replace(/één/g, 'een')
159
- nText = nText.replace(/één/g, 'een')
160
- } else if (!this.accentOne) {
161
- cText = cText.replace(/één/g, 'een')
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 { [`${cText}${nText}`]: cNumber + nNumber }
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
- convertWholePart (value) {
169
- if (value >= 1100n && value < 10_000n && !this.noHundredPairs) {
170
- const high = value / 100n
171
- const low = value % 100n
172
- if (high % 10n !== 0n) {
173
- let result = super.convertWholePart(high) + 'honderd'
174
- if (low) {
175
- result +=
176
- (this.includeOptionalAnd ? ' en ' : ' ') + super.convertWholePart(low)
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
- return result
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 a number to Dutch cardinal (written) form.
261
+ * Converts decimal digits to Dutch words.
187
262
  *
188
- * @param {number|string|bigint} value The number to convert.
189
- * @param {Object} [options] Conversion options (see Dutch class options).
190
- * @param {boolean} [options.includeOptionalAnd=false] Include optional 'en' (and) separator.
191
- * @param {boolean} [options.noHundredPairs=false] Don't combine hundreds with tens/units.
192
- * @param {boolean} [options.accentOne=true] Use accented 'één' for standalone 1.
193
- * @returns {string} The number expressed in Dutch words.
194
- * @throws {TypeError} If value is NaN or invalid type.
195
- * @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} 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
- * convertToWords(1); // 'één' (default accent)
199
- * convertToWords(1, { accentOne: false }); // 'een'
200
- * convertToWords(21); // 'eenentwintig' (no accent in compounds)
201
- * convertToWords(42); // 'tweeenveertig'
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
- export default function convertToWords (value, options = {}) {
205
- return new Dutch(options).convertToWords(value)
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 }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a numeric value to Punjabi words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Punjabi words
6
+ */
7
+ export function toWords(value: number | string | bigint): string;