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