n2words 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/CHANGELOG.md +128 -42
  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 +31 -22
  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 +100 -38
  266. package/src/sr-Latn-RS.d.ts +35 -15
  267. package/src/sr-Latn-RS.js +100 -38
  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/id-ID.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', 'miliar', 'triliun', 'kuadriliun', 'kuantiliun', 'sekstiliun', 'septiliun', 'oktiliun', 'noniliun', 'desiliun']
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 = 'nol'
30
37
  const NEGATIVE = 'min'
31
38
  const DECIMAL_SEP = 'koma'
@@ -48,7 +55,12 @@ const RUPIAH = 'rupiah'
48
55
  // Segment Building
49
56
  // ============================================================================
50
57
 
51
- function buildSegment (n) {
58
+ /**
59
+ * Builds the Indonesian words for a 1-3 digit segment (0-999).
60
+ * @param {number} n - The segment value (0-999)
61
+ * @returns {string} The segment in Indonesian words
62
+ */
63
+ function buildSegment(n) {
52
64
  if (n === 0) return ''
53
65
 
54
66
  const onesDigit = n % 10
@@ -61,7 +73,8 @@ function buildSegment (n) {
61
73
  if (hundredsDigit > 0) {
62
74
  if (hundredsDigit === 1) {
63
75
  parts.push('se' + HUNDRED_WORD)
64
- } else {
76
+ }
77
+ else {
65
78
  parts.push(ONES[hundredsDigit] + ' ' + HUNDRED_WORD)
66
79
  }
67
80
  }
@@ -71,13 +84,17 @@ function buildSegment (n) {
71
84
 
72
85
  if (tensOnes === 0) {
73
86
  // Just hundreds
74
- } else if (tensOnes < 10) {
87
+ }
88
+ else if (tensOnes < 10) {
75
89
  parts.push(ONES[tensOnes])
76
- } else if (tensOnes < 20) {
90
+ }
91
+ else if (tensOnes < 20) {
77
92
  parts.push(TEENS[tensOnes - 10])
78
- } else if (onesDigit === 0) {
93
+ }
94
+ else if (onesDigit === 0) {
79
95
  parts.push(TENS[tensDigit])
80
- } else {
96
+ }
97
+ else {
81
98
  parts.push(TENS[tensDigit] + ' ' + ONES[onesDigit])
82
99
  }
83
100
 
@@ -88,7 +105,12 @@ function buildSegment (n) {
88
105
  // Conversion Functions
89
106
  // ============================================================================
90
107
 
91
- function integerToWords (n) {
108
+ /**
109
+ * Converts a non-negative integer to Indonesian words.
110
+ * @param {bigint} n - The integer value to convert
111
+ * @returns {string} The integer in Indonesian words
112
+ */
113
+ function integerToWords(n) {
92
114
  if (n === 0n) return ZERO
93
115
 
94
116
  if (n < 1000n) {
@@ -102,7 +124,8 @@ function integerToWords (n) {
102
124
  let result
103
125
  if (thousands === 1) {
104
126
  result = 'se' + THOUSAND_WORD
105
- } else {
127
+ }
128
+ else {
106
129
  result = buildSegment(thousands) + ' ' + THOUSAND_WORD
107
130
  }
108
131
 
@@ -116,7 +139,12 @@ function integerToWords (n) {
116
139
  return buildLargeNumberWords(n)
117
140
  }
118
141
 
119
- function buildLargeNumberWords (n) {
142
+ /**
143
+ * Builds Indonesian words for large numbers (1,000,000 and above).
144
+ * @param {bigint} n - The integer value to convert
145
+ * @returns {string} The number in Indonesian words
146
+ */
147
+ function buildLargeNumberWords(n) {
120
148
  const numStr = n.toString()
121
149
  const len = numStr.length
122
150
 
@@ -143,13 +171,16 @@ function buildLargeNumberWords (n) {
143
171
  if (segment !== 0) {
144
172
  if (scaleIndex === 0) {
145
173
  parts.push(buildSegment(segment))
146
- } else if (scaleIndex === 1) {
174
+ }
175
+ else if (scaleIndex === 1) {
147
176
  if (segment === 1) {
148
177
  parts.push('se' + THOUSAND_WORD)
149
- } else {
178
+ }
179
+ else {
150
180
  parts.push(buildSegment(segment) + ' ' + THOUSAND_WORD)
151
181
  }
152
- } else {
182
+ }
183
+ else {
153
184
  // Indonesian: "satu juta" not "sejuta"
154
185
  const scaleWord = SCALE_WORDS[scaleIndex - 2]
155
186
  parts.push(buildSegment(segment) + ' ' + scaleWord)
@@ -162,7 +193,12 @@ function buildLargeNumberWords (n) {
162
193
  return parts.join(' ')
163
194
  }
164
195
 
165
- function decimalPartToWords (decimalPart) {
196
+ /**
197
+ * Converts the decimal-part digit string to Indonesian words.
198
+ * @param {string} decimalPart - The decimal digits as a string
199
+ * @returns {string} The decimal part in Indonesian words
200
+ */
201
+ function decimalPartToWords(decimalPart) {
166
202
  let result = ''
167
203
 
168
204
  let i = 0
@@ -183,12 +219,12 @@ function decimalPartToWords (decimalPart) {
183
219
 
184
220
  /**
185
221
  * Converts a numeric value to Indonesian words.
186
- *
187
222
  * @param {number | string | bigint} value - The numeric value to convert
188
223
  * @returns {string} The number in Indonesian words
189
224
  */
190
- function toCardinal (value) {
225
+ function toCardinal(value) {
191
226
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
227
+ checkMax(integerPart, cardinalMax, decimalPart)
192
228
 
193
229
  let result = ''
194
230
 
@@ -214,11 +250,10 @@ function toCardinal (value) {
214
250
  *
215
251
  * Indonesian ordinals use "ke-" prefix + cardinal number.
216
252
  * Special case: "pertama" for 1st (not "kesatu").
217
- *
218
253
  * @param {bigint} n - Positive integer to convert
219
254
  * @returns {string} Indonesian ordinal words
220
255
  */
221
- function integerToOrdinal (n) {
256
+ function integerToOrdinal(n) {
222
257
  // Special case: 1st is "pertama"
223
258
  if (n === 1n) {
224
259
  return ORDINAL_FIRST
@@ -230,19 +265,18 @@ function integerToOrdinal (n) {
230
265
 
231
266
  /**
232
267
  * Converts a numeric value to Indonesian ordinal words.
233
- *
234
268
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
235
269
  * @returns {string} The number as ordinal words
236
270
  * @throws {TypeError} If value is not a valid numeric type
237
271
  * @throws {RangeError} If value is negative, zero, or has a decimal part
238
- *
239
272
  * @example
240
273
  * toOrdinal(1) // 'pertama'
241
274
  * toOrdinal(2) // 'kedua'
242
275
  * toOrdinal(10) // 'kesepuluh'
243
276
  */
244
- function toOrdinal (value) {
277
+ function toOrdinal(value) {
245
278
  const integerPart = parseOrdinalValue(value)
279
+ checkMax(integerPart, ordinalMax)
246
280
  return integerToOrdinal(integerPart)
247
281
  }
248
282
 
@@ -255,19 +289,18 @@ function toOrdinal (value) {
255
289
  *
256
290
  * Indonesian Rupiah has no subunit in modern usage (sen are historical).
257
291
  * Amounts are rounded to whole rupiah.
258
- *
259
292
  * @param {number | string | bigint} value - The currency amount to convert
260
293
  * @returns {string} The amount in Indonesian currency words
261
294
  * @throws {TypeError} If value is not a valid numeric type
262
295
  * @throws {Error} If value is not a valid number format
263
- *
264
296
  * @example
265
297
  * toCurrency(42) // 'empat puluh dua rupiah'
266
298
  * toCurrency(1000) // 'seribu rupiah'
267
299
  * toCurrency(-5) // 'min lima rupiah'
268
300
  */
269
- function toCurrency (value) {
301
+ function toCurrency(value) {
270
302
  const { isNegative, dollars: rupiah } = parseCurrencyValue(value)
303
+ checkMax(rupiah, currencyMax)
271
304
 
272
305
  let result = ''
273
306
  if (isNegative) {
package/src/it-IT.d.ts CHANGED
@@ -1,14 +1,27 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CurrencyOptions
6
+ * @property {boolean} [and] - Use "e" between euros and centesimi
7
+ */
8
+ /** @type {Required<CurrencyOptions>} */
9
+ export const currencyDefaults: Required<CurrencyOptions>;
10
+ export type CurrencyOptions = {
11
+ /**
12
+ * - Use "e" between euros and centesimi
13
+ */
14
+ and?: boolean | undefined;
15
+ };
1
16
  /**
2
17
  * Converts a numeric value to Italian words.
3
18
  *
4
19
  * This is the main public API. It accepts any valid numeric input
5
20
  * (number, string, or bigint) and handles parsing internally.
6
- *
7
21
  * @param {number | string | bigint} value - The numeric value to convert
8
22
  * @returns {string} The number in Italian words
9
23
  * @throws {TypeError} If value is not a valid numeric type
10
24
  * @throws {Error} If value is not a valid number format
11
- *
12
25
  * @example
13
26
  * toCardinal(28) // 'ventotto'
14
27
  * toCardinal(23) // 'ventitré'
@@ -22,12 +35,10 @@ export function toCardinal(value: number | string | bigint): string;
22
35
  *
23
36
  * Italian ordinals: primo, secondo, terzo... (1-10 irregular)
24
37
  * For 11+: cardinal word (drop final vowel) + -esimo
25
- *
26
38
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
27
39
  * @returns {string} The number as ordinal words (masculine form)
28
40
  * @throws {TypeError} If value is not a valid numeric type
29
41
  * @throws {RangeError} If value is negative, zero, or has a decimal part
30
- *
31
42
  * @example
32
43
  * toOrdinal(1) // 'primo'
33
44
  * toOrdinal(2) // 'secondo'
@@ -39,14 +50,11 @@ export function toCardinal(value: number | string | bigint): string;
39
50
  export function toOrdinal(value: number | string | bigint): string;
40
51
  /**
41
52
  * Converts a numeric value to Italian currency words (Euro).
42
- *
43
53
  * @param {number | string | bigint} value - The currency amount to convert
44
- * @param {Object} [options] - Optional configuration
45
- * @param {boolean} [options.and=true] - Use "e" between euros and centesimi
54
+ * @param {CurrencyOptions} [options] - Optional configuration
46
55
  * @returns {string} The amount in Italian currency words
47
56
  * @throws {TypeError} If value is not a valid numeric type
48
57
  * @throws {Error} If value is not a valid number format
49
- *
50
58
  * @example
51
59
  * toCurrency(42.50) // 'quarantadue euro e cinquanta centesimi'
52
60
  * toCurrency(1) // 'un euro'
@@ -54,6 +62,4 @@ export function toOrdinal(value: number | string | bigint): string;
54
62
  * toCurrency(0.01) // 'un centesimo'
55
63
  * toCurrency(42.50, { and: false }) // 'quarantadue euro cinquanta centesimi'
56
64
  */
57
- export function toCurrency(value: number | string | bigint, options?: {
58
- and?: boolean | undefined;
59
- }): string;
65
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;
package/src/it-IT.js CHANGED
@@ -15,7 +15,9 @@
15
15
  import { parseCardinalValue } from './utils/parse-cardinal.js'
16
16
  import { parseCurrencyValue } from './utils/parse-currency.js'
17
17
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
18
- import { validateOptions } from './utils/validate-options.js'
18
+ import { checkMax } from './utils/check-max.js'
19
+ import { longScale } from './utils/scale.js'
20
+ import { resolveOptions } from './utils/resolve-options.js'
19
21
 
20
22
  // ============================================================================
21
23
  // Vocabulary (module-level constants)
@@ -42,6 +44,15 @@ const THOUSAND_PLURAL_SUFFIX = 'mila'
42
44
  // Scale word generation
43
45
  const SCALE_PREFIXES = ['m', 'b', 'tr', 'quadr', 'quint', 'sest', 'sett', 'ott', 'nov', 'dec']
44
46
 
47
+ // Long scale: each prefix yields an -ilione (10^6k) and an -iliardo (10^6k+3),
48
+ // so the table spans 6 powers of ten per prefix; past it the scale word is empty.
49
+ // Each form's maximum supported value (a bigint), derived from the prefix table
50
+ // so it can't drift. Exported per form so they split cleanly and the gate/docs
51
+ // read each as a fact. it-IT shares one ceiling across all three forms.
52
+ export const cardinalMax = longScale(SCALE_PREFIXES.length)
53
+ export const ordinalMax = longScale(SCALE_PREFIXES.length)
54
+ export const currencyMax = longScale(SCALE_PREFIXES.length)
55
+
45
56
  // ============================================================================
46
57
  // Ordinal Vocabulary
47
58
  // ============================================================================
@@ -72,8 +83,10 @@ const CENTESIMI = 'centesimi'
72
83
  * - Tens ending in vowel + uno/otto → drop tens vowel: ventuno, ventotto
73
84
  * - Hundreds cento + otto/ottanta → centotto, centottanta (drop 'o')
74
85
  * - Final 'tre' in compounds becomes 'tré': ventitré, trentatré
86
+ * @param {number} n - Number 0-999 to convert
87
+ * @returns {string} Segment word
75
88
  */
76
- function buildSegment (n) {
89
+ function buildSegment(n) {
77
90
  if (n === 0) return ''
78
91
 
79
92
  const ones = n % 10
@@ -89,7 +102,8 @@ function buildSegment (n) {
89
102
  if (tens === 8 || (tens === 0 && ones === 8)) {
90
103
  // Remove final 'o' from hundreds: cento→cent, duecento→duecent, etc.
91
104
  result = HUNDREDS[hundreds].slice(0, -1)
92
- } else {
105
+ }
106
+ else {
93
107
  result = HUNDREDS[hundreds]
94
108
  }
95
109
  }
@@ -97,28 +111,35 @@ function buildSegment (n) {
97
111
  // Tens and ones
98
112
  if (tens === 0 && ones === 0) {
99
113
  // Nothing more (just hundreds)
100
- } else if (tens === 1) {
114
+ }
115
+ else if (tens === 1) {
101
116
  // Teens: 10-19
102
117
  result += TEENS[ones]
103
- } else if (tens >= 2) {
118
+ }
119
+ else if (tens >= 2) {
104
120
  // 20-99: handle elision for uno (1) and otto (8)
105
121
  if (ones === 1 || ones === 8) {
106
122
  // Use stem form: vent + uno = ventuno, vent + otto = ventotto
107
123
  result += TENS_STEM[tens] + ONES[ones]
108
- } else if (ones === 3) {
124
+ }
125
+ else if (ones === 3) {
109
126
  // Final tre becomes tré
110
127
  result += TENS[tens] + 'tré'
111
- } else if (ones > 0) {
128
+ }
129
+ else if (ones > 0) {
112
130
  result += TENS[tens] + ONES[ones]
113
- } else {
131
+ }
132
+ else {
114
133
  result += TENS[tens]
115
134
  }
116
- } else if (ones > 0) {
135
+ }
136
+ else if (ones > 0) {
117
137
  // 1-9 (tens === 0)
118
138
  if (ones === 3 && hundreds > 0) {
119
139
  // centotré, duecentotré, etc.
120
140
  result += 'tré'
121
- } else {
141
+ }
142
+ else {
122
143
  result += ONES[ones]
123
144
  }
124
145
  }
@@ -129,8 +150,10 @@ function buildSegment (n) {
129
150
  /**
130
151
  * Builds segment word with "un" for scale context (millions, billions).
131
152
  * Same as buildSegment but returns "un" for 1 instead of "uno".
153
+ * @param {number} n - Number 0-999 to convert
154
+ * @returns {string} Segment word
132
155
  */
133
- function buildSegmentForScale (n) {
156
+ function buildSegmentForScale(n) {
134
157
  if (n === 0) return ''
135
158
  if (n === 1) return 'un' // "un milione" not "uno milione"
136
159
  return buildSegment(n)
@@ -139,8 +162,10 @@ function buildSegmentForScale (n) {
139
162
  /**
140
163
  * Builds thousands word for 1-999 thousand.
141
164
  * Handles elision: tre + mila = tremila (no accent), otto + mila = ottomila
165
+ * @param {number} n - Number of thousands 1-999
166
+ * @returns {string} Thousands word
142
167
  */
143
- function buildThousands (n) {
168
+ function buildThousands(n) {
144
169
  if (n === 0) return ''
145
170
  if (n === 1) return THOUSAND_SINGULAR // "mille"
146
171
 
@@ -157,8 +182,9 @@ function buildThousands (n) {
157
182
  /**
158
183
  * Gets singular scale word for index.
159
184
  * @param {number} scaleIndex - 2=million, 3=billion, etc.
185
+ * @returns {string} Singular scale word
160
186
  */
161
- function getScaleWordSingular (scaleIndex) {
187
+ function getScaleWordSingular(scaleIndex) {
162
188
  if (scaleIndex < 2) return ''
163
189
  const prefixIndex = Math.trunc((scaleIndex - 2) / 2)
164
190
  const isIardo = (scaleIndex - 2) % 2 === 1
@@ -170,8 +196,9 @@ function getScaleWordSingular (scaleIndex) {
170
196
  /**
171
197
  * Gets plural scale word for index.
172
198
  * @param {number} scaleIndex - 2=million, 3=billion, etc.
199
+ * @returns {string} Plural scale word
173
200
  */
174
- function getScaleWordPlural (scaleIndex) {
201
+ function getScaleWordPlural(scaleIndex) {
175
202
  if (scaleIndex < 2) return ''
176
203
  const prefixIndex = Math.trunc((scaleIndex - 2) / 2)
177
204
  const isIardo = (scaleIndex - 2) % 2 === 1
@@ -186,11 +213,10 @@ function getScaleWordPlural (scaleIndex) {
186
213
 
187
214
  /**
188
215
  * Converts a non-negative integer to Italian words.
189
- *
190
216
  * @param {bigint} n - Non-negative integer to convert
191
217
  * @returns {string} Italian words
192
218
  */
193
- function integerToWords (n) {
219
+ function integerToWords(n) {
194
220
  if (n === 0n) return ZERO
195
221
 
196
222
  // Fast path: numbers < 1000
@@ -217,11 +243,10 @@ function integerToWords (n) {
217
243
 
218
244
  /**
219
245
  * Builds words for numbers >= 1,000,000.
220
- *
221
246
  * @param {bigint} n - Number >= 1,000,000
222
247
  * @returns {string} Italian words
223
248
  */
224
- function buildLargeNumberWords (n) {
249
+ function buildLargeNumberWords(n) {
225
250
  const parts = []
226
251
  let remaining = n
227
252
 
@@ -250,10 +275,12 @@ function buildLargeNumberWords (n) {
250
275
  ? getScaleWordSingular(scaleIndex)
251
276
  : getScaleWordPlural(scaleIndex)
252
277
  parts.push(segmentWords + ' ' + scaleWord)
253
- } else if (scaleIndex === 1) {
278
+ }
279
+ else if (scaleIndex === 1) {
254
280
  // Thousands
255
281
  parts.push(buildThousands(segNum))
256
- } else {
282
+ }
283
+ else {
257
284
  // Units (scaleIndex === 0): just the segment
258
285
  parts.push(buildSegment(segNum))
259
286
  }
@@ -265,11 +292,10 @@ function buildLargeNumberWords (n) {
265
292
  /**
266
293
  * Joins parts with Italian connector rules.
267
294
  * Uses "e" before simple (non-compound) final segment.
268
- *
269
295
  * @param {string[]} parts - Parts to join
270
296
  * @returns {string} Joined string
271
297
  */
272
- function joinPartsWithConnector (parts) {
298
+ function joinPartsWithConnector(parts) {
273
299
  const len = parts.length
274
300
  if (len === 0) return ''
275
301
  if (len === 1) return parts[0]
@@ -295,11 +321,10 @@ function joinPartsWithConnector (parts) {
295
321
 
296
322
  /**
297
323
  * Converts decimal digits to Italian words.
298
- *
299
324
  * @param {string} decimalPart - Decimal digits (without the point)
300
325
  * @returns {string} Italian words for decimal part
301
326
  */
302
- function decimalPartToWords (decimalPart) {
327
+ function decimalPartToWords(decimalPart) {
303
328
  let result = ''
304
329
 
305
330
  // Handle leading zeros
@@ -325,12 +350,10 @@ function decimalPartToWords (decimalPart) {
325
350
  *
326
351
  * This is the main public API. It accepts any valid numeric input
327
352
  * (number, string, or bigint) and handles parsing internally.
328
- *
329
353
  * @param {number | string | bigint} value - The numeric value to convert
330
354
  * @returns {string} The number in Italian words
331
355
  * @throws {TypeError} If value is not a valid numeric type
332
356
  * @throws {Error} If value is not a valid number format
333
- *
334
357
  * @example
335
358
  * toCardinal(28) // 'ventotto'
336
359
  * toCardinal(23) // 'ventitré'
@@ -338,8 +361,11 @@ function decimalPartToWords (decimalPart) {
338
361
  * toCardinal(2000) // 'duemila'
339
362
  * toCardinal(1000000) // 'un milione'
340
363
  */
341
- function toCardinal (value) {
364
+ function toCardinal(value) {
342
365
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
366
+ // Both the integer part and the decimal's significant digits are spelled via
367
+ // the scale builder, so both must clear the ceiling.
368
+ checkMax(integerPart, cardinalMax, decimalPart)
343
369
 
344
370
  let result = ''
345
371
 
@@ -362,11 +388,10 @@ function toCardinal (value) {
362
388
 
363
389
  /**
364
390
  * Converts a cardinal word to ordinal form by dropping final vowel and adding -esimo.
365
- *
366
391
  * @param {string} cardinalWord - Cardinal word to convert
367
392
  * @returns {string} Ordinal form
368
393
  */
369
- function cardinalToOrdinal (cardinalWord) {
394
+ function cardinalToOrdinal(cardinalWord) {
370
395
  // Handle accented 'é' at end (tré → tre + esimo = treesimo)
371
396
  if (cardinalWord.endsWith('é')) {
372
397
  return cardinalWord.slice(0, -1) + 'e' + ORDINAL_SUFFIX
@@ -401,11 +426,10 @@ function cardinalToOrdinal (cardinalWord) {
401
426
 
402
427
  /**
403
428
  * Converts a positive integer to Italian ordinal words.
404
- *
405
429
  * @param {bigint} n - Positive integer
406
430
  * @returns {string} Italian ordinal words (masculine form)
407
431
  */
408
- function integerToOrdinal (n) {
432
+ function integerToOrdinal(n) {
409
433
  // Special cases: 1-10 have irregular forms
410
434
  if (n <= 10n) {
411
435
  return ORDINAL_ONES[Number(n)]
@@ -421,12 +445,10 @@ function integerToOrdinal (n) {
421
445
  *
422
446
  * Italian ordinals: primo, secondo, terzo... (1-10 irregular)
423
447
  * For 11+: cardinal word (drop final vowel) + -esimo
424
- *
425
448
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
426
449
  * @returns {string} The number as ordinal words (masculine form)
427
450
  * @throws {TypeError} If value is not a valid numeric type
428
451
  * @throws {RangeError} If value is negative, zero, or has a decimal part
429
- *
430
452
  * @example
431
453
  * toOrdinal(1) // 'primo'
432
454
  * toOrdinal(2) // 'secondo'
@@ -435,8 +457,10 @@ function integerToOrdinal (n) {
435
457
  * toOrdinal(100) // 'centesimo'
436
458
  * toOrdinal(1000) // 'millesimo'
437
459
  */
438
- function toOrdinal (value) {
460
+ function toOrdinal(value) {
439
461
  const integerPart = parseOrdinalValue(value)
462
+ // Ordinals are derived from the cardinal speller, so they share its ceiling.
463
+ checkMax(integerPart, ordinalMax)
440
464
  return integerToOrdinal(integerPart)
441
465
  }
442
466
 
@@ -444,16 +468,21 @@ function toOrdinal (value) {
444
468
  // CURRENCY: toCurrency(value, options?)
445
469
  // ============================================================================
446
470
 
471
+ /**
472
+ * @typedef {object} CurrencyOptions
473
+ * @property {boolean} [and] - Use "e" between euros and centesimi
474
+ */
475
+
476
+ /** @type {Required<CurrencyOptions>} */
477
+ export const currencyDefaults = { and: true }
478
+
447
479
  /**
448
480
  * Converts a numeric value to Italian currency words (Euro).
449
- *
450
481
  * @param {number | string | bigint} value - The currency amount to convert
451
- * @param {Object} [options] - Optional configuration
452
- * @param {boolean} [options.and=true] - Use "e" between euros and centesimi
482
+ * @param {CurrencyOptions} [options] - Optional configuration
453
483
  * @returns {string} The amount in Italian currency words
454
484
  * @throws {TypeError} If value is not a valid numeric type
455
485
  * @throws {Error} If value is not a valid number format
456
- *
457
486
  * @example
458
487
  * toCurrency(42.50) // 'quarantadue euro e cinquanta centesimi'
459
488
  * toCurrency(1) // 'un euro'
@@ -461,10 +490,10 @@ function toOrdinal (value) {
461
490
  * toCurrency(0.01) // 'un centesimo'
462
491
  * toCurrency(42.50, { and: false }) // 'quarantadue euro cinquanta centesimi'
463
492
  */
464
- function toCurrency (value, options) {
465
- options = validateOptions(options)
493
+ function toCurrency(value, options) {
466
494
  const { isNegative, dollars: euros, cents: centesimi } = parseCurrencyValue(value)
467
- const { and: useAnd = true } = options
495
+ checkMax(euros, currencyMax)
496
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
468
497
 
469
498
  // Build result
470
499
  let result = ''
@@ -475,7 +504,8 @@ function toCurrency (value, options) {
475
504
  // Use "un" for 1 euro instead of "uno"
476
505
  if (euros === 1n) {
477
506
  result += 'un'
478
- } else {
507
+ }
508
+ else {
479
509
  result += integerToWords(euros)
480
510
  }
481
511
  // Euro is invariable (doesn't change for plural in Italian)
@@ -490,7 +520,8 @@ function toCurrency (value, options) {
490
520
  // Use "un" for 1 centesimo instead of "uno"
491
521
  if (centesimi === 1n) {
492
522
  result += 'un'
493
- } else {
523
+ }
524
+ else {
494
525
  result += integerToWords(centesimi)
495
526
  }
496
527
  result += ' ' + (centesimi === 1n ? CENTESIMO : CENTESIMI)
package/src/ja-JP.d.ts CHANGED
@@ -1,14 +1,15 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Japanese words.
3
6
  *
4
7
  * This is the main public API. It accepts any valid numeric input
5
8
  * (number, string, or bigint) and handles parsing internally.
6
- *
7
9
  * @param {number | string | bigint} value - The numeric value to convert
8
10
  * @returns {string} The number in Japanese kanji words
9
11
  * @throws {TypeError} If value is not a valid numeric type
10
12
  * @throws {Error} If value is not a valid number format
11
- *
12
13
  * @example
13
14
  * toCardinal(42) // '四十二'
14
15
  * toCardinal(10000) // '一万'
@@ -17,12 +18,10 @@
17
18
  export function toCardinal(value: number | string | bigint): string;
18
19
  /**
19
20
  * Converts a numeric value to Japanese ordinal words.
20
- *
21
21
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
22
22
  * @returns {string} The number as ordinal words
23
23
  * @throws {TypeError} If value is not a valid numeric type
24
24
  * @throws {RangeError} If value is negative, zero, or has a decimal part
25
- *
26
25
  * @example
27
26
  * toOrdinal(1) // '第一'
28
27
  * toOrdinal(10) // '第十'
@@ -34,12 +33,10 @@ export function toOrdinal(value: number | string | bigint): string;
34
33
  *
35
34
  * Note: Sen (銭, 1/100 yen) is included for completeness but is rarely used
36
35
  * in modern Japan. Most transactions are in whole yen.
37
- *
38
36
  * @param {number | string | bigint} value - The currency amount to convert
39
37
  * @returns {string} The amount in Japanese currency words
40
38
  * @throws {TypeError} If value is not a valid numeric type
41
39
  * @throws {Error} If value is not a valid number format
42
- *
43
40
  * @example
44
41
  * toCurrency(42) // '四十二円'
45
42
  * toCurrency(1) // '一円'