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/hr-HR.js CHANGED
@@ -13,7 +13,9 @@
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 { validateOptions } from './utils/validate-options.js'
16
+ import { checkMax } from './utils/check-max.js'
17
+ import { western } from './utils/scale.js'
18
+ import { resolveOptions } from './utils/resolve-options.js'
17
19
 
18
20
  // ============================================================================
19
21
  // Vocabulary
@@ -71,14 +73,20 @@ const SCALE_FORMS = [
71
73
  ['trilijun', 'trilijuna', 'trilijuna'],
72
74
  ['trilijarda', 'trilijarde', 'trilijarda'],
73
75
  ['kvadrilijun', 'kvadrilijuna', 'kvadrilijuna'],
74
- ['kvadrilijarda', 'kvadrilijarde', 'kvadrilijarda']
76
+ ['kvadrilijarda', 'kvadrilijarde', 'kvadrilijarda'],
75
77
  ]
76
78
 
77
79
  // ============================================================================
78
80
  // Segment Building
79
81
  // ============================================================================
80
82
 
81
- function pluralize (n, forms) {
83
+ /**
84
+ * Selects the correct plural form for a count.
85
+ * @param {number | bigint} n - The count
86
+ * @param {string[]} forms - The [one, few, many] plural forms
87
+ * @returns {string} The selected plural form
88
+ */
89
+ function pluralize(n, forms) {
82
90
  const num = typeof n === 'bigint' ? Number(n) : n
83
91
  const lastDigit = num % 10
84
92
  const lastTwoDigits = num % 100
@@ -92,7 +100,12 @@ function pluralize (n, forms) {
92
100
  return forms[2]
93
101
  }
94
102
 
95
- function buildSegmentMasc (n) {
103
+ /**
104
+ * Builds the masculine words for a 0-999 segment.
105
+ * @param {number} n - Number 0-999
106
+ * @returns {string} The segment words
107
+ */
108
+ function buildSegmentMasc(n) {
96
109
  if (n === 0) return ''
97
110
 
98
111
  const onesDigit = n % 10
@@ -111,14 +124,20 @@ function buildSegmentMasc (n) {
111
124
 
112
125
  if (tensDigit === 1) {
113
126
  parts.push(TEENS[onesDigit])
114
- } else if (onesDigit > 0) {
127
+ }
128
+ else if (onesDigit > 0) {
115
129
  parts.push(ONES_MASC[onesDigit])
116
130
  }
117
131
 
118
132
  return parts.join(' ')
119
133
  }
120
134
 
121
- function buildSegmentFem (n) {
135
+ /**
136
+ * Builds the feminine words for a 0-999 segment.
137
+ * @param {number} n - Number 0-999
138
+ * @returns {string} The segment words
139
+ */
140
+ function buildSegmentFem(n) {
122
141
  if (n === 0) return ''
123
142
 
124
143
  const onesDigit = n % 10
@@ -137,7 +156,8 @@ function buildSegmentFem (n) {
137
156
 
138
157
  if (tensDigit === 1) {
139
158
  parts.push(TEENS[onesDigit])
140
- } else if (onesDigit > 0) {
159
+ }
160
+ else if (onesDigit > 0) {
141
161
  parts.push(ONES_FEM[onesDigit])
142
162
  }
143
163
 
@@ -148,7 +168,13 @@ function buildSegmentFem (n) {
148
168
  // Conversion Functions
149
169
  // ============================================================================
150
170
 
151
- function integerToWords (n, gender) {
171
+ /**
172
+ * Converts a non-negative integer to Croatian words.
173
+ * @param {bigint} n - Non-negative integer to convert
174
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
175
+ * @returns {string} The number in Croatian words
176
+ */
177
+ function integerToWords(n, gender) {
152
178
  if (n === 0n) return ZERO
153
179
 
154
180
  if (n < 1000n) {
@@ -158,7 +184,13 @@ function integerToWords (n, gender) {
158
184
  return buildLargeNumberWords(n, gender)
159
185
  }
160
186
 
161
- function buildLargeNumberWords (n, gender) {
187
+ /**
188
+ * Builds words for numbers >= 1000.
189
+ * @param {bigint} n - Number >= 1000
190
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
191
+ * @returns {string} The number in Croatian words
192
+ */
193
+ function buildLargeNumberWords(n, gender) {
162
194
  const numStr = n.toString()
163
195
  const len = numStr.length
164
196
 
@@ -185,7 +217,8 @@ function buildLargeNumberWords (n, gender) {
185
217
  if (segment !== 0) {
186
218
  if (scaleIndex === 0) {
187
219
  parts.push(gender === 'feminine' ? buildSegmentFem(segment) : buildSegmentMasc(segment))
188
- } else {
220
+ }
221
+ else {
189
222
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
190
223
  const scaleWord = pluralize(segment, scaleForms)
191
224
  // Thousands (scaleIndex=1) are feminine, others masculine
@@ -201,7 +234,13 @@ function buildLargeNumberWords (n, gender) {
201
234
  return parts.join(' ')
202
235
  }
203
236
 
204
- function decimalPartToWords (decimalPart, gender) {
237
+ /**
238
+ * Converts the decimal-part digit string to Croatian words.
239
+ * @param {string} decimalPart - The decimal digits as a string
240
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
241
+ * @returns {string} The decimal part in Croatian words
242
+ */
243
+ function decimalPartToWords(decimalPart, gender) {
205
244
  let result = ''
206
245
  let i = 0
207
246
 
@@ -220,20 +259,38 @@ function decimalPartToWords (decimalPart, gender) {
220
259
  return result
221
260
  }
222
261
 
262
+ // Supported magnitude ceilings (checked at the public entry points). Both
263
+ // tables are indexed [scaleIndex - 1] (units separate), so the ceiling is
264
+ // 10^((length + 1) * 3): cardinal/currency 10^30, ordinal 10^15.
265
+ export const cardinalMax = western(SCALE_FORMS.length)
266
+ export const ordinalMax = western(ORDINAL_SCALES.length)
267
+ export const currencyMax = western(SCALE_FORMS.length)
268
+
269
+ /**
270
+ * @typedef {object} CardinalOptions
271
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
272
+ */
273
+
274
+ /** @type {Required<CardinalOptions>} */
275
+ export const cardinalDefaults = { gender: 'masculine' }
276
+
277
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
278
+ export const cardinalValues = { gender: ['masculine', 'feminine'] }
279
+
223
280
  /**
224
281
  * Converts a numeric value to Croatian words.
225
- *
226
282
  * @param {number | string | bigint} value - The numeric value to convert
227
- * @param {Object} [options] - Optional configuration
228
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
283
+ * @param {CardinalOptions} [options] - Optional configuration
229
284
  * @returns {string} The number in Croatian words
230
285
  */
231
- function toCardinal (value, options) {
232
- options = validateOptions(options)
286
+ function toCardinal(value, options) {
233
287
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
288
+ // Both the integer part and the decimal's significant digits are spelled via
289
+ // the scale builder, so both must clear the ceiling.
290
+ checkMax(integerPart, cardinalMax, decimalPart)
234
291
 
235
292
  // Apply option defaults
236
- const { gender = 'masculine' } = options
293
+ const { gender } = resolveOptions(options, cardinalDefaults, cardinalValues)
237
294
 
238
295
  let result = ''
239
296
 
@@ -256,11 +313,10 @@ function toCardinal (value, options) {
256
313
 
257
314
  /**
258
315
  * Builds ordinal for a 0-99 segment when it's the final (ordinal) part.
259
- *
260
316
  * @param {number} n - Number 0-99
261
317
  * @returns {string} Ordinal words
262
318
  */
263
- function buildOrdinalTensOnes (n) {
319
+ function buildOrdinalTensOnes(n) {
264
320
  if (n === 0) return ''
265
321
 
266
322
  const onesDigit = n % 10
@@ -283,11 +339,10 @@ function buildOrdinalTensOnes (n) {
283
339
 
284
340
  /**
285
341
  * Converts a positive integer to Croatian ordinal words (masculine nominative).
286
- *
287
342
  * @param {bigint} n - Positive integer to convert
288
343
  * @returns {string} Ordinal Croatian words
289
344
  */
290
- function integerToOrdinal (n) {
345
+ function integerToOrdinal(n) {
291
346
  if (n < 100n) {
292
347
  return buildOrdinalTensOnes(Number(n))
293
348
  }
@@ -325,11 +380,10 @@ function integerToOrdinal (n) {
325
380
 
326
381
  /**
327
382
  * Builds ordinal words for numbers >= 1,000,000.
328
- *
329
383
  * @param {bigint} n - Number >= 1,000,000
330
384
  * @returns {string} Ordinal Croatian words
331
385
  */
332
- function buildLargeOrdinal (n) {
386
+ function buildLargeOrdinal(n) {
333
387
  const numStr = n.toString()
334
388
  const len = numStr.length
335
389
 
@@ -364,19 +418,23 @@ function buildLargeOrdinal (n) {
364
418
  if (scaleIndex === 0) {
365
419
  if (isLastNonZero) {
366
420
  parts.push(integerToOrdinal(BigInt(segment)))
367
- } else {
421
+ }
422
+ else {
368
423
  parts.push(buildSegmentMasc(segment))
369
424
  }
370
- } else {
425
+ }
426
+ else {
371
427
  if (isLastNonZero) {
372
428
  if (segment === 1) {
373
429
  parts.push(ORDINAL_SCALES[scaleIndex - 1])
374
- } else {
430
+ }
431
+ else {
375
432
  const isFeminine = scaleIndex === 1
376
433
  const segmentWord = isFeminine ? buildSegmentFem(segment) : buildSegmentMasc(segment)
377
434
  parts.push(segmentWord + ' ' + ORDINAL_SCALES[scaleIndex - 1])
378
435
  }
379
- } else {
436
+ }
437
+ else {
380
438
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
381
439
  const scaleWord = pluralize(segment, scaleForms)
382
440
  const isFeminine = scaleIndex === 1
@@ -394,12 +452,10 @@ function buildLargeOrdinal (n) {
394
452
 
395
453
  /**
396
454
  * Converts a numeric value to Croatian ordinal words (masculine nominative).
397
- *
398
455
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
399
456
  * @returns {string} The number as ordinal words
400
457
  * @throws {TypeError} If value is not a valid numeric type
401
458
  * @throws {RangeError} If value is negative, zero, or has a decimal part
402
- *
403
459
  * @example
404
460
  * toOrdinal(1) // 'prvi'
405
461
  * toOrdinal(2) // 'drugi'
@@ -407,8 +463,9 @@ function buildLargeOrdinal (n) {
407
463
  * toOrdinal(100) // 'stoti'
408
464
  * toOrdinal(1000) // 'tisućiti'
409
465
  */
410
- function toOrdinal (value) {
466
+ function toOrdinal(value) {
411
467
  const integerPart = parseOrdinalValue(value)
468
+ checkMax(integerPart, ordinalMax)
412
469
  return integerToOrdinal(integerPart)
413
470
  }
414
471
 
@@ -418,20 +475,19 @@ function toOrdinal (value) {
418
475
 
419
476
  /**
420
477
  * Converts a numeric value to Croatian currency words (Euro).
421
- *
422
478
  * @param {number | string | bigint} value - The currency amount to convert
423
479
  * @returns {string} The amount in Croatian currency words
424
480
  * @throws {TypeError} If value is not a valid numeric type
425
481
  * @throws {Error} If value is not a valid number format
426
- *
427
482
  * @example
428
483
  * toCurrency(42) // 'četrdeset dva eura'
429
484
  * toCurrency(1) // 'jedan euro'
430
485
  * toCurrency(1.50) // 'jedan euro pedeset centi'
431
486
  * toCurrency(-5) // 'minus pet eura'
432
487
  */
433
- function toCurrency (value) {
488
+ function toCurrency(value) {
434
489
  const { isNegative, dollars: euros, cents } = parseCurrencyValue(value)
490
+ checkMax(euros, currencyMax)
435
491
 
436
492
  let result = ''
437
493
  if (isNegative) {
package/src/hu-HU.d.ts CHANGED
@@ -1,18 +1,18 @@
1
+ export const cardinalMax: null;
2
+ export const ordinalMax: null;
3
+ export const currencyMax: null;
1
4
  /**
2
5
  * Converts a numeric value to Hungarian words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Hungarian words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Hungarian 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) // 'első'
18
18
  * toOrdinal(2) // 'második'
@@ -24,12 +24,10 @@ export function toOrdinal(value: number | string | bigint): string;
24
24
  *
25
25
  * Uses forint (no plural form needed in Hungarian).
26
26
  * Fillér (1/100) is rarely used but included for completeness.
27
- *
28
27
  * @param {number | string | bigint} value - The currency amount to convert
29
28
  * @returns {string} The amount in Hungarian currency words
30
29
  * @throws {TypeError} If value is not a valid numeric type
31
30
  * @throws {Error} If value is not a valid number format
32
- *
33
31
  * @example
34
32
  * toCurrency(42) // 'negyvenkettő forint'
35
33
  * toCurrency(1.50) // 'egy forint ötven fillér'
package/src/hu-HU.js CHANGED
@@ -13,6 +13,12 @@
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 { UNBOUNDED } from './utils/scale.js'
17
+
18
+ // No fixed scale ceiling — the speller composes every magnitude.
19
+ export const cardinalMax = UNBOUNDED
20
+ export const ordinalMax = UNBOUNDED
21
+ export const currencyMax = UNBOUNDED
16
22
 
17
23
  // ============================================================================
18
24
  // Vocabulary
@@ -66,7 +72,7 @@ const WORDS = new Map([
66
72
  [3n, 'három'],
67
73
  [2n, 'kettő'],
68
74
  [1n, 'egy'],
69
- [0n, 'nulla']
75
+ [0n, 'nulla'],
70
76
  ])
71
77
 
72
78
  const ZERO = 'nulla'
@@ -79,6 +85,7 @@ const DECIMAL_SEP = 'egész'
79
85
 
80
86
  // Hungarian ordinals: special forms 1-10, then cardinal + -dik/-ik suffix
81
87
  // Vowel harmony determines -dik vs -ik
88
+ /** @type {Record<number, string>} */
82
89
  const ORDINAL_SPECIAL = {
83
90
  1: 'első',
84
91
  2: 'második',
@@ -99,7 +106,7 @@ const ORDINAL_SPECIAL = {
99
106
  80: 'nyolcvanadik',
100
107
  90: 'kilencvenedik',
101
108
  100: 'századik',
102
- 1000: 'ezredik'
109
+ 1000: 'ezredik',
103
110
  }
104
111
 
105
112
  // ============================================================================
@@ -113,20 +120,32 @@ const FILLER = 'fillér' // subunit (rarely used, same singular and plural)
113
120
  // Conversion Functions
114
121
  // ============================================================================
115
122
 
116
- function wordForScale (value) {
123
+ /**
124
+ * @param {bigint} value The scale or vocabulary key to look up
125
+ * @returns {string | undefined} The Hungarian word for the key, or undefined if absent
126
+ */
127
+ function wordForScale(value) {
117
128
  return WORDS.get(value)
118
129
  }
119
130
 
120
- function tensToCardinal (n) {
131
+ /**
132
+ * @param {bigint} n The integer (under 100) to convert
133
+ * @returns {string} The number in Hungarian words
134
+ */
135
+ function tensToCardinal(n) {
121
136
  if (WORDS.has(n)) {
122
- return WORDS.get(n)
137
+ return /** @type {string} */ (WORDS.get(n))
123
138
  }
124
139
  const tens = n / 10n
125
140
  const units = n % 10n
126
141
  return wordForScale(tens * 10n) + integerToWordsInner(units)
127
142
  }
128
143
 
129
- function hundredsToCardinal (n) {
144
+ /**
145
+ * @param {bigint} n The integer (under 1000) to convert
146
+ * @returns {string} The number in Hungarian words
147
+ */
148
+ function hundredsToCardinal(n) {
130
149
  const hundreds = n / 100n
131
150
  let prefix = 'száz'
132
151
  if (hundreds !== 1n) {
@@ -136,7 +155,11 @@ function hundredsToCardinal (n) {
136
155
  return prefix + postfix
137
156
  }
138
157
 
139
- function thousandsToCardinal (n) {
158
+ /**
159
+ * @param {bigint} n The integer (under one million) to convert
160
+ * @returns {string} The number in Hungarian words
161
+ */
162
+ function thousandsToCardinal(n) {
140
163
  const thousands = n / 1000n
141
164
  let prefix = 'ezer'
142
165
  if (thousands !== 1n) {
@@ -156,10 +179,14 @@ const SCALES = [
156
179
  1_000_000_000_000_000n,
157
180
  1_000_000_000_000n,
158
181
  1_000_000_000n,
159
- 1_000_000n
182
+ 1_000_000n,
160
183
  ]
161
184
 
162
- function bigNumberToCardinal (n) {
185
+ /**
186
+ * @param {bigint} n The integer (one million or greater) to convert
187
+ * @returns {string} The number in Hungarian words
188
+ */
189
+ function bigNumberToCardinal(n) {
163
190
  // Find appropriate scale using BigInt comparison (avoids toString)
164
191
  let exp = 1_000_000n
165
192
  for (const scale of SCALES) {
@@ -175,7 +202,12 @@ function bigNumberToCardinal (n) {
175
202
  return prefix + wordForScale(exp) + postfix
176
203
  }
177
204
 
178
- function integerToWordsInner (n, zeroWord = ZERO) {
205
+ /**
206
+ * @param {bigint} n The integer to convert
207
+ * @param {string} [zeroWord] The word to use for zero (empty string in compound contexts)
208
+ * @returns {string} The number in Hungarian words
209
+ */
210
+ function integerToWordsInner(n, zeroWord = ZERO) {
179
211
  // Normalize to BigInt for consistent comparisons
180
212
  if (typeof n !== 'bigint') n = BigInt(n)
181
213
 
@@ -186,7 +218,7 @@ function integerToWordsInner (n, zeroWord = ZERO) {
186
218
  return 'két'
187
219
  }
188
220
  if (n < 30n) {
189
- return wordForScale(n)
221
+ return /** @type {string} */ (wordForScale(n))
190
222
  }
191
223
  if (n < 100n) {
192
224
  return tensToCardinal(n)
@@ -200,11 +232,19 @@ function integerToWordsInner (n, zeroWord = ZERO) {
200
232
  return bigNumberToCardinal(n)
201
233
  }
202
234
 
203
- function integerToWords (n) {
235
+ /**
236
+ * @param {bigint} n The integer to convert
237
+ * @returns {string} The number in Hungarian words
238
+ */
239
+ function integerToWords(n) {
204
240
  return integerToWordsInner(n, ZERO)
205
241
  }
206
242
 
207
- function decimalPartToWords (decimalPart) {
243
+ /**
244
+ * @param {string} decimalPart The decimal digits (after the separator) to convert
245
+ * @returns {string} The decimal digits in Hungarian words
246
+ */
247
+ function decimalPartToWords(decimalPart) {
208
248
  let result = ''
209
249
  let i = 0
210
250
 
@@ -225,11 +265,10 @@ function decimalPartToWords (decimalPart) {
225
265
 
226
266
  /**
227
267
  * Converts a numeric value to Hungarian words.
228
- *
229
268
  * @param {number | string | bigint} value - The numeric value to convert
230
269
  * @returns {string} The number in Hungarian words
231
270
  */
232
- function toCardinal (value) {
271
+ function toCardinal(value) {
233
272
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
234
273
 
235
274
  let result = ''
@@ -256,11 +295,10 @@ function toCardinal (value) {
256
295
  *
257
296
  * Hungarian ordinals: első (1st), második (2nd), harmadik (3rd), etc.
258
297
  * 1-10 have special forms, others use cardinal + -dik/-edik/-adik/-ödik suffix.
259
- *
260
298
  * @param {bigint} n - Positive integer to convert
261
299
  * @returns {string} Hungarian ordinal words
262
300
  */
263
- function integerToOrdinal (n) {
301
+ function integerToOrdinal(n) {
264
302
  const num = Number(n)
265
303
 
266
304
  // Exact special forms
@@ -307,18 +345,16 @@ function integerToOrdinal (n) {
307
345
 
308
346
  /**
309
347
  * Converts a numeric value to Hungarian ordinal words.
310
- *
311
348
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
312
349
  * @returns {string} The number as ordinal words
313
350
  * @throws {TypeError} If value is not a valid numeric type
314
351
  * @throws {RangeError} If value is negative, zero, or has a decimal part
315
- *
316
352
  * @example
317
353
  * toOrdinal(1) // 'első'
318
354
  * toOrdinal(2) // 'második'
319
355
  * toOrdinal(21) // 'huszonegyedik'
320
356
  */
321
- function toOrdinal (value) {
357
+ function toOrdinal(value) {
322
358
  const integerPart = parseOrdinalValue(value)
323
359
  return integerToOrdinal(integerPart)
324
360
  }
@@ -332,18 +368,16 @@ function toOrdinal (value) {
332
368
  *
333
369
  * Uses forint (no plural form needed in Hungarian).
334
370
  * Fillér (1/100) is rarely used but included for completeness.
335
- *
336
371
  * @param {number | string | bigint} value - The currency amount to convert
337
372
  * @returns {string} The amount in Hungarian currency words
338
373
  * @throws {TypeError} If value is not a valid numeric type
339
374
  * @throws {Error} If value is not a valid number format
340
- *
341
375
  * @example
342
376
  * toCurrency(42) // 'negyvenkettő forint'
343
377
  * toCurrency(1.50) // 'egy forint ötven fillér'
344
378
  * toCurrency(-5) // 'mínusz öt forint'
345
379
  */
346
- function toCurrency (value) {
380
+ function toCurrency(value) {
347
381
  const { isNegative, dollars: forint, cents: filler } = parseCurrencyValue(value)
348
382
 
349
383
  let result = ''
package/src/id-ID.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 Indonesian words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Indonesian words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Indonesian 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) // 'pertama'
18
18
  * toOrdinal(2) // 'kedua'
@@ -24,12 +24,10 @@ export function toOrdinal(value: number | string | bigint): string;
24
24
  *
25
25
  * Indonesian Rupiah has no subunit in modern usage (sen are historical).
26
26
  * Amounts are rounded to whole rupiah.
27
- *
28
27
  * @param {number | string | bigint} value - The currency amount to convert
29
28
  * @returns {string} The amount in Indonesian currency words
30
29
  * @throws {TypeError} If value is not a valid numeric type
31
30
  * @throws {Error} If value is not a valid number format
32
- *
33
31
  * @example
34
32
  * toCurrency(42) // 'empat puluh dua rupiah'
35
33
  * toCurrency(1000) // 'seribu rupiah'