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,90 +1,243 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
2
-
3
1
  /**
4
- * Greek language converter.
2
+ * Greek 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
  * - Space-separated number composition
8
8
  * - Implicit "one" (ένα) omission before scale words
9
- * - Digit-by-digit decimal reading
9
+ * - Irregular hundreds (διακόσια, τριακόσια, etc.)
10
+ * - Per-digit decimal reading
11
+ */
12
+
13
+ import { parseNumericValue } from '../utils/parse-numeric.js'
14
+
15
+ // ============================================================================
16
+ // Vocabulary (module-level constants)
17
+ // ============================================================================
18
+
19
+ const ONES = ['', 'ένα', 'δύο', 'τρία', 'τέσσερα', 'πέντε', 'έξι', 'επτά', 'οκτώ', 'εννέα']
20
+
21
+ const TEENS = ['δέκα', 'έντεκα', 'δώδεκα', 'δεκατρία', 'δεκατέσσερα', 'δεκαπέντε', 'δεκαέξι', 'δεκαεπτά', 'δεκαοκτώ', 'δεκαεννέα']
22
+
23
+ const TENS = ['', '', 'είκοσι', 'τριάντα', 'σαράντα', 'πενήντα', 'εξήντα', 'εβδομήντα', 'ογδόντα', 'ενενήντα']
24
+
25
+ // Greek has irregular hundreds
26
+ const HUNDREDS = ['', 'εκατό', 'διακόσια', 'τριακόσια', 'τετρακόσια', 'πεντακόσια', 'εξακόσια', 'επτακόσια', 'οκτακόσια', 'εννιακόσια']
27
+
28
+ const THOUSAND = 'χίλια'
29
+
30
+ const ZERO = 'μηδέν'
31
+ const NEGATIVE = 'μείον'
32
+ const DECIMAL_SEP = 'κόμμα'
33
+
34
+ // Short scale
35
+ const SCALES = ['εκατομμύριο', 'δισεκατομμύριο', 'τρισεκατομμύριο']
36
+
37
+ // ============================================================================
38
+ // Precomputed Lookup Tables (built once at module load)
39
+ // ============================================================================
40
+
41
+ /**
42
+ * Builds segment word for 0-999.
10
43
  */
11
- export class Greek extends GreedyScaleLanguage {
12
- negativeWord = 'μείον'
13
- decimalSeparatorWord = 'κόμμα'
14
- zeroWord = 'μηδέν'
15
- usePerDigitDecimals = true
16
-
17
- scaleWords = [
18
- // Large numbers (limited set for now)
19
- [1_000_000_000n, 'δισεκατομμύριο'],
20
- [1_000_000n, 'εκατομμύριο'],
21
- [1000n, 'χίλια'],
22
-
23
- // Hundreds
24
- [900n, 'εννιακόσια'],
25
- [800n, 'οκτακόσια'],
26
- [700n, 'επτακόσια'],
27
- [600n, 'εξακόσια'],
28
- [500n, 'πεντακόσια'],
29
- [400n, 'τετρακόσια'],
30
- [300n, 'τριακόσια'],
31
- [200n, 'διακόσια'],
32
- [100n, 'εκατό'],
33
-
34
- // Tens
35
- [90n, 'ενενήντα'],
36
- [80n, 'ογδόντα'],
37
- [70n, 'εβδομήντα'],
38
- [60n, 'εξήντα'],
39
- [50n, 'πενήντα'],
40
- [40n, 'σαράντα'],
41
- [30n, 'τριάντα'],
42
- [20n, 'είκοσι'],
43
- [19n, 'δεκαεννέα'],
44
- [18n, 'δεκαοκτώ'],
45
- [17n, 'δεκαεπτά'],
46
- [16n, 'δεκαέξι'],
47
- [15n, 'δεκαπέντε'],
48
- [14n, 'δεκατέσσερα'],
49
- [13n, 'δεκατρία'],
50
- [12n, 'δώδεκα'],
51
- [11n, 'έντεκα'],
52
- [10n, 'δέκα'],
53
-
54
- // Singles
55
- [9n, 'εννέα'],
56
- [8n, 'οκτώ'],
57
- [7n, 'επτά'],
58
- [6n, 'έξι'],
59
- [5n, 'πέντε'],
60
- [4n, 'τέσσερα'],
61
- [3n, 'τρία'],
62
- [2n, 'δύο'],
63
- [1n, 'ένα'],
64
- [0n, 'μηδέν']
65
- ]
66
-
67
- /** Combines two word-sets with Greek space-separation rules. */
68
- combineWordSets (preceding, following) {
69
- const precedingWord = Object.keys(preceding)[0]
70
- const precedingValue = Object.values(preceding)[0]
71
- const followingWord = Object.keys(following)[0]
72
- const followingValue = Object.values(following)[0]
73
-
74
- // Implicit one: omit "ένα" before any following value (> 0)
75
- if (precedingValue === 1n) {
76
- return following
44
+ function buildSegment (n) {
45
+ if (n === 0) return ''
46
+
47
+ const ones = n % 10
48
+ const tens = Math.floor(n / 10) % 10
49
+ const hundreds = Math.floor(n / 100)
50
+
51
+ const parts = []
52
+
53
+ // Hundreds (irregular forms)
54
+ if (hundreds > 0) {
55
+ parts.push(HUNDREDS[hundreds])
56
+ }
57
+
58
+ // Tens and ones
59
+ const tensOnes = n % 100
60
+
61
+ if (tensOnes === 0) {
62
+ // Just hundreds
63
+ } else if (tensOnes < 10) {
64
+ parts.push(ONES[ones])
65
+ } else if (tensOnes < 20) {
66
+ parts.push(TEENS[ones])
67
+ } else if (ones === 0) {
68
+ parts.push(TENS[tens])
69
+ } else {
70
+ parts.push(TENS[tens] + ' ' + ONES[ones])
71
+ }
72
+
73
+ return parts.join(' ')
74
+ }
75
+
76
+ // Precompute all 1000 segment words (0-999)
77
+ const SEGMENTS = new Array(1000)
78
+
79
+ for (let i = 0; i < 1000; i++) {
80
+ SEGMENTS[i] = buildSegment(i)
81
+ }
82
+
83
+ // ============================================================================
84
+ // Conversion Functions
85
+ // ============================================================================
86
+
87
+ /**
88
+ * Converts a non-negative integer to Greek words.
89
+ *
90
+ * @param {bigint} n - Non-negative integer to convert
91
+ * @returns {string} Greek words
92
+ */
93
+ function integerToWords (n) {
94
+ if (n === 0n) return ZERO
95
+
96
+ // Fast path: numbers < 1000 (direct lookup)
97
+ if (n < 1000n) {
98
+ return SEGMENTS[Number(n)]
99
+ }
100
+
101
+ // Fast path: numbers < 1,000,000 (thousands)
102
+ if (n < 1_000_000n) {
103
+ const thousands = Number(n / 1000n)
104
+ const remainder = Number(n % 1000n)
105
+
106
+ // Omit "ένα" before χίλια
107
+ let result
108
+ if (thousands === 1) {
109
+ result = THOUSAND
110
+ } else {
111
+ result = SEGMENTS[thousands] + ' ' + THOUSAND
112
+ }
113
+
114
+ if (remainder > 0) {
115
+ result += ' ' + SEGMENTS[remainder]
77
116
  }
78
117
 
79
- // No special handling needed for trailing 'ένα';
80
- // nested merge will first collapse {1, 'ένα'} -> 'ένα'.
118
+ return result
119
+ }
81
120
 
82
- // Multiplication: larger following scale multiplied by preceding number
83
- if (followingValue > precedingValue) {
84
- return { [`${precedingWord} ${followingWord}`]: precedingValue * followingValue }
121
+ // For numbers >= 1,000,000, use scale decomposition
122
+ return buildLargeNumberWords(n)
123
+ }
124
+
125
+ /**
126
+ * Builds words for numbers >= 1,000,000.
127
+ *
128
+ * @param {bigint} n - Number >= 1,000,000
129
+ * @returns {string} Greek words
130
+ */
131
+ function buildLargeNumberWords (n) {
132
+ const numStr = n.toString()
133
+ const len = numStr.length
134
+
135
+ // Build segments of 3 digits from right to left
136
+ const segments = []
137
+ const segmentSize = 3
138
+
139
+ const remainderLen = len % segmentSize
140
+ let pos = 0
141
+ if (remainderLen > 0) {
142
+ segments.push(Number(numStr.slice(0, remainderLen)))
143
+ pos = remainderLen
144
+ }
145
+ while (pos < len) {
146
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
147
+ pos += segmentSize
148
+ }
149
+
150
+ // Convert segments to words
151
+ const parts = []
152
+ let scaleIndex = segments.length - 1
153
+
154
+ for (let i = 0; i < segments.length; i++) {
155
+ const segment = segments[i]
156
+
157
+ if (segment !== 0) {
158
+ const segmentWord = SEGMENTS[segment]
159
+
160
+ if (scaleIndex === 0) {
161
+ // Units segment
162
+ parts.push(segmentWord)
163
+ } else if (scaleIndex === 1) {
164
+ // Thousands - omit "ένα" before χίλια
165
+ if (segment === 1) {
166
+ parts.push(THOUSAND)
167
+ } else {
168
+ parts.push(segmentWord + ' ' + THOUSAND)
169
+ }
170
+ } else {
171
+ // Millions+ - omit "ένα" before scale words
172
+ const scaleWord = SCALES[scaleIndex - 2]
173
+ if (segment === 1) {
174
+ parts.push(scaleWord)
175
+ } else {
176
+ parts.push(segmentWord + ' ' + scaleWord)
177
+ }
178
+ }
179
+ }
180
+
181
+ scaleIndex--
182
+ }
183
+
184
+ return parts.join(' ')
185
+ }
186
+
187
+ /**
188
+ * Converts decimal digits to Greek words (per-digit).
189
+ *
190
+ * @param {string} decimalPart - Decimal digits (without the point)
191
+ * @returns {string} Greek words for decimal part
192
+ */
193
+ function decimalPartToWords (decimalPart) {
194
+ const parts = []
195
+
196
+ for (const digit of decimalPart) {
197
+ const d = parseInt(digit, 10)
198
+ if (d === 0) {
199
+ parts.push(ZERO)
200
+ } else {
201
+ parts.push(ONES[d])
85
202
  }
203
+ }
86
204
 
87
- // Addition: smaller numbers added together
88
- return { [`${precedingWord} ${followingWord}`]: precedingValue + followingValue }
205
+ return parts.join(' ')
206
+ }
207
+
208
+ /**
209
+ * Converts a numeric value to Greek words.
210
+ *
211
+ * @param {number | string | bigint} value - The numeric value to convert
212
+ * @returns {string} The number in Greek words
213
+ * @throws {TypeError} If value is not a valid numeric type
214
+ * @throws {Error} If value is not a valid number format
215
+ *
216
+ * @example
217
+ * toWords(21) // 'είκοσι ένα'
218
+ * toWords(1000) // 'χίλια'
219
+ * toWords('3.14') // 'τρία κόμμα ένα τέσσερα'
220
+ */
221
+ function toWords (value) {
222
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
223
+
224
+ let result = ''
225
+
226
+ if (isNegative) {
227
+ result = NEGATIVE + ' '
89
228
  }
229
+
230
+ result += integerToWords(integerPart)
231
+
232
+ if (decimalPart) {
233
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
234
+ }
235
+
236
+ return result
90
237
  }
238
+
239
+ // ============================================================================
240
+ // Public API
241
+ // ============================================================================
242
+
243
+ export { toWords }
@@ -1,16 +1,17 @@
1
1
  /**
2
- * English language converter.
2
+ * Converts a numeric value to English words.
3
3
  *
4
- * Supports:
5
- * - Hyphenated compounds (e.g., "twenty-three")
6
- * - "and" after hundreds (e.g., "one hundred and one")
7
- * - Numbers up to octillions
4
+ * This is the main public API. It accepts any valid numeric input
5
+ * (number, string, or bigint) and handles parsing internally.
6
+ *
7
+ * @param {number | string | bigint} value - The numeric value to convert
8
+ * @returns {string} The number in English words
9
+ * @throws {TypeError} If value is not a valid numeric type
10
+ * @throws {Error} If value is not a valid number format
11
+ *
12
+ * @example
13
+ * toWords(42) // 'forty-two'
14
+ * toWords(-3.14) // 'minus three point one four'
15
+ * toWords('1000000') // 'one million'
8
16
  */
9
- export class English extends GreedyScaleLanguage {
10
- scaleWords: (string | bigint)[][];
11
- /** Combines two word-sets with English hyphenation and "and" rules. */
12
- combineWordSets(preceding: any, following: any): {
13
- [x: string]: any;
14
- };
15
- }
16
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js';
17
+ export function toWords(value: number | string | bigint): string;
@@ -1,86 +1,256 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
1
+ /**
2
+ * English language converter - Functional Implementation v2
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
+ * English-specific rules (handled in precomputation):
11
+ * - "and" after hundreds: "one hundred and twenty-three"
12
+ * - Hyphenated tens-ones: "twenty-one", "forty-two"
13
+ * - "and" before final segment when following scale word
14
+ */
15
+
16
+ import { parseNumericValue } from '../utils/parse-numeric.js'
17
+
18
+ // ============================================================================
19
+ // Vocabulary (module-level constants)
20
+ // ============================================================================
21
+
22
+ const ONES = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
23
+ const TEENS = ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen']
24
+ const TENS = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']
25
+
26
+ const SCALES = ['thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion', 'sextillion', 'septillion', 'octillion']
27
+
28
+ const HUNDRED = 'hundred'
29
+ const ZERO = 'zero'
30
+ const NEGATIVE = 'minus'
31
+ const DECIMAL_SEP = 'point'
32
+
33
+ // ============================================================================
34
+ // Precomputed Lookup Tables (built once at module load)
35
+ // ============================================================================
2
36
 
3
37
  /**
4
- * English language converter.
38
+ * Builds segment word for 0-999.
39
+ * Returns object with word and whether it contains "hundred" (for "and" logic).
40
+ */
41
+ function buildSegment (n) {
42
+ if (n === 0) return { word: '', hasHundred: false }
43
+
44
+ const ones = n % 10
45
+ const tens = Math.floor(n / 10) % 10
46
+ const hundreds = Math.floor(n / 100)
47
+
48
+ let result = ''
49
+ let hasHundred = false
50
+
51
+ // Hundreds
52
+ if (hundreds > 0) {
53
+ result = ONES[hundreds] + ' ' + HUNDRED
54
+ hasHundred = true
55
+ }
56
+
57
+ // Tens and ones
58
+ let tensOnes = ''
59
+ if (tens === 1) {
60
+ tensOnes = TEENS[ones]
61
+ } else if (tens >= 2) {
62
+ if (ones > 0) {
63
+ tensOnes = TENS[tens] + '-' + ONES[ones]
64
+ } else {
65
+ tensOnes = TENS[tens]
66
+ }
67
+ } else if (ones > 0) {
68
+ tensOnes = ONES[ones]
69
+ }
70
+
71
+ // Combine with "and" after hundreds
72
+ if (result && tensOnes) {
73
+ return { word: result + ' and ' + tensOnes, hasHundred: true }
74
+ }
75
+
76
+ return { word: result || tensOnes, hasHundred }
77
+ }
78
+
79
+ // Precompute all 1000 segment words (0-999)
80
+ const SEGMENTS = new Array(1000)
81
+ const SEGMENTS_HAS_HUNDRED = new Array(1000)
82
+
83
+ for (let i = 0; i < 1000; i++) {
84
+ const result = buildSegment(i)
85
+ SEGMENTS[i] = result.word
86
+ SEGMENTS_HAS_HUNDRED[i] = result.hasHundred
87
+ }
88
+
89
+ // ============================================================================
90
+ // Conversion Functions
91
+ // ============================================================================
92
+
93
+ /**
94
+ * Converts a non-negative integer to English words.
5
95
  *
6
- * Supports:
7
- * - Hyphenated compounds (e.g., "twenty-three")
8
- * - "and" after hundreds (e.g., "one hundred and one")
9
- * - Numbers up to octillions
96
+ * @param {bigint} n - Non-negative integer to convert
97
+ * @returns {string} English words
10
98
  */
11
- export class English extends GreedyScaleLanguage {
12
- negativeWord = 'minus'
13
- decimalSeparatorWord = 'point'
14
- zeroWord = 'zero'
15
-
16
- scaleWords = [
17
- [1_000_000_000_000_000_000_000_000_000n, 'octillion'],
18
- [1_000_000_000_000_000_000_000_000n, 'septillion'],
19
- [1_000_000_000_000_000_000_000n, 'sextillion'],
20
- [1_000_000_000_000_000_000n, 'quintillion'],
21
- [1_000_000_000_000_000n, 'quadrillion'],
22
- [1_000_000_000_000n, 'trillion'],
23
- [1_000_000_000n, 'billion'],
24
- [1_000_000n, 'million'],
25
- [1000n, 'thousand'],
26
- [100n, 'hundred'],
27
- [90n, 'ninety'],
28
- [80n, 'eighty'],
29
- [70n, 'seventy'],
30
- [60n, 'sixty'],
31
- [50n, 'fifty'],
32
- [40n, 'forty'],
33
- [30n, 'thirty'],
34
- [20n, 'twenty'],
35
- [19n, 'nineteen'],
36
- [18n, 'eighteen'],
37
- [17n, 'seventeen'],
38
- [16n, 'sixteen'],
39
- [15n, 'fifteen'],
40
- [14n, 'fourteen'],
41
- [13n, 'thirteen'],
42
- [12n, 'twelve'],
43
- [11n, 'eleven'],
44
- [10n, 'ten'],
45
- [9n, 'nine'],
46
- [8n, 'eight'],
47
- [7n, 'seven'],
48
- [6n, 'six'],
49
- [5n, 'five'],
50
- [4n, 'four'],
51
- [3n, 'three'],
52
- [2n, 'two'],
53
- [1n, 'one'],
54
- [0n, 'zero']
55
- ]
56
-
57
- /** Combines two word-sets with English hyphenation and "and" rules. */
58
- combineWordSets (preceding, following) {
59
- const precedingWord = Object.keys(preceding)[0]
60
- const precedingValue = Object.values(preceding)[0]
61
- const followingWord = Object.keys(following)[0]
62
- const followingValue = Object.values(following)[0]
63
-
64
- // Rule 1: Implicit "one" - omit when multiplying ("one hundred" → "hundred")
65
- if (precedingValue === 1n && followingValue < 100n) {
66
- return { [followingWord]: followingValue }
99
+ function integerToWords (n) {
100
+ if (n === 0n) return ZERO
101
+
102
+ // Fast path: numbers < 1000 (direct lookup)
103
+ if (n < 1000n) {
104
+ return SEGMENTS[Number(n)]
105
+ }
106
+
107
+ // Fast path: numbers < 1,000,000 (thousands)
108
+ if (n < 1_000_000n) {
109
+ const thousands = Number(n / 1000n)
110
+ const remainder = Number(n % 1000n)
111
+
112
+ let result = SEGMENTS[thousands] + ' ' + SCALES[0]
113
+
114
+ if (remainder > 0) {
115
+ const remainderWord = SEGMENTS[remainder]
116
+ // Insert "and" if remainder doesn't have hundred
117
+ if (!SEGMENTS_HAS_HUNDRED[remainder]) {
118
+ result += ' and ' + remainderWord
119
+ } else {
120
+ result += ' ' + remainderWord
121
+ }
67
122
  }
68
123
 
69
- // Rule 2: Hyphenate compounds under 100 ("twenty-three")
70
- if (precedingValue < 100n && precedingValue > followingValue) {
71
- return { [`${precedingWord}-${followingWord}`]: precedingValue + followingValue }
124
+ return result
125
+ }
126
+
127
+ // For numbers >= 1,000,000, use scale decomposition
128
+ return buildLargeNumberWords(n)
129
+ }
130
+
131
+ /**
132
+ * Builds words for numbers >= 1,000,000.
133
+ * Uses BigInt division for faster segment extraction.
134
+ *
135
+ * @param {bigint} n - Number >= 1,000,000
136
+ * @returns {string} English words
137
+ */
138
+ function buildLargeNumberWords (n) {
139
+ // Extract segments using BigInt division (faster than string slicing)
140
+ // Segments are stored least-significant first (index 0 = ones, 1 = thousands, etc.)
141
+ const segments = []
142
+ let temp = n
143
+ while (temp > 0n) {
144
+ segments.push(Number(temp % 1000n))
145
+ temp = temp / 1000n
146
+ }
147
+
148
+ // Find the first (smallest index) non-zero segment - this is processed last
149
+ let firstNonZeroIdx = -1
150
+ for (let i = 0; i < segments.length; i++) {
151
+ if (segments[i] !== 0) {
152
+ firstNonZeroIdx = i
153
+ break
72
154
  }
155
+ }
156
+
157
+ // Build result string directly (avoids intermediate object allocations)
158
+ // Process from most-significant (end) to least-significant (start)
159
+ let result = ''
160
+ let prevWasScale = false
161
+
162
+ for (let i = segments.length - 1; i >= 0; i--) {
163
+ const segment = segments[i]
164
+ if (segment === 0) continue
165
+
166
+ const segmentWord = SEGMENTS[segment]
167
+ const hasHundred = SEGMENTS_HAS_HUNDRED[segment]
168
+ const isLastSegment = (i === firstNonZeroIdx)
73
169
 
74
- // Rule 3: Add "and" before units after hundreds ("one hundred and one")
75
- if (precedingValue >= 100n && followingValue < 100n) {
76
- return { [`${precedingWord} and ${followingWord}`]: precedingValue + followingValue }
170
+ // Add "and" only before FINAL segment if it follows scale and doesn't have hundred
171
+ if (result && isLastSegment && prevWasScale && !hasHundred) {
172
+ result += ' and'
77
173
  }
78
174
 
79
- // Rule 4: Multiply when following > preceding ("one thousand")
80
- if (followingValue > precedingValue) {
81
- return { [`${precedingWord} ${followingWord}`]: precedingValue * followingValue }
175
+ // Add segment word
176
+ if (result) result += ' '
177
+ result += segmentWord
178
+
179
+ // Add scale word (i=0 is units, i=1 is thousands, etc.)
180
+ if (i > 0) {
181
+ result += ' ' + SCALES[i - 1]
182
+ prevWasScale = true
183
+ } else {
184
+ prevWasScale = false
82
185
  }
186
+ }
83
187
 
84
- return { [`${precedingWord} ${followingWord}`]: precedingValue + followingValue }
188
+ return result
189
+ }
190
+
191
+ /**
192
+ * Converts decimal digits to English words.
193
+ *
194
+ * @param {string} decimalPart - Decimal digits (without the point)
195
+ * @returns {string} English words for decimal part
196
+ */
197
+ function decimalPartToWords (decimalPart) {
198
+ let result = ''
199
+
200
+ // Handle leading zeros
201
+ let i = 0
202
+ while (i < decimalPart.length && decimalPart[i] === '0') {
203
+ if (result) result += ' '
204
+ result += ZERO
205
+ i++
85
206
  }
207
+
208
+ // Convert remainder as a single number
209
+ const remainder = decimalPart.slice(i)
210
+ if (remainder) {
211
+ if (result) result += ' '
212
+ result += integerToWords(BigInt(remainder))
213
+ }
214
+
215
+ return result
216
+ }
217
+
218
+ /**
219
+ * Converts a numeric value to English words.
220
+ *
221
+ * This is the main public API. It accepts any valid numeric input
222
+ * (number, string, or bigint) and handles parsing internally.
223
+ *
224
+ * @param {number | string | bigint} value - The numeric value to convert
225
+ * @returns {string} The number in English 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(42) // 'forty-two'
231
+ * toWords(-3.14) // 'minus three point one four'
232
+ * toWords('1000000') // 'one million'
233
+ */
234
+ function toWords (value) {
235
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
236
+
237
+ let result = ''
238
+
239
+ if (isNegative) {
240
+ result = NEGATIVE + ' '
241
+ }
242
+
243
+ result += integerToWords(integerPart)
244
+
245
+ if (decimalPart) {
246
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
247
+ }
248
+
249
+ return result
86
250
  }
251
+
252
+ // ============================================================================
253
+ // Public API
254
+ // ============================================================================
255
+
256
+ export { toWords }