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/ru-RU.js
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
import { parseCardinalValue } from './utils/parse-cardinal.js'
|
|
14
14
|
import { parseCurrencyValue } from './utils/parse-currency.js'
|
|
15
15
|
import { parseOrdinalValue } from './utils/parse-ordinal.js'
|
|
16
|
-
import {
|
|
16
|
+
import { checkMax } from './utils/check-max.js'
|
|
17
|
+
import { western } from './utils/scale.js'
|
|
18
|
+
import { resolveOptions } from './utils/resolve-options.js'
|
|
17
19
|
|
|
18
20
|
// ============================================================================
|
|
19
21
|
// Vocabulary
|
|
@@ -44,7 +46,7 @@ const SCALE_FORMS = [
|
|
|
44
46
|
['секстиллион', 'секстиллиона', 'секстиллионов'],
|
|
45
47
|
['септиллион', 'септиллиона', 'септиллионов'],
|
|
46
48
|
['октиллион', 'октиллиона', 'октиллионов'],
|
|
47
|
-
['нониллион', 'нониллиона', 'нониллионов']
|
|
49
|
+
['нониллион', 'нониллиона', 'нониллионов'],
|
|
48
50
|
]
|
|
49
51
|
|
|
50
52
|
// ============================================================================
|
|
@@ -74,7 +76,7 @@ const ORDINAL_SCALES = [
|
|
|
74
76
|
'секстиллионный',
|
|
75
77
|
'септиллионный',
|
|
76
78
|
'октиллионный',
|
|
77
|
-
'нониллионный'
|
|
79
|
+
'нониллионный',
|
|
78
80
|
]
|
|
79
81
|
|
|
80
82
|
// Prefixes for compound ordinal thousands (двух-, трёх-, etc. + тысячный)
|
|
@@ -94,7 +96,13 @@ const KOPECK_FORMS = ['копейка', 'копейки', 'копеек']
|
|
|
94
96
|
// Segment Building
|
|
95
97
|
// ============================================================================
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Selects the correct plural form based on Russian pluralization rules.
|
|
101
|
+
* @param {number | bigint} n - The count
|
|
102
|
+
* @param {string[]} forms - [one, few, many] forms
|
|
103
|
+
* @returns {string} The matching plural form
|
|
104
|
+
*/
|
|
105
|
+
function pluralize(n, forms) {
|
|
98
106
|
const num = typeof n === 'bigint' ? Number(n) : n
|
|
99
107
|
const lastDigit = num % 10
|
|
100
108
|
const lastTwoDigits = num % 100
|
|
@@ -108,7 +116,12 @@ function pluralize (n, forms) {
|
|
|
108
116
|
return forms[2]
|
|
109
117
|
}
|
|
110
118
|
|
|
111
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Builds masculine cardinal words for a 0-999 segment.
|
|
121
|
+
* @param {number} n - Number 0-999
|
|
122
|
+
* @returns {string} Masculine cardinal words
|
|
123
|
+
*/
|
|
124
|
+
function buildSegmentMasc(n) {
|
|
112
125
|
if (n === 0) return ''
|
|
113
126
|
|
|
114
127
|
const onesDigit = n % 10
|
|
@@ -127,14 +140,20 @@ function buildSegmentMasc (n) {
|
|
|
127
140
|
|
|
128
141
|
if (tensDigit === 1) {
|
|
129
142
|
parts.push(TEENS[onesDigit])
|
|
130
|
-
}
|
|
143
|
+
}
|
|
144
|
+
else if (onesDigit > 0) {
|
|
131
145
|
parts.push(ONES_MASC[onesDigit])
|
|
132
146
|
}
|
|
133
147
|
|
|
134
148
|
return parts.join(' ')
|
|
135
149
|
}
|
|
136
150
|
|
|
137
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Builds feminine cardinal words for a 0-999 segment.
|
|
153
|
+
* @param {number} n - Number 0-999
|
|
154
|
+
* @returns {string} Feminine cardinal words
|
|
155
|
+
*/
|
|
156
|
+
function buildSegmentFem(n) {
|
|
138
157
|
if (n === 0) return ''
|
|
139
158
|
|
|
140
159
|
const onesDigit = n % 10
|
|
@@ -153,7 +172,8 @@ function buildSegmentFem (n) {
|
|
|
153
172
|
|
|
154
173
|
if (tensDigit === 1) {
|
|
155
174
|
parts.push(TEENS[onesDigit])
|
|
156
|
-
}
|
|
175
|
+
}
|
|
176
|
+
else if (onesDigit > 0) {
|
|
157
177
|
parts.push(ONES_FEM[onesDigit])
|
|
158
178
|
}
|
|
159
179
|
|
|
@@ -164,7 +184,13 @@ function buildSegmentFem (n) {
|
|
|
164
184
|
// Conversion Functions
|
|
165
185
|
// ============================================================================
|
|
166
186
|
|
|
167
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Converts a non-negative integer to Russian cardinal words.
|
|
189
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
190
|
+
* @param {('masculine'|'feminine')} gender - Grammatical gender
|
|
191
|
+
* @returns {string} Cardinal Russian words
|
|
192
|
+
*/
|
|
193
|
+
function integerToWords(n, gender) {
|
|
168
194
|
if (n === 0n) return ZERO
|
|
169
195
|
|
|
170
196
|
const feminine = gender === 'feminine'
|
|
@@ -193,7 +219,13 @@ function integerToWords (n, gender) {
|
|
|
193
219
|
return buildLargeNumberWords(n, gender)
|
|
194
220
|
}
|
|
195
221
|
|
|
196
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Builds cardinal words for numbers >= 1,000,000 via scale decomposition.
|
|
224
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
225
|
+
* @param {('masculine'|'feminine')} gender - Grammatical gender
|
|
226
|
+
* @returns {string} Cardinal Russian words
|
|
227
|
+
*/
|
|
228
|
+
function buildLargeNumberWords(n, gender) {
|
|
197
229
|
const feminine = gender === 'feminine'
|
|
198
230
|
const numStr = n.toString()
|
|
199
231
|
const len = numStr.length
|
|
@@ -221,7 +253,8 @@ function buildLargeNumberWords (n, gender) {
|
|
|
221
253
|
if (segment !== 0) {
|
|
222
254
|
if (scaleIndex === 0) {
|
|
223
255
|
parts.push(feminine ? buildSegmentFem(segment) : buildSegmentMasc(segment))
|
|
224
|
-
}
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
225
258
|
const scaleForms = SCALE_FORMS[scaleIndex - 1]
|
|
226
259
|
const scaleWord = pluralize(segment, scaleForms)
|
|
227
260
|
// Thousands (scaleIndex=1) are feminine, others masculine
|
|
@@ -237,7 +270,13 @@ function buildLargeNumberWords (n, gender) {
|
|
|
237
270
|
return parts.join(' ')
|
|
238
271
|
}
|
|
239
272
|
|
|
240
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Converts the fractional digit string to Russian words.
|
|
275
|
+
* @param {string} decimalPart - The fractional digits
|
|
276
|
+
* @param {('masculine'|'feminine')} gender - Grammatical gender
|
|
277
|
+
* @returns {string} Cardinal Russian words for the decimal part
|
|
278
|
+
*/
|
|
279
|
+
function decimalPartToWords(decimalPart, gender) {
|
|
241
280
|
let result = ''
|
|
242
281
|
let i = 0
|
|
243
282
|
|
|
@@ -256,20 +295,38 @@ function decimalPartToWords (decimalPart, gender) {
|
|
|
256
295
|
return result
|
|
257
296
|
}
|
|
258
297
|
|
|
298
|
+
// Supported magnitude ceilings (checked at the public entry points). Both
|
|
299
|
+
// tables are indexed [scaleIndex - 1] (units separate), so the ceiling is
|
|
300
|
+
// 10^((length + 1) * 3): cardinal/currency 10^33, ordinal 10^33.
|
|
301
|
+
export const cardinalMax = western(SCALE_FORMS.length)
|
|
302
|
+
export const ordinalMax = western(ORDINAL_SCALES.length)
|
|
303
|
+
export const currencyMax = western(SCALE_FORMS.length)
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @typedef {object} CardinalOptions
|
|
307
|
+
* @property {('masculine'|'feminine')} [gender] - Grammatical gender
|
|
308
|
+
*/
|
|
309
|
+
|
|
310
|
+
/** @type {Required<CardinalOptions>} */
|
|
311
|
+
export const cardinalDefaults = { gender: 'masculine' }
|
|
312
|
+
|
|
313
|
+
/** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
|
|
314
|
+
export const cardinalValues = { gender: ['masculine', 'feminine'] }
|
|
315
|
+
|
|
259
316
|
/**
|
|
260
317
|
* Converts a numeric value to Russian words.
|
|
261
|
-
*
|
|
262
318
|
* @param {number | string | bigint} value - The numeric value to convert
|
|
263
|
-
* @param {
|
|
264
|
-
* @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
|
|
319
|
+
* @param {CardinalOptions} [options] - Optional configuration
|
|
265
320
|
* @returns {string} The number in Russian words
|
|
266
321
|
*/
|
|
267
|
-
function toCardinal
|
|
268
|
-
options = validateOptions(options)
|
|
322
|
+
function toCardinal(value, options) {
|
|
269
323
|
const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
|
|
324
|
+
// Both the integer part and the decimal's significant digits are spelled via
|
|
325
|
+
// the scale builder, so both must clear the ceiling.
|
|
326
|
+
checkMax(integerPart, cardinalMax, decimalPart)
|
|
270
327
|
|
|
271
328
|
// Apply option defaults
|
|
272
|
-
const { gender
|
|
329
|
+
const { gender } = resolveOptions(options, cardinalDefaults, cardinalValues)
|
|
273
330
|
|
|
274
331
|
let result = ''
|
|
275
332
|
|
|
@@ -293,11 +350,10 @@ function toCardinal (value, options) {
|
|
|
293
350
|
/**
|
|
294
351
|
* Builds ordinal for a 0-99 segment when it's the final (ordinal) part.
|
|
295
352
|
* Returns ordinal form: "первый", "двадцать первый", etc.
|
|
296
|
-
*
|
|
297
353
|
* @param {number} n - Number 0-99
|
|
298
354
|
* @returns {string} Ordinal words
|
|
299
355
|
*/
|
|
300
|
-
function buildOrdinalTensOnes
|
|
356
|
+
function buildOrdinalTensOnes(n) {
|
|
301
357
|
if (n === 0) return ''
|
|
302
358
|
|
|
303
359
|
const onesDigit = n % 10
|
|
@@ -328,11 +384,10 @@ function buildOrdinalTensOnes (n) {
|
|
|
328
384
|
*
|
|
329
385
|
* In Russian ordinals, only the LAST component becomes ordinal.
|
|
330
386
|
* E.g., 121 = "сто двадцать первый" (one hundred twenty first)
|
|
331
|
-
*
|
|
332
387
|
* @param {bigint} n - Positive integer to convert
|
|
333
388
|
* @returns {string} Ordinal Russian words
|
|
334
389
|
*/
|
|
335
|
-
function integerToOrdinal
|
|
390
|
+
function integerToOrdinal(n) {
|
|
336
391
|
// Fast path: numbers < 100
|
|
337
392
|
if (n < 100n) {
|
|
338
393
|
return buildOrdinalTensOnes(Number(n))
|
|
@@ -383,11 +438,10 @@ function integerToOrdinal (n) {
|
|
|
383
438
|
/**
|
|
384
439
|
* Builds ordinal words for numbers >= 1,000,000.
|
|
385
440
|
* All segments except the final one are cardinal; final segment is ordinal.
|
|
386
|
-
*
|
|
387
441
|
* @param {bigint} n - Number >= 1,000,000
|
|
388
442
|
* @returns {string} Ordinal Russian words
|
|
389
443
|
*/
|
|
390
|
-
function buildLargeOrdinal
|
|
444
|
+
function buildLargeOrdinal(n) {
|
|
391
445
|
const numStr = n.toString()
|
|
392
446
|
const len = numStr.length
|
|
393
447
|
|
|
@@ -425,22 +479,26 @@ function buildLargeOrdinal (n) {
|
|
|
425
479
|
// Units position (no scale)
|
|
426
480
|
if (isLastNonZero) {
|
|
427
481
|
parts.push(integerToOrdinal(BigInt(segment)))
|
|
428
|
-
}
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
429
484
|
parts.push(buildSegmentMasc(segment))
|
|
430
485
|
}
|
|
431
|
-
}
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
432
488
|
// Has scale word
|
|
433
489
|
if (isLastNonZero) {
|
|
434
490
|
// This scale position is the final ordinal
|
|
435
491
|
if (segment === 1) {
|
|
436
492
|
parts.push(ORDINAL_SCALES[scaleIndex - 1])
|
|
437
|
-
}
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
438
495
|
// Use cardinal segment + ordinal scale
|
|
439
496
|
const isFeminine = scaleIndex === 1 // thousands are feminine
|
|
440
497
|
const segmentWord = isFeminine ? buildSegmentFem(segment) : buildSegmentMasc(segment)
|
|
441
498
|
parts.push(segmentWord + ' ' + ORDINAL_SCALES[scaleIndex - 1])
|
|
442
499
|
}
|
|
443
|
-
}
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
444
502
|
// Not the final segment: use cardinal
|
|
445
503
|
const scaleForms = SCALE_FORMS[scaleIndex - 1]
|
|
446
504
|
const scaleWord = pluralize(segment, scaleForms)
|
|
@@ -459,12 +517,10 @@ function buildLargeOrdinal (n) {
|
|
|
459
517
|
|
|
460
518
|
/**
|
|
461
519
|
* Converts a numeric value to Russian ordinal words (masculine nominative).
|
|
462
|
-
*
|
|
463
520
|
* @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
|
|
464
521
|
* @returns {string} The number as ordinal words (e.g., "первый", "сорок второй")
|
|
465
522
|
* @throws {TypeError} If value is not a valid numeric type
|
|
466
523
|
* @throws {RangeError} If value is negative, zero, or has a decimal part
|
|
467
|
-
*
|
|
468
524
|
* @example
|
|
469
525
|
* toOrdinal(1) // 'первый'
|
|
470
526
|
* toOrdinal(2) // 'второй'
|
|
@@ -475,8 +531,9 @@ function buildLargeOrdinal (n) {
|
|
|
475
531
|
* toOrdinal(101) // 'сто первый'
|
|
476
532
|
* toOrdinal(1000) // 'тысячный'
|
|
477
533
|
*/
|
|
478
|
-
function toOrdinal
|
|
534
|
+
function toOrdinal(value) {
|
|
479
535
|
const integerPart = parseOrdinalValue(value)
|
|
536
|
+
checkMax(integerPart, ordinalMax)
|
|
480
537
|
return integerToOrdinal(integerPart)
|
|
481
538
|
}
|
|
482
539
|
|
|
@@ -484,16 +541,21 @@ function toOrdinal (value) {
|
|
|
484
541
|
// CURRENCY: toCurrency(value, options?)
|
|
485
542
|
// ============================================================================
|
|
486
543
|
|
|
544
|
+
/**
|
|
545
|
+
* @typedef {object} CurrencyOptions
|
|
546
|
+
* @property {boolean} [and] - Use "и" between rubles and kopecks
|
|
547
|
+
*/
|
|
548
|
+
|
|
549
|
+
/** @type {Required<CurrencyOptions>} */
|
|
550
|
+
export const currencyDefaults = { and: true }
|
|
551
|
+
|
|
487
552
|
/**
|
|
488
553
|
* Converts a numeric value to Russian currency words (Russian Ruble).
|
|
489
|
-
*
|
|
490
554
|
* @param {number | string | bigint} value - The currency amount to convert
|
|
491
|
-
* @param {
|
|
492
|
-
* @param {boolean} [options.and=true] - Use "и" between rubles and kopecks
|
|
555
|
+
* @param {CurrencyOptions} [options] - Optional configuration
|
|
493
556
|
* @returns {string} The amount in Russian currency words
|
|
494
557
|
* @throws {TypeError} If value is not a valid numeric type
|
|
495
558
|
* @throws {Error} If value is not a valid number format
|
|
496
|
-
*
|
|
497
559
|
* @example
|
|
498
560
|
* toCurrency(42.50) // 'сорок два рубля и пятьдесят копеек'
|
|
499
561
|
* toCurrency(1) // 'один рубль'
|
|
@@ -501,10 +563,10 @@ function toOrdinal (value) {
|
|
|
501
563
|
* toCurrency(0.01) // 'одна копейка'
|
|
502
564
|
* toCurrency(42.50, { and: false }) // 'сорок два рубля пятьдесят копеек'
|
|
503
565
|
*/
|
|
504
|
-
function toCurrency
|
|
505
|
-
options = validateOptions(options)
|
|
566
|
+
function toCurrency(value, options) {
|
|
506
567
|
const { isNegative, dollars: rubles, cents: kopecks } = parseCurrencyValue(value)
|
|
507
|
-
|
|
568
|
+
checkMax(rubles, currencyMax)
|
|
569
|
+
const { and: useAnd } = resolveOptions(options, currencyDefaults)
|
|
508
570
|
|
|
509
571
|
// Build result
|
|
510
572
|
let result = ''
|
package/src/sr-Cyrl-RS.d.ts
CHANGED
|
@@ -1,22 +1,47 @@
|
|
|
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} CurrencyOptions
|
|
16
|
+
* @property {boolean} [and] - Use "и" between dinars and para
|
|
17
|
+
*/
|
|
18
|
+
/** @type {Required<CurrencyOptions>} */
|
|
19
|
+
export const currencyDefaults: Required<CurrencyOptions>;
|
|
20
|
+
export type CardinalOptions = {
|
|
21
|
+
/**
|
|
22
|
+
* - Grammatical gender
|
|
23
|
+
*/
|
|
24
|
+
gender?: "feminine" | "masculine" | undefined;
|
|
25
|
+
};
|
|
26
|
+
export type CurrencyOptions = {
|
|
27
|
+
/**
|
|
28
|
+
* - Use "и" between dinars and para
|
|
29
|
+
*/
|
|
30
|
+
and?: boolean | undefined;
|
|
31
|
+
};
|
|
1
32
|
/**
|
|
2
33
|
* Converts a numeric value to Serbian (Cyrillic) words.
|
|
3
|
-
*
|
|
4
34
|
* @param {number | string | bigint} value - The numeric value to convert
|
|
5
|
-
* @param {
|
|
6
|
-
* @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
|
|
35
|
+
* @param {CardinalOptions} [options] - Optional configuration
|
|
7
36
|
* @returns {string} The number in Serbian Cyrillic words
|
|
8
37
|
*/
|
|
9
|
-
export function toCardinal(value: number | string | bigint, options?:
|
|
10
|
-
gender?: "masculine" | "feminine" | undefined;
|
|
11
|
-
}): string;
|
|
38
|
+
export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
|
|
12
39
|
/**
|
|
13
40
|
* Converts a numeric value to Serbian ordinal words (masculine nominative).
|
|
14
|
-
*
|
|
15
41
|
* @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
|
|
16
42
|
* @returns {string} The number as ordinal words (e.g., "први", "четрдесет други")
|
|
17
43
|
* @throws {TypeError} If value is not a valid numeric type
|
|
18
44
|
* @throws {RangeError} If value is negative, zero, or has a decimal part
|
|
19
|
-
*
|
|
20
45
|
* @example
|
|
21
46
|
* toOrdinal(1) // 'први'
|
|
22
47
|
* toOrdinal(2) // 'други'
|
|
@@ -29,14 +54,11 @@ export function toCardinal(value: number | string | bigint, options?: {
|
|
|
29
54
|
export function toOrdinal(value: number | string | bigint): string;
|
|
30
55
|
/**
|
|
31
56
|
* Converts a numeric value to Serbian currency words (Serbian Dinar).
|
|
32
|
-
*
|
|
33
57
|
* @param {number | string | bigint} value - The currency amount to convert
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {boolean} [options.and=true] - Use "и" between dinars and para
|
|
58
|
+
* @param {CurrencyOptions} [options] - Optional configuration
|
|
36
59
|
* @returns {string} The amount in Serbian currency words
|
|
37
60
|
* @throws {TypeError} If value is not a valid numeric type
|
|
38
61
|
* @throws {Error} If value is not a valid number format
|
|
39
|
-
*
|
|
40
62
|
* @example
|
|
41
63
|
* toCurrency(42.50) // 'четрдесет два динара и педесет пара'
|
|
42
64
|
* toCurrency(1) // 'један динар'
|
|
@@ -44,6 +66,4 @@ export function toOrdinal(value: number | string | bigint): string;
|
|
|
44
66
|
* toCurrency(0.01) // 'једна пара'
|
|
45
67
|
* toCurrency(42.50, { and: false }) // 'четрдесет два динара педесет пара'
|
|
46
68
|
*/
|
|
47
|
-
export function toCurrency(value: number | string | bigint, options?:
|
|
48
|
-
and?: boolean | undefined;
|
|
49
|
-
}): string;
|
|
69
|
+
export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;
|