n2words 1.24.0 → 3.0.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 +49 -0
- package/README.md +183 -156
- package/dist/languages/am-Latn.js +3 -0
- package/dist/languages/am-Latn.js.map +1 -0
- package/dist/languages/am.js +3 -0
- package/dist/languages/am.js.map +1 -0
- package/dist/languages/ar.js +3 -2
- package/dist/languages/ar.js.map +1 -1
- package/dist/languages/az.js +3 -2
- package/dist/languages/az.js.map +1 -1
- package/dist/languages/bn.js +3 -2
- package/dist/languages/bn.js.map +1 -1
- package/dist/languages/cs.js +3 -2
- package/dist/languages/cs.js.map +1 -1
- package/dist/languages/da.js +3 -2
- package/dist/languages/da.js.map +1 -1
- package/dist/languages/de.js +3 -2
- package/dist/languages/de.js.map +1 -1
- package/dist/languages/el.js +3 -2
- package/dist/languages/el.js.map +1 -1
- package/dist/languages/en.js +3 -2
- package/dist/languages/en.js.map +1 -1
- package/dist/languages/es.js +3 -2
- package/dist/languages/es.js.map +1 -1
- package/dist/languages/fa.js +3 -2
- package/dist/languages/fa.js.map +1 -1
- package/dist/languages/fi.js +3 -0
- package/dist/languages/fi.js.map +1 -0
- package/dist/languages/fil.js +3 -2
- package/dist/languages/fil.js.map +1 -1
- package/dist/languages/fr-BE.js +3 -2
- package/dist/languages/fr-BE.js.map +1 -1
- package/dist/languages/fr.js +3 -2
- package/dist/languages/fr.js.map +1 -1
- package/dist/languages/gu.js +3 -2
- package/dist/languages/gu.js.map +1 -1
- package/dist/languages/ha.js +3 -0
- package/dist/languages/ha.js.map +1 -0
- package/dist/languages/hbo.js +3 -0
- package/dist/languages/hbo.js.map +1 -0
- package/dist/languages/he.js +3 -2
- package/dist/languages/he.js.map +1 -1
- package/dist/languages/hi.js +3 -2
- package/dist/languages/hi.js.map +1 -1
- package/dist/languages/hr.js +3 -2
- package/dist/languages/hr.js.map +1 -1
- package/dist/languages/hu.js +3 -2
- package/dist/languages/hu.js.map +1 -1
- package/dist/languages/id.js +3 -2
- package/dist/languages/id.js.map +1 -1
- package/dist/languages/it.js +3 -2
- package/dist/languages/it.js.map +1 -1
- package/dist/languages/ja.js +3 -2
- package/dist/languages/ja.js.map +1 -1
- package/dist/languages/kn.js +3 -2
- package/dist/languages/kn.js.map +1 -1
- package/dist/languages/ko.js +3 -2
- package/dist/languages/ko.js.map +1 -1
- package/dist/languages/lt.js +3 -2
- package/dist/languages/lt.js.map +1 -1
- package/dist/languages/lv.js +3 -2
- package/dist/languages/lv.js.map +1 -1
- package/dist/languages/mr.js +3 -2
- package/dist/languages/mr.js.map +1 -1
- package/dist/languages/ms.js +3 -2
- package/dist/languages/ms.js.map +1 -1
- package/dist/languages/nb.js +3 -2
- package/dist/languages/nb.js.map +1 -1
- package/dist/languages/nl.js +3 -2
- package/dist/languages/nl.js.map +1 -1
- package/dist/languages/pa.js +3 -0
- package/dist/languages/pa.js.map +1 -0
- package/dist/languages/pl.js +3 -2
- package/dist/languages/pl.js.map +1 -1
- package/dist/languages/pt.js +3 -2
- package/dist/languages/pt.js.map +1 -1
- package/dist/languages/ro.js +3 -2
- package/dist/languages/ro.js.map +1 -1
- package/dist/languages/ru.js +3 -2
- package/dist/languages/ru.js.map +1 -1
- package/dist/languages/sr-Cyrl.js +3 -0
- package/dist/languages/sr-Cyrl.js.map +1 -0
- package/dist/languages/sr-Latn.js +3 -2
- package/dist/languages/sr-Latn.js.map +1 -1
- package/dist/languages/sv.js +3 -2
- package/dist/languages/sv.js.map +1 -1
- package/dist/languages/sw.js +3 -2
- package/dist/languages/sw.js.map +1 -1
- package/dist/languages/ta.js +3 -2
- package/dist/languages/ta.js.map +1 -1
- package/dist/languages/te.js +3 -2
- package/dist/languages/te.js.map +1 -1
- package/dist/languages/th.js +3 -2
- package/dist/languages/th.js.map +1 -1
- package/dist/languages/tr.js +3 -2
- package/dist/languages/tr.js.map +1 -1
- package/dist/languages/uk.js +3 -2
- package/dist/languages/uk.js.map +1 -1
- package/dist/languages/ur.js +3 -2
- package/dist/languages/ur.js.map +1 -1
- package/dist/languages/vi.js +3 -2
- package/dist/languages/vi.js.map +1 -1
- package/dist/languages/zh-Hans.js +3 -2
- package/dist/languages/zh-Hans.js.map +1 -1
- package/dist/languages/zh-Hant.js +3 -0
- package/dist/languages/zh-Hant.js.map +1 -0
- package/dist/n2words.js +3 -2
- package/dist/n2words.js.map +1 -1
- package/lib/languages/am-Latn.d.ts +7 -0
- package/lib/languages/am-Latn.js +164 -0
- package/lib/languages/am.d.ts +7 -0
- package/lib/languages/am.js +164 -0
- package/lib/languages/ar.d.ts +17 -0
- package/lib/languages/ar.js +171 -209
- package/lib/languages/az.d.ts +7 -0
- package/lib/languages/az.js +167 -49
- package/lib/languages/bn.d.ts +7 -0
- package/lib/languages/bn.js +142 -123
- package/lib/languages/cs.d.ts +18 -0
- package/lib/languages/cs.js +303 -176
- package/lib/languages/da.d.ts +14 -0
- package/lib/languages/da.js +267 -139
- package/lib/languages/de.d.ts +17 -0
- package/lib/languages/de.js +310 -113
- package/lib/languages/el.d.ts +14 -0
- package/lib/languages/el.js +225 -98
- package/lib/languages/en.d.ts +17 -0
- package/lib/languages/en.js +235 -102
- package/lib/languages/es.d.ts +21 -0
- package/lib/languages/es.js +307 -125
- package/lib/languages/fa.d.ts +7 -0
- package/lib/languages/fa.js +115 -108
- package/lib/languages/fi.d.ts +14 -0
- package/lib/languages/fi.js +245 -0
- package/lib/languages/fil.d.ts +7 -0
- package/lib/languages/fil.js +199 -139
- package/lib/languages/fr-BE.d.ts +11 -0
- package/lib/languages/fr-BE.js +287 -48
- package/lib/languages/fr.d.ts +21 -0
- package/lib/languages/fr.js +343 -119
- package/lib/languages/gu.d.ts +7 -0
- package/lib/languages/gu.js +125 -144
- package/lib/languages/ha.d.ts +7 -0
- package/lib/languages/ha.js +230 -0
- package/lib/languages/hbo.d.ts +13 -0
- package/lib/languages/hbo.js +300 -0
- package/lib/languages/he.d.ts +13 -0
- package/lib/languages/he.js +230 -283
- package/lib/languages/hi.d.ts +7 -0
- package/lib/languages/hi.js +142 -123
- package/lib/languages/hr.d.ts +11 -0
- package/lib/languages/hr.js +190 -129
- package/lib/languages/hu.d.ts +7 -0
- package/lib/languages/hu.js +194 -133
- package/lib/languages/id.d.ts +7 -0
- package/lib/languages/id.js +167 -140
- package/lib/languages/it.d.ts +19 -0
- package/lib/languages/it.js +337 -108
- package/lib/languages/ja.d.ts +17 -0
- package/lib/languages/ja.js +224 -155
- package/lib/languages/kn.d.ts +7 -0
- package/lib/languages/kn.js +128 -62
- package/lib/languages/ko.d.ts +14 -0
- package/lib/languages/ko.js +250 -70
- package/lib/languages/lt.d.ts +18 -0
- package/lib/languages/lt.js +287 -148
- package/lib/languages/lv.d.ts +18 -0
- package/lib/languages/lv.js +291 -123
- package/lib/languages/mr.d.ts +7 -0
- package/lib/languages/mr.js +125 -144
- package/lib/languages/ms.d.ts +7 -0
- package/lib/languages/ms.js +171 -112
- package/lib/languages/nb.d.ts +14 -0
- package/lib/languages/nb.js +275 -100
- package/lib/languages/nl.d.ts +26 -0
- package/lib/languages/nl.js +307 -174
- package/lib/languages/pa.d.ts +7 -0
- package/lib/languages/pa.js +163 -0
- package/lib/languages/pl.d.ts +22 -0
- package/lib/languages/pl.js +299 -158
- package/lib/languages/pt.d.ts +17 -0
- package/lib/languages/pt.js +279 -120
- package/lib/languages/ro.d.ts +18 -0
- package/lib/languages/ro.js +214 -337
- package/lib/languages/ru.d.ts +11 -0
- package/lib/languages/ru.js +219 -95
- package/lib/languages/sr-Cyrl.d.ts +11 -0
- package/lib/languages/sr-Cyrl.js +215 -0
- package/lib/languages/sr-Latn.d.ts +11 -0
- package/lib/languages/sr-Latn.js +190 -132
- package/lib/languages/sv.d.ts +14 -0
- package/lib/languages/sv.js +280 -103
- package/lib/languages/sw.d.ts +7 -0
- package/lib/languages/sw.js +135 -103
- package/lib/languages/ta.d.ts +7 -0
- package/lib/languages/ta.js +133 -205
- package/lib/languages/te.d.ts +7 -0
- package/lib/languages/te.js +148 -213
- package/lib/languages/th.d.ts +7 -0
- package/lib/languages/th.js +139 -101
- package/lib/languages/tr.d.ts +18 -0
- package/lib/languages/tr.js +246 -66
- package/lib/languages/uk.d.ts +11 -0
- package/lib/languages/uk.js +197 -101
- package/lib/languages/ur.d.ts +7 -0
- package/lib/languages/ur.js +160 -123
- package/lib/languages/vi.d.ts +17 -0
- package/lib/languages/vi.js +287 -164
- package/lib/languages/zh-Hans.d.ts +11 -0
- package/lib/languages/zh-Hans.js +159 -142
- package/lib/languages/zh-Hant.d.ts +11 -0
- package/lib/languages/zh-Hant.js +202 -0
- package/lib/n2words.d.ts +53 -0
- package/lib/n2words.js +91 -227
- package/lib/utils/is-plain-object.d.ts +13 -0
- package/lib/utils/is-plain-object.js +17 -0
- package/lib/utils/parse-numeric.d.ts +17 -0
- package/lib/utils/parse-numeric.js +108 -0
- package/lib/utils/validate-options.d.ts +8 -0
- package/lib/utils/validate-options.js +16 -0
- package/package.json +118 -67
- package/dist/languages/pa-Guru.js +0 -2
- package/dist/languages/pa-Guru.js.map +0 -1
- package/lib/classes/abstract-language.js +0 -261
- package/lib/classes/greedy-scale-language.js +0 -195
- package/lib/classes/slavic-language.js +0 -251
- package/lib/classes/south-asian-language.js +0 -161
- package/lib/classes/turkic-language.js +0 -63
- package/lib/languages/pa-Guru.js +0 -126
- package/typings/classes/abstract-language.d.ts +0 -144
- package/typings/classes/greedy-scale-language.d.ts +0 -148
- package/typings/classes/slavic-language.d.ts +0 -145
- package/typings/classes/south-asian-language.d.ts +0 -101
- package/typings/classes/turkic-language.d.ts +0 -42
- package/typings/languages/ar.d.ts +0 -93
- package/typings/languages/az.d.ts +0 -25
- package/typings/languages/bn.d.ts +0 -1
- package/typings/languages/cs.d.ts +0 -120
- package/typings/languages/da.d.ts +0 -53
- package/typings/languages/de.d.ts +0 -26
- package/typings/languages/el.d.ts +0 -11
- package/typings/languages/en.d.ts +0 -30
- package/typings/languages/es.d.ts +0 -43
- package/typings/languages/fa.d.ts +0 -81
- package/typings/languages/fil.d.ts +0 -12
- package/typings/languages/fr-BE.d.ts +0 -41
- package/typings/languages/fr.d.ts +0 -43
- package/typings/languages/gu.d.ts +0 -12
- package/typings/languages/he.d.ts +0 -197
- package/typings/languages/hi.d.ts +0 -1
- package/typings/languages/hr.d.ts +0 -110
- package/typings/languages/hu.d.ts +0 -37
- package/typings/languages/id.d.ts +0 -69
- package/typings/languages/it.d.ts +0 -51
- package/typings/languages/ja.d.ts +0 -58
- package/typings/languages/kn.d.ts +0 -11
- package/typings/languages/ko.d.ts +0 -25
- package/typings/languages/lt.d.ts +0 -110
- package/typings/languages/lv.d.ts +0 -99
- package/typings/languages/mr.d.ts +0 -12
- package/typings/languages/ms.d.ts +0 -37
- package/typings/languages/nb.d.ts +0 -27
- package/typings/languages/nl.d.ts +0 -65
- package/typings/languages/pa-Guru.d.ts +0 -1
- package/typings/languages/pl.d.ts +0 -116
- package/typings/languages/pt.d.ts +0 -39
- package/typings/languages/ro.d.ts +0 -229
- package/typings/languages/ru.d.ts +0 -108
- package/typings/languages/sr-Latn.d.ts +0 -98
- package/typings/languages/sv.d.ts +0 -30
- package/typings/languages/sw.d.ts +0 -1
- package/typings/languages/ta.d.ts +0 -1
- package/typings/languages/te.d.ts +0 -1
- package/typings/languages/th.d.ts +0 -1
- package/typings/languages/tr.d.ts +0 -46
- package/typings/languages/uk.d.ts +0 -117
- package/typings/languages/ur.d.ts +0 -1
- package/typings/languages/vi.d.ts +0 -116
- package/typings/languages/zh-Hans.d.ts +0 -57
- package/typings/n2words.d.ts +0 -177
package/lib/languages/ro.js
CHANGED
|
@@ -1,380 +1,257 @@
|
|
|
1
|
-
import AbstractLanguage from '../classes/abstract-language.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @typedef {Object} RomanianOptions
|
|
5
|
-
* @property {boolean} [feminine=false] Use feminine forms for numbers.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
/**
|
|
9
|
-
* Romanian language converter
|
|
2
|
+
* Romanian language converter - Functional Implementation
|
|
10
3
|
*
|
|
11
|
-
*
|
|
12
|
-
* - Gender agreement (masculine/feminine forms)
|
|
13
|
-
* - Complex pluralization (singular/plural forms)
|
|
14
|
-
* - "De" preposition insertion for groups >= 20
|
|
15
|
-
* - Special feminine handling for thousands
|
|
16
|
-
* - Proper case and agreement patterns
|
|
17
|
-
*
|
|
18
|
-
* Key Features:
|
|
19
|
-
* - Gender-aware number forms (unu/una, doi/două, doisprezece/douăsprezece)
|
|
20
|
-
* - Group-based algorithm:
|
|
21
|
-
* 1. Split number into groups of 3 digits
|
|
22
|
-
* 2. Convert each group using gender rules and special forms
|
|
23
|
-
* 3. Insert "de" preposition for groups >= 20 (e.g., "douăzeci de mii")
|
|
24
|
-
* 4. Append magnitude word with proper singular/plural form (mie/mii, milion/milioane)
|
|
25
|
-
* 5. Join with spaces
|
|
26
|
-
* - Special feminine units for thousands group
|
|
27
|
-
* - Automatic "de" insertion rules (nouăsprezece mii vs douăzeci de mii)
|
|
28
|
-
* - Proper singular/plural forms (mie/mii, milion/milioane)
|
|
29
|
-
* - Support for very large numbers (up to decillions)
|
|
4
|
+
* A performance-optimized number-to-words converter using precomputed lookup tables.
|
|
30
5
|
*
|
|
31
|
-
*
|
|
32
|
-
* -
|
|
33
|
-
* -
|
|
6
|
+
* Key features:
|
|
7
|
+
* - Gender agreement (unu/una, doi/două)
|
|
8
|
+
* - "De" preposition insertion for groups >= 20
|
|
9
|
+
* - Complex scale word handling (mie/mii, milion/milioane)
|
|
10
|
+
* - Feminine units for thousands
|
|
34
11
|
*/
|
|
35
|
-
export class Romanian extends AbstractLanguage {
|
|
36
|
-
negativeWord = 'minus'
|
|
37
|
-
decimalSeparatorWord = 'virgulă'
|
|
38
|
-
zeroWord = 'zero'
|
|
39
|
-
|
|
40
|
-
ones = {
|
|
41
|
-
1: 'unu',
|
|
42
|
-
2: 'doi',
|
|
43
|
-
3: 'trei',
|
|
44
|
-
4: 'patru',
|
|
45
|
-
5: 'cinci',
|
|
46
|
-
6: 'șase',
|
|
47
|
-
7: 'șapte',
|
|
48
|
-
8: 'opt',
|
|
49
|
-
9: 'nouă'
|
|
50
|
-
}
|
|
51
12
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
13
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
14
|
+
import { validateOptions } from '../utils/validate-options.js'
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Vocabulary (module-level constants)
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
const ONES_MASC = ['', 'unu', 'doi', 'trei', 'patru', 'cinci', 'șase', 'șapte', 'opt', 'nouă']
|
|
21
|
+
const ONES_FEM = ['', 'una', 'două', 'trei', 'patru', 'cinci', 'șase', 'șapte', 'opt', 'nouă']
|
|
22
|
+
|
|
23
|
+
const TEENS = ['zece', 'unsprezece', 'douăsprezece', 'treisprezece', 'paisprezece', 'cincisprezece', 'șaisprezece', 'șaptesprezece', 'optsprezece', 'nouăsprezece']
|
|
24
|
+
const TEENS_MASC = ['zece', 'unsprezece', 'doisprezece', 'treisprezece', 'paisprezece', 'cincisprezece', 'șaisprezece', 'șaptesprezece', 'optsprezece', 'nouăsprezece']
|
|
25
|
+
|
|
26
|
+
const TWENTIES = ['', '', 'douăzeci', 'treizeci', 'patruzeci', 'cincizeci', 'șaizeci', 'șaptezeci', 'optzeci', 'nouăzeci']
|
|
27
|
+
|
|
28
|
+
const HUNDREDS = ['', 'o sută', 'două sute', 'trei sute', 'patru sute', 'cinci sute', 'șase sute', 'șapte sute', 'opt sute', 'nouă sute']
|
|
29
|
+
|
|
30
|
+
const ZERO = 'zero'
|
|
31
|
+
const NEGATIVE = 'minus'
|
|
32
|
+
const DECIMAL_SEP = 'virgulă'
|
|
33
|
+
|
|
34
|
+
// Scale metadata: [singular, plural, article, feminine, needsDe]
|
|
35
|
+
// - singular: form for 1
|
|
36
|
+
// - plural: form for 2+
|
|
37
|
+
// - article: 'o' for feminine, 'un' for masculine
|
|
38
|
+
// - feminine: whether units should be feminine
|
|
39
|
+
// - needsDe: whether "de" is inserted for segment >= 20
|
|
40
|
+
const SCALE_META = [
|
|
41
|
+
{ singular: 'mie', plural: 'mii', article: 'o', feminine: true, needsDe: true },
|
|
42
|
+
{ singular: 'milion', plural: 'milioane', article: 'un', feminine: false, needsDe: true },
|
|
43
|
+
{ singular: 'miliard', plural: 'miliarde', article: 'un', feminine: false, needsDe: true },
|
|
44
|
+
{ singular: 'trilion', plural: 'trilioane', article: 'un', feminine: false, needsDe: true },
|
|
45
|
+
{ singular: 'cvadrilion', plural: 'cvadrilioane', article: 'un', feminine: false, needsDe: true },
|
|
46
|
+
{ singular: 'cvintilion', plural: 'cvintilioane', article: 'un', feminine: false, needsDe: true },
|
|
47
|
+
{ singular: 'sextilion', plural: 'sextilioane', article: 'un', feminine: false, needsDe: true },
|
|
48
|
+
{ singular: 'septilion', plural: 'septilioane', article: 'un', feminine: false, needsDe: true },
|
|
49
|
+
{ singular: 'octilion', plural: 'octilioane', article: 'un', feminine: false, needsDe: true }
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Helper Functions
|
|
54
|
+
// ============================================================================
|
|
63
55
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
6: 'șaisprezece',
|
|
72
|
-
7: 'șaptesprezece',
|
|
73
|
-
8: 'optsprezece',
|
|
74
|
-
9: 'nouăsprezece'
|
|
56
|
+
/**
|
|
57
|
+
* Spells number under 100.
|
|
58
|
+
*/
|
|
59
|
+
function spellUnder100 (n, feminine = false, masculineTeens = false) {
|
|
60
|
+
if (n === 0) return ''
|
|
61
|
+
if (n < 10) {
|
|
62
|
+
return feminine ? ONES_FEM[n] : ONES_MASC[n]
|
|
75
63
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
0: 'zece',
|
|
79
|
-
1: 'unsprezece',
|
|
80
|
-
2: 'doisprezece',
|
|
81
|
-
3: 'treisprezece',
|
|
82
|
-
4: 'paisprezece',
|
|
83
|
-
5: 'cincisprezece',
|
|
84
|
-
6: 'șaisprezece',
|
|
85
|
-
7: 'șaptesprezece',
|
|
86
|
-
8: 'optsprezece',
|
|
87
|
-
9: 'nouăsprezece'
|
|
64
|
+
if (n < 20) {
|
|
65
|
+
return masculineTeens ? TEENS_MASC[n - 10] : TEENS[n - 10]
|
|
88
66
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
4: 'patruzeci',
|
|
94
|
-
5: 'cincizeci',
|
|
95
|
-
6: 'șaizeci',
|
|
96
|
-
7: 'șaptezeci',
|
|
97
|
-
8: 'optzeci',
|
|
98
|
-
9: 'nouăzeci'
|
|
67
|
+
const t = Math.floor(n / 10)
|
|
68
|
+
const u = n % 10
|
|
69
|
+
if (u === 0) {
|
|
70
|
+
return TWENTIES[t]
|
|
99
71
|
}
|
|
72
|
+
const onesWord = feminine ? ONES_FEM[u] : ONES_MASC[u]
|
|
73
|
+
return TWENTIES[t] + ' și ' + onesWord
|
|
74
|
+
}
|
|
100
75
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
6: 'șase sute',
|
|
108
|
-
7: 'șapte sute',
|
|
109
|
-
8: 'opt sute',
|
|
110
|
-
9: 'nouă sute'
|
|
111
|
-
}
|
|
76
|
+
/**
|
|
77
|
+
* Spells number under 1000.
|
|
78
|
+
*/
|
|
79
|
+
function spellUnder1000 (n, feminine = false, masculineTeens = false) {
|
|
80
|
+
if (n === 0) return ''
|
|
81
|
+
if (n < 100) return spellUnder100(n, feminine, masculineTeens)
|
|
112
82
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
* - 10^3: mie/mii (feminine units in chunk; "de" for chunk >= 20)
|
|
117
|
-
* - 10^6: milion/milioane ("de" for chunk >= 20)
|
|
118
|
-
* - 10^9: miliard/miliarde ("de" for chunk >= 20)
|
|
119
|
-
*/
|
|
120
|
-
thousands = {
|
|
121
|
-
1: { singular: 'mie', plural: 'mii', feminine: true, needsDe: true }, // 10^3
|
|
122
|
-
2: { singular: 'milion', plural: 'milioane', feminine: false, needsDe: true }, // 10^6
|
|
123
|
-
3: { singular: 'miliard', plural: 'miliarde', feminine: false, needsDe: true }, // 10^9
|
|
124
|
-
4: { singular: 'trilion', plural: 'trilioane', feminine: false, needsDe: true }, // 10^12
|
|
125
|
-
5: { singular: 'cvadrilion', plural: 'cvadrilioane', feminine: false, needsDe: true }, // 10^15
|
|
126
|
-
6: { singular: 'cvintilion', plural: 'cvintilioane', feminine: false, needsDe: true }, // 10^18
|
|
127
|
-
7: { singular: 'sextilion', plural: 'sextilioane', feminine: false, needsDe: true }, // 10^21
|
|
128
|
-
8: { singular: 'septilion', plural: 'septilioane', feminine: false, needsDe: true }, // 10^24
|
|
129
|
-
9: { singular: 'octilion', plural: 'octilioane', feminine: false, needsDe: true }, // 10^27
|
|
130
|
-
10: { singular: 'decilion', plural: 'decilioane', feminine: false, needsDe: true } // 10^33
|
|
131
|
-
}
|
|
83
|
+
const h = Math.floor(n / 100)
|
|
84
|
+
const r = n % 100
|
|
85
|
+
const hundredWord = HUNDREDS[h]
|
|
132
86
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
* @param {RomanianOptions} [options={}] Configuration options.
|
|
137
|
-
*/
|
|
138
|
-
constructor ({ feminine = false } = {}) {
|
|
139
|
-
super()
|
|
87
|
+
if (r === 0) return hundredWord
|
|
88
|
+
return hundredWord + ' ' + spellUnder100(r, feminine, masculineTeens)
|
|
89
|
+
}
|
|
140
90
|
|
|
141
|
-
|
|
142
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Builds scale word with proper pluralization and "de" insertion.
|
|
93
|
+
* Romanian always uses feminine forms (două, not doi) when counting scale words.
|
|
94
|
+
*/
|
|
95
|
+
function buildScalePhrase (segment, scaleIndex) {
|
|
96
|
+
const meta = SCALE_META[scaleIndex - 1]
|
|
97
|
+
if (!meta) return spellUnder1000(segment, true)
|
|
143
98
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
* @param {string} n - The numeric string to split
|
|
147
|
-
* @param {number} x - The size of each group
|
|
148
|
-
* @returns {bigint[]} Array of BigInt groups
|
|
149
|
-
*/
|
|
150
|
-
splitByX (n, x) {
|
|
151
|
-
const results = []
|
|
152
|
-
const l = n.length
|
|
153
|
-
let result
|
|
154
|
-
|
|
155
|
-
if (l > x) {
|
|
156
|
-
const start = l % x
|
|
157
|
-
if (start > 0) {
|
|
158
|
-
result = n.slice(0, start)
|
|
159
|
-
results.push(BigInt(result))
|
|
160
|
-
}
|
|
161
|
-
for (let index = start; index < l; index += x) {
|
|
162
|
-
result = n.slice(index, index + x)
|
|
163
|
-
results.push(BigInt(result))
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
results.push(BigInt(n))
|
|
167
|
-
}
|
|
168
|
-
return results
|
|
99
|
+
if (segment === 1) {
|
|
100
|
+
return meta.article + ' ' + meta.singular
|
|
169
101
|
}
|
|
170
102
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return a.map(BigInt)
|
|
103
|
+
// Special case: 21 with scale words uses feminine "una"
|
|
104
|
+
if (segment === 21 && meta.needsDe) {
|
|
105
|
+
return 'douăzeci și una de ' + meta.plural
|
|
175
106
|
}
|
|
176
107
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
* - 1 → singular with article ("o mie", "un milion", "un miliard", …)
|
|
180
|
-
* - otherwise → spell chunk + (optional "de") + plural
|
|
181
|
-
* "de" is inserted when chunk >= 20 (e.g., "douăzeci de mii/milioane/miliarde").
|
|
182
|
-
* @param {bigint} chunk - The chunk value
|
|
183
|
-
* @param {object} form - The form object with singular, plural, feminine, needsDe properties
|
|
184
|
-
* @returns {string} The pluralized form
|
|
185
|
-
*/
|
|
186
|
-
romanianPluralize (chunk, form) {
|
|
187
|
-
const n = Number(chunk)
|
|
188
|
-
|
|
189
|
-
if (n === 1) {
|
|
190
|
-
// article differs for feminine "mie" (o mie) vs the rest (un milion/miliard…)
|
|
191
|
-
const article = form.feminine ? 'o' : 'un'
|
|
192
|
-
return `${article} ${form.singular}`
|
|
193
|
-
}
|
|
108
|
+
// Romanian always uses feminine when counting scale words (două milioane, not doi milioane)
|
|
109
|
+
const words = spellUnder1000(segment, true)
|
|
194
110
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
111
|
+
// "de" after >= 20
|
|
112
|
+
const needsDe = meta.needsDe && segment >= 20
|
|
113
|
+
const separator = needsDe ? ' de ' : ' '
|
|
199
114
|
|
|
200
|
-
|
|
201
|
-
|
|
115
|
+
return words + separator + meta.plural
|
|
116
|
+
}
|
|
202
117
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Conversion Functions
|
|
120
|
+
// ============================================================================
|
|
206
121
|
|
|
207
|
-
|
|
208
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Converts a non-negative integer to Romanian words.
|
|
124
|
+
*
|
|
125
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
126
|
+
* @param {Object} options - Conversion options
|
|
127
|
+
* @returns {string} Romanian words
|
|
128
|
+
*/
|
|
129
|
+
function integerToWords (n, options = {}) {
|
|
130
|
+
if (n === 0n) return ZERO
|
|
209
131
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (n < 20) {
|
|
215
|
-
return this.tens[n - 10]
|
|
216
|
-
}
|
|
217
|
-
const t = Math.floor(n / 10)
|
|
218
|
-
const u = n % 10
|
|
219
|
-
return u
|
|
220
|
-
? `${this.twenties[t]} și ${(feminineUnits ? this.onesFeminine : this.ones)[u]}`
|
|
221
|
-
: this.twenties[t]
|
|
132
|
+
// Fast path: numbers < 1000
|
|
133
|
+
if (n < 1000n) {
|
|
134
|
+
const feminine = options.gender === 'feminine'
|
|
135
|
+
return spellUnder1000(Number(n), feminine)
|
|
222
136
|
}
|
|
223
137
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const h = Math.floor(n / 100)
|
|
227
|
-
const r = n % 100
|
|
228
|
-
const hundredWords = this.hundreds[h]
|
|
229
|
-
if (!r) return hundredWords
|
|
230
|
-
// Standard readable form: "o sută unu" (for units) or "o sută cincizeci" (for tens)
|
|
231
|
-
const separator = ' '
|
|
232
|
-
return `${hundredWords}${separator}${this.spellUnder100(r, feminineUnits)}`
|
|
233
|
-
}
|
|
138
|
+
return buildLargeNumberWords(n, options)
|
|
139
|
+
}
|
|
234
140
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Builds words for numbers >= 1000.
|
|
143
|
+
* Uses BigInt division for faster segment extraction.
|
|
144
|
+
*
|
|
145
|
+
* @param {bigint} n - Number >= 1000
|
|
146
|
+
* @param {Object} options - Conversion options
|
|
147
|
+
* @returns {string} Romanian words
|
|
148
|
+
*/
|
|
149
|
+
function buildLargeNumberWords (n, options) {
|
|
150
|
+
// Extract segments using BigInt division (faster than string slicing)
|
|
151
|
+
// Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
|
|
152
|
+
const segmentValues = []
|
|
153
|
+
let temp = n
|
|
154
|
+
while (temp > 0n) {
|
|
155
|
+
segmentValues.push(Number(temp % 1000n))
|
|
156
|
+
temp = temp / 1000n
|
|
157
|
+
}
|
|
251
158
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return words
|
|
255
|
-
}
|
|
159
|
+
// Build result string directly (avoid regex cleanup)
|
|
160
|
+
let result = ''
|
|
256
161
|
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
return [...words, masculineWords]
|
|
261
|
-
}
|
|
162
|
+
for (let i = segmentValues.length - 1; i >= 0; i--) {
|
|
163
|
+
const segment = segmentValues[i]
|
|
164
|
+
if (segment === 0) continue
|
|
262
165
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
166
|
+
let segmentWords
|
|
167
|
+
if (i === 0) {
|
|
168
|
+
// Units segment - use gender from options
|
|
169
|
+
const feminine = options.gender === 'feminine'
|
|
170
|
+
segmentWords = spellUnder1000(segment, feminine)
|
|
171
|
+
} else {
|
|
172
|
+
// Scale segment
|
|
173
|
+
segmentWords = buildScalePhrase(segment, i)
|
|
271
174
|
}
|
|
272
175
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
for (const x of chunks) {
|
|
278
|
-
let onesMap = []
|
|
279
|
-
index = index - 1
|
|
280
|
-
|
|
281
|
-
if (x === 0n) continue
|
|
282
|
-
|
|
283
|
-
const [n1, n2, n3] = this.getDigits(x) // units, tens, hundreds (as BigInt)
|
|
284
|
-
|
|
285
|
-
// hundreds
|
|
286
|
-
if (n3 > 0n) {
|
|
287
|
-
words.push(this.hundreds[Number(n3)])
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// tens & teens
|
|
291
|
-
if (n2 > 1n) {
|
|
292
|
-
words.push(this.twenties[Number(n2)])
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (n2 === 1n) {
|
|
296
|
-
words.push(this.tensMasculine[Number(n1)])
|
|
297
|
-
} else if (n1 > 0n) {
|
|
298
|
-
// Always use masculine units for decimal places
|
|
299
|
-
onesMap = this.ones
|
|
300
|
-
|
|
301
|
-
// if there is a twenty/treizeci/etc AND ones > 0 → add "și"
|
|
302
|
-
if (n2 > 1n) words.push('și')
|
|
303
|
-
words.push(onesMap[Number(n1)])
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// big unit name (mie/mii, milion/milioane, …)
|
|
307
|
-
if (index > 0) {
|
|
308
|
-
const form = this.thousands[index]
|
|
309
|
-
if (form) {
|
|
310
|
-
words.push(this.romanianPluralize(x, form))
|
|
311
|
-
} else {
|
|
312
|
-
// For very large numbers beyond our defined units, just spell out the number
|
|
313
|
-
words.push(this.spellUnder1000(Number(x), false))
|
|
314
|
-
}
|
|
315
|
-
}
|
|
176
|
+
if (result && segmentWords) {
|
|
177
|
+
result += ' ' + segmentWords
|
|
178
|
+
} else if (segmentWords) {
|
|
179
|
+
result = segmentWords
|
|
316
180
|
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return result
|
|
184
|
+
}
|
|
317
185
|
|
|
318
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Converts decimal digits to Romanian words.
|
|
188
|
+
* Decimals always use masculine forms.
|
|
189
|
+
*
|
|
190
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
191
|
+
* @returns {string} Romanian words for decimal part
|
|
192
|
+
*/
|
|
193
|
+
function decimalPartToWords (decimalPart) {
|
|
194
|
+
let result = ''
|
|
195
|
+
let i = 0
|
|
196
|
+
|
|
197
|
+
// Handle leading zeros
|
|
198
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
199
|
+
if (result) result += ' '
|
|
200
|
+
result += ZERO
|
|
201
|
+
i++
|
|
319
202
|
}
|
|
320
203
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
index = index - 1
|
|
331
|
-
if (x === 0n) continue
|
|
332
|
-
const [n1, n2, n3] = this.getDigits(x) // units, tens, hundreds (as BigInt)
|
|
333
|
-
// hundreds (only for the last group, not for thousands)
|
|
334
|
-
if (n3 > 0n && index === 0) {
|
|
335
|
-
words.push(this.hundreds[Number(n3)])
|
|
336
|
-
}
|
|
337
|
-
// tens & teens (only for the last group, not for thousands)
|
|
338
|
-
if (index === 0) {
|
|
339
|
-
if (n2 > 1n) {
|
|
340
|
-
words.push(this.twenties[Number(n2)])
|
|
341
|
-
}
|
|
342
|
-
if (n2 === 1n) {
|
|
343
|
-
words.push(this.tens[Number(n1)])
|
|
344
|
-
} else if (n1 > 0n) {
|
|
345
|
-
// pick masculine/feminine units (only for the last group, not for thousands)
|
|
346
|
-
const feminineUnits = this.feminine && index === 0
|
|
347
|
-
onesMap = feminineUnits ? this.onesFeminine : this.ones
|
|
348
|
-
// if there is a twenty/treizeci/etc AND ones > 0 → add "și"
|
|
349
|
-
if (n2 > 1n) words.push('și')
|
|
350
|
-
words.push(onesMap[Number(n1)])
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
// big unit name (mie/mii, milion/milioane, …)
|
|
354
|
-
if (index > 0) {
|
|
355
|
-
const form = this.thousands[index]
|
|
356
|
-
if (form) {
|
|
357
|
-
words.push(this.romanianPluralize(x, form))
|
|
358
|
-
} else {
|
|
359
|
-
// For very large numbers beyond our defined units, just spell out the number
|
|
360
|
-
words.push(this.spellUnder1000(Number(x), true))
|
|
361
|
-
}
|
|
362
|
-
}
|
|
204
|
+
// Convert remainder as a single number (masculine, with masculine teens)
|
|
205
|
+
const remainder = decimalPart.slice(i)
|
|
206
|
+
if (remainder) {
|
|
207
|
+
if (result) result += ' '
|
|
208
|
+
const n = BigInt(remainder)
|
|
209
|
+
if (n < 1000n) {
|
|
210
|
+
result += spellUnder1000(Number(n), false, true)
|
|
211
|
+
} else {
|
|
212
|
+
result += integerToWords(n, { gender: 'masculine' })
|
|
363
213
|
}
|
|
364
|
-
return words.join(' ').replaceAll(/\s+/g, ' ').trim()
|
|
365
214
|
}
|
|
215
|
+
|
|
216
|
+
return result
|
|
366
217
|
}
|
|
367
218
|
|
|
368
219
|
/**
|
|
369
|
-
* Converts a
|
|
220
|
+
* Converts a numeric value to Romanian words.
|
|
221
|
+
*
|
|
222
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
223
|
+
* @param {Object} [options] - Conversion options
|
|
224
|
+
* @param {string} [options.gender='masculine'] - Gender for numbers
|
|
225
|
+
* @returns {string} The number in Romanian words
|
|
226
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
227
|
+
* @throws {Error} If value is not a valid number format
|
|
370
228
|
*
|
|
371
|
-
* @
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* @throws {TypeError} If value is NaN or invalid type.
|
|
376
|
-
* @throws {Error} If value is an invalid number string.
|
|
229
|
+
* @example
|
|
230
|
+
* toWords(21) // 'douăzeci și unu'
|
|
231
|
+
* toWords(1, { gender: 'feminine' }) // 'una'
|
|
232
|
+
* toWords(1000) // 'o mie'
|
|
377
233
|
*/
|
|
378
|
-
|
|
379
|
-
|
|
234
|
+
function toWords (value, options) {
|
|
235
|
+
options = validateOptions(options)
|
|
236
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
237
|
+
|
|
238
|
+
let result = ''
|
|
239
|
+
|
|
240
|
+
if (isNegative) {
|
|
241
|
+
result = NEGATIVE + ' '
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
result += integerToWords(integerPart, options)
|
|
245
|
+
|
|
246
|
+
if (decimalPart) {
|
|
247
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return result
|
|
380
251
|
}
|
|
252
|
+
|
|
253
|
+
// ============================================================================
|
|
254
|
+
// Public API
|
|
255
|
+
// ============================================================================
|
|
256
|
+
|
|
257
|
+
export { toWords }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a numeric value to Russian words.
|
|
3
|
+
*
|
|
4
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
5
|
+
* @param {Object} [options] - Optional configuration
|
|
6
|
+
* @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
|
|
7
|
+
* @returns {string} The number in Russian words
|
|
8
|
+
*/
|
|
9
|
+
export function toWords(value: number | string | bigint, options?: {
|
|
10
|
+
gender?: "masculine" | "feminine" | undefined;
|
|
11
|
+
}): string;
|