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
  * Gujarati 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 Gujarati 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} Gujarati 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
  * Hausa 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
  * - Authentic Boko orthography with ɗ (hooked d) and ' (glottal stop)
@@ -88,11 +88,6 @@ function buildSegment (n) {
88
88
  return parts.join(' ')
89
89
  }
90
90
 
91
- const SEGMENTS = new Array(1000)
92
- for (let i = 0; i < 1000; i++) {
93
- SEGMENTS[i] = buildSegment(i)
94
- }
95
-
96
91
  // ============================================================================
97
92
  // Conversion Functions
98
93
  // ============================================================================
@@ -101,7 +96,7 @@ function integerToWords (n) {
101
96
  if (n === 0n) return ZERO
102
97
 
103
98
  if (n < 1000n) {
104
- return SEGMENTS[Number(n)]
99
+ return buildSegment(Number(n))
105
100
  }
106
101
 
107
102
  return buildLargeNumberWords(n)
@@ -143,9 +138,9 @@ function buildLargeNumberWords (n) {
143
138
  const scaleWord = SCALE_WORDS[scaleIndex] || ''
144
139
 
145
140
  if (scaleIndex === 0) {
146
- rawParts.push(SEGMENTS[segment])
141
+ rawParts.push(buildSegment(segment))
147
142
  } else {
148
- rawParts.push(SEGMENTS[segment])
143
+ rawParts.push(buildSegment(segment))
149
144
  rawParts.push(scaleWord)
150
145
  }
151
146
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Biblical Hebrew language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter with segment-based decomposition.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Gender agreement (masculine default, feminine via option)
@@ -9,8 +9,6 @@
9
9
  * - Special 1-9 thousands construct state
10
10
  * - "ו" (ve) conjunction rules vary by position
11
11
  * - Per-digit decimal reading
12
- *
13
- * Optimization: Precomputed segment lookup tables for both genders.
14
12
  */
15
13
 
16
14
  import { parseNumericValue } from '../utils/parse-numeric.js'
@@ -44,7 +42,7 @@ const NEGATIVE = 'מינוס'
44
42
  const DECIMAL_SEP = 'נקודה'
45
43
 
46
44
  // ============================================================================
47
- // Precomputed Lookup Tables (built once at module load)
45
+ // Segment Building
48
46
  // ============================================================================
49
47
 
50
48
  /**
@@ -145,19 +143,6 @@ function buildUnitsSegment (n, andWord, ONES, TEENS, HUNDREDS_ARR) {
145
143
  return result
146
144
  }
147
145
 
148
- // Precompute all 1000 segment words for masculine (default) with default conjunction
149
- const SCALE_SEGMENTS_MASC = new Array(1000)
150
- const UNITS_SEGMENTS_MASC = new Array(1000)
151
- const SCALE_SEGMENTS_FEM = new Array(1000)
152
- const UNITS_SEGMENTS_FEM = new Array(1000)
153
-
154
- for (let i = 0; i < 1000; i++) {
155
- SCALE_SEGMENTS_MASC[i] = buildScaleSegment(i, 'ו', ONES_MASC, TEENS_MASC, HUNDREDS)
156
- UNITS_SEGMENTS_MASC[i] = buildUnitsSegment(i, 'ו', ONES_MASC, TEENS_MASC, HUNDREDS)
157
- SCALE_SEGMENTS_FEM[i] = buildScaleSegment(i, 'ו', ONES_FEM, TEENS_FEM, HUNDREDS_FEM)
158
- UNITS_SEGMENTS_FEM[i] = buildUnitsSegment(i, 'ו', ONES_FEM, TEENS_FEM, HUNDREDS_FEM)
159
- }
160
-
161
146
  // ============================================================================
162
147
  // Conversion Functions
163
148
  // ============================================================================
@@ -175,19 +160,16 @@ function integerToWords (n, options) {
175
160
  const andWord = options.andWord ?? 'ו'
176
161
  const gender = options.gender || 'masculine'
177
162
  const isFeminine = gender === 'feminine'
178
- const usePrecomputed = andWord === 'ו'
179
163
 
180
164
  // Select vocabulary based on gender
181
165
  const ONES = isFeminine ? ONES_FEM : ONES_MASC
182
166
  const TEENS = isFeminine ? TEENS_FEM : TEENS_MASC
183
167
  const THOUSANDS_SPECIAL = isFeminine ? THOUSANDS_FEM : THOUSANDS_MASC
184
168
  const HUNDREDS_ARR = isFeminine ? HUNDREDS_FEM : HUNDREDS
185
- const SCALE_SEGS = isFeminine ? SCALE_SEGMENTS_FEM : SCALE_SEGMENTS_MASC
186
- const UNITS_SEGS = isFeminine ? UNITS_SEGMENTS_FEM : UNITS_SEGMENTS_MASC
187
169
 
188
- // Fast path: numbers < 1000 (direct lookup)
170
+ // Fast path: numbers < 1000
189
171
  if (n < 1000n) {
190
- return usePrecomputed ? UNITS_SEGS[Number(n)] : buildUnitsSegment(Number(n), andWord, ONES, TEENS, HUNDREDS_ARR)
172
+ return buildUnitsSegment(Number(n), andWord, ONES, TEENS, HUNDREDS_ARR)
191
173
  }
192
174
 
193
175
  // Extract segments using BigInt modulo
@@ -207,7 +189,7 @@ function integerToWords (n, options) {
207
189
 
208
190
  if (i === 0) {
209
191
  // Units segment (no scale word)
210
- const segmentWord = usePrecomputed ? UNITS_SEGS[segment] : buildUnitsSegment(segment, andWord, ONES, TEENS, HUNDREDS_ARR)
192
+ const segmentWord = buildUnitsSegment(segment, andWord, ONES, TEENS, HUNDREDS_ARR)
211
193
  if (result) {
212
194
  // Add "ו" before single-digit units when following scale words
213
195
  if (segment <= 9) {
@@ -224,7 +206,7 @@ function integerToWords (n, options) {
224
206
  if (result) result += ' '
225
207
  result += THOUSANDS_SPECIAL[segment]
226
208
  } else {
227
- const segmentWord = usePrecomputed ? SCALE_SEGS[segment] : buildScaleSegment(segment, andWord, ONES, TEENS, HUNDREDS_ARR)
209
+ const segmentWord = buildScaleSegment(segment, andWord, ONES, TEENS, HUNDREDS_ARR)
228
210
  if (result) result += ' '
229
211
  result += segmentWord + ' ' + SCALE[1]
230
212
  }
@@ -234,7 +216,7 @@ function integerToWords (n, options) {
234
216
  if (result) result += ' '
235
217
  result += SCALE[i]
236
218
  } else {
237
- const segmentWord = usePrecomputed ? SCALE_SEGS[segment] : buildScaleSegment(segment, andWord, ONES, TEENS, HUNDREDS_ARR)
219
+ const segmentWord = buildScaleSegment(segment, andWord, ONES, TEENS, HUNDREDS_ARR)
238
220
  if (result) result += ' '
239
221
  result += segmentWord + ' ' + SCALE_PLURAL[i]
240
222
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Modern Hebrew language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter with segment-based decomposition.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Feminine grammatical forms (default in Modern Hebrew)
@@ -9,8 +9,6 @@
9
9
  * - Special 1-9 thousands construct state
10
10
  * - "ו" (ve) conjunction rules vary by position
11
11
  * - Per-digit decimal reading
12
- *
13
- * Optimization: Precomputed segment lookup tables for 0-999.
14
12
  */
15
13
 
16
14
  import { parseNumericValue } from '../utils/parse-numeric.js'
@@ -38,7 +36,7 @@ const NEGATIVE = 'מינוס'
38
36
  const DECIMAL_SEP = 'נקודה'
39
37
 
40
38
  // ============================================================================
41
- // Precomputed Lookup Tables (built once at module load)
39
+ // Segment Building
42
40
  // ============================================================================
43
41
 
44
42
  /**
@@ -139,14 +137,6 @@ function buildUnitsSegment (n, andWord) {
139
137
  return result
140
138
  }
141
139
 
142
- // Precompute all 1000 segment words with default conjunction
143
- const SCALE_SEGMENTS = new Array(1000)
144
- const UNITS_SEGMENTS = new Array(1000)
145
- for (let i = 0; i < 1000; i++) {
146
- SCALE_SEGMENTS[i] = buildScaleSegment(i, 'ו')
147
- UNITS_SEGMENTS[i] = buildUnitsSegment(i, 'ו')
148
- }
149
-
150
140
  // ============================================================================
151
141
  // Conversion Functions
152
142
  // ============================================================================
@@ -162,11 +152,10 @@ function integerToWords (n, options) {
162
152
  if (n === 0n) return ZERO
163
153
 
164
154
  const andWord = options.andWord ?? 'ו'
165
- const usePrecomputed = andWord === 'ו'
166
155
 
167
- // Fast path: numbers < 1000 (direct lookup)
156
+ // Fast path: numbers < 1000
168
157
  if (n < 1000n) {
169
- return usePrecomputed ? UNITS_SEGMENTS[Number(n)] : buildUnitsSegment(Number(n), andWord)
158
+ return buildUnitsSegment(Number(n), andWord)
170
159
  }
171
160
 
172
161
  // Extract segments using BigInt modulo
@@ -186,7 +175,7 @@ function integerToWords (n, options) {
186
175
 
187
176
  if (i === 0) {
188
177
  // Units segment (no scale word)
189
- const segmentWord = usePrecomputed ? UNITS_SEGMENTS[segment] : buildUnitsSegment(segment, andWord)
178
+ const segmentWord = buildUnitsSegment(segment, andWord)
190
179
  if (result) {
191
180
  // Add "ו" before single-digit units when following scale words
192
181
  if (segment <= 9) {
@@ -203,7 +192,7 @@ function integerToWords (n, options) {
203
192
  if (result) result += ' '
204
193
  result += THOUSANDS_SPECIAL[segment]
205
194
  } else {
206
- const segmentWord = usePrecomputed ? SCALE_SEGMENTS[segment] : buildScaleSegment(segment, andWord)
195
+ const segmentWord = buildScaleSegment(segment, andWord)
207
196
  if (result) result += ' '
208
197
  result += segmentWord + ' ' + SCALE[1]
209
198
  }
@@ -213,7 +202,7 @@ function integerToWords (n, options) {
213
202
  if (result) result += ' '
214
203
  result += SCALE[i]
215
204
  } else {
216
- const segmentWord = usePrecomputed ? SCALE_SEGMENTS[segment] : buildScaleSegment(segment, andWord)
205
+ const segmentWord = buildScaleSegment(segment, andWord)
217
206
  if (result) result += ' '
218
207
  result += segmentWord + ' ' + SCALE_PLURAL[i]
219
208
  }
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Hindi 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 (हज़ार, लाख, करोड़)
8
8
  * - Devanagari script
9
9
  * - 3-2-2 grouping pattern (last 3 digits, then groups of 2)
10
10
  * - Complete word forms for 0-99
11
+ * - BigInt modulo for efficient segment extraction
11
12
  */
12
13
 
13
14
  import { parseNumericValue } from '../utils/parse-numeric.js'
@@ -38,26 +39,16 @@ const BELOW_HUNDRED = [
38
39
  const SCALE_WORDS = ['', 'हज़ार', 'लाख', 'करोड़', 'अरब', 'खरब', 'नील', 'पद्म', 'शंख']
39
40
 
40
41
  // ============================================================================
41
- // Segment Splitting (inlined for performance)
42
+ // Segment Building
42
43
  // ============================================================================
43
44
 
44
- function groupByThreeThenTwos (n) {
45
- const numStr = n.toString()
46
- if (numStr.length <= 3) return [Number(numStr)]
47
-
48
- const segments = []
49
- segments.unshift(Number(numStr.slice(-3)))
50
-
51
- let remaining = numStr.slice(0, -3)
52
- while (remaining.length > 0) {
53
- segments.unshift(Number(remaining.slice(-2)))
54
- remaining = remaining.slice(0, -2)
55
- }
56
-
57
- return segments
58
- }
59
-
60
- function segmentToWords (n) {
45
+ /**
46
+ * Builds words for a 0-999 segment.
47
+ *
48
+ * @param {number} n - Number 0-999
49
+ * @returns {string} Hindi words for the segment
50
+ */
51
+ function buildSegment (n) {
61
52
  if (n === 0) return ''
62
53
  if (n < 100) return BELOW_HUNDRED[n]
63
54
 
@@ -74,25 +65,59 @@ function segmentToWords (n) {
74
65
  // Conversion Functions
75
66
  // ============================================================================
76
67
 
68
+ /**
69
+ * Converts a non-negative integer to Hindi words.
70
+ *
71
+ * Uses BigInt modulo for segment extraction (faster than string slicing).
72
+ * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
73
+ *
74
+ * @param {bigint} n - Non-negative integer to convert
75
+ * @returns {string} Hindi words
76
+ */
77
77
  function integerToWords (n) {
78
78
  if (n === 0n) return ZERO
79
79
 
80
- const segments = groupByThreeThenTwos(n)
81
- const segmentCount = segments.length
82
- const words = []
80
+ // Fast path: numbers < 1000
81
+ if (n < 1000n) {
82
+ return buildSegment(Number(n))
83
+ }
84
+
85
+ // Extract segments using BigInt modulo
86
+ // First segment is 3 digits (thousands), rest are 2 digits (lakhs, crores, etc.)
87
+ // Segments stored least-significant first
88
+ const segments = []
83
89
 
84
- for (let i = 0; i < segmentCount; i++) {
85
- const segmentValue = segments[i]
86
- if (segmentValue === 0) continue
90
+ // First segment: last 3 digits
91
+ segments.push(Number(n % 1000n))
92
+ let temp = n / 1000n
93
+
94
+ // Remaining segments: 2 digits each (lakh = 100k, crore = 10M, etc.)
95
+ while (temp > 0n) {
96
+ segments.push(Number(temp % 100n))
97
+ temp = temp / 100n
98
+ }
99
+
100
+ // Build result string (process from most-significant to least)
101
+ const words = []
102
+ for (let i = segments.length - 1; i >= 0; i--) {
103
+ const segment = segments[i]
104
+ if (segment === 0) continue
105
+
106
+ if (i === 0) {
107
+ // First segment (units place) can be 0-999
108
+ words.push(buildSegment(segment))
109
+ } else {
110
+ // Other segments are 0-99
111
+ words.push(BELOW_HUNDRED[segment])
112
+ }
87
113
 
88
- const scaleIndex = segmentCount - i - 1
89
- words.push(segmentToWords(segmentValue))
90
- if (scaleIndex > 0 && SCALE_WORDS[scaleIndex]) {
91
- words.push(SCALE_WORDS[scaleIndex])
114
+ // Add scale word if not the units segment
115
+ if (i > 0 && SCALE_WORDS[i]) {
116
+ words.push(SCALE_WORDS[i])
92
117
  }
93
118
  }
94
119
 
95
- return words.join(' ').trim()
120
+ return words.join(' ')
96
121
  }
97
122
 
98
123
  function decimalPartToWords (decimalPart) {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Croatian language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter using shared Slavic utilities.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Three-form pluralization (one/few/many)
@@ -14,7 +14,38 @@ import { parseNumericValue } from '../utils/parse-numeric.js'
14
14
  import { validateOptions } from '../utils/validate-options.js'
15
15
 
16
16
  // ============================================================================
17
- // Slavic Utilities (inlined for performance)
17
+ // Vocabulary
18
+ // ============================================================================
19
+
20
+ const ONES_MASC = ['', 'jedan', 'dva', 'tri', 'četiri', 'pet', 'šest', 'sedam', 'osam', 'devet']
21
+ const ONES_FEM = ['', 'jedna', 'dvije', 'tri', 'četiri', 'pet', 'šest', 'sedam', 'osam', 'devet']
22
+
23
+ const TEENS = ['deset', 'jedanaest', 'dvanaest', 'trinaest', 'četrnaest', 'petnaest', 'šesnaest', 'sedamnaest', 'osamnaest', 'devetnaest']
24
+ const TENS = ['', '', 'dvadeset', 'trideset', 'četrdeset', 'pedeset', 'šezdeset', 'sedamdeset', 'osamdeset', 'devedeset']
25
+
26
+ // Croatian has irregular hundreds
27
+ const HUNDREDS = ['', 'sto', 'dvjesto', 'tristo', 'četiristo', 'petsto', 'šesto', 'sedamsto', 'osamsto', 'devetsto']
28
+
29
+ const ZERO = 'nula'
30
+ const NEGATIVE = 'minus'
31
+ const DECIMAL_SEP = 'zarez'
32
+
33
+ // Scale words: [singular, few, many]
34
+ // Thousands (index 0) are feminine, rest are masculine
35
+ const SCALE_FORMS = [
36
+ ['tisuća', 'tisuće', 'tisuća'],
37
+ ['milijun', 'milijuna', 'milijuna'],
38
+ ['milijarda', 'milijarde', 'milijarda'],
39
+ ['bilijun', 'bilijuna', 'bilijuna'],
40
+ ['bilijarda', 'bilijarde', 'bilijarda'],
41
+ ['trilijun', 'trilijuna', 'trilijuna'],
42
+ ['trilijarda', 'trilijarde', 'trilijarda'],
43
+ ['kvadrilijun', 'kvadrilijuna', 'kvadrilijuna'],
44
+ ['kvadrilijarda', 'kvadrilijarde', 'kvadrilijarda']
45
+ ]
46
+
47
+ // ============================================================================
48
+ // Segment Building
18
49
  // ============================================================================
19
50
 
20
51
  function pluralize (n, forms) {
@@ -31,19 +62,7 @@ function pluralize (n, forms) {
31
62
  return forms[2]
32
63
  }
33
64
 
34
- function buildAllSegments (onesMasc, onesFem, teens, tens, hundreds) {
35
- const masc = new Array(1000)
36
- const fem = new Array(1000)
37
-
38
- for (let i = 0; i < 1000; i++) {
39
- masc[i] = buildSegment(i, onesMasc, teens, tens, hundreds)
40
- fem[i] = buildSegment(i, onesFem, teens, tens, hundreds)
41
- }
42
-
43
- return { masc, fem }
44
- }
45
-
46
- function buildSegment (n, ones, teens, tens, hundreds) {
65
+ function buildSegmentMasc (n) {
47
66
  if (n === 0) return ''
48
67
 
49
68
  const onesDigit = n % 10
@@ -53,58 +72,47 @@ function buildSegment (n, ones, teens, tens, hundreds) {
53
72
  const parts = []
54
73
 
55
74
  if (hundredsDigit > 0) {
56
- parts.push(hundreds[hundredsDigit])
75
+ parts.push(HUNDREDS[hundredsDigit])
57
76
  }
58
77
 
59
78
  if (tensDigit > 1) {
60
- parts.push(tens[tensDigit])
79
+ parts.push(TENS[tensDigit])
61
80
  }
62
81
 
63
82
  if (tensDigit === 1) {
64
- parts.push(teens[onesDigit])
83
+ parts.push(TEENS[onesDigit])
65
84
  } else if (onesDigit > 0) {
66
- parts.push(ones[onesDigit])
85
+ parts.push(ONES_MASC[onesDigit])
67
86
  }
68
87
 
69
88
  return parts.join(' ')
70
89
  }
71
90
 
72
- // ============================================================================
73
- // Vocabulary
74
- // ============================================================================
75
-
76
- const ONES_MASC = ['', 'jedan', 'dva', 'tri', 'četiri', 'pet', 'šest', 'sedam', 'osam', 'devet']
77
- const ONES_FEM = ['', 'jedna', 'dvije', 'tri', 'četiri', 'pet', 'šest', 'sedam', 'osam', 'devet']
91
+ function buildSegmentFem (n) {
92
+ if (n === 0) return ''
78
93
 
79
- const TEENS = ['deset', 'jedanaest', 'dvanaest', 'trinaest', 'četrnaest', 'petnaest', 'šesnaest', 'sedamnaest', 'osamnaest', 'devetnaest']
80
- const TENS = ['', '', 'dvadeset', 'trideset', 'četrdeset', 'pedeset', 'šezdeset', 'sedamdeset', 'osamdeset', 'devedeset']
94
+ const onesDigit = n % 10
95
+ const tensDigit = Math.floor(n / 10) % 10
96
+ const hundredsDigit = Math.floor(n / 100)
81
97
 
82
- // Croatian has irregular hundreds
83
- const HUNDREDS = ['', 'sto', 'dvjesto', 'tristo', 'četiristo', 'petsto', 'šesto', 'sedamsto', 'osamsto', 'devetsto']
98
+ const parts = []
84
99
 
85
- const ZERO = 'nula'
86
- const NEGATIVE = 'minus'
87
- const DECIMAL_SEP = 'zarez'
100
+ if (hundredsDigit > 0) {
101
+ parts.push(HUNDREDS[hundredsDigit])
102
+ }
88
103
 
89
- // Scale words: [singular, few, many]
90
- // Thousands (index 0) are feminine, rest are masculine
91
- const SCALE_FORMS = [
92
- ['tisuća', 'tisuće', 'tisuća'],
93
- ['milijun', 'milijuna', 'milijuna'],
94
- ['milijarda', 'milijarde', 'milijarda'],
95
- ['bilijun', 'bilijuna', 'bilijuna'],
96
- ['bilijarda', 'bilijarde', 'bilijarda'],
97
- ['trilijun', 'trilijuna', 'trilijuna'],
98
- ['trilijarda', 'trilijarde', 'trilijarda'],
99
- ['kvadrilijun', 'kvadrilijuna', 'kvadrilijuna'],
100
- ['kvadrilijarda', 'kvadrilijarde', 'kvadrilijarda']
101
- ]
104
+ if (tensDigit > 1) {
105
+ parts.push(TENS[tensDigit])
106
+ }
102
107
 
103
- // ============================================================================
104
- // Precomputed Lookup Tables
105
- // ============================================================================
108
+ if (tensDigit === 1) {
109
+ parts.push(TEENS[onesDigit])
110
+ } else if (onesDigit > 0) {
111
+ parts.push(ONES_FEM[onesDigit])
112
+ }
106
113
 
107
- const { masc: SEGMENTS_MASC, fem: SEGMENTS_FEM } = buildAllSegments(ONES_MASC, ONES_FEM, TEENS, TENS, HUNDREDS)
114
+ return parts.join(' ')
115
+ }
108
116
 
109
117
  // ============================================================================
110
118
  // Conversion Functions
@@ -114,8 +122,7 @@ function integerToWords (n, options = {}) {
114
122
  if (n === 0n) return ZERO
115
123
 
116
124
  if (n < 1000n) {
117
- const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
118
- return segments[Number(n)]
125
+ return options.gender === 'feminine' ? buildSegmentFem(Number(n)) : buildSegmentMasc(Number(n))
119
126
  }
120
127
 
121
128
  return buildLargeNumberWords(n, options)
@@ -147,15 +154,14 @@ function buildLargeNumberWords (n, options) {
147
154
 
148
155
  if (segment !== 0) {
149
156
  if (scaleIndex === 0) {
150
- const segmentWords = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
151
- parts.push(segmentWords[segment])
157
+ parts.push(options.gender === 'feminine' ? buildSegmentFem(segment) : buildSegmentMasc(segment))
152
158
  } else {
153
159
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
154
160
  const scaleWord = pluralize(segment, scaleForms)
155
161
  // Thousands (scaleIndex=1) are feminine, others masculine
156
162
  const isFeminine = scaleIndex === 1
157
- const segmentWords = isFeminine ? SEGMENTS_FEM : SEGMENTS_MASC
158
- parts.push(segmentWords[segment] + ' ' + scaleWord)
163
+ const segmentWord = isFeminine ? buildSegmentFem(segment) : buildSegmentMasc(segment)
164
+ parts.push(segmentWord + ' ' + scaleWord)
159
165
  }
160
166
  }
161
167