n2words 5.0.0 → 5.1.1

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 (309) hide show
  1. package/CHANGELOG.md +133 -40
  2. package/README.md +6 -4
  3. package/dist/am-ET.js +2 -2
  4. package/dist/am-ET.umd.js +2 -2
  5. package/dist/am-Latn-ET.js +2 -2
  6. package/dist/am-Latn-ET.umd.js +2 -2
  7. package/dist/ar-SA.js +2 -2
  8. package/dist/ar-SA.umd.js +2 -2
  9. package/dist/az-AZ.js +2 -2
  10. package/dist/az-AZ.umd.js +2 -2
  11. package/dist/bn-BD.js +2 -2
  12. package/dist/bn-BD.umd.js +2 -2
  13. package/dist/cs-CZ.js +2 -2
  14. package/dist/cs-CZ.umd.js +2 -2
  15. package/dist/da-DK.js +2 -2
  16. package/dist/da-DK.umd.js +2 -2
  17. package/dist/de-DE.js +2 -2
  18. package/dist/de-DE.umd.js +2 -2
  19. package/dist/el-GR.js +2 -2
  20. package/dist/el-GR.umd.js +2 -2
  21. package/dist/en-AU.js +2 -2
  22. package/dist/en-AU.umd.js +2 -2
  23. package/dist/en-BD.js +2 -2
  24. package/dist/en-BD.umd.js +2 -2
  25. package/dist/en-CA.js +2 -2
  26. package/dist/en-CA.umd.js +2 -2
  27. package/dist/en-GB.js +2 -2
  28. package/dist/en-GB.umd.js +2 -2
  29. package/dist/en-GH.js +2 -2
  30. package/dist/en-GH.umd.js +2 -2
  31. package/dist/en-IE.js +2 -2
  32. package/dist/en-IE.umd.js +2 -2
  33. package/dist/en-IN.js +2 -2
  34. package/dist/en-IN.umd.js +2 -2
  35. package/dist/en-KE.js +2 -2
  36. package/dist/en-KE.umd.js +2 -2
  37. package/dist/en-MY.js +2 -2
  38. package/dist/en-MY.umd.js +2 -2
  39. package/dist/en-NG.js +2 -2
  40. package/dist/en-NG.umd.js +2 -2
  41. package/dist/en-NZ.js +2 -2
  42. package/dist/en-NZ.umd.js +2 -2
  43. package/dist/en-PH.js +2 -2
  44. package/dist/en-PH.umd.js +2 -2
  45. package/dist/en-PK.js +2 -2
  46. package/dist/en-PK.umd.js +2 -2
  47. package/dist/en-SG.js +2 -2
  48. package/dist/en-SG.umd.js +2 -2
  49. package/dist/en-US.js +2 -2
  50. package/dist/en-US.umd.js +2 -2
  51. package/dist/en-ZA.js +2 -2
  52. package/dist/en-ZA.umd.js +2 -2
  53. package/dist/es-ES.js +2 -2
  54. package/dist/es-ES.umd.js +2 -2
  55. package/dist/es-MX.js +2 -2
  56. package/dist/es-MX.umd.js +2 -2
  57. package/dist/es-US.js +2 -2
  58. package/dist/es-US.umd.js +2 -2
  59. package/dist/fa-IR.js +2 -2
  60. package/dist/fa-IR.umd.js +2 -2
  61. package/dist/fi-FI.js +2 -2
  62. package/dist/fi-FI.umd.js +2 -2
  63. package/dist/fil-PH.js +2 -2
  64. package/dist/fil-PH.umd.js +2 -2
  65. package/dist/fr-BE.js +2 -2
  66. package/dist/fr-BE.umd.js +2 -2
  67. package/dist/fr-FR.js +2 -2
  68. package/dist/fr-FR.umd.js +2 -2
  69. package/dist/gu-IN.js +2 -2
  70. package/dist/gu-IN.umd.js +2 -2
  71. package/dist/ha-NG.js +2 -2
  72. package/dist/ha-NG.umd.js +2 -2
  73. package/dist/hbo-IL.js +2 -2
  74. package/dist/hbo-IL.umd.js +2 -2
  75. package/dist/he-IL.js +2 -2
  76. package/dist/he-IL.umd.js +2 -2
  77. package/dist/hi-IN.js +2 -2
  78. package/dist/hi-IN.umd.js +2 -2
  79. package/dist/hr-HR.js +2 -2
  80. package/dist/hr-HR.umd.js +2 -2
  81. package/dist/hu-HU.js +2 -2
  82. package/dist/hu-HU.umd.js +2 -2
  83. package/dist/id-ID.js +2 -2
  84. package/dist/id-ID.umd.js +2 -2
  85. package/dist/it-IT.js +2 -2
  86. package/dist/it-IT.umd.js +2 -2
  87. package/dist/ja-JP.js +2 -2
  88. package/dist/ja-JP.umd.js +2 -2
  89. package/dist/ka-GE.js +2 -2
  90. package/dist/ka-GE.umd.js +2 -2
  91. package/dist/kn-IN.js +2 -2
  92. package/dist/kn-IN.umd.js +2 -2
  93. package/dist/ko-KR.js +2 -2
  94. package/dist/ko-KR.umd.js +2 -2
  95. package/dist/lt-LT.js +2 -2
  96. package/dist/lt-LT.umd.js +2 -2
  97. package/dist/lv-LV.js +2 -2
  98. package/dist/lv-LV.umd.js +2 -2
  99. package/dist/mr-IN.js +2 -2
  100. package/dist/mr-IN.umd.js +2 -2
  101. package/dist/ms-MY.js +2 -2
  102. package/dist/ms-MY.umd.js +2 -2
  103. package/dist/nb-NO.js +2 -2
  104. package/dist/nb-NO.umd.js +2 -2
  105. package/dist/nl-NL.js +2 -2
  106. package/dist/nl-NL.umd.js +2 -2
  107. package/dist/pa-IN.js +2 -2
  108. package/dist/pa-IN.umd.js +2 -2
  109. package/dist/pl-PL.js +2 -2
  110. package/dist/pl-PL.umd.js +2 -2
  111. package/dist/pt-BR.js +2 -2
  112. package/dist/pt-BR.umd.js +2 -2
  113. package/dist/pt-PT.js +2 -2
  114. package/dist/pt-PT.umd.js +2 -2
  115. package/dist/ro-RO.js +2 -2
  116. package/dist/ro-RO.umd.js +2 -2
  117. package/dist/ru-RU.js +2 -2
  118. package/dist/ru-RU.umd.js +2 -2
  119. package/dist/sr-Cyrl-RS.js +2 -2
  120. package/dist/sr-Cyrl-RS.umd.js +2 -2
  121. package/dist/sr-Latn-RS.js +2 -2
  122. package/dist/sr-Latn-RS.umd.js +2 -2
  123. package/dist/sv-SE.js +2 -2
  124. package/dist/sv-SE.umd.js +2 -2
  125. package/dist/sw-KE.js +2 -2
  126. package/dist/sw-KE.umd.js +2 -2
  127. package/dist/ta-IN.js +2 -2
  128. package/dist/ta-IN.umd.js +2 -2
  129. package/dist/te-IN.js +2 -2
  130. package/dist/te-IN.umd.js +2 -2
  131. package/dist/th-TH.js +2 -2
  132. package/dist/th-TH.umd.js +2 -2
  133. package/dist/tr-TR.js +2 -2
  134. package/dist/tr-TR.umd.js +2 -2
  135. package/dist/uk-UA.js +2 -2
  136. package/dist/uk-UA.umd.js +2 -2
  137. package/dist/ur-PK.js +2 -2
  138. package/dist/ur-PK.umd.js +2 -2
  139. package/dist/vi-VN.js +2 -2
  140. package/dist/vi-VN.umd.js +2 -2
  141. package/dist/yo-NG.js +2 -2
  142. package/dist/yo-NG.umd.js +2 -2
  143. package/dist/zh-Hans-CN.js +2 -2
  144. package/dist/zh-Hans-CN.umd.js +2 -2
  145. package/dist/zh-Hant-TW.js +2 -2
  146. package/dist/zh-Hant-TW.umd.js +2 -2
  147. package/package.json +33 -24
  148. package/src/am-ET.d.ts +3 -5
  149. package/src/am-ET.js +41 -16
  150. package/src/am-Latn-ET.d.ts +3 -5
  151. package/src/am-Latn-ET.js +45 -16
  152. package/src/ar-SA.d.ts +44 -18
  153. package/src/ar-SA.js +93 -40
  154. package/src/az-AZ.d.ts +3 -5
  155. package/src/az-AZ.js +58 -20
  156. package/src/bn-BD.d.ts +3 -5
  157. package/src/bn-BD.js +32 -16
  158. package/src/cs-CZ.d.ts +3 -6
  159. package/src/cs-CZ.js +66 -42
  160. package/src/da-DK.d.ts +3 -6
  161. package/src/da-DK.js +53 -48
  162. package/src/de-DE.d.ts +17 -11
  163. package/src/de-DE.js +88 -57
  164. package/src/el-GR.d.ts +3 -6
  165. package/src/el-GR.js +45 -32
  166. package/src/en-AU.d.ts +17 -11
  167. package/src/en-AU.js +56 -41
  168. package/src/en-BD.d.ts +17 -11
  169. package/src/en-BD.js +60 -41
  170. package/src/en-CA.d.ts +36 -18
  171. package/src/en-CA.js +67 -46
  172. package/src/en-GB.d.ts +17 -11
  173. package/src/en-GB.js +56 -41
  174. package/src/en-GH.d.ts +32 -3
  175. package/src/en-GH.js +104 -26
  176. package/src/en-IE.d.ts +17 -11
  177. package/src/en-IE.js +56 -41
  178. package/src/en-IN.d.ts +17 -11
  179. package/src/en-IN.js +60 -41
  180. package/src/en-KE.d.ts +28 -3
  181. package/src/en-KE.js +93 -26
  182. package/src/en-MY.d.ts +26 -3
  183. package/src/en-MY.js +91 -26
  184. package/src/en-NG.d.ts +17 -11
  185. package/src/en-NG.js +56 -41
  186. package/src/en-NZ.d.ts +32 -3
  187. package/src/en-NZ.js +85 -31
  188. package/src/en-PH.d.ts +32 -3
  189. package/src/en-PH.js +97 -26
  190. package/src/en-PK.d.ts +17 -11
  191. package/src/en-PK.js +60 -41
  192. package/src/en-SG.d.ts +28 -3
  193. package/src/en-SG.js +93 -26
  194. package/src/en-US.d.ts +36 -18
  195. package/src/en-US.js +70 -47
  196. package/src/en-ZA.d.ts +17 -11
  197. package/src/en-ZA.js +56 -41
  198. package/src/es-ES.d.ts +53 -21
  199. package/src/es-ES.js +104 -56
  200. package/src/es-MX.d.ts +53 -21
  201. package/src/es-MX.js +104 -56
  202. package/src/es-US.d.ts +53 -21
  203. package/src/es-US.js +92 -51
  204. package/src/fa-IR.d.ts +3 -5
  205. package/src/fa-IR.js +28 -13
  206. package/src/fi-FI.d.ts +3 -6
  207. package/src/fi-FI.js +47 -29
  208. package/src/fil-PH.d.ts +3 -5
  209. package/src/fil-PH.js +61 -28
  210. package/src/fr-BE.d.ts +31 -15
  211. package/src/fr-BE.js +128 -57
  212. package/src/fr-FR.d.ts +31 -16
  213. package/src/fr-FR.js +97 -60
  214. package/src/gu-IN.d.ts +3 -5
  215. package/src/gu-IN.js +31 -16
  216. package/src/ha-NG.d.ts +3 -5
  217. package/src/ha-NG.js +55 -27
  218. package/src/hbo-IL.d.ts +26 -12
  219. package/src/hbo-IL.js +92 -51
  220. package/src/he-IL.d.ts +17 -10
  221. package/src/he-IL.js +92 -50
  222. package/src/hi-IN.d.ts +3 -5
  223. package/src/hi-IN.js +30 -17
  224. package/src/hr-HR.d.ts +21 -10
  225. package/src/hr-HR.js +89 -33
  226. package/src/hu-HU.d.ts +3 -5
  227. package/src/hu-HU.js +57 -23
  228. package/src/id-ID.d.ts +3 -5
  229. package/src/id-ID.js +56 -23
  230. package/src/it-IT.d.ts +17 -11
  231. package/src/it-IT.js +74 -43
  232. package/src/ja-JP.d.ts +3 -6
  233. package/src/ja-JP.js +39 -26
  234. package/src/ka-GE.d.ts +3 -6
  235. package/src/ka-GE.js +38 -26
  236. package/src/kn-IN.d.ts +3 -5
  237. package/src/kn-IN.js +31 -16
  238. package/src/ko-KR.d.ts +3 -6
  239. package/src/ko-KR.js +34 -26
  240. package/src/lt-LT.d.ts +21 -11
  241. package/src/lt-LT.js +64 -42
  242. package/src/lv-LV.d.ts +21 -11
  243. package/src/lv-LV.js +79 -51
  244. package/src/mr-IN.d.ts +3 -5
  245. package/src/mr-IN.js +31 -16
  246. package/src/ms-MY.d.ts +3 -5
  247. package/src/ms-MY.js +58 -24
  248. package/src/nb-NO.d.ts +3 -6
  249. package/src/nb-NO.js +54 -34
  250. package/src/nl-NL.d.ts +41 -20
  251. package/src/nl-NL.js +111 -69
  252. package/src/pa-IN.d.ts +3 -5
  253. package/src/pa-IN.js +32 -16
  254. package/src/pl-PL.d.ts +21 -11
  255. package/src/pl-PL.js +69 -45
  256. package/src/pt-BR.d.ts +22 -11
  257. package/src/pt-BR.js +93 -53
  258. package/src/pt-PT.d.ts +17 -11
  259. package/src/pt-PT.js +80 -48
  260. package/src/ro-RO.d.ts +21 -11
  261. package/src/ro-RO.js +77 -39
  262. package/src/ru-RU.d.ts +35 -15
  263. package/src/ru-RU.js +100 -38
  264. package/src/sr-Cyrl-RS.d.ts +35 -15
  265. package/src/sr-Cyrl-RS.js +106 -43
  266. package/src/sr-Latn-RS.d.ts +35 -15
  267. package/src/sr-Latn-RS.js +106 -43
  268. package/src/sv-SE.d.ts +3 -6
  269. package/src/sv-SE.js +53 -34
  270. package/src/sw-KE.d.ts +3 -5
  271. package/src/sw-KE.js +50 -20
  272. package/src/ta-IN.d.ts +3 -5
  273. package/src/ta-IN.js +29 -17
  274. package/src/te-IN.d.ts +3 -5
  275. package/src/te-IN.js +31 -16
  276. package/src/th-TH.d.ts +3 -5
  277. package/src/th-TH.js +42 -19
  278. package/src/tr-TR.d.ts +17 -11
  279. package/src/tr-TR.js +63 -37
  280. package/src/uk-UA.d.ts +21 -10
  281. package/src/uk-UA.js +89 -33
  282. package/src/ur-PK.d.ts +3 -5
  283. package/src/ur-PK.js +32 -16
  284. package/src/utils/check-max.d.ts +26 -0
  285. package/src/utils/check-max.js +33 -0
  286. package/src/utils/expand-scientific.d.ts +0 -4
  287. package/src/utils/expand-scientific.js +7 -9
  288. package/src/utils/is-plain-object.d.ts +3 -4
  289. package/src/utils/is-plain-object.js +3 -4
  290. package/src/utils/parse-cardinal.d.ts +1 -2
  291. package/src/utils/parse-cardinal.js +12 -9
  292. package/src/utils/parse-currency.d.ts +1 -2
  293. package/src/utils/parse-currency.js +9 -11
  294. package/src/utils/parse-ordinal.d.ts +0 -1
  295. package/src/utils/parse-ordinal.js +9 -10
  296. package/src/utils/resolve-options.d.ts +17 -0
  297. package/src/utils/resolve-options.js +56 -0
  298. package/src/utils/scale.d.ts +49 -0
  299. package/src/utils/scale.js +65 -0
  300. package/src/vi-VN.d.ts +3 -6
  301. package/src/vi-VN.js +41 -28
  302. package/src/yo-NG.d.ts +3 -5
  303. package/src/yo-NG.js +49 -33
  304. package/src/zh-Hans-CN.d.ts +45 -20
  305. package/src/zh-Hans-CN.js +84 -31
  306. package/src/zh-Hant-TW.d.ts +45 -20
  307. package/src/zh-Hant-TW.js +85 -34
  308. package/src/utils/validate-options.d.ts +0 -8
  309. package/src/utils/validate-options.js +0 -16
package/src/nl-NL.js CHANGED
@@ -16,7 +16,9 @@
16
16
  import { parseCardinalValue } from './utils/parse-cardinal.js'
17
17
  import { parseCurrencyValue } from './utils/parse-currency.js'
18
18
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
19
- import { validateOptions } from './utils/validate-options.js'
19
+ import { checkMax } from './utils/check-max.js'
20
+ import { western } from './utils/scale.js'
21
+ import { resolveOptions } from './utils/resolve-options.js'
20
22
 
21
23
  // ============================================================================
22
24
  // Vocabulary (module-level constants)
@@ -31,6 +33,19 @@ const HUNDRED = 'honderd'
31
33
  // Scale words (long scale with -ard forms)
32
34
  const SCALES = ['duizend', 'miljoen', 'miljard', 'biljoen', 'biljard', 'triljoen', 'triljard', 'quadriljoen', 'quadriljard']
33
35
 
36
+ // Scale ordinal words. Module-scope so its length can derive the ordinal
37
+ // ceiling and so it isn't rebuilt on every call.
38
+ const SCALE_ORDINAL = ['', 'duizendste', 'miljoenste', 'miljardste', 'biljoenste', 'biljardste', 'triljoenste']
39
+
40
+ // Supported magnitude ceilings (checked at the public entry points). SCALES is
41
+ // indexed [scaleIndex - 1] from 'duizend' (10^3), so cardinals/currency reach
42
+ // 10^((SCALES.length + 1) * 3) = 10^30. The ordinal of a number whose lowest
43
+ // non-zero group is a scale group uses the shorter SCALE_ORDINAL, so ordinals
44
+ // are bounded at 10^(SCALE_ORDINAL.length * 3) = 10^21.
45
+ export const cardinalMax = western(SCALES.length)
46
+ export const ordinalMax = western(SCALE_ORDINAL.length - 1)
47
+ export const currencyMax = western(SCALES.length)
48
+
34
49
  const ZERO = 'nul'
35
50
  const NEGATIVE = 'min'
36
51
  const DECIMAL_SEP = 'komma'
@@ -51,7 +66,7 @@ const CENTEN = 'cent' // Cent doesn't pluralize in written currency
51
66
  * @param {boolean} withAnd - Include "en" for values < 13 after hundreds
52
67
  * @returns {string} Dutch word (compound, no spaces)
53
68
  */
54
- function buildSegment (n, withAnd) {
69
+ function buildSegment(n, withAnd) {
55
70
  if (n === 0) return ''
56
71
 
57
72
  const ones = n % 10
@@ -65,7 +80,8 @@ function buildSegment (n, withAnd) {
65
80
  if (hundreds > 0) {
66
81
  if (hundreds === 1) {
67
82
  result = HUNDRED
68
- } else {
83
+ }
84
+ else {
69
85
  result = ONES[hundreds] + HUNDRED
70
86
  }
71
87
  }
@@ -73,25 +89,31 @@ function buildSegment (n, withAnd) {
73
89
  // Tens and ones
74
90
  if (tensOnes === 0) {
75
91
  // Just hundreds
76
- } else if (tensOnes < 10) {
92
+ }
93
+ else if (tensOnes < 10) {
77
94
  // Single digit - add "en" if withAnd and after hundreds
78
95
  if (hundreds > 0 && withAnd) {
79
96
  result += 'en' + ONES[tensOnes]
80
- } else {
97
+ }
98
+ else {
81
99
  result += ONES[tensOnes]
82
100
  }
83
- } else if (tensOnes < 20) {
101
+ }
102
+ else if (tensOnes < 20) {
84
103
  // Teens - add "en" if withAnd and after hundreds and < 13
85
104
  if (hundreds > 0 && withAnd && tensOnes < 13) {
86
105
  result += 'en' + TEENS[ones]
87
- } else {
106
+ }
107
+ else {
88
108
  result += TEENS[ones]
89
109
  }
90
- } else {
110
+ }
111
+ else {
91
112
  // 20-99: Dutch inverts with connector
92
113
  if (ones === 0) {
93
114
  result += TENS[tens]
94
- } else {
115
+ }
116
+ else {
95
117
  // "ën" if ones ends in 'e' (twee, drie)
96
118
  const onesWord = ONES[ones]
97
119
  const connector = onesWord.endsWith('e') ? 'ën' : 'en'
@@ -108,17 +130,20 @@ function buildSegment (n, withAnd) {
108
130
 
109
131
  /**
110
132
  * Converts a non-negative integer to Dutch words.
111
- *
112
133
  * @param {bigint} n - Non-negative integer to convert
113
- * @param {Object} options - Conversion options
134
+ * @param {{accentOne: boolean, includeOptionalAnd: boolean, noHundredPairing: boolean}} options - Conversion options
114
135
  * @returns {string} Dutch words
115
136
  */
116
- function integerToWords (n, options) {
137
+ function integerToWords(n, options) {
117
138
  if (n === 0n) return ZERO
118
139
 
119
140
  const { accentOne, includeOptionalAnd, noHundredPairing } = options
120
141
 
121
142
  // Apply één/een replacement
143
+ /**
144
+ * @param {string} word The word to apply accent replacement to
145
+ * @returns {string} The word with "een" replaced by "één" when accentOne is set
146
+ */
122
147
  const applyAccent = (word) => {
123
148
  if (accentOne) {
124
149
  return word.replace(/\been\b/g, 'één')
@@ -143,7 +168,8 @@ function integerToWords (n, options) {
143
168
  const lowWord = buildSegment(low, includeOptionalAnd)
144
169
  if (includeOptionalAnd && low < 13) {
145
170
  result += ' en ' + lowWord
146
- } else {
171
+ }
172
+ else {
147
173
  result += ' ' + lowWord
148
174
  }
149
175
  }
@@ -160,7 +186,8 @@ function integerToWords (n, options) {
160
186
  if (thousands === 1) {
161
187
  // "duizend" not "eenduizend"
162
188
  result = SCALES[0]
163
- } else {
189
+ }
190
+ else {
164
191
  // Compound: "vijfduizend"
165
192
  result = buildSegment(thousands, includeOptionalAnd) + SCALES[0]
166
193
  }
@@ -169,7 +196,8 @@ function integerToWords (n, options) {
169
196
  const remainderWord = buildSegment(remainder, includeOptionalAnd)
170
197
  if (includeOptionalAnd && remainder < 13) {
171
198
  result += ' en ' + remainderWord
172
- } else {
199
+ }
200
+ else {
173
201
  result += ' ' + remainderWord
174
202
  }
175
203
  }
@@ -184,12 +212,11 @@ function integerToWords (n, options) {
184
212
  /**
185
213
  * Builds words for numbers >= 1,000,000.
186
214
  * Uses BigInt division for faster segment extraction (4x faster than string slicing).
187
- *
188
215
  * @param {bigint} n - Number >= 1,000,000
189
- * @param {Object} options - Conversion options
216
+ * @param {{accentOne: boolean, includeOptionalAnd: boolean, noHundredPairing: boolean}} options - Conversion options
190
217
  * @returns {string} Dutch words
191
218
  */
192
- function buildLargeNumberWords (n, options) {
219
+ function buildLargeNumberWords(n, options) {
193
220
  const { includeOptionalAnd } = options
194
221
 
195
222
  // Extract segments using BigInt division (faster than string slicing)
@@ -215,29 +242,35 @@ function buildLargeNumberWords (n, options) {
215
242
  if (result) {
216
243
  if (prevWasScale && includeOptionalAnd && segment < 13) {
217
244
  result += ' en ' + word
218
- } else {
245
+ }
246
+ else {
219
247
  result += ' ' + word
220
248
  }
221
- } else {
249
+ }
250
+ else {
222
251
  result = word
223
252
  }
224
253
  prevWasScale = false
225
- } else if (i === 1) {
254
+ }
255
+ else if (i === 1) {
226
256
  // Thousands - compound
227
257
  if (result) result += ' '
228
258
  if (segment === 1) {
229
259
  result += SCALES[0]
230
- } else {
260
+ }
261
+ else {
231
262
  result += buildSegment(segment, includeOptionalAnd) + SCALES[0]
232
263
  }
233
264
  prevWasScale = true
234
- } else {
265
+ }
266
+ else {
235
267
  // Million and above - space around scale
236
268
  const scaleWord = SCALES[i - 1]
237
269
  if (result) result += ' '
238
270
  if (segment === 1) {
239
271
  result += 'een ' + scaleWord
240
- } else {
272
+ }
273
+ else {
241
274
  result += buildSegment(segment, includeOptionalAnd) + ' ' + scaleWord
242
275
  }
243
276
  prevWasScale = true
@@ -249,12 +282,11 @@ function buildLargeNumberWords (n, options) {
249
282
 
250
283
  /**
251
284
  * Converts decimal digits to Dutch words.
252
- *
253
285
  * @param {string} decimalPart - Decimal digits (without the point)
254
- * @param {Object} options - Conversion options
286
+ * @param {{accentOne: boolean, includeOptionalAnd: boolean, noHundredPairing: boolean}} options - Conversion options
255
287
  * @returns {string} Dutch words for decimal part
256
288
  */
257
- function decimalPartToWords (decimalPart, options) {
289
+ function decimalPartToWords(decimalPart, options) {
258
290
  let result = ''
259
291
 
260
292
  // Handle leading zeros
@@ -276,37 +308,40 @@ function decimalPartToWords (decimalPart, options) {
276
308
  return result
277
309
  }
278
310
 
311
+ /**
312
+ * @typedef {object} CardinalOptions
313
+ * @property {boolean} [accentOne] - Use "één" instead of "een"
314
+ * @property {boolean} [includeOptionalAnd] - Include "en" before small numbers
315
+ * @property {boolean} [noHundredPairing] - Disable hundred pairing (1104→duizend honderdvier)
316
+ */
317
+
318
+ /** @type {Required<CardinalOptions>} */
319
+ export const cardinalDefaults = { accentOne: true, includeOptionalAnd: false, noHundredPairing: false }
320
+
279
321
  /**
280
322
  * Converts a numeric value to Dutch words.
281
323
  *
282
324
  * This is the main public API. It accepts any valid numeric input
283
325
  * (number, string, or bigint) and handles parsing internally.
284
- *
285
326
  * @param {number | string | bigint} value - The numeric value to convert
286
- * @param {Object} [options] - Optional configuration
287
- * @param {boolean} [options.accentOne=true] - Use "één" instead of "een"
288
- * @param {boolean} [options.includeOptionalAnd=false] - Include "en" before small numbers
289
- * @param {boolean} [options.noHundredPairing=false] - Disable hundred pairing (1104→duizend honderdvier)
327
+ * @param {CardinalOptions} [options] - Optional configuration
290
328
  * @returns {string} The number in Dutch words
291
329
  * @throws {TypeError} If value is not a valid numeric type
292
330
  * @throws {Error} If value is not a valid number format
293
- *
294
331
  * @example
295
332
  * toCardinal(21) // 'eenentwintig'
296
333
  * toCardinal(1) // 'één'
297
334
  * toCardinal(1, {accentOne: false}) // 'een'
298
335
  * toCardinal(1104) // 'elfhonderd vier'
299
336
  */
300
- function toCardinal (value, options) {
301
- options = validateOptions(options)
337
+ function toCardinal(value, options) {
302
338
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
339
+ // Both the integer part and the decimal's significant digits are spelled via
340
+ // the scale builder, so both must clear the ceiling.
341
+ checkMax(integerPart, cardinalMax, decimalPart)
303
342
 
304
343
  // Apply option defaults
305
- const {
306
- accentOne = true,
307
- includeOptionalAnd = false,
308
- noHundredPairing = false
309
- } = options
344
+ const { accentOne, includeOptionalAnd, noHundredPairing } = resolveOptions(options, cardinalDefaults)
310
345
 
311
346
  const opts = { accentOne, includeOptionalAnd, noHundredPairing }
312
347
 
@@ -335,12 +370,11 @@ const ORDINAL_ONES = ['', 'eerste', 'tweede', 'derde', 'vierde', 'vijfde', 'zesd
335
370
  /**
336
371
  * Converts a small cardinal to ordinal.
337
372
  * Rules: 1-19 add -de (except eerste, derde, achtste), 20+ add -ste
338
- *
339
373
  * @param {string} cardinalWord - Cardinal word
340
374
  * @param {number} n - The number value (0-99)
341
375
  * @returns {string} Ordinal word
342
376
  */
343
- function smallCardinalToOrdinal (cardinalWord, n) {
377
+ function smallCardinalToOrdinal(cardinalWord, n) {
344
378
  // Special cases for 1-9
345
379
  if (n >= 1 && n <= 9) return ORDINAL_ONES[n]
346
380
 
@@ -358,11 +392,10 @@ function smallCardinalToOrdinal (cardinalWord, n) {
358
392
 
359
393
  /**
360
394
  * Builds ordinal words for 0-999.
361
- *
362
395
  * @param {number} n - Number 0-999
363
396
  * @returns {string} Dutch ordinal words
364
397
  */
365
- function buildOrdinalSegment (n) {
398
+ function buildOrdinalSegment(n) {
366
399
  if (n === 0) return ''
367
400
 
368
401
  const hundreds = Math.trunc(n / 100)
@@ -375,10 +408,11 @@ function buildOrdinalSegment (n) {
375
408
  }
376
409
 
377
410
  // Hundreds: need to build prefix + ordinal suffix
378
- let prefix = ''
411
+ let prefix
379
412
  if (hundreds === 1) {
380
413
  prefix = HUNDRED
381
- } else {
414
+ }
415
+ else {
382
416
  prefix = ONES[hundreds] + HUNDRED
383
417
  }
384
418
 
@@ -395,17 +429,16 @@ function buildOrdinalSegment (n) {
395
429
 
396
430
  /**
397
431
  * Converts a number to Dutch ordinal words.
398
- *
399
432
  * @param {number | string | bigint} value - The number to convert
400
433
  * @returns {string} Dutch ordinal words
401
- *
402
434
  * @example
403
435
  * toOrdinal(1) // 'eerste'
404
436
  * toOrdinal(21) // 'eenentwintigste'
405
437
  * toOrdinal(100) // 'honderdste'
406
438
  */
407
- function toOrdinal (value) {
439
+ function toOrdinal(value) {
408
440
  const n = parseOrdinalValue(value)
441
+ checkMax(n, ordinalMax)
409
442
 
410
443
  // Fast path: 1-9
411
444
  if (n < 10n) return ORDINAL_ONES[Number(n)]
@@ -427,11 +460,10 @@ function toOrdinal (value) {
427
460
 
428
461
  /**
429
462
  * Builds ordinal words for large numbers.
430
- *
431
463
  * @param {bigint} n - Non-negative integer >= 1000
432
464
  * @returns {string} Dutch ordinal words
433
465
  */
434
- function buildLargeOrdinal (n) {
466
+ function buildLargeOrdinal(n) {
435
467
  // Extract segments
436
468
  const segments = []
437
469
  let temp = n
@@ -449,9 +481,6 @@ function buildLargeOrdinal (n) {
449
481
  }
450
482
  }
451
483
 
452
- // Scale ordinal words
453
- const SCALE_ORDINAL = ['', 'duizendste', 'miljoenste', 'miljardste', 'biljoenste', 'biljardste', 'triljoenste']
454
-
455
484
  let result = ''
456
485
 
457
486
  for (let i = segments.length - 1; i >= 0; i--) {
@@ -465,34 +494,42 @@ function buildLargeOrdinal (n) {
465
494
  if (i === 0) {
466
495
  // Units segment: use ordinal segment builder
467
496
  result += buildOrdinalSegment(segment)
468
- } else if (segment === 1 && i > 0) {
497
+ }
498
+ else if (segment === 1 && i > 0) {
469
499
  // Exact scale: duizendste, miljoenste, etc.
470
500
  if (i === 1) {
471
501
  result += SCALE_ORDINAL[i]
472
- } else {
502
+ }
503
+ else {
473
504
  result += 'een ' + SCALE_ORDINAL[i]
474
505
  }
475
- } else {
506
+ }
507
+ else {
476
508
  // Segment + scale ordinal
477
509
  if (i === 1) {
478
510
  result += buildSegment(segment, false) + SCALE_ORDINAL[i]
479
- } else {
511
+ }
512
+ else {
480
513
  result += buildSegment(segment, false) + ' ' + SCALE_ORDINAL[i]
481
514
  }
482
515
  }
483
- } else {
516
+ }
517
+ else {
484
518
  // Higher segments use cardinal form
485
519
  if (i === 1) {
486
520
  if (segment === 1) {
487
521
  result += SCALES[0]
488
- } else {
522
+ }
523
+ else {
489
524
  result += buildSegment(segment, false) + SCALES[0]
490
525
  }
491
- } else {
526
+ }
527
+ else {
492
528
  const scaleWord = SCALES[i - 1]
493
529
  if (segment === 1) {
494
530
  result += 'een ' + scaleWord
495
- } else {
531
+ }
532
+ else {
496
533
  result += buildSegment(segment, false) + ' ' + scaleWord
497
534
  }
498
535
  }
@@ -506,23 +543,28 @@ function buildLargeOrdinal (n) {
506
543
  // Currency Functions
507
544
  // ============================================================================
508
545
 
546
+ /**
547
+ * @typedef {object} CurrencyOptions
548
+ * @property {boolean} [and] - Include "en" between euros and cents
549
+ */
550
+
551
+ /** @type {Required<CurrencyOptions>} */
552
+ export const currencyDefaults = { and: true }
553
+
509
554
  /**
510
555
  * Converts a number to Dutch currency words (Euro).
511
- *
512
556
  * @param {number | string | bigint} value - The amount to convert
513
- * @param {Object} [options]
514
- * @param {boolean} [options.and=true] - Include "en" between euros and cents
557
+ * @param {CurrencyOptions} [options] Optional configuration
515
558
  * @returns {string} Dutch currency words
516
- *
517
559
  * @example
518
560
  * toCurrency(42.50) // 'tweeënveertig euro en vijftig cent'
519
561
  * toCurrency(1) // 'één euro'
520
562
  * toCurrency(0.01) // 'één cent'
521
563
  */
522
- function toCurrency (value, options) {
523
- options = validateOptions(options)
564
+ function toCurrency(value, options) {
524
565
  const { isNegative, dollars: euros, cents } = parseCurrencyValue(value)
525
- const { and = true } = options
566
+ checkMax(euros, currencyMax)
567
+ const { and } = resolveOptions(options, currencyDefaults)
526
568
 
527
569
  let result = ''
528
570
 
package/src/pa-IN.d.ts CHANGED
@@ -1,18 +1,18 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Punjabi words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Punjabi words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Punjabi ordinal words.
10
- *
11
12
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
12
13
  * @returns {string} The number as ordinal words
13
14
  * @throws {TypeError} If value is not a valid numeric type
14
15
  * @throws {RangeError} If value is negative, zero, or has a decimal part
15
- *
16
16
  * @example
17
17
  * toOrdinal(1) // 'ਪਹਿਲਾ'
18
18
  * toOrdinal(2) // 'ਦੂਜਾ'
@@ -22,12 +22,10 @@ export function toCardinal(value: number | string | bigint): string;
22
22
  export function toOrdinal(value: number | string | bigint): string;
23
23
  /**
24
24
  * Converts a numeric value to Punjabi currency words (Indian Rupee).
25
- *
26
25
  * @param {number | string | bigint} value - The currency amount to convert
27
26
  * @returns {string} The amount in Punjabi currency words
28
27
  * @throws {TypeError} If value is not a valid numeric type
29
28
  * @throws {Error} If value is not a valid number format
30
- *
31
29
  * @example
32
30
  * toCurrency(42.50) // 'ਬਿਆਲੀ ਰੁਪਏ ਪੰਜਾਹ ਪੈਸੇ'
33
31
  * toCurrency(1) // 'ਇੱਕ ਰੁਪਇਆ'
package/src/pa-IN.js CHANGED
@@ -13,6 +13,8 @@
13
13
  import { parseCardinalValue } from './utils/parse-cardinal.js'
14
14
  import { parseCurrencyValue } from './utils/parse-currency.js'
15
15
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
16
+ import { checkMax } from './utils/check-max.js'
17
+ import { indian } from './utils/scale.js'
16
18
 
17
19
  // ============================================================================
18
20
  // Vocabulary
@@ -55,20 +57,29 @@ const BELOW_HUNDRED = [
55
57
  'ਸੱਠ', 'ਇਕਾਹਠ', 'ਬਾਹਠ', 'ਤਰਸਠ', 'ਚੌਂਸਠ', 'ਪੈਂਸਠ', 'ਛਿਆਸਠ', 'ਸੜਸਠ', 'ਅੜਸਠ', 'ਉਣਹੱਤਰ',
56
58
  'ਸਤੱਰ', 'ਇਕਹੱਤਰ', 'ਬਹੱਤਰ', 'ਤਹੱਤਰ', 'ਚੌਹੱਤਰ', 'ਪੰਝਹੱਤਰ', 'ਛਿਹੱਤਰ', 'ਸਤੱਤਰ', 'ਅਠੱਤਰ', 'ਉਨਾਸੀ',
57
59
  'ਅੱਸੀ', 'ਇਕਿਆਸੀ', 'ਬਿਆਸੀ', 'ਤਰਿਆਸੀ', 'ਚੌਰਿਆਸੀ', 'ਪਚਾਸੀ', 'ਛਿਆਸੀ', 'ਸੱਤਾਸੀ', 'ਅਠਾਸੀ', 'ਨਵਾਸੀ',
58
- 'ਨੱਬੇ', 'ਇਕਾਨਵੇਂ', 'ਬਾਨਵੇਂ', 'ਤਰਾਨਵੇਂ', 'ਚੁਰਾਨਵੇਂ', 'ਪੰਚਾਨਵੇਂ', 'ਛਿਆਨਵੇਂ', 'ਸਤਾਨਵੇਂ', 'ਅਠਾਨਵੇਂ', 'ਨਿਨਾਨਵੇਂ'
60
+ 'ਨੱਬੇ', 'ਇਕਾਨਵੇਂ', 'ਬਾਨਵੇਂ', 'ਤਰਾਨਵੇਂ', 'ਚੁਰਾਨਵੇਂ', 'ਪੰਚਾਨਵੇਂ', 'ਛਿਆਨਵੇਂ', 'ਸਤਾਨਵੇਂ', 'ਅਠਾਨਵੇਂ', 'ਨਿਨਾਨਵੇਂ',
59
61
  ]
60
62
 
61
63
  // Scale words: index 0 = units (empty), 1 = thousand, 2 = lakh, 3 = crore, etc.
62
64
  const SCALE_WORDS = ['', 'ਹਜ਼ਾਰ', 'ਲੱਖ', 'ਕਰੋੜ', 'ਅਰਬ', 'ਖਰਬ', 'ਨੀਲ', 'ਪਦਮ', 'ਸ਼ੰਖ']
63
65
 
66
+ // 3-2-2 Indian grouping: a 3-digit base segment, then 2 digits per scale word
67
+ // (SCALE_WORDS[0] = '' is the units slot). Past the table the scale word is
68
+ // dropped, which collapses the magnitude — so cap there.
69
+ export const cardinalMax = indian(SCALE_WORDS.length)
70
+ export const ordinalMax = indian(SCALE_WORDS.length)
71
+ export const currencyMax = indian(SCALE_WORDS.length)
72
+
64
73
  // ============================================================================
65
74
  // Segment Building
66
75
  // ============================================================================
67
76
 
68
77
  /**
69
78
  * Builds words for a 0-999 segment.
79
+ * @param {number} n - Segment value (0-999)
80
+ * @returns {string} Punjabi words for the segment
70
81
  */
71
- function buildSegment (n) {
82
+ function buildSegment(n) {
72
83
  if (n === 0) return ''
73
84
  if (n < 100) return BELOW_HUNDRED[n]
74
85
 
@@ -90,11 +101,10 @@ function buildSegment (n) {
90
101
  *
91
102
  * Uses BigInt modulo for segment extraction (faster than string slicing).
92
103
  * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
93
- *
94
104
  * @param {bigint} n - Non-negative integer to convert
95
105
  * @returns {string} Punjabi words
96
106
  */
97
- function integerToWords (n) {
107
+ function integerToWords(n) {
98
108
  if (n === 0n) return ZERO
99
109
 
100
110
  // Fast path: numbers < 1000 (direct lookup)
@@ -120,7 +130,8 @@ function integerToWords (n) {
120
130
 
121
131
  if (i === 0) {
122
132
  words.push(buildSegment(segment))
123
- } else {
133
+ }
134
+ else {
124
135
  words.push(BELOW_HUNDRED[segment])
125
136
  }
126
137
 
@@ -132,7 +143,12 @@ function integerToWords (n) {
132
143
  return words.join(' ')
133
144
  }
134
145
 
135
- function decimalPartToWords (decimalPart) {
146
+ /**
147
+ * Converts the fractional digit string to Punjabi words.
148
+ * @param {string} decimalPart - Digits after the decimal point
149
+ * @returns {string} Punjabi words for the decimal part
150
+ */
151
+ function decimalPartToWords(decimalPart) {
136
152
  let result = ''
137
153
  let i = 0
138
154
 
@@ -153,12 +169,14 @@ function decimalPartToWords (decimalPart) {
153
169
 
154
170
  /**
155
171
  * Converts a numeric value to Punjabi words.
156
- *
157
172
  * @param {number | string | bigint} value - The numeric value to convert
158
173
  * @returns {string} The number in Punjabi words
159
174
  */
160
- function toCardinal (value) {
175
+ function toCardinal(value) {
161
176
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
177
+ // Both the integer part and the decimal's significant digits are spelled via
178
+ // the scale builder, so both must clear the ceiling.
179
+ checkMax(integerPart, cardinalMax, decimalPart)
162
180
 
163
181
  let result = ''
164
182
 
@@ -183,11 +201,10 @@ function toCardinal (value) {
183
201
  * Converts a positive integer to Punjabi ordinal words.
184
202
  *
185
203
  * Punjabi ordinals: First 6 are irregular, then add -ਵਾਂ suffix.
186
- *
187
204
  * @param {bigint} n - Positive integer to convert
188
205
  * @returns {string} Punjabi ordinal words
189
206
  */
190
- function integerToOrdinal (n) {
207
+ function integerToOrdinal(n) {
191
208
  // Special ordinals for 1-6
192
209
  if (n >= 1n && n <= 6n) {
193
210
  return ORDINAL_SPECIAL[Number(n)]
@@ -200,20 +217,20 @@ function integerToOrdinal (n) {
200
217
 
201
218
  /**
202
219
  * Converts a numeric value to Punjabi ordinal words.
203
- *
204
220
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
205
221
  * @returns {string} The number as ordinal words
206
222
  * @throws {TypeError} If value is not a valid numeric type
207
223
  * @throws {RangeError} If value is negative, zero, or has a decimal part
208
- *
209
224
  * @example
210
225
  * toOrdinal(1) // 'ਪਹਿਲਾ'
211
226
  * toOrdinal(2) // 'ਦੂਜਾ'
212
227
  * toOrdinal(3) // 'ਤੀਜਾ'
213
228
  * toOrdinal(10) // 'ਦੱਸਵਾਂ'
214
229
  */
215
- function toOrdinal (value) {
230
+ function toOrdinal(value) {
216
231
  const integerPart = parseOrdinalValue(value)
232
+ // Ordinals build on the cardinal speller, so they share its ceiling.
233
+ checkMax(integerPart, ordinalMax)
217
234
  return integerToOrdinal(integerPart)
218
235
  }
219
236
 
@@ -223,19 +240,18 @@ function toOrdinal (value) {
223
240
 
224
241
  /**
225
242
  * Converts a numeric value to Punjabi currency words (Indian Rupee).
226
- *
227
243
  * @param {number | string | bigint} value - The currency amount to convert
228
244
  * @returns {string} The amount in Punjabi currency words
229
245
  * @throws {TypeError} If value is not a valid numeric type
230
246
  * @throws {Error} If value is not a valid number format
231
- *
232
247
  * @example
233
248
  * toCurrency(42.50) // 'ਬਿਆਲੀ ਰੁਪਏ ਪੰਜਾਹ ਪੈਸੇ'
234
249
  * toCurrency(1) // 'ਇੱਕ ਰੁਪਇਆ'
235
250
  * toCurrency(0.01) // 'ਇੱਕ ਪੈਸਾ'
236
251
  */
237
- function toCurrency (value) {
252
+ function toCurrency(value) {
238
253
  const { isNegative, dollars: rupees, cents: paise } = parseCurrencyValue(value)
254
+ checkMax(rupees, currencyMax)
239
255
 
240
256
  // Build result
241
257
  let result = ''
package/src/pl-PL.d.ts CHANGED
@@ -1,33 +1,45 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CardinalOptions
6
+ * @property {('masculine'|'feminine')} [gender] - Gender for numbers < 1000
7
+ */
8
+ /** @type {Required<CardinalOptions>} */
9
+ export const cardinalDefaults: Required<CardinalOptions>;
10
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
11
+ export const cardinalValues: {
12
+ gender: ReadonlyArray<Required<CardinalOptions>["gender"]>;
13
+ };
14
+ export type CardinalOptions = {
15
+ /**
16
+ * - Gender for numbers < 1000
17
+ */
18
+ gender?: "feminine" | "masculine" | undefined;
19
+ };
1
20
  /**
2
21
  * Converts a numeric value to Polish words.
3
22
  *
4
23
  * This is the main public API. It accepts any valid numeric input
5
24
  * (number, string, or bigint) and handles parsing internally.
6
- *
7
25
  * @param {number | string | bigint} value - The numeric value to convert
8
- * @param {Object} [options] - Conversion options
9
- * @param {string} [options.gender='masculine'] - Gender for numbers < 1000
26
+ * @param {CardinalOptions} [options] - Conversion options
10
27
  * @returns {string} The number in Polish words
11
28
  * @throws {TypeError} If value is not a valid numeric type
12
29
  * @throws {Error} If value is not a valid number format
13
- *
14
30
  * @example
15
31
  * toCardinal(1) // 'jeden'
16
32
  * toCardinal(1, { gender: 'feminine' }) // 'jedna'
17
33
  * toCardinal(1000) // 'tysiąc'
18
34
  * toCardinal(2000) // 'dwa tysiące'
19
35
  */
20
- export function toCardinal(value: number | string | bigint, options?: {
21
- gender?: string | undefined;
22
- }): string;
36
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
23
37
  /**
24
38
  * Converts a numeric value to Polish ordinal words (masculine nominative).
25
- *
26
39
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
27
40
  * @returns {string} The number as ordinal words (e.g., "pierwszy", "czterdziesty drugi")
28
41
  * @throws {TypeError} If value is not a valid numeric type
29
42
  * @throws {RangeError} If value is negative, zero, or has a decimal part
30
- *
31
43
  * @example
32
44
  * toOrdinal(1) // 'pierwszy'
33
45
  * toOrdinal(2) // 'drugi'
@@ -40,12 +52,10 @@ export function toCardinal(value: number | string | bigint, options?: {
40
52
  export function toOrdinal(value: number | string | bigint): string;
41
53
  /**
42
54
  * Converts a numeric value to Polish currency words (Polish Złoty).
43
- *
44
55
  * @param {number | string | bigint} value - The currency amount to convert
45
56
  * @returns {string} The amount in Polish currency words
46
57
  * @throws {TypeError} If value is not a valid numeric type
47
58
  * @throws {Error} If value is not a valid number format
48
- *
49
59
  * @example
50
60
  * toCurrency(42) // 'czterdzieści dwa złote'
51
61
  * toCurrency(1) // 'jeden złoty'