n2words 1.24.0 → 2.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 (349) hide show
  1. package/README.md +285 -156
  2. package/dist/ArabicConverter.js +3 -0
  3. package/dist/ArabicConverter.js.map +1 -0
  4. package/dist/AzerbaijaniConverter.js +3 -0
  5. package/dist/AzerbaijaniConverter.js.map +1 -0
  6. package/dist/BanglaConverter.js +3 -0
  7. package/dist/BanglaConverter.js.map +1 -0
  8. package/dist/BiblicalHebrewConverter.js +3 -0
  9. package/dist/BiblicalHebrewConverter.js.map +1 -0
  10. package/dist/CroatianConverter.js +3 -0
  11. package/dist/CroatianConverter.js.map +1 -0
  12. package/dist/CzechConverter.js +3 -0
  13. package/dist/CzechConverter.js.map +1 -0
  14. package/dist/DanishConverter.js +3 -0
  15. package/dist/DanishConverter.js.map +1 -0
  16. package/dist/DutchConverter.js +3 -0
  17. package/dist/DutchConverter.js.map +1 -0
  18. package/dist/EnglishConverter.js +3 -0
  19. package/dist/EnglishConverter.js.map +1 -0
  20. package/dist/FilipinoConverter.js +3 -0
  21. package/dist/FilipinoConverter.js.map +1 -0
  22. package/dist/FrenchBelgiumConverter.js +3 -0
  23. package/dist/FrenchBelgiumConverter.js.map +1 -0
  24. package/dist/FrenchConverter.js +3 -0
  25. package/dist/FrenchConverter.js.map +1 -0
  26. package/dist/GermanConverter.js +3 -0
  27. package/dist/GermanConverter.js.map +1 -0
  28. package/dist/GreekConverter.js +3 -0
  29. package/dist/GreekConverter.js.map +1 -0
  30. package/dist/GujaratiConverter.js +3 -0
  31. package/dist/GujaratiConverter.js.map +1 -0
  32. package/dist/HebrewConverter.js +3 -0
  33. package/dist/HebrewConverter.js.map +1 -0
  34. package/dist/HindiConverter.js +3 -0
  35. package/dist/HindiConverter.js.map +1 -0
  36. package/dist/HungarianConverter.js +3 -0
  37. package/dist/HungarianConverter.js.map +1 -0
  38. package/dist/IndonesianConverter.js +3 -0
  39. package/dist/IndonesianConverter.js.map +1 -0
  40. package/dist/ItalianConverter.js +3 -0
  41. package/dist/ItalianConverter.js.map +1 -0
  42. package/dist/JapaneseConverter.js +3 -0
  43. package/dist/JapaneseConverter.js.map +1 -0
  44. package/dist/KannadaConverter.js +3 -0
  45. package/dist/KannadaConverter.js.map +1 -0
  46. package/dist/KoreanConverter.js +3 -0
  47. package/dist/KoreanConverter.js.map +1 -0
  48. package/dist/LatvianConverter.js +3 -0
  49. package/dist/LatvianConverter.js.map +1 -0
  50. package/dist/LithuanianConverter.js +3 -0
  51. package/dist/LithuanianConverter.js.map +1 -0
  52. package/dist/MalayConverter.js +3 -0
  53. package/dist/MalayConverter.js.map +1 -0
  54. package/dist/MarathiConverter.js +3 -0
  55. package/dist/MarathiConverter.js.map +1 -0
  56. package/dist/NorwegianBokmalConverter.js +3 -0
  57. package/dist/NorwegianBokmalConverter.js.map +1 -0
  58. package/dist/PersianConverter.js +3 -0
  59. package/dist/PersianConverter.js.map +1 -0
  60. package/dist/PolishConverter.js +3 -0
  61. package/dist/PolishConverter.js.map +1 -0
  62. package/dist/PortugueseConverter.js +3 -0
  63. package/dist/PortugueseConverter.js.map +1 -0
  64. package/dist/PunjabiConverter.js +3 -0
  65. package/dist/PunjabiConverter.js.map +1 -0
  66. package/dist/RomanianConverter.js +3 -0
  67. package/dist/RomanianConverter.js.map +1 -0
  68. package/dist/RussianConverter.js +3 -0
  69. package/dist/RussianConverter.js.map +1 -0
  70. package/dist/SerbianCyrillicConverter.js +3 -0
  71. package/dist/SerbianCyrillicConverter.js.map +1 -0
  72. package/dist/SerbianLatinConverter.js +3 -0
  73. package/dist/SerbianLatinConverter.js.map +1 -0
  74. package/dist/SimplifiedChineseConverter.js +3 -0
  75. package/dist/SimplifiedChineseConverter.js.map +1 -0
  76. package/dist/SpanishConverter.js +3 -0
  77. package/dist/SpanishConverter.js.map +1 -0
  78. package/dist/SwahiliConverter.js +3 -0
  79. package/dist/SwahiliConverter.js.map +1 -0
  80. package/dist/SwedishConverter.js +3 -0
  81. package/dist/SwedishConverter.js.map +1 -0
  82. package/dist/TamilConverter.js +3 -0
  83. package/dist/TamilConverter.js.map +1 -0
  84. package/dist/TeluguConverter.js +3 -0
  85. package/dist/TeluguConverter.js.map +1 -0
  86. package/dist/ThaiConverter.js +3 -0
  87. package/dist/ThaiConverter.js.map +1 -0
  88. package/dist/TraditionalChineseConverter.js +3 -0
  89. package/dist/TraditionalChineseConverter.js.map +1 -0
  90. package/dist/TurkishConverter.js +3 -0
  91. package/dist/TurkishConverter.js.map +1 -0
  92. package/dist/UkrainianConverter.js +3 -0
  93. package/dist/UkrainianConverter.js.map +1 -0
  94. package/dist/UrduConverter.js +3 -0
  95. package/dist/UrduConverter.js.map +1 -0
  96. package/dist/VietnameseConverter.js +3 -0
  97. package/dist/VietnameseConverter.js.map +1 -0
  98. package/dist/n2words.js +3 -2
  99. package/dist/n2words.js.map +1 -1
  100. package/lib/classes/abstract-language.d.ts +178 -0
  101. package/lib/classes/abstract-language.js +192 -185
  102. package/lib/classes/greedy-scale-language.d.ts +109 -0
  103. package/lib/classes/greedy-scale-language.js +96 -90
  104. package/lib/classes/slavic-language.d.ts +148 -0
  105. package/lib/classes/slavic-language.js +136 -106
  106. package/lib/classes/south-asian-language.d.ts +70 -0
  107. package/lib/classes/south-asian-language.js +58 -65
  108. package/lib/classes/turkic-language.d.ts +26 -0
  109. package/lib/classes/turkic-language.js +22 -26
  110. package/lib/languages/ar.d.ts +30 -0
  111. package/lib/languages/ar.js +49 -133
  112. package/lib/languages/az.d.ts +12 -0
  113. package/lib/languages/az.js +7 -23
  114. package/lib/languages/bn.d.ts +11 -0
  115. package/lib/languages/bn.js +12 -7
  116. package/lib/languages/cs.d.ts +88 -0
  117. package/lib/languages/cs.js +44 -113
  118. package/lib/languages/da.d.ts +15 -0
  119. package/lib/languages/da.js +40 -87
  120. package/lib/languages/de.d.ts +14 -0
  121. package/lib/languages/de.js +34 -68
  122. package/lib/languages/el.d.ts +14 -0
  123. package/lib/languages/el.js +22 -48
  124. package/lib/languages/en.d.ts +16 -0
  125. package/lib/languages/en.js +22 -59
  126. package/lib/languages/es.d.ts +15 -0
  127. package/lib/languages/es.js +49 -81
  128. package/lib/languages/fa.d.ts +47 -0
  129. package/lib/languages/fa.js +90 -73
  130. package/lib/languages/fil.d.ts +16 -0
  131. package/lib/languages/fil.js +35 -76
  132. package/lib/languages/fr-BE.d.ts +11 -0
  133. package/lib/languages/fr-BE.js +15 -51
  134. package/lib/languages/fr.d.ts +15 -0
  135. package/lib/languages/fr.js +33 -72
  136. package/lib/languages/gu.d.ts +11 -0
  137. package/lib/languages/gu.js +10 -34
  138. package/lib/languages/hbo.d.ts +113 -0
  139. package/lib/languages/hbo.js +251 -0
  140. package/lib/languages/he.d.ts +80 -0
  141. package/lib/languages/he.js +41 -164
  142. package/lib/languages/hi.d.ts +11 -0
  143. package/lib/languages/hi.js +12 -7
  144. package/lib/languages/hr.d.ts +80 -0
  145. package/lib/languages/hr.js +51 -95
  146. package/lib/languages/hu.d.ts +22 -0
  147. package/lib/languages/hu.js +35 -53
  148. package/lib/languages/id.d.ts +37 -0
  149. package/lib/languages/id.js +29 -44
  150. package/lib/languages/it.d.ts +37 -0
  151. package/lib/languages/it.js +36 -52
  152. package/lib/languages/ja.d.ts +17 -0
  153. package/lib/languages/ja.js +22 -75
  154. package/lib/languages/kn.d.ts +11 -0
  155. package/lib/languages/kn.js +10 -39
  156. package/lib/languages/ko.d.ts +14 -0
  157. package/lib/languages/ko.js +17 -45
  158. package/lib/languages/lt.d.ts +70 -0
  159. package/lib/languages/lt.js +28 -63
  160. package/lib/languages/lv.d.ts +70 -0
  161. package/lib/languages/lv.js +35 -58
  162. package/lib/languages/mr.d.ts +11 -0
  163. package/lib/languages/mr.js +10 -34
  164. package/lib/languages/ms.d.ts +31 -0
  165. package/lib/languages/ms.js +24 -20
  166. package/lib/languages/nb.d.ts +12 -0
  167. package/lib/languages/nb.js +36 -56
  168. package/lib/languages/nl.d.ts +16 -0
  169. package/lib/languages/nl.js +58 -109
  170. package/lib/languages/pa.d.ts +11 -0
  171. package/lib/languages/{pa-Guru.js → pa.js} +12 -7
  172. package/lib/languages/pl.d.ts +80 -0
  173. package/lib/languages/pl.js +26 -105
  174. package/lib/languages/pt.d.ts +29 -0
  175. package/lib/languages/pt.js +29 -64
  176. package/lib/languages/ro.d.ts +158 -0
  177. package/lib/languages/ro.js +60 -167
  178. package/lib/languages/ru.d.ts +85 -0
  179. package/lib/languages/ru.js +17 -37
  180. package/lib/languages/sr-Cyrl.d.ts +80 -0
  181. package/lib/languages/sr-Cyrl.js +113 -0
  182. package/lib/languages/sr-Latn.d.ts +80 -0
  183. package/lib/languages/sr-Latn.js +54 -98
  184. package/lib/languages/sv.d.ts +14 -0
  185. package/lib/languages/sv.js +26 -63
  186. package/lib/languages/sw.d.ts +39 -0
  187. package/lib/languages/sw.js +26 -21
  188. package/lib/languages/ta.d.ts +20 -0
  189. package/lib/languages/ta.js +26 -26
  190. package/lib/languages/te.d.ts +22 -0
  191. package/lib/languages/te.js +28 -38
  192. package/lib/languages/th.d.ts +17 -0
  193. package/lib/languages/th.js +25 -31
  194. package/lib/languages/tr.d.ts +12 -0
  195. package/lib/languages/tr.js +11 -38
  196. package/lib/languages/uk.d.ts +85 -0
  197. package/lib/languages/uk.js +18 -44
  198. package/lib/languages/ur.d.ts +11 -0
  199. package/lib/languages/ur.js +12 -7
  200. package/lib/languages/vi.d.ts +72 -0
  201. package/lib/languages/vi.js +25 -71
  202. package/lib/languages/zh-Hans.d.ts +21 -0
  203. package/lib/languages/zh-Hans.js +33 -87
  204. package/lib/languages/zh-Hant.d.ts +21 -0
  205. package/lib/languages/zh-Hant.js +111 -0
  206. package/lib/n2words.d.ts +209 -0
  207. package/lib/n2words.js +474 -191
  208. package/package.json +106 -67
  209. package/dist/languages/ar.js +0 -2
  210. package/dist/languages/ar.js.map +0 -1
  211. package/dist/languages/az.js +0 -2
  212. package/dist/languages/az.js.map +0 -1
  213. package/dist/languages/bn.js +0 -2
  214. package/dist/languages/bn.js.map +0 -1
  215. package/dist/languages/cs.js +0 -2
  216. package/dist/languages/cs.js.map +0 -1
  217. package/dist/languages/da.js +0 -2
  218. package/dist/languages/da.js.map +0 -1
  219. package/dist/languages/de.js +0 -2
  220. package/dist/languages/de.js.map +0 -1
  221. package/dist/languages/el.js +0 -2
  222. package/dist/languages/el.js.map +0 -1
  223. package/dist/languages/en.js +0 -2
  224. package/dist/languages/en.js.map +0 -1
  225. package/dist/languages/es.js +0 -2
  226. package/dist/languages/es.js.map +0 -1
  227. package/dist/languages/fa.js +0 -2
  228. package/dist/languages/fa.js.map +0 -1
  229. package/dist/languages/fil.js +0 -2
  230. package/dist/languages/fil.js.map +0 -1
  231. package/dist/languages/fr-BE.js +0 -2
  232. package/dist/languages/fr-BE.js.map +0 -1
  233. package/dist/languages/fr.js +0 -2
  234. package/dist/languages/fr.js.map +0 -1
  235. package/dist/languages/gu.js +0 -2
  236. package/dist/languages/gu.js.map +0 -1
  237. package/dist/languages/he.js +0 -2
  238. package/dist/languages/he.js.map +0 -1
  239. package/dist/languages/hi.js +0 -2
  240. package/dist/languages/hi.js.map +0 -1
  241. package/dist/languages/hr.js +0 -2
  242. package/dist/languages/hr.js.map +0 -1
  243. package/dist/languages/hu.js +0 -2
  244. package/dist/languages/hu.js.map +0 -1
  245. package/dist/languages/id.js +0 -2
  246. package/dist/languages/id.js.map +0 -1
  247. package/dist/languages/it.js +0 -2
  248. package/dist/languages/it.js.map +0 -1
  249. package/dist/languages/ja.js +0 -2
  250. package/dist/languages/ja.js.map +0 -1
  251. package/dist/languages/kn.js +0 -2
  252. package/dist/languages/kn.js.map +0 -1
  253. package/dist/languages/ko.js +0 -2
  254. package/dist/languages/ko.js.map +0 -1
  255. package/dist/languages/lt.js +0 -2
  256. package/dist/languages/lt.js.map +0 -1
  257. package/dist/languages/lv.js +0 -2
  258. package/dist/languages/lv.js.map +0 -1
  259. package/dist/languages/mr.js +0 -2
  260. package/dist/languages/mr.js.map +0 -1
  261. package/dist/languages/ms.js +0 -2
  262. package/dist/languages/ms.js.map +0 -1
  263. package/dist/languages/nb.js +0 -2
  264. package/dist/languages/nb.js.map +0 -1
  265. package/dist/languages/nl.js +0 -2
  266. package/dist/languages/nl.js.map +0 -1
  267. package/dist/languages/pa-Guru.js +0 -2
  268. package/dist/languages/pa-Guru.js.map +0 -1
  269. package/dist/languages/pl.js +0 -2
  270. package/dist/languages/pl.js.map +0 -1
  271. package/dist/languages/pt.js +0 -2
  272. package/dist/languages/pt.js.map +0 -1
  273. package/dist/languages/ro.js +0 -2
  274. package/dist/languages/ro.js.map +0 -1
  275. package/dist/languages/ru.js +0 -2
  276. package/dist/languages/ru.js.map +0 -1
  277. package/dist/languages/sr-Latn.js +0 -2
  278. package/dist/languages/sr-Latn.js.map +0 -1
  279. package/dist/languages/sv.js +0 -2
  280. package/dist/languages/sv.js.map +0 -1
  281. package/dist/languages/sw.js +0 -2
  282. package/dist/languages/sw.js.map +0 -1
  283. package/dist/languages/ta.js +0 -2
  284. package/dist/languages/ta.js.map +0 -1
  285. package/dist/languages/te.js +0 -2
  286. package/dist/languages/te.js.map +0 -1
  287. package/dist/languages/th.js +0 -2
  288. package/dist/languages/th.js.map +0 -1
  289. package/dist/languages/tr.js +0 -2
  290. package/dist/languages/tr.js.map +0 -1
  291. package/dist/languages/uk.js +0 -2
  292. package/dist/languages/uk.js.map +0 -1
  293. package/dist/languages/ur.js +0 -2
  294. package/dist/languages/ur.js.map +0 -1
  295. package/dist/languages/vi.js +0 -2
  296. package/dist/languages/vi.js.map +0 -1
  297. package/dist/languages/zh-Hans.js +0 -2
  298. package/dist/languages/zh-Hans.js.map +0 -1
  299. package/typings/classes/abstract-language.d.ts +0 -144
  300. package/typings/classes/greedy-scale-language.d.ts +0 -148
  301. package/typings/classes/slavic-language.d.ts +0 -145
  302. package/typings/classes/south-asian-language.d.ts +0 -101
  303. package/typings/classes/turkic-language.d.ts +0 -42
  304. package/typings/languages/ar.d.ts +0 -93
  305. package/typings/languages/az.d.ts +0 -25
  306. package/typings/languages/bn.d.ts +0 -1
  307. package/typings/languages/cs.d.ts +0 -120
  308. package/typings/languages/da.d.ts +0 -53
  309. package/typings/languages/de.d.ts +0 -26
  310. package/typings/languages/el.d.ts +0 -11
  311. package/typings/languages/en.d.ts +0 -30
  312. package/typings/languages/es.d.ts +0 -43
  313. package/typings/languages/fa.d.ts +0 -81
  314. package/typings/languages/fil.d.ts +0 -12
  315. package/typings/languages/fr-BE.d.ts +0 -41
  316. package/typings/languages/fr.d.ts +0 -43
  317. package/typings/languages/gu.d.ts +0 -12
  318. package/typings/languages/he.d.ts +0 -197
  319. package/typings/languages/hi.d.ts +0 -1
  320. package/typings/languages/hr.d.ts +0 -110
  321. package/typings/languages/hu.d.ts +0 -37
  322. package/typings/languages/id.d.ts +0 -69
  323. package/typings/languages/it.d.ts +0 -51
  324. package/typings/languages/ja.d.ts +0 -58
  325. package/typings/languages/kn.d.ts +0 -11
  326. package/typings/languages/ko.d.ts +0 -25
  327. package/typings/languages/lt.d.ts +0 -110
  328. package/typings/languages/lv.d.ts +0 -99
  329. package/typings/languages/mr.d.ts +0 -12
  330. package/typings/languages/ms.d.ts +0 -37
  331. package/typings/languages/nb.d.ts +0 -27
  332. package/typings/languages/nl.d.ts +0 -65
  333. package/typings/languages/pa-Guru.d.ts +0 -1
  334. package/typings/languages/pl.d.ts +0 -116
  335. package/typings/languages/pt.d.ts +0 -39
  336. package/typings/languages/ro.d.ts +0 -229
  337. package/typings/languages/ru.d.ts +0 -108
  338. package/typings/languages/sr-Latn.d.ts +0 -98
  339. package/typings/languages/sv.d.ts +0 -30
  340. package/typings/languages/sw.d.ts +0 -1
  341. package/typings/languages/ta.d.ts +0 -1
  342. package/typings/languages/te.d.ts +0 -1
  343. package/typings/languages/th.d.ts +0 -1
  344. package/typings/languages/tr.d.ts +0 -46
  345. package/typings/languages/uk.d.ts +0 -117
  346. package/typings/languages/ur.d.ts +0 -1
  347. package/typings/languages/vi.d.ts +0 -116
  348. package/typings/languages/zh-Hans.d.ts +0 -57
  349. package/typings/n2words.d.ts +0 -177
@@ -1,11 +1,10 @@
1
- import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
1
+ import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
2
2
 
3
3
  /**
4
4
  * (European) Portuguese language converter.
5
5
  *
6
- * Features:
7
- * - Gender-aware hundreds (hundredos, duzentos, etc.)
8
- * - Million/Billion pluralization
6
+ * Supports:
7
+ * - Gender-aware hundreds (duzentos, trezentos)
9
8
  * - "e" (and) conjunction for number combinations
10
9
  * - Post-processing to normalize word flow
11
10
  */
@@ -13,7 +12,8 @@ export class Portuguese extends GreedyScaleLanguage {
13
12
  negativeWord = 'menos'
14
13
  decimalSeparatorWord = 'vírgula'
15
14
  zeroWord = 'zero'
16
- scaleWordPairs = [
15
+
16
+ scaleWords = [
17
17
  [1_000_000_000_000_000_000_000_000n, 'quatrilião'],
18
18
  [1_000_000_000_000_000_000n, 'trilião'],
19
19
  [1_000_000_000_000n, 'bilião'],
@@ -50,7 +50,7 @@ export class Portuguese extends GreedyScaleLanguage {
50
50
  [0n, 'zero']
51
51
  ]
52
52
 
53
- hundreds = {
53
+ hundredsWords = {
54
54
  1: 'cento',
55
55
  2: 'duzentos',
56
56
  3: 'trezentos',
@@ -69,79 +69,44 @@ export class Portuguese extends GreedyScaleLanguage {
69
69
  return words.replaceAll(Portuguese.POSTCLEAN_REGEX, ' $1 ')
70
70
  }
71
71
 
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) {
72
+ /** Combines two word-sets according to Portuguese grammar rules. */
73
+ combineWordSets (preceding, following) {
92
74
  // 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
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
97
79
 
98
80
  // 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) {
81
+ if (precedingValue === 1n) {
82
+ if (followingValue < 1_000_000n) return { [followingWord]: followingValue }
83
+ precedingWord = 'um'
84
+ } else if (precedingValue === 100n && followingValue % 1000n !== 0n) {
103
85
  // Special handling: "cem" (100) becomes "cento" when followed by non-thousands
104
- cText = 'cento'
86
+ precedingWord = 'cento'
105
87
  }
106
88
 
107
- if (nNumber < cNumber) {
108
- return { [`${cText} e ${nText}`]: cNumber + nNumber }
89
+ if (followingValue < precedingValue) {
90
+ return { [`${precedingWord} e ${followingWord}`]: precedingValue + followingValue }
109
91
  }
110
92
 
111
93
  // Handle "milião" -> "milhão" conversion
112
- if (nText === 'milião') nText = 'milhão'
94
+ if (followingWord === 'milião') followingWord = 'milhão'
113
95
 
114
96
  // 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')
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')
120
102
  }
121
103
  }
122
104
 
123
- if (nNumber === 100n) {
124
- cText = this.hundreds[cNumber]
125
- return { [`${cText}`]: cNumber * nNumber }
105
+ if (followingValue === 100n) {
106
+ precedingWord = this.hundredsWords[precedingValue]
107
+ return { [`${precedingWord}`]: precedingValue * followingValue }
126
108
  }
127
109
 
128
- return { [`${cText} ${nText}`]: cNumber * nNumber }
110
+ return { [`${precedingWord} ${followingWord}`]: precedingValue * followingValue }
129
111
  }
130
112
  }
131
-
132
- /**
133
- * Converts a number to Portuguese cardinal (written) form.
134
- *
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.
140
- *
141
- * @example
142
- * convertToWords(42); // 'quarenta e dois'
143
- * convertToWords('100.5'); // 'cem vírgula cinco'
144
- */
145
- export default function convertToWords (value, options = {}) {
146
- return new Portuguese(options).convertToWords(value)
147
- }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Romanian language converter.
3
+ *
4
+ * Supports:
5
+ * - Gender agreement (unu/una, doi/două)
6
+ * - Complex pluralization (singular/plural forms)
7
+ * - "De" preposition insertion for groups >= 20
8
+ */
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';
@@ -1,43 +1,19 @@
1
- import AbstractLanguage from '../classes/abstract-language.js'
2
-
3
- /**
4
- * @typedef {Object} RomanianOptions
5
- * @property {boolean} [feminine=false] Use feminine forms for numbers.
6
- */
1
+ import { AbstractLanguage } from '../classes/abstract-language.js'
7
2
 
8
3
  /**
9
4
  * Romanian language converter.
10
5
  *
11
- * Converts numbers to Romanian words with full grammatical support:
12
- * - Gender agreement (masculine/feminine forms)
6
+ * Supports:
7
+ * - Gender agreement (unu/una, doi/două)
13
8
  * - Complex pluralization (singular/plural forms)
14
9
  * - "De" preposition insertion for groups >= 20
15
- * - Special feminine handling for thousands
16
- * - Proper case and agreement patterns
17
- *
18
- * Key Features:
19
- * - Gender-aware number forms (unu/una, doi/două, doisprezece/douăsprezece)
20
- * - Group-based algorithm:
21
- * 1. Split number into groups of 3 digits
22
- * 2. Convert each group using gender rules and special forms
23
- * 3. Insert "de" preposition for groups >= 20 (e.g., "douăzeci de mii")
24
- * 4. Append magnitude word with proper singular/plural form (mie/mii, milion/milioane)
25
- * 5. Join with spaces
26
- * - Special feminine units for thousands group
27
- * - Automatic "de" insertion rules (nouăsprezece mii vs douăzeci de mii)
28
- * - Proper singular/plural forms (mie/mii, milion/milioane)
29
- * - Support for very large numbers (up to decillions)
30
- *
31
- * Features:
32
- * - Feminine units for thousands group
33
- * - Support for very large numbers (up to decillions)
34
10
  */
35
11
  export class Romanian extends AbstractLanguage {
36
12
  negativeWord = 'minus'
37
13
  decimalSeparatorWord = 'virgulă'
38
14
  zeroWord = 'zero'
39
15
 
40
- ones = {
16
+ onesWords = {
41
17
  1: 'unu',
42
18
  2: 'doi',
43
19
  3: 'trei',
@@ -49,7 +25,7 @@ export class Romanian extends AbstractLanguage {
49
25
  9: 'nouă'
50
26
  }
51
27
 
52
- onesFeminine = {
28
+ onesFeminineWords = {
53
29
  1: 'una',
54
30
  2: 'două',
55
31
  3: 'trei',
@@ -61,7 +37,7 @@ export class Romanian extends AbstractLanguage {
61
37
  9: 'nouă'
62
38
  }
63
39
 
64
- tens = {
40
+ teensWords = {
65
41
  0: 'zece',
66
42
  1: 'unsprezece',
67
43
  2: 'douăsprezece',
@@ -74,7 +50,7 @@ export class Romanian extends AbstractLanguage {
74
50
  9: 'nouăsprezece'
75
51
  }
76
52
 
77
- tensMasculine = {
53
+ teensMasculineWords = {
78
54
  0: 'zece',
79
55
  1: 'unsprezece',
80
56
  2: 'doisprezece',
@@ -87,7 +63,7 @@ export class Romanian extends AbstractLanguage {
87
63
  9: 'nouăsprezece'
88
64
  }
89
65
 
90
- twenties = {
66
+ twentiesWords = {
91
67
  2: 'douăzeci',
92
68
  3: 'treizeci',
93
69
  4: 'patruzeci',
@@ -98,7 +74,7 @@ export class Romanian extends AbstractLanguage {
98
74
  9: 'nouăzeci'
99
75
  }
100
76
 
101
- hundreds = {
77
+ hundredsWords = {
102
78
  1: 'o sută',
103
79
  2: 'două sute',
104
80
  3: 'trei sute',
@@ -113,11 +89,11 @@ export class Romanian extends AbstractLanguage {
113
89
  /**
114
90
  * Romanian big units.
115
91
  * For each power group we keep: singular, plural, feminineUnits?, needsDe?
116
- * - 10^3: mie/mii (feminine units in chunk; "de" for chunk >= 20)
117
- * - 10^6: milion/milioane ("de" for chunk >= 20)
118
- * - 10^9: miliard/miliarde ("de" for chunk >= 20)
92
+ * - 10^3: mie/mii (feminine units in segment; "de" for segment >= 20)
93
+ * - 10^6: milion/milioane ("de" for segment >= 20)
94
+ * - 10^9: miliard/miliarde ("de" for segment >= 20)
119
95
  */
120
- thousands = {
96
+ scaleMetadata = {
121
97
  1: { singular: 'mie', plural: 'mii', feminine: true, needsDe: true }, // 10^3
122
98
  2: { singular: 'milion', plural: 'milioane', feminine: false, needsDe: true }, // 10^6
123
99
  3: { singular: 'miliard', plural: 'miliarde', feminine: false, needsDe: true }, // 10^9
@@ -130,24 +106,16 @@ export class Romanian extends AbstractLanguage {
130
106
  10: { singular: 'decilion', plural: 'decilioane', feminine: false, needsDe: true } // 10^33
131
107
  }
132
108
 
133
- /**
134
- * Initializes the Romanian converter with language-specific options.
135
- *
136
- * @param {RomanianOptions} [options={}] Configuration options.
137
- */
138
- constructor ({ feminine = false } = {}) {
109
+ constructor (options = {}) {
139
110
  super()
140
111
 
141
- this.feminine = feminine
112
+ this.setOptions({
113
+ gender: 'masculine'
114
+ }, options)
142
115
  }
143
116
 
144
- /**
145
- * Split numeric string into BigInt groups of size x from left to right.
146
- * @param {string} n - The numeric string to split
147
- * @param {number} x - The size of each group
148
- * @returns {bigint[]} Array of BigInt groups
149
- */
150
- splitByX (n, x) {
117
+ /** Split numeric string into BigInt segments of specified size from left to right. */
118
+ splitToSegments (n, x) {
151
119
  const results = []
152
120
  const l = n.length
153
121
  let result
@@ -168,23 +136,15 @@ export class Romanian extends AbstractLanguage {
168
136
  return results
169
137
  }
170
138
 
171
- getDigits (value) {
139
+ extractDigits (value) {
172
140
  const stringValue = value.toString().padStart(3, '0').slice(-3)
173
141
  const a = [...stringValue].toReversed()
174
142
  return a.map(BigInt)
175
143
  }
176
144
 
177
- /**
178
- * Romanian pluralization & "de" rule for big units.
179
- * - 1 → singular with article ("o mie", "un milion", "un miliard", …)
180
- * - otherwise → spell chunk + (optional "de") + plural
181
- * "de" is inserted when chunk >= 20 (e.g., "douăzeci de mii/milioane/miliarde").
182
- * @param {bigint} chunk - The chunk value
183
- * @param {object} form - The form object with singular, plural, feminine, needsDe properties
184
- * @returns {string} The pluralized form
185
- */
186
- romanianPluralize (chunk, form) {
187
- const n = Number(chunk)
145
+ /** Romanian pluralization & "de" rule for big units. */
146
+ romanianPluralize (segment, form) {
147
+ const n = Number(segment)
188
148
 
189
149
  if (n === 1) {
190
150
  // article differs for feminine "mie" (o mie) vs the rest (un milion/miliard…)
@@ -197,7 +157,7 @@ export class Romanian extends AbstractLanguage {
197
157
  return `douăzeci și una de ${form.plural}`
198
158
  }
199
159
 
200
- // spell the chunk itself (use feminine units for big numbers)
160
+ // spell the segment itself (use feminine units for big numbers)
201
161
  const words = this.spellUnder1000(n, true)
202
162
 
203
163
  // "de" after >= 20 (covers 20, 21, 100, 101, 999, etc.)
@@ -207,144 +167,91 @@ export class Romanian extends AbstractLanguage {
207
167
  return `${words}${de}${form.plural}`
208
168
  }
209
169
 
210
- spellUnder100 (n, feminineUnits = false) {
170
+ spellUnder100 (n, feminineUnits = false, masculineTeens = false) {
211
171
  if (n < 10) {
212
- return (feminineUnits ? this.onesFeminine : this.ones)[n]
172
+ return (feminineUnits ? this.onesFeminineWords : this.onesWords)[n]
213
173
  }
214
174
  if (n < 20) {
215
- return this.tens[n - 10]
175
+ return (masculineTeens ? this.teensMasculineWords : this.teensWords)[n - 10]
216
176
  }
217
177
  const t = Math.floor(n / 10)
218
178
  const u = n % 10
219
179
  return u
220
- ? `${this.twenties[t]} și ${(feminineUnits ? this.onesFeminine : this.ones)[u]}`
221
- : this.twenties[t]
180
+ ? `${this.twentiesWords[t]} și ${(feminineUnits ? this.onesFeminineWords : this.onesWords)[u]}`
181
+ : this.twentiesWords[t]
222
182
  }
223
183
 
224
- spellUnder1000 (n, feminineUnits = false) {
225
- if (n < 100) return this.spellUnder100(n, feminineUnits)
184
+ spellUnder1000 (n, feminineUnits = false, masculineTeens = false) {
185
+ if (n < 100) return this.spellUnder100(n, feminineUnits, masculineTeens)
226
186
  const h = Math.floor(n / 100)
227
187
  const r = n % 100
228
- const hundredWords = this.hundreds[h]
188
+ const hundredWords = this.hundredsWords[h]
229
189
  if (!r) return hundredWords
230
190
  // Standard readable form: "o sută unu" (for units) or "o sută cincizeci" (for tens)
231
191
  const separator = ' '
232
- return `${hundredWords}${separator}${this.spellUnder100(r, feminineUnits)}`
192
+ return `${hundredWords}${separator}${this.spellUnder100(r, feminineUnits, masculineTeens)}`
233
193
  }
234
194
 
235
- /**
236
- * Override decimalDigitsToWords to use masculine forms for decimal places
237
- * @param {string} decimal Decimal string to convert
238
- * @returns {string} Value in written format
239
- */
240
- decimalDigitsToWords (decimal) {
241
- const words = []
242
-
243
- const chars = [...decimal]
244
-
245
- // Loop through characters adding leading zeros to words array
246
- let index = 0
247
- while (index < chars.length && chars[index] === '0') {
248
- words.push(this.zeroWord)
249
- index++
250
- }
251
-
252
- // Prevent further processing if entire string was zeros
253
- if (index === chars.length) {
254
- return words
255
- }
256
-
257
- // Convert and add remaining using masculine forms for decimal places
258
- const decimalNumber = BigInt(decimal)
259
- const masculineWords = this.toCardinalWithMasculine(decimalNumber)
260
- return [...words, masculineWords]
261
- }
262
-
263
- /**
264
- * Convert number to cardinal form using masculine units
265
- * @param {bigint} number Number to convert
266
- * @returns {string} Value in written format
267
- */
268
- toCardinalWithMasculine (number) {
269
- if (number === 0n) {
270
- return this.zeroWord
271
- }
272
-
195
+ /** Decimals always use masculine forms regardless of gender option. */
196
+ decimalIntegerToWords (integerPart) {
197
+ if (integerPart === 0n) return this.zeroWord
198
+ // Use spellUnder1000 with masculine forms (feminineUnits=false, masculineTeens=true)
199
+ if (integerPart < 1000n) return this.spellUnder1000(Number(integerPart), false, true)
200
+ // For larger decimals, process segments with masculine forms
273
201
  const words = []
274
- const chunks = this.splitByX(number.toString(), 3)
275
- let index = chunks.length
276
-
277
- for (const x of chunks) {
278
- let onesMap = []
202
+ const segments = this.splitToSegments(integerPart.toString(), 3)
203
+ let index = segments.length
204
+ for (const x of segments) {
279
205
  index = index - 1
280
-
281
206
  if (x === 0n) continue
282
-
283
- const [n1, n2, n3] = this.getDigits(x) // units, tens, hundreds (as BigInt)
284
-
285
- // hundreds
286
- if (n3 > 0n) {
287
- words.push(this.hundreds[Number(n3)])
288
- }
289
-
290
- // tens & teens
291
- if (n2 > 1n) {
292
- words.push(this.twenties[Number(n2)])
293
- }
294
-
207
+ const [n1, n2, n3] = this.extractDigits(x)
208
+ if (n3 > 0n) words.push(this.hundredsWords[Number(n3)])
209
+ if (n2 > 1n) words.push(this.twentiesWords[Number(n2)])
295
210
  if (n2 === 1n) {
296
- words.push(this.tensMasculine[Number(n1)])
211
+ words.push(this.teensMasculineWords[Number(n1)])
297
212
  } else if (n1 > 0n) {
298
- // Always use masculine units for decimal places
299
- onesMap = this.ones
300
-
301
- // if there is a twenty/treizeci/etc AND ones > 0 → add "și"
302
213
  if (n2 > 1n) words.push('și')
303
- words.push(onesMap[Number(n1)])
214
+ words.push(this.onesWords[Number(n1)])
304
215
  }
305
-
306
- // big unit name (mie/mii, milion/milioane, …)
307
216
  if (index > 0) {
308
- const form = this.thousands[index]
217
+ const form = this.scaleMetadata[index]
309
218
  if (form) {
310
219
  words.push(this.romanianPluralize(x, form))
311
220
  } else {
312
- // For very large numbers beyond our defined units, just spell out the number
313
221
  words.push(this.spellUnder1000(Number(x), false))
314
222
  }
315
223
  }
316
224
  }
317
-
318
225
  return words.join(' ').replaceAll(/\s+/g, ' ').trim()
319
226
  }
320
227
 
321
- convertWholePart (number) {
322
- if (number === 0n) {
228
+ integerToWords (integerPart) {
229
+ if (integerPart === 0n) {
323
230
  return this.zeroWord
324
231
  }
325
232
  const words = []
326
- const chunks = this.splitByX(number.toString(), 3)
327
- let index = chunks.length
328
- for (const x of chunks) {
233
+ const segments = this.splitToSegments(integerPart.toString(), 3)
234
+ let index = segments.length
235
+ for (const x of segments) {
329
236
  let onesMap = []
330
237
  index = index - 1
331
238
  if (x === 0n) continue
332
- const [n1, n2, n3] = this.getDigits(x) // units, tens, hundreds (as BigInt)
239
+ const [n1, n2, n3] = this.extractDigits(x) // units, tens, hundreds (as BigInt)
333
240
  // hundreds (only for the last group, not for thousands)
334
241
  if (n3 > 0n && index === 0) {
335
- words.push(this.hundreds[Number(n3)])
242
+ words.push(this.hundredsWords[Number(n3)])
336
243
  }
337
244
  // tens & teens (only for the last group, not for thousands)
338
245
  if (index === 0) {
339
246
  if (n2 > 1n) {
340
- words.push(this.twenties[Number(n2)])
247
+ words.push(this.twentiesWords[Number(n2)])
341
248
  }
342
249
  if (n2 === 1n) {
343
- words.push(this.tens[Number(n1)])
250
+ words.push(this.teensWords[Number(n1)])
344
251
  } else if (n1 > 0n) {
345
252
  // pick masculine/feminine units (only for the last group, not for thousands)
346
- const feminineUnits = this.feminine && index === 0
347
- onesMap = feminineUnits ? this.onesFeminine : this.ones
253
+ const feminineUnits = this.options.gender === 'feminine' && index === 0
254
+ onesMap = feminineUnits ? this.onesFeminineWords : this.onesWords
348
255
  // if there is a twenty/treizeci/etc AND ones > 0 → add "și"
349
256
  if (n2 > 1n) words.push('și')
350
257
  words.push(onesMap[Number(n1)])
@@ -352,7 +259,7 @@ export class Romanian extends AbstractLanguage {
352
259
  }
353
260
  // big unit name (mie/mii, milion/milioane, …)
354
261
  if (index > 0) {
355
- const form = this.thousands[index]
262
+ const form = this.scaleMetadata[index]
356
263
  if (form) {
357
264
  words.push(this.romanianPluralize(x, form))
358
265
  } else {
@@ -364,17 +271,3 @@ export class Romanian extends AbstractLanguage {
364
271
  return words.join(' ').replaceAll(/\s+/g, ' ').trim()
365
272
  }
366
273
  }
367
-
368
- /**
369
- * Converts a number to Romanian cardinal (written) form.
370
- *
371
- * @param {number|string|bigint} value The number to convert.
372
- * @param {Object} [options={}] Configuration options.
373
- * @param {boolean} [options.feminine=false] Use feminine forms for numbers.
374
- * @returns {string} The number expressed in Romanian words.
375
- * @throws {TypeError} If value is NaN or invalid type.
376
- * @throws {Error} If value is an invalid number string.
377
- */
378
- export default function convertToWords (value, options = {}) {
379
- return new Romanian(options).convertToWords(value)
380
- }