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
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Georgian language converter - Functional Implementation
|
|
3
|
+
*
|
|
4
|
+
* Self-contained module with its own input validation, ready for subpath exports.
|
|
5
|
+
*
|
|
6
|
+
* Georgian-specific rules:
|
|
7
|
+
* - Vigesimal (base-20) system for 20-99
|
|
8
|
+
* - 40 = ორმოცი (2×20), 60 = სამოცი (3×20), 80 = ოთხმოცი (4×20)
|
|
9
|
+
* - 30/50/70/90 use "და" + "ათი": ოცდაათი (20+10), ორმოცდაათი (40+10)
|
|
10
|
+
* - Compound numbers use "და" (da = "and") connector
|
|
11
|
+
* - Hundreds: unit prefix + ას (ორასი = 200)
|
|
12
|
+
* - Short scale for large numbers (მილიონი, მილიარდი, ტრილიონი)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Vocabulary (module-level constants)
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
// Numbers 0-9 (primitives)
|
|
22
|
+
const ONES = ['ნული', 'ერთი', 'ორი', 'სამი', 'ოთხი', 'ხუთი', 'ექვსი', 'შვიდი', 'რვა', 'ცხრა']
|
|
23
|
+
|
|
24
|
+
// Numbers 10-19
|
|
25
|
+
const TEENS = ['ათი', 'თერთმეტი', 'თორმეტი', 'ცამეტი', 'თოთხმეტი', 'თხუთმეტი', 'თექვსმეტი', 'ჩვიდმეტი', 'თვრამეტი', 'ცხრამეტი']
|
|
26
|
+
|
|
27
|
+
// Vigesimal bases: 20, 40, 60, 80 (with და connector forms)
|
|
28
|
+
const VIGESIMAL = ['', 'ოცი', 'ორმოცი', 'სამოცი', 'ოთხმოცი']
|
|
29
|
+
const VIGESIMAL_DA = ['', 'ოცდა', 'ორმოცდა', 'სამოცდა', 'ოთხმოცდა']
|
|
30
|
+
|
|
31
|
+
// Prefixes for hundreds (unit forms without final vowel)
|
|
32
|
+
const HUNDRED_PREFIXES = ['', '', 'ორ', 'სამ', 'ოთხ', 'ხუთ', 'ექვს', 'შვიდ', 'რვა', 'ცხრა']
|
|
33
|
+
|
|
34
|
+
const HUNDRED = 'ასი'
|
|
35
|
+
const HUNDRED_STEM = 'ას' // Without final vowel
|
|
36
|
+
const THOUSAND = 'ათასი'
|
|
37
|
+
const THOUSAND_STEM = 'ათას' // Without final vowel
|
|
38
|
+
|
|
39
|
+
// Scale words (short scale) - indexed by segment position
|
|
40
|
+
const SCALES = ['', '', 'მილიონი', 'მილიარდი', 'ტრილიონი', 'კვადრილიონი', 'კვინტილიონი', 'სექსტილიონი']
|
|
41
|
+
|
|
42
|
+
const ZERO = 'ნული'
|
|
43
|
+
const NEGATIVE = 'მინუს'
|
|
44
|
+
const DECIMAL_SEP = 'მთელი'
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Segment Building
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Builds segment word for 0-99 using vigesimal system.
|
|
52
|
+
* @param {number} n - Number 0-99
|
|
53
|
+
* @returns {string} Georgian word
|
|
54
|
+
*/
|
|
55
|
+
function buildTens (n) {
|
|
56
|
+
if (n < 10) return ONES[n]
|
|
57
|
+
if (n < 20) return TEENS[n - 10]
|
|
58
|
+
|
|
59
|
+
const vigesimalGroup = Math.floor(n / 20)
|
|
60
|
+
const remainder = n % 20
|
|
61
|
+
|
|
62
|
+
if (remainder === 0) {
|
|
63
|
+
return VIGESIMAL[vigesimalGroup]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Use და connector: ოცდა + remainder
|
|
67
|
+
const base = VIGESIMAL_DA[vigesimalGroup]
|
|
68
|
+
if (remainder < 10) {
|
|
69
|
+
return base + ONES[remainder]
|
|
70
|
+
}
|
|
71
|
+
return base + TEENS[remainder - 10]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Builds segment word for 0-999.
|
|
76
|
+
* Returns object with full form and stem form (without final vowel).
|
|
77
|
+
* @param {number} n - Number 0-999
|
|
78
|
+
* @returns {{full: string, stem: string}} Georgian words
|
|
79
|
+
*/
|
|
80
|
+
function buildSegment (n) {
|
|
81
|
+
if (n === 0) return { full: '', stem: '' }
|
|
82
|
+
if (n < 100) {
|
|
83
|
+
const word = buildTens(n)
|
|
84
|
+
// Remove final vowel for stem
|
|
85
|
+
const lastChar = word.slice(-1)
|
|
86
|
+
const stem = (lastChar === 'ი' || lastChar === 'ა') ? word.slice(0, -1) : word
|
|
87
|
+
return { full: word, stem }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const hundreds = Math.floor(n / 100)
|
|
91
|
+
const remainder = n % 100
|
|
92
|
+
|
|
93
|
+
// Build hundreds: ასი (100), ორასი (200), etc.
|
|
94
|
+
let hundredWord
|
|
95
|
+
if (hundreds === 1) {
|
|
96
|
+
hundredWord = remainder > 0 ? HUNDRED_STEM : HUNDRED
|
|
97
|
+
} else {
|
|
98
|
+
hundredWord = HUNDRED_PREFIXES[hundreds] + (remainder > 0 ? HUNDRED_STEM : HUNDRED)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (remainder > 0) {
|
|
102
|
+
const remainderWord = buildTens(remainder)
|
|
103
|
+
const full = hundredWord + ' ' + remainderWord
|
|
104
|
+
// Stem removes final vowel from remainder
|
|
105
|
+
const lastChar = remainderWord.slice(-1)
|
|
106
|
+
const remainderStem = (lastChar === 'ი' || lastChar === 'ა') ? remainderWord.slice(0, -1) : remainderWord
|
|
107
|
+
return { full, stem: hundredWord + ' ' + remainderStem }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Hundreds only - stem removes final ი
|
|
111
|
+
return { full: hundredWord, stem: hundredWord.slice(0, -1) }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Conversion Functions
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Converts a non-negative integer to Georgian words.
|
|
120
|
+
*
|
|
121
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
122
|
+
* @returns {string} Georgian words
|
|
123
|
+
*/
|
|
124
|
+
function integerToWords (n) {
|
|
125
|
+
if (n === 0n) return ZERO
|
|
126
|
+
|
|
127
|
+
// Fast path: numbers < 1000
|
|
128
|
+
if (n < 1000n) {
|
|
129
|
+
const { full } = buildSegment(Number(n))
|
|
130
|
+
return full
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
134
|
+
if (n < 1_000_000n) {
|
|
135
|
+
const thousands = Number(n / 1000n)
|
|
136
|
+
const remainder = Number(n % 1000n)
|
|
137
|
+
|
|
138
|
+
let result
|
|
139
|
+
if (thousands === 1) {
|
|
140
|
+
// "ათასი" not "ერთი ათასი"
|
|
141
|
+
result = remainder > 0 ? THOUSAND_STEM : THOUSAND
|
|
142
|
+
} else {
|
|
143
|
+
// Use stem form before ათასი
|
|
144
|
+
const { stem: thousandsPart } = buildSegment(thousands)
|
|
145
|
+
result = thousandsPart + ' ' + (remainder > 0 ? THOUSAND_STEM : THOUSAND)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (remainder > 0) {
|
|
149
|
+
const { full: remainderWord } = buildSegment(remainder)
|
|
150
|
+
result += ' ' + remainderWord
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return result
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
157
|
+
return buildLargeNumberWords(n)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Builds words for numbers >= 1,000,000.
|
|
162
|
+
*
|
|
163
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
164
|
+
* @returns {string} Georgian words
|
|
165
|
+
*/
|
|
166
|
+
function buildLargeNumberWords (n) {
|
|
167
|
+
const numStr = n.toString()
|
|
168
|
+
const len = numStr.length
|
|
169
|
+
|
|
170
|
+
// Build segments of 3 digits from left to right
|
|
171
|
+
const segments = []
|
|
172
|
+
const segmentSize = 3
|
|
173
|
+
|
|
174
|
+
const remainderLen = len % segmentSize
|
|
175
|
+
let pos = 0
|
|
176
|
+
if (remainderLen > 0) {
|
|
177
|
+
segments.push(Number(numStr.slice(0, remainderLen)))
|
|
178
|
+
pos = remainderLen
|
|
179
|
+
}
|
|
180
|
+
while (pos < len) {
|
|
181
|
+
segments.push(Number(numStr.slice(pos, pos + segmentSize)))
|
|
182
|
+
pos += segmentSize
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Convert segments to words
|
|
186
|
+
const parts = []
|
|
187
|
+
let scaleIndex = segments.length - 1
|
|
188
|
+
|
|
189
|
+
for (let i = 0; i < segments.length; i++) {
|
|
190
|
+
const segment = segments[i]
|
|
191
|
+
|
|
192
|
+
if (segment !== 0) {
|
|
193
|
+
if (scaleIndex === 0) {
|
|
194
|
+
// Units (no scale)
|
|
195
|
+
const { full } = buildSegment(segment)
|
|
196
|
+
parts.push(full)
|
|
197
|
+
} else if (scaleIndex === 1) {
|
|
198
|
+
// Thousands - check if there's a remainder
|
|
199
|
+
const hasRemainder = segments.slice(i + 1).some(s => s !== 0)
|
|
200
|
+
const thousandWord = hasRemainder ? THOUSAND_STEM : THOUSAND
|
|
201
|
+
|
|
202
|
+
if (segment === 1) {
|
|
203
|
+
parts.push(thousandWord)
|
|
204
|
+
} else {
|
|
205
|
+
const { stem } = buildSegment(segment)
|
|
206
|
+
parts.push(stem + ' ' + thousandWord)
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
// Million and above
|
|
210
|
+
const scaleWord = SCALES[scaleIndex] || SCALES[SCALES.length - 1]
|
|
211
|
+
if (segment === 1) {
|
|
212
|
+
parts.push('ერთი ' + scaleWord)
|
|
213
|
+
} else {
|
|
214
|
+
const { full } = buildSegment(segment)
|
|
215
|
+
parts.push(full + ' ' + scaleWord)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
scaleIndex--
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return parts.join(' ')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Converts decimal digits to Georgian words.
|
|
228
|
+
*
|
|
229
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
230
|
+
* @returns {string} Georgian words for decimal part
|
|
231
|
+
*/
|
|
232
|
+
function decimalPartToWords (decimalPart) {
|
|
233
|
+
let result = ''
|
|
234
|
+
|
|
235
|
+
// Handle leading zeros
|
|
236
|
+
let i = 0
|
|
237
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
238
|
+
if (result) result += ' '
|
|
239
|
+
result += ZERO
|
|
240
|
+
i++
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Convert remainder as a single number
|
|
244
|
+
const remainder = decimalPart.slice(i)
|
|
245
|
+
if (remainder) {
|
|
246
|
+
if (result) result += ' '
|
|
247
|
+
result += integerToWords(BigInt(remainder))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return result
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Converts a numeric value to Georgian words.
|
|
255
|
+
*
|
|
256
|
+
* This is the main public API. It accepts any valid numeric input
|
|
257
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
258
|
+
*
|
|
259
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
260
|
+
* @returns {string} The number in Georgian words
|
|
261
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
262
|
+
* @throws {Error} If value is not a valid number format
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* toWords(21) // 'ოცდაერთი'
|
|
266
|
+
* toWords(100) // 'ასი'
|
|
267
|
+
* toWords(1000) // 'ათასი'
|
|
268
|
+
*/
|
|
269
|
+
function toWords (value) {
|
|
270
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
271
|
+
|
|
272
|
+
let result = ''
|
|
273
|
+
|
|
274
|
+
if (isNegative) {
|
|
275
|
+
result = NEGATIVE + ' '
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
result += integerToWords(integerPart)
|
|
279
|
+
|
|
280
|
+
if (decimalPart) {
|
|
281
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return result
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// Public API
|
|
289
|
+
// ============================================================================
|
|
290
|
+
|
|
291
|
+
export { toWords }
|
package/lib/languages/kn.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Kannada 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
|
* - Indian numbering system (ಸಾವಿರ, ಲಕ್ಷ, ಕೋಟಿ)
|
|
@@ -39,26 +39,13 @@ const BELOW_HUNDRED = [
|
|
|
39
39
|
const SCALE_WORDS = ['', 'ಸಾವಿರ', 'ಲಕ್ಷ', 'ಕೋಟಿ', 'ಅಬ್ಜ', 'ಖರ್ವ', 'ನೀಲ', 'ಪದ್ಮ', 'ಶಂಖ']
|
|
40
40
|
|
|
41
41
|
// ============================================================================
|
|
42
|
-
// Segment
|
|
42
|
+
// Segment Building
|
|
43
43
|
// ============================================================================
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const segments = []
|
|
50
|
-
segments.unshift(Number(numStr.slice(-3)))
|
|
51
|
-
|
|
52
|
-
let remaining = numStr.slice(0, -3)
|
|
53
|
-
while (remaining.length > 0) {
|
|
54
|
-
segments.unshift(Number(remaining.slice(-2)))
|
|
55
|
-
remaining = remaining.slice(0, -2)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return segments
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function segmentToWords (n) {
|
|
45
|
+
/**
|
|
46
|
+
* Builds words for a 0-999 segment.
|
|
47
|
+
*/
|
|
48
|
+
function buildSegment (n) {
|
|
62
49
|
if (n === 0) return ''
|
|
63
50
|
if (n < 100) return BELOW_HUNDRED[n]
|
|
64
51
|
|
|
@@ -75,25 +62,51 @@ function segmentToWords (n) {
|
|
|
75
62
|
// Conversion Functions
|
|
76
63
|
// ============================================================================
|
|
77
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Converts a non-negative integer to Kannada words.
|
|
67
|
+
*
|
|
68
|
+
* Uses BigInt modulo for segment extraction (faster than string slicing).
|
|
69
|
+
* South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
|
|
70
|
+
*
|
|
71
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
72
|
+
* @returns {string} Kannada words
|
|
73
|
+
*/
|
|
78
74
|
function integerToWords (n) {
|
|
79
75
|
if (n === 0n) return ZERO
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
// Fast path: numbers < 1000 (direct lookup)
|
|
78
|
+
if (n < 1000n) {
|
|
79
|
+
return buildSegment(Number(n))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Extract segments using BigInt modulo
|
|
83
|
+
const segments = []
|
|
84
|
+
segments.push(Number(n % 1000n))
|
|
85
|
+
let temp = n / 1000n
|
|
86
|
+
|
|
87
|
+
while (temp > 0n) {
|
|
88
|
+
segments.push(Number(temp % 100n))
|
|
89
|
+
temp = temp / 100n
|
|
90
|
+
}
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
// Build result string (process from most-significant to least)
|
|
93
|
+
const words = []
|
|
94
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
95
|
+
const segment = segments[i]
|
|
96
|
+
if (segment === 0) continue
|
|
97
|
+
|
|
98
|
+
if (i === 0) {
|
|
99
|
+
words.push(buildSegment(segment))
|
|
100
|
+
} else {
|
|
101
|
+
words.push(BELOW_HUNDRED[segment])
|
|
102
|
+
}
|
|
88
103
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (scaleIndex > 0 && SCALE_WORDS[scaleIndex]) {
|
|
92
|
-
words.push(SCALE_WORDS[scaleIndex])
|
|
104
|
+
if (i > 0 && SCALE_WORDS[i]) {
|
|
105
|
+
words.push(SCALE_WORDS[i])
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
|
|
96
|
-
return words.join(' ')
|
|
109
|
+
return words.join(' ')
|
|
97
110
|
}
|
|
98
111
|
|
|
99
112
|
function decimalPartToWords (decimalPart) {
|
package/lib/languages/ko.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Korean 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
|
* - Myriad-based (만) grouping - 4 digits
|
|
@@ -31,7 +31,7 @@ const DECIMAL_SEP = '점'
|
|
|
31
31
|
const SCALES = ['만', '억', '조', '경', '해', '자', '양']
|
|
32
32
|
|
|
33
33
|
// ============================================================================
|
|
34
|
-
//
|
|
34
|
+
// Segment Building
|
|
35
35
|
// ============================================================================
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -83,13 +83,6 @@ function buildSegment (n) {
|
|
|
83
83
|
return result
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
// Precompute all 10000 segment words (0-9999) for myriad grouping
|
|
87
|
-
const SEGMENTS = new Array(10000)
|
|
88
|
-
|
|
89
|
-
for (let i = 0; i < 10000; i++) {
|
|
90
|
-
SEGMENTS[i] = buildSegment(i)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
86
|
// ============================================================================
|
|
94
87
|
// Conversion Functions
|
|
95
88
|
// ============================================================================
|
|
@@ -103,9 +96,9 @@ for (let i = 0; i < 10000; i++) {
|
|
|
103
96
|
function integerToWords (n) {
|
|
104
97
|
if (n === 0n) return ZERO
|
|
105
98
|
|
|
106
|
-
// Fast path: numbers < 10000
|
|
99
|
+
// Fast path: numbers < 10000
|
|
107
100
|
if (n < 10000n) {
|
|
108
|
-
return
|
|
101
|
+
return buildSegment(Number(n))
|
|
109
102
|
}
|
|
110
103
|
|
|
111
104
|
// For numbers >= 10000, use myriad decomposition
|
|
@@ -148,7 +141,7 @@ function buildLargeNumberWords (n) {
|
|
|
148
141
|
if (segment !== 0) {
|
|
149
142
|
if (scaleIndex === 0) {
|
|
150
143
|
// Units segment (no scale word)
|
|
151
|
-
parts.push({ word:
|
|
144
|
+
parts.push({ word: buildSegment(segment), isScale: false })
|
|
152
145
|
} else {
|
|
153
146
|
// Segment with scale word
|
|
154
147
|
const scaleWord = SCALES[scaleIndex - 1]
|
|
@@ -157,7 +150,7 @@ function buildLargeNumberWords (n) {
|
|
|
157
150
|
if (segment === 1) {
|
|
158
151
|
parts.push({ word: scaleWord, isScale: true })
|
|
159
152
|
} else {
|
|
160
|
-
parts.push({ word:
|
|
153
|
+
parts.push({ word: buildSegment(segment), isScale: false })
|
|
161
154
|
parts.push({ word: scaleWord, isScale: true })
|
|
162
155
|
}
|
|
163
156
|
}
|
package/lib/languages/lt.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lithuanian 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
|
* - Three-form pluralization (singular/plural/genitive)
|
|
@@ -45,7 +45,7 @@ const SCALE_FORMS = [
|
|
|
45
45
|
]
|
|
46
46
|
|
|
47
47
|
// ============================================================================
|
|
48
|
-
//
|
|
48
|
+
// Segment Building
|
|
49
49
|
// ============================================================================
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -114,15 +114,6 @@ function buildSegmentFeminine (n) {
|
|
|
114
114
|
return parts.join(' ')
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
// Precompute all 1000 segment words (0-999)
|
|
118
|
-
const SEGMENTS_MASC = new Array(1000)
|
|
119
|
-
const SEGMENTS_FEM = new Array(1000)
|
|
120
|
-
|
|
121
|
-
for (let i = 0; i < 1000; i++) {
|
|
122
|
-
SEGMENTS_MASC[i] = buildSegment(i)
|
|
123
|
-
SEGMENTS_FEM[i] = buildSegmentFeminine(i)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
117
|
// ============================================================================
|
|
127
118
|
// Pluralization
|
|
128
119
|
// ============================================================================
|
|
@@ -176,10 +167,10 @@ function pluralize (n, forms) {
|
|
|
176
167
|
function integerToWords (n, options = {}) {
|
|
177
168
|
if (n === 0n) return ZERO
|
|
178
169
|
|
|
179
|
-
// Fast path: numbers < 1000
|
|
170
|
+
// Fast path: numbers < 1000
|
|
180
171
|
if (n < 1000n) {
|
|
181
|
-
const
|
|
182
|
-
return
|
|
172
|
+
const num = Number(n)
|
|
173
|
+
return options.gender === 'feminine' ? buildSegmentFeminine(num) : buildSegment(num)
|
|
183
174
|
}
|
|
184
175
|
|
|
185
176
|
// For numbers >= 1000, feminine only applies to final segment if < 1000
|
|
@@ -222,7 +213,7 @@ function buildLargeNumberWords (n, options) {
|
|
|
222
213
|
const segment = segments[i]
|
|
223
214
|
|
|
224
215
|
if (segment !== 0) {
|
|
225
|
-
const segmentWord =
|
|
216
|
+
const segmentWord = buildSegment(segment)
|
|
226
217
|
|
|
227
218
|
if (scaleIndex === 0) {
|
|
228
219
|
// Units segment - use masculine (feminine doesn't apply when n >= 1000)
|
package/lib/languages/lv.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Latvian 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
|
* - Two-form pluralization (singular for 1 except 11, plural otherwise)
|
|
@@ -47,7 +47,7 @@ const SCALE_FORMS = [
|
|
|
47
47
|
]
|
|
48
48
|
|
|
49
49
|
// ============================================================================
|
|
50
|
-
//
|
|
50
|
+
// Segment Building
|
|
51
51
|
// ============================================================================
|
|
52
52
|
|
|
53
53
|
/**
|
|
@@ -133,15 +133,6 @@ function buildSegmentFeminine (n) {
|
|
|
133
133
|
return parts.join(' ')
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
// Precompute all 1000 segment words (0-999)
|
|
137
|
-
const SEGMENTS_MASC = new Array(1000)
|
|
138
|
-
const SEGMENTS_FEM = new Array(1000)
|
|
139
|
-
|
|
140
|
-
for (let i = 0; i < 1000; i++) {
|
|
141
|
-
SEGMENTS_MASC[i] = buildSegment(i)
|
|
142
|
-
SEGMENTS_FEM[i] = buildSegmentFeminine(i)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
136
|
// ============================================================================
|
|
146
137
|
// Pluralization
|
|
147
138
|
// ============================================================================
|
|
@@ -182,10 +173,10 @@ function pluralize (n, forms) {
|
|
|
182
173
|
function integerToWords (n, options = {}) {
|
|
183
174
|
if (n === 0n) return ZERO
|
|
184
175
|
|
|
185
|
-
// Fast path: numbers < 1000
|
|
176
|
+
// Fast path: numbers < 1000
|
|
186
177
|
if (n < 1000n) {
|
|
187
|
-
const
|
|
188
|
-
return
|
|
178
|
+
const num = Number(n)
|
|
179
|
+
return options.gender === 'feminine' ? buildSegmentFeminine(num) : buildSegment(num)
|
|
189
180
|
}
|
|
190
181
|
|
|
191
182
|
// For numbers >= 1000, feminine only applies to final segment if < 1000
|
|
@@ -227,7 +218,7 @@ function buildLargeNumberWords (n, options) {
|
|
|
227
218
|
const segment = segments[i]
|
|
228
219
|
|
|
229
220
|
if (segment !== 0) {
|
|
230
|
-
const segmentWord =
|
|
221
|
+
const segmentWord = buildSegment(segment)
|
|
231
222
|
|
|
232
223
|
if (scaleIndex === 0) {
|
|
233
224
|
// Units segment - use masculine (feminine doesn't apply when n >= 1000)
|
package/lib/languages/mr.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Marathi 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
|
* - Indian numbering system (हजार, लाख, कोटी)
|
|
@@ -39,26 +39,13 @@ const BELOW_HUNDRED = [
|
|
|
39
39
|
const SCALE_WORDS = ['', 'हजार', 'लाख', 'कोटी', 'अब्ज', 'खर्व', 'निखर्व', 'महापद्म', 'शंकू']
|
|
40
40
|
|
|
41
41
|
// ============================================================================
|
|
42
|
-
// Segment
|
|
42
|
+
// Segment Building
|
|
43
43
|
// ============================================================================
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const segments = []
|
|
50
|
-
segments.unshift(Number(numStr.slice(-3)))
|
|
51
|
-
|
|
52
|
-
let remaining = numStr.slice(0, -3)
|
|
53
|
-
while (remaining.length > 0) {
|
|
54
|
-
segments.unshift(Number(remaining.slice(-2)))
|
|
55
|
-
remaining = remaining.slice(0, -2)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return segments
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function segmentToWords (n) {
|
|
45
|
+
/**
|
|
46
|
+
* Builds words for a 0-999 segment.
|
|
47
|
+
*/
|
|
48
|
+
function buildSegment (n) {
|
|
62
49
|
if (n === 0) return ''
|
|
63
50
|
if (n < 100) return BELOW_HUNDRED[n]
|
|
64
51
|
|
|
@@ -75,25 +62,51 @@ function segmentToWords (n) {
|
|
|
75
62
|
// Conversion Functions
|
|
76
63
|
// ============================================================================
|
|
77
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Converts a non-negative integer to Marathi words.
|
|
67
|
+
*
|
|
68
|
+
* Uses BigInt modulo for segment extraction (faster than string slicing).
|
|
69
|
+
* South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
|
|
70
|
+
*
|
|
71
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
72
|
+
* @returns {string} Marathi words
|
|
73
|
+
*/
|
|
78
74
|
function integerToWords (n) {
|
|
79
75
|
if (n === 0n) return ZERO
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
// Fast path: numbers < 1000 (direct lookup)
|
|
78
|
+
if (n < 1000n) {
|
|
79
|
+
return buildSegment(Number(n))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Extract segments using BigInt modulo
|
|
83
|
+
const segments = []
|
|
84
|
+
segments.push(Number(n % 1000n))
|
|
85
|
+
let temp = n / 1000n
|
|
86
|
+
|
|
87
|
+
while (temp > 0n) {
|
|
88
|
+
segments.push(Number(temp % 100n))
|
|
89
|
+
temp = temp / 100n
|
|
90
|
+
}
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
// Build result string (process from most-significant to least)
|
|
93
|
+
const words = []
|
|
94
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
95
|
+
const segment = segments[i]
|
|
96
|
+
if (segment === 0) continue
|
|
97
|
+
|
|
98
|
+
if (i === 0) {
|
|
99
|
+
words.push(buildSegment(segment))
|
|
100
|
+
} else {
|
|
101
|
+
words.push(BELOW_HUNDRED[segment])
|
|
102
|
+
}
|
|
88
103
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (scaleIndex > 0 && SCALE_WORDS[scaleIndex]) {
|
|
92
|
-
words.push(SCALE_WORDS[scaleIndex])
|
|
104
|
+
if (i > 0 && SCALE_WORDS[i]) {
|
|
105
|
+
words.push(SCALE_WORDS[i])
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
|
|
96
|
-
return words.join(' ')
|
|
109
|
+
return words.join(' ')
|
|
97
110
|
}
|
|
98
111
|
|
|
99
112
|
function decimalPartToWords (decimalPart) {
|