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/en.js
CHANGED
|
@@ -1,123 +1,256 @@
|
|
|
1
|
-
import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* English language converter
|
|
2
|
+
* English language converter - Functional Implementation v2
|
|
3
|
+
*
|
|
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.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
* - Decimal numbers (word "point" between whole and fractional parts)
|
|
9
|
-
* - Numbers up to octillions
|
|
7
|
+
* Key optimization: Precompute all segment values (0-999) at module load.
|
|
8
|
+
* This eliminates all per-call string manipulation for segment conversion.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
10
|
+
* English-specific rules (handled in precomputation):
|
|
11
|
+
* - "and" after hundreds: "one hundred and twenty-three"
|
|
12
|
+
* - Hyphenated tens-ones: "twenty-one", "forty-two"
|
|
13
|
+
* - "and" before final segment when following scale word
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Vocabulary (module-level constants)
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
const ONES = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
|
|
23
|
+
const TEENS = ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen']
|
|
24
|
+
const TENS = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']
|
|
25
|
+
|
|
26
|
+
const SCALES = ['thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion', 'sextillion', 'septillion', 'octillion']
|
|
27
|
+
|
|
28
|
+
const HUNDRED = 'hundred'
|
|
29
|
+
const ZERO = 'zero'
|
|
30
|
+
const NEGATIVE = 'minus'
|
|
31
|
+
const DECIMAL_SEP = 'point'
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Precomputed Lookup Tables (built once at module load)
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Builds segment word for 0-999.
|
|
39
|
+
* Returns object with word and whether it contains "hundred" (for "and" logic).
|
|
15
40
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
[
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
[
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
[17n, 'seventeen'],
|
|
42
|
-
[16n, 'sixteen'],
|
|
43
|
-
[15n, 'fifteen'],
|
|
44
|
-
[14n, 'fourteen'],
|
|
45
|
-
[13n, 'thirteen'],
|
|
46
|
-
[12n, 'twelve'],
|
|
47
|
-
[11n, 'eleven'],
|
|
48
|
-
[10n, 'ten'],
|
|
49
|
-
[9n, 'nine'],
|
|
50
|
-
[8n, 'eight'],
|
|
51
|
-
[7n, 'seven'],
|
|
52
|
-
[6n, 'six'],
|
|
53
|
-
[5n, 'five'],
|
|
54
|
-
[4n, 'four'],
|
|
55
|
-
[3n, 'three'],
|
|
56
|
-
[2n, 'two'],
|
|
57
|
-
[1n, 'one'],
|
|
58
|
-
[0n, 'zero']
|
|
59
|
-
]
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Merges two adjacent word-number pairs according to English grammar rules.
|
|
63
|
-
*
|
|
64
|
-
* English-specific rules:
|
|
65
|
-
* - Implicit "one": `mergeScales({ 'one': 1n }, { 'hundred': 100n })` → `{ 'one hundred': 100n }`
|
|
66
|
-
* - Hyphenated compounds: `mergeScales({ 'twenty': 20n }, { 'three': 3n })` → `{ 'twenty-three': 23n }`
|
|
67
|
-
* - "and" after hundreds: `mergeScales({ 'one hundred': 100n }, { 'one': 1n })` → `{ 'one hundred and one': 101n }`
|
|
68
|
-
* - Multiplication: `mergeScales({ 'one': 1n }, { 'thousand': 1000n })` → `{ 'one thousand': 1000n }`
|
|
69
|
-
*
|
|
70
|
-
* @param {Object} leftPair Left word-set as `{ word: BigInt }`.
|
|
71
|
-
* @param {Object} rightPair Right word-set as `{ word: BigInt }`.
|
|
72
|
-
* @returns {Object} Merged pair with combined word and resulting numeric value.
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* mergeScales({ 'one': 1n }, { 'hundred': 100n }); // { 'one hundred': 100n }
|
|
76
|
-
* mergeScales({ 'twenty': 20n }, { 'three': 3n }); // { 'twenty-three': 23n }
|
|
77
|
-
*/
|
|
78
|
-
mergeScales (leftPair, rightPair) {
|
|
79
|
-
const leftWord = Object.keys(leftPair)[0]
|
|
80
|
-
const leftNumber = Object.values(leftPair)[0]
|
|
81
|
-
const rightWord = Object.keys(rightPair)[0]
|
|
82
|
-
const rightNumber = Object.values(rightPair)[0]
|
|
83
|
-
|
|
84
|
-
// Rule 1: Implicit "one" - omit when multiplying ("one hundred" → "hundred")
|
|
85
|
-
if (leftNumber === 1n && rightNumber < 100n) {
|
|
86
|
-
return { [rightWord]: rightNumber }
|
|
41
|
+
function buildSegment (n) {
|
|
42
|
+
if (n === 0) return { word: '', hasHundred: false }
|
|
43
|
+
|
|
44
|
+
const ones = n % 10
|
|
45
|
+
const tens = Math.floor(n / 10) % 10
|
|
46
|
+
const hundreds = Math.floor(n / 100)
|
|
47
|
+
|
|
48
|
+
let result = ''
|
|
49
|
+
let hasHundred = false
|
|
50
|
+
|
|
51
|
+
// Hundreds
|
|
52
|
+
if (hundreds > 0) {
|
|
53
|
+
result = ONES[hundreds] + ' ' + HUNDRED
|
|
54
|
+
hasHundred = true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Tens and ones
|
|
58
|
+
let tensOnes = ''
|
|
59
|
+
if (tens === 1) {
|
|
60
|
+
tensOnes = TEENS[ones]
|
|
61
|
+
} else if (tens >= 2) {
|
|
62
|
+
if (ones > 0) {
|
|
63
|
+
tensOnes = TENS[tens] + '-' + ONES[ones]
|
|
64
|
+
} else {
|
|
65
|
+
tensOnes = TENS[tens]
|
|
87
66
|
}
|
|
67
|
+
} else if (ones > 0) {
|
|
68
|
+
tensOnes = ONES[ones]
|
|
69
|
+
}
|
|
88
70
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
71
|
+
// Combine with "and" after hundreds
|
|
72
|
+
if (result && tensOnes) {
|
|
73
|
+
return { word: result + ' and ' + tensOnes, hasHundred: true }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { word: result || tensOnes, hasHundred }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Precompute all 1000 segment words (0-999)
|
|
80
|
+
const SEGMENTS = new Array(1000)
|
|
81
|
+
const SEGMENTS_HAS_HUNDRED = new Array(1000)
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < 1000; i++) {
|
|
84
|
+
const result = buildSegment(i)
|
|
85
|
+
SEGMENTS[i] = result.word
|
|
86
|
+
SEGMENTS_HAS_HUNDRED[i] = result.hasHundred
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// Conversion Functions
|
|
91
|
+
// ============================================================================
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Converts a non-negative integer to English words.
|
|
95
|
+
*
|
|
96
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
97
|
+
* @returns {string} English words
|
|
98
|
+
*/
|
|
99
|
+
function integerToWords (n) {
|
|
100
|
+
if (n === 0n) return ZERO
|
|
101
|
+
|
|
102
|
+
// Fast path: numbers < 1000 (direct lookup)
|
|
103
|
+
if (n < 1000n) {
|
|
104
|
+
return SEGMENTS[Number(n)]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
108
|
+
if (n < 1_000_000n) {
|
|
109
|
+
const thousands = Number(n / 1000n)
|
|
110
|
+
const remainder = Number(n % 1000n)
|
|
111
|
+
|
|
112
|
+
let result = SEGMENTS[thousands] + ' ' + SCALES[0]
|
|
113
|
+
|
|
114
|
+
if (remainder > 0) {
|
|
115
|
+
const remainderWord = SEGMENTS[remainder]
|
|
116
|
+
// Insert "and" if remainder doesn't have hundred
|
|
117
|
+
if (!SEGMENTS_HAS_HUNDRED[remainder]) {
|
|
118
|
+
result += ' and ' + remainderWord
|
|
119
|
+
} else {
|
|
120
|
+
result += ' ' + remainderWord
|
|
121
|
+
}
|
|
92
122
|
}
|
|
93
123
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
124
|
+
return result
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
128
|
+
return buildLargeNumberWords(n)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Builds words for numbers >= 1,000,000.
|
|
133
|
+
* Uses BigInt division for faster segment extraction.
|
|
134
|
+
*
|
|
135
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
136
|
+
* @returns {string} English words
|
|
137
|
+
*/
|
|
138
|
+
function buildLargeNumberWords (n) {
|
|
139
|
+
// Extract segments using BigInt division (faster than string slicing)
|
|
140
|
+
// Segments are stored least-significant first (index 0 = ones, 1 = thousands, etc.)
|
|
141
|
+
const segments = []
|
|
142
|
+
let temp = n
|
|
143
|
+
while (temp > 0n) {
|
|
144
|
+
segments.push(Number(temp % 1000n))
|
|
145
|
+
temp = temp / 1000n
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Find the first (smallest index) non-zero segment - this is processed last
|
|
149
|
+
let firstNonZeroIdx = -1
|
|
150
|
+
for (let i = 0; i < segments.length; i++) {
|
|
151
|
+
if (segments[i] !== 0) {
|
|
152
|
+
firstNonZeroIdx = i
|
|
153
|
+
break
|
|
97
154
|
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Build result string directly (avoids intermediate object allocations)
|
|
158
|
+
// Process from most-significant (end) to least-significant (start)
|
|
159
|
+
let result = ''
|
|
160
|
+
let prevWasScale = false
|
|
98
161
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
162
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
163
|
+
const segment = segments[i]
|
|
164
|
+
if (segment === 0) continue
|
|
165
|
+
|
|
166
|
+
const segmentWord = SEGMENTS[segment]
|
|
167
|
+
const hasHundred = SEGMENTS_HAS_HUNDRED[segment]
|
|
168
|
+
const isLastSegment = (i === firstNonZeroIdx)
|
|
169
|
+
|
|
170
|
+
// Add "and" only before FINAL segment if it follows scale and doesn't have hundred
|
|
171
|
+
if (result && isLastSegment && prevWasScale && !hasHundred) {
|
|
172
|
+
result += ' and'
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Add segment word
|
|
176
|
+
if (result) result += ' '
|
|
177
|
+
result += segmentWord
|
|
178
|
+
|
|
179
|
+
// Add scale word (i=0 is units, i=1 is thousands, etc.)
|
|
180
|
+
if (i > 0) {
|
|
181
|
+
result += ' ' + SCALES[i - 1]
|
|
182
|
+
prevWasScale = true
|
|
183
|
+
} else {
|
|
184
|
+
prevWasScale = false
|
|
102
185
|
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result
|
|
189
|
+
}
|
|
103
190
|
|
|
104
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Converts decimal digits to English words.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
195
|
+
* @returns {string} English words for decimal part
|
|
196
|
+
*/
|
|
197
|
+
function decimalPartToWords (decimalPart) {
|
|
198
|
+
let result = ''
|
|
199
|
+
|
|
200
|
+
// Handle leading zeros
|
|
201
|
+
let i = 0
|
|
202
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
203
|
+
if (result) result += ' '
|
|
204
|
+
result += ZERO
|
|
205
|
+
i++
|
|
105
206
|
}
|
|
207
|
+
|
|
208
|
+
// Convert remainder as a single number
|
|
209
|
+
const remainder = decimalPart.slice(i)
|
|
210
|
+
if (remainder) {
|
|
211
|
+
if (result) result += ' '
|
|
212
|
+
result += integerToWords(BigInt(remainder))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return result
|
|
106
216
|
}
|
|
107
217
|
|
|
108
218
|
/**
|
|
109
|
-
* Converts a
|
|
219
|
+
* Converts a numeric value to English words.
|
|
110
220
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* @
|
|
115
|
-
* @
|
|
221
|
+
* This is the main public API. It accepts any valid numeric input
|
|
222
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
223
|
+
*
|
|
224
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
225
|
+
* @returns {string} The number in English words
|
|
226
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
227
|
+
* @throws {Error} If value is not a valid number format
|
|
116
228
|
*
|
|
117
229
|
* @example
|
|
118
|
-
*
|
|
119
|
-
*
|
|
230
|
+
* toWords(42) // 'forty-two'
|
|
231
|
+
* toWords(-3.14) // 'minus three point one four'
|
|
232
|
+
* toWords('1000000') // 'one million'
|
|
120
233
|
*/
|
|
121
|
-
|
|
122
|
-
|
|
234
|
+
function toWords (value) {
|
|
235
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
236
|
+
|
|
237
|
+
let result = ''
|
|
238
|
+
|
|
239
|
+
if (isNegative) {
|
|
240
|
+
result = NEGATIVE + ' '
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
result += integerToWords(integerPart)
|
|
244
|
+
|
|
245
|
+
if (decimalPart) {
|
|
246
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return result
|
|
123
250
|
}
|
|
251
|
+
|
|
252
|
+
// ============================================================================
|
|
253
|
+
// Public API
|
|
254
|
+
// ============================================================================
|
|
255
|
+
|
|
256
|
+
export { toWords }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a numeric value to Spanish 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
|
+
* @param {Object} [options] - Optional configuration
|
|
9
|
+
* @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
|
|
10
|
+
* @returns {string} The number in Spanish words
|
|
11
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
12
|
+
* @throws {Error} If value is not a valid number format
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* toWords(21) // 'veintiuno'
|
|
16
|
+
* toWords(21, {gender: 'feminine'}) // 'veintiuna'
|
|
17
|
+
* toWords(1000000) // 'un millón'
|
|
18
|
+
*/
|
|
19
|
+
export function toWords(value: number | string | bigint, options?: {
|
|
20
|
+
gender?: "masculine" | "feminine" | undefined;
|
|
21
|
+
}): string;
|