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.
Files changed (154) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/languages/am-Latn.js +2 -2
  3. package/dist/languages/am-Latn.js.map +1 -1
  4. package/dist/languages/am.js +2 -2
  5. package/dist/languages/am.js.map +1 -1
  6. package/dist/languages/ar.js +1 -1
  7. package/dist/languages/az.js +2 -2
  8. package/dist/languages/az.js.map +1 -1
  9. package/dist/languages/bn.js +2 -2
  10. package/dist/languages/bn.js.map +1 -1
  11. package/dist/languages/cs.js +2 -2
  12. package/dist/languages/cs.js.map +1 -1
  13. package/dist/languages/da.js +2 -2
  14. package/dist/languages/da.js.map +1 -1
  15. package/dist/languages/de.js +2 -2
  16. package/dist/languages/de.js.map +1 -1
  17. package/dist/languages/el.js +2 -2
  18. package/dist/languages/el.js.map +1 -1
  19. package/dist/languages/en.js +2 -2
  20. package/dist/languages/en.js.map +1 -1
  21. package/dist/languages/es.js +2 -2
  22. package/dist/languages/es.js.map +1 -1
  23. package/dist/languages/fa.js +1 -1
  24. package/dist/languages/fi.js +2 -2
  25. package/dist/languages/fi.js.map +1 -1
  26. package/dist/languages/fil.js +2 -2
  27. package/dist/languages/fil.js.map +1 -1
  28. package/dist/languages/fr-BE.js +2 -2
  29. package/dist/languages/fr-BE.js.map +1 -1
  30. package/dist/languages/fr.js +2 -2
  31. package/dist/languages/fr.js.map +1 -1
  32. package/dist/languages/gu.js +2 -2
  33. package/dist/languages/gu.js.map +1 -1
  34. package/dist/languages/ha.js +2 -2
  35. package/dist/languages/ha.js.map +1 -1
  36. package/dist/languages/hbo.js +2 -2
  37. package/dist/languages/hbo.js.map +1 -1
  38. package/dist/languages/he.js +2 -2
  39. package/dist/languages/he.js.map +1 -1
  40. package/dist/languages/hi.js +2 -2
  41. package/dist/languages/hi.js.map +1 -1
  42. package/dist/languages/hr.js +2 -2
  43. package/dist/languages/hr.js.map +1 -1
  44. package/dist/languages/hu.js +1 -1
  45. package/dist/languages/id.js +2 -2
  46. package/dist/languages/id.js.map +1 -1
  47. package/dist/languages/it.js +2 -2
  48. package/dist/languages/it.js.map +1 -1
  49. package/dist/languages/ja.js +2 -2
  50. package/dist/languages/ja.js.map +1 -1
  51. package/dist/languages/ka.js +3 -0
  52. package/dist/languages/ka.js.map +1 -0
  53. package/dist/languages/kn.js +2 -2
  54. package/dist/languages/kn.js.map +1 -1
  55. package/dist/languages/ko.js +2 -2
  56. package/dist/languages/ko.js.map +1 -1
  57. package/dist/languages/lt.js +2 -2
  58. package/dist/languages/lt.js.map +1 -1
  59. package/dist/languages/lv.js +2 -2
  60. package/dist/languages/lv.js.map +1 -1
  61. package/dist/languages/mr.js +2 -2
  62. package/dist/languages/mr.js.map +1 -1
  63. package/dist/languages/ms.js +2 -2
  64. package/dist/languages/ms.js.map +1 -1
  65. package/dist/languages/nb.js +2 -2
  66. package/dist/languages/nb.js.map +1 -1
  67. package/dist/languages/nl.js +2 -2
  68. package/dist/languages/nl.js.map +1 -1
  69. package/dist/languages/pa.js +2 -2
  70. package/dist/languages/pa.js.map +1 -1
  71. package/dist/languages/pl.js +2 -2
  72. package/dist/languages/pl.js.map +1 -1
  73. package/dist/languages/pt.js +2 -2
  74. package/dist/languages/pt.js.map +1 -1
  75. package/dist/languages/ro.js +1 -1
  76. package/dist/languages/ru.js +2 -2
  77. package/dist/languages/ru.js.map +1 -1
  78. package/dist/languages/sr-Cyrl.js +2 -2
  79. package/dist/languages/sr-Cyrl.js.map +1 -1
  80. package/dist/languages/sr-Latn.js +2 -2
  81. package/dist/languages/sr-Latn.js.map +1 -1
  82. package/dist/languages/sv.js +2 -2
  83. package/dist/languages/sv.js.map +1 -1
  84. package/dist/languages/sw.js +1 -1
  85. package/dist/languages/ta.js +2 -2
  86. package/dist/languages/ta.js.map +1 -1
  87. package/dist/languages/te.js +2 -2
  88. package/dist/languages/te.js.map +1 -1
  89. package/dist/languages/th.js +1 -1
  90. package/dist/languages/tr.js +2 -2
  91. package/dist/languages/tr.js.map +1 -1
  92. package/dist/languages/uk.js +2 -2
  93. package/dist/languages/uk.js.map +1 -1
  94. package/dist/languages/ur.js +2 -2
  95. package/dist/languages/ur.js.map +1 -1
  96. package/dist/languages/vi.js +2 -2
  97. package/dist/languages/vi.js.map +1 -1
  98. package/dist/languages/yo.js +3 -0
  99. package/dist/languages/yo.js.map +1 -0
  100. package/dist/languages/zh-Hans.js +1 -1
  101. package/dist/languages/zh-Hant.js +1 -1
  102. package/dist/n2words.js +2 -2
  103. package/dist/n2words.js.map +1 -1
  104. package/lib/languages/am-Latn.js +4 -9
  105. package/lib/languages/am.js +4 -9
  106. package/lib/languages/az.js +4 -9
  107. package/lib/languages/bn.js +51 -30
  108. package/lib/languages/cs.js +9 -26
  109. package/lib/languages/da.js +6 -13
  110. package/lib/languages/de.js +21 -33
  111. package/lib/languages/el.js +6 -13
  112. package/lib/languages/en.js +44 -58
  113. package/lib/languages/es.js +10 -30
  114. package/lib/languages/fi.js +6 -13
  115. package/lib/languages/fil.js +6 -17
  116. package/lib/languages/fr-BE.js +17 -26
  117. package/lib/languages/fr.js +18 -31
  118. package/lib/languages/gu.js +43 -30
  119. package/lib/languages/ha.js +4 -9
  120. package/lib/languages/hbo.js +7 -25
  121. package/lib/languages/he.js +7 -18
  122. package/lib/languages/hi.js +55 -30
  123. package/lib/languages/hr.js +61 -55
  124. package/lib/languages/id.js +8 -13
  125. package/lib/languages/it.js +64 -99
  126. package/lib/languages/ja.js +8 -20
  127. package/lib/languages/ka.d.ts +17 -0
  128. package/lib/languages/ka.js +291 -0
  129. package/lib/languages/kn.js +43 -30
  130. package/lib/languages/ko.js +6 -13
  131. package/lib/languages/lt.js +6 -15
  132. package/lib/languages/lv.js +6 -15
  133. package/lib/languages/mr.js +43 -30
  134. package/lib/languages/ms.js +8 -13
  135. package/lib/languages/nb.js +13 -23
  136. package/lib/languages/nl.js +11 -29
  137. package/lib/languages/pa.js +32 -37
  138. package/lib/languages/pl.js +7 -20
  139. package/lib/languages/pt.js +17 -31
  140. package/lib/languages/ru.js +64 -59
  141. package/lib/languages/sr-Cyrl.js +58 -52
  142. package/lib/languages/sr-Latn.js +58 -52
  143. package/lib/languages/sv.js +14 -24
  144. package/lib/languages/ta.js +55 -42
  145. package/lib/languages/te.js +33 -41
  146. package/lib/languages/tr.js +7 -18
  147. package/lib/languages/uk.js +61 -55
  148. package/lib/languages/ur.js +32 -37
  149. package/lib/languages/vi.js +23 -43
  150. package/lib/languages/yo.d.ts +7 -0
  151. package/lib/languages/yo.js +303 -0
  152. package/lib/n2words.d.ts +3 -1
  153. package/lib/n2words.js +4 -0
  154. 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 }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Kannada language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter for South Asian numbering.
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 Splitting (inlined for performance)
42
+ // Segment Building
43
43
  // ============================================================================
44
44
 
45
- function groupByThreeThenTwos (n) {
46
- const numStr = n.toString()
47
- if (numStr.length <= 3) return [Number(numStr)]
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
- const segments = groupByThreeThenTwos(n)
82
- const segmentCount = segments.length
83
- const words = []
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
- for (let i = 0; i < segmentCount; i++) {
86
- const segmentValue = segments[i]
87
- if (segmentValue === 0) continue
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
- const scaleIndex = segmentCount - i - 1
90
- words.push(segmentToWords(segmentValue))
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(' ').trim()
109
+ return words.join(' ')
97
110
  }
98
111
 
99
112
  function decimalPartToWords (decimalPart) {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Korean language converter - Functional Implementation
3
3
  *
4
- * A performance-optimized number-to-words converter using precomputed lookup tables.
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
- // Precomputed Lookup Tables (built once at module load)
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 (direct lookup)
99
+ // Fast path: numbers < 10000
107
100
  if (n < 10000n) {
108
- return SEGMENTS[Number(n)]
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: SEGMENTS[segment], isScale: false })
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: SEGMENTS[segment], isScale: false })
153
+ parts.push({ word: buildSegment(segment), isScale: false })
161
154
  parts.push({ word: scaleWord, isScale: true })
162
155
  }
163
156
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Lithuanian language converter - Functional Implementation
3
3
  *
4
- * A performance-optimized number-to-words converter using precomputed lookup tables.
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
- // Precomputed Lookup Tables (built once at module load)
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 (direct lookup)
170
+ // Fast path: numbers < 1000
180
171
  if (n < 1000n) {
181
- const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
182
- return segments[Number(n)]
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 = SEGMENTS_MASC[segment]
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)
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Latvian language converter - Functional Implementation
3
3
  *
4
- * A performance-optimized number-to-words converter using precomputed lookup tables.
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
- // Precomputed Lookup Tables (built once at module load)
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 (direct lookup)
176
+ // Fast path: numbers < 1000
186
177
  if (n < 1000n) {
187
- const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
188
- return segments[Number(n)]
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 = SEGMENTS_MASC[segment]
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)
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Marathi language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter for South Asian numbering.
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 Splitting (inlined for performance)
42
+ // Segment Building
43
43
  // ============================================================================
44
44
 
45
- function groupByThreeThenTwos (n) {
46
- const numStr = n.toString()
47
- if (numStr.length <= 3) return [Number(numStr)]
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
- const segments = groupByThreeThenTwos(n)
82
- const segmentCount = segments.length
83
- const words = []
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
- for (let i = 0; i < segmentCount; i++) {
86
- const segmentValue = segments[i]
87
- if (segmentValue === 0) continue
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
- const scaleIndex = segmentCount - i - 1
90
- words.push(segmentToWords(segmentValue))
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(' ').trim()
109
+ return words.join(' ')
97
110
  }
98
111
 
99
112
  function decimalPartToWords (decimalPart) {