n2words 2.0.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 (327) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +86 -188
  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 -0
  8. package/dist/languages/ar.js.map +1 -0
  9. package/dist/languages/az.js +3 -0
  10. package/dist/languages/az.js.map +1 -0
  11. package/dist/languages/bn.js +3 -0
  12. package/dist/languages/bn.js.map +1 -0
  13. package/dist/languages/cs.js +3 -0
  14. package/dist/languages/cs.js.map +1 -0
  15. package/dist/languages/da.js +3 -0
  16. package/dist/languages/da.js.map +1 -0
  17. package/dist/languages/de.js +3 -0
  18. package/dist/languages/de.js.map +1 -0
  19. package/dist/languages/el.js +3 -0
  20. package/dist/languages/el.js.map +1 -0
  21. package/dist/languages/en.js +3 -0
  22. package/dist/languages/en.js.map +1 -0
  23. package/dist/languages/es.js +3 -0
  24. package/dist/languages/es.js.map +1 -0
  25. package/dist/languages/fa.js +3 -0
  26. package/dist/languages/fa.js.map +1 -0
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -0
  30. package/dist/languages/fil.js.map +1 -0
  31. package/dist/languages/fr-BE.js +3 -0
  32. package/dist/languages/fr-BE.js.map +1 -0
  33. package/dist/languages/fr.js +3 -0
  34. package/dist/languages/fr.js.map +1 -0
  35. package/dist/languages/gu.js +3 -0
  36. package/dist/languages/gu.js.map +1 -0
  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 -0
  42. package/dist/languages/he.js.map +1 -0
  43. package/dist/languages/hi.js +3 -0
  44. package/dist/languages/hi.js.map +1 -0
  45. package/dist/languages/hr.js +3 -0
  46. package/dist/languages/hr.js.map +1 -0
  47. package/dist/languages/hu.js +3 -0
  48. package/dist/languages/hu.js.map +1 -0
  49. package/dist/languages/id.js +3 -0
  50. package/dist/languages/id.js.map +1 -0
  51. package/dist/languages/it.js +3 -0
  52. package/dist/languages/it.js.map +1 -0
  53. package/dist/languages/ja.js +3 -0
  54. package/dist/languages/ja.js.map +1 -0
  55. package/dist/languages/kn.js +3 -0
  56. package/dist/languages/kn.js.map +1 -0
  57. package/dist/languages/ko.js +3 -0
  58. package/dist/languages/ko.js.map +1 -0
  59. package/dist/languages/lt.js +3 -0
  60. package/dist/languages/lt.js.map +1 -0
  61. package/dist/languages/lv.js +3 -0
  62. package/dist/languages/lv.js.map +1 -0
  63. package/dist/languages/mr.js +3 -0
  64. package/dist/languages/mr.js.map +1 -0
  65. package/dist/languages/ms.js +3 -0
  66. package/dist/languages/ms.js.map +1 -0
  67. package/dist/languages/nb.js +3 -0
  68. package/dist/languages/nb.js.map +1 -0
  69. package/dist/languages/nl.js +3 -0
  70. package/dist/languages/nl.js.map +1 -0
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -0
  74. package/dist/languages/pl.js.map +1 -0
  75. package/dist/languages/pt.js +3 -0
  76. package/dist/languages/pt.js.map +1 -0
  77. package/dist/languages/ro.js +3 -0
  78. package/dist/languages/ro.js.map +1 -0
  79. package/dist/languages/ru.js +3 -0
  80. package/dist/languages/ru.js.map +1 -0
  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 -0
  84. package/dist/languages/sr-Latn.js.map +1 -0
  85. package/dist/languages/sv.js +3 -0
  86. package/dist/languages/sv.js.map +1 -0
  87. package/dist/languages/sw.js +3 -0
  88. package/dist/languages/sw.js.map +1 -0
  89. package/dist/languages/ta.js +3 -0
  90. package/dist/languages/ta.js.map +1 -0
  91. package/dist/languages/te.js +3 -0
  92. package/dist/languages/te.js.map +1 -0
  93. package/dist/languages/th.js +3 -0
  94. package/dist/languages/th.js.map +1 -0
  95. package/dist/languages/tr.js +3 -0
  96. package/dist/languages/tr.js.map +1 -0
  97. package/dist/languages/uk.js +3 -0
  98. package/dist/languages/uk.js.map +1 -0
  99. package/dist/languages/ur.js +3 -0
  100. package/dist/languages/ur.js.map +1 -0
  101. package/dist/languages/vi.js +3 -0
  102. package/dist/languages/vi.js.map +1 -0
  103. package/dist/languages/zh-Hans.js +3 -0
  104. package/dist/languages/zh-Hans.js.map +1 -0
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +2 -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 +14 -27
  114. package/lib/languages/ar.js +175 -129
  115. package/lib/languages/az.d.ts +4 -9
  116. package/lib/languages/az.js +171 -37
  117. package/lib/languages/bn.d.ts +4 -8
  118. package/lib/languages/bn.js +138 -124
  119. package/lib/languages/cs.d.ts +15 -85
  120. package/lib/languages/cs.js +310 -114
  121. package/lib/languages/da.d.ts +11 -12
  122. package/lib/languages/da.js +276 -101
  123. package/lib/languages/de.d.ts +14 -11
  124. package/lib/languages/de.js +317 -86
  125. package/lib/languages/el.d.ts +11 -11
  126. package/lib/languages/el.js +231 -78
  127. package/lib/languages/en.d.ts +14 -13
  128. package/lib/languages/en.js +242 -72
  129. package/lib/languages/es.d.ts +18 -12
  130. package/lib/languages/es.js +317 -103
  131. package/lib/languages/fa.d.ts +4 -44
  132. package/lib/languages/fa.js +112 -122
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +4 -13
  136. package/lib/languages/fil.js +207 -106
  137. package/lib/languages/fr-BE.d.ts +8 -8
  138. package/lib/languages/fr-BE.js +294 -19
  139. package/lib/languages/fr.d.ts +18 -12
  140. package/lib/languages/fr.js +352 -89
  141. package/lib/languages/gu.d.ts +4 -8
  142. package/lib/languages/gu.js +130 -125
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +10 -110
  146. package/lib/languages/hbo.js +263 -214
  147. package/lib/languages/he.d.ts +10 -77
  148. package/lib/languages/he.js +242 -172
  149. package/lib/languages/hi.d.ts +4 -8
  150. package/lib/languages/hi.js +138 -124
  151. package/lib/languages/hr.d.ts +8 -77
  152. package/lib/languages/hr.js +194 -89
  153. package/lib/languages/hu.d.ts +4 -19
  154. package/lib/languages/hu.js +198 -119
  155. package/lib/languages/id.d.ts +4 -34
  156. package/lib/languages/id.js +171 -129
  157. package/lib/languages/it.d.ts +16 -34
  158. package/lib/languages/it.js +339 -94
  159. package/lib/languages/ja.d.ts +14 -14
  160. package/lib/languages/ja.js +233 -111
  161. package/lib/languages/kn.d.ts +4 -8
  162. package/lib/languages/kn.js +130 -35
  163. package/lib/languages/ko.d.ts +11 -11
  164. package/lib/languages/ko.js +257 -49
  165. package/lib/languages/lt.d.ts +15 -67
  166. package/lib/languages/lt.js +296 -122
  167. package/lib/languages/lv.d.ts +15 -67
  168. package/lib/languages/lv.js +297 -106
  169. package/lib/languages/mr.d.ts +4 -8
  170. package/lib/languages/mr.js +130 -125
  171. package/lib/languages/ms.d.ts +4 -28
  172. package/lib/languages/ms.js +171 -116
  173. package/lib/languages/nb.d.ts +11 -9
  174. package/lib/languages/nb.js +282 -87
  175. package/lib/languages/nl.d.ts +23 -13
  176. package/lib/languages/nl.js +317 -133
  177. package/lib/languages/pa.d.ts +4 -8
  178. package/lib/languages/pa.js +156 -124
  179. package/lib/languages/pl.d.ts +19 -77
  180. package/lib/languages/pl.js +307 -87
  181. package/lib/languages/pt.d.ts +14 -26
  182. package/lib/languages/pt.js +286 -92
  183. package/lib/languages/ro.d.ts +15 -155
  184. package/lib/languages/ro.js +219 -235
  185. package/lib/languages/ru.d.ts +8 -82
  186. package/lib/languages/ru.js +222 -78
  187. package/lib/languages/sr-Cyrl.d.ts +8 -77
  188. package/lib/languages/sr-Cyrl.js +191 -89
  189. package/lib/languages/sr-Latn.d.ts +8 -77
  190. package/lib/languages/sr-Latn.js +191 -89
  191. package/lib/languages/sv.d.ts +11 -11
  192. package/lib/languages/sv.js +288 -74
  193. package/lib/languages/sw.d.ts +4 -36
  194. package/lib/languages/sw.js +133 -106
  195. package/lib/languages/ta.d.ts +4 -17
  196. package/lib/languages/ta.js +129 -201
  197. package/lib/languages/te.d.ts +4 -19
  198. package/lib/languages/te.js +141 -196
  199. package/lib/languages/th.d.ts +4 -14
  200. package/lib/languages/th.js +135 -91
  201. package/lib/languages/tr.d.ts +15 -9
  202. package/lib/languages/tr.js +256 -49
  203. package/lib/languages/uk.d.ts +8 -82
  204. package/lib/languages/uk.js +200 -78
  205. package/lib/languages/ur.d.ts +4 -8
  206. package/lib/languages/ur.js +156 -124
  207. package/lib/languages/vi.d.ts +14 -69
  208. package/lib/languages/vi.js +294 -125
  209. package/lib/languages/zh-Hans.d.ts +8 -18
  210. package/lib/languages/zh-Hans.js +163 -92
  211. package/lib/languages/zh-Hant.d.ts +8 -18
  212. package/lib/languages/zh-Hant.js +181 -90
  213. package/lib/n2words.d.ts +53 -209
  214. package/lib/n2words.js +111 -530
  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 +26 -14
  222. package/dist/ArabicConverter.js +0 -3
  223. package/dist/ArabicConverter.js.map +0 -1
  224. package/dist/AzerbaijaniConverter.js +0 -3
  225. package/dist/AzerbaijaniConverter.js.map +0 -1
  226. package/dist/BanglaConverter.js +0 -3
  227. package/dist/BanglaConverter.js.map +0 -1
  228. package/dist/BiblicalHebrewConverter.js +0 -3
  229. package/dist/BiblicalHebrewConverter.js.map +0 -1
  230. package/dist/CroatianConverter.js +0 -3
  231. package/dist/CroatianConverter.js.map +0 -1
  232. package/dist/CzechConverter.js +0 -3
  233. package/dist/CzechConverter.js.map +0 -1
  234. package/dist/DanishConverter.js +0 -3
  235. package/dist/DanishConverter.js.map +0 -1
  236. package/dist/DutchConverter.js +0 -3
  237. package/dist/DutchConverter.js.map +0 -1
  238. package/dist/EnglishConverter.js +0 -3
  239. package/dist/EnglishConverter.js.map +0 -1
  240. package/dist/FilipinoConverter.js +0 -3
  241. package/dist/FilipinoConverter.js.map +0 -1
  242. package/dist/FrenchBelgiumConverter.js +0 -3
  243. package/dist/FrenchBelgiumConverter.js.map +0 -1
  244. package/dist/FrenchConverter.js +0 -3
  245. package/dist/FrenchConverter.js.map +0 -1
  246. package/dist/GermanConverter.js +0 -3
  247. package/dist/GermanConverter.js.map +0 -1
  248. package/dist/GreekConverter.js +0 -3
  249. package/dist/GreekConverter.js.map +0 -1
  250. package/dist/GujaratiConverter.js +0 -3
  251. package/dist/GujaratiConverter.js.map +0 -1
  252. package/dist/HebrewConverter.js +0 -3
  253. package/dist/HebrewConverter.js.map +0 -1
  254. package/dist/HindiConverter.js +0 -3
  255. package/dist/HindiConverter.js.map +0 -1
  256. package/dist/HungarianConverter.js +0 -3
  257. package/dist/HungarianConverter.js.map +0 -1
  258. package/dist/IndonesianConverter.js +0 -3
  259. package/dist/IndonesianConverter.js.map +0 -1
  260. package/dist/ItalianConverter.js +0 -3
  261. package/dist/ItalianConverter.js.map +0 -1
  262. package/dist/JapaneseConverter.js +0 -3
  263. package/dist/JapaneseConverter.js.map +0 -1
  264. package/dist/KannadaConverter.js +0 -3
  265. package/dist/KannadaConverter.js.map +0 -1
  266. package/dist/KoreanConverter.js +0 -3
  267. package/dist/KoreanConverter.js.map +0 -1
  268. package/dist/LatvianConverter.js +0 -3
  269. package/dist/LatvianConverter.js.map +0 -1
  270. package/dist/LithuanianConverter.js +0 -3
  271. package/dist/LithuanianConverter.js.map +0 -1
  272. package/dist/MalayConverter.js +0 -3
  273. package/dist/MalayConverter.js.map +0 -1
  274. package/dist/MarathiConverter.js +0 -3
  275. package/dist/MarathiConverter.js.map +0 -1
  276. package/dist/NorwegianBokmalConverter.js +0 -3
  277. package/dist/NorwegianBokmalConverter.js.map +0 -1
  278. package/dist/PersianConverter.js +0 -3
  279. package/dist/PersianConverter.js.map +0 -1
  280. package/dist/PolishConverter.js +0 -3
  281. package/dist/PolishConverter.js.map +0 -1
  282. package/dist/PortugueseConverter.js +0 -3
  283. package/dist/PortugueseConverter.js.map +0 -1
  284. package/dist/PunjabiConverter.js +0 -3
  285. package/dist/PunjabiConverter.js.map +0 -1
  286. package/dist/RomanianConverter.js +0 -3
  287. package/dist/RomanianConverter.js.map +0 -1
  288. package/dist/RussianConverter.js +0 -3
  289. package/dist/RussianConverter.js.map +0 -1
  290. package/dist/SerbianCyrillicConverter.js +0 -3
  291. package/dist/SerbianCyrillicConverter.js.map +0 -1
  292. package/dist/SerbianLatinConverter.js +0 -3
  293. package/dist/SerbianLatinConverter.js.map +0 -1
  294. package/dist/SimplifiedChineseConverter.js +0 -3
  295. package/dist/SimplifiedChineseConverter.js.map +0 -1
  296. package/dist/SpanishConverter.js +0 -3
  297. package/dist/SpanishConverter.js.map +0 -1
  298. package/dist/SwahiliConverter.js +0 -3
  299. package/dist/SwahiliConverter.js.map +0 -1
  300. package/dist/SwedishConverter.js +0 -3
  301. package/dist/SwedishConverter.js.map +0 -1
  302. package/dist/TamilConverter.js +0 -3
  303. package/dist/TamilConverter.js.map +0 -1
  304. package/dist/TeluguConverter.js +0 -3
  305. package/dist/TeluguConverter.js.map +0 -1
  306. package/dist/ThaiConverter.js +0 -3
  307. package/dist/ThaiConverter.js.map +0 -1
  308. package/dist/TraditionalChineseConverter.js +0 -3
  309. package/dist/TraditionalChineseConverter.js.map +0 -1
  310. package/dist/TurkishConverter.js +0 -3
  311. package/dist/TurkishConverter.js.map +0 -1
  312. package/dist/UkrainianConverter.js +0 -3
  313. package/dist/UkrainianConverter.js.map +0 -1
  314. package/dist/UrduConverter.js +0 -3
  315. package/dist/UrduConverter.js.map +0 -1
  316. package/dist/VietnameseConverter.js +0 -3
  317. package/dist/VietnameseConverter.js.map +0 -1
  318. package/lib/classes/abstract-language.d.ts +0 -178
  319. package/lib/classes/abstract-language.js +0 -268
  320. package/lib/classes/greedy-scale-language.d.ts +0 -109
  321. package/lib/classes/greedy-scale-language.js +0 -201
  322. package/lib/classes/slavic-language.d.ts +0 -148
  323. package/lib/classes/slavic-language.js +0 -281
  324. package/lib/classes/south-asian-language.d.ts +0 -70
  325. package/lib/classes/south-asian-language.js +0 -154
  326. package/lib/classes/turkic-language.d.ts +0 -26
  327. package/lib/classes/turkic-language.js +0 -59
@@ -1,155 +1,339 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
2
-
3
1
  /**
4
- * Dutch language converter.
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.
5
9
  *
6
- * Supports:
7
- * - Optional "en" (and) separator for tens
8
- * - Optional comma before hundreds
9
- * - Compound word formation without hyphenation
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
10
18
  */
11
- export class Dutch extends GreedyScaleLanguage {
12
- negativeWord = 'min'
13
- decimalSeparatorWord = 'komma'
14
- zeroWord = 'nul'
15
-
16
- scaleWords = [
17
- [1_000_000_000_000_000_000_000_000_000n, 'quadriljard'],
18
- [1_000_000_000_000_000_000_000_000n, 'quadriljoen'],
19
- [1_000_000_000_000_000_000_000n, 'triljard'],
20
- [1_000_000_000_000_000_000n, 'triljoen'],
21
- [1_000_000_000_000_000n, 'biljard'],
22
- [1_000_000_000_000n, 'biljoen'],
23
- [1_000_000_000n, 'miljard'],
24
- [1_000_000n, 'miljoen'],
25
- [1000n, 'duizend'],
26
- [100n, 'honderd'],
27
- [90n, 'negentig'],
28
- [80n, 'tachtig'],
29
- [70n, 'zeventig'],
30
- [60n, 'zestig'],
31
- [50n, 'vijftig'],
32
- [40n, 'veertig'],
33
- [30n, 'dertig'],
34
- [20n, 'twintig'],
35
- [19n, 'negentien'],
36
- [18n, 'achttien'],
37
- [17n, 'zeventien'],
38
- [16n, 'zestien'],
39
- [15n, 'vijftien'],
40
- [14n, 'veertien'],
41
- [13n, 'dertien'],
42
- [12n, 'twaalf'],
43
- [11n, 'elf'],
44
- [10n, 'tien'],
45
- [9n, 'negen'],
46
- [8n, 'acht'],
47
- [7n, 'zeven'],
48
- [6n, 'zes'],
49
- [5n, 'vijf'],
50
- [4n, 'vier'],
51
- [3n, 'drie'],
52
- [2n, 'twee'],
53
- [1n, 'één'],
54
- [0n, 'nul']
55
- ]
56
-
57
- constructor (options = {}) {
58
- super()
59
-
60
- this.setOptions({
61
- includeOptionalAnd: false,
62
- noHundredPairing: false,
63
- accentOne: true
64
- }, options)
65
-
66
- if (!this.options.accentOne) {
67
- this.scaleWords[this.scaleWords.length - 2][1] = 'een'
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
+ // ============================================================================
43
+
44
+ /**
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)
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
68
66
  }
69
67
  }
70
68
 
71
- /** Combines two word-sets according to Dutch grammar rules. */
72
- combineWordSets (preceding, following) {
73
- let precedingWord = Object.keys(preceding)[0]
74
- let followingWord = Object.keys(following)[0]
75
- const precedingValue = Object.values(preceding)[0] // BigInt
76
- const followingValue = Object.values(following)[0] // BigInt
77
-
78
- // Implicit "een": omit before large multipliers ("miljoen" not "een miljoen")
79
- if (precedingValue === 1n) {
80
- if (followingValue < 1_000_000n) {
81
- return following
82
- }
83
- precedingWord = this.options.accentOne ? 'één' : 'een'
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]
84
95
  }
96
+ }
85
97
 
86
- // Handle multiplication and spacing
87
- if (followingValue > precedingValue) {
88
- let hadSpace = false
89
- // Large scale words (millions+): add space before multiplier
90
- if (followingValue >= 1_000_000n) {
91
- precedingWord += ' '
92
- hadSpace = true
93
- } else if (followingValue > 100n) {
94
- // Hundreds and above: add space after multiplier
95
- followingWord += ' '
96
- hadSpace = true
97
- }
98
- // Convert 'één' to 'een' in compounds (when no space or accentOne disabled)
99
- if (!hadSpace || !this.options.accentOne) {
100
- precedingWord = precedingWord.replace(/één/g, 'een')
101
- followingWord = followingWord.replace(/één/g, 'een')
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
+ // ============================================================================
116
+
117
+ /**
118
+ * Converts a non-negative integer to Dutch words.
119
+ *
120
+ * @param {bigint} n - Non-negative integer to convert
121
+ * @param {Object} options - Conversion options
122
+ * @returns {string} Dutch words
123
+ */
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')
134
+ }
135
+ return word
136
+ }
137
+
138
+ // Fast path: numbers < 1000 (direct lookup)
139
+ if (n < 1000n) {
140
+ return applyAccent(segments[Number(n)])
141
+ }
142
+
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
+ }
102
158
  }
103
- return { [`${precedingWord}${followingWord}`]: precedingValue * followingValue }
159
+ return applyAccent(result)
104
160
  }
161
+ }
162
+
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)
105
167
 
106
- // Track if we're adding a space (which keeps words separate)
107
- let hasSpace = false
108
-
109
- if (followingValue < 10n && precedingValue > 10n && precedingValue < 100n) {
110
- const temporary = followingWord
111
- followingWord = precedingWord
112
- const andTxt = temporary.endsWith('e') ? 'ën' : 'en'
113
- precedingWord = `${temporary}${andTxt}`
114
- } else if (followingValue < 13n && precedingValue < 1000n && this.options.includeOptionalAnd) {
115
- precedingWord = `${precedingWord}en`
116
- } else if (followingValue < 13n && precedingValue >= 1000n && this.options.includeOptionalAnd) {
117
- followingWord = ` en ${followingWord}`
118
- hasSpace = true
119
- } else if (precedingValue >= 1_000_000n) {
120
- precedingWord += ' '
121
- hasSpace = true
122
- } else if (precedingValue === 1000n) {
123
- precedingWord += ' '
124
- hasSpace = true
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]
125
175
  }
126
176
 
127
- // Convert 'één' to 'een' in direct compounds (no space)
128
- // Keep 'één' only if there's a space AND accentOne=true
129
- if (!hasSpace) {
130
- precedingWord = precedingWord.replace(/één/g, 'een')
131
- followingWord = followingWord.replace(/één/g, 'een')
132
- } else if (!this.options.accentOne) {
133
- precedingWord = precedingWord.replace(/één/g, 'een')
134
- followingWord = followingWord.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
+ }
135
184
  }
136
185
 
137
- return { [`${precedingWord}${followingWord}`]: precedingValue + followingValue }
186
+ return applyAccent(result)
138
187
  }
139
188
 
140
- integerToWords (integerPart) {
141
- if (integerPart >= 1100n && integerPart < 10_000n && !this.options.noHundredPairing) {
142
- const high = integerPart / 100n
143
- const low = integerPart % 100n
144
- if (high % 10n !== 0n) {
145
- let result = super.integerToWords(high) + 'honderd'
146
- if (low) {
147
- result +=
148
- (this.options.includeOptionalAnd ? ' en ' : ' ') + super.integerToWords(low)
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
212
+ }
213
+
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
149
230
  }
150
- return result
231
+ } else {
232
+ result = word
151
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]
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
152
254
  }
153
- return super.integerToWords(integerPart)
154
255
  }
256
+
257
+ return result
258
+ }
259
+
260
+ /**
261
+ * Converts decimal digits to Dutch words.
262
+ *
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
155
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
303
+ *
304
+ * @example
305
+ * toWords(21) // 'eenentwintig'
306
+ * toWords(1) // 'één'
307
+ * toWords(1, {accentOne: false}) // 'een'
308
+ * toWords(1104) // 'elfhonderd vier'
309
+ */
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
333
+ }
334
+
335
+ // ============================================================================
336
+ // Public API
337
+ // ============================================================================
338
+
339
+ export { toWords }
@@ -1,11 +1,7 @@
1
1
  /**
2
- * Punjabi language converter.
2
+ * Converts a numeric value to Punjabi words.
3
3
  *
4
- * Supports:
5
- * - Indian numbering system (ਹਜ਼ਾਰ, ਲੱਖ, ਕਰੋੜ)
6
- * - Gurmukhi script
7
- * - Complete word forms for 0-99
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Punjabi words
8
6
  */
9
- export class Punjabi extends SouthAsianLanguage {
10
- }
11
- import { SouthAsianLanguage } from '../classes/south-asian-language.js';
7
+ export function toWords(value: number | string | bigint): string;