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/it.js
CHANGED
|
@@ -1,148 +1,377 @@
|
|
|
1
|
-
import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Italian language converter
|
|
2
|
+
* Italian language converter - Functional Implementation v2
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
* - Accentuation rules for "tre" in compounds ("ventitré" not "ventitre")
|
|
9
|
-
* - Special handling for "uno" and vowel agreement
|
|
10
|
-
* - Complex composition patterns for large numbers
|
|
4
|
+
* A performance-optimized number-to-words converter using precomputed lookup tables.
|
|
5
|
+
* Self-contained module with its own input validation, ready for subpath exports.
|
|
11
6
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* rather than the standard highest-matching-scale approach. This is necessary
|
|
15
|
-
* because Italian's word formation rules are irregular and context-dependent.
|
|
16
|
-
* See tensToCardinal(), hundredsToCardinal(), and bigNumberToCardinal().
|
|
7
|
+
* Key optimization: Precompute all 1000 segment values (0-999) at module load.
|
|
8
|
+
* This eliminates all per-call string manipulation for segment conversion.
|
|
17
9
|
*
|
|
18
|
-
*
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
22
|
-
* -
|
|
10
|
+
* Italian-specific rules (handled in precomputation):
|
|
11
|
+
* - Concatenation without spaces within segments ("ventotto" not "venti otto")
|
|
12
|
+
* - Phonetic vowel elision: "venti" + "otto" → "ventotto"
|
|
13
|
+
* - Accent on final "tre" in compounds: "ventitré"
|
|
14
|
+
* - mille/mila alternation for thousands
|
|
15
|
+
* - Scale words: milione/milioni, miliardo/miliardi, etc.
|
|
16
|
+
* - "e" connector before simple final remainder
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Vocabulary (module-level constants)
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
// Base vocabulary for building lookup tables
|
|
26
|
+
const ONES = ['', 'uno', 'due', 'tre', 'quattro', 'cinque', 'sei', 'sette', 'otto', 'nove']
|
|
27
|
+
const TEENS = ['dieci', 'undici', 'dodici', 'tredici', 'quattordici', 'quindici', 'sedici', 'diciassette', 'diciotto', 'diciannove']
|
|
28
|
+
const TENS = ['', '', 'venti', 'trenta', 'quaranta', 'cinquanta', 'sessanta', 'settanta', 'ottanta', 'novanta']
|
|
29
|
+
const HUNDREDS = ['', 'cento', 'duecento', 'trecento', 'quattrocento', 'cinquecento', 'seicento', 'settecento', 'ottocento', 'novecento']
|
|
30
|
+
|
|
31
|
+
const ZERO = 'zero'
|
|
32
|
+
const NEGATIVE = 'meno'
|
|
33
|
+
const DECIMAL_SEP = 'virgola'
|
|
34
|
+
|
|
35
|
+
// Thousands
|
|
36
|
+
const THOUSAND_SINGULAR = 'mille'
|
|
37
|
+
const THOUSAND_PLURAL_SUFFIX = 'mila'
|
|
38
|
+
|
|
39
|
+
// Scale word generation
|
|
40
|
+
const SCALE_PREFIXES = ['m', 'b', 'tr', 'quadr', 'quint', 'sest', 'sett', 'ott', 'nov', 'dec']
|
|
41
|
+
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// Precomputed Lookup Tables (built once at module load)
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Applies Italian phonetic vowel elision rules.
|
|
48
|
+
* Only used during table construction.
|
|
23
49
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const result = splittedString.map(word => {
|
|
42
|
-
return word.slice(-3) === 'tre' && word.length > 3 ? word.replaceAll('tré', 'tre').slice(0, -3) + 'tré' : word.replaceAll('tré', 'tre')
|
|
43
|
-
})
|
|
44
|
-
return result.join(' ')
|
|
50
|
+
function applyPhoneticRules (str) {
|
|
51
|
+
return str
|
|
52
|
+
.replace(/io/g, 'o')
|
|
53
|
+
.replace(/ao/g, 'o')
|
|
54
|
+
.replace(/oo/g, 'o')
|
|
55
|
+
.replace(/iu/g, 'u')
|
|
56
|
+
.replace(/au/g, 'u')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Adds accent to final "tre" in a word.
|
|
61
|
+
* Only used during table construction.
|
|
62
|
+
*/
|
|
63
|
+
function accentuateTre (word) {
|
|
64
|
+
if (word.length > 3 && word.slice(-3) === 'tre') {
|
|
65
|
+
return word.slice(0, -3) + 'tré'
|
|
45
66
|
}
|
|
67
|
+
return word
|
|
68
|
+
}
|
|
46
69
|
|
|
47
|
-
|
|
48
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Builds the segment word for a number 0-999.
|
|
72
|
+
* Only used during table construction.
|
|
73
|
+
*/
|
|
74
|
+
function buildSegmentWord (n) {
|
|
75
|
+
if (n === 0) return ''
|
|
76
|
+
|
|
77
|
+
const ones = n % 10
|
|
78
|
+
const tens = Math.floor(n / 10) % 10
|
|
79
|
+
const hundreds = Math.floor(n / 100)
|
|
80
|
+
|
|
81
|
+
let result = ''
|
|
82
|
+
|
|
83
|
+
// Hundreds
|
|
84
|
+
if (hundreds > 0) {
|
|
85
|
+
result = HUNDREDS[hundreds]
|
|
49
86
|
}
|
|
50
87
|
|
|
51
|
-
|
|
52
|
-
|
|
88
|
+
// Tens and ones
|
|
89
|
+
if (tens === 0 && ones === 0) {
|
|
90
|
+
// Nothing more
|
|
91
|
+
} else if (tens === 1) {
|
|
92
|
+
// Teens: 10-19
|
|
93
|
+
result += TEENS[ones]
|
|
94
|
+
} else if (tens >= 2) {
|
|
95
|
+
// 20-99
|
|
96
|
+
result += TENS[tens]
|
|
97
|
+
if (ones > 0) {
|
|
98
|
+
result += ONES[ones]
|
|
99
|
+
}
|
|
100
|
+
} else if (ones > 0) {
|
|
101
|
+
// 1-9 (tens === 0)
|
|
102
|
+
result += ONES[ones]
|
|
53
103
|
}
|
|
54
104
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
105
|
+
// Apply phonetic rules and accent
|
|
106
|
+
return accentuateTre(applyPhoneticRules(result))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Builds segment word with "un" for scale context (millions, billions).
|
|
111
|
+
* Only used during table construction.
|
|
112
|
+
*/
|
|
113
|
+
function buildSegmentWordForScale (n) {
|
|
114
|
+
if (n === 0) return ''
|
|
115
|
+
if (n === 1) return 'un' // "un milione" not "uno milione"
|
|
116
|
+
|
|
117
|
+
const ones = n % 10
|
|
118
|
+
const tens = Math.floor(n / 10) % 10
|
|
119
|
+
const hundreds = Math.floor(n / 100)
|
|
120
|
+
|
|
121
|
+
let result = ''
|
|
122
|
+
|
|
123
|
+
if (hundreds > 0) {
|
|
124
|
+
result = HUNDREDS[hundreds]
|
|
61
125
|
}
|
|
62
126
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
127
|
+
if (tens === 0 && ones === 0) {
|
|
128
|
+
// Nothing more
|
|
129
|
+
} else if (tens === 1) {
|
|
130
|
+
result += TEENS[ones]
|
|
131
|
+
} else if (tens >= 2) {
|
|
132
|
+
result += TENS[tens]
|
|
133
|
+
if (ones > 0) {
|
|
134
|
+
result += ONES[ones]
|
|
68
135
|
}
|
|
69
|
-
|
|
70
|
-
|
|
136
|
+
} else if (ones > 0) {
|
|
137
|
+
// 1-9 with tens === 0
|
|
138
|
+
// "un" only for exactly 1, others normal
|
|
139
|
+
result += ONES[ones]
|
|
71
140
|
}
|
|
72
141
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
142
|
+
return accentuateTre(applyPhoneticRules(result))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Precompute all 1000 segment words (0-999)
|
|
146
|
+
// SEGMENTS[n] gives the Italian word for n within a segment
|
|
147
|
+
const SEGMENTS = new Array(1000)
|
|
148
|
+
for (let i = 0; i < 1000; i++) {
|
|
149
|
+
SEGMENTS[i] = buildSegmentWord(i)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Precompute segment words for scale context (uses "un" for 1)
|
|
153
|
+
const SEGMENTS_SCALE = new Array(1000)
|
|
154
|
+
for (let i = 0; i < 1000; i++) {
|
|
155
|
+
SEGMENTS_SCALE[i] = buildSegmentWordForScale(i)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Precompute thousands words (1-999) + "mila" suffix
|
|
159
|
+
// THOUSANDS[n] gives the word for n thousand (e.g., THOUSANDS[2] = "duemila")
|
|
160
|
+
const THOUSANDS = new Array(1000)
|
|
161
|
+
THOUSANDS[0] = ''
|
|
162
|
+
THOUSANDS[1] = THOUSAND_SINGULAR // "mille"
|
|
163
|
+
for (let i = 2; i < 1000; i++) {
|
|
164
|
+
THOUSANDS[i] = applyPhoneticRules(SEGMENTS[i] + THOUSAND_PLURAL_SUFFIX)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ============================================================================
|
|
168
|
+
// Scale Word Functions
|
|
169
|
+
// ============================================================================
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Gets singular scale word for index.
|
|
173
|
+
* @param {number} scaleIndex - 2=million, 3=billion, etc.
|
|
174
|
+
*/
|
|
175
|
+
function getScaleWordSingular (scaleIndex) {
|
|
176
|
+
if (scaleIndex < 2) return ''
|
|
177
|
+
const prefixIndex = Math.floor((scaleIndex - 2) / 2)
|
|
178
|
+
const isIardo = (scaleIndex - 2) % 2 === 1
|
|
179
|
+
const prefix = SCALE_PREFIXES[prefixIndex]
|
|
180
|
+
if (!prefix) return ''
|
|
181
|
+
return prefix + (isIardo ? 'iliardo' : 'ilione')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Gets plural scale word for index.
|
|
186
|
+
* @param {number} scaleIndex - 2=million, 3=billion, etc.
|
|
187
|
+
*/
|
|
188
|
+
function getScaleWordPlural (scaleIndex) {
|
|
189
|
+
if (scaleIndex < 2) return ''
|
|
190
|
+
const prefixIndex = Math.floor((scaleIndex - 2) / 2)
|
|
191
|
+
const isIardo = (scaleIndex - 2) % 2 === 1
|
|
192
|
+
const prefix = SCALE_PREFIXES[prefixIndex]
|
|
193
|
+
if (!prefix) return ''
|
|
194
|
+
return prefix + (isIardo ? 'iliardi' : 'ilioni')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// Conversion Functions
|
|
199
|
+
// ============================================================================
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Converts a non-negative integer to Italian words.
|
|
203
|
+
*
|
|
204
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
205
|
+
* @returns {string} Italian words
|
|
206
|
+
*/
|
|
207
|
+
function integerToWords (n) {
|
|
208
|
+
if (n === 0n) return ZERO
|
|
79
209
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return
|
|
210
|
+
// Fast path: numbers < 1000 (direct lookup)
|
|
211
|
+
if (n < 1000n) {
|
|
212
|
+
return SEGMENTS[Number(n)]
|
|
83
213
|
}
|
|
84
214
|
|
|
85
|
-
|
|
86
|
-
|
|
215
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
216
|
+
if (n < 1_000_000n) {
|
|
217
|
+
const thousands = Number(n / 1000n)
|
|
218
|
+
const remainder = Number(n % 1000n)
|
|
87
219
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
preDigits = 3
|
|
220
|
+
if (remainder === 0) {
|
|
221
|
+
return THOUSANDS[thousands]
|
|
91
222
|
}
|
|
92
223
|
|
|
93
|
-
|
|
94
|
-
|
|
224
|
+
// Concatenate thousands + remainder
|
|
225
|
+
return THOUSANDS[thousands] + SEGMENTS[remainder]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
229
|
+
return buildLargeNumberWords(n)
|
|
230
|
+
}
|
|
95
231
|
|
|
96
|
-
|
|
97
|
-
|
|
232
|
+
/**
|
|
233
|
+
* Builds words for numbers >= 1,000,000.
|
|
234
|
+
*
|
|
235
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
236
|
+
* @returns {string} Italian words
|
|
237
|
+
*/
|
|
238
|
+
function buildLargeNumberWords (n) {
|
|
239
|
+
const parts = []
|
|
240
|
+
let remaining = n
|
|
98
241
|
|
|
99
|
-
|
|
100
|
-
|
|
242
|
+
// Find the highest scale
|
|
243
|
+
let maxScale = 2
|
|
244
|
+
let testValue = 1_000_000n
|
|
245
|
+
while (testValue * 1000n <= remaining) {
|
|
246
|
+
testValue *= 1000n
|
|
247
|
+
maxScale++
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Process from highest scale down
|
|
251
|
+
for (let scaleIndex = maxScale; scaleIndex >= 0; scaleIndex--) {
|
|
252
|
+
const divisor = 1000n ** BigInt(scaleIndex)
|
|
253
|
+
const segment = remaining / divisor
|
|
254
|
+
remaining = remaining % divisor
|
|
255
|
+
|
|
256
|
+
if (segment === 0n) continue
|
|
257
|
+
|
|
258
|
+
const segNum = Number(segment)
|
|
259
|
+
|
|
260
|
+
if (scaleIndex >= 2) {
|
|
261
|
+
// Millions and above: "segment scaleWord"
|
|
262
|
+
const segmentWords = SEGMENTS_SCALE[segNum]
|
|
263
|
+
const scaleWord = segment === 1n
|
|
264
|
+
? getScaleWordSingular(scaleIndex)
|
|
265
|
+
: getScaleWordPlural(scaleIndex)
|
|
266
|
+
parts.push(segmentWords + ' ' + scaleWord)
|
|
267
|
+
} else if (scaleIndex === 1) {
|
|
268
|
+
// Thousands: use precomputed table
|
|
269
|
+
parts.push(THOUSANDS[segNum])
|
|
101
270
|
} else {
|
|
102
|
-
|
|
103
|
-
|
|
271
|
+
// Units (scaleIndex === 0): just the segment
|
|
272
|
+
parts.push(SEGMENTS[segNum])
|
|
104
273
|
}
|
|
274
|
+
}
|
|
105
275
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
postfix = ''
|
|
109
|
-
} else {
|
|
110
|
-
postfix = this.convertWholePart(Math.trunc(exponent.join('')))
|
|
276
|
+
return joinPartsWithConnector(parts)
|
|
277
|
+
}
|
|
111
278
|
|
|
112
|
-
|
|
279
|
+
/**
|
|
280
|
+
* Joins parts with Italian connector rules.
|
|
281
|
+
* Uses "e" before simple (non-compound) final segment.
|
|
282
|
+
*
|
|
283
|
+
* @param {string[]} parts - Parts to join
|
|
284
|
+
* @returns {string} Joined string
|
|
285
|
+
*/
|
|
286
|
+
function joinPartsWithConnector (parts) {
|
|
287
|
+
const len = parts.length
|
|
288
|
+
if (len === 0) return ''
|
|
289
|
+
if (len === 1) return parts[0]
|
|
290
|
+
|
|
291
|
+
// Check if last part is "simple" (no space = no scale word)
|
|
292
|
+
const lastPart = parts[len - 1]
|
|
293
|
+
if (lastPart.indexOf(' ') === -1) {
|
|
294
|
+
// Join all but last with space, then add "e" connector
|
|
295
|
+
let result = parts[0]
|
|
296
|
+
for (let i = 1; i < len - 1; i++) {
|
|
297
|
+
result += ' ' + parts[i]
|
|
113
298
|
}
|
|
299
|
+
return result + ' e ' + lastPart
|
|
300
|
+
}
|
|
114
301
|
|
|
115
|
-
|
|
302
|
+
// Join with spaces
|
|
303
|
+
let result = parts[0]
|
|
304
|
+
for (let i = 1; i < len; i++) {
|
|
305
|
+
result += ' ' + parts[i]
|
|
116
306
|
}
|
|
307
|
+
return result
|
|
308
|
+
}
|
|
117
309
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
words = this.hundredsToCardinal(Number(number))
|
|
127
|
-
} else if (number < 1_000_000) {
|
|
128
|
-
words = this.thousandsToCardinal(Number(number))
|
|
129
|
-
} else {
|
|
130
|
-
words = this.bigNumberToCardinal(number)
|
|
131
|
-
}
|
|
310
|
+
/**
|
|
311
|
+
* Converts decimal digits to Italian words.
|
|
312
|
+
*
|
|
313
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
314
|
+
* @returns {string} Italian words for decimal part
|
|
315
|
+
*/
|
|
316
|
+
function decimalPartToWords (decimalPart) {
|
|
317
|
+
let result = ''
|
|
132
318
|
|
|
133
|
-
|
|
319
|
+
// Handle leading zeros
|
|
320
|
+
let i = 0
|
|
321
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
322
|
+
if (result) result += ' '
|
|
323
|
+
result += ZERO
|
|
324
|
+
i++
|
|
134
325
|
}
|
|
326
|
+
|
|
327
|
+
// Convert remainder as a single number
|
|
328
|
+
const remainder = decimalPart.slice(i)
|
|
329
|
+
if (remainder) {
|
|
330
|
+
if (result) result += ' '
|
|
331
|
+
result += integerToWords(BigInt(remainder))
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return result
|
|
135
335
|
}
|
|
136
336
|
|
|
137
337
|
/**
|
|
138
|
-
* Converts a
|
|
338
|
+
* Converts a numeric value to Italian words.
|
|
139
339
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* @
|
|
144
|
-
* @
|
|
340
|
+
* This is the main public API. It accepts any valid numeric input
|
|
341
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
342
|
+
*
|
|
343
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
344
|
+
* @returns {string} The number in Italian words
|
|
345
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
346
|
+
* @throws {Error} If value is not a valid number format
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* toWords(28) // 'ventotto'
|
|
350
|
+
* toWords(23) // 'ventitré'
|
|
351
|
+
* toWords(1000) // 'mille'
|
|
352
|
+
* toWords(2000) // 'duemila'
|
|
353
|
+
* toWords(1000000) // 'un milione'
|
|
145
354
|
*/
|
|
146
|
-
|
|
147
|
-
|
|
355
|
+
function toWords (value) {
|
|
356
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
357
|
+
|
|
358
|
+
let result = ''
|
|
359
|
+
|
|
360
|
+
if (isNegative) {
|
|
361
|
+
result = NEGATIVE + ' '
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
result += integerToWords(integerPart)
|
|
365
|
+
|
|
366
|
+
if (decimalPart) {
|
|
367
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return result
|
|
148
371
|
}
|
|
372
|
+
|
|
373
|
+
// ============================================================================
|
|
374
|
+
// Public API
|
|
375
|
+
// ============================================================================
|
|
376
|
+
|
|
377
|
+
export { toWords }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a numeric value to Japanese words.
|
|
3
|
+
*
|
|
4
|
+
* This is the main public API. It accepts any valid numeric input
|
|
5
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
6
|
+
*
|
|
7
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
8
|
+
* @returns {string} The number in Japanese kanji words
|
|
9
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
10
|
+
* @throws {Error} If value is not a valid number format
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* toWords(42) // '四十二'
|
|
14
|
+
* toWords(10000) // '一万'
|
|
15
|
+
* toWords(100000000) // '一億'
|
|
16
|
+
*/
|
|
17
|
+
export function toWords(value: number | string | bigint): string;
|