n2words 1.24.0 → 3.0.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 (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,147 +1,306 @@
1
- import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
1
+ /**
2
+ * Portuguese language converter - Functional Implementation
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ * Self-contained module with its own input validation, ready for subpath exports.
6
+ *
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):
11
+ * - "e" conjunction everywhere: vinte e um, cento e um, mil e um
12
+ * - "cem" for exact 100, "cento" for 100+ remainder
13
+ * - Irregular hundreds: duzentos, trezentos, quatrocentos, etc.
14
+ * - Compound scale: milhão (10^6), mil milhões (10^9), bilião (10^12)
15
+ * - Omit "um" before "mil"
16
+ */
17
+
18
+ import { parseNumericValue } from '../utils/parse-numeric.js'
19
+
20
+ // ============================================================================
21
+ // Vocabulary (module-level constants)
22
+ // ============================================================================
23
+
24
+ const ONES = ['', 'um', 'dois', 'três', 'quatro', 'cinco', 'seis', 'sete', 'oito', 'nove']
25
+ const TEENS = ['dez', 'onze', 'doze', 'treze', 'catorze', 'quinze', 'dezasseis', 'dezassete', 'dezoito', 'dezanove']
26
+ const TENS = ['', '', 'vinte', 'trinta', 'quarenta', 'cinquenta', 'sessenta', 'setenta', 'oitenta', 'noventa']
27
+
28
+ // Irregular hundreds
29
+ const HUNDREDS = ['', 'cento', 'duzentos', 'trezentos', 'quatrocentos', 'quinhentos', 'seiscentos', 'setecentos', 'oitocentos', 'novecentos']
30
+
31
+ const THOUSAND = 'mil'
32
+ const ZERO = 'zero'
33
+ const NEGATIVE = 'menos'
34
+ const DECIMAL_SEP = 'vírgula'
35
+
36
+ // ============================================================================
37
+ // Precomputed Lookup Tables (built once at module load)
38
+ // ============================================================================
2
39
 
3
40
  /**
4
- * (European) Portuguese language converter.
41
+ * Builds segment word for 0-999 with Portuguese "e" rules.
42
+ * Returns the word and whether it's an exact hundred (for "cem" handling).
43
+ */
44
+ function buildSegment (n) {
45
+ if (n === 0) return { word: '', isExactHundred: false }
46
+
47
+ // Special case: exact 100 is "cem"
48
+ if (n === 100) return { word: 'cem', isExactHundred: true }
49
+
50
+ const ones = n % 10
51
+ const tens = Math.floor(n / 10) % 10
52
+ const hundreds = Math.floor(n / 100)
53
+
54
+ const parts = []
55
+
56
+ // Hundreds
57
+ if (hundreds > 0) {
58
+ parts.push(HUNDREDS[hundreds])
59
+ }
60
+
61
+ // Tens and ones
62
+ if (tens === 1) {
63
+ // Teens (10-19)
64
+ parts.push(TEENS[ones])
65
+ } else if (tens >= 2) {
66
+ if (ones > 0) {
67
+ // Tens + ones with "e": "vinte e um"
68
+ parts.push(TENS[tens] + ' e ' + ONES[ones])
69
+ } else {
70
+ parts.push(TENS[tens])
71
+ }
72
+ } else if (ones > 0) {
73
+ parts.push(ONES[ones])
74
+ }
75
+
76
+ // Join hundreds with "e": "cento e um", "duzentos e trinta e um"
77
+ const word = parts.join(' e ')
78
+
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
91
+ }
92
+
93
+ // ============================================================================
94
+ // Scale Word Lookup (precomputed for common scales)
95
+ // ============================================================================
96
+
97
+ // Precompute scale words for singular and plural forms
98
+ // Index 1 = thousands, 2 = millions, 3 = billions (mil milhões), etc.
99
+ const SCALE_WORDS_SINGULAR = [
100
+ '', // 0 unused
101
+ THOUSAND, // 1: mil
102
+ 'milhão', // 2: 10^6
103
+ 'mil milhões', // 3: 10^9 (compound)
104
+ 'bilião', // 4: 10^12
105
+ 'mil biliões', // 5: 10^15 (compound)
106
+ 'trilião', // 6: 10^18
107
+ 'mil triliões', // 7: 10^21 (compound)
108
+ 'quatrilião' // 8: 10^24
109
+ ]
110
+
111
+ const SCALE_WORDS_PLURAL = [
112
+ '', // 0 unused
113
+ THOUSAND, // 1: mil (same)
114
+ 'milhões', // 2: 10^6
115
+ 'mil milhões', // 3: 10^9 (compound, same)
116
+ 'biliões', // 4: 10^12
117
+ 'mil biliões', // 5: 10^15 (compound, same)
118
+ 'triliões', // 6: 10^18
119
+ 'mil triliões', // 7: 10^21 (compound, same)
120
+ 'quatriliões' // 8: 10^24
121
+ ]
122
+
123
+ // ============================================================================
124
+ // Conversion Functions
125
+ // ============================================================================
126
+
127
+ /**
128
+ * Converts a non-negative integer to Portuguese words.
5
129
  *
6
- * Features:
7
- * - Gender-aware hundreds (hundredos, duzentos, etc.)
8
- * - Million/Billion pluralization
9
- * - "e" (and) conjunction for number combinations
10
- * - Post-processing to normalize word flow
130
+ * @param {bigint} n - Non-negative integer to convert
131
+ * @returns {string} Portuguese words
11
132
  */
12
- export class Portuguese extends GreedyScaleLanguage {
13
- negativeWord = 'menos'
14
- decimalSeparatorWord = 'vírgula'
15
- zeroWord = 'zero'
16
- scaleWordPairs = [
17
- [1_000_000_000_000_000_000_000_000n, 'quatrilião'],
18
- [1_000_000_000_000_000_000n, 'trilião'],
19
- [1_000_000_000_000n, 'bilião'],
20
- [1_000_000n, 'milião'],
21
- [1000n, 'mil'],
22
- [100n, 'cem'],
23
- [90n, 'noventa'],
24
- [80n, 'oitenta'],
25
- [70n, 'setenta'],
26
- [60n, 'sessenta'],
27
- [50n, 'cinquenta'],
28
- [40n, 'quarenta'],
29
- [30n, 'trinta'],
30
- [20n, 'vinte'],
31
- [19n, 'dezanove'],
32
- [18n, 'dezoito'],
33
- [17n, 'dezassete'],
34
- [16n, 'dezasseis'],
35
- [15n, 'quinze'],
36
- [14n, 'catorze'],
37
- [13n, 'treze'],
38
- [12n, 'doze'],
39
- [11n, 'onze'],
40
- [10n, 'dez'],
41
- [9n, 'nove'],
42
- [8n, 'oito'],
43
- [7n, 'sete'],
44
- [6n, 'seis'],
45
- [5n, 'cinco'],
46
- [4n, 'quatro'],
47
- [3n, 'três'],
48
- [2n, 'dois'],
49
- [1n, 'um'],
50
- [0n, 'zero']
51
- ]
52
-
53
- hundreds = {
54
- 1: 'cento',
55
- 2: 'duzentos',
56
- 3: 'trezentos',
57
- 4: 'quatrocentos',
58
- 5: 'quinhentos',
59
- 6: 'seiscentos',
60
- 7: 'setecentos',
61
- 8: 'oitocentos',
62
- 9: 'novecentos'
133
+ function integerToWords (n) {
134
+ if (n === 0n) return ZERO
135
+
136
+ // Fast path: numbers < 1000 (direct lookup)
137
+ if (n < 1000n) {
138
+ return SEGMENTS[Number(n)]
139
+ }
140
+
141
+ // Fast path: numbers < 1,000,000 (thousands)
142
+ if (n < 1_000_000n) {
143
+ const thousands = Number(n / 1000n)
144
+ const remainder = Number(n % 1000n)
145
+
146
+ let result
147
+ if (thousands === 1) {
148
+ // "mil" not "um mil"
149
+ result = THOUSAND
150
+ } else {
151
+ result = SEGMENTS[thousands] + ' ' + THOUSAND
152
+ }
153
+
154
+ if (remainder > 0) {
155
+ // Insert "e" before remainder if it doesn't start with hundreds (< 100)
156
+ if (!SEGMENTS_STARTS_WITH_HUNDREDS[remainder]) {
157
+ result += ' e ' + SEGMENTS[remainder]
158
+ } else {
159
+ result += ' ' + SEGMENTS[remainder]
160
+ }
161
+ }
162
+
163
+ return result
63
164
  }
64
165
 
65
- // Pre-compiled regex patterns for postClean - avoid recompilation
66
- static POSTCLEAN_REGEX = / e (.*entos?) (?=.*e)/g
166
+ // For numbers >= 1,000,000, use scale decomposition
167
+ return buildLargeNumberWords(n)
168
+ }
67
169
 
68
- finalizeWords (words) {
69
- return words.replaceAll(Portuguese.POSTCLEAN_REGEX, ' $1 ')
170
+ /**
171
+ * Builds words for numbers >= 1,000,000.
172
+ * Uses BigInt division for faster segment extraction.
173
+ *
174
+ * @param {bigint} n - Number >= 1,000,000
175
+ * @returns {string} Portuguese words
176
+ */
177
+ function buildLargeNumberWords (n) {
178
+ // Extract segments using BigInt division
179
+ // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
180
+ const segments = []
181
+ let temp = n
182
+ while (temp > 0n) {
183
+ segments.push(Number(temp % 1000n))
184
+ temp = temp / 1000n
70
185
  }
71
186
 
72
- /**
73
- * Merges two adjacent word-number pairs according to Portuguese grammar rules.
74
- *
75
- * Portuguese-specific rules:
76
- * - Implicit "um": `mergeScales({ 'um': 1n }, { 'mil': 1000n })` → `{ 'mil': 1000n }`
77
- * - "e" (and) between most combinations: `mergeScales({ 'vinte': 20n }, { 'três': 3n })` → `{ 'vinte e três': 23n }`
78
- * - Special handling: "cem" (100) becomes "cento" when followed by non-thousands
79
- * - Gender-aware hundreds: "duzentos", "trezentos", etc.
80
- * - Million pluralization: "milhões" instead of "milhão" when coefficient > 1
81
- * - Post-processing with postClean() to normalize word flow
82
- *
83
- * @param {Object} current The left operand as `{ word: BigInt }`.
84
- * @param {Object} next The right operand as `{ word: BigInt }`.
85
- * @returns {Object} Merged pair with combined word and resulting numeric value.
86
- *
87
- * @example
88
- * mergeScales({ 'um': 1n }, { 'mil': 1000n }); // { 'mil': 1000n }
89
- * mergeScales({ 'vinte': 20n }, { 'dois': 2n }); // { 'vinte e dois': 22n }
90
- */
91
- mergeScales (current, next) {
92
- // Extract words and numeric values
93
- let cText = Object.keys(current)[0]
94
- let nText = Object.keys(next)[0]
95
- const cNumber = Object.values(current)[0] // BigInt
96
- const nNumber = Object.values(next)[0] // BigInt
97
-
98
- // Implicit "um": omit before millions ("um milhão" → "milhão")
99
- if (cNumber === 1n) {
100
- if (nNumber < 1_000_000n) return { [nText]: nNumber }
101
- cText = 'um'
102
- } else if (cNumber === 100n && nNumber % 1000n !== 0n) {
103
- // Special handling: "cem" (100) becomes "cento" when followed by non-thousands
104
- cText = 'cento'
187
+ // Find the first non-zero segment index (lowest scale with value)
188
+ let firstNonZeroIdx = 0
189
+ for (let i = 0; i < segments.length; i++) {
190
+ if (segments[i] !== 0) {
191
+ firstNonZeroIdx = i
192
+ break
105
193
  }
194
+ }
195
+
196
+ // Build result string directly
197
+ let result = ''
198
+ let prevWasScale = false
199
+
200
+ for (let i = segments.length - 1; i >= 0; i--) {
201
+ const segment = segments[i]
202
+ if (segment === 0) continue
203
+
204
+ const segmentWord = SEGMENTS[segment]
205
+ const isLastSegment = (i === firstNonZeroIdx)
106
206
 
107
- if (nNumber < cNumber) {
108
- return { [`${cText} e ${nText}`]: cNumber + nNumber }
207
+ // 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]) {
209
+ result += ' e'
109
210
  }
110
211
 
111
- // Handle "milião" -> "milhão" conversion
112
- if (nText === 'milião') nText = 'milhão'
212
+ if (result) result += ' '
113
213
 
114
- // Pluralization logic for large numbers
115
- if (cNumber > 1n) {
116
- if (nNumber % 1_000_000_000n === 0n) {
117
- nText = nText.replace('bilião', 'biliões')
118
- } else if (nNumber % 1_000_000n === 0n) {
119
- nText = nText.replace('milhão', 'milhões')
214
+ if (i === 0) {
215
+ // Units segment
216
+ result += segmentWord
217
+ prevWasScale = false
218
+ } else if (i === 1) {
219
+ // Thousands
220
+ if (segment === 1) {
221
+ result += THOUSAND
222
+ } else {
223
+ result += segmentWord + ' ' + THOUSAND
120
224
  }
225
+ prevWasScale = true
226
+ } else {
227
+ // Million and above - use precomputed scale arrays
228
+ const scaleWord = segment === 1 ? SCALE_WORDS_SINGULAR[i] : SCALE_WORDS_PLURAL[i]
229
+ if (segment === 1) {
230
+ result += 'um ' + scaleWord
231
+ } else {
232
+ result += segmentWord + ' ' + scaleWord
233
+ }
234
+ prevWasScale = true
121
235
  }
236
+ }
122
237
 
123
- if (nNumber === 100n) {
124
- cText = this.hundreds[cNumber]
125
- return { [`${cText}`]: cNumber * nNumber }
126
- }
238
+ return result
239
+ }
127
240
 
128
- return { [`${cText} ${nText}`]: cNumber * nNumber }
241
+ /**
242
+ * Converts decimal digits to Portuguese words.
243
+ *
244
+ * @param {string} decimalPart - Decimal digits (without the point)
245
+ * @returns {string} Portuguese words for decimal part
246
+ */
247
+ function decimalPartToWords (decimalPart) {
248
+ let result = ''
249
+
250
+ // Handle leading zeros
251
+ let i = 0
252
+ while (i < decimalPart.length && decimalPart[i] === '0') {
253
+ if (result) result += ' '
254
+ result += ZERO
255
+ i++
129
256
  }
257
+
258
+ // Convert remainder as a single number
259
+ const remainder = decimalPart.slice(i)
260
+ if (remainder) {
261
+ if (result) result += ' '
262
+ result += integerToWords(BigInt(remainder))
263
+ }
264
+
265
+ return result
130
266
  }
131
267
 
132
268
  /**
133
- * Converts a number to Portuguese cardinal (written) form.
269
+ * Converts a numeric value to Portuguese words.
134
270
  *
135
- * @param {number|string|bigint} value The number to convert.
136
- * @param {Object} [options] Conversion options (see Portuguese class options).
137
- * @returns {string} The number expressed in Portuguese words.
138
- * @throws {TypeError} If value is NaN or invalid type.
139
- * @throws {Error} If value is an invalid number string.
271
+ * This is the main public API. It accepts any valid numeric input
272
+ * (number, string, or bigint) and handles parsing internally.
273
+ *
274
+ * @param {number | string | bigint} value - The numeric value to convert
275
+ * @returns {string} The number in Portuguese words
276
+ * @throws {TypeError} If value is not a valid numeric type
277
+ * @throws {Error} If value is not a valid number format
140
278
  *
141
279
  * @example
142
- * convertToWords(42); // 'quarenta e dois'
143
- * convertToWords('100.5'); // 'cem vírgula cinco'
280
+ * toWords(21) // 'vinte e um'
281
+ * toWords(100) // 'cem'
282
+ * toWords(1000000) // 'um milhão'
144
283
  */
145
- export default function convertToWords (value, options = {}) {
146
- return new Portuguese(options).convertToWords(value)
284
+ function toWords (value) {
285
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
286
+
287
+ let result = ''
288
+
289
+ if (isNegative) {
290
+ result = NEGATIVE + ' '
291
+ }
292
+
293
+ result += integerToWords(integerPart)
294
+
295
+ if (decimalPart) {
296
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
297
+ }
298
+
299
+ return result
147
300
  }
301
+
302
+ // ============================================================================
303
+ // Public API
304
+ // ============================================================================
305
+
306
+ export { toWords }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Converts a numeric value to Romanian words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @param {Object} [options] - Conversion options
6
+ * @param {string} [options.gender='masculine'] - Gender for numbers
7
+ * @returns {string} The number in Romanian words
8
+ * @throws {TypeError} If value is not a valid numeric type
9
+ * @throws {Error} If value is not a valid number format
10
+ *
11
+ * @example
12
+ * toWords(21) // 'douăzeci și unu'
13
+ * toWords(1, { gender: 'feminine' }) // 'una'
14
+ * toWords(1000) // 'o mie'
15
+ */
16
+ export function toWords(value: number | string | bigint, options?: {
17
+ gender?: string | undefined;
18
+ }): string;