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.
- package/CHANGELOG.md +128 -42
- package/README.md +6 -4
- package/dist/am-ET.js +2 -2
- package/dist/am-ET.umd.js +2 -2
- package/dist/am-Latn-ET.js +2 -2
- package/dist/am-Latn-ET.umd.js +2 -2
- package/dist/ar-SA.js +2 -2
- package/dist/ar-SA.umd.js +2 -2
- package/dist/az-AZ.js +2 -2
- package/dist/az-AZ.umd.js +2 -2
- package/dist/bn-BD.js +2 -2
- package/dist/bn-BD.umd.js +2 -2
- package/dist/cs-CZ.js +2 -2
- package/dist/cs-CZ.umd.js +2 -2
- package/dist/da-DK.js +2 -2
- package/dist/da-DK.umd.js +2 -2
- package/dist/de-DE.js +2 -2
- package/dist/de-DE.umd.js +2 -2
- package/dist/el-GR.js +2 -2
- package/dist/el-GR.umd.js +2 -2
- package/dist/en-AU.js +2 -2
- package/dist/en-AU.umd.js +2 -2
- package/dist/en-BD.js +2 -2
- package/dist/en-BD.umd.js +2 -2
- package/dist/en-CA.js +2 -2
- package/dist/en-CA.umd.js +2 -2
- package/dist/en-GB.js +2 -2
- package/dist/en-GB.umd.js +2 -2
- package/dist/en-GH.js +2 -2
- package/dist/en-GH.umd.js +2 -2
- package/dist/en-IE.js +2 -2
- package/dist/en-IE.umd.js +2 -2
- package/dist/en-IN.js +2 -2
- package/dist/en-IN.umd.js +2 -2
- package/dist/en-KE.js +2 -2
- package/dist/en-KE.umd.js +2 -2
- package/dist/en-MY.js +2 -2
- package/dist/en-MY.umd.js +2 -2
- package/dist/en-NG.js +2 -2
- package/dist/en-NG.umd.js +2 -2
- package/dist/en-NZ.js +2 -2
- package/dist/en-NZ.umd.js +2 -2
- package/dist/en-PH.js +2 -2
- package/dist/en-PH.umd.js +2 -2
- package/dist/en-PK.js +2 -2
- package/dist/en-PK.umd.js +2 -2
- package/dist/en-SG.js +2 -2
- package/dist/en-SG.umd.js +2 -2
- package/dist/en-US.js +2 -2
- package/dist/en-US.umd.js +2 -2
- package/dist/en-ZA.js +2 -2
- package/dist/en-ZA.umd.js +2 -2
- package/dist/es-ES.js +2 -2
- package/dist/es-ES.umd.js +2 -2
- package/dist/es-MX.js +2 -2
- package/dist/es-MX.umd.js +2 -2
- package/dist/es-US.js +2 -2
- package/dist/es-US.umd.js +2 -2
- package/dist/fa-IR.js +2 -2
- package/dist/fa-IR.umd.js +2 -2
- package/dist/fi-FI.js +2 -2
- package/dist/fi-FI.umd.js +2 -2
- package/dist/fil-PH.js +2 -2
- package/dist/fil-PH.umd.js +2 -2
- package/dist/fr-BE.js +2 -2
- package/dist/fr-BE.umd.js +2 -2
- package/dist/fr-FR.js +2 -2
- package/dist/fr-FR.umd.js +2 -2
- package/dist/gu-IN.js +2 -2
- package/dist/gu-IN.umd.js +2 -2
- package/dist/ha-NG.js +2 -2
- package/dist/ha-NG.umd.js +2 -2
- package/dist/hbo-IL.js +2 -2
- package/dist/hbo-IL.umd.js +2 -2
- package/dist/he-IL.js +2 -2
- package/dist/he-IL.umd.js +2 -2
- package/dist/hi-IN.js +2 -2
- package/dist/hi-IN.umd.js +2 -2
- package/dist/hr-HR.js +2 -2
- package/dist/hr-HR.umd.js +2 -2
- package/dist/hu-HU.js +2 -2
- package/dist/hu-HU.umd.js +2 -2
- package/dist/id-ID.js +2 -2
- package/dist/id-ID.umd.js +2 -2
- package/dist/it-IT.js +2 -2
- package/dist/it-IT.umd.js +2 -2
- package/dist/ja-JP.js +2 -2
- package/dist/ja-JP.umd.js +2 -2
- package/dist/ka-GE.js +2 -2
- package/dist/ka-GE.umd.js +2 -2
- package/dist/kn-IN.js +2 -2
- package/dist/kn-IN.umd.js +2 -2
- package/dist/ko-KR.js +2 -2
- package/dist/ko-KR.umd.js +2 -2
- package/dist/lt-LT.js +2 -2
- package/dist/lt-LT.umd.js +2 -2
- package/dist/lv-LV.js +2 -2
- package/dist/lv-LV.umd.js +2 -2
- package/dist/mr-IN.js +2 -2
- package/dist/mr-IN.umd.js +2 -2
- package/dist/ms-MY.js +2 -2
- package/dist/ms-MY.umd.js +2 -2
- package/dist/nb-NO.js +2 -2
- package/dist/nb-NO.umd.js +2 -2
- package/dist/nl-NL.js +2 -2
- package/dist/nl-NL.umd.js +2 -2
- package/dist/pa-IN.js +2 -2
- package/dist/pa-IN.umd.js +2 -2
- package/dist/pl-PL.js +2 -2
- package/dist/pl-PL.umd.js +2 -2
- package/dist/pt-BR.js +2 -2
- package/dist/pt-BR.umd.js +2 -2
- package/dist/pt-PT.js +2 -2
- package/dist/pt-PT.umd.js +2 -2
- package/dist/ro-RO.js +2 -2
- package/dist/ro-RO.umd.js +2 -2
- package/dist/ru-RU.js +2 -2
- package/dist/ru-RU.umd.js +2 -2
- package/dist/sr-Cyrl-RS.js +2 -2
- package/dist/sr-Cyrl-RS.umd.js +2 -2
- package/dist/sr-Latn-RS.js +2 -2
- package/dist/sr-Latn-RS.umd.js +2 -2
- package/dist/sv-SE.js +2 -2
- package/dist/sv-SE.umd.js +2 -2
- package/dist/sw-KE.js +2 -2
- package/dist/sw-KE.umd.js +2 -2
- package/dist/ta-IN.js +2 -2
- package/dist/ta-IN.umd.js +2 -2
- package/dist/te-IN.js +2 -2
- package/dist/te-IN.umd.js +2 -2
- package/dist/th-TH.js +2 -2
- package/dist/th-TH.umd.js +2 -2
- package/dist/tr-TR.js +2 -2
- package/dist/tr-TR.umd.js +2 -2
- package/dist/uk-UA.js +2 -2
- package/dist/uk-UA.umd.js +2 -2
- package/dist/ur-PK.js +2 -2
- package/dist/ur-PK.umd.js +2 -2
- package/dist/vi-VN.js +2 -2
- package/dist/vi-VN.umd.js +2 -2
- package/dist/yo-NG.js +2 -2
- package/dist/yo-NG.umd.js +2 -2
- package/dist/zh-Hans-CN.js +2 -2
- package/dist/zh-Hans-CN.umd.js +2 -2
- package/dist/zh-Hant-TW.js +2 -2
- package/dist/zh-Hant-TW.umd.js +2 -2
- package/package.json +31 -22
- package/src/am-ET.d.ts +3 -5
- package/src/am-ET.js +41 -16
- package/src/am-Latn-ET.d.ts +3 -5
- package/src/am-Latn-ET.js +45 -16
- package/src/ar-SA.d.ts +44 -18
- package/src/ar-SA.js +93 -40
- package/src/az-AZ.d.ts +3 -5
- package/src/az-AZ.js +58 -20
- package/src/bn-BD.d.ts +3 -5
- package/src/bn-BD.js +32 -16
- package/src/cs-CZ.d.ts +3 -6
- package/src/cs-CZ.js +66 -42
- package/src/da-DK.d.ts +3 -6
- package/src/da-DK.js +53 -48
- package/src/de-DE.d.ts +17 -11
- package/src/de-DE.js +88 -57
- package/src/el-GR.d.ts +3 -6
- package/src/el-GR.js +45 -32
- package/src/en-AU.d.ts +17 -11
- package/src/en-AU.js +56 -41
- package/src/en-BD.d.ts +17 -11
- package/src/en-BD.js +60 -41
- package/src/en-CA.d.ts +36 -18
- package/src/en-CA.js +67 -46
- package/src/en-GB.d.ts +17 -11
- package/src/en-GB.js +56 -41
- package/src/en-GH.d.ts +32 -3
- package/src/en-GH.js +104 -26
- package/src/en-IE.d.ts +17 -11
- package/src/en-IE.js +56 -41
- package/src/en-IN.d.ts +17 -11
- package/src/en-IN.js +60 -41
- package/src/en-KE.d.ts +28 -3
- package/src/en-KE.js +93 -26
- package/src/en-MY.d.ts +26 -3
- package/src/en-MY.js +91 -26
- package/src/en-NG.d.ts +17 -11
- package/src/en-NG.js +56 -41
- package/src/en-NZ.d.ts +32 -3
- package/src/en-NZ.js +85 -31
- package/src/en-PH.d.ts +32 -3
- package/src/en-PH.js +97 -26
- package/src/en-PK.d.ts +17 -11
- package/src/en-PK.js +60 -41
- package/src/en-SG.d.ts +28 -3
- package/src/en-SG.js +93 -26
- package/src/en-US.d.ts +36 -18
- package/src/en-US.js +70 -47
- package/src/en-ZA.d.ts +17 -11
- package/src/en-ZA.js +56 -41
- package/src/es-ES.d.ts +53 -21
- package/src/es-ES.js +104 -56
- package/src/es-MX.d.ts +53 -21
- package/src/es-MX.js +104 -56
- package/src/es-US.d.ts +53 -21
- package/src/es-US.js +92 -51
- package/src/fa-IR.d.ts +3 -5
- package/src/fa-IR.js +28 -13
- package/src/fi-FI.d.ts +3 -6
- package/src/fi-FI.js +47 -29
- package/src/fil-PH.d.ts +3 -5
- package/src/fil-PH.js +61 -28
- package/src/fr-BE.d.ts +31 -15
- package/src/fr-BE.js +128 -57
- package/src/fr-FR.d.ts +31 -16
- package/src/fr-FR.js +97 -60
- package/src/gu-IN.d.ts +3 -5
- package/src/gu-IN.js +31 -16
- package/src/ha-NG.d.ts +3 -5
- package/src/ha-NG.js +55 -27
- package/src/hbo-IL.d.ts +26 -12
- package/src/hbo-IL.js +92 -51
- package/src/he-IL.d.ts +17 -10
- package/src/he-IL.js +92 -50
- package/src/hi-IN.d.ts +3 -5
- package/src/hi-IN.js +30 -17
- package/src/hr-HR.d.ts +21 -10
- package/src/hr-HR.js +89 -33
- package/src/hu-HU.d.ts +3 -5
- package/src/hu-HU.js +57 -23
- package/src/id-ID.d.ts +3 -5
- package/src/id-ID.js +56 -23
- package/src/it-IT.d.ts +17 -11
- package/src/it-IT.js +74 -43
- package/src/ja-JP.d.ts +3 -6
- package/src/ja-JP.js +39 -26
- package/src/ka-GE.d.ts +3 -6
- package/src/ka-GE.js +38 -26
- package/src/kn-IN.d.ts +3 -5
- package/src/kn-IN.js +31 -16
- package/src/ko-KR.d.ts +3 -6
- package/src/ko-KR.js +34 -26
- package/src/lt-LT.d.ts +21 -11
- package/src/lt-LT.js +64 -42
- package/src/lv-LV.d.ts +21 -11
- package/src/lv-LV.js +79 -51
- package/src/mr-IN.d.ts +3 -5
- package/src/mr-IN.js +31 -16
- package/src/ms-MY.d.ts +3 -5
- package/src/ms-MY.js +58 -24
- package/src/nb-NO.d.ts +3 -6
- package/src/nb-NO.js +54 -34
- package/src/nl-NL.d.ts +41 -20
- package/src/nl-NL.js +111 -69
- package/src/pa-IN.d.ts +3 -5
- package/src/pa-IN.js +32 -16
- package/src/pl-PL.d.ts +21 -11
- package/src/pl-PL.js +69 -45
- package/src/pt-BR.d.ts +22 -11
- package/src/pt-BR.js +93 -53
- package/src/pt-PT.d.ts +17 -11
- package/src/pt-PT.js +80 -48
- package/src/ro-RO.d.ts +21 -11
- package/src/ro-RO.js +77 -39
- package/src/ru-RU.d.ts +35 -15
- package/src/ru-RU.js +100 -38
- package/src/sr-Cyrl-RS.d.ts +35 -15
- package/src/sr-Cyrl-RS.js +100 -38
- package/src/sr-Latn-RS.d.ts +35 -15
- package/src/sr-Latn-RS.js +100 -38
- package/src/sv-SE.d.ts +3 -6
- package/src/sv-SE.js +53 -34
- package/src/sw-KE.d.ts +3 -5
- package/src/sw-KE.js +50 -20
- package/src/ta-IN.d.ts +3 -5
- package/src/ta-IN.js +29 -17
- package/src/te-IN.d.ts +3 -5
- package/src/te-IN.js +31 -16
- package/src/th-TH.d.ts +3 -5
- package/src/th-TH.js +42 -19
- package/src/tr-TR.d.ts +17 -11
- package/src/tr-TR.js +63 -37
- package/src/uk-UA.d.ts +21 -10
- package/src/uk-UA.js +89 -33
- package/src/ur-PK.d.ts +3 -5
- package/src/ur-PK.js +32 -16
- package/src/utils/check-max.d.ts +26 -0
- package/src/utils/check-max.js +33 -0
- package/src/utils/expand-scientific.d.ts +0 -4
- package/src/utils/expand-scientific.js +7 -9
- package/src/utils/is-plain-object.d.ts +3 -4
- package/src/utils/is-plain-object.js +3 -4
- package/src/utils/parse-cardinal.d.ts +1 -2
- package/src/utils/parse-cardinal.js +12 -9
- package/src/utils/parse-currency.d.ts +1 -2
- package/src/utils/parse-currency.js +9 -11
- package/src/utils/parse-ordinal.d.ts +0 -1
- package/src/utils/parse-ordinal.js +9 -10
- package/src/utils/resolve-options.d.ts +17 -0
- package/src/utils/resolve-options.js +56 -0
- package/src/utils/scale.d.ts +49 -0
- package/src/utils/scale.js +65 -0
- package/src/vi-VN.d.ts +3 -6
- package/src/vi-VN.js +41 -28
- package/src/yo-NG.d.ts +3 -5
- package/src/yo-NG.js +49 -33
- package/src/zh-Hans-CN.d.ts +45 -20
- package/src/zh-Hans-CN.js +84 -31
- package/src/zh-Hant-TW.d.ts +45 -20
- package/src/zh-Hant-TW.js +85 -34
- package/src/utils/validate-options.d.ts +0 -8
- 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 {
|
|
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
|
|
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
|
-
}
|
|
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
|
-
}
|
|
121
|
+
}
|
|
122
|
+
else if (tensOnes < 20) {
|
|
110
123
|
// 10-19: teens
|
|
111
124
|
parts.push(TEENS[ones])
|
|
112
|
-
}
|
|
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
|
-
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
117
132
|
// 30-99: tens y ones
|
|
118
133
|
if (ones === 0) {
|
|
119
134
|
parts.push(TENS[tens])
|
|
120
|
-
}
|
|
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
|
|
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
|
-
}
|
|
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
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
|
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
|
-
}
|
|
258
|
+
}
|
|
259
|
+
else if (i === 1) {
|
|
242
260
|
// Thousands: omit "uno" before mil
|
|
243
261
|
if (segment === 1n) {
|
|
244
262
|
result += THOUSAND
|
|
245
|
-
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
246
265
|
result += buildSegment(Number(segment), false) + ' ' + scaleWord
|
|
247
266
|
}
|
|
248
|
-
}
|
|
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
|
-
}
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
254
275
|
result += buildSegment(Number(segment), false) + ' ' + scaleWord
|
|
255
276
|
}
|
|
256
|
-
}
|
|
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
|
-
}
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
-
}
|
|
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
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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 {
|
|
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
|
|
471
|
-
options = validateOptions(options)
|
|
511
|
+
function toOrdinal(value, options) {
|
|
472
512
|
const integerPart = parseOrdinalValue(value)
|
|
513
|
+
checkMax(integerPart, ordinalMax)
|
|
473
514
|
|
|
474
|
-
const { gender
|
|
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 {
|
|
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
|
|
504
|
-
options = validateOptions(options)
|
|
549
|
+
function toCurrency(value, options) {
|
|
505
550
|
const { isNegative, dollars: euros, cents: centimos } = parseCurrencyValue(value)
|
|
506
|
-
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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;
|