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