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/es-ES.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
  const ZERO = 'cero'
50
61
  const NEGATIVE = 'menos'
@@ -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,30 +318,40 @@ 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.
301
334
  *
302
335
  * This is the main public API. It accepts any valid numeric input
303
336
  * (number, string, or bigint) and handles parsing internally.
304
- *
305
337
  * @param {number | string | bigint} value - The numeric value to convert
306
- * @param {Object} [options] - Optional configuration
307
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
338
+ * @param {CardinalOptions} [options] - Optional configuration
308
339
  * @returns {string} The number in Spanish words
309
340
  * @throws {TypeError} If value is not a valid numeric type
310
341
  * @throws {Error} If value is not a valid number format
311
- *
312
342
  * @example
313
343
  * toCardinal(21) // 'veintiuno'
314
344
  * toCardinal(21, {gender: 'feminine'}) // 'veintiuna'
315
345
  * toCardinal(1000000) // 'un millón'
316
346
  */
317
- function toCardinal (value, options) {
318
- options = validateOptions(options)
347
+ function toCardinal(value, options) {
319
348
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
349
+ // Both the integer part and the decimal's significant digits are spelled via
350
+ // the scale builder, so both must clear the ceiling.
351
+ checkMax(integerPart, cardinalMax, decimalPart)
320
352
 
321
353
  // Apply option defaults
322
- const { gender = 'masculine' } = options
354
+ const { gender } = resolveOptions(options, cardinalDefaults, cardinalValues)
323
355
  const feminine = gender === 'feminine'
324
356
 
325
357
  let result = ''
@@ -349,12 +381,11 @@ function toCardinal (value, options) {
349
381
  * - 21st = "vigésimo primero"
350
382
  * - 100th = "centésimo"
351
383
  * - 101st = "centésimo primero"
352
- *
353
384
  * @param {number} n - Segment value 0-999
354
385
  * @param {boolean} feminine - Use feminine forms
355
386
  * @returns {string} Spanish ordinal word
356
387
  */
357
- function buildOrdinalSegment (n, feminine) {
388
+ function buildOrdinalSegment(n, feminine) {
358
389
  if (n === 0) return ''
359
390
 
360
391
  const ones = n % 10
@@ -371,7 +402,8 @@ function buildOrdinalSegment (n, feminine) {
371
402
  if (hundreds > 0) {
372
403
  if (hundreds === 1) {
373
404
  parts.push(hundredWord)
374
- } else {
405
+ }
406
+ else {
375
407
  // 200th = ducentésimo, 300th = tricentésimo, etc.
376
408
  // Use cardinal prefix + centésimo
377
409
  const prefixes = ['', '', 'du', 'tri', 'cuadri', 'quin', 'sex', 'septi', 'octi', 'noni']
@@ -394,12 +426,11 @@ function buildOrdinalSegment (n, feminine) {
394
426
 
395
427
  /**
396
428
  * Converts a positive integer to Spanish ordinal words.
397
- *
398
429
  * @param {bigint} n - Positive integer to convert
399
430
  * @param {boolean} feminine - Use feminine forms
400
431
  * @returns {string} Spanish ordinal words
401
432
  */
402
- function integerToOrdinal (n, feminine) {
433
+ function integerToOrdinal(n, feminine) {
403
434
  const thousandWord = feminine ? ORDINAL_THOUSAND_FEM : ORDINAL_THOUSAND_MASC
404
435
  const millionWord = feminine ? ORDINAL_MILLION_FEM : ORDINAL_MILLION_MASC
405
436
 
@@ -413,11 +444,12 @@ function integerToOrdinal (n, feminine) {
413
444
  const thousands = Number(n / 1000n)
414
445
  const remainder = Number(n % 1000n)
415
446
 
416
- let result = ''
447
+ let result
417
448
 
418
449
  if (thousands === 1) {
419
450
  result = thousandWord
420
- } else {
451
+ }
452
+ else {
421
453
  // Use ordinal for thousands multiplier
422
454
  result = buildOrdinalSegment(thousands, feminine) + ' ' + thousandWord
423
455
  }
@@ -433,11 +465,12 @@ function integerToOrdinal (n, feminine) {
433
465
  const millions = Number(n / 1_000_000n)
434
466
  const remainder = n % 1_000_000n
435
467
 
436
- let result = ''
468
+ let result
437
469
 
438
470
  if (millions === 1) {
439
471
  result = millionWord
440
- } else {
472
+ }
473
+ else {
441
474
  result = buildOrdinalSegment(millions, feminine) + ' ' + millionWord
442
475
  }
443
476
 
@@ -448,30 +481,38 @@ function integerToOrdinal (n, feminine) {
448
481
  return result
449
482
  }
450
483
 
484
+ /**
485
+ * @typedef {object} OrdinalOptions
486
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
487
+ */
488
+
489
+ /** @type {Required<OrdinalOptions>} */
490
+ export const ordinalDefaults = { gender: 'masculine' }
491
+
492
+ /** @type {{ gender: ReadonlyArray<Required<OrdinalOptions>['gender']> }} */
493
+ export const ordinalValues = { gender: ['masculine', 'feminine'] }
494
+
451
495
  /**
452
496
  * Converts a numeric value to Spanish ordinal words.
453
497
  *
454
498
  * Spanish ordinals agree in gender with the noun they modify.
455
499
  * Only positive integers are valid for ordinals.
456
- *
457
500
  * @param {number | string | bigint} value - The positive integer to convert
458
- * @param {Object} [options] - Optional configuration
459
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
501
+ * @param {OrdinalOptions} [options] - Optional configuration
460
502
  * @returns {string} The number in Spanish ordinal words
461
503
  * @throws {TypeError} If value is not a valid numeric type
462
504
  * @throws {Error} If value is not a positive integer
463
- *
464
505
  * @example
465
506
  * toOrdinal(1) // 'primero'
466
507
  * toOrdinal(1, { gender: 'feminine' }) // 'primera'
467
508
  * toOrdinal(21) // 'vigésimo primero'
468
509
  * toOrdinal(100) // 'centésimo'
469
510
  */
470
- function toOrdinal (value, options) {
471
- options = validateOptions(options)
511
+ function toOrdinal(value, options) {
472
512
  const integerPart = parseOrdinalValue(value)
513
+ checkMax(integerPart, ordinalMax)
473
514
 
474
- const { gender = 'masculine' } = options
515
+ const { gender } = resolveOptions(options, ordinalDefaults, ordinalValues)
475
516
  const feminine = gender === 'feminine'
476
517
 
477
518
  return integerToOrdinal(integerPart, feminine)
@@ -481,29 +522,34 @@ function toOrdinal (value, options) {
481
522
  // CURRENCY: toCurrency(value, options?)
482
523
  // ============================================================================
483
524
 
525
+ /**
526
+ * @typedef {object} CurrencyOptions
527
+ * @property {boolean} [and] - Use "con" between euros and cents
528
+ */
529
+
530
+ /** @type {Required<CurrencyOptions>} */
531
+ export const currencyDefaults = { and: true }
532
+
484
533
  /**
485
534
  * Converts a numeric value to Spanish Euro currency words.
486
535
  *
487
536
  * Spanish currency uses masculine gender for euros (el euro)
488
537
  * and masculine for céntimos (el céntimo).
489
- *
490
538
  * @param {number | string | bigint} value - The currency amount to convert
491
- * @param {Object} [options] - Optional configuration
492
- * @param {boolean} [options.and=true] - Use "con" between euros and cents
539
+ * @param {CurrencyOptions} [options] - Optional configuration
493
540
  * @returns {string} The amount in Spanish currency words
494
541
  * @throws {TypeError} If value is not a valid numeric type
495
542
  * @throws {Error} If value is not a valid number format
496
- *
497
543
  * @example
498
544
  * toCurrency(42.50) // 'cuarenta y dos euros con cincuenta céntimos'
499
545
  * toCurrency(1) // 'un euro'
500
546
  * toCurrency(0.99) // 'noventa y nueve céntimos'
501
547
  * toCurrency(42.50, { and: false }) // 'cuarenta y dos euros cincuenta céntimos'
502
548
  */
503
- function toCurrency (value, options) {
504
- options = validateOptions(options)
549
+ function toCurrency(value, options) {
505
550
  const { isNegative, dollars: euros, cents: centimos } = parseCurrencyValue(value)
506
- const { and: useAnd = true } = options
551
+ checkMax(euros, currencyMax)
552
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
507
553
 
508
554
  let result = ''
509
555
  if (isNegative) result = NEGATIVE + ' '
@@ -513,7 +559,8 @@ function toCurrency (value, options) {
513
559
  // Use masculine for euros, but "un euro" not "uno euro"
514
560
  if (euros === 1n) {
515
561
  result += 'un ' + EURO
516
- } else {
562
+ }
563
+ else {
517
564
  result += integerToWords(euros, false) + ' ' + EUROS
518
565
  }
519
566
  }
@@ -526,7 +573,8 @@ function toCurrency (value, options) {
526
573
  // Use masculine for centimos, but "un céntimo" not "uno céntimo"
527
574
  if (centimos === 1n) {
528
575
  result += 'un ' + CENTIMO
529
- } else {
576
+ }
577
+ else {
530
578
  result += integerToWords(centimos, false) + ' ' + CENTIMOS
531
579
  }
532
580
  }
package/src/es-MX.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 pesos and centavos
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 pesos and centavos
45
+ */
46
+ and?: boolean | undefined;
47
+ };
1
48
  /**
2
49
  * Converts a numeric value to Spanish words (long 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) // 'mil millones'
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 Mexican Peso currency words.
39
76
  *
40
77
  * Mexican currency uses masculine gender for pesos (el peso)
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 pesos and centavos
80
+ * @param {CurrencyOptions} [options] - Optional configuration
46
81
  * @returns {string} The amount in Mexican 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 pesos con cincuenta centavos'
52
86
  * toCurrency(1) // 'un peso'
53
87
  * toCurrency(0.99) // 'noventa y nueve centavos'
54
88
  * toCurrency(42.50, { and: false }) // 'cuarenta y dos pesos 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;