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-US.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, western } 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,14 @@ const HUNDREDS_FEM = ['', 'cienta', 'doscientas', 'trescientas', 'cuatrocientas'
|
|
|
45
47
|
const SCALES = ['mil', 'millón', 'billón', 'trillón', 'cuatrillón', 'quintillón']
|
|
46
48
|
const SCALES_PLURAL = ['mil', 'millones', 'billones', 'trillones', 'cuatrillones', 'quintillones']
|
|
47
49
|
|
|
50
|
+
// Supported magnitude ceilings (checked at the public entry points). Short
|
|
51
|
+
// scale: SCALES covers 10^3..10^18, plus the units group, so cardinals span at
|
|
52
|
+
// most SCALES.length + 1 groups of 3 digits (below 10^21). Ordinals are bounded
|
|
53
|
+
// lower: the millions multiplier uses buildOrdinalSegment (0-999), so n < 10^9.
|
|
54
|
+
export const cardinalMax = western(SCALES.length)
|
|
55
|
+
export const ordinalMax = bounded(9)
|
|
56
|
+
export const currencyMax = western(SCALES.length)
|
|
57
|
+
|
|
48
58
|
const ZERO = 'cero'
|
|
49
59
|
const NEGATIVE = 'menos'
|
|
50
60
|
const DECIMAL_SEP = 'punto'
|
|
@@ -78,7 +88,7 @@ const CURRENCY_CONNECTOR = 'con'
|
|
|
78
88
|
* @param {boolean} feminine - Use feminine forms
|
|
79
89
|
* @returns {string} Spanish word
|
|
80
90
|
*/
|
|
81
|
-
function buildSegment
|
|
91
|
+
function buildSegment(n, feminine) {
|
|
82
92
|
if (n === 0) return ''
|
|
83
93
|
|
|
84
94
|
// Special case: exact 100 is "cien" (no gender)
|
|
@@ -100,22 +110,27 @@ function buildSegment (n, feminine) {
|
|
|
100
110
|
// Tens and ones
|
|
101
111
|
if (tensOnes === 0) {
|
|
102
112
|
// Just hundreds
|
|
103
|
-
}
|
|
113
|
+
}
|
|
114
|
+
else if (tensOnes < 10) {
|
|
104
115
|
// Single digit
|
|
105
116
|
const onesArr = feminine ? ONES_FEM : ONES_MASC
|
|
106
117
|
parts.push(onesArr[tensOnes])
|
|
107
|
-
}
|
|
118
|
+
}
|
|
119
|
+
else if (tensOnes < 20) {
|
|
108
120
|
// 10-19: teens
|
|
109
121
|
parts.push(TEENS[ones])
|
|
110
|
-
}
|
|
122
|
+
}
|
|
123
|
+
else if (tensOnes < 30) {
|
|
111
124
|
// 20-29: special twenties
|
|
112
125
|
const twentiesArr = feminine ? TWENTIES_FEM : TWENTIES_MASC
|
|
113
126
|
parts.push(twentiesArr[ones])
|
|
114
|
-
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
115
129
|
// 30-99: tens y ones
|
|
116
130
|
if (ones === 0) {
|
|
117
131
|
parts.push(TENS[tens])
|
|
118
|
-
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
119
134
|
const onesArr = feminine ? ONES_FEM : ONES_MASC
|
|
120
135
|
parts.push(TENS[tens] + ' y ' + onesArr[ones])
|
|
121
136
|
}
|
|
@@ -130,12 +145,11 @@ function buildSegment (n, feminine) {
|
|
|
130
145
|
|
|
131
146
|
/**
|
|
132
147
|
* Converts a non-negative integer to Spanish words (short scale).
|
|
133
|
-
*
|
|
134
148
|
* @param {bigint} n - Non-negative integer to convert
|
|
135
149
|
* @param {boolean} feminine - Use feminine forms
|
|
136
150
|
* @returns {string} Spanish words
|
|
137
151
|
*/
|
|
138
|
-
function integerToWords
|
|
152
|
+
function integerToWords(n, feminine) {
|
|
139
153
|
if (n === 0n) return ZERO
|
|
140
154
|
|
|
141
155
|
// Fast path: numbers < 1000
|
|
@@ -164,23 +178,25 @@ function integerToWords (n, feminine) {
|
|
|
164
178
|
if (i === 0) {
|
|
165
179
|
// Units segment - use requested gender
|
|
166
180
|
result += buildSegment(Number(segment), feminine)
|
|
167
|
-
}
|
|
181
|
+
}
|
|
182
|
+
else if (i === 1) {
|
|
168
183
|
// Thousands: "mil" not "uno mil"
|
|
169
184
|
if (segment === 1n) {
|
|
170
185
|
result += SCALES[0]
|
|
171
|
-
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
172
188
|
result += buildSegment(Number(segment), false) + ' ' + SCALES[0]
|
|
173
189
|
}
|
|
174
|
-
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
175
192
|
// Millions and above: "un millón", "dos millones", etc.
|
|
193
|
+
// Callers guard the magnitude (cardinalMax) so scaleIndex stays in range.
|
|
176
194
|
const scaleIndex = i - 1 // SCALES[1] = millón, SCALES[2] = billón, etc.
|
|
177
|
-
if (
|
|
178
|
-
// Beyond our scale vocabulary
|
|
179
|
-
result += buildSegment(Number(segment), false)
|
|
180
|
-
} else if (segment === 1n) {
|
|
195
|
+
if (segment === 1n) {
|
|
181
196
|
// "un millón" not "uno millón"
|
|
182
197
|
result += 'un ' + SCALES[scaleIndex]
|
|
183
|
-
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
184
200
|
result += buildSegment(Number(segment), false) + ' ' + SCALES_PLURAL[scaleIndex]
|
|
185
201
|
}
|
|
186
202
|
}
|
|
@@ -191,12 +207,11 @@ function integerToWords (n, feminine) {
|
|
|
191
207
|
|
|
192
208
|
/**
|
|
193
209
|
* Converts decimal digits to Spanish words.
|
|
194
|
-
*
|
|
195
210
|
* @param {string} decimalPart - Decimal digits (without the point)
|
|
196
211
|
* @param {boolean} feminine - Use feminine forms
|
|
197
212
|
* @returns {string} Spanish words for decimal part
|
|
198
213
|
*/
|
|
199
|
-
function decimalPartToWords
|
|
214
|
+
function decimalPartToWords(decimalPart, feminine) {
|
|
200
215
|
let result = ''
|
|
201
216
|
|
|
202
217
|
// Handle leading zeros
|
|
@@ -217,27 +232,37 @@ function decimalPartToWords (decimalPart, feminine) {
|
|
|
217
232
|
return result
|
|
218
233
|
}
|
|
219
234
|
|
|
235
|
+
/**
|
|
236
|
+
* @typedef {object} CardinalOptions
|
|
237
|
+
* @property {('masculine'|'feminine')} [gender] - Grammatical gender
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
/** @type {Required<CardinalOptions>} */
|
|
241
|
+
export const cardinalDefaults = { gender: 'masculine' }
|
|
242
|
+
|
|
243
|
+
/** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
|
|
244
|
+
export const cardinalValues = { gender: ['masculine', 'feminine'] }
|
|
245
|
+
|
|
220
246
|
/**
|
|
221
247
|
* Converts a numeric value to Spanish words (US short scale).
|
|
222
|
-
*
|
|
223
248
|
* @param {number | string | bigint} value - The numeric value to convert
|
|
224
|
-
* @param {
|
|
225
|
-
* @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
|
|
249
|
+
* @param {CardinalOptions} [options] - Optional configuration
|
|
226
250
|
* @returns {string} The number in Spanish words
|
|
227
251
|
* @throws {TypeError} If value is not a valid numeric type
|
|
228
252
|
* @throws {Error} If value is not a valid number format
|
|
229
|
-
*
|
|
230
253
|
* @example
|
|
231
254
|
* toCardinal(21) // 'veintiuno'
|
|
232
255
|
* toCardinal(21, {gender: 'feminine'}) // 'veintiuna'
|
|
233
256
|
* toCardinal(1000000000) // 'un billón'
|
|
234
257
|
*/
|
|
235
|
-
function toCardinal
|
|
236
|
-
options = validateOptions(options)
|
|
258
|
+
function toCardinal(value, options) {
|
|
237
259
|
const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
|
|
260
|
+
// Both the integer part and the decimal's significant digits are spelled via
|
|
261
|
+
// the scale builder, so both must clear the ceiling.
|
|
262
|
+
checkMax(integerPart, cardinalMax, decimalPart)
|
|
238
263
|
|
|
239
264
|
// Apply option defaults
|
|
240
|
-
const { gender
|
|
265
|
+
const { gender } = resolveOptions(options, cardinalDefaults, cardinalValues)
|
|
241
266
|
const feminine = gender === 'feminine'
|
|
242
267
|
|
|
243
268
|
let result = ''
|
|
@@ -261,12 +286,11 @@ function toCardinal (value, options) {
|
|
|
261
286
|
|
|
262
287
|
/**
|
|
263
288
|
* Builds ordinal word for a 0-999 segment.
|
|
264
|
-
*
|
|
265
289
|
* @param {number} n - Segment value 0-999
|
|
266
290
|
* @param {boolean} feminine - Use feminine forms
|
|
267
291
|
* @returns {string} Spanish ordinal word
|
|
268
292
|
*/
|
|
269
|
-
function buildOrdinalSegment
|
|
293
|
+
function buildOrdinalSegment(n, feminine) {
|
|
270
294
|
if (n === 0) return ''
|
|
271
295
|
|
|
272
296
|
const ones = n % 10
|
|
@@ -283,7 +307,8 @@ function buildOrdinalSegment (n, feminine) {
|
|
|
283
307
|
if (hundreds > 0) {
|
|
284
308
|
if (hundreds === 1) {
|
|
285
309
|
parts.push(hundredWord)
|
|
286
|
-
}
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
287
312
|
const prefixes = ['', '', 'du', 'tri', 'cuadri', 'quin', 'sex', 'septi', 'octi', 'noni']
|
|
288
313
|
parts.push(prefixes[hundreds] + hundredWord)
|
|
289
314
|
}
|
|
@@ -304,12 +329,11 @@ function buildOrdinalSegment (n, feminine) {
|
|
|
304
329
|
|
|
305
330
|
/**
|
|
306
331
|
* Converts a positive integer to Spanish ordinal words.
|
|
307
|
-
*
|
|
308
332
|
* @param {bigint} n - Positive integer to convert
|
|
309
333
|
* @param {boolean} feminine - Use feminine forms
|
|
310
334
|
* @returns {string} Spanish ordinal words
|
|
311
335
|
*/
|
|
312
|
-
function integerToOrdinal
|
|
336
|
+
function integerToOrdinal(n, feminine) {
|
|
313
337
|
const thousandWord = feminine ? ORDINAL_THOUSAND_FEM : ORDINAL_THOUSAND_MASC
|
|
314
338
|
const millionWord = feminine ? ORDINAL_MILLION_FEM : ORDINAL_MILLION_MASC
|
|
315
339
|
|
|
@@ -323,11 +347,12 @@ function integerToOrdinal (n, feminine) {
|
|
|
323
347
|
const thousands = Number(n / 1000n)
|
|
324
348
|
const remainder = Number(n % 1000n)
|
|
325
349
|
|
|
326
|
-
let result
|
|
350
|
+
let result
|
|
327
351
|
|
|
328
352
|
if (thousands === 1) {
|
|
329
353
|
result = thousandWord
|
|
330
|
-
}
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
331
356
|
result = buildOrdinalSegment(thousands, feminine) + ' ' + thousandWord
|
|
332
357
|
}
|
|
333
358
|
|
|
@@ -342,11 +367,12 @@ function integerToOrdinal (n, feminine) {
|
|
|
342
367
|
const millions = Number(n / 1_000_000n)
|
|
343
368
|
const remainder = n % 1_000_000n
|
|
344
369
|
|
|
345
|
-
let result
|
|
370
|
+
let result
|
|
346
371
|
|
|
347
372
|
if (millions === 1) {
|
|
348
373
|
result = millionWord
|
|
349
|
-
}
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
350
376
|
result = buildOrdinalSegment(millions, feminine) + ' ' + millionWord
|
|
351
377
|
}
|
|
352
378
|
|
|
@@ -357,26 +383,34 @@ function integerToOrdinal (n, feminine) {
|
|
|
357
383
|
return result
|
|
358
384
|
}
|
|
359
385
|
|
|
386
|
+
/**
|
|
387
|
+
* @typedef {object} OrdinalOptions
|
|
388
|
+
* @property {('masculine'|'feminine')} [gender] - Grammatical gender
|
|
389
|
+
*/
|
|
390
|
+
|
|
391
|
+
/** @type {Required<OrdinalOptions>} */
|
|
392
|
+
export const ordinalDefaults = { gender: 'masculine' }
|
|
393
|
+
|
|
394
|
+
/** @type {{ gender: ReadonlyArray<Required<OrdinalOptions>['gender']> }} */
|
|
395
|
+
export const ordinalValues = { gender: ['masculine', 'feminine'] }
|
|
396
|
+
|
|
360
397
|
/**
|
|
361
398
|
* Converts a numeric value to Spanish ordinal words.
|
|
362
|
-
*
|
|
363
399
|
* @param {number | string | bigint} value - The positive integer to convert
|
|
364
|
-
* @param {
|
|
365
|
-
* @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
|
|
400
|
+
* @param {OrdinalOptions} [options] - Optional configuration
|
|
366
401
|
* @returns {string} The number in Spanish ordinal words
|
|
367
402
|
* @throws {TypeError} If value is not a valid numeric type
|
|
368
403
|
* @throws {Error} If value is not a positive integer
|
|
369
|
-
*
|
|
370
404
|
* @example
|
|
371
405
|
* toOrdinal(1) // 'primero'
|
|
372
406
|
* toOrdinal(1, { gender: 'feminine' }) // 'primera'
|
|
373
407
|
* toOrdinal(21) // 'vigésimo primero'
|
|
374
408
|
*/
|
|
375
|
-
function toOrdinal
|
|
376
|
-
options = validateOptions(options)
|
|
409
|
+
function toOrdinal(value, options) {
|
|
377
410
|
const integerPart = parseOrdinalValue(value)
|
|
411
|
+
checkMax(integerPart, ordinalMax)
|
|
378
412
|
|
|
379
|
-
const { gender
|
|
413
|
+
const { gender } = resolveOptions(options, ordinalDefaults, ordinalValues)
|
|
380
414
|
const feminine = gender === 'feminine'
|
|
381
415
|
|
|
382
416
|
return integerToOrdinal(integerPart, feminine)
|
|
@@ -386,29 +420,34 @@ function toOrdinal (value, options) {
|
|
|
386
420
|
// CURRENCY: toCurrency(value, options?)
|
|
387
421
|
// ============================================================================
|
|
388
422
|
|
|
423
|
+
/**
|
|
424
|
+
* @typedef {object} CurrencyOptions
|
|
425
|
+
* @property {boolean} [and] - Use "con" between dollars and cents
|
|
426
|
+
*/
|
|
427
|
+
|
|
428
|
+
/** @type {Required<CurrencyOptions>} */
|
|
429
|
+
export const currencyDefaults = { and: true }
|
|
430
|
+
|
|
389
431
|
/**
|
|
390
432
|
* Converts a numeric value to US Dollar currency words in Spanish.
|
|
391
433
|
*
|
|
392
434
|
* US Dollar uses masculine gender for dólares (el dólar)
|
|
393
435
|
* and masculine for centavos (el centavo).
|
|
394
|
-
*
|
|
395
436
|
* @param {number | string | bigint} value - The currency amount to convert
|
|
396
|
-
* @param {
|
|
397
|
-
* @param {boolean} [options.and=true] - Use "con" between dollars and cents
|
|
437
|
+
* @param {CurrencyOptions} [options] - Optional configuration
|
|
398
438
|
* @returns {string} The amount in Spanish US Dollar currency words
|
|
399
439
|
* @throws {TypeError} If value is not a valid numeric type
|
|
400
440
|
* @throws {Error} If value is not a valid number format
|
|
401
|
-
*
|
|
402
441
|
* @example
|
|
403
442
|
* toCurrency(42.50) // 'cuarenta y dos dólares con cincuenta centavos'
|
|
404
443
|
* toCurrency(1) // 'un dólar'
|
|
405
444
|
* toCurrency(0.99) // 'noventa y nueve centavos'
|
|
406
445
|
* toCurrency(42.50, { and: false }) // 'cuarenta y dos dólares cincuenta centavos'
|
|
407
446
|
*/
|
|
408
|
-
function toCurrency
|
|
409
|
-
options = validateOptions(options)
|
|
447
|
+
function toCurrency(value, options) {
|
|
410
448
|
const { isNegative, dollars, cents: centavos } = parseCurrencyValue(value)
|
|
411
|
-
|
|
449
|
+
checkMax(dollars, currencyMax)
|
|
450
|
+
const { and: useAnd } = resolveOptions(options, currencyDefaults)
|
|
412
451
|
|
|
413
452
|
let result = ''
|
|
414
453
|
if (isNegative) result = NEGATIVE + ' '
|
|
@@ -418,7 +457,8 @@ function toCurrency (value, options) {
|
|
|
418
457
|
// Use masculine for dollars, but "un dólar" not "uno dólar"
|
|
419
458
|
if (dollars === 1n) {
|
|
420
459
|
result += 'un ' + DOLAR
|
|
421
|
-
}
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
422
462
|
result += integerToWords(dollars, false) + ' ' + DOLARES
|
|
423
463
|
}
|
|
424
464
|
}
|
|
@@ -431,7 +471,8 @@ function toCurrency (value, options) {
|
|
|
431
471
|
// Use masculine for centavos, but "un centavo" not "uno centavo"
|
|
432
472
|
if (centavos === 1n) {
|
|
433
473
|
result += 'un ' + CENTAVO
|
|
434
|
-
}
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
435
476
|
result += integerToWords(centavos, false) + ' ' + CENTAVOS
|
|
436
477
|
}
|
|
437
478
|
}
|
package/src/fa-IR.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
+
export const cardinalMax: null;
|
|
2
|
+
export const ordinalMax: null;
|
|
3
|
+
export const currencyMax: null;
|
|
1
4
|
/**
|
|
2
5
|
* Converts a numeric value to Persian words.
|
|
3
|
-
*
|
|
4
6
|
* @param {number | string | bigint} value - The numeric value to convert
|
|
5
7
|
* @returns {string} The number in Persian words
|
|
6
8
|
*/
|
|
7
9
|
export function toCardinal(value: number | string | bigint): string;
|
|
8
10
|
/**
|
|
9
11
|
* Converts a numeric value to Persian ordinal words.
|
|
10
|
-
*
|
|
11
12
|
* @param {number | string | bigint} value - The numeric value to convert (positive integer)
|
|
12
13
|
* @returns {string} The number as ordinal words
|
|
13
14
|
* @throws {TypeError} If value is not a valid numeric type
|
|
14
15
|
* @throws {RangeError} If value is negative, zero, or has a decimal part
|
|
15
|
-
*
|
|
16
16
|
* @example
|
|
17
17
|
* toOrdinal(1) // 'اول'
|
|
18
18
|
* toOrdinal(2) // 'دوم'
|
|
@@ -24,12 +24,10 @@ export function toOrdinal(value: number | string | bigint): string;
|
|
|
24
24
|
*
|
|
25
25
|
* Iranian Rial has no subunit in modern usage.
|
|
26
26
|
* (Historically dinar was 1/100 rial, but not used today)
|
|
27
|
-
*
|
|
28
27
|
* @param {number | string | bigint} value - The currency amount to convert
|
|
29
28
|
* @returns {string} The amount in Persian currency words
|
|
30
29
|
* @throws {TypeError} If value is not a valid numeric type
|
|
31
30
|
* @throws {Error} If value is not a valid number format
|
|
32
|
-
*
|
|
33
31
|
* @example
|
|
34
32
|
* toCurrency(42) // 'چهل و دو ریال'
|
|
35
33
|
* toCurrency(1000) // 'هزار ریال'
|
package/src/fa-IR.js
CHANGED
|
@@ -12,14 +12,24 @@
|
|
|
12
12
|
import { parseCardinalValue } from './utils/parse-cardinal.js'
|
|
13
13
|
import { parseCurrencyValue } from './utils/parse-currency.js'
|
|
14
14
|
import { parseOrdinalValue } from './utils/parse-ordinal.js'
|
|
15
|
+
import { UNBOUNDED } from './utils/scale.js'
|
|
16
|
+
|
|
17
|
+
// No fixed scale ceiling — the speller composes every magnitude.
|
|
18
|
+
export const cardinalMax = UNBOUNDED
|
|
19
|
+
export const ordinalMax = UNBOUNDED
|
|
20
|
+
export const currencyMax = UNBOUNDED
|
|
15
21
|
|
|
16
22
|
// ============================================================================
|
|
17
23
|
// Vocabulary
|
|
18
24
|
// ============================================================================
|
|
19
25
|
|
|
26
|
+
/** @type {Record<number, string>} */
|
|
20
27
|
const ONES = { 1: 'یک', 2: 'دو', 3: 'سه', 4: 'چهار', 5: 'پنج', 6: 'شش', 7: 'هفت', 8: 'هشت', 9: 'نه' }
|
|
28
|
+
/** @type {Record<number, string>} */
|
|
21
29
|
const TEENS = { 10: 'ده', 11: 'یازده', 12: 'دوازده', 13: 'سیزده', 14: 'چهارده', 15: 'پانزده', 16: 'شانزده', 17: 'هفده', 18: 'هجده', 19: 'نوزده' }
|
|
30
|
+
/** @type {Record<number, string>} */
|
|
22
31
|
const TENS = { 20: 'بیست', 30: 'سی', 40: 'چهل', 50: 'پنجاه', 60: 'شصت', 70: 'هفتاد', 80: 'هشتاد', 90: 'نود' }
|
|
32
|
+
/** @type {Record<number, string>} */
|
|
23
33
|
const HUNDREDS = { 100: 'صد', 200: 'دویست', 300: 'سيصد', 400: 'چهار صد', 500: 'پانصد', 600: 'ششصد', 700: 'هفتصد', 800: 'هشتصد', 900: 'نهصد' }
|
|
24
34
|
|
|
25
35
|
const THOUSAND = 'هزار'
|
|
@@ -37,6 +47,7 @@ const DECIMAL_SEP = 'ممیّز'
|
|
|
37
47
|
// Persian ordinals: add -ُم (-om) suffix to cardinal
|
|
38
48
|
// Special forms for 1st, 2nd, 3rd
|
|
39
49
|
const ORDINAL_SUFFIX = 'م' // ـُم (-om)
|
|
50
|
+
/** @type {Record<number, string>} */
|
|
40
51
|
const ORDINAL_ONES = {
|
|
41
52
|
1: 'اول', // avval (first)
|
|
42
53
|
2: 'دوم', // dovvom (second)
|
|
@@ -46,7 +57,7 @@ const ORDINAL_ONES = {
|
|
|
46
57
|
6: 'ششم',
|
|
47
58
|
7: 'هفتم',
|
|
48
59
|
8: 'هشتم',
|
|
49
|
-
9: 'نهم'
|
|
60
|
+
9: 'نهم',
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
// ============================================================================
|
|
@@ -59,7 +70,12 @@ const RIAL = 'ریال'
|
|
|
59
70
|
// Conversion Functions
|
|
60
71
|
// ============================================================================
|
|
61
72
|
|
|
62
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Converts a non-negative integer to Persian cardinal words.
|
|
75
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
76
|
+
* @returns {string} Persian cardinal words
|
|
77
|
+
*/
|
|
78
|
+
function integerToWords(n) {
|
|
63
79
|
if (n === 0n) return ZERO
|
|
64
80
|
|
|
65
81
|
// 1-9
|
|
@@ -121,7 +137,12 @@ function integerToWords (n) {
|
|
|
121
137
|
return `${milliardPrefix}${suffix}`
|
|
122
138
|
}
|
|
123
139
|
|
|
124
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Converts the fractional digits of a number to Persian words.
|
|
142
|
+
* @param {string} decimalPart - The fractional digits as a string
|
|
143
|
+
* @returns {string} Persian words for the decimal portion
|
|
144
|
+
*/
|
|
145
|
+
function decimalPartToWords(decimalPart) {
|
|
125
146
|
let result = ''
|
|
126
147
|
let i = 0
|
|
127
148
|
|
|
@@ -142,11 +163,10 @@ function decimalPartToWords (decimalPart) {
|
|
|
142
163
|
|
|
143
164
|
/**
|
|
144
165
|
* Converts a numeric value to Persian words.
|
|
145
|
-
*
|
|
146
166
|
* @param {number | string | bigint} value - The numeric value to convert
|
|
147
167
|
* @returns {string} The number in Persian words
|
|
148
168
|
*/
|
|
149
|
-
function toCardinal
|
|
169
|
+
function toCardinal(value) {
|
|
150
170
|
const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
|
|
151
171
|
|
|
152
172
|
let result = ''
|
|
@@ -172,11 +192,10 @@ function toCardinal (value) {
|
|
|
172
192
|
* Converts a non-negative integer to Persian ordinal words.
|
|
173
193
|
*
|
|
174
194
|
* Persian ordinals: اول (1st), دوم (2nd), سوم (3rd), then cardinal + م suffix.
|
|
175
|
-
*
|
|
176
195
|
* @param {bigint} n - Positive integer to convert
|
|
177
196
|
* @returns {string} Persian ordinal words
|
|
178
197
|
*/
|
|
179
|
-
function integerToOrdinal
|
|
198
|
+
function integerToOrdinal(n) {
|
|
180
199
|
// Special forms for 1-9
|
|
181
200
|
if (n >= 1n && n <= 9n) {
|
|
182
201
|
return ORDINAL_ONES[Number(n)]
|
|
@@ -189,18 +208,16 @@ function integerToOrdinal (n) {
|
|
|
189
208
|
|
|
190
209
|
/**
|
|
191
210
|
* Converts a numeric value to Persian ordinal words.
|
|
192
|
-
*
|
|
193
211
|
* @param {number | string | bigint} value - The numeric value to convert (positive integer)
|
|
194
212
|
* @returns {string} The number as ordinal words
|
|
195
213
|
* @throws {TypeError} If value is not a valid numeric type
|
|
196
214
|
* @throws {RangeError} If value is negative, zero, or has a decimal part
|
|
197
|
-
*
|
|
198
215
|
* @example
|
|
199
216
|
* toOrdinal(1) // 'اول'
|
|
200
217
|
* toOrdinal(2) // 'دوم'
|
|
201
218
|
* toOrdinal(10) // 'دهم'
|
|
202
219
|
*/
|
|
203
|
-
function toOrdinal
|
|
220
|
+
function toOrdinal(value) {
|
|
204
221
|
const integerPart = parseOrdinalValue(value)
|
|
205
222
|
return integerToOrdinal(integerPart)
|
|
206
223
|
}
|
|
@@ -214,18 +231,16 @@ function toOrdinal (value) {
|
|
|
214
231
|
*
|
|
215
232
|
* Iranian Rial has no subunit in modern usage.
|
|
216
233
|
* (Historically dinar was 1/100 rial, but not used today)
|
|
217
|
-
*
|
|
218
234
|
* @param {number | string | bigint} value - The currency amount to convert
|
|
219
235
|
* @returns {string} The amount in Persian currency words
|
|
220
236
|
* @throws {TypeError} If value is not a valid numeric type
|
|
221
237
|
* @throws {Error} If value is not a valid number format
|
|
222
|
-
*
|
|
223
238
|
* @example
|
|
224
239
|
* toCurrency(42) // 'چهل و دو ریال'
|
|
225
240
|
* toCurrency(1000) // 'هزار ریال'
|
|
226
241
|
* toCurrency(-5) // 'منفى پنج ریال'
|
|
227
242
|
*/
|
|
228
|
-
function toCurrency
|
|
243
|
+
function toCurrency(value) {
|
|
229
244
|
const { isNegative, dollars: rial } = parseCurrencyValue(value)
|
|
230
245
|
|
|
231
246
|
let result = ''
|
package/src/fi-FI.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
export const cardinalMax: bigint;
|
|
2
|
+
export const ordinalMax: bigint;
|
|
3
|
+
export const currencyMax: bigint;
|
|
1
4
|
/**
|
|
2
5
|
* Converts a numeric value to Finnish words.
|
|
3
|
-
*
|
|
4
6
|
* @param {number | string | bigint} value - The numeric value to convert
|
|
5
7
|
* @returns {string} The number in Finnish words
|
|
6
8
|
* @throws {TypeError} If value is not a valid numeric type
|
|
7
9
|
* @throws {Error} If value is not a valid number format
|
|
8
|
-
*
|
|
9
10
|
* @example
|
|
10
11
|
* toCardinal(21) // 'kaksikymmentäyksi'
|
|
11
12
|
* toCardinal(1000) // 'tuhat'
|
|
@@ -14,12 +15,10 @@
|
|
|
14
15
|
export function toCardinal(value: number | string | bigint): string;
|
|
15
16
|
/**
|
|
16
17
|
* Converts a numeric value to Finnish ordinal words.
|
|
17
|
-
*
|
|
18
18
|
* @param {number | string | bigint} value - The numeric value to convert (positive integer)
|
|
19
19
|
* @returns {string} The number as ordinal words
|
|
20
20
|
* @throws {TypeError} If value is not a valid numeric type
|
|
21
21
|
* @throws {RangeError} If value is negative, zero, or has a decimal part
|
|
22
|
-
*
|
|
23
22
|
* @example
|
|
24
23
|
* toOrdinal(1) // 'ensimmäinen'
|
|
25
24
|
* toOrdinal(2) // 'toinen'
|
|
@@ -32,12 +31,10 @@ export function toOrdinal(value: number | string | bigint): string;
|
|
|
32
31
|
*
|
|
33
32
|
* Euro uses sentti as subunit (100 senttiä = 1 euro).
|
|
34
33
|
* Finnish has singular/plural: 1 euro vs 2 euroa, 1 sentti vs 2 senttiä.
|
|
35
|
-
*
|
|
36
34
|
* @param {number | string | bigint} value - The currency amount to convert
|
|
37
35
|
* @returns {string} The amount in Finnish currency words
|
|
38
36
|
* @throws {TypeError} If value is not a valid numeric type
|
|
39
37
|
* @throws {Error} If value is not a valid number format
|
|
40
|
-
*
|
|
41
38
|
* @example
|
|
42
39
|
* toCurrency(1) // 'yksi euro'
|
|
43
40
|
* toCurrency(42) // 'neljäkymmentäkaksi euroa'
|