n2words 2.0.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 (327) hide show
  1. package/CHANGELOG.md +49 -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/kn.js +3 -0
  56. package/dist/languages/kn.js.map +1 -0
  57. package/dist/languages/ko.js +3 -0
  58. package/dist/languages/ko.js.map +1 -0
  59. package/dist/languages/lt.js +3 -0
  60. package/dist/languages/lt.js.map +1 -0
  61. package/dist/languages/lv.js +3 -0
  62. package/dist/languages/lv.js.map +1 -0
  63. package/dist/languages/mr.js +3 -0
  64. package/dist/languages/mr.js.map +1 -0
  65. package/dist/languages/ms.js +3 -0
  66. package/dist/languages/ms.js.map +1 -0
  67. package/dist/languages/nb.js +3 -0
  68. package/dist/languages/nb.js.map +1 -0
  69. package/dist/languages/nl.js +3 -0
  70. package/dist/languages/nl.js.map +1 -0
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -0
  74. package/dist/languages/pl.js.map +1 -0
  75. package/dist/languages/pt.js +3 -0
  76. package/dist/languages/pt.js.map +1 -0
  77. package/dist/languages/ro.js +3 -0
  78. package/dist/languages/ro.js.map +1 -0
  79. package/dist/languages/ru.js +3 -0
  80. package/dist/languages/ru.js.map +1 -0
  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 -0
  84. package/dist/languages/sr-Latn.js.map +1 -0
  85. package/dist/languages/sv.js +3 -0
  86. package/dist/languages/sv.js.map +1 -0
  87. package/dist/languages/sw.js +3 -0
  88. package/dist/languages/sw.js.map +1 -0
  89. package/dist/languages/ta.js +3 -0
  90. package/dist/languages/ta.js.map +1 -0
  91. package/dist/languages/te.js +3 -0
  92. package/dist/languages/te.js.map +1 -0
  93. package/dist/languages/th.js +3 -0
  94. package/dist/languages/th.js.map +1 -0
  95. package/dist/languages/tr.js +3 -0
  96. package/dist/languages/tr.js.map +1 -0
  97. package/dist/languages/uk.js +3 -0
  98. package/dist/languages/uk.js.map +1 -0
  99. package/dist/languages/ur.js +3 -0
  100. package/dist/languages/ur.js.map +1 -0
  101. package/dist/languages/vi.js +3 -0
  102. package/dist/languages/vi.js.map +1 -0
  103. package/dist/languages/zh-Hans.js +3 -0
  104. package/dist/languages/zh-Hans.js.map +1 -0
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +2 -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 +14 -27
  114. package/lib/languages/ar.js +175 -129
  115. package/lib/languages/az.d.ts +4 -9
  116. package/lib/languages/az.js +171 -37
  117. package/lib/languages/bn.d.ts +4 -8
  118. package/lib/languages/bn.js +138 -124
  119. package/lib/languages/cs.d.ts +15 -85
  120. package/lib/languages/cs.js +310 -114
  121. package/lib/languages/da.d.ts +11 -12
  122. package/lib/languages/da.js +276 -101
  123. package/lib/languages/de.d.ts +14 -11
  124. package/lib/languages/de.js +317 -86
  125. package/lib/languages/el.d.ts +11 -11
  126. package/lib/languages/el.js +231 -78
  127. package/lib/languages/en.d.ts +14 -13
  128. package/lib/languages/en.js +242 -72
  129. package/lib/languages/es.d.ts +18 -12
  130. package/lib/languages/es.js +317 -103
  131. package/lib/languages/fa.d.ts +4 -44
  132. package/lib/languages/fa.js +112 -122
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +4 -13
  136. package/lib/languages/fil.js +207 -106
  137. package/lib/languages/fr-BE.d.ts +8 -8
  138. package/lib/languages/fr-BE.js +294 -19
  139. package/lib/languages/fr.d.ts +18 -12
  140. package/lib/languages/fr.js +352 -89
  141. package/lib/languages/gu.d.ts +4 -8
  142. package/lib/languages/gu.js +130 -125
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +10 -110
  146. package/lib/languages/hbo.js +263 -214
  147. package/lib/languages/he.d.ts +10 -77
  148. package/lib/languages/he.js +242 -172
  149. package/lib/languages/hi.d.ts +4 -8
  150. package/lib/languages/hi.js +138 -124
  151. package/lib/languages/hr.d.ts +8 -77
  152. package/lib/languages/hr.js +194 -89
  153. package/lib/languages/hu.d.ts +4 -19
  154. package/lib/languages/hu.js +198 -119
  155. package/lib/languages/id.d.ts +4 -34
  156. package/lib/languages/id.js +171 -129
  157. package/lib/languages/it.d.ts +16 -34
  158. package/lib/languages/it.js +339 -94
  159. package/lib/languages/ja.d.ts +14 -14
  160. package/lib/languages/ja.js +233 -111
  161. package/lib/languages/kn.d.ts +4 -8
  162. package/lib/languages/kn.js +130 -35
  163. package/lib/languages/ko.d.ts +11 -11
  164. package/lib/languages/ko.js +257 -49
  165. package/lib/languages/lt.d.ts +15 -67
  166. package/lib/languages/lt.js +296 -122
  167. package/lib/languages/lv.d.ts +15 -67
  168. package/lib/languages/lv.js +297 -106
  169. package/lib/languages/mr.d.ts +4 -8
  170. package/lib/languages/mr.js +130 -125
  171. package/lib/languages/ms.d.ts +4 -28
  172. package/lib/languages/ms.js +171 -116
  173. package/lib/languages/nb.d.ts +11 -9
  174. package/lib/languages/nb.js +282 -87
  175. package/lib/languages/nl.d.ts +23 -13
  176. package/lib/languages/nl.js +317 -133
  177. package/lib/languages/pa.d.ts +4 -8
  178. package/lib/languages/pa.js +156 -124
  179. package/lib/languages/pl.d.ts +19 -77
  180. package/lib/languages/pl.js +307 -87
  181. package/lib/languages/pt.d.ts +14 -26
  182. package/lib/languages/pt.js +286 -92
  183. package/lib/languages/ro.d.ts +15 -155
  184. package/lib/languages/ro.js +219 -235
  185. package/lib/languages/ru.d.ts +8 -82
  186. package/lib/languages/ru.js +222 -78
  187. package/lib/languages/sr-Cyrl.d.ts +8 -77
  188. package/lib/languages/sr-Cyrl.js +191 -89
  189. package/lib/languages/sr-Latn.d.ts +8 -77
  190. package/lib/languages/sr-Latn.js +191 -89
  191. package/lib/languages/sv.d.ts +11 -11
  192. package/lib/languages/sv.js +288 -74
  193. package/lib/languages/sw.d.ts +4 -36
  194. package/lib/languages/sw.js +133 -106
  195. package/lib/languages/ta.d.ts +4 -17
  196. package/lib/languages/ta.js +129 -201
  197. package/lib/languages/te.d.ts +4 -19
  198. package/lib/languages/te.js +141 -196
  199. package/lib/languages/th.d.ts +4 -14
  200. package/lib/languages/th.js +135 -91
  201. package/lib/languages/tr.d.ts +15 -9
  202. package/lib/languages/tr.js +256 -49
  203. package/lib/languages/uk.d.ts +8 -82
  204. package/lib/languages/uk.js +200 -78
  205. package/lib/languages/ur.d.ts +4 -8
  206. package/lib/languages/ur.js +156 -124
  207. package/lib/languages/vi.d.ts +14 -69
  208. package/lib/languages/vi.js +294 -125
  209. package/lib/languages/zh-Hans.d.ts +8 -18
  210. package/lib/languages/zh-Hans.js +163 -92
  211. package/lib/languages/zh-Hant.d.ts +8 -18
  212. package/lib/languages/zh-Hant.js +181 -90
  213. package/lib/n2words.d.ts +53 -209
  214. package/lib/n2words.js +111 -530
  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 +26 -14
  222. package/dist/ArabicConverter.js +0 -3
  223. package/dist/ArabicConverter.js.map +0 -1
  224. package/dist/AzerbaijaniConverter.js +0 -3
  225. package/dist/AzerbaijaniConverter.js.map +0 -1
  226. package/dist/BanglaConverter.js +0 -3
  227. package/dist/BanglaConverter.js.map +0 -1
  228. package/dist/BiblicalHebrewConverter.js +0 -3
  229. package/dist/BiblicalHebrewConverter.js.map +0 -1
  230. package/dist/CroatianConverter.js +0 -3
  231. package/dist/CroatianConverter.js.map +0 -1
  232. package/dist/CzechConverter.js +0 -3
  233. package/dist/CzechConverter.js.map +0 -1
  234. package/dist/DanishConverter.js +0 -3
  235. package/dist/DanishConverter.js.map +0 -1
  236. package/dist/DutchConverter.js +0 -3
  237. package/dist/DutchConverter.js.map +0 -1
  238. package/dist/EnglishConverter.js +0 -3
  239. package/dist/EnglishConverter.js.map +0 -1
  240. package/dist/FilipinoConverter.js +0 -3
  241. package/dist/FilipinoConverter.js.map +0 -1
  242. package/dist/FrenchBelgiumConverter.js +0 -3
  243. package/dist/FrenchBelgiumConverter.js.map +0 -1
  244. package/dist/FrenchConverter.js +0 -3
  245. package/dist/FrenchConverter.js.map +0 -1
  246. package/dist/GermanConverter.js +0 -3
  247. package/dist/GermanConverter.js.map +0 -1
  248. package/dist/GreekConverter.js +0 -3
  249. package/dist/GreekConverter.js.map +0 -1
  250. package/dist/GujaratiConverter.js +0 -3
  251. package/dist/GujaratiConverter.js.map +0 -1
  252. package/dist/HebrewConverter.js +0 -3
  253. package/dist/HebrewConverter.js.map +0 -1
  254. package/dist/HindiConverter.js +0 -3
  255. package/dist/HindiConverter.js.map +0 -1
  256. package/dist/HungarianConverter.js +0 -3
  257. package/dist/HungarianConverter.js.map +0 -1
  258. package/dist/IndonesianConverter.js +0 -3
  259. package/dist/IndonesianConverter.js.map +0 -1
  260. package/dist/ItalianConverter.js +0 -3
  261. package/dist/ItalianConverter.js.map +0 -1
  262. package/dist/JapaneseConverter.js +0 -3
  263. package/dist/JapaneseConverter.js.map +0 -1
  264. package/dist/KannadaConverter.js +0 -3
  265. package/dist/KannadaConverter.js.map +0 -1
  266. package/dist/KoreanConverter.js +0 -3
  267. package/dist/KoreanConverter.js.map +0 -1
  268. package/dist/LatvianConverter.js +0 -3
  269. package/dist/LatvianConverter.js.map +0 -1
  270. package/dist/LithuanianConverter.js +0 -3
  271. package/dist/LithuanianConverter.js.map +0 -1
  272. package/dist/MalayConverter.js +0 -3
  273. package/dist/MalayConverter.js.map +0 -1
  274. package/dist/MarathiConverter.js +0 -3
  275. package/dist/MarathiConverter.js.map +0 -1
  276. package/dist/NorwegianBokmalConverter.js +0 -3
  277. package/dist/NorwegianBokmalConverter.js.map +0 -1
  278. package/dist/PersianConverter.js +0 -3
  279. package/dist/PersianConverter.js.map +0 -1
  280. package/dist/PolishConverter.js +0 -3
  281. package/dist/PolishConverter.js.map +0 -1
  282. package/dist/PortugueseConverter.js +0 -3
  283. package/dist/PortugueseConverter.js.map +0 -1
  284. package/dist/PunjabiConverter.js +0 -3
  285. package/dist/PunjabiConverter.js.map +0 -1
  286. package/dist/RomanianConverter.js +0 -3
  287. package/dist/RomanianConverter.js.map +0 -1
  288. package/dist/RussianConverter.js +0 -3
  289. package/dist/RussianConverter.js.map +0 -1
  290. package/dist/SerbianCyrillicConverter.js +0 -3
  291. package/dist/SerbianCyrillicConverter.js.map +0 -1
  292. package/dist/SerbianLatinConverter.js +0 -3
  293. package/dist/SerbianLatinConverter.js.map +0 -1
  294. package/dist/SimplifiedChineseConverter.js +0 -3
  295. package/dist/SimplifiedChineseConverter.js.map +0 -1
  296. package/dist/SpanishConverter.js +0 -3
  297. package/dist/SpanishConverter.js.map +0 -1
  298. package/dist/SwahiliConverter.js +0 -3
  299. package/dist/SwahiliConverter.js.map +0 -1
  300. package/dist/SwedishConverter.js +0 -3
  301. package/dist/SwedishConverter.js.map +0 -1
  302. package/dist/TamilConverter.js +0 -3
  303. package/dist/TamilConverter.js.map +0 -1
  304. package/dist/TeluguConverter.js +0 -3
  305. package/dist/TeluguConverter.js.map +0 -1
  306. package/dist/ThaiConverter.js +0 -3
  307. package/dist/ThaiConverter.js.map +0 -1
  308. package/dist/TraditionalChineseConverter.js +0 -3
  309. package/dist/TraditionalChineseConverter.js.map +0 -1
  310. package/dist/TurkishConverter.js +0 -3
  311. package/dist/TurkishConverter.js.map +0 -1
  312. package/dist/UkrainianConverter.js +0 -3
  313. package/dist/UkrainianConverter.js.map +0 -1
  314. package/dist/UrduConverter.js +0 -3
  315. package/dist/UrduConverter.js.map +0 -1
  316. package/dist/VietnameseConverter.js +0 -3
  317. package/dist/VietnameseConverter.js.map +0 -1
  318. package/lib/classes/abstract-language.d.ts +0 -178
  319. package/lib/classes/abstract-language.js +0 -268
  320. package/lib/classes/greedy-scale-language.d.ts +0 -109
  321. package/lib/classes/greedy-scale-language.js +0 -201
  322. package/lib/classes/slavic-language.d.ts +0 -148
  323. package/lib/classes/slavic-language.js +0 -281
  324. package/lib/classes/south-asian-language.d.ts +0 -70
  325. package/lib/classes/south-asian-language.js +0 -154
  326. package/lib/classes/turkic-language.d.ts +0 -26
  327. package/lib/classes/turkic-language.js +0 -59
@@ -1,112 +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
+ // ============================================================================
39
+
40
+ /**
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
+ // ============================================================================
2
126
 
3
127
  /**
4
- * (European) Portuguese language converter.
128
+ * Converts a non-negative integer to Portuguese words.
5
129
  *
6
- * Supports:
7
- * - Gender-aware hundreds (duzentos, trezentos)
8
- * - "e" (and) conjunction for number combinations
9
- * - Post-processing to normalize word flow
130
+ * @param {bigint} n - Non-negative integer to convert
131
+ * @returns {string} Portuguese words
10
132
  */
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'
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
- /** 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'
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
87
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
88
203
 
89
- if (followingValue < precedingValue) {
90
- return { [`${precedingWord} e ${followingWord}`]: precedingValue + followingValue }
204
+ const segmentWord = SEGMENTS[segment]
205
+ const isLastSegment = (i === firstNonZeroIdx)
206
+
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'
91
210
  }
92
211
 
93
- // Handle "milião" -> "milhão" conversion
94
- if (followingWord === 'milião') followingWord = 'milhão'
212
+ if (result) result += ' '
95
213
 
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')
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
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
102
233
  }
234
+ prevWasScale = true
103
235
  }
236
+ }
104
237
 
105
- if (followingValue === 100n) {
106
- precedingWord = this.hundredsWords[precedingValue]
107
- return { [`${precedingWord}`]: precedingValue * followingValue }
108
- }
238
+ return result
239
+ }
240
+
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 = ''
109
249
 
110
- return { [`${precedingWord} ${followingWord}`]: precedingValue * followingValue }
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++
111
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
112
266
  }
267
+
268
+ /**
269
+ * Converts a numeric value to Portuguese words.
270
+ *
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
278
+ *
279
+ * @example
280
+ * toWords(21) // 'vinte e um'
281
+ * toWords(100) // 'cem'
282
+ * toWords(1000000) // 'um milhão'
283
+ */
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
300
+ }
301
+
302
+ // ============================================================================
303
+ // Public API
304
+ // ============================================================================
305
+
306
+ 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;