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
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Indonesian language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter with precomputed lookup tables.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - "Se-" prefix for 100 (seratus) and 1000 (seribu)
@@ -29,7 +29,7 @@ const NEGATIVE = 'min'
29
29
  const DECIMAL_SEP = 'koma'
30
30
 
31
31
  // ============================================================================
32
- // Precomputed Lookup Tables
32
+ // Segment Building
33
33
  // ============================================================================
34
34
 
35
35
  function buildSegment (n) {
@@ -68,11 +68,6 @@ function buildSegment (n) {
68
68
  return parts.join(' ')
69
69
  }
70
70
 
71
- const SEGMENTS = new Array(1000)
72
- for (let i = 0; i < 1000; i++) {
73
- SEGMENTS[i] = buildSegment(i)
74
- }
75
-
76
71
  // ============================================================================
77
72
  // Conversion Functions
78
73
  // ============================================================================
@@ -81,7 +76,7 @@ function integerToWords (n) {
81
76
  if (n === 0n) return ZERO
82
77
 
83
78
  if (n < 1000n) {
84
- return SEGMENTS[Number(n)]
79
+ return buildSegment(Number(n))
85
80
  }
86
81
 
87
82
  if (n < 1_000_000n) {
@@ -92,11 +87,11 @@ function integerToWords (n) {
92
87
  if (thousands === 1) {
93
88
  result = 'se' + THOUSAND_WORD
94
89
  } else {
95
- result = SEGMENTS[thousands] + ' ' + THOUSAND_WORD
90
+ result = buildSegment(thousands) + ' ' + THOUSAND_WORD
96
91
  }
97
92
 
98
93
  if (remainder > 0) {
99
- result += ' ' + SEGMENTS[remainder]
94
+ result += ' ' + buildSegment(remainder)
100
95
  }
101
96
 
102
97
  return result
@@ -131,17 +126,17 @@ function buildLargeNumberWords (n) {
131
126
 
132
127
  if (segment !== 0) {
133
128
  if (scaleIndex === 0) {
134
- parts.push(SEGMENTS[segment])
129
+ parts.push(buildSegment(segment))
135
130
  } else if (scaleIndex === 1) {
136
131
  if (segment === 1) {
137
132
  parts.push('se' + THOUSAND_WORD)
138
133
  } else {
139
- parts.push(SEGMENTS[segment] + ' ' + THOUSAND_WORD)
134
+ parts.push(buildSegment(segment) + ' ' + THOUSAND_WORD)
140
135
  }
141
136
  } else {
142
137
  // Indonesian: "satu juta" not "sejuta"
143
138
  const scaleWord = SCALE_WORDS[scaleIndex - 2]
144
- parts.push(SEGMENTS[segment] + ' ' + scaleWord)
139
+ parts.push(buildSegment(segment) + ' ' + scaleWord)
145
140
  }
146
141
  }
147
142
 
@@ -1,13 +1,9 @@
1
1
  /**
2
- * Italian language converter - Functional Implementation v2
2
+ * Italian 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 optimization: Precompute all 1000 segment values (0-999) at module load.
8
- * This eliminates all per-call string manipulation for segment conversion.
9
- *
10
- * Italian-specific rules (handled in precomputation):
6
+ * Italian-specific rules:
11
7
  * - Concatenation without spaces within segments ("ventotto" not "venti otto")
12
8
  * - Phonetic vowel elision: "venti" + "otto" → "ventotto"
13
9
  * - Accent on final "tre" in compounds: "ventitré"
@@ -22,12 +18,16 @@ import { parseNumericValue } from '../utils/parse-numeric.js'
22
18
  // Vocabulary (module-level constants)
23
19
  // ============================================================================
24
20
 
25
- // Base vocabulary for building lookup tables
21
+ // Base vocabulary
26
22
  const ONES = ['', 'uno', 'due', 'tre', 'quattro', 'cinque', 'sei', 'sette', 'otto', 'nove']
27
23
  const TEENS = ['dieci', 'undici', 'dodici', 'tredici', 'quattordici', 'quindici', 'sedici', 'diciassette', 'diciotto', 'diciannove']
28
24
  const TENS = ['', '', 'venti', 'trenta', 'quaranta', 'cinquanta', 'sessanta', 'settanta', 'ottanta', 'novanta']
29
25
  const HUNDREDS = ['', 'cento', 'duecento', 'trecento', 'quattrocento', 'cinquecento', 'seicento', 'settecento', 'ottocento', 'novecento']
30
26
 
27
+ // Pre-elided tens stems (drop final vowel before 'uno'/'otto')
28
+ // vent- (from venti), trent- (from trenta), etc.
29
+ const TENS_STEM = ['', '', 'vent', 'trent', 'quarant', 'cinquant', 'sessant', 'settant', 'ottant', 'novant']
30
+
31
31
  const ZERO = 'zero'
32
32
  const NEGATIVE = 'meno'
33
33
  const DECIMAL_SEP = 'virgola'
@@ -40,38 +40,19 @@ const THOUSAND_PLURAL_SUFFIX = 'mila'
40
40
  const SCALE_PREFIXES = ['m', 'b', 'tr', 'quadr', 'quint', 'sest', 'sett', 'ott', 'nov', 'dec']
41
41
 
42
42
  // ============================================================================
43
- // Precomputed Lookup Tables (built once at module load)
43
+ // Segment Building
44
44
  // ============================================================================
45
45
 
46
- /**
47
- * Applies Italian phonetic vowel elision rules.
48
- * Only used during table construction.
49
- */
50
- function applyPhoneticRules (str) {
51
- return str
52
- .replace(/io/g, 'o')
53
- .replace(/ao/g, 'o')
54
- .replace(/oo/g, 'o')
55
- .replace(/iu/g, 'u')
56
- .replace(/au/g, 'u')
57
- }
58
-
59
- /**
60
- * Adds accent to final "tre" in a word.
61
- * Only used during table construction.
62
- */
63
- function accentuateTre (word) {
64
- if (word.length > 3 && word.slice(-3) === 'tre') {
65
- return word.slice(0, -3) + 'tré'
66
- }
67
- return word
68
- }
69
-
70
46
  /**
71
47
  * Builds the segment word for a number 0-999.
72
- * Only used during table construction.
48
+ * Handles Italian phonetic elision inline (no regex).
49
+ *
50
+ * Elision rules:
51
+ * - Tens ending in vowel + uno/otto → drop tens vowel: ventuno, ventotto
52
+ * - Hundreds cento + otto/ottanta → centotto, centottanta (drop 'o')
53
+ * - Final 'tre' in compounds becomes 'tré': ventitré, trentatré
73
54
  */
74
- function buildSegmentWord (n) {
55
+ function buildSegment (n) {
75
56
  if (n === 0) return ''
76
57
 
77
58
  const ones = n % 10
@@ -82,86 +63,70 @@ function buildSegmentWord (n) {
82
63
 
83
64
  // Hundreds
84
65
  if (hundreds > 0) {
85
- result = HUNDREDS[hundreds]
66
+ // Elision: *cento + otto/ottanta → *centotto/centottanta (drop final 'o')
67
+ // Only applies when tens = 8 (ottanta) or tens = 0 and ones = 8 (otto)
68
+ if (tens === 8 || (tens === 0 && ones === 8)) {
69
+ // Remove final 'o' from hundreds: cento→cent, duecento→duecent, etc.
70
+ result = HUNDREDS[hundreds].slice(0, -1)
71
+ } else {
72
+ result = HUNDREDS[hundreds]
73
+ }
86
74
  }
87
75
 
88
76
  // Tens and ones
89
77
  if (tens === 0 && ones === 0) {
90
- // Nothing more
78
+ // Nothing more (just hundreds)
91
79
  } else if (tens === 1) {
92
80
  // Teens: 10-19
93
81
  result += TEENS[ones]
94
82
  } else if (tens >= 2) {
95
- // 20-99
96
- result += TENS[tens]
97
- if (ones > 0) {
98
- result += ONES[ones]
83
+ // 20-99: handle elision for uno (1) and otto (8)
84
+ if (ones === 1 || ones === 8) {
85
+ // Use stem form: vent + uno = ventuno, vent + otto = ventotto
86
+ result += TENS_STEM[tens] + ONES[ones]
87
+ } else if (ones === 3) {
88
+ // Final tre becomes tré
89
+ result += TENS[tens] + 'tré'
90
+ } else if (ones > 0) {
91
+ result += TENS[tens] + ONES[ones]
92
+ } else {
93
+ result += TENS[tens]
99
94
  }
100
95
  } else if (ones > 0) {
101
96
  // 1-9 (tens === 0)
102
- result += ONES[ones]
97
+ if (ones === 3 && hundreds > 0) {
98
+ // centotré, duecentotré, etc.
99
+ result += 'tré'
100
+ } else {
101
+ result += ONES[ones]
102
+ }
103
103
  }
104
104
 
105
- // Apply phonetic rules and accent
106
- return accentuateTre(applyPhoneticRules(result))
105
+ return result
107
106
  }
108
107
 
109
108
  /**
110
109
  * Builds segment word with "un" for scale context (millions, billions).
111
- * Only used during table construction.
110
+ * Same as buildSegment but returns "un" for 1 instead of "uno".
112
111
  */
113
- function buildSegmentWordForScale (n) {
112
+ function buildSegmentForScale (n) {
114
113
  if (n === 0) return ''
115
114
  if (n === 1) return 'un' // "un milione" not "uno milione"
116
-
117
- const ones = n % 10
118
- const tens = Math.floor(n / 10) % 10
119
- const hundreds = Math.floor(n / 100)
120
-
121
- let result = ''
122
-
123
- if (hundreds > 0) {
124
- result = HUNDREDS[hundreds]
125
- }
126
-
127
- if (tens === 0 && ones === 0) {
128
- // Nothing more
129
- } else if (tens === 1) {
130
- result += TEENS[ones]
131
- } else if (tens >= 2) {
132
- result += TENS[tens]
133
- if (ones > 0) {
134
- result += ONES[ones]
135
- }
136
- } else if (ones > 0) {
137
- // 1-9 with tens === 0
138
- // "un" only for exactly 1, others normal
139
- result += ONES[ones]
140
- }
141
-
142
- return accentuateTre(applyPhoneticRules(result))
115
+ return buildSegment(n)
143
116
  }
144
117
 
145
- // Precompute all 1000 segment words (0-999)
146
- // SEGMENTS[n] gives the Italian word for n within a segment
147
- const SEGMENTS = new Array(1000)
148
- for (let i = 0; i < 1000; i++) {
149
- SEGMENTS[i] = buildSegmentWord(i)
150
- }
151
-
152
- // Precompute segment words for scale context (uses "un" for 1)
153
- const SEGMENTS_SCALE = new Array(1000)
154
- for (let i = 0; i < 1000; i++) {
155
- SEGMENTS_SCALE[i] = buildSegmentWordForScale(i)
156
- }
118
+ /**
119
+ * Builds thousands word for 1-999 thousand.
120
+ * Handles elision: tre + mila = tremila (no accent), otto + mila = ottomila
121
+ */
122
+ function buildThousands (n) {
123
+ if (n === 0) return ''
124
+ if (n === 1) return THOUSAND_SINGULAR // "mille"
157
125
 
158
- // Precompute thousands words (1-999) + "mila" suffix
159
- // THOUSANDS[n] gives the word for n thousand (e.g., THOUSANDS[2] = "duemila")
160
- const THOUSANDS = new Array(1000)
161
- THOUSANDS[0] = ''
162
- THOUSANDS[1] = THOUSAND_SINGULAR // "mille"
163
- for (let i = 2; i < 1000; i++) {
164
- THOUSANDS[i] = applyPhoneticRules(SEGMENTS[i] + THOUSAND_PLURAL_SUFFIX)
126
+ // Build segment and append "mila"
127
+ // Note: elision of segment ending vowel + 'mila' not needed (mila starts with 'm')
128
+ // But we need to handle cases like "ottomila" (no double-o issue since we build directly)
129
+ return buildSegment(n) + THOUSAND_PLURAL_SUFFIX
165
130
  }
166
131
 
167
132
  // ============================================================================
@@ -207,9 +172,9 @@ function getScaleWordPlural (scaleIndex) {
207
172
  function integerToWords (n) {
208
173
  if (n === 0n) return ZERO
209
174
 
210
- // Fast path: numbers < 1000 (direct lookup)
175
+ // Fast path: numbers < 1000
211
176
  if (n < 1000n) {
212
- return SEGMENTS[Number(n)]
177
+ return buildSegment(Number(n))
213
178
  }
214
179
 
215
180
  // Fast path: numbers < 1,000,000 (thousands)
@@ -218,11 +183,11 @@ function integerToWords (n) {
218
183
  const remainder = Number(n % 1000n)
219
184
 
220
185
  if (remainder === 0) {
221
- return THOUSANDS[thousands]
186
+ return buildThousands(thousands)
222
187
  }
223
188
 
224
189
  // Concatenate thousands + remainder
225
- return THOUSANDS[thousands] + SEGMENTS[remainder]
190
+ return buildThousands(thousands) + buildSegment(remainder)
226
191
  }
227
192
 
228
193
  // For numbers >= 1,000,000, use scale decomposition
@@ -259,17 +224,17 @@ function buildLargeNumberWords (n) {
259
224
 
260
225
  if (scaleIndex >= 2) {
261
226
  // Millions and above: "segment scaleWord"
262
- const segmentWords = SEGMENTS_SCALE[segNum]
227
+ const segmentWords = buildSegmentForScale(segNum)
263
228
  const scaleWord = segment === 1n
264
229
  ? getScaleWordSingular(scaleIndex)
265
230
  : getScaleWordPlural(scaleIndex)
266
231
  parts.push(segmentWords + ' ' + scaleWord)
267
232
  } else if (scaleIndex === 1) {
268
- // Thousands: use precomputed table
269
- parts.push(THOUSANDS[segNum])
233
+ // Thousands
234
+ parts.push(buildThousands(segNum))
270
235
  } else {
271
236
  // Units (scaleIndex === 0): just the segment
272
- parts.push(SEGMENTS[segNum])
237
+ parts.push(buildSegment(segNum))
273
238
  }
274
239
  }
275
240
 
@@ -1,13 +1,9 @@
1
1
  /**
2
2
  * Japanese 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 optimization: Precompute all segment values (0-9999) at module load.
8
- * This eliminates all per-call string manipulation for segment conversion.
9
- *
10
- * Japanese-specific rules (handled in precomputation):
6
+ * Japanese-specific rules:
11
7
  * - Myriad (万-based) grouping: 4 digits per segment instead of 3
12
8
  * - 一 omission: Omit "一" before 十, 百, 千 but NOT before 万 and higher scales
13
9
  * - Kanji numerals: 零一二三四五六七八九
@@ -55,13 +51,12 @@ const HUNDRED = '百'
55
51
  const THOUSAND = '千'
56
52
 
57
53
  // ============================================================================
58
- // Precomputed Lookup Tables (built once at module load)
54
+ // Segment Building
59
55
  // ============================================================================
60
56
 
61
57
  /**
62
58
  * Builds segment word for 0-9999 with 一 omission rules.
63
59
  * - Omit 一 before 十, 百, 千
64
- * Only used during table construction.
65
60
  */
66
61
  function buildSegment (n) {
67
62
  if (n === 0) return ''
@@ -108,13 +103,6 @@ function buildSegment (n) {
108
103
  return result
109
104
  }
110
105
 
111
- // Precompute all 10000 segment words (0-9999)
112
- // SEGMENTS[n] gives the Japanese word for n within a segment
113
- const SEGMENTS = new Array(10000)
114
- for (let i = 0; i < 10000; i++) {
115
- SEGMENTS[i] = buildSegment(i)
116
- }
117
-
118
106
  // ============================================================================
119
107
  // Conversion Functions
120
108
  // ============================================================================
@@ -128,9 +116,9 @@ for (let i = 0; i < 10000; i++) {
128
116
  function integerToWords (n) {
129
117
  if (n === 0n) return ZERO
130
118
 
131
- // Fast path: numbers < 10000 (direct lookup)
119
+ // Fast path: numbers < 10000
132
120
  if (n < 10000n) {
133
- return SEGMENTS[Number(n)]
121
+ return buildSegment(Number(n))
134
122
  }
135
123
 
136
124
  // Fast path: numbers < 100,000,000 (万 range)
@@ -143,11 +131,11 @@ function integerToWords (n) {
143
131
  if (man === 1) {
144
132
  result = '一' + SCALES[0] // 一万
145
133
  } else {
146
- result = SEGMENTS[man] + SCALES[0]
134
+ result = buildSegment(man) + SCALES[0]
147
135
  }
148
136
 
149
137
  if (remainder > 0) {
150
- result += SEGMENTS[remainder]
138
+ result += buildSegment(remainder)
151
139
  }
152
140
 
153
141
  return result
@@ -186,11 +174,11 @@ function buildLargeNumberWords (n) {
186
174
  if (segment === 1) {
187
175
  result += '一' + SCALES[i - 1]
188
176
  } else {
189
- result += SEGMENTS[segment] + SCALES[i - 1]
177
+ result += buildSegment(segment) + SCALES[i - 1]
190
178
  }
191
179
  } else {
192
180
  // Units segment (no scale word)
193
- result += SEGMENTS[segment]
181
+ result += buildSegment(segment)
194
182
  }
195
183
  }
196
184
 
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Converts a numeric value to Georgian words.
3
+ *
4
+ * This is the main public API. It accepts any valid numeric input
5
+ * (number, string, or bigint) and handles parsing internally.
6
+ *
7
+ * @param {number | string | bigint} value - The numeric value to convert
8
+ * @returns {string} The number in Georgian words
9
+ * @throws {TypeError} If value is not a valid numeric type
10
+ * @throws {Error} If value is not a valid number format
11
+ *
12
+ * @example
13
+ * toWords(21) // 'ოცდაერთი'
14
+ * toWords(100) // 'ასი'
15
+ * toWords(1000) // 'ათასი'
16
+ */
17
+ export function toWords(value: number | string | bigint): string;