n2words 3.0.0 → 3.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 +15 -0
- package/dist/languages/am-Latn.js +2 -2
- package/dist/languages/am-Latn.js.map +1 -1
- package/dist/languages/am.js +2 -2
- package/dist/languages/am.js.map +1 -1
- package/dist/languages/ar.js +1 -1
- package/dist/languages/az.js +2 -2
- package/dist/languages/az.js.map +1 -1
- package/dist/languages/bn.js +2 -2
- package/dist/languages/bn.js.map +1 -1
- package/dist/languages/cs.js +2 -2
- package/dist/languages/cs.js.map +1 -1
- package/dist/languages/da.js +2 -2
- package/dist/languages/da.js.map +1 -1
- package/dist/languages/de.js +2 -2
- package/dist/languages/de.js.map +1 -1
- package/dist/languages/el.js +2 -2
- package/dist/languages/el.js.map +1 -1
- package/dist/languages/en.js +2 -2
- package/dist/languages/en.js.map +1 -1
- package/dist/languages/es.js +2 -2
- package/dist/languages/es.js.map +1 -1
- package/dist/languages/fa.js +1 -1
- package/dist/languages/fi.js +2 -2
- package/dist/languages/fi.js.map +1 -1
- package/dist/languages/fil.js +2 -2
- package/dist/languages/fil.js.map +1 -1
- package/dist/languages/fr-BE.js +2 -2
- package/dist/languages/fr-BE.js.map +1 -1
- package/dist/languages/fr.js +2 -2
- package/dist/languages/fr.js.map +1 -1
- package/dist/languages/gu.js +2 -2
- package/dist/languages/gu.js.map +1 -1
- package/dist/languages/ha.js +2 -2
- package/dist/languages/ha.js.map +1 -1
- package/dist/languages/hbo.js +2 -2
- package/dist/languages/hbo.js.map +1 -1
- package/dist/languages/he.js +2 -2
- package/dist/languages/he.js.map +1 -1
- package/dist/languages/hi.js +2 -2
- package/dist/languages/hi.js.map +1 -1
- package/dist/languages/hr.js +2 -2
- package/dist/languages/hr.js.map +1 -1
- package/dist/languages/hu.js +1 -1
- package/dist/languages/id.js +2 -2
- package/dist/languages/id.js.map +1 -1
- package/dist/languages/it.js +2 -2
- package/dist/languages/it.js.map +1 -1
- package/dist/languages/ja.js +2 -2
- package/dist/languages/ja.js.map +1 -1
- package/dist/languages/ka.js +3 -0
- package/dist/languages/ka.js.map +1 -0
- package/dist/languages/kn.js +2 -2
- package/dist/languages/kn.js.map +1 -1
- package/dist/languages/ko.js +2 -2
- package/dist/languages/ko.js.map +1 -1
- package/dist/languages/lt.js +2 -2
- package/dist/languages/lt.js.map +1 -1
- package/dist/languages/lv.js +2 -2
- package/dist/languages/lv.js.map +1 -1
- package/dist/languages/mr.js +2 -2
- package/dist/languages/mr.js.map +1 -1
- package/dist/languages/ms.js +2 -2
- package/dist/languages/ms.js.map +1 -1
- package/dist/languages/nb.js +2 -2
- package/dist/languages/nb.js.map +1 -1
- package/dist/languages/nl.js +2 -2
- package/dist/languages/nl.js.map +1 -1
- package/dist/languages/pa.js +2 -2
- package/dist/languages/pa.js.map +1 -1
- package/dist/languages/pl.js +2 -2
- package/dist/languages/pl.js.map +1 -1
- package/dist/languages/pt.js +2 -2
- package/dist/languages/pt.js.map +1 -1
- package/dist/languages/ro.js +1 -1
- package/dist/languages/ru.js +2 -2
- package/dist/languages/ru.js.map +1 -1
- package/dist/languages/sr-Cyrl.js +2 -2
- package/dist/languages/sr-Cyrl.js.map +1 -1
- package/dist/languages/sr-Latn.js +2 -2
- package/dist/languages/sr-Latn.js.map +1 -1
- package/dist/languages/sv.js +2 -2
- package/dist/languages/sv.js.map +1 -1
- package/dist/languages/sw.js +1 -1
- package/dist/languages/ta.js +2 -2
- package/dist/languages/ta.js.map +1 -1
- package/dist/languages/te.js +2 -2
- package/dist/languages/te.js.map +1 -1
- package/dist/languages/th.js +1 -1
- package/dist/languages/tr.js +2 -2
- package/dist/languages/tr.js.map +1 -1
- package/dist/languages/uk.js +2 -2
- package/dist/languages/uk.js.map +1 -1
- package/dist/languages/ur.js +2 -2
- package/dist/languages/ur.js.map +1 -1
- package/dist/languages/vi.js +2 -2
- package/dist/languages/vi.js.map +1 -1
- package/dist/languages/yo.js +3 -0
- package/dist/languages/yo.js.map +1 -0
- package/dist/languages/zh-Hans.js +1 -1
- package/dist/languages/zh-Hant.js +1 -1
- package/dist/n2words.js +2 -2
- package/dist/n2words.js.map +1 -1
- package/lib/languages/am-Latn.js +4 -9
- package/lib/languages/am.js +4 -9
- package/lib/languages/az.js +4 -9
- package/lib/languages/bn.js +51 -30
- package/lib/languages/cs.js +9 -26
- package/lib/languages/da.js +6 -13
- package/lib/languages/de.js +21 -33
- package/lib/languages/el.js +6 -13
- package/lib/languages/en.js +44 -58
- package/lib/languages/es.js +10 -30
- package/lib/languages/fi.js +6 -13
- package/lib/languages/fil.js +6 -17
- package/lib/languages/fr-BE.js +17 -26
- package/lib/languages/fr.js +18 -31
- package/lib/languages/gu.js +43 -30
- package/lib/languages/ha.js +4 -9
- package/lib/languages/hbo.js +7 -25
- package/lib/languages/he.js +7 -18
- package/lib/languages/hi.js +55 -30
- package/lib/languages/hr.js +61 -55
- package/lib/languages/id.js +8 -13
- package/lib/languages/it.js +64 -99
- package/lib/languages/ja.js +8 -20
- package/lib/languages/ka.d.ts +17 -0
- package/lib/languages/ka.js +291 -0
- package/lib/languages/kn.js +43 -30
- package/lib/languages/ko.js +6 -13
- package/lib/languages/lt.js +6 -15
- package/lib/languages/lv.js +6 -15
- package/lib/languages/mr.js +43 -30
- package/lib/languages/ms.js +8 -13
- package/lib/languages/nb.js +13 -23
- package/lib/languages/nl.js +11 -29
- package/lib/languages/pa.js +32 -37
- package/lib/languages/pl.js +7 -20
- package/lib/languages/pt.js +17 -31
- package/lib/languages/ru.js +64 -59
- package/lib/languages/sr-Cyrl.js +58 -52
- package/lib/languages/sr-Latn.js +58 -52
- package/lib/languages/sv.js +14 -24
- package/lib/languages/ta.js +55 -42
- package/lib/languages/te.js +33 -41
- package/lib/languages/tr.js +7 -18
- package/lib/languages/uk.js +61 -55
- package/lib/languages/ur.js +32 -37
- package/lib/languages/vi.js +23 -43
- package/lib/languages/yo.d.ts +7 -0
- package/lib/languages/yo.js +303 -0
- package/lib/n2words.d.ts +3 -1
- package/lib/n2words.js +4 -0
- package/package.json +1 -1
package/lib/languages/en.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* English language converter - Functional Implementation
|
|
2
|
+
* English language converter - Functional Implementation
|
|
3
3
|
*
|
|
4
|
-
* A performance-optimized number-to-words converter using precomputed lookup tables.
|
|
5
4
|
* Self-contained module with its own input validation, ready for subpath exports.
|
|
6
5
|
*
|
|
7
|
-
* Key
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* English-specific rules (handled in precomputation):
|
|
6
|
+
* Key features:
|
|
7
|
+
* - Western numbering system (thousand, million, billion)
|
|
11
8
|
* - "and" after hundreds: "one hundred and twenty-three"
|
|
12
9
|
* - Hyphenated tens-ones: "twenty-one", "forty-two"
|
|
13
10
|
* - "and" before final segment when following scale word
|
|
11
|
+
* - BigInt modulo for efficient segment extraction
|
|
14
12
|
*/
|
|
15
13
|
|
|
16
14
|
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
@@ -31,59 +29,53 @@ const NEGATIVE = 'minus'
|
|
|
31
29
|
const DECIMAL_SEP = 'point'
|
|
32
30
|
|
|
33
31
|
// ============================================================================
|
|
34
|
-
//
|
|
32
|
+
// Segment Building
|
|
35
33
|
// ============================================================================
|
|
36
34
|
|
|
35
|
+
// Reusable result object to avoid allocation per call
|
|
36
|
+
const segmentResult = { word: '', hasHundred: false }
|
|
37
|
+
|
|
37
38
|
/**
|
|
38
|
-
* Builds
|
|
39
|
-
*
|
|
39
|
+
* Builds words for a 0-999 segment.
|
|
40
|
+
*
|
|
41
|
+
* @param {number} n - Number 0-999
|
|
42
|
+
* @returns {{ word: string, hasHundred: boolean }}
|
|
40
43
|
*/
|
|
41
44
|
function buildSegment (n) {
|
|
42
|
-
if (n === 0)
|
|
45
|
+
if (n === 0) {
|
|
46
|
+
segmentResult.word = ''
|
|
47
|
+
segmentResult.hasHundred = false
|
|
48
|
+
return segmentResult
|
|
49
|
+
}
|
|
43
50
|
|
|
44
51
|
const ones = n % 10
|
|
45
|
-
const tens = Math.
|
|
46
|
-
const hundreds = Math.
|
|
47
|
-
|
|
48
|
-
let result = ''
|
|
49
|
-
let hasHundred = false
|
|
50
|
-
|
|
51
|
-
// Hundreds
|
|
52
|
-
if (hundreds > 0) {
|
|
53
|
-
result = ONES[hundreds] + ' ' + HUNDRED
|
|
54
|
-
hasHundred = true
|
|
55
|
-
}
|
|
52
|
+
const tens = Math.trunc(n / 10) % 10
|
|
53
|
+
const hundreds = Math.trunc(n / 100)
|
|
56
54
|
|
|
57
|
-
//
|
|
55
|
+
// Build tens-ones part first
|
|
58
56
|
let tensOnes = ''
|
|
59
57
|
if (tens === 1) {
|
|
60
58
|
tensOnes = TEENS[ones]
|
|
61
59
|
} else if (tens >= 2) {
|
|
62
|
-
|
|
63
|
-
tensOnes = TENS[tens] + '-' + ONES[ones]
|
|
64
|
-
} else {
|
|
65
|
-
tensOnes = TENS[tens]
|
|
66
|
-
}
|
|
60
|
+
tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
|
|
67
61
|
} else if (ones > 0) {
|
|
68
62
|
tensOnes = ONES[ones]
|
|
69
63
|
}
|
|
70
64
|
|
|
71
|
-
//
|
|
72
|
-
if (
|
|
73
|
-
|
|
65
|
+
// Hundreds place
|
|
66
|
+
if (hundreds > 0) {
|
|
67
|
+
if (tensOnes) {
|
|
68
|
+
segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + ' and ' + tensOnes
|
|
69
|
+
} else {
|
|
70
|
+
segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
|
|
71
|
+
}
|
|
72
|
+
segmentResult.hasHundred = true
|
|
73
|
+
} else {
|
|
74
|
+
segmentResult.word = tensOnes
|
|
75
|
+
segmentResult.hasHundred = false
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
return
|
|
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
|
|
78
|
+
return segmentResult
|
|
87
79
|
}
|
|
88
80
|
|
|
89
81
|
// ============================================================================
|
|
@@ -99,26 +91,22 @@ for (let i = 0; i < 1000; i++) {
|
|
|
99
91
|
function integerToWords (n) {
|
|
100
92
|
if (n === 0n) return ZERO
|
|
101
93
|
|
|
102
|
-
// Fast path: numbers < 1000
|
|
94
|
+
// Fast path: numbers < 1000
|
|
103
95
|
if (n < 1000n) {
|
|
104
|
-
return
|
|
96
|
+
return buildSegment(Number(n)).word
|
|
105
97
|
}
|
|
106
98
|
|
|
107
|
-
// Fast path: numbers < 1,000,000
|
|
99
|
+
// Fast path: numbers < 1,000,000
|
|
108
100
|
if (n < 1_000_000n) {
|
|
109
101
|
const thousands = Number(n / 1000n)
|
|
110
102
|
const remainder = Number(n % 1000n)
|
|
111
103
|
|
|
112
|
-
|
|
104
|
+
const { word: thousandsWord } = buildSegment(thousands)
|
|
105
|
+
let result = thousandsWord + ' ' + SCALES[0]
|
|
113
106
|
|
|
114
107
|
if (remainder > 0) {
|
|
115
|
-
const remainderWord =
|
|
116
|
-
|
|
117
|
-
if (!SEGMENTS_HAS_HUNDRED[remainder]) {
|
|
118
|
-
result += ' and ' + remainderWord
|
|
119
|
-
} else {
|
|
120
|
-
result += ' ' + remainderWord
|
|
121
|
-
}
|
|
108
|
+
const { word: remainderWord, hasHundred } = buildSegment(remainder)
|
|
109
|
+
result += hasHundred ? ' ' + remainderWord : ' and ' + remainderWord
|
|
122
110
|
}
|
|
123
111
|
|
|
124
112
|
return result
|
|
@@ -136,7 +124,7 @@ function integerToWords (n) {
|
|
|
136
124
|
* @returns {string} English words
|
|
137
125
|
*/
|
|
138
126
|
function buildLargeNumberWords (n) {
|
|
139
|
-
// Extract segments using BigInt division
|
|
127
|
+
// Extract segments using BigInt division
|
|
140
128
|
// Segments are stored least-significant first (index 0 = ones, 1 = thousands, etc.)
|
|
141
129
|
const segments = []
|
|
142
130
|
let temp = n
|
|
@@ -154,8 +142,7 @@ function buildLargeNumberWords (n) {
|
|
|
154
142
|
}
|
|
155
143
|
}
|
|
156
144
|
|
|
157
|
-
// Build result string
|
|
158
|
-
// Process from most-significant (end) to least-significant (start)
|
|
145
|
+
// Build result string (process from most-significant to least)
|
|
159
146
|
let result = ''
|
|
160
147
|
let prevWasScale = false
|
|
161
148
|
|
|
@@ -163,8 +150,7 @@ function buildLargeNumberWords (n) {
|
|
|
163
150
|
const segment = segments[i]
|
|
164
151
|
if (segment === 0) continue
|
|
165
152
|
|
|
166
|
-
const
|
|
167
|
-
const hasHundred = SEGMENTS_HAS_HUNDRED[segment]
|
|
153
|
+
const { word, hasHundred } = buildSegment(segment)
|
|
168
154
|
const isLastSegment = (i === firstNonZeroIdx)
|
|
169
155
|
|
|
170
156
|
// Add "and" only before FINAL segment if it follows scale and doesn't have hundred
|
|
@@ -174,7 +160,7 @@ function buildLargeNumberWords (n) {
|
|
|
174
160
|
|
|
175
161
|
// Add segment word
|
|
176
162
|
if (result) result += ' '
|
|
177
|
-
result +=
|
|
163
|
+
result += word
|
|
178
164
|
|
|
179
165
|
// Add scale word (i=0 is units, i=1 is thousands, etc.)
|
|
180
166
|
if (i > 0) {
|
package/lib/languages/es.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Spanish language converter - Functional Implementation
|
|
3
3
|
*
|
|
4
|
-
* A performance-optimized number-to-words converter using precomputed lookup tables.
|
|
5
4
|
* Self-contained module with its own input validation, ready for subpath exports.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
* This eliminates all per-call string manipulation for segment conversion.
|
|
9
|
-
*
|
|
10
|
-
* Spanish-specific rules (handled in precomputation):
|
|
6
|
+
* Spanish-specific rules:
|
|
11
7
|
* - Gender agreement: uno/una, veintiuno/veintiuna, hundreds
|
|
12
8
|
* - Special twenties: veinte, veintiuno, veintidós, ... veintinueve
|
|
13
9
|
* - "y" conjunction: treinta y uno (only 30-99 with ones)
|
|
@@ -49,7 +45,7 @@ const NEGATIVE = 'menos'
|
|
|
49
45
|
const DECIMAL_SEP = 'punto'
|
|
50
46
|
|
|
51
47
|
// ============================================================================
|
|
52
|
-
//
|
|
48
|
+
// Segment Building
|
|
53
49
|
// ============================================================================
|
|
54
50
|
|
|
55
51
|
/**
|
|
@@ -104,18 +100,6 @@ function buildSegment (n, feminine) {
|
|
|
104
100
|
return parts.join(' ')
|
|
105
101
|
}
|
|
106
102
|
|
|
107
|
-
// Precompute all 1000 segment words (0-999) for masculine
|
|
108
|
-
const SEGMENTS_MASC = new Array(1000)
|
|
109
|
-
for (let i = 0; i < 1000; i++) {
|
|
110
|
-
SEGMENTS_MASC[i] = buildSegment(i, false)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Precompute all 1000 segment words (0-999) for feminine
|
|
114
|
-
const SEGMENTS_FEM = new Array(1000)
|
|
115
|
-
for (let i = 0; i < 1000; i++) {
|
|
116
|
-
SEGMENTS_FEM[i] = buildSegment(i, true)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
103
|
// ============================================================================
|
|
120
104
|
// Helper Functions
|
|
121
105
|
// ============================================================================
|
|
@@ -160,11 +144,9 @@ function getScaleWord (scaleIndex, segment) {
|
|
|
160
144
|
function integerToWords (n, feminine) {
|
|
161
145
|
if (n === 0n) return ZERO
|
|
162
146
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// Fast path: numbers < 1000 (direct lookup)
|
|
147
|
+
// Fast path: numbers < 1000
|
|
166
148
|
if (n < 1000n) {
|
|
167
|
-
return
|
|
149
|
+
return buildSegment(Number(n), feminine)
|
|
168
150
|
}
|
|
169
151
|
|
|
170
152
|
// Fast path: numbers < 1,000,000 (thousands)
|
|
@@ -178,7 +160,7 @@ function integerToWords (n, feminine) {
|
|
|
178
160
|
result = THOUSAND
|
|
179
161
|
} else {
|
|
180
162
|
// Use masculine for thousands segment, but check for "uno" → omit before mil
|
|
181
|
-
const thousandsWord =
|
|
163
|
+
const thousandsWord = buildSegment(thousands, false)
|
|
182
164
|
// "uno mil" → "mil" (handled in joinSegments equivalent)
|
|
183
165
|
if (thousandsWord === 'uno' || thousandsWord === 'una') {
|
|
184
166
|
result = THOUSAND
|
|
@@ -188,7 +170,7 @@ function integerToWords (n, feminine) {
|
|
|
188
170
|
}
|
|
189
171
|
|
|
190
172
|
if (remainder > 0) {
|
|
191
|
-
result += ' ' +
|
|
173
|
+
result += ' ' + buildSegment(remainder, feminine)
|
|
192
174
|
}
|
|
193
175
|
|
|
194
176
|
return result
|
|
@@ -216,8 +198,6 @@ function buildLargeNumberWords (n, feminine) {
|
|
|
216
198
|
temp = temp / 1000n
|
|
217
199
|
}
|
|
218
200
|
|
|
219
|
-
const segments = feminine ? SEGMENTS_FEM : SEGMENTS_MASC
|
|
220
|
-
|
|
221
201
|
// Build result string directly
|
|
222
202
|
let result = ''
|
|
223
203
|
|
|
@@ -231,13 +211,13 @@ function buildLargeNumberWords (n, feminine) {
|
|
|
231
211
|
|
|
232
212
|
if (i === 0) {
|
|
233
213
|
// Units segment
|
|
234
|
-
result +=
|
|
214
|
+
result += buildSegment(Number(segment), feminine)
|
|
235
215
|
} else if (i === 1) {
|
|
236
216
|
// Thousands: omit "uno" before mil
|
|
237
217
|
if (segment === 1n) {
|
|
238
218
|
result += THOUSAND
|
|
239
219
|
} else {
|
|
240
|
-
result +=
|
|
220
|
+
result += buildSegment(Number(segment), false) + ' ' + scaleWord
|
|
241
221
|
}
|
|
242
222
|
} else if (i % 2 === 1) {
|
|
243
223
|
// Odd scale indices (3, 5, 7): "mil millones", "mil billones", etc.
|
|
@@ -245,7 +225,7 @@ function buildLargeNumberWords (n, feminine) {
|
|
|
245
225
|
if (segment === 1n) {
|
|
246
226
|
result += scaleWord
|
|
247
227
|
} else {
|
|
248
|
-
result +=
|
|
228
|
+
result += buildSegment(Number(segment), false) + ' ' + scaleWord
|
|
249
229
|
}
|
|
250
230
|
} else {
|
|
251
231
|
// Even scale indices (2, 4, 6): millón, billón, trillón
|
|
@@ -254,7 +234,7 @@ function buildLargeNumberWords (n, feminine) {
|
|
|
254
234
|
result += 'un ' + scaleWord
|
|
255
235
|
} else {
|
|
256
236
|
// Use masculine for scale segment
|
|
257
|
-
result +=
|
|
237
|
+
result += buildSegment(Number(segment), false) + ' ' + scaleWord
|
|
258
238
|
}
|
|
259
239
|
}
|
|
260
240
|
}
|
package/lib/languages/fi.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Finnish language converter - Functional Implementation
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Self-contained module with its own input validation, ready for subpath exports.
|
|
5
5
|
*
|
|
6
6
|
* Key features:
|
|
7
7
|
* - Compound tens+ones without spaces: "kaksikymmentäyksi" (21)
|
|
@@ -35,7 +35,7 @@ const DECIMAL_SEP = 'pilkku'
|
|
|
35
35
|
const SCALES = ['miljoona', 'miljardi', 'biljoona', 'triljoona']
|
|
36
36
|
|
|
37
37
|
// ============================================================================
|
|
38
|
-
//
|
|
38
|
+
// Segment Building
|
|
39
39
|
// ============================================================================
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -79,13 +79,6 @@ function buildSegment (n) {
|
|
|
79
79
|
return parts.join(' ')
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
// Precompute all 1000 segment words (0-999)
|
|
83
|
-
const SEGMENTS = new Array(1000)
|
|
84
|
-
|
|
85
|
-
for (let i = 0; i < 1000; i++) {
|
|
86
|
-
SEGMENTS[i] = buildSegment(i)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
82
|
// ============================================================================
|
|
90
83
|
// Conversion Functions
|
|
91
84
|
// ============================================================================
|
|
@@ -101,7 +94,7 @@ function integerToWords (n) {
|
|
|
101
94
|
|
|
102
95
|
// Fast path: numbers < 1000 (direct lookup)
|
|
103
96
|
if (n < 1000n) {
|
|
104
|
-
return
|
|
97
|
+
return buildSegment(Number(n))
|
|
105
98
|
}
|
|
106
99
|
|
|
107
100
|
// Fast path: numbers < 1,000,000 (thousands)
|
|
@@ -114,11 +107,11 @@ function integerToWords (n) {
|
|
|
114
107
|
if (thousands === 1) {
|
|
115
108
|
result = THOUSAND
|
|
116
109
|
} else {
|
|
117
|
-
result =
|
|
110
|
+
result = buildSegment(thousands) + ' ' + THOUSAND
|
|
118
111
|
}
|
|
119
112
|
|
|
120
113
|
if (remainder > 0) {
|
|
121
|
-
result += ' ' +
|
|
114
|
+
result += ' ' + buildSegment(remainder)
|
|
122
115
|
}
|
|
123
116
|
|
|
124
117
|
return result
|
|
@@ -161,7 +154,7 @@ function buildLargeNumberWords (n) {
|
|
|
161
154
|
const segment = segments[i]
|
|
162
155
|
|
|
163
156
|
if (segment !== 0) {
|
|
164
|
-
const segmentWord =
|
|
157
|
+
const segmentWord = buildSegment(segment)
|
|
165
158
|
|
|
166
159
|
if (scaleIndex === 0) {
|
|
167
160
|
// Units segment
|
package/lib/languages/fil.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Filipino language converter - Functional Implementation
|
|
3
3
|
*
|
|
4
|
-
* Self-contained
|
|
4
|
+
* Self-contained module with its own input validation, ready for subpath exports.
|
|
5
5
|
*
|
|
6
6
|
* Key features:
|
|
7
7
|
* - Linker "ng" after vowels: "isang daang" (100)
|
|
@@ -46,7 +46,7 @@ function addLinker (word) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// ============================================================================
|
|
49
|
-
//
|
|
49
|
+
// Segment Building
|
|
50
50
|
// ============================================================================
|
|
51
51
|
|
|
52
52
|
function buildSegment (n) {
|
|
@@ -91,16 +91,11 @@ function buildSegment (n) {
|
|
|
91
91
|
return parts.join(' ')
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const SEGMENTS = new Array(1000)
|
|
95
|
-
for (let i = 0; i < 1000; i++) {
|
|
96
|
-
SEGMENTS[i] = buildSegment(i)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
94
|
/**
|
|
100
95
|
* Builds segment with linker added to last word (for use before scale words).
|
|
101
96
|
*/
|
|
102
97
|
function buildSegmentWithLinker (n) {
|
|
103
|
-
const segmentWord =
|
|
98
|
+
const segmentWord = buildSegment(n)
|
|
104
99
|
if (!segmentWord) return ''
|
|
105
100
|
|
|
106
101
|
// Find the last space to get the last word
|
|
@@ -124,12 +119,6 @@ function buildSegmentWithLinker (n) {
|
|
|
124
119
|
return prefix + addLinker(lastWord)
|
|
125
120
|
}
|
|
126
121
|
|
|
127
|
-
// Precompute segments with linker for scale word usage
|
|
128
|
-
const SEGMENTS_WITH_LINKER = new Array(1000)
|
|
129
|
-
for (let i = 0; i < 1000; i++) {
|
|
130
|
-
SEGMENTS_WITH_LINKER[i] = buildSegmentWithLinker(i)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
122
|
// ============================================================================
|
|
134
123
|
// Conversion Functions
|
|
135
124
|
// ============================================================================
|
|
@@ -138,7 +127,7 @@ function integerToWords (n) {
|
|
|
138
127
|
if (n === 0n) return ZERO
|
|
139
128
|
|
|
140
129
|
if (n < 1000n) {
|
|
141
|
-
return
|
|
130
|
+
return buildSegment(Number(n))
|
|
142
131
|
}
|
|
143
132
|
|
|
144
133
|
return buildLargeNumberWords(n)
|
|
@@ -170,10 +159,10 @@ function buildLargeNumberWords (n) {
|
|
|
170
159
|
if (result) result += ' '
|
|
171
160
|
|
|
172
161
|
if (i === 0) {
|
|
173
|
-
result +=
|
|
162
|
+
result += buildSegment(segment)
|
|
174
163
|
} else {
|
|
175
164
|
// Add linker to segment before scale word
|
|
176
|
-
const segmentWord =
|
|
165
|
+
const segmentWord = buildSegmentWithLinker(segment)
|
|
177
166
|
result += segmentWord + ' ' + scaleWord
|
|
178
167
|
}
|
|
179
168
|
}
|
package/lib/languages/fr-BE.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* French (Belgium) language converter - Functional Implementation
|
|
3
3
|
*
|
|
4
|
-
* Self-contained
|
|
4
|
+
* Self-contained module with its own input validation, ready for subpath exports.
|
|
5
5
|
*
|
|
6
6
|
* Belgian French differences from standard French:
|
|
7
7
|
* - septante (70) instead of soixante-dix
|
|
@@ -32,7 +32,7 @@ const NEGATIVE = 'moins'
|
|
|
32
32
|
const DECIMAL_SEP = 'virgule'
|
|
33
33
|
|
|
34
34
|
// ============================================================================
|
|
35
|
-
//
|
|
35
|
+
// Segment Building
|
|
36
36
|
// ============================================================================
|
|
37
37
|
|
|
38
38
|
function buildSegment (n) {
|
|
@@ -112,18 +112,6 @@ function buildSegment (n) {
|
|
|
112
112
|
return { word: parts.join(' '), endsWithCents, endsWithVingts }
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// Precompute all 1000 segment words
|
|
116
|
-
const SEGMENTS = new Array(1000)
|
|
117
|
-
const SEGMENTS_ENDS_CENTS = new Array(1000)
|
|
118
|
-
const SEGMENTS_ENDS_VINGTS = new Array(1000)
|
|
119
|
-
|
|
120
|
-
for (let i = 0; i < 1000; i++) {
|
|
121
|
-
const result = buildSegment(i)
|
|
122
|
-
SEGMENTS[i] = result.word
|
|
123
|
-
SEGMENTS_ENDS_CENTS[i] = result.endsWithCents
|
|
124
|
-
SEGMENTS_ENDS_VINGTS[i] = result.endsWithVingts
|
|
125
|
-
}
|
|
126
|
-
|
|
127
115
|
// ============================================================================
|
|
128
116
|
// Helper Functions
|
|
129
117
|
// ============================================================================
|
|
@@ -152,7 +140,7 @@ function integerToWords (n, withHyphen = false) {
|
|
|
152
140
|
if (n === 0n) return ZERO
|
|
153
141
|
|
|
154
142
|
if (n < 1000n) {
|
|
155
|
-
const word =
|
|
143
|
+
const { word } = buildSegment(Number(n))
|
|
156
144
|
return withHyphen ? word.replace(/ /g, '-') : word
|
|
157
145
|
}
|
|
158
146
|
|
|
@@ -164,15 +152,17 @@ function integerToWords (n, withHyphen = false) {
|
|
|
164
152
|
if (thousands === 1) {
|
|
165
153
|
result = THOUSAND
|
|
166
154
|
} else {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
const { word: thousandsWord, endsWithCents, endsWithVingts } = buildSegment(thousands)
|
|
156
|
+
let adjustedWord = thousandsWord
|
|
157
|
+
if (endsWithCents || endsWithVingts) {
|
|
158
|
+
adjustedWord = thousandsWord.slice(0, -1)
|
|
170
159
|
}
|
|
171
|
-
result =
|
|
160
|
+
result = adjustedWord + (withHyphen ? '-' : ' ') + THOUSAND
|
|
172
161
|
}
|
|
173
162
|
|
|
174
163
|
if (remainder > 0) {
|
|
175
|
-
|
|
164
|
+
const { word: remainderWord } = buildSegment(remainder)
|
|
165
|
+
result += (withHyphen ? '-' : ' ') + remainderWord
|
|
176
166
|
}
|
|
177
167
|
|
|
178
168
|
if (withHyphen) {
|
|
@@ -211,22 +201,23 @@ function buildLargeNumberWords (n, withHyphen) {
|
|
|
211
201
|
|
|
212
202
|
if (segment !== 0) {
|
|
213
203
|
const scaleWord = scaleIndex > 0 ? getScaleWord(scaleIndex, BigInt(segment)) : ''
|
|
204
|
+
const { word: segWords, endsWithCents, endsWithVingts } = buildSegment(segment)
|
|
214
205
|
|
|
215
206
|
if (scaleIndex === 0) {
|
|
216
|
-
parts.push(
|
|
207
|
+
parts.push(segWords)
|
|
217
208
|
} else if (scaleIndex === 1) {
|
|
218
209
|
if (segment === 1) {
|
|
219
210
|
parts.push(THOUSAND)
|
|
220
211
|
} else {
|
|
221
|
-
let
|
|
222
|
-
if (
|
|
223
|
-
|
|
212
|
+
let adjustedWord = segWords
|
|
213
|
+
if (endsWithCents || endsWithVingts) {
|
|
214
|
+
adjustedWord = segWords.slice(0, -1)
|
|
224
215
|
}
|
|
225
|
-
parts.push(
|
|
216
|
+
parts.push(adjustedWord)
|
|
226
217
|
parts.push(scaleWord)
|
|
227
218
|
}
|
|
228
219
|
} else {
|
|
229
|
-
parts.push(
|
|
220
|
+
parts.push(segWords)
|
|
230
221
|
parts.push(scaleWord)
|
|
231
222
|
}
|
|
232
223
|
}
|
package/lib/languages/fr.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* French language converter - Functional Implementation
|
|
3
3
|
*
|
|
4
|
-
* A performance-optimized number-to-words converter using precomputed lookup tables.
|
|
5
4
|
* Self-contained module with its own input validation, ready for subpath exports.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
* This eliminates all per-call string manipulation for segment conversion.
|
|
9
|
-
*
|
|
10
|
-
* French-specific rules (handled in precomputation):
|
|
6
|
+
* French-specific rules:
|
|
11
7
|
* - Vigesimal patterns: 70 = soixante-dix, 80 = quatre-vingts, 90 = quatre-vingt-dix
|
|
12
8
|
* - "et" conjunction: vingt et un (21), soixante et onze (71), but NOT quatre-vingt-un
|
|
13
9
|
* - Pluralization: "cents" loses 's' when followed by more digits
|
|
@@ -37,7 +33,7 @@ const NEGATIVE = 'moins'
|
|
|
37
33
|
const DECIMAL_SEP = 'virgule'
|
|
38
34
|
|
|
39
35
|
// ============================================================================
|
|
40
|
-
//
|
|
36
|
+
// Segment Building
|
|
41
37
|
// ============================================================================
|
|
42
38
|
|
|
43
39
|
/**
|
|
@@ -128,18 +124,6 @@ function buildSegment (n) {
|
|
|
128
124
|
return { word: parts.join(' '), endsWithCents, endsWithVingts }
|
|
129
125
|
}
|
|
130
126
|
|
|
131
|
-
// Precompute all 1000 segment words (0-999)
|
|
132
|
-
const SEGMENTS = new Array(1000)
|
|
133
|
-
const SEGMENTS_ENDS_CENTS = new Array(1000)
|
|
134
|
-
const SEGMENTS_ENDS_VINGTS = new Array(1000)
|
|
135
|
-
|
|
136
|
-
for (let i = 0; i < 1000; i++) {
|
|
137
|
-
const result = buildSegment(i)
|
|
138
|
-
SEGMENTS[i] = result.word
|
|
139
|
-
SEGMENTS_ENDS_CENTS[i] = result.endsWithCents
|
|
140
|
-
SEGMENTS_ENDS_VINGTS[i] = result.endsWithVingts
|
|
141
|
-
}
|
|
142
|
-
|
|
143
127
|
// ============================================================================
|
|
144
128
|
// Helper Functions
|
|
145
129
|
// ============================================================================
|
|
@@ -183,9 +167,9 @@ function getScaleWord (scaleIndex, segment) {
|
|
|
183
167
|
function integerToWords (n, withHyphen = false) {
|
|
184
168
|
if (n === 0n) return ZERO
|
|
185
169
|
|
|
186
|
-
// Fast path: numbers < 1000
|
|
170
|
+
// Fast path: numbers < 1000
|
|
187
171
|
if (n < 1000n) {
|
|
188
|
-
const word =
|
|
172
|
+
const { word } = buildSegment(Number(n))
|
|
189
173
|
return withHyphen ? word.replace(/ /g, '-') : word
|
|
190
174
|
}
|
|
191
175
|
|
|
@@ -200,15 +184,17 @@ function integerToWords (n, withHyphen = false) {
|
|
|
200
184
|
result = THOUSAND
|
|
201
185
|
} else {
|
|
202
186
|
// Check if segment ends with "cents" or "vingts" - need to strip 's' before mille
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
187
|
+
const { word: thousandsWord, endsWithCents, endsWithVingts } = buildSegment(thousands)
|
|
188
|
+
let adjustedWord = thousandsWord
|
|
189
|
+
if (endsWithCents || endsWithVingts) {
|
|
190
|
+
adjustedWord = thousandsWord.slice(0, -1) // Remove trailing 's'
|
|
206
191
|
}
|
|
207
|
-
result =
|
|
192
|
+
result = adjustedWord + (withHyphen ? '-' : ' ') + THOUSAND
|
|
208
193
|
}
|
|
209
194
|
|
|
210
195
|
if (remainder > 0) {
|
|
211
|
-
|
|
196
|
+
const { word: remainderWord } = buildSegment(remainder)
|
|
197
|
+
result += (withHyphen ? '-' : ' ') + remainderWord
|
|
212
198
|
}
|
|
213
199
|
|
|
214
200
|
if (withHyphen) {
|
|
@@ -257,26 +243,27 @@ function buildLargeNumberWords (n, withHyphen) {
|
|
|
257
243
|
|
|
258
244
|
if (segment !== 0) {
|
|
259
245
|
const scaleWord = scaleIndex > 0 ? getScaleWord(scaleIndex, BigInt(segment)) : ''
|
|
246
|
+
const { word: segWords, endsWithCents, endsWithVingts } = buildSegment(segment)
|
|
260
247
|
|
|
261
248
|
if (scaleIndex === 0) {
|
|
262
249
|
// Units segment
|
|
263
|
-
parts.push(
|
|
250
|
+
parts.push(segWords)
|
|
264
251
|
} else if (scaleIndex === 1) {
|
|
265
252
|
// Thousands: "mille" not "un mille"
|
|
266
253
|
if (segment === 1) {
|
|
267
254
|
parts.push(THOUSAND)
|
|
268
255
|
} else {
|
|
269
|
-
let segWords = SEGMENTS[segment]
|
|
270
256
|
// Strip 's' from cents/vingts before mille
|
|
271
|
-
|
|
272
|
-
|
|
257
|
+
let adjustedWord = segWords
|
|
258
|
+
if (endsWithCents || endsWithVingts) {
|
|
259
|
+
adjustedWord = segWords.slice(0, -1)
|
|
273
260
|
}
|
|
274
|
-
parts.push(
|
|
261
|
+
parts.push(adjustedWord)
|
|
275
262
|
parts.push(scaleWord)
|
|
276
263
|
}
|
|
277
264
|
} else {
|
|
278
265
|
// Million and above
|
|
279
|
-
parts.push(
|
|
266
|
+
parts.push(segWords)
|
|
280
267
|
parts.push(scaleWord)
|
|
281
268
|
}
|
|
282
269
|
}
|