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,273 +1,257 @@
1
- import { AbstractLanguage } from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Romanian language converter.
2
+ * Romanian language converter - Functional Implementation
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
5
  *
6
- * Supports:
6
+ * Key features:
7
7
  * - Gender agreement (unu/una, doi/două)
8
- * - Complex pluralization (singular/plural forms)
9
8
  * - "De" preposition insertion for groups >= 20
9
+ * - Complex scale word handling (mie/mii, milion/milioane)
10
+ * - Feminine units for thousands
10
11
  */
11
- export class Romanian extends AbstractLanguage {
12
- negativeWord = 'minus'
13
- decimalSeparatorWord = 'virgulă'
14
- zeroWord = 'zero'
15
-
16
- onesWords = {
17
- 1: 'unu',
18
- 2: 'doi',
19
- 3: 'trei',
20
- 4: 'patru',
21
- 5: 'cinci',
22
- 6: 'șase',
23
- 7: 'șapte',
24
- 8: 'opt',
25
- 9: 'nouă'
26
- }
27
12
 
28
- onesFeminineWords = {
29
- 1: 'una',
30
- 2: 'două',
31
- 3: 'trei',
32
- 4: 'patru',
33
- 5: 'cinci',
34
- 6: 'șase',
35
- 7: 'șapte',
36
- 8: 'opt',
37
- 9: 'nouă'
38
- }
13
+ import { parseNumericValue } from '../utils/parse-numeric.js'
14
+ import { validateOptions } from '../utils/validate-options.js'
39
15
 
40
- teensWords = {
41
- 0: 'zece',
42
- 1: 'unsprezece',
43
- 2: 'douăsprezece',
44
- 3: 'treisprezece',
45
- 4: 'paisprezece',
46
- 5: 'cincisprezece',
47
- 6: 'șaisprezece',
48
- 7: 'șaptesprezece',
49
- 8: 'optsprezece',
50
- 9: 'nouăsprezece'
51
- }
16
+ // ============================================================================
17
+ // Vocabulary (module-level constants)
18
+ // ============================================================================
52
19
 
53
- teensMasculineWords = {
54
- 0: 'zece',
55
- 1: 'unsprezece',
56
- 2: 'doisprezece',
57
- 3: 'treisprezece',
58
- 4: 'paisprezece',
59
- 5: 'cincisprezece',
60
- 6: 'șaisprezece',
61
- 7: 'șaptesprezece',
62
- 8: 'optsprezece',
63
- 9: 'nouăsprezece'
64
- }
20
+ const ONES_MASC = ['', 'unu', 'doi', 'trei', 'patru', 'cinci', 'șase', 'șapte', 'opt', 'nouă']
21
+ const ONES_FEM = ['', 'una', 'două', 'trei', 'patru', 'cinci', 'șase', 'șapte', 'opt', 'nouă']
22
+
23
+ const TEENS = ['zece', 'unsprezece', 'douăsprezece', 'treisprezece', 'paisprezece', 'cincisprezece', 'șaisprezece', 'șaptesprezece', 'optsprezece', 'nouăsprezece']
24
+ const TEENS_MASC = ['zece', 'unsprezece', 'doisprezece', 'treisprezece', 'paisprezece', 'cincisprezece', 'șaisprezece', 'șaptesprezece', 'optsprezece', 'nouăsprezece']
25
+
26
+ const TWENTIES = ['', '', 'douăzeci', 'treizeci', 'patruzeci', 'cincizeci', 'șaizeci', 'șaptezeci', 'optzeci', 'nouăzeci']
27
+
28
+ const HUNDREDS = ['', 'o sută', 'două sute', 'trei sute', 'patru sute', 'cinci sute', 'șase sute', 'șapte sute', 'opt sute', 'nouă sute']
65
29
 
66
- twentiesWords = {
67
- 2: 'douăzeci',
68
- 3: 'treizeci',
69
- 4: 'patruzeci',
70
- 5: 'cincizeci',
71
- 6: 'șaizeci',
72
- 7: 'șaptezeci',
73
- 8: 'optzeci',
74
- 9: 'nouăzeci'
30
+ const ZERO = 'zero'
31
+ const NEGATIVE = 'minus'
32
+ const DECIMAL_SEP = 'virgulă'
33
+
34
+ // Scale metadata: [singular, plural, article, feminine, needsDe]
35
+ // - singular: form for 1
36
+ // - plural: form for 2+
37
+ // - article: 'o' for feminine, 'un' for masculine
38
+ // - feminine: whether units should be feminine
39
+ // - needsDe: whether "de" is inserted for segment >= 20
40
+ const SCALE_META = [
41
+ { singular: 'mie', plural: 'mii', article: 'o', feminine: true, needsDe: true },
42
+ { singular: 'milion', plural: 'milioane', article: 'un', feminine: false, needsDe: true },
43
+ { singular: 'miliard', plural: 'miliarde', article: 'un', feminine: false, needsDe: true },
44
+ { singular: 'trilion', plural: 'trilioane', article: 'un', feminine: false, needsDe: true },
45
+ { singular: 'cvadrilion', plural: 'cvadrilioane', article: 'un', feminine: false, needsDe: true },
46
+ { singular: 'cvintilion', plural: 'cvintilioane', article: 'un', feminine: false, needsDe: true },
47
+ { singular: 'sextilion', plural: 'sextilioane', article: 'un', feminine: false, needsDe: true },
48
+ { singular: 'septilion', plural: 'septilioane', article: 'un', feminine: false, needsDe: true },
49
+ { singular: 'octilion', plural: 'octilioane', article: 'un', feminine: false, needsDe: true }
50
+ ]
51
+
52
+ // ============================================================================
53
+ // Helper Functions
54
+ // ============================================================================
55
+
56
+ /**
57
+ * Spells number under 100.
58
+ */
59
+ function spellUnder100 (n, feminine = false, masculineTeens = false) {
60
+ if (n === 0) return ''
61
+ if (n < 10) {
62
+ return feminine ? ONES_FEM[n] : ONES_MASC[n]
63
+ }
64
+ if (n < 20) {
65
+ return masculineTeens ? TEENS_MASC[n - 10] : TEENS[n - 10]
75
66
  }
67
+ const t = Math.floor(n / 10)
68
+ const u = n % 10
69
+ if (u === 0) {
70
+ return TWENTIES[t]
71
+ }
72
+ const onesWord = feminine ? ONES_FEM[u] : ONES_MASC[u]
73
+ return TWENTIES[t] + ' și ' + onesWord
74
+ }
76
75
 
77
- hundredsWords = {
78
- 1: 'o sută',
79
- 2: 'două sute',
80
- 3: 'trei sute',
81
- 4: 'patru sute',
82
- 5: 'cinci sute',
83
- 6: 'șase sute',
84
- 7: 'șapte sute',
85
- 8: 'opt sute',
86
- 9: 'nouă sute'
76
+ /**
77
+ * Spells number under 1000.
78
+ */
79
+ function spellUnder1000 (n, feminine = false, masculineTeens = false) {
80
+ if (n === 0) return ''
81
+ if (n < 100) return spellUnder100(n, feminine, masculineTeens)
82
+
83
+ const h = Math.floor(n / 100)
84
+ const r = n % 100
85
+ const hundredWord = HUNDREDS[h]
86
+
87
+ if (r === 0) return hundredWord
88
+ return hundredWord + ' ' + spellUnder100(r, feminine, masculineTeens)
89
+ }
90
+
91
+ /**
92
+ * Builds scale word with proper pluralization and "de" insertion.
93
+ * Romanian always uses feminine forms (două, not doi) when counting scale words.
94
+ */
95
+ function buildScalePhrase (segment, scaleIndex) {
96
+ const meta = SCALE_META[scaleIndex - 1]
97
+ if (!meta) return spellUnder1000(segment, true)
98
+
99
+ if (segment === 1) {
100
+ return meta.article + ' ' + meta.singular
87
101
  }
88
102
 
89
- /**
90
- * Romanian big units.
91
- * For each power group we keep: singular, plural, feminineUnits?, needsDe?
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)
95
- */
96
- scaleMetadata = {
97
- 1: { singular: 'mie', plural: 'mii', feminine: true, needsDe: true }, // 10^3
98
- 2: { singular: 'milion', plural: 'milioane', feminine: false, needsDe: true }, // 10^6
99
- 3: { singular: 'miliard', plural: 'miliarde', feminine: false, needsDe: true }, // 10^9
100
- 4: { singular: 'trilion', plural: 'trilioane', feminine: false, needsDe: true }, // 10^12
101
- 5: { singular: 'cvadrilion', plural: 'cvadrilioane', feminine: false, needsDe: true }, // 10^15
102
- 6: { singular: 'cvintilion', plural: 'cvintilioane', feminine: false, needsDe: true }, // 10^18
103
- 7: { singular: 'sextilion', plural: 'sextilioane', feminine: false, needsDe: true }, // 10^21
104
- 8: { singular: 'septilion', plural: 'septilioane', feminine: false, needsDe: true }, // 10^24
105
- 9: { singular: 'octilion', plural: 'octilioane', feminine: false, needsDe: true }, // 10^27
106
- 10: { singular: 'decilion', plural: 'decilioane', feminine: false, needsDe: true } // 10^33
103
+ // Special case: 21 with scale words uses feminine "una"
104
+ if (segment === 21 && meta.needsDe) {
105
+ return 'douăzeci și una de ' + meta.plural
107
106
  }
108
107
 
109
- constructor (options = {}) {
110
- super()
108
+ // Romanian always uses feminine when counting scale words (două milioane, not doi milioane)
109
+ const words = spellUnder1000(segment, true)
111
110
 
112
- this.setOptions({
113
- gender: 'masculine'
114
- }, options)
115
- }
111
+ // "de" after >= 20
112
+ const needsDe = meta.needsDe && segment >= 20
113
+ const separator = needsDe ? ' de ' : ' '
116
114
 
117
- /** Split numeric string into BigInt segments of specified size from left to right. */
118
- splitToSegments (n, x) {
119
- const results = []
120
- const l = n.length
121
- let result
122
-
123
- if (l > x) {
124
- const start = l % x
125
- if (start > 0) {
126
- result = n.slice(0, start)
127
- results.push(BigInt(result))
128
- }
129
- for (let index = start; index < l; index += x) {
130
- result = n.slice(index, index + x)
131
- results.push(BigInt(result))
132
- }
133
- } else {
134
- results.push(BigInt(n))
135
- }
136
- return results
115
+ return words + separator + meta.plural
116
+ }
117
+
118
+ // ============================================================================
119
+ // Conversion Functions
120
+ // ============================================================================
121
+
122
+ /**
123
+ * Converts a non-negative integer to Romanian words.
124
+ *
125
+ * @param {bigint} n - Non-negative integer to convert
126
+ * @param {Object} options - Conversion options
127
+ * @returns {string} Romanian words
128
+ */
129
+ function integerToWords (n, options = {}) {
130
+ if (n === 0n) return ZERO
131
+
132
+ // Fast path: numbers < 1000
133
+ if (n < 1000n) {
134
+ const feminine = options.gender === 'feminine'
135
+ return spellUnder1000(Number(n), feminine)
137
136
  }
138
137
 
139
- extractDigits (value) {
140
- const stringValue = value.toString().padStart(3, '0').slice(-3)
141
- const a = [...stringValue].toReversed()
142
- return a.map(BigInt)
138
+ return buildLargeNumberWords(n, options)
139
+ }
140
+
141
+ /**
142
+ * Builds words for numbers >= 1000.
143
+ * Uses BigInt division for faster segment extraction.
144
+ *
145
+ * @param {bigint} n - Number >= 1000
146
+ * @param {Object} options - Conversion options
147
+ * @returns {string} Romanian words
148
+ */
149
+ function buildLargeNumberWords (n, options) {
150
+ // Extract segments using BigInt division (faster than string slicing)
151
+ // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
152
+ const segmentValues = []
153
+ let temp = n
154
+ while (temp > 0n) {
155
+ segmentValues.push(Number(temp % 1000n))
156
+ temp = temp / 1000n
143
157
  }
144
158
 
145
- /** Romanian pluralization & "de" rule for big units. */
146
- romanianPluralize (segment, form) {
147
- const n = Number(segment)
159
+ // Build result string directly (avoid regex cleanup)
160
+ let result = ''
148
161
 
149
- if (n === 1) {
150
- // article differs for feminine "mie" (o mie) vs the rest (un milion/miliard…)
151
- const article = form.feminine ? 'o' : 'un'
152
- return `${article} ${form.singular}`
162
+ for (let i = segmentValues.length - 1; i >= 0; i--) {
163
+ const segment = segmentValues[i]
164
+ if (segment === 0) continue
165
+
166
+ let segmentWords
167
+ if (i === 0) {
168
+ // Units segment - use gender from options
169
+ const feminine = options.gender === 'feminine'
170
+ segmentWords = spellUnder1000(segment, feminine)
171
+ } else {
172
+ // Scale segment
173
+ segmentWords = buildScalePhrase(segment, i)
153
174
  }
154
175
 
155
- // For 21 with big units, use feminine "una" with plural nouns
156
- if (n === 21 && form.needsDe) {
157
- return `douăzeci și una de ${form.plural}`
176
+ if (result && segmentWords) {
177
+ result += ' ' + segmentWords
178
+ } else if (segmentWords) {
179
+ result = segmentWords
158
180
  }
181
+ }
159
182
 
160
- // spell the segment itself (use feminine units for big numbers)
161
- const words = this.spellUnder1000(n, true)
183
+ return result
184
+ }
162
185
 
163
- // "de" after >= 20 (covers 20, 21, 100, 101, 999, etc.)
164
- const needsDe = form.needsDe && n >= 20
165
- const de = needsDe ? ' de ' : ' '
186
+ /**
187
+ * Converts decimal digits to Romanian words.
188
+ * Decimals always use masculine forms.
189
+ *
190
+ * @param {string} decimalPart - Decimal digits (without the point)
191
+ * @returns {string} Romanian words for decimal part
192
+ */
193
+ function decimalPartToWords (decimalPart) {
194
+ let result = ''
195
+ let i = 0
166
196
 
167
- return `${words}${de}${form.plural}`
197
+ // Handle leading zeros
198
+ while (i < decimalPart.length && decimalPart[i] === '0') {
199
+ if (result) result += ' '
200
+ result += ZERO
201
+ i++
168
202
  }
169
203
 
170
- spellUnder100 (n, feminineUnits = false, masculineTeens = false) {
171
- if (n < 10) {
172
- return (feminineUnits ? this.onesFeminineWords : this.onesWords)[n]
173
- }
174
- if (n < 20) {
175
- return (masculineTeens ? this.teensMasculineWords : this.teensWords)[n - 10]
204
+ // Convert remainder as a single number (masculine, with masculine teens)
205
+ const remainder = decimalPart.slice(i)
206
+ if (remainder) {
207
+ if (result) result += ' '
208
+ const n = BigInt(remainder)
209
+ if (n < 1000n) {
210
+ result += spellUnder1000(Number(n), false, true)
211
+ } else {
212
+ result += integerToWords(n, { gender: 'masculine' })
176
213
  }
177
- const t = Math.floor(n / 10)
178
- const u = n % 10
179
- return u
180
- ? `${this.twentiesWords[t]} și ${(feminineUnits ? this.onesFeminineWords : this.onesWords)[u]}`
181
- : this.twentiesWords[t]
182
214
  }
183
215
 
184
- spellUnder1000 (n, feminineUnits = false, masculineTeens = false) {
185
- if (n < 100) return this.spellUnder100(n, feminineUnits, masculineTeens)
186
- const h = Math.floor(n / 100)
187
- const r = n % 100
188
- const hundredWords = this.hundredsWords[h]
189
- if (!r) return hundredWords
190
- // Standard readable form: "o sută unu" (for units) or "o sută cincizeci" (for tens)
191
- const separator = ' '
192
- return `${hundredWords}${separator}${this.spellUnder100(r, feminineUnits, masculineTeens)}`
193
- }
216
+ return result
217
+ }
194
218
 
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
201
- const words = []
202
- const segments = this.splitToSegments(integerPart.toString(), 3)
203
- let index = segments.length
204
- for (const x of segments) {
205
- index = index - 1
206
- if (x === 0n) continue
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)])
210
- if (n2 === 1n) {
211
- words.push(this.teensMasculineWords[Number(n1)])
212
- } else if (n1 > 0n) {
213
- if (n2 > 1n) words.push('și')
214
- words.push(this.onesWords[Number(n1)])
215
- }
216
- if (index > 0) {
217
- const form = this.scaleMetadata[index]
218
- if (form) {
219
- words.push(this.romanianPluralize(x, form))
220
- } else {
221
- words.push(this.spellUnder1000(Number(x), false))
222
- }
223
- }
224
- }
225
- return words.join(' ').replaceAll(/\s+/g, ' ').trim()
219
+ /**
220
+ * Converts a numeric value to Romanian words.
221
+ *
222
+ * @param {number | string | bigint} value - The numeric value to convert
223
+ * @param {Object} [options] - Conversion options
224
+ * @param {string} [options.gender='masculine'] - Gender for numbers
225
+ * @returns {string} The number in Romanian words
226
+ * @throws {TypeError} If value is not a valid numeric type
227
+ * @throws {Error} If value is not a valid number format
228
+ *
229
+ * @example
230
+ * toWords(21) // 'douăzeci și unu'
231
+ * toWords(1, { gender: 'feminine' }) // 'una'
232
+ * toWords(1000) // 'o mie'
233
+ */
234
+ function toWords (value, options) {
235
+ options = validateOptions(options)
236
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
237
+
238
+ let result = ''
239
+
240
+ if (isNegative) {
241
+ result = NEGATIVE + ' '
226
242
  }
227
243
 
228
- integerToWords (integerPart) {
229
- if (integerPart === 0n) {
230
- return this.zeroWord
231
- }
232
- const words = []
233
- const segments = this.splitToSegments(integerPart.toString(), 3)
234
- let index = segments.length
235
- for (const x of segments) {
236
- let onesMap = []
237
- index = index - 1
238
- if (x === 0n) continue
239
- const [n1, n2, n3] = this.extractDigits(x) // units, tens, hundreds (as BigInt)
240
- // hundreds (only for the last group, not for thousands)
241
- if (n3 > 0n && index === 0) {
242
- words.push(this.hundredsWords[Number(n3)])
243
- }
244
- // tens & teens (only for the last group, not for thousands)
245
- if (index === 0) {
246
- if (n2 > 1n) {
247
- words.push(this.twentiesWords[Number(n2)])
248
- }
249
- if (n2 === 1n) {
250
- words.push(this.teensWords[Number(n1)])
251
- } else if (n1 > 0n) {
252
- // pick masculine/feminine units (only for the last group, not for thousands)
253
- const feminineUnits = this.options.gender === 'feminine' && index === 0
254
- onesMap = feminineUnits ? this.onesFeminineWords : this.onesWords
255
- // if there is a twenty/treizeci/etc AND ones > 0 → add "și"
256
- if (n2 > 1n) words.push('și')
257
- words.push(onesMap[Number(n1)])
258
- }
259
- }
260
- // big unit name (mie/mii, milion/milioane, …)
261
- if (index > 0) {
262
- const form = this.scaleMetadata[index]
263
- if (form) {
264
- words.push(this.romanianPluralize(x, form))
265
- } else {
266
- // For very large numbers beyond our defined units, just spell out the number
267
- words.push(this.spellUnder1000(Number(x), true))
268
- }
269
- }
270
- }
271
- return words.join(' ').replaceAll(/\s+/g, ' ').trim()
244
+ result += integerToWords(integerPart, options)
245
+
246
+ if (decimalPart) {
247
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
272
248
  }
249
+
250
+ return result
273
251
  }
252
+
253
+ // ============================================================================
254
+ // Public API
255
+ // ============================================================================
256
+
257
+ export { toWords }
@@ -1,85 +1,11 @@
1
1
  /**
2
- * Russian language converter.
2
+ * Converts a numeric value to Russian words.
3
3
  *
4
- * Supports:
5
- * - Gender agreement (masculine/feminine forms)
6
- * - Three-form pluralization (one/few/many)
7
- * - Large numbers up to nonillions
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @param {Object} [options] - Optional configuration
6
+ * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
7
+ * @returns {string} The number in Russian words
8
8
  */
9
- export class Russian extends SlavicLanguage {
10
- onesWords: {
11
- 1: string;
12
- 2: string;
13
- 3: string;
14
- 4: string;
15
- 5: string;
16
- 6: string;
17
- 7: string;
18
- 8: string;
19
- 9: string;
20
- };
21
- onesFeminineWords: {
22
- 1: string;
23
- 2: string;
24
- 3: string;
25
- 4: string;
26
- 5: string;
27
- 6: string;
28
- 7: string;
29
- 8: string;
30
- 9: string;
31
- };
32
- teensWords: {
33
- 0: string;
34
- 1: string;
35
- 2: string;
36
- 3: string;
37
- 4: string;
38
- 5: string;
39
- 6: string;
40
- 7: string;
41
- 8: string;
42
- 9: string;
43
- };
44
- twentiesWords: {
45
- 2: string;
46
- 3: string;
47
- 4: string;
48
- 5: string;
49
- 6: string;
50
- 7: string;
51
- 8: string;
52
- 9: string;
53
- };
54
- hundredsWords: {
55
- 1: string;
56
- 2: string;
57
- 3: string;
58
- 4: string;
59
- 5: string;
60
- 6: string;
61
- 7: string;
62
- 8: string;
63
- 9: string;
64
- };
65
- pluralForms: {
66
- 1: string[];
67
- 2: string[];
68
- 3: string[];
69
- 4: string[];
70
- 5: string[];
71
- 6: string[];
72
- 7: string[];
73
- 8: string[];
74
- 9: string[];
75
- 10: string[];
76
- };
77
- /**
78
- * Russian thousands (тысяча) are feminine, requiring одна/две forms.
79
- * Other scales (million, billion, etc.) are masculine.
80
- */
81
- scaleGenders: {
82
- 1: boolean;
83
- };
84
- }
85
- import { SlavicLanguage } from '../classes/slavic-language.js';
9
+ export function toWords(value: number | string | bigint, options?: {
10
+ gender?: "masculine" | "feminine" | undefined;
11
+ }): string;