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
  * Malay (Bahasa Melayu) 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 ALL singular scale units (seratus, seribu, sejuta, sebilion)
@@ -29,7 +29,7 @@ const NEGATIVE = 'minus'
29
29
  const DECIMAL_SEP = 'perpuluhan'
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,12 +126,12 @@ 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
  // Malay: "se-" prefix for ALL scale words when segment is 1
@@ -144,7 +139,7 @@ function buildLargeNumberWords (n) {
144
139
  if (segment === 1) {
145
140
  parts.push('se' + scaleWord)
146
141
  } else {
147
- parts.push(SEGMENTS[segment] + ' ' + scaleWord)
142
+ parts.push(buildSegment(segment) + ' ' + scaleWord)
148
143
  }
149
144
  }
150
145
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Norwegian Bokmål 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
  * - Hyphenated tens+ones: "tjue-en" (21)
@@ -32,7 +32,7 @@ const DECIMAL_SEP = 'komma'
32
32
  const SCALES = ['million', 'milliard', 'billion', 'billiard', 'kvintillion', 'sekstillion', 'septillion', 'oktillion']
33
33
 
34
34
  // ============================================================================
35
- // Precomputed Lookup Tables (built once at module load)
35
+ // Segment Building
36
36
  // ============================================================================
37
37
 
38
38
  /**
@@ -81,16 +81,6 @@ function buildSegment (n) {
81
81
  return { word: parts[0] || '', hasHundred }
82
82
  }
83
83
 
84
- // Precompute all 1000 segment words (0-999)
85
- const SEGMENTS = new Array(1000)
86
- const SEGMENTS_HAS_HUNDRED = new Array(1000)
87
-
88
- for (let i = 0; i < 1000; i++) {
89
- const result = buildSegment(i)
90
- SEGMENTS[i] = result.word
91
- SEGMENTS_HAS_HUNDRED[i] = result.hasHundred
92
- }
93
-
94
84
  // ============================================================================
95
85
  // Conversion Functions
96
86
  // ============================================================================
@@ -104,9 +94,9 @@ for (let i = 0; i < 1000; i++) {
104
94
  function integerToWords (n) {
105
95
  if (n === 0n) return ZERO
106
96
 
107
- // Fast path: numbers < 1000 (direct lookup)
97
+ // Fast path: numbers < 1000
108
98
  if (n < 1000n) {
109
- return SEGMENTS[Number(n)]
99
+ return buildSegment(Number(n)).word
110
100
  }
111
101
 
112
102
  // Fast path: numbers < 1,000,000 (thousands)
@@ -114,14 +104,15 @@ function integerToWords (n) {
114
104
  const thousands = Number(n / 1000n)
115
105
  const remainder = Number(n % 1000n)
116
106
 
117
- let result = SEGMENTS[thousands] + ' ' + THOUSAND
107
+ let result = buildSegment(thousands).word + ' ' + THOUSAND
118
108
 
119
109
  if (remainder > 0) {
110
+ const remainderResult = buildSegment(remainder)
120
111
  // Comma before hundreds, " og " before small numbers
121
- if (SEGMENTS_HAS_HUNDRED[remainder]) {
122
- result += ', ' + SEGMENTS[remainder]
112
+ if (remainderResult.hasHundred) {
113
+ result += ', ' + remainderResult.word
123
114
  } else {
124
- result += ' og ' + SEGMENTS[remainder]
115
+ result += ' og ' + remainderResult.word
125
116
  }
126
117
  }
127
118
 
@@ -165,19 +156,18 @@ function buildLargeNumberWords (n) {
165
156
  const segment = segments[i]
166
157
 
167
158
  if (segment !== 0) {
168
- const segmentWord = SEGMENTS[segment]
169
- const hasHundred = SEGMENTS_HAS_HUNDRED[segment]
159
+ const segmentResult = buildSegment(segment)
170
160
 
171
161
  if (scaleIndex === 0) {
172
162
  // Units segment
173
- parts.push({ word: segmentWord, hasHundred, type: 'units' })
163
+ parts.push({ word: segmentResult.word, hasHundred: segmentResult.hasHundred, type: 'units' })
174
164
  } else if (scaleIndex === 1) {
175
165
  // Thousands
176
- parts.push({ word: segmentWord + ' ' + THOUSAND, hasHundred: false, type: 'thousand' })
166
+ parts.push({ word: segmentResult.word + ' ' + THOUSAND, hasHundred: false, type: 'thousand' })
177
167
  } else {
178
168
  // Millions+
179
169
  const scaleWord = SCALES[scaleIndex - 2]
180
- parts.push({ word: segmentWord + ' ' + scaleWord, hasHundred: false, type: 'million' })
170
+ parts.push({ word: segmentResult.word + ' ' + scaleWord, hasHundred: false, type: 'million' })
181
171
  }
182
172
  }
183
173
 
@@ -1,13 +1,9 @@
1
1
  /**
2
2
  * Dutch 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
- * Dutch-specific rules (handled in precomputation):
6
+ * Dutch-specific rules:
11
7
  * - Inverted tens-ones: eenentwintig (one-and-twenty)
12
8
  * - "ën" connector when ones ends in 'e' (twee, drie)
13
9
  * - Compound words without spaces
@@ -38,7 +34,7 @@ const NEGATIVE = 'min'
38
34
  const DECIMAL_SEP = 'komma'
39
35
 
40
36
  // ============================================================================
41
- // Precomputed Lookup Tables (built once at module load)
37
+ // Segment Building
42
38
  // ============================================================================
43
39
 
44
40
  /**
@@ -98,18 +94,6 @@ function buildSegment (n, withAnd) {
98
94
  return result
99
95
  }
100
96
 
101
- // Precompute all 1000 segment words (0-999) - standard form
102
- const SEGMENTS = new Array(1000)
103
- for (let i = 0; i < 1000; i++) {
104
- SEGMENTS[i] = buildSegment(i, false)
105
- }
106
-
107
- // Precompute all 1000 segment words (0-999) - with optional "en"
108
- const SEGMENTS_WITH_AND = new Array(1000)
109
- for (let i = 0; i < 1000; i++) {
110
- SEGMENTS_WITH_AND[i] = buildSegment(i, true)
111
- }
112
-
113
97
  // ============================================================================
114
98
  // Conversion Functions
115
99
  // ============================================================================
@@ -125,7 +109,6 @@ function integerToWords (n, options) {
125
109
  if (n === 0n) return ZERO
126
110
 
127
111
  const { accentOne, includeOptionalAnd, noHundredPairing } = options
128
- const segments = includeOptionalAnd ? SEGMENTS_WITH_AND : SEGMENTS
129
112
 
130
113
  // Apply één/een replacement
131
114
  const applyAccent = (word) => {
@@ -135,9 +118,9 @@ function integerToWords (n, options) {
135
118
  return word
136
119
  }
137
120
 
138
- // Fast path: numbers < 1000 (direct lookup)
121
+ // Fast path: numbers < 1000
139
122
  if (n < 1000n) {
140
- return applyAccent(segments[Number(n)])
123
+ return applyAccent(buildSegment(Number(n), includeOptionalAnd))
141
124
  }
142
125
 
143
126
  // Hundred pairing for 1100-9999
@@ -147,9 +130,9 @@ function integerToWords (n, options) {
147
130
 
148
131
  // Only use pairing when high is not a multiple of 10
149
132
  if (high % 10 !== 0) {
150
- let result = segments[high] + HUNDRED
133
+ let result = buildSegment(high, includeOptionalAnd) + HUNDRED
151
134
  if (low > 0) {
152
- const lowWord = segments[low]
135
+ const lowWord = buildSegment(low, includeOptionalAnd)
153
136
  if (includeOptionalAnd && low < 13) {
154
137
  result += ' en ' + lowWord
155
138
  } else {
@@ -171,11 +154,11 @@ function integerToWords (n, options) {
171
154
  result = SCALES[0]
172
155
  } else {
173
156
  // Compound: "vijfduizend"
174
- result = segments[thousands] + SCALES[0]
157
+ result = buildSegment(thousands, includeOptionalAnd) + SCALES[0]
175
158
  }
176
159
 
177
160
  if (remainder > 0) {
178
- const remainderWord = segments[remainder]
161
+ const remainderWord = buildSegment(remainder, includeOptionalAnd)
179
162
  if (includeOptionalAnd && remainder < 13) {
180
163
  result += ' en ' + remainderWord
181
164
  } else {
@@ -200,7 +183,6 @@ function integerToWords (n, options) {
200
183
  */
201
184
  function buildLargeNumberWords (n, options) {
202
185
  const { includeOptionalAnd } = options
203
- const segmentLookup = includeOptionalAnd ? SEGMENTS_WITH_AND : SEGMENTS
204
186
 
205
187
  // Extract segments using BigInt division (faster than string slicing)
206
188
  // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
@@ -221,7 +203,7 @@ function buildLargeNumberWords (n, options) {
221
203
 
222
204
  if (i === 0) {
223
205
  // Units segment
224
- const word = segmentLookup[segment]
206
+ const word = buildSegment(segment, includeOptionalAnd)
225
207
  if (result) {
226
208
  if (prevWasScale && includeOptionalAnd && segment < 13) {
227
209
  result += ' en ' + word
@@ -238,7 +220,7 @@ function buildLargeNumberWords (n, options) {
238
220
  if (segment === 1) {
239
221
  result += SCALES[0]
240
222
  } else {
241
- result += segmentLookup[segment] + SCALES[0]
223
+ result += buildSegment(segment, includeOptionalAnd) + SCALES[0]
242
224
  }
243
225
  prevWasScale = true
244
226
  } else {
@@ -248,7 +230,7 @@ function buildLargeNumberWords (n, options) {
248
230
  if (segment === 1) {
249
231
  result += 'een ' + scaleWord
250
232
  } else {
251
- result += segmentLookup[segment] + ' ' + scaleWord
233
+ result += buildSegment(segment, includeOptionalAnd) + ' ' + scaleWord
252
234
  }
253
235
  prevWasScale = true
254
236
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Punjabi 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 (ਹਜ਼ਾਰ, ਲੱਖ, ਕਰੋੜ)
@@ -38,13 +38,13 @@ const BELOW_HUNDRED = [
38
38
  const SCALE_WORDS = ['', 'ਹਜ਼ਾਰ', 'ਲੱਖ', 'ਕਰੋੜ', 'ਅਰਬ', 'ਖਰਬ', 'ਨੀਲ', 'ਪਦਮ', 'ਸ਼ੰਖ']
39
39
 
40
40
  // ============================================================================
41
- // Conversion Functions
41
+ // Segment Building
42
42
  // ============================================================================
43
43
 
44
44
  /**
45
- * Converts 0-999 to Punjabi words.
45
+ * Builds words for a 0-999 segment.
46
46
  */
47
- function segmentToWords (n) {
47
+ function buildSegment (n) {
48
48
  if (n === 0) return ''
49
49
  if (n < 100) return BELOW_HUNDRED[n]
50
50
 
@@ -57,9 +57,15 @@ function segmentToWords (n) {
57
57
  return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED + ' ' + BELOW_HUNDRED[remainder]
58
58
  }
59
59
 
60
+ // ============================================================================
61
+ // Conversion Functions
62
+ // ============================================================================
63
+
60
64
  /**
61
65
  * Converts a non-negative integer to Punjabi words.
62
- * Uses recursive approach for Indian 3-2-2 grouping pattern.
66
+ *
67
+ * Uses BigInt modulo for segment extraction (faster than string slicing).
68
+ * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
63
69
  *
64
70
  * @param {bigint} n - Non-negative integer to convert
65
71
  * @returns {string} Punjabi words
@@ -69,48 +75,37 @@ function integerToWords (n) {
69
75
 
70
76
  // Fast path: numbers < 1000 (direct lookup)
71
77
  if (n < 1000n) {
72
- return segmentToWords(Number(n))
78
+ return buildSegment(Number(n))
73
79
  }
74
80
 
75
- return buildLargeNumberWords(n, 0)
76
- }
77
-
78
- /**
79
- * Recursively builds words for numbers >= 1000.
80
- * Indian grouping: first 3 digits, then 2-digit groups.
81
- *
82
- * @param {bigint} n - Number to convert
83
- * @param {number} scale - Current scale index (0=units, 1=thousands, etc.)
84
- * @returns {string} Punjabi words
85
- */
86
- function buildLargeNumberWords (n, scale) {
87
- if (n === 0n) return ''
88
-
89
- // Determine divisor: 1000 for first split, 100 for rest
90
- const divisor = scale === 0 ? 1000n : 100n
91
- const segment = Number(n % divisor)
92
- const rest = n / divisor
81
+ // Extract segments using BigInt modulo
82
+ const segments = []
83
+ segments.push(Number(n % 1000n))
84
+ let temp = n / 1000n
93
85
 
94
- // Build higher segments first (recursive)
95
- let result = ''
96
- if (rest > 0n) {
97
- result = buildLargeNumberWords(rest, scale + 1)
86
+ while (temp > 0n) {
87
+ segments.push(Number(temp % 100n))
88
+ temp = temp / 100n
98
89
  }
99
90
 
100
- // Add current segment
101
- if (segment > 0) {
102
- if (result) result += ' '
91
+ // Build result string (process from most-significant to least)
92
+ const words = []
93
+ for (let i = segments.length - 1; i >= 0; i--) {
94
+ const segment = segments[i]
95
+ if (segment === 0) continue
103
96
 
104
- if (scale === 0) {
105
- // Units segment (0-999)
106
- result += segmentToWords(segment)
97
+ if (i === 0) {
98
+ words.push(buildSegment(segment))
107
99
  } else {
108
- // Scale segments (0-99)
109
- result += BELOW_HUNDRED[segment] + ' ' + SCALE_WORDS[scale]
100
+ words.push(BELOW_HUNDRED[segment])
101
+ }
102
+
103
+ if (i > 0 && SCALE_WORDS[i]) {
104
+ words.push(SCALE_WORDS[i])
110
105
  }
111
106
  }
112
107
 
113
- return result
108
+ return words.join(' ')
114
109
  }
115
110
 
116
111
  function decimalPartToWords (decimalPart) {
@@ -1,13 +1,9 @@
1
1
  /**
2
2
  * Polish 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
- * Polish-specific rules (handled in precomputation):
6
+ * Polish-specific rules:
11
7
  * - Three-form pluralization: 1 = singular, 2-4 = few, 5+ = many
12
8
  * - Gender agreement (masculine/feminine for numbers < 1000)
13
9
  * - Omit "jeden" before scale words (tysiąc, milion, etc.)
@@ -51,7 +47,7 @@ const NEGATIVE = 'minus'
51
47
  const DECIMAL_SEP = 'przecinek'
52
48
 
53
49
  // ============================================================================
54
- // Precomputed Lookup Tables (built once at module load)
50
+ // Segment Building
55
51
  // ============================================================================
56
52
 
57
53
  /**
@@ -123,14 +119,6 @@ function buildSegmentFeminine (n) {
123
119
  return parts.join(' ')
124
120
  }
125
121
 
126
- // Precompute all 1000 segment words (0-999)
127
- const SEGMENTS_MASC = new Array(1000)
128
- const SEGMENTS_FEM = new Array(1000)
129
- for (let i = 0; i < 1000; i++) {
130
- SEGMENTS_MASC[i] = buildSegment(i)
131
- SEGMENTS_FEM[i] = buildSegmentFeminine(i)
132
- }
133
-
134
122
  // ============================================================================
135
123
  // Helper Functions
136
124
  // ============================================================================
@@ -174,10 +162,9 @@ function pluralize (n, forms) {
174
162
  function integerToWords (n, options = {}) {
175
163
  if (n === 0n) return ZERO
176
164
 
177
- // Fast path: numbers < 1000 (direct lookup)
165
+ // Fast path: numbers < 1000
178
166
  if (n < 1000n) {
179
- const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
180
- return segments[Number(n)]
167
+ return options.gender === 'feminine' ? buildSegmentFeminine(Number(n)) : buildSegment(Number(n))
181
168
  }
182
169
 
183
170
  // Fast path: numbers < 1,000,000 (thousands)
@@ -192,11 +179,11 @@ function integerToWords (n, options = {}) {
192
179
  // Omit "jeden" before tysiąc
193
180
  result = scaleWord
194
181
  } else {
195
- result = SEGMENTS_MASC[thousands] + ' ' + scaleWord
182
+ result = buildSegment(thousands) + ' ' + scaleWord
196
183
  }
197
184
 
198
185
  if (remainder > 0) {
199
- result += ' ' + SEGMENTS_MASC[remainder]
186
+ result += ' ' + buildSegment(remainder)
200
187
  }
201
188
 
202
189
  return result
@@ -231,7 +218,7 @@ function buildLargeNumberWords (n, options) {
231
218
  const segment = segmentValues[i]
232
219
  if (segment === 0n) continue
233
220
 
234
- const segmentWord = SEGMENTS_MASC[Number(segment)]
221
+ const segmentWord = buildSegment(Number(segment))
235
222
 
236
223
  if (result) result += ' '
237
224
 
@@ -1,13 +1,9 @@
1
1
  /**
2
2
  * Portuguese 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
- * Portuguese-specific rules (handled in precomputation):
6
+ * Portuguese-specific rules:
11
7
  * - "e" conjunction everywhere: vinte e um, cento e um, mil e um
12
8
  * - "cem" for exact 100, "cento" for 100+ remainder
13
9
  * - Irregular hundreds: duzentos, trezentos, quatrocentos, etc.
@@ -34,7 +30,7 @@ const NEGATIVE = 'menos'
34
30
  const DECIMAL_SEP = 'vírgula'
35
31
 
36
32
  // ============================================================================
37
- // Precomputed Lookup Tables (built once at module load)
33
+ // Segment Building
38
34
  // ============================================================================
39
35
 
40
36
  /**
@@ -76,22 +72,11 @@ function buildSegment (n) {
76
72
  // Join hundreds with "e": "cento e um", "duzentos e trinta e um"
77
73
  const word = parts.join(' e ')
78
74
 
79
- return { word, isExactHundred: hundreds > 0 && tens === 0 && ones === 0 }
80
- }
81
-
82
- // Precompute all 1000 segment words (0-999)
83
- const SEGMENTS = new Array(1000)
84
- const SEGMENTS_STARTS_WITH_HUNDREDS = new Array(1000)
85
-
86
- for (let i = 0; i < 1000; i++) {
87
- const result = buildSegment(i)
88
- SEGMENTS[i] = result.word
89
- // Precompute whether segment starts with hundreds (100-999)
90
- SEGMENTS_STARTS_WITH_HUNDREDS[i] = i >= 100
75
+ return { word, isExactHundred: hundreds > 0 && tens === 0 && ones === 0, startsWithHundreds: n >= 100 }
91
76
  }
92
77
 
93
78
  // ============================================================================
94
- // Scale Word Lookup (precomputed for common scales)
79
+ // Scale Word Lookup
95
80
  // ============================================================================
96
81
 
97
82
  // Precompute scale words for singular and plural forms
@@ -133,9 +118,9 @@ const SCALE_WORDS_PLURAL = [
133
118
  function integerToWords (n) {
134
119
  if (n === 0n) return ZERO
135
120
 
136
- // Fast path: numbers < 1000 (direct lookup)
121
+ // Fast path: numbers < 1000
137
122
  if (n < 1000n) {
138
- return SEGMENTS[Number(n)]
123
+ return buildSegment(Number(n)).word
139
124
  }
140
125
 
141
126
  // Fast path: numbers < 1,000,000 (thousands)
@@ -148,15 +133,16 @@ function integerToWords (n) {
148
133
  // "mil" not "um mil"
149
134
  result = THOUSAND
150
135
  } else {
151
- result = SEGMENTS[thousands] + ' ' + THOUSAND
136
+ result = buildSegment(thousands).word + ' ' + THOUSAND
152
137
  }
153
138
 
154
139
  if (remainder > 0) {
140
+ const remainderResult = buildSegment(remainder)
155
141
  // Insert "e" before remainder if it doesn't start with hundreds (< 100)
156
- if (!SEGMENTS_STARTS_WITH_HUNDREDS[remainder]) {
157
- result += ' e ' + SEGMENTS[remainder]
142
+ if (!remainderResult.startsWithHundreds) {
143
+ result += ' e ' + remainderResult.word
158
144
  } else {
159
- result += ' ' + SEGMENTS[remainder]
145
+ result += ' ' + remainderResult.word
160
146
  }
161
147
  }
162
148
 
@@ -201,11 +187,11 @@ function buildLargeNumberWords (n) {
201
187
  const segment = segments[i]
202
188
  if (segment === 0) continue
203
189
 
204
- const segmentWord = SEGMENTS[segment]
190
+ const segmentResult = buildSegment(segment)
205
191
  const isLastSegment = (i === firstNonZeroIdx)
206
192
 
207
193
  // Add "e" before final segment if previous was scale and this doesn't start with hundreds
208
- if (result && isLastSegment && prevWasScale && !SEGMENTS_STARTS_WITH_HUNDREDS[segment]) {
194
+ if (result && isLastSegment && prevWasScale && !segmentResult.startsWithHundreds) {
209
195
  result += ' e'
210
196
  }
211
197
 
@@ -213,23 +199,23 @@ function buildLargeNumberWords (n) {
213
199
 
214
200
  if (i === 0) {
215
201
  // Units segment
216
- result += segmentWord
202
+ result += segmentResult.word
217
203
  prevWasScale = false
218
204
  } else if (i === 1) {
219
205
  // Thousands
220
206
  if (segment === 1) {
221
207
  result += THOUSAND
222
208
  } else {
223
- result += segmentWord + ' ' + THOUSAND
209
+ result += segmentResult.word + ' ' + THOUSAND
224
210
  }
225
211
  prevWasScale = true
226
212
  } else {
227
- // Million and above - use precomputed scale arrays
213
+ // Million and above - use scale arrays
228
214
  const scaleWord = segment === 1 ? SCALE_WORDS_SINGULAR[i] : SCALE_WORDS_PLURAL[i]
229
215
  if (segment === 1) {
230
216
  result += 'um ' + scaleWord
231
217
  } else {
232
- result += segmentWord + ' ' + scaleWord
218
+ result += segmentResult.word + ' ' + scaleWord
233
219
  }
234
220
  prevWasScale = true
235
221
  }