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/ms-MY.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 { western } from './utils/scale.js'
16
18
 
17
19
  // ============================================================================
18
20
  // Vocabulary
@@ -26,6 +28,11 @@ const HUNDRED_WORD = 'ratus'
26
28
  const THOUSAND_WORD = 'ribu'
27
29
  const SCALE_WORDS = ['juta', 'bilion', 'trilion']
28
30
 
31
+ // Supported magnitude ceiling (checked at the public entry points), derived from the scale table.
32
+ export const cardinalMax = western(SCALE_WORDS.length + 1)
33
+ export const ordinalMax = western(SCALE_WORDS.length + 1)
34
+ export const currencyMax = western(SCALE_WORDS.length + 1)
35
+
29
36
  const ZERO = 'sifar'
30
37
  const NEGATIVE = 'minus'
31
38
  const DECIMAL_SEP = 'perpuluhan'
@@ -49,7 +56,12 @@ const SEN = 'sen'
49
56
  // Segment Building
50
57
  // ============================================================================
51
58
 
52
- function buildSegment (n) {
59
+ /**
60
+ * Builds the words for a number segment (0-999).
61
+ * @param {number} n - Integer segment in range 0-999
62
+ * @returns {string} The segment in Malay words
63
+ */
64
+ function buildSegment(n) {
53
65
  if (n === 0) return ''
54
66
 
55
67
  const onesDigit = n % 10
@@ -62,7 +74,8 @@ function buildSegment (n) {
62
74
  if (hundredsDigit > 0) {
63
75
  if (hundredsDigit === 1) {
64
76
  parts.push('se' + HUNDRED_WORD)
65
- } else {
77
+ }
78
+ else {
66
79
  parts.push(ONES[hundredsDigit] + ' ' + HUNDRED_WORD)
67
80
  }
68
81
  }
@@ -72,13 +85,17 @@ function buildSegment (n) {
72
85
 
73
86
  if (tensOnes === 0) {
74
87
  // Just hundreds
75
- } else if (tensOnes < 10) {
88
+ }
89
+ else if (tensOnes < 10) {
76
90
  parts.push(ONES[tensOnes])
77
- } else if (tensOnes < 20) {
91
+ }
92
+ else if (tensOnes < 20) {
78
93
  parts.push(TEENS[tensOnes - 10])
79
- } else if (onesDigit === 0) {
94
+ }
95
+ else if (onesDigit === 0) {
80
96
  parts.push(TENS[tensDigit])
81
- } else {
97
+ }
98
+ else {
82
99
  parts.push(TENS[tensDigit] + ' ' + ONES[onesDigit])
83
100
  }
84
101
 
@@ -89,7 +106,12 @@ function buildSegment (n) {
89
106
  // Conversion Functions
90
107
  // ============================================================================
91
108
 
92
- function integerToWords (n) {
109
+ /**
110
+ * Converts a non-negative integer to Malay words.
111
+ * @param {bigint} n - Non-negative integer to convert
112
+ * @returns {string} The integer in Malay words
113
+ */
114
+ function integerToWords(n) {
93
115
  if (n === 0n) return ZERO
94
116
 
95
117
  if (n < 1000n) {
@@ -103,7 +125,8 @@ function integerToWords (n) {
103
125
  let result
104
126
  if (thousands === 1) {
105
127
  result = 'se' + THOUSAND_WORD
106
- } else {
128
+ }
129
+ else {
107
130
  result = buildSegment(thousands) + ' ' + THOUSAND_WORD
108
131
  }
109
132
 
@@ -117,7 +140,12 @@ function integerToWords (n) {
117
140
  return buildLargeNumberWords(n)
118
141
  }
119
142
 
120
- function buildLargeNumberWords (n) {
143
+ /**
144
+ * Builds words for large numbers (>= 1,000,000) using scale words.
145
+ * @param {bigint} n - Integer to convert (>= 1,000,000)
146
+ * @returns {string} The integer in Malay words
147
+ */
148
+ function buildLargeNumberWords(n) {
121
149
  const numStr = n.toString()
122
150
  const len = numStr.length
123
151
 
@@ -144,18 +172,22 @@ function buildLargeNumberWords (n) {
144
172
  if (segment !== 0) {
145
173
  if (scaleIndex === 0) {
146
174
  parts.push(buildSegment(segment))
147
- } else if (scaleIndex === 1) {
175
+ }
176
+ else if (scaleIndex === 1) {
148
177
  if (segment === 1) {
149
178
  parts.push('se' + THOUSAND_WORD)
150
- } else {
179
+ }
180
+ else {
151
181
  parts.push(buildSegment(segment) + ' ' + THOUSAND_WORD)
152
182
  }
153
- } else {
183
+ }
184
+ else {
154
185
  // Malay: "se-" prefix for ALL scale words when segment is 1
155
186
  const scaleWord = SCALE_WORDS[scaleIndex - 2]
156
187
  if (segment === 1) {
157
188
  parts.push('se' + scaleWord)
158
- } else {
189
+ }
190
+ else {
159
191
  parts.push(buildSegment(segment) + ' ' + scaleWord)
160
192
  }
161
193
  }
@@ -167,7 +199,12 @@ function buildLargeNumberWords (n) {
167
199
  return parts.join(' ')
168
200
  }
169
201
 
170
- function decimalPartToWords (decimalPart) {
202
+ /**
203
+ * Converts the decimal part (digit string) to Malay words.
204
+ * @param {string} decimalPart - Decimal digits as a string
205
+ * @returns {string} The decimal digits in Malay words
206
+ */
207
+ function decimalPartToWords(decimalPart) {
171
208
  let result = ''
172
209
 
173
210
  let i = 0
@@ -188,12 +225,12 @@ function decimalPartToWords (decimalPart) {
188
225
 
189
226
  /**
190
227
  * Converts a numeric value to Malay words.
191
- *
192
228
  * @param {number | string | bigint} value - The numeric value to convert
193
229
  * @returns {string} The number in Malay words
194
230
  */
195
- function toCardinal (value) {
231
+ function toCardinal(value) {
196
232
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
233
+ checkMax(integerPart, cardinalMax, decimalPart)
197
234
 
198
235
  let result = ''
199
236
 
@@ -219,11 +256,10 @@ function toCardinal (value) {
219
256
  *
220
257
  * Malay ordinals use "ke-" prefix + cardinal number.
221
258
  * Special case: "pertama" for 1st (not "kesatu").
222
- *
223
259
  * @param {bigint} n - Positive integer to convert
224
260
  * @returns {string} Malay ordinal words
225
261
  */
226
- function integerToOrdinal (n) {
262
+ function integerToOrdinal(n) {
227
263
  // Special case: 1st is "pertama"
228
264
  if (n === 1n) {
229
265
  return ORDINAL_FIRST
@@ -235,19 +271,18 @@ function integerToOrdinal (n) {
235
271
 
236
272
  /**
237
273
  * Converts a numeric value to Malay ordinal words.
238
- *
239
274
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
240
275
  * @returns {string} The number as ordinal words
241
276
  * @throws {TypeError} If value is not a valid numeric type
242
277
  * @throws {RangeError} If value is negative, zero, or has a decimal part
243
- *
244
278
  * @example
245
279
  * toOrdinal(1) // 'pertama'
246
280
  * toOrdinal(2) // 'kedua'
247
281
  * toOrdinal(10) // 'kesepuluh'
248
282
  */
249
- function toOrdinal (value) {
283
+ function toOrdinal(value) {
250
284
  const integerPart = parseOrdinalValue(value)
285
+ checkMax(integerPart, ordinalMax)
251
286
  return integerToOrdinal(integerPart)
252
287
  }
253
288
 
@@ -259,19 +294,18 @@ function toOrdinal (value) {
259
294
  * Converts a numeric value to Malay currency words (Ringgit).
260
295
  *
261
296
  * Malaysian Ringgit uses sen as subunit (100 sen = 1 ringgit).
262
- *
263
297
  * @param {number | string | bigint} value - The currency amount to convert
264
298
  * @returns {string} The amount in Malay currency words
265
299
  * @throws {TypeError} If value is not a valid numeric type
266
300
  * @throws {Error} If value is not a valid number format
267
- *
268
301
  * @example
269
302
  * toCurrency(42) // 'empat puluh dua ringgit'
270
303
  * toCurrency(1.50) // 'satu ringgit lima puluh sen'
271
304
  * toCurrency(-5) // 'minus lima ringgit'
272
305
  */
273
- function toCurrency (value) {
306
+ function toCurrency(value) {
274
307
  const { isNegative, dollars: ringgit, cents: sen } = parseCurrencyValue(value)
308
+ checkMax(ringgit, currencyMax)
275
309
 
276
310
  let result = ''
277
311
  if (isNegative) {
package/src/nb-NO.d.ts CHANGED
@@ -1,11 +1,12 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Norwegian Bokmål words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Norwegian words
6
8
  * @throws {TypeError} If value is not a valid numeric type
7
9
  * @throws {Error} If value is not a valid number format
8
- *
9
10
  * @example
10
11
  * toCardinal(21) // 'tjue-en'
11
12
  * toCardinal(101) // 'en hundre og en'
@@ -14,12 +15,10 @@
14
15
  export function toCardinal(value: number | string | bigint): string;
15
16
  /**
16
17
  * Converts a numeric value to Norwegian Bokmål ordinal words.
17
- *
18
18
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
19
19
  * @returns {string} The number as ordinal words
20
20
  * @throws {TypeError} If value is not a valid numeric type
21
21
  * @throws {RangeError} If value is negative, zero, or has a decimal part
22
- *
23
22
  * @example
24
23
  * toOrdinal(1) // 'første'
25
24
  * toOrdinal(2) // 'andre'
@@ -30,12 +29,10 @@ export function toOrdinal(value: number | string | bigint): string;
30
29
  * Converts a numeric value to Norwegian currency words (Norwegian Krone).
31
30
  *
32
31
  * Uses krone/kroner and øre (100 øre = 1 krone).
33
- *
34
32
  * @param {number | string | bigint} value - The currency amount to convert
35
33
  * @returns {string} The amount in Norwegian currency words
36
34
  * @throws {TypeError} If value is not a valid numeric type
37
35
  * @throws {Error} If value is not a valid number format
38
- *
39
36
  * @example
40
37
  * toCurrency(1) // 'en krone'
41
38
  * toCurrency(42) // 'førti-to kroner'
package/src/nb-NO.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 { western } from './utils/scale.js'
16
18
 
17
19
  // ============================================================================
18
20
  // Vocabulary (module-level constants)
@@ -33,11 +35,21 @@ const DECIMAL_SEP = 'komma'
33
35
  // Short scale: million, milliard, billion, etc.
34
36
  const SCALES = ['million', 'milliard', 'billion', 'billiard', 'kvintillion', 'sekstillion', 'septillion', 'oktillion']
35
37
 
38
+ // Supported magnitude ceiling (checked at the public entry points). SCALES is
39
+ // indexed [scaleIndex - 2] (units and thousands separate), so segments are
40
+ // [units, thousands, then one per SCALES entry] -> ceiling 10^((SCALES.length +
41
+ // 2) * 3) = 10^30. Ordinal (cardinal + suffix) and currency build on the
42
+ // cardinal, and the decimal is spelled via integerToWords, so all share it.
43
+ export const cardinalMax = western(SCALES.length + 1)
44
+ export const ordinalMax = western(SCALES.length + 1)
45
+ export const currencyMax = western(SCALES.length + 1)
46
+
36
47
  // ============================================================================
37
48
  // Ordinal Vocabulary
38
49
  // ============================================================================
39
50
 
40
51
  // Norwegian ordinals: 1st-12th special forms, then cardinal + -de/-te
52
+ /** @type {Record<number, string>} */
41
53
  const ORDINAL_SPECIAL = {
42
54
  1: 'første',
43
55
  2: 'andre',
@@ -50,7 +62,7 @@ const ORDINAL_SPECIAL = {
50
62
  9: 'niende',
51
63
  10: 'tiende',
52
64
  11: 'ellevte',
53
- 12: 'tolvte'
65
+ 12: 'tolvte',
54
66
  }
55
67
 
56
68
  // ============================================================================
@@ -68,8 +80,10 @@ const ORE = 'øre' // same singular and plural
68
80
  /**
69
81
  * Builds segment word for 0-999.
70
82
  * Returns object with word and hasHundred flag.
83
+ * @param {number} n - Integer 0-999
84
+ * @returns {{word: string, hasHundred: boolean}} Segment word and hundred flag
71
85
  */
72
- function buildSegment (n) {
86
+ function buildSegment(n) {
73
87
  if (n === 0) return { word: '', hasHundred: false }
74
88
 
75
89
  const ones = n % 10
@@ -90,16 +104,20 @@ function buildSegment (n) {
90
104
 
91
105
  if (tensOnes === 0) {
92
106
  // Just hundreds
93
- } else if (tensOnes < 10) {
107
+ }
108
+ else if (tensOnes < 10) {
94
109
  // Single digit
95
110
  parts.push(ONES[ones])
96
- } else if (tensOnes < 20) {
111
+ }
112
+ else if (tensOnes < 20) {
97
113
  // Teens
98
114
  parts.push(TEENS[ones])
99
- } else if (ones === 0) {
115
+ }
116
+ else if (ones === 0) {
100
117
  // Even tens
101
118
  parts.push(TENS[tens])
102
- } else {
119
+ }
120
+ else {
103
121
  // Hyphenated: "tjue-en"
104
122
  parts.push(TENS[tens] + '-' + ONES[ones])
105
123
  }
@@ -117,11 +135,10 @@ function buildSegment (n) {
117
135
 
118
136
  /**
119
137
  * Converts a non-negative integer to Norwegian Bokmål words.
120
- *
121
138
  * @param {bigint} n - Non-negative integer to convert
122
139
  * @returns {string} Norwegian words
123
140
  */
124
- function integerToWords (n) {
141
+ function integerToWords(n) {
125
142
  if (n === 0n) return ZERO
126
143
 
127
144
  // Fast path: numbers < 1000
@@ -141,7 +158,8 @@ function integerToWords (n) {
141
158
  // Comma before hundreds, " og " before small numbers
142
159
  if (remainderResult.hasHundred) {
143
160
  result += ', ' + remainderResult.word
144
- } else {
161
+ }
162
+ else {
145
163
  result += ' og ' + remainderResult.word
146
164
  }
147
165
  }
@@ -155,11 +173,10 @@ function integerToWords (n) {
155
173
 
156
174
  /**
157
175
  * Builds words for numbers >= 1,000,000.
158
- *
159
176
  * @param {bigint} n - Number >= 1,000,000
160
177
  * @returns {string} Norwegian words
161
178
  */
162
- function buildLargeNumberWords (n) {
179
+ function buildLargeNumberWords(n) {
163
180
  const numStr = n.toString()
164
181
  const len = numStr.length
165
182
 
@@ -191,10 +208,12 @@ function buildLargeNumberWords (n) {
191
208
  if (scaleIndex === 0) {
192
209
  // Units segment
193
210
  parts.push({ word: segmentResult.word, hasHundred: segmentResult.hasHundred, type: 'units' })
194
- } else if (scaleIndex === 1) {
211
+ }
212
+ else if (scaleIndex === 1) {
195
213
  // Thousands
196
214
  parts.push({ word: segmentResult.word + ' ' + THOUSAND, hasHundred: false, type: 'thousand' })
197
- } else {
215
+ }
216
+ else {
198
217
  // Millions+
199
218
  const scaleWord = SCALES[scaleIndex - 2]
200
219
  parts.push({ word: segmentResult.word + ' ' + scaleWord, hasHundred: false, type: 'million' })
@@ -210,11 +229,10 @@ function buildLargeNumberWords (n) {
210
229
 
211
230
  /**
212
231
  * Joins parts with Norwegian spacing and comma rules.
213
- *
214
- * @param {Array} parts - Parts with type metadata
232
+ * @param {Array<{word: string, hasHundred: boolean, type: string}>} parts - Parts with type metadata
215
233
  * @returns {string} Joined string
216
234
  */
217
- function joinNorwegianParts (parts) {
235
+ function joinNorwegianParts(parts) {
218
236
  if (parts.length === 0) return ZERO
219
237
  if (parts.length === 1) return parts[0].word
220
238
 
@@ -231,17 +249,21 @@ function joinNorwegianParts (parts) {
231
249
  // After thousands: comma if next has hundred, else " og "
232
250
  if (nextPart.hasHundred) {
233
251
  result.push(', ')
234
- } else {
252
+ }
253
+ else {
235
254
  result.push(' og ')
236
255
  }
237
- } else if (part.type === 'million') {
256
+ }
257
+ else if (part.type === 'million') {
238
258
  // After millions: " og " for units without hundred, space otherwise
239
259
  if (nextPart.type === 'units' && !nextPart.hasHundred) {
240
260
  result.push(' og ')
241
- } else {
261
+ }
262
+ else {
242
263
  result.push(' ')
243
264
  }
244
- } else {
265
+ }
266
+ else {
245
267
  result.push(' ')
246
268
  }
247
269
  }
@@ -252,11 +274,10 @@ function joinNorwegianParts (parts) {
252
274
 
253
275
  /**
254
276
  * Converts decimal digits to Norwegian words.
255
- *
256
277
  * @param {string} decimalPart - Decimal digits (without the point)
257
278
  * @returns {string} Norwegian words for decimal part
258
279
  */
259
- function decimalPartToWords (decimalPart) {
280
+ function decimalPartToWords(decimalPart) {
260
281
  let result = ''
261
282
 
262
283
  // Handle leading zeros
@@ -279,19 +300,20 @@ function decimalPartToWords (decimalPart) {
279
300
 
280
301
  /**
281
302
  * Converts a numeric value to Norwegian Bokmål words.
282
- *
283
303
  * @param {number | string | bigint} value - The numeric value to convert
284
304
  * @returns {string} The number in Norwegian words
285
305
  * @throws {TypeError} If value is not a valid numeric type
286
306
  * @throws {Error} If value is not a valid number format
287
- *
288
307
  * @example
289
308
  * toCardinal(21) // 'tjue-en'
290
309
  * toCardinal(101) // 'en hundre og en'
291
310
  * toCardinal(1000000) // 'en million'
292
311
  */
293
- function toCardinal (value) {
312
+ function toCardinal(value) {
294
313
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
314
+ // Both the integer part and the decimal's significant digits are spelled via
315
+ // the scale builder, so both must clear the ceiling.
316
+ checkMax(integerPart, cardinalMax, decimalPart)
295
317
 
296
318
  let result = ''
297
319
 
@@ -320,11 +342,10 @@ function toCardinal (value) {
320
342
  * Teens (13-19): drop -en and add -ende (tretten → trettende)
321
343
  * Numbers ending in 'en' (one): replace with 'ende' (tjue-en → tjue-ende)
322
344
  * Numbers ending in 9 (ni): add 'ede' (nitti-ni → nitti-niede)
323
- *
324
345
  * @param {bigint} n - Positive integer to convert
325
346
  * @returns {string} Norwegian ordinal words
326
347
  */
327
- function integerToOrdinal (n) {
348
+ function integerToOrdinal(n) {
328
349
  // Special forms for 1-12
329
350
  if (n >= 1n && n <= 12n) {
330
351
  return ORDINAL_SPECIAL[Number(n)]
@@ -357,19 +378,18 @@ function integerToOrdinal (n) {
357
378
 
358
379
  /**
359
380
  * Converts a numeric value to Norwegian Bokmål ordinal words.
360
- *
361
381
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
362
382
  * @returns {string} The number as ordinal words
363
383
  * @throws {TypeError} If value is not a valid numeric type
364
384
  * @throws {RangeError} If value is negative, zero, or has a decimal part
365
- *
366
385
  * @example
367
386
  * toOrdinal(1) // 'første'
368
387
  * toOrdinal(2) // 'andre'
369
388
  * toOrdinal(21) // 'tjue-ende'
370
389
  */
371
- function toOrdinal (value) {
390
+ function toOrdinal(value) {
372
391
  const integerPart = parseOrdinalValue(value)
392
+ checkMax(integerPart, ordinalMax)
373
393
  return integerToOrdinal(integerPart)
374
394
  }
375
395
 
@@ -381,19 +401,18 @@ function toOrdinal (value) {
381
401
  * Converts a numeric value to Norwegian currency words (Norwegian Krone).
382
402
  *
383
403
  * Uses krone/kroner and øre (100 øre = 1 krone).
384
- *
385
404
  * @param {number | string | bigint} value - The currency amount to convert
386
405
  * @returns {string} The amount in Norwegian currency words
387
406
  * @throws {TypeError} If value is not a valid numeric type
388
407
  * @throws {Error} If value is not a valid number format
389
- *
390
408
  * @example
391
409
  * toCurrency(1) // 'en krone'
392
410
  * toCurrency(42) // 'førti-to kroner'
393
411
  * toCurrency(1.50) // 'en krone og femti øre'
394
412
  */
395
- function toCurrency (value) {
413
+ function toCurrency(value) {
396
414
  const { isNegative, dollars: kroner, cents: ore } = parseCurrencyValue(value)
415
+ checkMax(kroner, currencyMax)
397
416
 
398
417
  let result = ''
399
418
  if (isNegative) {
@@ -404,7 +423,8 @@ function toCurrency (value) {
404
423
  if (kroner > 0n || ore === 0n) {
405
424
  if (kroner === 1n) {
406
425
  result += 'en ' + KRONE
407
- } else {
426
+ }
427
+ else {
408
428
  result += integerToWords(kroner) + ' ' + KRONER
409
429
  }
410
430
  }
package/src/nl-NL.d.ts CHANGED
@@ -1,35 +1,61 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CardinalOptions
6
+ * @property {boolean} [accentOne] - Use "één" instead of "een"
7
+ * @property {boolean} [includeOptionalAnd] - Include "en" before small numbers
8
+ * @property {boolean} [noHundredPairing] - Disable hundred pairing (1104→duizend honderdvier)
9
+ */
10
+ /** @type {Required<CardinalOptions>} */
11
+ export const cardinalDefaults: Required<CardinalOptions>;
12
+ /**
13
+ * @typedef {object} CurrencyOptions
14
+ * @property {boolean} [and] - Include "en" between euros and cents
15
+ */
16
+ /** @type {Required<CurrencyOptions>} */
17
+ export const currencyDefaults: Required<CurrencyOptions>;
18
+ export type CardinalOptions = {
19
+ /**
20
+ * - Use "één" instead of "een"
21
+ */
22
+ accentOne?: boolean | undefined;
23
+ /**
24
+ * - Include "en" before small numbers
25
+ */
26
+ includeOptionalAnd?: boolean | undefined;
27
+ /**
28
+ * - Disable hundred pairing (1104→duizend honderdvier)
29
+ */
30
+ noHundredPairing?: boolean | undefined;
31
+ };
32
+ export type CurrencyOptions = {
33
+ /**
34
+ * - Include "en" between euros and cents
35
+ */
36
+ and?: boolean | undefined;
37
+ };
1
38
  /**
2
39
  * Converts a numeric value to Dutch words.
3
40
  *
4
41
  * This is the main public API. It accepts any valid numeric input
5
42
  * (number, string, or bigint) and handles parsing internally.
6
- *
7
43
  * @param {number | string | bigint} value - The numeric value to convert
8
- * @param {Object} [options] - Optional configuration
9
- * @param {boolean} [options.accentOne=true] - Use "één" instead of "een"
10
- * @param {boolean} [options.includeOptionalAnd=false] - Include "en" before small numbers
11
- * @param {boolean} [options.noHundredPairing=false] - Disable hundred pairing (1104→duizend honderdvier)
44
+ * @param {CardinalOptions} [options] - Optional configuration
12
45
  * @returns {string} The number in Dutch words
13
46
  * @throws {TypeError} If value is not a valid numeric type
14
47
  * @throws {Error} If value is not a valid number format
15
- *
16
48
  * @example
17
49
  * toCardinal(21) // 'eenentwintig'
18
50
  * toCardinal(1) // 'één'
19
51
  * toCardinal(1, {accentOne: false}) // 'een'
20
52
  * toCardinal(1104) // 'elfhonderd vier'
21
53
  */
22
- export function toCardinal(value: number | string | bigint, options?: {
23
- accentOne?: boolean | undefined;
24
- includeOptionalAnd?: boolean | undefined;
25
- noHundredPairing?: boolean | undefined;
26
- }): string;
54
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
27
55
  /**
28
56
  * Converts a number to Dutch ordinal words.
29
- *
30
57
  * @param {number | string | bigint} value - The number to convert
31
58
  * @returns {string} Dutch ordinal words
32
- *
33
59
  * @example
34
60
  * toOrdinal(1) // 'eerste'
35
61
  * toOrdinal(21) // 'eenentwintigste'
@@ -38,17 +64,12 @@ export function toCardinal(value: number | string | bigint, options?: {
38
64
  export function toOrdinal(value: number | string | bigint): string;
39
65
  /**
40
66
  * Converts a number to Dutch currency words (Euro).
41
- *
42
67
  * @param {number | string | bigint} value - The amount to convert
43
- * @param {Object} [options]
44
- * @param {boolean} [options.and=true] - Include "en" between euros and cents
68
+ * @param {CurrencyOptions} [options] Optional configuration
45
69
  * @returns {string} Dutch currency words
46
- *
47
70
  * @example
48
71
  * toCurrency(42.50) // 'tweeënveertig euro en vijftig cent'
49
72
  * toCurrency(1) // 'één euro'
50
73
  * toCurrency(0.01) // 'één cent'
51
74
  */
52
- export function toCurrency(value: number | string | bigint, options?: {
53
- and?: boolean | undefined;
54
- }): string;
75
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;