n2words 2.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 (335) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +86 -188
  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 -0
  8. package/dist/languages/ar.js.map +1 -0
  9. package/dist/languages/az.js +3 -0
  10. package/dist/languages/az.js.map +1 -0
  11. package/dist/languages/bn.js +3 -0
  12. package/dist/languages/bn.js.map +1 -0
  13. package/dist/languages/cs.js +3 -0
  14. package/dist/languages/cs.js.map +1 -0
  15. package/dist/languages/da.js +3 -0
  16. package/dist/languages/da.js.map +1 -0
  17. package/dist/languages/de.js +3 -0
  18. package/dist/languages/de.js.map +1 -0
  19. package/dist/languages/el.js +3 -0
  20. package/dist/languages/el.js.map +1 -0
  21. package/dist/languages/en.js +3 -0
  22. package/dist/languages/en.js.map +1 -0
  23. package/dist/languages/es.js +3 -0
  24. package/dist/languages/es.js.map +1 -0
  25. package/dist/languages/fa.js +3 -0
  26. package/dist/languages/fa.js.map +1 -0
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -0
  30. package/dist/languages/fil.js.map +1 -0
  31. package/dist/languages/fr-BE.js +3 -0
  32. package/dist/languages/fr-BE.js.map +1 -0
  33. package/dist/languages/fr.js +3 -0
  34. package/dist/languages/fr.js.map +1 -0
  35. package/dist/languages/gu.js +3 -0
  36. package/dist/languages/gu.js.map +1 -0
  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 -0
  42. package/dist/languages/he.js.map +1 -0
  43. package/dist/languages/hi.js +3 -0
  44. package/dist/languages/hi.js.map +1 -0
  45. package/dist/languages/hr.js +3 -0
  46. package/dist/languages/hr.js.map +1 -0
  47. package/dist/languages/hu.js +3 -0
  48. package/dist/languages/hu.js.map +1 -0
  49. package/dist/languages/id.js +3 -0
  50. package/dist/languages/id.js.map +1 -0
  51. package/dist/languages/it.js +3 -0
  52. package/dist/languages/it.js.map +1 -0
  53. package/dist/languages/ja.js +3 -0
  54. package/dist/languages/ja.js.map +1 -0
  55. package/dist/languages/ka.js +3 -0
  56. package/dist/languages/ka.js.map +1 -0
  57. package/dist/languages/kn.js +3 -0
  58. package/dist/languages/kn.js.map +1 -0
  59. package/dist/languages/ko.js +3 -0
  60. package/dist/languages/ko.js.map +1 -0
  61. package/dist/languages/lt.js +3 -0
  62. package/dist/languages/lt.js.map +1 -0
  63. package/dist/languages/lv.js +3 -0
  64. package/dist/languages/lv.js.map +1 -0
  65. package/dist/languages/mr.js +3 -0
  66. package/dist/languages/mr.js.map +1 -0
  67. package/dist/languages/ms.js +3 -0
  68. package/dist/languages/ms.js.map +1 -0
  69. package/dist/languages/nb.js +3 -0
  70. package/dist/languages/nb.js.map +1 -0
  71. package/dist/languages/nl.js +3 -0
  72. package/dist/languages/nl.js.map +1 -0
  73. package/dist/languages/pa.js +3 -0
  74. package/dist/languages/pa.js.map +1 -0
  75. package/dist/languages/pl.js +3 -0
  76. package/dist/languages/pl.js.map +1 -0
  77. package/dist/languages/pt.js +3 -0
  78. package/dist/languages/pt.js.map +1 -0
  79. package/dist/languages/ro.js +3 -0
  80. package/dist/languages/ro.js.map +1 -0
  81. package/dist/languages/ru.js +3 -0
  82. package/dist/languages/ru.js.map +1 -0
  83. package/dist/languages/sr-Cyrl.js +3 -0
  84. package/dist/languages/sr-Cyrl.js.map +1 -0
  85. package/dist/languages/sr-Latn.js +3 -0
  86. package/dist/languages/sr-Latn.js.map +1 -0
  87. package/dist/languages/sv.js +3 -0
  88. package/dist/languages/sv.js.map +1 -0
  89. package/dist/languages/sw.js +3 -0
  90. package/dist/languages/sw.js.map +1 -0
  91. package/dist/languages/ta.js +3 -0
  92. package/dist/languages/ta.js.map +1 -0
  93. package/dist/languages/te.js +3 -0
  94. package/dist/languages/te.js.map +1 -0
  95. package/dist/languages/th.js +3 -0
  96. package/dist/languages/th.js.map +1 -0
  97. package/dist/languages/tr.js +3 -0
  98. package/dist/languages/tr.js.map +1 -0
  99. package/dist/languages/uk.js +3 -0
  100. package/dist/languages/uk.js.map +1 -0
  101. package/dist/languages/ur.js +3 -0
  102. package/dist/languages/ur.js.map +1 -0
  103. package/dist/languages/vi.js +3 -0
  104. package/dist/languages/vi.js.map +1 -0
  105. package/dist/languages/yo.js +3 -0
  106. package/dist/languages/yo.js.map +1 -0
  107. package/dist/languages/zh-Hans.js +3 -0
  108. package/dist/languages/zh-Hans.js.map +1 -0
  109. package/dist/languages/zh-Hant.js +3 -0
  110. package/dist/languages/zh-Hant.js.map +1 -0
  111. package/dist/n2words.js +2 -2
  112. package/dist/n2words.js.map +1 -1
  113. package/lib/languages/am-Latn.d.ts +7 -0
  114. package/lib/languages/am-Latn.js +159 -0
  115. package/lib/languages/am.d.ts +7 -0
  116. package/lib/languages/am.js +159 -0
  117. package/lib/languages/ar.d.ts +14 -27
  118. package/lib/languages/ar.js +175 -129
  119. package/lib/languages/az.d.ts +4 -9
  120. package/lib/languages/az.js +166 -37
  121. package/lib/languages/bn.d.ts +4 -8
  122. package/lib/languages/bn.js +159 -124
  123. package/lib/languages/cs.d.ts +15 -85
  124. package/lib/languages/cs.js +293 -114
  125. package/lib/languages/da.d.ts +11 -12
  126. package/lib/languages/da.js +269 -101
  127. package/lib/languages/de.d.ts +14 -11
  128. package/lib/languages/de.js +305 -86
  129. package/lib/languages/el.d.ts +11 -11
  130. package/lib/languages/el.js +224 -78
  131. package/lib/languages/en.d.ts +14 -13
  132. package/lib/languages/en.js +228 -72
  133. package/lib/languages/es.d.ts +18 -12
  134. package/lib/languages/es.js +297 -103
  135. package/lib/languages/fa.d.ts +4 -44
  136. package/lib/languages/fa.js +112 -122
  137. package/lib/languages/fi.d.ts +14 -0
  138. package/lib/languages/fi.js +238 -0
  139. package/lib/languages/fil.d.ts +4 -13
  140. package/lib/languages/fil.js +196 -106
  141. package/lib/languages/fr-BE.d.ts +8 -8
  142. package/lib/languages/fr-BE.js +285 -19
  143. package/lib/languages/fr.d.ts +18 -12
  144. package/lib/languages/fr.js +339 -89
  145. package/lib/languages/gu.d.ts +4 -8
  146. package/lib/languages/gu.js +143 -125
  147. package/lib/languages/ha.d.ts +7 -0
  148. package/lib/languages/ha.js +225 -0
  149. package/lib/languages/hbo.d.ts +10 -110
  150. package/lib/languages/hbo.js +245 -214
  151. package/lib/languages/he.d.ts +10 -77
  152. package/lib/languages/he.js +231 -172
  153. package/lib/languages/hi.d.ts +4 -8
  154. package/lib/languages/hi.js +163 -124
  155. package/lib/languages/hr.d.ts +8 -77
  156. package/lib/languages/hr.js +200 -89
  157. package/lib/languages/hu.d.ts +4 -19
  158. package/lib/languages/hu.js +198 -119
  159. package/lib/languages/id.d.ts +4 -34
  160. package/lib/languages/id.js +166 -129
  161. package/lib/languages/it.d.ts +16 -34
  162. package/lib/languages/it.js +307 -97
  163. package/lib/languages/ja.d.ts +14 -14
  164. package/lib/languages/ja.js +221 -111
  165. package/lib/languages/ka.d.ts +17 -0
  166. package/lib/languages/ka.js +291 -0
  167. package/lib/languages/kn.d.ts +4 -8
  168. package/lib/languages/kn.js +143 -35
  169. package/lib/languages/ko.d.ts +11 -11
  170. package/lib/languages/ko.js +250 -49
  171. package/lib/languages/lt.d.ts +15 -67
  172. package/lib/languages/lt.js +287 -122
  173. package/lib/languages/lv.d.ts +15 -67
  174. package/lib/languages/lv.js +288 -106
  175. package/lib/languages/mr.d.ts +4 -8
  176. package/lib/languages/mr.js +143 -125
  177. package/lib/languages/ms.d.ts +4 -28
  178. package/lib/languages/ms.js +166 -116
  179. package/lib/languages/nb.d.ts +11 -9
  180. package/lib/languages/nb.js +272 -87
  181. package/lib/languages/nl.d.ts +23 -13
  182. package/lib/languages/nl.js +299 -133
  183. package/lib/languages/pa.d.ts +4 -8
  184. package/lib/languages/pa.js +151 -124
  185. package/lib/languages/pl.d.ts +19 -77
  186. package/lib/languages/pl.js +294 -87
  187. package/lib/languages/pt.d.ts +14 -26
  188. package/lib/languages/pt.js +272 -92
  189. package/lib/languages/ro.d.ts +15 -155
  190. package/lib/languages/ro.js +219 -235
  191. package/lib/languages/ru.d.ts +8 -82
  192. package/lib/languages/ru.js +239 -90
  193. package/lib/languages/sr-Cyrl.d.ts +8 -77
  194. package/lib/languages/sr-Cyrl.js +197 -89
  195. package/lib/languages/sr-Latn.d.ts +8 -77
  196. package/lib/languages/sr-Latn.js +197 -89
  197. package/lib/languages/sv.d.ts +11 -11
  198. package/lib/languages/sv.js +278 -74
  199. package/lib/languages/sw.d.ts +4 -36
  200. package/lib/languages/sw.js +133 -106
  201. package/lib/languages/ta.d.ts +4 -17
  202. package/lib/languages/ta.js +143 -202
  203. package/lib/languages/te.d.ts +4 -19
  204. package/lib/languages/te.js +133 -196
  205. package/lib/languages/th.d.ts +4 -14
  206. package/lib/languages/th.js +135 -91
  207. package/lib/languages/tr.d.ts +15 -9
  208. package/lib/languages/tr.js +245 -49
  209. package/lib/languages/uk.d.ts +8 -82
  210. package/lib/languages/uk.js +206 -78
  211. package/lib/languages/ur.d.ts +4 -8
  212. package/lib/languages/ur.js +151 -124
  213. package/lib/languages/vi.d.ts +14 -69
  214. package/lib/languages/vi.js +278 -129
  215. package/lib/languages/yo.d.ts +7 -0
  216. package/lib/languages/yo.js +303 -0
  217. package/lib/languages/zh-Hans.d.ts +8 -18
  218. package/lib/languages/zh-Hans.js +163 -92
  219. package/lib/languages/zh-Hant.d.ts +8 -18
  220. package/lib/languages/zh-Hant.js +181 -90
  221. package/lib/n2words.d.ts +55 -209
  222. package/lib/n2words.js +115 -530
  223. package/lib/utils/is-plain-object.d.ts +13 -0
  224. package/lib/utils/is-plain-object.js +17 -0
  225. package/lib/utils/parse-numeric.d.ts +17 -0
  226. package/lib/utils/parse-numeric.js +108 -0
  227. package/lib/utils/validate-options.d.ts +8 -0
  228. package/lib/utils/validate-options.js +16 -0
  229. package/package.json +26 -14
  230. package/dist/ArabicConverter.js +0 -3
  231. package/dist/ArabicConverter.js.map +0 -1
  232. package/dist/AzerbaijaniConverter.js +0 -3
  233. package/dist/AzerbaijaniConverter.js.map +0 -1
  234. package/dist/BanglaConverter.js +0 -3
  235. package/dist/BanglaConverter.js.map +0 -1
  236. package/dist/BiblicalHebrewConverter.js +0 -3
  237. package/dist/BiblicalHebrewConverter.js.map +0 -1
  238. package/dist/CroatianConverter.js +0 -3
  239. package/dist/CroatianConverter.js.map +0 -1
  240. package/dist/CzechConverter.js +0 -3
  241. package/dist/CzechConverter.js.map +0 -1
  242. package/dist/DanishConverter.js +0 -3
  243. package/dist/DanishConverter.js.map +0 -1
  244. package/dist/DutchConverter.js +0 -3
  245. package/dist/DutchConverter.js.map +0 -1
  246. package/dist/EnglishConverter.js +0 -3
  247. package/dist/EnglishConverter.js.map +0 -1
  248. package/dist/FilipinoConverter.js +0 -3
  249. package/dist/FilipinoConverter.js.map +0 -1
  250. package/dist/FrenchBelgiumConverter.js +0 -3
  251. package/dist/FrenchBelgiumConverter.js.map +0 -1
  252. package/dist/FrenchConverter.js +0 -3
  253. package/dist/FrenchConverter.js.map +0 -1
  254. package/dist/GermanConverter.js +0 -3
  255. package/dist/GermanConverter.js.map +0 -1
  256. package/dist/GreekConverter.js +0 -3
  257. package/dist/GreekConverter.js.map +0 -1
  258. package/dist/GujaratiConverter.js +0 -3
  259. package/dist/GujaratiConverter.js.map +0 -1
  260. package/dist/HebrewConverter.js +0 -3
  261. package/dist/HebrewConverter.js.map +0 -1
  262. package/dist/HindiConverter.js +0 -3
  263. package/dist/HindiConverter.js.map +0 -1
  264. package/dist/HungarianConverter.js +0 -3
  265. package/dist/HungarianConverter.js.map +0 -1
  266. package/dist/IndonesianConverter.js +0 -3
  267. package/dist/IndonesianConverter.js.map +0 -1
  268. package/dist/ItalianConverter.js +0 -3
  269. package/dist/ItalianConverter.js.map +0 -1
  270. package/dist/JapaneseConverter.js +0 -3
  271. package/dist/JapaneseConverter.js.map +0 -1
  272. package/dist/KannadaConverter.js +0 -3
  273. package/dist/KannadaConverter.js.map +0 -1
  274. package/dist/KoreanConverter.js +0 -3
  275. package/dist/KoreanConverter.js.map +0 -1
  276. package/dist/LatvianConverter.js +0 -3
  277. package/dist/LatvianConverter.js.map +0 -1
  278. package/dist/LithuanianConverter.js +0 -3
  279. package/dist/LithuanianConverter.js.map +0 -1
  280. package/dist/MalayConverter.js +0 -3
  281. package/dist/MalayConverter.js.map +0 -1
  282. package/dist/MarathiConverter.js +0 -3
  283. package/dist/MarathiConverter.js.map +0 -1
  284. package/dist/NorwegianBokmalConverter.js +0 -3
  285. package/dist/NorwegianBokmalConverter.js.map +0 -1
  286. package/dist/PersianConverter.js +0 -3
  287. package/dist/PersianConverter.js.map +0 -1
  288. package/dist/PolishConverter.js +0 -3
  289. package/dist/PolishConverter.js.map +0 -1
  290. package/dist/PortugueseConverter.js +0 -3
  291. package/dist/PortugueseConverter.js.map +0 -1
  292. package/dist/PunjabiConverter.js +0 -3
  293. package/dist/PunjabiConverter.js.map +0 -1
  294. package/dist/RomanianConverter.js +0 -3
  295. package/dist/RomanianConverter.js.map +0 -1
  296. package/dist/RussianConverter.js +0 -3
  297. package/dist/RussianConverter.js.map +0 -1
  298. package/dist/SerbianCyrillicConverter.js +0 -3
  299. package/dist/SerbianCyrillicConverter.js.map +0 -1
  300. package/dist/SerbianLatinConverter.js +0 -3
  301. package/dist/SerbianLatinConverter.js.map +0 -1
  302. package/dist/SimplifiedChineseConverter.js +0 -3
  303. package/dist/SimplifiedChineseConverter.js.map +0 -1
  304. package/dist/SpanishConverter.js +0 -3
  305. package/dist/SpanishConverter.js.map +0 -1
  306. package/dist/SwahiliConverter.js +0 -3
  307. package/dist/SwahiliConverter.js.map +0 -1
  308. package/dist/SwedishConverter.js +0 -3
  309. package/dist/SwedishConverter.js.map +0 -1
  310. package/dist/TamilConverter.js +0 -3
  311. package/dist/TamilConverter.js.map +0 -1
  312. package/dist/TeluguConverter.js +0 -3
  313. package/dist/TeluguConverter.js.map +0 -1
  314. package/dist/ThaiConverter.js +0 -3
  315. package/dist/ThaiConverter.js.map +0 -1
  316. package/dist/TraditionalChineseConverter.js +0 -3
  317. package/dist/TraditionalChineseConverter.js.map +0 -1
  318. package/dist/TurkishConverter.js +0 -3
  319. package/dist/TurkishConverter.js.map +0 -1
  320. package/dist/UkrainianConverter.js +0 -3
  321. package/dist/UkrainianConverter.js.map +0 -1
  322. package/dist/UrduConverter.js +0 -3
  323. package/dist/UrduConverter.js.map +0 -1
  324. package/dist/VietnameseConverter.js +0 -3
  325. package/dist/VietnameseConverter.js.map +0 -1
  326. package/lib/classes/abstract-language.d.ts +0 -178
  327. package/lib/classes/abstract-language.js +0 -268
  328. package/lib/classes/greedy-scale-language.d.ts +0 -109
  329. package/lib/classes/greedy-scale-language.js +0 -201
  330. package/lib/classes/slavic-language.d.ts +0 -148
  331. package/lib/classes/slavic-language.js +0 -281
  332. package/lib/classes/south-asian-language.d.ts +0 -70
  333. package/lib/classes/south-asian-language.js +0 -154
  334. package/lib/classes/turkic-language.d.ts +0 -26
  335. package/lib/classes/turkic-language.js +0 -59
@@ -1,112 +1,292 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
1
+ /**
2
+ * Portuguese language converter - Functional Implementation
3
+ *
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
+ *
6
+ * Portuguese-specific rules:
7
+ * - "e" conjunction everywhere: vinte e um, cento e um, mil e um
8
+ * - "cem" for exact 100, "cento" for 100+ remainder
9
+ * - Irregular hundreds: duzentos, trezentos, quatrocentos, etc.
10
+ * - Compound scale: milhão (10^6), mil milhões (10^9), bilião (10^12)
11
+ * - Omit "um" before "mil"
12
+ */
13
+
14
+ import { parseNumericValue } from '../utils/parse-numeric.js'
15
+
16
+ // ============================================================================
17
+ // Vocabulary (module-level constants)
18
+ // ============================================================================
19
+
20
+ const ONES = ['', 'um', 'dois', 'três', 'quatro', 'cinco', 'seis', 'sete', 'oito', 'nove']
21
+ const TEENS = ['dez', 'onze', 'doze', 'treze', 'catorze', 'quinze', 'dezasseis', 'dezassete', 'dezoito', 'dezanove']
22
+ const TENS = ['', '', 'vinte', 'trinta', 'quarenta', 'cinquenta', 'sessenta', 'setenta', 'oitenta', 'noventa']
23
+
24
+ // Irregular hundreds
25
+ const HUNDREDS = ['', 'cento', 'duzentos', 'trezentos', 'quatrocentos', 'quinhentos', 'seiscentos', 'setecentos', 'oitocentos', 'novecentos']
26
+
27
+ const THOUSAND = 'mil'
28
+ const ZERO = 'zero'
29
+ const NEGATIVE = 'menos'
30
+ const DECIMAL_SEP = 'vírgula'
31
+
32
+ // ============================================================================
33
+ // Segment Building
34
+ // ============================================================================
35
+
36
+ /**
37
+ * Builds segment word for 0-999 with Portuguese "e" rules.
38
+ * Returns the word and whether it's an exact hundred (for "cem" handling).
39
+ */
40
+ function buildSegment (n) {
41
+ if (n === 0) return { word: '', isExactHundred: false }
42
+
43
+ // Special case: exact 100 is "cem"
44
+ if (n === 100) return { word: 'cem', isExactHundred: true }
45
+
46
+ const ones = n % 10
47
+ const tens = Math.floor(n / 10) % 10
48
+ const hundreds = Math.floor(n / 100)
49
+
50
+ const parts = []
51
+
52
+ // Hundreds
53
+ if (hundreds > 0) {
54
+ parts.push(HUNDREDS[hundreds])
55
+ }
56
+
57
+ // Tens and ones
58
+ if (tens === 1) {
59
+ // Teens (10-19)
60
+ parts.push(TEENS[ones])
61
+ } else if (tens >= 2) {
62
+ if (ones > 0) {
63
+ // Tens + ones with "e": "vinte e um"
64
+ parts.push(TENS[tens] + ' e ' + ONES[ones])
65
+ } else {
66
+ parts.push(TENS[tens])
67
+ }
68
+ } else if (ones > 0) {
69
+ parts.push(ONES[ones])
70
+ }
71
+
72
+ // Join hundreds with "e": "cento e um", "duzentos e trinta e um"
73
+ const word = parts.join(' e ')
74
+
75
+ return { word, isExactHundred: hundreds > 0 && tens === 0 && ones === 0, startsWithHundreds: n >= 100 }
76
+ }
77
+
78
+ // ============================================================================
79
+ // Scale Word Lookup
80
+ // ============================================================================
81
+
82
+ // Precompute scale words for singular and plural forms
83
+ // Index 1 = thousands, 2 = millions, 3 = billions (mil milhões), etc.
84
+ const SCALE_WORDS_SINGULAR = [
85
+ '', // 0 unused
86
+ THOUSAND, // 1: mil
87
+ 'milhão', // 2: 10^6
88
+ 'mil milhões', // 3: 10^9 (compound)
89
+ 'bilião', // 4: 10^12
90
+ 'mil biliões', // 5: 10^15 (compound)
91
+ 'trilião', // 6: 10^18
92
+ 'mil triliões', // 7: 10^21 (compound)
93
+ 'quatrilião' // 8: 10^24
94
+ ]
95
+
96
+ const SCALE_WORDS_PLURAL = [
97
+ '', // 0 unused
98
+ THOUSAND, // 1: mil (same)
99
+ 'milhões', // 2: 10^6
100
+ 'mil milhões', // 3: 10^9 (compound, same)
101
+ 'biliões', // 4: 10^12
102
+ 'mil biliões', // 5: 10^15 (compound, same)
103
+ 'triliões', // 6: 10^18
104
+ 'mil triliões', // 7: 10^21 (compound, same)
105
+ 'quatriliões' // 8: 10^24
106
+ ]
107
+
108
+ // ============================================================================
109
+ // Conversion Functions
110
+ // ============================================================================
2
111
 
3
112
  /**
4
- * (European) Portuguese language converter.
113
+ * Converts a non-negative integer to Portuguese words.
5
114
  *
6
- * Supports:
7
- * - Gender-aware hundreds (duzentos, trezentos)
8
- * - "e" (and) conjunction for number combinations
9
- * - Post-processing to normalize word flow
115
+ * @param {bigint} n - Non-negative integer to convert
116
+ * @returns {string} Portuguese words
10
117
  */
11
- export class Portuguese extends GreedyScaleLanguage {
12
- negativeWord = 'menos'
13
- decimalSeparatorWord = 'vírgula'
14
- zeroWord = 'zero'
15
-
16
- scaleWords = [
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
- hundredsWords = {
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'
118
+ function integerToWords (n) {
119
+ if (n === 0n) return ZERO
120
+
121
+ // Fast path: numbers < 1000
122
+ if (n < 1000n) {
123
+ return buildSegment(Number(n)).word
124
+ }
125
+
126
+ // Fast path: numbers < 1,000,000 (thousands)
127
+ if (n < 1_000_000n) {
128
+ const thousands = Number(n / 1000n)
129
+ const remainder = Number(n % 1000n)
130
+
131
+ let result
132
+ if (thousands === 1) {
133
+ // "mil" not "um mil"
134
+ result = THOUSAND
135
+ } else {
136
+ result = buildSegment(thousands).word + ' ' + THOUSAND
137
+ }
138
+
139
+ if (remainder > 0) {
140
+ const remainderResult = buildSegment(remainder)
141
+ // Insert "e" before remainder if it doesn't start with hundreds (< 100)
142
+ if (!remainderResult.startsWithHundreds) {
143
+ result += ' e ' + remainderResult.word
144
+ } else {
145
+ result += ' ' + remainderResult.word
146
+ }
147
+ }
148
+
149
+ return result
63
150
  }
64
151
 
65
- // Pre-compiled regex patterns for postClean - avoid recompilation
66
- static POSTCLEAN_REGEX = / e (.*entos?) (?=.*e)/g
152
+ // For numbers >= 1,000,000, use scale decomposition
153
+ return buildLargeNumberWords(n)
154
+ }
67
155
 
68
- finalizeWords (words) {
69
- return words.replaceAll(Portuguese.POSTCLEAN_REGEX, ' $1 ')
156
+ /**
157
+ * Builds words for numbers >= 1,000,000.
158
+ * Uses BigInt division for faster segment extraction.
159
+ *
160
+ * @param {bigint} n - Number >= 1,000,000
161
+ * @returns {string} Portuguese words
162
+ */
163
+ function buildLargeNumberWords (n) {
164
+ // Extract segments using BigInt division
165
+ // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
166
+ const segments = []
167
+ let temp = n
168
+ while (temp > 0n) {
169
+ segments.push(Number(temp % 1000n))
170
+ temp = temp / 1000n
70
171
  }
71
172
 
72
- /** Combines two word-sets according to Portuguese grammar rules. */
73
- combineWordSets (preceding, following) {
74
- // Extract words and numeric values
75
- let precedingWord = Object.keys(preceding)[0]
76
- let followingWord = Object.keys(following)[0]
77
- const precedingValue = Object.values(preceding)[0] // BigInt
78
- const followingValue = Object.values(following)[0] // BigInt
79
-
80
- // Implicit "um": omit before millions ("um milhão" → "milhão")
81
- if (precedingValue === 1n) {
82
- if (followingValue < 1_000_000n) return { [followingWord]: followingValue }
83
- precedingWord = 'um'
84
- } else if (precedingValue === 100n && followingValue % 1000n !== 0n) {
85
- // Special handling: "cem" (100) becomes "cento" when followed by non-thousands
86
- precedingWord = 'cento'
173
+ // Find the first non-zero segment index (lowest scale with value)
174
+ let firstNonZeroIdx = 0
175
+ for (let i = 0; i < segments.length; i++) {
176
+ if (segments[i] !== 0) {
177
+ firstNonZeroIdx = i
178
+ break
87
179
  }
180
+ }
181
+
182
+ // Build result string directly
183
+ let result = ''
184
+ let prevWasScale = false
185
+
186
+ for (let i = segments.length - 1; i >= 0; i--) {
187
+ const segment = segments[i]
188
+ if (segment === 0) continue
189
+
190
+ const segmentResult = buildSegment(segment)
191
+ const isLastSegment = (i === firstNonZeroIdx)
88
192
 
89
- if (followingValue < precedingValue) {
90
- return { [`${precedingWord} e ${followingWord}`]: precedingValue + followingValue }
193
+ // Add "e" before final segment if previous was scale and this doesn't start with hundreds
194
+ if (result && isLastSegment && prevWasScale && !segmentResult.startsWithHundreds) {
195
+ result += ' e'
91
196
  }
92
197
 
93
- // Handle "milião" -> "milhão" conversion
94
- if (followingWord === 'milião') followingWord = 'milhão'
198
+ if (result) result += ' '
95
199
 
96
- // Pluralization logic for large numbers
97
- if (precedingValue > 1n) {
98
- if (followingValue % 1_000_000_000n === 0n) {
99
- followingWord = followingWord.replace('bilião', 'biliões')
100
- } else if (followingValue % 1_000_000n === 0n) {
101
- followingWord = followingWord.replace('milhão', 'milhões')
200
+ if (i === 0) {
201
+ // Units segment
202
+ result += segmentResult.word
203
+ prevWasScale = false
204
+ } else if (i === 1) {
205
+ // Thousands
206
+ if (segment === 1) {
207
+ result += THOUSAND
208
+ } else {
209
+ result += segmentResult.word + ' ' + THOUSAND
102
210
  }
211
+ prevWasScale = true
212
+ } else {
213
+ // Million and above - use scale arrays
214
+ const scaleWord = segment === 1 ? SCALE_WORDS_SINGULAR[i] : SCALE_WORDS_PLURAL[i]
215
+ if (segment === 1) {
216
+ result += 'um ' + scaleWord
217
+ } else {
218
+ result += segmentResult.word + ' ' + scaleWord
219
+ }
220
+ prevWasScale = true
103
221
  }
222
+ }
104
223
 
105
- if (followingValue === 100n) {
106
- precedingWord = this.hundredsWords[precedingValue]
107
- return { [`${precedingWord}`]: precedingValue * followingValue }
108
- }
224
+ return result
225
+ }
226
+
227
+ /**
228
+ * Converts decimal digits to Portuguese words.
229
+ *
230
+ * @param {string} decimalPart - Decimal digits (without the point)
231
+ * @returns {string} Portuguese words for decimal part
232
+ */
233
+ function decimalPartToWords (decimalPart) {
234
+ let result = ''
235
+
236
+ // Handle leading zeros
237
+ let i = 0
238
+ while (i < decimalPart.length && decimalPart[i] === '0') {
239
+ if (result) result += ' '
240
+ result += ZERO
241
+ i++
242
+ }
243
+
244
+ // Convert remainder as a single number
245
+ const remainder = decimalPart.slice(i)
246
+ if (remainder) {
247
+ if (result) result += ' '
248
+ result += integerToWords(BigInt(remainder))
249
+ }
250
+
251
+ return result
252
+ }
253
+
254
+ /**
255
+ * Converts a numeric value to Portuguese words.
256
+ *
257
+ * This is the main public API. It accepts any valid numeric input
258
+ * (number, string, or bigint) and handles parsing internally.
259
+ *
260
+ * @param {number | string | bigint} value - The numeric value to convert
261
+ * @returns {string} The number in Portuguese words
262
+ * @throws {TypeError} If value is not a valid numeric type
263
+ * @throws {Error} If value is not a valid number format
264
+ *
265
+ * @example
266
+ * toWords(21) // 'vinte e um'
267
+ * toWords(100) // 'cem'
268
+ * toWords(1000000) // 'um milhão'
269
+ */
270
+ function toWords (value) {
271
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
272
+
273
+ let result = ''
109
274
 
110
- return { [`${precedingWord} ${followingWord}`]: precedingValue * followingValue }
275
+ if (isNegative) {
276
+ result = NEGATIVE + ' '
111
277
  }
278
+
279
+ result += integerToWords(integerPart)
280
+
281
+ if (decimalPart) {
282
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
283
+ }
284
+
285
+ return result
112
286
  }
287
+
288
+ // ============================================================================
289
+ // Public API
290
+ // ============================================================================
291
+
292
+ export { toWords }
@@ -1,158 +1,18 @@
1
1
  /**
2
- * Romanian language converter.
2
+ * Converts a numeric value to Romanian words.
3
3
  *
4
- * Supports:
5
- * - Gender agreement (unu/una, doi/două)
6
- * - Complex pluralization (singular/plural forms)
7
- * - "De" preposition insertion for groups >= 20
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'
8
15
  */
9
- export class Romanian extends AbstractLanguage {
10
- constructor(options?: {});
11
- onesWords: {
12
- 1: string;
13
- 2: string;
14
- 3: string;
15
- 4: string;
16
- 5: string;
17
- 6: string;
18
- 7: string;
19
- 8: string;
20
- 9: string;
21
- };
22
- onesFeminineWords: {
23
- 1: string;
24
- 2: string;
25
- 3: string;
26
- 4: string;
27
- 5: string;
28
- 6: string;
29
- 7: string;
30
- 8: string;
31
- 9: string;
32
- };
33
- teensWords: {
34
- 0: string;
35
- 1: string;
36
- 2: string;
37
- 3: string;
38
- 4: string;
39
- 5: string;
40
- 6: string;
41
- 7: string;
42
- 8: string;
43
- 9: string;
44
- };
45
- teensMasculineWords: {
46
- 0: string;
47
- 1: string;
48
- 2: string;
49
- 3: string;
50
- 4: string;
51
- 5: string;
52
- 6: string;
53
- 7: string;
54
- 8: string;
55
- 9: string;
56
- };
57
- twentiesWords: {
58
- 2: string;
59
- 3: string;
60
- 4: string;
61
- 5: string;
62
- 6: string;
63
- 7: string;
64
- 8: string;
65
- 9: string;
66
- };
67
- hundredsWords: {
68
- 1: string;
69
- 2: string;
70
- 3: string;
71
- 4: string;
72
- 5: string;
73
- 6: string;
74
- 7: string;
75
- 8: string;
76
- 9: string;
77
- };
78
- /**
79
- * Romanian big units.
80
- * For each power group we keep: singular, plural, feminineUnits?, needsDe?
81
- * - 10^3: mie/mii (feminine units in segment; "de" for segment >= 20)
82
- * - 10^6: milion/milioane ("de" for segment >= 20)
83
- * - 10^9: miliard/miliarde ("de" for segment >= 20)
84
- */
85
- scaleMetadata: {
86
- 1: {
87
- singular: string;
88
- plural: string;
89
- feminine: boolean;
90
- needsDe: boolean;
91
- };
92
- 2: {
93
- singular: string;
94
- plural: string;
95
- feminine: boolean;
96
- needsDe: boolean;
97
- };
98
- 3: {
99
- singular: string;
100
- plural: string;
101
- feminine: boolean;
102
- needsDe: boolean;
103
- };
104
- 4: {
105
- singular: string;
106
- plural: string;
107
- feminine: boolean;
108
- needsDe: boolean;
109
- };
110
- 5: {
111
- singular: string;
112
- plural: string;
113
- feminine: boolean;
114
- needsDe: boolean;
115
- };
116
- 6: {
117
- singular: string;
118
- plural: string;
119
- feminine: boolean;
120
- needsDe: boolean;
121
- };
122
- 7: {
123
- singular: string;
124
- plural: string;
125
- feminine: boolean;
126
- needsDe: boolean;
127
- };
128
- 8: {
129
- singular: string;
130
- plural: string;
131
- feminine: boolean;
132
- needsDe: boolean;
133
- };
134
- 9: {
135
- singular: string;
136
- plural: string;
137
- feminine: boolean;
138
- needsDe: boolean;
139
- };
140
- 10: {
141
- singular: string;
142
- plural: string;
143
- feminine: boolean;
144
- needsDe: boolean;
145
- };
146
- };
147
- /** Split numeric string into BigInt segments of specified size from left to right. */
148
- splitToSegments(n: any, x: any): bigint[];
149
- extractDigits(value: any): any;
150
- /** Romanian pluralization & "de" rule for big units. */
151
- romanianPluralize(segment: any, form: any): string;
152
- spellUnder100(n: any, feminineUnits?: boolean, masculineTeens?: boolean): any;
153
- spellUnder1000(n: any, feminineUnits?: boolean, masculineTeens?: boolean): any;
154
- /** Decimals always use masculine forms regardless of gender option. */
155
- decimalIntegerToWords(integerPart: any): any;
156
- integerToWords(integerPart: any): string;
157
- }
158
- import { AbstractLanguage } from '../classes/abstract-language.js';
16
+ export function toWords(value: number | string | bigint, options?: {
17
+ gender?: string | undefined;
18
+ }): string;