n2words 2.0.0 → 3.1.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 (335) hide show
  1. package/CHANGELOG.md +64 -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/ka.js +3 -0
  56. package/dist/languages/ka.js.map +1 -0
  57. package/dist/languages/kn.js +3 -0
  58. package/dist/languages/kn.js.map +1 -0
  59. package/dist/languages/ko.js +3 -0
  60. package/dist/languages/ko.js.map +1 -0
  61. package/dist/languages/lt.js +3 -0
  62. package/dist/languages/lt.js.map +1 -0
  63. package/dist/languages/lv.js +3 -0
  64. package/dist/languages/lv.js.map +1 -0
  65. package/dist/languages/mr.js +3 -0
  66. package/dist/languages/mr.js.map +1 -0
  67. package/dist/languages/ms.js +3 -0
  68. package/dist/languages/ms.js.map +1 -0
  69. package/dist/languages/nb.js +3 -0
  70. package/dist/languages/nb.js.map +1 -0
  71. package/dist/languages/nl.js +3 -0
  72. package/dist/languages/nl.js.map +1 -0
  73. package/dist/languages/pa.js +3 -0
  74. package/dist/languages/pa.js.map +1 -0
  75. package/dist/languages/pl.js +3 -0
  76. package/dist/languages/pl.js.map +1 -0
  77. package/dist/languages/pt.js +3 -0
  78. package/dist/languages/pt.js.map +1 -0
  79. package/dist/languages/ro.js +3 -0
  80. package/dist/languages/ro.js.map +1 -0
  81. package/dist/languages/ru.js +3 -0
  82. package/dist/languages/ru.js.map +1 -0
  83. package/dist/languages/sr-Cyrl.js +3 -0
  84. package/dist/languages/sr-Cyrl.js.map +1 -0
  85. package/dist/languages/sr-Latn.js +3 -0
  86. package/dist/languages/sr-Latn.js.map +1 -0
  87. package/dist/languages/sv.js +3 -0
  88. package/dist/languages/sv.js.map +1 -0
  89. package/dist/languages/sw.js +3 -0
  90. package/dist/languages/sw.js.map +1 -0
  91. package/dist/languages/ta.js +3 -0
  92. package/dist/languages/ta.js.map +1 -0
  93. package/dist/languages/te.js +3 -0
  94. package/dist/languages/te.js.map +1 -0
  95. package/dist/languages/th.js +3 -0
  96. package/dist/languages/th.js.map +1 -0
  97. package/dist/languages/tr.js +3 -0
  98. package/dist/languages/tr.js.map +1 -0
  99. package/dist/languages/uk.js +3 -0
  100. package/dist/languages/uk.js.map +1 -0
  101. package/dist/languages/ur.js +3 -0
  102. package/dist/languages/ur.js.map +1 -0
  103. package/dist/languages/vi.js +3 -0
  104. package/dist/languages/vi.js.map +1 -0
  105. package/dist/languages/yo.js +3 -0
  106. package/dist/languages/yo.js.map +1 -0
  107. package/dist/languages/zh-Hans.js +3 -0
  108. package/dist/languages/zh-Hans.js.map +1 -0
  109. package/dist/languages/zh-Hant.js +3 -0
  110. package/dist/languages/zh-Hant.js.map +1 -0
  111. package/dist/n2words.js +2 -2
  112. package/dist/n2words.js.map +1 -1
  113. package/lib/languages/am-Latn.d.ts +7 -0
  114. package/lib/languages/am-Latn.js +159 -0
  115. package/lib/languages/am.d.ts +7 -0
  116. package/lib/languages/am.js +159 -0
  117. package/lib/languages/ar.d.ts +14 -27
  118. package/lib/languages/ar.js +175 -129
  119. package/lib/languages/az.d.ts +4 -9
  120. package/lib/languages/az.js +166 -37
  121. package/lib/languages/bn.d.ts +4 -8
  122. package/lib/languages/bn.js +159 -124
  123. package/lib/languages/cs.d.ts +15 -85
  124. package/lib/languages/cs.js +293 -114
  125. package/lib/languages/da.d.ts +11 -12
  126. package/lib/languages/da.js +269 -101
  127. package/lib/languages/de.d.ts +14 -11
  128. package/lib/languages/de.js +305 -86
  129. package/lib/languages/el.d.ts +11 -11
  130. package/lib/languages/el.js +224 -78
  131. package/lib/languages/en.d.ts +14 -13
  132. package/lib/languages/en.js +228 -72
  133. package/lib/languages/es.d.ts +18 -12
  134. package/lib/languages/es.js +297 -103
  135. package/lib/languages/fa.d.ts +4 -44
  136. package/lib/languages/fa.js +112 -122
  137. package/lib/languages/fi.d.ts +14 -0
  138. package/lib/languages/fi.js +238 -0
  139. package/lib/languages/fil.d.ts +4 -13
  140. package/lib/languages/fil.js +196 -106
  141. package/lib/languages/fr-BE.d.ts +8 -8
  142. package/lib/languages/fr-BE.js +285 -19
  143. package/lib/languages/fr.d.ts +18 -12
  144. package/lib/languages/fr.js +339 -89
  145. package/lib/languages/gu.d.ts +4 -8
  146. package/lib/languages/gu.js +143 -125
  147. package/lib/languages/ha.d.ts +7 -0
  148. package/lib/languages/ha.js +225 -0
  149. package/lib/languages/hbo.d.ts +10 -110
  150. package/lib/languages/hbo.js +245 -214
  151. package/lib/languages/he.d.ts +10 -77
  152. package/lib/languages/he.js +231 -172
  153. package/lib/languages/hi.d.ts +4 -8
  154. package/lib/languages/hi.js +163 -124
  155. package/lib/languages/hr.d.ts +8 -77
  156. package/lib/languages/hr.js +200 -89
  157. package/lib/languages/hu.d.ts +4 -19
  158. package/lib/languages/hu.js +198 -119
  159. package/lib/languages/id.d.ts +4 -34
  160. package/lib/languages/id.js +166 -129
  161. package/lib/languages/it.d.ts +16 -34
  162. package/lib/languages/it.js +307 -97
  163. package/lib/languages/ja.d.ts +14 -14
  164. package/lib/languages/ja.js +221 -111
  165. package/lib/languages/ka.d.ts +17 -0
  166. package/lib/languages/ka.js +291 -0
  167. package/lib/languages/kn.d.ts +4 -8
  168. package/lib/languages/kn.js +143 -35
  169. package/lib/languages/ko.d.ts +11 -11
  170. package/lib/languages/ko.js +250 -49
  171. package/lib/languages/lt.d.ts +15 -67
  172. package/lib/languages/lt.js +287 -122
  173. package/lib/languages/lv.d.ts +15 -67
  174. package/lib/languages/lv.js +288 -106
  175. package/lib/languages/mr.d.ts +4 -8
  176. package/lib/languages/mr.js +143 -125
  177. package/lib/languages/ms.d.ts +4 -28
  178. package/lib/languages/ms.js +166 -116
  179. package/lib/languages/nb.d.ts +11 -9
  180. package/lib/languages/nb.js +272 -87
  181. package/lib/languages/nl.d.ts +23 -13
  182. package/lib/languages/nl.js +299 -133
  183. package/lib/languages/pa.d.ts +4 -8
  184. package/lib/languages/pa.js +151 -124
  185. package/lib/languages/pl.d.ts +19 -77
  186. package/lib/languages/pl.js +294 -87
  187. package/lib/languages/pt.d.ts +14 -26
  188. package/lib/languages/pt.js +272 -92
  189. package/lib/languages/ro.d.ts +15 -155
  190. package/lib/languages/ro.js +219 -235
  191. package/lib/languages/ru.d.ts +8 -82
  192. package/lib/languages/ru.js +239 -90
  193. package/lib/languages/sr-Cyrl.d.ts +8 -77
  194. package/lib/languages/sr-Cyrl.js +197 -89
  195. package/lib/languages/sr-Latn.d.ts +8 -77
  196. package/lib/languages/sr-Latn.js +197 -89
  197. package/lib/languages/sv.d.ts +11 -11
  198. package/lib/languages/sv.js +278 -74
  199. package/lib/languages/sw.d.ts +4 -36
  200. package/lib/languages/sw.js +133 -106
  201. package/lib/languages/ta.d.ts +4 -17
  202. package/lib/languages/ta.js +143 -202
  203. package/lib/languages/te.d.ts +4 -19
  204. package/lib/languages/te.js +133 -196
  205. package/lib/languages/th.d.ts +4 -14
  206. package/lib/languages/th.js +135 -91
  207. package/lib/languages/tr.d.ts +15 -9
  208. package/lib/languages/tr.js +245 -49
  209. package/lib/languages/uk.d.ts +8 -82
  210. package/lib/languages/uk.js +206 -78
  211. package/lib/languages/ur.d.ts +4 -8
  212. package/lib/languages/ur.js +151 -124
  213. package/lib/languages/vi.d.ts +14 -69
  214. package/lib/languages/vi.js +278 -129
  215. package/lib/languages/yo.d.ts +7 -0
  216. package/lib/languages/yo.js +303 -0
  217. package/lib/languages/zh-Hans.d.ts +8 -18
  218. package/lib/languages/zh-Hans.js +163 -92
  219. package/lib/languages/zh-Hant.d.ts +8 -18
  220. package/lib/languages/zh-Hant.js +181 -90
  221. package/lib/n2words.d.ts +55 -209
  222. package/lib/n2words.js +115 -530
  223. package/lib/utils/is-plain-object.d.ts +13 -0
  224. package/lib/utils/is-plain-object.js +17 -0
  225. package/lib/utils/parse-numeric.d.ts +17 -0
  226. package/lib/utils/parse-numeric.js +108 -0
  227. package/lib/utils/validate-options.d.ts +8 -0
  228. package/lib/utils/validate-options.js +16 -0
  229. package/package.json +26 -14
  230. package/dist/ArabicConverter.js +0 -3
  231. package/dist/ArabicConverter.js.map +0 -1
  232. package/dist/AzerbaijaniConverter.js +0 -3
  233. package/dist/AzerbaijaniConverter.js.map +0 -1
  234. package/dist/BanglaConverter.js +0 -3
  235. package/dist/BanglaConverter.js.map +0 -1
  236. package/dist/BiblicalHebrewConverter.js +0 -3
  237. package/dist/BiblicalHebrewConverter.js.map +0 -1
  238. package/dist/CroatianConverter.js +0 -3
  239. package/dist/CroatianConverter.js.map +0 -1
  240. package/dist/CzechConverter.js +0 -3
  241. package/dist/CzechConverter.js.map +0 -1
  242. package/dist/DanishConverter.js +0 -3
  243. package/dist/DanishConverter.js.map +0 -1
  244. package/dist/DutchConverter.js +0 -3
  245. package/dist/DutchConverter.js.map +0 -1
  246. package/dist/EnglishConverter.js +0 -3
  247. package/dist/EnglishConverter.js.map +0 -1
  248. package/dist/FilipinoConverter.js +0 -3
  249. package/dist/FilipinoConverter.js.map +0 -1
  250. package/dist/FrenchBelgiumConverter.js +0 -3
  251. package/dist/FrenchBelgiumConverter.js.map +0 -1
  252. package/dist/FrenchConverter.js +0 -3
  253. package/dist/FrenchConverter.js.map +0 -1
  254. package/dist/GermanConverter.js +0 -3
  255. package/dist/GermanConverter.js.map +0 -1
  256. package/dist/GreekConverter.js +0 -3
  257. package/dist/GreekConverter.js.map +0 -1
  258. package/dist/GujaratiConverter.js +0 -3
  259. package/dist/GujaratiConverter.js.map +0 -1
  260. package/dist/HebrewConverter.js +0 -3
  261. package/dist/HebrewConverter.js.map +0 -1
  262. package/dist/HindiConverter.js +0 -3
  263. package/dist/HindiConverter.js.map +0 -1
  264. package/dist/HungarianConverter.js +0 -3
  265. package/dist/HungarianConverter.js.map +0 -1
  266. package/dist/IndonesianConverter.js +0 -3
  267. package/dist/IndonesianConverter.js.map +0 -1
  268. package/dist/ItalianConverter.js +0 -3
  269. package/dist/ItalianConverter.js.map +0 -1
  270. package/dist/JapaneseConverter.js +0 -3
  271. package/dist/JapaneseConverter.js.map +0 -1
  272. package/dist/KannadaConverter.js +0 -3
  273. package/dist/KannadaConverter.js.map +0 -1
  274. package/dist/KoreanConverter.js +0 -3
  275. package/dist/KoreanConverter.js.map +0 -1
  276. package/dist/LatvianConverter.js +0 -3
  277. package/dist/LatvianConverter.js.map +0 -1
  278. package/dist/LithuanianConverter.js +0 -3
  279. package/dist/LithuanianConverter.js.map +0 -1
  280. package/dist/MalayConverter.js +0 -3
  281. package/dist/MalayConverter.js.map +0 -1
  282. package/dist/MarathiConverter.js +0 -3
  283. package/dist/MarathiConverter.js.map +0 -1
  284. package/dist/NorwegianBokmalConverter.js +0 -3
  285. package/dist/NorwegianBokmalConverter.js.map +0 -1
  286. package/dist/PersianConverter.js +0 -3
  287. package/dist/PersianConverter.js.map +0 -1
  288. package/dist/PolishConverter.js +0 -3
  289. package/dist/PolishConverter.js.map +0 -1
  290. package/dist/PortugueseConverter.js +0 -3
  291. package/dist/PortugueseConverter.js.map +0 -1
  292. package/dist/PunjabiConverter.js +0 -3
  293. package/dist/PunjabiConverter.js.map +0 -1
  294. package/dist/RomanianConverter.js +0 -3
  295. package/dist/RomanianConverter.js.map +0 -1
  296. package/dist/RussianConverter.js +0 -3
  297. package/dist/RussianConverter.js.map +0 -1
  298. package/dist/SerbianCyrillicConverter.js +0 -3
  299. package/dist/SerbianCyrillicConverter.js.map +0 -1
  300. package/dist/SerbianLatinConverter.js +0 -3
  301. package/dist/SerbianLatinConverter.js.map +0 -1
  302. package/dist/SimplifiedChineseConverter.js +0 -3
  303. package/dist/SimplifiedChineseConverter.js.map +0 -1
  304. package/dist/SpanishConverter.js +0 -3
  305. package/dist/SpanishConverter.js.map +0 -1
  306. package/dist/SwahiliConverter.js +0 -3
  307. package/dist/SwahiliConverter.js.map +0 -1
  308. package/dist/SwedishConverter.js +0 -3
  309. package/dist/SwedishConverter.js.map +0 -1
  310. package/dist/TamilConverter.js +0 -3
  311. package/dist/TamilConverter.js.map +0 -1
  312. package/dist/TeluguConverter.js +0 -3
  313. package/dist/TeluguConverter.js.map +0 -1
  314. package/dist/ThaiConverter.js +0 -3
  315. package/dist/ThaiConverter.js.map +0 -1
  316. package/dist/TraditionalChineseConverter.js +0 -3
  317. package/dist/TraditionalChineseConverter.js.map +0 -1
  318. package/dist/TurkishConverter.js +0 -3
  319. package/dist/TurkishConverter.js.map +0 -1
  320. package/dist/UkrainianConverter.js +0 -3
  321. package/dist/UkrainianConverter.js.map +0 -1
  322. package/dist/UrduConverter.js +0 -3
  323. package/dist/UrduConverter.js.map +0 -1
  324. package/dist/VietnameseConverter.js +0 -3
  325. package/dist/VietnameseConverter.js.map +0 -1
  326. package/lib/classes/abstract-language.d.ts +0 -178
  327. package/lib/classes/abstract-language.js +0 -268
  328. package/lib/classes/greedy-scale-language.d.ts +0 -109
  329. package/lib/classes/greedy-scale-language.js +0 -201
  330. package/lib/classes/slavic-language.d.ts +0 -148
  331. package/lib/classes/slavic-language.js +0 -281
  332. package/lib/classes/south-asian-language.d.ts +0 -70
  333. package/lib/classes/south-asian-language.js +0 -154
  334. package/lib/classes/turkic-language.d.ts +0 -26
  335. package/lib/classes/turkic-language.js +0 -59
@@ -1,155 +1,321 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
2
-
3
1
  /**
4
- * Dutch language converter.
2
+ * Dutch language converter - Functional Implementation
3
+ *
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
- * Supports:
7
- * - Optional "en" (and) separator for tens
8
- * - Optional comma before hundreds
9
- * - Compound word formation without hyphenation
6
+ * Dutch-specific rules:
7
+ * - Inverted tens-ones: eenentwintig (one-and-twenty)
8
+ * - "ën" connector when ones ends in 'e' (twee, drie)
9
+ * - Compound words without spaces
10
+ * - Hundred pairing for 1100-9999 (elfhonderd style)
11
+ * - "één" vs "een" (accentOne option)
12
+ * - Optional "en" separator (includeOptionalAnd option)
13
+ * - Long scale with -ard forms
14
+ */
15
+
16
+ import { parseNumericValue } from '../utils/parse-numeric.js'
17
+ import { validateOptions } from '../utils/validate-options.js'
18
+
19
+ // ============================================================================
20
+ // Vocabulary (module-level constants)
21
+ // ============================================================================
22
+
23
+ const ONES = ['', 'een', 'twee', 'drie', 'vier', 'vijf', 'zes', 'zeven', 'acht', 'negen']
24
+ const TEENS = ['tien', 'elf', 'twaalf', 'dertien', 'veertien', 'vijftien', 'zestien', 'zeventien', 'achttien', 'negentien']
25
+ const TENS = ['', '', 'twintig', 'dertig', 'veertig', 'vijftig', 'zestig', 'zeventig', 'tachtig', 'negentig']
26
+
27
+ const HUNDRED = 'honderd'
28
+
29
+ // Scale words (long scale with -ard forms)
30
+ const SCALES = ['duizend', 'miljoen', 'miljard', 'biljoen', 'biljard', 'triljoen', 'triljard', 'quadriljoen', 'quadriljard']
31
+
32
+ const ZERO = 'nul'
33
+ const NEGATIVE = 'min'
34
+ const DECIMAL_SEP = 'komma'
35
+
36
+ // ============================================================================
37
+ // Segment Building
38
+ // ============================================================================
39
+
40
+ /**
41
+ * Builds segment word for 0-999.
42
+ * @param {number} n - Segment value
43
+ * @param {boolean} withAnd - Include "en" for values < 13 after hundreds
44
+ * @returns {string} Dutch word (compound, no spaces)
10
45
  */
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'
46
+ function buildSegment (n, withAnd) {
47
+ if (n === 0) return ''
48
+
49
+ const ones = n % 10
50
+ const tens = Math.floor(n / 10) % 10
51
+ const hundreds = Math.floor(n / 100)
52
+ const tensOnes = n % 100
53
+
54
+ let result = ''
55
+
56
+ // Hundreds
57
+ if (hundreds > 0) {
58
+ if (hundreds === 1) {
59
+ result = HUNDRED
60
+ } else {
61
+ result = ONES[hundreds] + HUNDRED
68
62
  }
69
63
  }
70
64
 
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'
65
+ // Tens and ones
66
+ if (tensOnes === 0) {
67
+ // Just hundreds
68
+ } else if (tensOnes < 10) {
69
+ // Single digit - add "en" if withAnd and after hundreds
70
+ if (hundreds > 0 && withAnd) {
71
+ result += 'en' + ONES[tensOnes]
72
+ } else {
73
+ result += ONES[tensOnes]
74
+ }
75
+ } else if (tensOnes < 20) {
76
+ // Teens - add "en" if withAnd and after hundreds and < 13
77
+ if (hundreds > 0 && withAnd && tensOnes < 13) {
78
+ result += 'en' + TEENS[ones]
79
+ } else {
80
+ result += TEENS[ones]
81
+ }
82
+ } else {
83
+ // 20-99: Dutch inverts with connector
84
+ if (ones === 0) {
85
+ result += TENS[tens]
86
+ } else {
87
+ // "ën" if ones ends in 'e' (twee, drie)
88
+ const onesWord = ONES[ones]
89
+ const connector = onesWord.endsWith('e') ? 'ën' : 'en'
90
+ result += onesWord + connector + TENS[tens]
84
91
  }
92
+ }
85
93
 
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')
94
+ return result
95
+ }
96
+
97
+ // ============================================================================
98
+ // Conversion Functions
99
+ // ============================================================================
100
+
101
+ /**
102
+ * Converts a non-negative integer to Dutch words.
103
+ *
104
+ * @param {bigint} n - Non-negative integer to convert
105
+ * @param {Object} options - Conversion options
106
+ * @returns {string} Dutch words
107
+ */
108
+ function integerToWords (n, options) {
109
+ if (n === 0n) return ZERO
110
+
111
+ const { accentOne, includeOptionalAnd, noHundredPairing } = options
112
+
113
+ // Apply één/een replacement
114
+ const applyAccent = (word) => {
115
+ if (accentOne) {
116
+ return word.replace(/\been\b/g, 'één')
117
+ }
118
+ return word
119
+ }
120
+
121
+ // Fast path: numbers < 1000
122
+ if (n < 1000n) {
123
+ return applyAccent(buildSegment(Number(n), includeOptionalAnd))
124
+ }
125
+
126
+ // Hundred pairing for 1100-9999
127
+ if (!noHundredPairing && n >= 1100n && n < 10000n) {
128
+ const high = Number(n / 100n)
129
+ const low = Number(n % 100n)
130
+
131
+ // Only use pairing when high is not a multiple of 10
132
+ if (high % 10 !== 0) {
133
+ let result = buildSegment(high, includeOptionalAnd) + HUNDRED
134
+ if (low > 0) {
135
+ const lowWord = buildSegment(low, includeOptionalAnd)
136
+ if (includeOptionalAnd && low < 13) {
137
+ result += ' en ' + lowWord
138
+ } else {
139
+ result += ' ' + lowWord
140
+ }
102
141
  }
103
- return { [`${precedingWord}${followingWord}`]: precedingValue * followingValue }
142
+ return applyAccent(result)
104
143
  }
144
+ }
145
+
146
+ // Fast path: numbers < 1,000,000 (thousands)
147
+ if (n < 1_000_000n) {
148
+ const thousands = Number(n / 1000n)
149
+ const remainder = Number(n % 1000n)
105
150
 
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
151
+ let result
152
+ if (thousands === 1) {
153
+ // "duizend" not "eenduizend"
154
+ result = SCALES[0]
155
+ } else {
156
+ // Compound: "vijfduizend"
157
+ result = buildSegment(thousands, includeOptionalAnd) + SCALES[0]
125
158
  }
126
159
 
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')
160
+ if (remainder > 0) {
161
+ const remainderWord = buildSegment(remainder, includeOptionalAnd)
162
+ if (includeOptionalAnd && remainder < 13) {
163
+ result += ' en ' + remainderWord
164
+ } else {
165
+ result += ' ' + remainderWord
166
+ }
135
167
  }
136
168
 
137
- return { [`${precedingWord}${followingWord}`]: precedingValue + followingValue }
169
+ return applyAccent(result)
138
170
  }
139
171
 
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)
172
+ // For numbers >= 1,000,000, use scale decomposition
173
+ return applyAccent(buildLargeNumberWords(n, options))
174
+ }
175
+
176
+ /**
177
+ * Builds words for numbers >= 1,000,000.
178
+ * Uses BigInt division for faster segment extraction (4x faster than string slicing).
179
+ *
180
+ * @param {bigint} n - Number >= 1,000,000
181
+ * @param {Object} options - Conversion options
182
+ * @returns {string} Dutch words
183
+ */
184
+ function buildLargeNumberWords (n, options) {
185
+ const { includeOptionalAnd } = options
186
+
187
+ // Extract segments using BigInt division (faster than string slicing)
188
+ // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
189
+ const segmentValues = []
190
+ let temp = n
191
+ while (temp > 0n) {
192
+ segmentValues.push(Number(temp % 1000n))
193
+ temp = temp / 1000n
194
+ }
195
+
196
+ // Build result string directly (avoids object allocation and join)
197
+ let result = ''
198
+ let prevWasScale = false
199
+
200
+ for (let i = segmentValues.length - 1; i >= 0; i--) {
201
+ const segment = segmentValues[i]
202
+ if (segment === 0) continue
203
+
204
+ if (i === 0) {
205
+ // Units segment
206
+ const word = buildSegment(segment, includeOptionalAnd)
207
+ if (result) {
208
+ if (prevWasScale && includeOptionalAnd && segment < 13) {
209
+ result += ' en ' + word
210
+ } else {
211
+ result += ' ' + word
149
212
  }
150
- return result
213
+ } else {
214
+ result = word
215
+ }
216
+ prevWasScale = false
217
+ } else if (i === 1) {
218
+ // Thousands - compound
219
+ if (result) result += ' '
220
+ if (segment === 1) {
221
+ result += SCALES[0]
222
+ } else {
223
+ result += buildSegment(segment, includeOptionalAnd) + SCALES[0]
224
+ }
225
+ prevWasScale = true
226
+ } else {
227
+ // Million and above - space around scale
228
+ const scaleWord = SCALES[i - 1]
229
+ if (result) result += ' '
230
+ if (segment === 1) {
231
+ result += 'een ' + scaleWord
232
+ } else {
233
+ result += buildSegment(segment, includeOptionalAnd) + ' ' + scaleWord
151
234
  }
235
+ prevWasScale = true
152
236
  }
153
- return super.integerToWords(integerPart)
154
237
  }
238
+
239
+ return result
155
240
  }
241
+
242
+ /**
243
+ * Converts decimal digits to Dutch words.
244
+ *
245
+ * @param {string} decimalPart - Decimal digits (without the point)
246
+ * @param {Object} options - Conversion options
247
+ * @returns {string} Dutch words for decimal part
248
+ */
249
+ function decimalPartToWords (decimalPart, options) {
250
+ let result = ''
251
+
252
+ // Handle leading zeros
253
+ let i = 0
254
+ while (i < decimalPart.length && decimalPart[i] === '0') {
255
+ if (result) result += ' '
256
+ result += ZERO
257
+ i++
258
+ }
259
+
260
+ // Convert remainder as a single number
261
+ const remainder = decimalPart.slice(i)
262
+ if (remainder) {
263
+ if (result) result += ' '
264
+ const word = integerToWords(BigInt(remainder), { ...options, noHundredPairing: true })
265
+ result += word
266
+ }
267
+
268
+ return result
269
+ }
270
+
271
+ /**
272
+ * Converts a numeric value to Dutch words.
273
+ *
274
+ * This is the main public API. It accepts any valid numeric input
275
+ * (number, string, or bigint) and handles parsing internally.
276
+ *
277
+ * @param {number | string | bigint} value - The numeric value to convert
278
+ * @param {Object} [options] - Optional configuration
279
+ * @param {boolean} [options.accentOne=true] - Use "één" instead of "een"
280
+ * @param {boolean} [options.includeOptionalAnd=false] - Include "en" before small numbers
281
+ * @param {boolean} [options.noHundredPairing=false] - Disable hundred pairing (1104→duizend honderdvier)
282
+ * @returns {string} The number in Dutch words
283
+ * @throws {TypeError} If value is not a valid numeric type
284
+ * @throws {Error} If value is not a valid number format
285
+ *
286
+ * @example
287
+ * toWords(21) // 'eenentwintig'
288
+ * toWords(1) // 'één'
289
+ * toWords(1, {accentOne: false}) // 'een'
290
+ * toWords(1104) // 'elfhonderd vier'
291
+ */
292
+ function toWords (value, options) {
293
+ options = validateOptions(options)
294
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
295
+
296
+ const opts = {
297
+ accentOne: options.accentOne !== false, // default true
298
+ includeOptionalAnd: options.includeOptionalAnd || false,
299
+ noHundredPairing: options.noHundredPairing || false
300
+ }
301
+
302
+ let result = ''
303
+
304
+ if (isNegative) {
305
+ result = NEGATIVE + ' '
306
+ }
307
+
308
+ result += integerToWords(integerPart, opts)
309
+
310
+ if (decimalPart) {
311
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, opts)
312
+ }
313
+
314
+ return result
315
+ }
316
+
317
+ // ============================================================================
318
+ // Public API
319
+ // ============================================================================
320
+
321
+ 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;