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/es-MX.js CHANGED
@@ -20,7 +20,9 @@
20
20
  import { parseCardinalValue } from './utils/parse-cardinal.js'
21
21
  import { parseCurrencyValue } from './utils/parse-currency.js'
22
22
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
23
- import { validateOptions } from './utils/validate-options.js'
23
+ import { checkMax } from './utils/check-max.js'
24
+ import { bounded, longScale } from './utils/scale.js'
25
+ import { resolveOptions } from './utils/resolve-options.js'
24
26
 
25
27
  // ============================================================================
26
28
  // Vocabulary (module-level constants)
@@ -45,6 +47,15 @@ const HUNDREDS_FEM = ['', 'cienta', 'doscientas', 'trescientas', 'cuatrocientas'
45
47
  const SCALES = ['millón', 'billón', 'trillón', 'cuatrillón']
46
48
  const SCALES_PLURAL = ['millones', 'billones', 'trillones', 'cuatrillones']
47
49
 
50
+ // Supported magnitude ceilings (checked at the public entry points). Each
51
+ // SCALES entry spans two segment groups (X and "mil X"); with the units group
52
+ // that's 2 * SCALES.length + 2 groups of 3 digits, so cardinals must stay below
53
+ // 10^30. Ordinals are bounded lower: the millions multiplier uses
54
+ // buildOrdinalSegment (0-999), so n must stay below 10^9.
55
+ export const cardinalMax = longScale(SCALES.length)
56
+ export const ordinalMax = bounded(9)
57
+ export const currencyMax = longScale(SCALES.length)
58
+
48
59
  const THOUSAND = 'mil'
49
60
 
50
61
  const ZERO = 'cero'
@@ -80,7 +91,7 @@ const CURRENCY_CONNECTOR = 'con'
80
91
  * @param {boolean} feminine - Use feminine forms
81
92
  * @returns {string} Spanish word
82
93
  */
83
- function buildSegment (n, feminine) {
94
+ function buildSegment(n, feminine) {
84
95
  if (n === 0) return ''
85
96
 
86
97
  // Special case: exact 100 is "cien" (no gender)
@@ -102,22 +113,27 @@ function buildSegment (n, feminine) {
102
113
  // Tens and ones
103
114
  if (tensOnes === 0) {
104
115
  // Just hundreds
105
- } else if (tensOnes < 10) {
116
+ }
117
+ else if (tensOnes < 10) {
106
118
  // Single digit
107
119
  const onesArr = feminine ? ONES_FEM : ONES_MASC
108
120
  parts.push(onesArr[tensOnes])
109
- } else if (tensOnes < 20) {
121
+ }
122
+ else if (tensOnes < 20) {
110
123
  // 10-19: teens
111
124
  parts.push(TEENS[ones])
112
- } else if (tensOnes < 30) {
125
+ }
126
+ else if (tensOnes < 30) {
113
127
  // 20-29: special twenties
114
128
  const twentiesArr = feminine ? TWENTIES_FEM : TWENTIES_MASC
115
129
  parts.push(twentiesArr[ones])
116
- } else {
130
+ }
131
+ else {
117
132
  // 30-99: tens y ones
118
133
  if (ones === 0) {
119
134
  parts.push(TENS[tens])
120
- } else {
135
+ }
136
+ else {
121
137
  const onesArr = feminine ? ONES_FEM : ONES_MASC
122
138
  parts.push(TENS[tens] + ' y ' + onesArr[ones])
123
139
  }
@@ -132,12 +148,11 @@ function buildSegment (n, feminine) {
132
148
 
133
149
  /**
134
150
  * Gets scale word for Spanish compound long scale.
135
- *
136
151
  * @param {number} scaleIndex - Scale level (1 = thousand, 2 = million, etc.)
137
152
  * @param {bigint} segment - Segment value for pluralization
138
153
  * @returns {string} Scale word
139
154
  */
140
- function getScaleWord (scaleIndex, segment) {
155
+ function getScaleWord(scaleIndex, segment) {
141
156
  if (scaleIndex === 1) return THOUSAND
142
157
 
143
158
  // Even indices (2, 4, 6, 8): millón, billón, trillón, cuatrillón
@@ -147,7 +162,8 @@ function getScaleWord (scaleIndex, segment) {
147
162
  const baseWord = SCALES[arrayIndex]
148
163
  if (!baseWord) return ''
149
164
  return segment > 1n ? SCALES_PLURAL[arrayIndex] : baseWord
150
- } else {
165
+ }
166
+ else {
151
167
  // Compound: "mil millones" pattern
152
168
  const arrayIndex = ((scaleIndex - 1) / 2) - 1
153
169
  const pluralWord = SCALES_PLURAL[arrayIndex]
@@ -162,12 +178,11 @@ function getScaleWord (scaleIndex, segment) {
162
178
 
163
179
  /**
164
180
  * Converts a non-negative integer to Spanish words.
165
- *
166
181
  * @param {bigint} n - Non-negative integer to convert
167
182
  * @param {boolean} feminine - Use feminine forms
168
183
  * @returns {string} Spanish words
169
184
  */
170
- function integerToWords (n, feminine) {
185
+ function integerToWords(n, feminine) {
171
186
  if (n === 0n) return ZERO
172
187
 
173
188
  // Fast path: numbers < 1000
@@ -184,13 +199,15 @@ function integerToWords (n, feminine) {
184
199
  if (thousands === 1) {
185
200
  // "mil" not "uno mil"
186
201
  result = THOUSAND
187
- } else {
202
+ }
203
+ else {
188
204
  // Use masculine for thousands segment, but check for "uno" → omit before mil
189
205
  const thousandsWord = buildSegment(thousands, false)
190
206
  // "uno mil" → "mil" (handled in joinSegments equivalent)
191
207
  if (thousandsWord === 'uno' || thousandsWord === 'una') {
192
208
  result = THOUSAND
193
- } else {
209
+ }
210
+ else {
194
211
  result = thousandsWord + ' ' + THOUSAND
195
212
  }
196
213
  }
@@ -209,14 +226,14 @@ function integerToWords (n, feminine) {
209
226
  /**
210
227
  * Builds words for numbers >= 1,000,000.
211
228
  * Uses BigInt division for faster segment extraction.
212
- *
213
229
  * @param {bigint} n - Number >= 1,000,000
214
230
  * @param {boolean} feminine - Use feminine forms
215
231
  * @returns {string} Spanish words
216
232
  */
217
- function buildLargeNumberWords (n, feminine) {
233
+ function buildLargeNumberWords(n, feminine) {
218
234
  // Extract segments using BigInt division (faster than string slicing)
219
235
  // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
236
+ // Callers guard the magnitude (cardinalMax) before reaching here.
220
237
  const segmentValues = []
221
238
  let temp = n
222
239
  while (temp > 0n) {
@@ -238,27 +255,33 @@ function buildLargeNumberWords (n, feminine) {
238
255
  if (i === 0) {
239
256
  // Units segment
240
257
  result += buildSegment(Number(segment), feminine)
241
- } else if (i === 1) {
258
+ }
259
+ else if (i === 1) {
242
260
  // Thousands: omit "uno" before mil
243
261
  if (segment === 1n) {
244
262
  result += THOUSAND
245
- } else {
263
+ }
264
+ else {
246
265
  result += buildSegment(Number(segment), false) + ' ' + scaleWord
247
266
  }
248
- } else if (i % 2 === 1) {
267
+ }
268
+ else if (i % 2 === 1) {
249
269
  // Odd scale indices (3, 5, 7): "mil millones", "mil billones", etc.
250
270
  // Omit "uno" before these compound scales
251
271
  if (segment === 1n) {
252
272
  result += scaleWord
253
- } else {
273
+ }
274
+ else {
254
275
  result += buildSegment(Number(segment), false) + ' ' + scaleWord
255
276
  }
256
- } else {
277
+ }
278
+ else {
257
279
  // Even scale indices (2, 4, 6): millón, billón, trillón
258
280
  if (segment === 1n) {
259
281
  // "un millón" not "uno millón"
260
282
  result += 'un ' + scaleWord
261
- } else {
283
+ }
284
+ else {
262
285
  // Use masculine for scale segment
263
286
  result += buildSegment(Number(segment), false) + ' ' + scaleWord
264
287
  }
@@ -270,12 +293,11 @@ function buildLargeNumberWords (n, feminine) {
270
293
 
271
294
  /**
272
295
  * Converts decimal digits to Spanish words.
273
- *
274
296
  * @param {string} decimalPart - Decimal digits (without the point)
275
297
  * @param {boolean} feminine - Use feminine forms
276
298
  * @returns {string} Spanish words for decimal part
277
299
  */
278
- function decimalPartToWords (decimalPart, feminine) {
300
+ function decimalPartToWords(decimalPart, feminine) {
279
301
  let result = ''
280
302
 
281
303
  // Handle leading zeros
@@ -296,27 +318,37 @@ function decimalPartToWords (decimalPart, feminine) {
296
318
  return result
297
319
  }
298
320
 
321
+ /**
322
+ * @typedef {object} CardinalOptions
323
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
324
+ */
325
+
326
+ /** @type {Required<CardinalOptions>} */
327
+ export const cardinalDefaults = { gender: 'masculine' }
328
+
329
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
330
+ export const cardinalValues = { gender: ['masculine', 'feminine'] }
331
+
299
332
  /**
300
333
  * Converts a numeric value to Spanish words (long scale).
301
- *
302
334
  * @param {number | string | bigint} value - The numeric value to convert
303
- * @param {Object} [options] - Optional configuration
304
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
335
+ * @param {CardinalOptions} [options] - Optional configuration
305
336
  * @returns {string} The number in Spanish words
306
337
  * @throws {TypeError} If value is not a valid numeric type
307
338
  * @throws {Error} If value is not a valid number format
308
- *
309
339
  * @example
310
340
  * toCardinal(21) // 'veintiuno'
311
341
  * toCardinal(21, {gender: 'feminine'}) // 'veintiuna'
312
342
  * toCardinal(1000000000) // 'mil millones'
313
343
  */
314
- function toCardinal (value, options) {
315
- options = validateOptions(options)
344
+ function toCardinal(value, options) {
316
345
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
346
+ // Both the integer part and the decimal's significant digits are spelled via
347
+ // the scale builder, so both must clear the ceiling.
348
+ checkMax(integerPart, cardinalMax, decimalPart)
317
349
 
318
350
  // Apply option defaults
319
- const { gender = 'masculine' } = options
351
+ const { gender } = resolveOptions(options, cardinalDefaults, cardinalValues)
320
352
  const feminine = gender === 'feminine'
321
353
 
322
354
  let result = ''
@@ -340,12 +372,11 @@ function toCardinal (value, options) {
340
372
 
341
373
  /**
342
374
  * Builds ordinal word for a 0-999 segment.
343
- *
344
375
  * @param {number} n - Segment value 0-999
345
376
  * @param {boolean} feminine - Use feminine forms
346
377
  * @returns {string} Spanish ordinal word
347
378
  */
348
- function buildOrdinalSegment (n, feminine) {
379
+ function buildOrdinalSegment(n, feminine) {
349
380
  if (n === 0) return ''
350
381
 
351
382
  const ones = n % 10
@@ -362,7 +393,8 @@ function buildOrdinalSegment (n, feminine) {
362
393
  if (hundreds > 0) {
363
394
  if (hundreds === 1) {
364
395
  parts.push(hundredWord)
365
- } else {
396
+ }
397
+ else {
366
398
  const prefixes = ['', '', 'du', 'tri', 'cuadri', 'quin', 'sex', 'septi', 'octi', 'noni']
367
399
  parts.push(prefixes[hundreds] + hundredWord)
368
400
  }
@@ -383,12 +415,11 @@ function buildOrdinalSegment (n, feminine) {
383
415
 
384
416
  /**
385
417
  * Converts a positive integer to Spanish ordinal words.
386
- *
387
418
  * @param {bigint} n - Positive integer to convert
388
419
  * @param {boolean} feminine - Use feminine forms
389
420
  * @returns {string} Spanish ordinal words
390
421
  */
391
- function integerToOrdinal (n, feminine) {
422
+ function integerToOrdinal(n, feminine) {
392
423
  const thousandWord = feminine ? ORDINAL_THOUSAND_FEM : ORDINAL_THOUSAND_MASC
393
424
  const millionWord = feminine ? ORDINAL_MILLION_FEM : ORDINAL_MILLION_MASC
394
425
 
@@ -402,11 +433,12 @@ function integerToOrdinal (n, feminine) {
402
433
  const thousands = Number(n / 1000n)
403
434
  const remainder = Number(n % 1000n)
404
435
 
405
- let result = ''
436
+ let result
406
437
 
407
438
  if (thousands === 1) {
408
439
  result = thousandWord
409
- } else {
440
+ }
441
+ else {
410
442
  result = buildOrdinalSegment(thousands, feminine) + ' ' + thousandWord
411
443
  }
412
444
 
@@ -421,11 +453,12 @@ function integerToOrdinal (n, feminine) {
421
453
  const millions = Number(n / 1_000_000n)
422
454
  const remainder = n % 1_000_000n
423
455
 
424
- let result = ''
456
+ let result
425
457
 
426
458
  if (millions === 1) {
427
459
  result = millionWord
428
- } else {
460
+ }
461
+ else {
429
462
  result = buildOrdinalSegment(millions, feminine) + ' ' + millionWord
430
463
  }
431
464
 
@@ -436,26 +469,34 @@ function integerToOrdinal (n, feminine) {
436
469
  return result
437
470
  }
438
471
 
472
+ /**
473
+ * @typedef {object} OrdinalOptions
474
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
475
+ */
476
+
477
+ /** @type {Required<OrdinalOptions>} */
478
+ export const ordinalDefaults = { gender: 'masculine' }
479
+
480
+ /** @type {{ gender: ReadonlyArray<Required<OrdinalOptions>['gender']> }} */
481
+ export const ordinalValues = { gender: ['masculine', 'feminine'] }
482
+
439
483
  /**
440
484
  * Converts a numeric value to Spanish ordinal words.
441
- *
442
485
  * @param {number | string | bigint} value - The positive integer to convert
443
- * @param {Object} [options] - Optional configuration
444
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
486
+ * @param {OrdinalOptions} [options] - Optional configuration
445
487
  * @returns {string} The number in Spanish ordinal words
446
488
  * @throws {TypeError} If value is not a valid numeric type
447
489
  * @throws {Error} If value is not a positive integer
448
- *
449
490
  * @example
450
491
  * toOrdinal(1) // 'primero'
451
492
  * toOrdinal(1, { gender: 'feminine' }) // 'primera'
452
493
  * toOrdinal(21) // 'vigésimo primero'
453
494
  */
454
- function toOrdinal (value, options) {
455
- options = validateOptions(options)
495
+ function toOrdinal(value, options) {
456
496
  const integerPart = parseOrdinalValue(value)
497
+ checkMax(integerPart, ordinalMax)
457
498
 
458
- const { gender = 'masculine' } = options
499
+ const { gender } = resolveOptions(options, ordinalDefaults, ordinalValues)
459
500
  const feminine = gender === 'feminine'
460
501
 
461
502
  return integerToOrdinal(integerPart, feminine)
@@ -465,29 +506,34 @@ function toOrdinal (value, options) {
465
506
  // CURRENCY: toCurrency(value, options?)
466
507
  // ============================================================================
467
508
 
509
+ /**
510
+ * @typedef {object} CurrencyOptions
511
+ * @property {boolean} [and] - Use "con" between pesos and centavos
512
+ */
513
+
514
+ /** @type {Required<CurrencyOptions>} */
515
+ export const currencyDefaults = { and: true }
516
+
468
517
  /**
469
518
  * Converts a numeric value to Mexican Peso currency words.
470
519
  *
471
520
  * Mexican currency uses masculine gender for pesos (el peso)
472
521
  * and masculine for centavos (el centavo).
473
- *
474
522
  * @param {number | string | bigint} value - The currency amount to convert
475
- * @param {Object} [options] - Optional configuration
476
- * @param {boolean} [options.and=true] - Use "con" between pesos and centavos
523
+ * @param {CurrencyOptions} [options] - Optional configuration
477
524
  * @returns {string} The amount in Mexican currency words
478
525
  * @throws {TypeError} If value is not a valid numeric type
479
526
  * @throws {Error} If value is not a valid number format
480
- *
481
527
  * @example
482
528
  * toCurrency(42.50) // 'cuarenta y dos pesos con cincuenta centavos'
483
529
  * toCurrency(1) // 'un peso'
484
530
  * toCurrency(0.99) // 'noventa y nueve centavos'
485
531
  * toCurrency(42.50, { and: false }) // 'cuarenta y dos pesos cincuenta centavos'
486
532
  */
487
- function toCurrency (value, options) {
488
- options = validateOptions(options)
533
+ function toCurrency(value, options) {
489
534
  const { isNegative, dollars: pesos, cents: centavos } = parseCurrencyValue(value)
490
- const { and: useAnd = true } = options
535
+ checkMax(pesos, currencyMax)
536
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
491
537
 
492
538
  let result = ''
493
539
  if (isNegative) result = NEGATIVE + ' '
@@ -497,7 +543,8 @@ function toCurrency (value, options) {
497
543
  // Use masculine for pesos, but "un peso" not "uno peso"
498
544
  if (pesos === 1n) {
499
545
  result += 'un ' + PESO
500
- } else {
546
+ }
547
+ else {
501
548
  result += integerToWords(pesos, false) + ' ' + PESOS
502
549
  }
503
550
  }
@@ -510,7 +557,8 @@ function toCurrency (value, options) {
510
557
  // Use masculine for centavos, but "un centavo" not "uno centavo"
511
558
  if (centavos === 1n) {
512
559
  result += 'un ' + CENTAVO
513
- } else {
560
+ }
561
+ else {
514
562
  result += integerToWords(centavos, false) + ' ' + CENTAVOS
515
563
  }
516
564
  }
package/src/es-US.d.ts CHANGED
@@ -1,58 +1,90 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CardinalOptions
6
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
7
+ */
8
+ /** @type {Required<CardinalOptions>} */
9
+ export const cardinalDefaults: Required<CardinalOptions>;
10
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
11
+ export const cardinalValues: {
12
+ gender: ReadonlyArray<Required<CardinalOptions>["gender"]>;
13
+ };
14
+ /**
15
+ * @typedef {object} OrdinalOptions
16
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
17
+ */
18
+ /** @type {Required<OrdinalOptions>} */
19
+ export const ordinalDefaults: Required<OrdinalOptions>;
20
+ /** @type {{ gender: ReadonlyArray<Required<OrdinalOptions>['gender']> }} */
21
+ export const ordinalValues: {
22
+ gender: ReadonlyArray<Required<OrdinalOptions>["gender"]>;
23
+ };
24
+ /**
25
+ * @typedef {object} CurrencyOptions
26
+ * @property {boolean} [and] - Use "con" between dollars and cents
27
+ */
28
+ /** @type {Required<CurrencyOptions>} */
29
+ export const currencyDefaults: Required<CurrencyOptions>;
30
+ export type CardinalOptions = {
31
+ /**
32
+ * - Grammatical gender
33
+ */
34
+ gender?: "feminine" | "masculine" | undefined;
35
+ };
36
+ export type OrdinalOptions = {
37
+ /**
38
+ * - Grammatical gender
39
+ */
40
+ gender?: "feminine" | "masculine" | undefined;
41
+ };
42
+ export type CurrencyOptions = {
43
+ /**
44
+ * - Use "con" between dollars and cents
45
+ */
46
+ and?: boolean | undefined;
47
+ };
1
48
  /**
2
49
  * Converts a numeric value to Spanish words (US short scale).
3
- *
4
50
  * @param {number | string | bigint} value - The numeric value to convert
5
- * @param {Object} [options] - Optional configuration
6
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
51
+ * @param {CardinalOptions} [options] - Optional configuration
7
52
  * @returns {string} The number in Spanish words
8
53
  * @throws {TypeError} If value is not a valid numeric type
9
54
  * @throws {Error} If value is not a valid number format
10
- *
11
55
  * @example
12
56
  * toCardinal(21) // 'veintiuno'
13
57
  * toCardinal(21, {gender: 'feminine'}) // 'veintiuna'
14
58
  * toCardinal(1000000000) // 'un billón'
15
59
  */
16
- export function toCardinal(value: number | string | bigint, options?: {
17
- gender?: "masculine" | "feminine" | undefined;
18
- }): string;
60
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
19
61
  /**
20
62
  * Converts a numeric value to Spanish ordinal words.
21
- *
22
63
  * @param {number | string | bigint} value - The positive integer to convert
23
- * @param {Object} [options] - Optional configuration
24
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
64
+ * @param {OrdinalOptions} [options] - Optional configuration
25
65
  * @returns {string} The number in Spanish ordinal words
26
66
  * @throws {TypeError} If value is not a valid numeric type
27
67
  * @throws {Error} If value is not a positive integer
28
- *
29
68
  * @example
30
69
  * toOrdinal(1) // 'primero'
31
70
  * toOrdinal(1, { gender: 'feminine' }) // 'primera'
32
71
  * toOrdinal(21) // 'vigésimo primero'
33
72
  */
34
- export function toOrdinal(value: number | string | bigint, options?: {
35
- gender?: "masculine" | "feminine" | undefined;
36
- }): string;
73
+ export function toOrdinal(value: number | string | bigint, options?: OrdinalOptions): string;
37
74
  /**
38
75
  * Converts a numeric value to US Dollar currency words in Spanish.
39
76
  *
40
77
  * US Dollar uses masculine gender for dólares (el dólar)
41
78
  * and masculine for centavos (el centavo).
42
- *
43
79
  * @param {number | string | bigint} value - The currency amount to convert
44
- * @param {Object} [options] - Optional configuration
45
- * @param {boolean} [options.and=true] - Use "con" between dollars and cents
80
+ * @param {CurrencyOptions} [options] - Optional configuration
46
81
  * @returns {string} The amount in Spanish US Dollar currency words
47
82
  * @throws {TypeError} If value is not a valid numeric type
48
83
  * @throws {Error} If value is not a valid number format
49
- *
50
84
  * @example
51
85
  * toCurrency(42.50) // 'cuarenta y dos dólares con cincuenta centavos'
52
86
  * toCurrency(1) // 'un dólar'
53
87
  * toCurrency(0.99) // 'noventa y nueve centavos'
54
88
  * toCurrency(42.50, { and: false }) // 'cuarenta y dos dólares cincuenta centavos'
55
89
  */
56
- export function toCurrency(value: number | string | bigint, options?: {
57
- and?: boolean | undefined;
58
- }): string;
90
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;