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,101 +1,332 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
1
+ /**
2
+ * German language converter - Functional Implementation
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ * Self-contained module with its own input validation, ready for subpath exports.
6
+ *
7
+ * Key optimization: Precompute all segment values (0-999) at module load.
8
+ * This eliminates all per-call string manipulation for segment conversion.
9
+ *
10
+ * German-specific rules (handled in precomputation):
11
+ * - Inverted tens-ones order: "einundzwanzig" (one-and-twenty) for 21-99
12
+ * - Compound words without spaces below million level
13
+ * - Three forms of 1: "eins" (standalone), "ein" (before hundert/tausend), "eine" (before Million+)
14
+ * - Scale pluralization: Million → Millionen, Milliarde → Milliarden
15
+ * - Spaces only around million+ scale words
16
+ */
17
+
18
+ import { parseNumericValue } from '../utils/parse-numeric.js'
19
+
20
+ // ============================================================================
21
+ // Vocabulary (module-level constants)
22
+ // ============================================================================
23
+
24
+ // Ones words (1-9), index 0 unused
25
+ const ONES = ['', 'eins', 'zwei', 'drei', 'vier', 'fünf', 'sechs', 'sieben', 'acht', 'neun']
26
+
27
+ // "ein" form for use before hundert/und
28
+ const EIN = 'ein'
29
+
30
+ // Teens (10-19)
31
+ const TEENS = ['zehn', 'elf', 'zwölf', 'dreizehn', 'vierzehn', 'fünfzehn', 'sechzehn', 'siebzehn', 'achtzehn', 'neunzehn']
32
+
33
+ // Tens (20-90)
34
+ const TENS = ['', '', 'zwanzig', 'dreißig', 'vierzig', 'fünfzig', 'sechzig', 'siebzig', 'achtzig', 'neunzig']
35
+
36
+ // Scale words (index 0 = thousand, 1 = million, etc.)
37
+ const SCALES = ['tausend', 'Million', 'Milliarde', 'Billion', 'Billiarde', 'Trillion', 'Trilliarde', 'Quadrillion', 'Quadrilliarde']
38
+
39
+ // Pluralized scale words (million+)
40
+ const SCALES_PLURAL = ['tausend', 'Millionen', 'Milliarden', 'Billionen', 'Billiarden', 'Trillionen', 'Trilliarden', 'Quadrillionen', 'Quadrilliarden']
41
+
42
+ const HUNDRED = 'hundert'
43
+ const ZERO = 'null'
44
+ const NEGATIVE = 'minus'
45
+ const DECIMAL_SEP = 'komma'
46
+
47
+ // ============================================================================
48
+ // Precomputed Lookup Tables (built once at module load)
49
+ // ============================================================================
50
+
51
+ /**
52
+ * Builds segment word for 0-999 (standalone form, uses "eins").
53
+ * German inverts ones and tens: "einundzwanzig" = one-and-twenty
54
+ * Only used during table construction.
55
+ */
56
+ function buildSegment (n) {
57
+ if (n === 0) return ''
58
+
59
+ const ones = n % 10
60
+ const tens = Math.floor(n / 10) % 10
61
+ const hundreds = Math.floor(n / 100)
62
+
63
+ let result = ''
64
+
65
+ // Hundreds: "ein" before hundert, not "eins"
66
+ if (hundreds > 0) {
67
+ result += (hundreds === 1 ? EIN : ONES[hundreds]) + HUNDRED
68
+ }
69
+
70
+ // Tens and ones
71
+ if (tens === 1) {
72
+ // Teens
73
+ result += TEENS[ones]
74
+ } else if (tens >= 2 && ones > 0) {
75
+ // Inverted: "einundzwanzig" (one-and-twenty)
76
+ // Use "ein" before "und", not "eins"
77
+ result += (ones === 1 ? EIN : ONES[ones]) + 'und' + TENS[tens]
78
+ } else if (tens >= 2) {
79
+ // Just tens
80
+ result += TENS[tens]
81
+ } else if (ones > 0) {
82
+ // Just ones (no tens, possibly after hundreds)
83
+ // Use "eins" for standalone/after hundreds
84
+ result += ONES[ones]
85
+ }
86
+
87
+ return result
88
+ }
2
89
 
3
90
  /**
4
- * German language converter.
91
+ * Builds segment word for use before "tausend".
92
+ * Uses "ein" instead of "eins" for 1.
93
+ */
94
+ function buildSegmentForThousand (n) {
95
+ if (n === 0) return ''
96
+ if (n === 1) return EIN // "eintausend"
97
+
98
+ const ones = n % 10
99
+ const tens = Math.floor(n / 10) % 10
100
+ const hundreds = Math.floor(n / 100)
101
+
102
+ let result = ''
103
+
104
+ if (hundreds > 0) {
105
+ result += (hundreds === 1 ? EIN : ONES[hundreds]) + HUNDRED
106
+ }
107
+
108
+ if (tens === 1) {
109
+ result += TEENS[ones]
110
+ } else if (tens >= 2 && ones > 0) {
111
+ result += (ones === 1 ? EIN : ONES[ones]) + 'und' + TENS[tens]
112
+ } else if (tens >= 2) {
113
+ result += TENS[tens]
114
+ } else if (ones > 0 && hundreds > 0) {
115
+ // After hundreds, ones 1 stays "eins" when not followed by scale
116
+ // But we're going to tausend, so this path won't hit for n=1
117
+ result += ONES[ones]
118
+ } else if (ones > 0) {
119
+ result += ONES[ones]
120
+ }
121
+
122
+ return result
123
+ }
124
+
125
+ // Precompute all 1000 segment words (0-999) - standalone form
126
+ const SEGMENTS = new Array(1000)
127
+ for (let i = 0; i < 1000; i++) {
128
+ SEGMENTS[i] = buildSegment(i)
129
+ }
130
+
131
+ // Precompute all 1000 segment words for thousand context
132
+ const SEGMENTS_THOUSAND = new Array(1000)
133
+ for (let i = 0; i < 1000; i++) {
134
+ SEGMENTS_THOUSAND[i] = buildSegmentForThousand(i)
135
+ }
136
+
137
+ // ============================================================================
138
+ // Conversion Functions
139
+ // ============================================================================
140
+
141
+ /**
142
+ * Converts a non-negative integer to German words.
5
143
  *
6
- * Supports:
7
- * - Compound formation (no separators between words)
8
- * - Three forms of 1 (eins/ein/eine)
9
- * - Units-before-tens ordering (einundzwanzig = one and twenty)
144
+ * @param {bigint} n - Non-negative integer to convert
145
+ * @returns {string} German words
10
146
  */
11
- export class German extends GreedyScaleLanguage {
12
- negativeWord = 'minus'
13
- decimalSeparatorWord = 'komma'
14
- zeroWord = 'null'
15
-
16
- scaleWords = [
17
- [1_000_000_000_000_000_000_000_000_000n, 'Quadrilliarde'],
18
- [1_000_000_000_000_000_000_000_000n, 'Quadrillion'],
19
- [1_000_000_000_000_000_000_000n, 'Trilliarde'],
20
- [1_000_000_000_000_000_000n, 'Trillion'],
21
- [1_000_000_000_000_000n, 'Billiarde'],
22
- [1_000_000_000_000n, 'Billion'],
23
- [1_000_000_000n, 'Milliarde'],
24
- [1_000_000n, 'Million'],
25
- [1000n, 'tausend'],
26
- [100n, 'hundert'],
27
- [90n, 'neunzig'],
28
- [80n, 'achtzig'],
29
- [70n, 'siebzig'],
30
- [60n, 'sechzig'],
31
- [50n, 'fünfzig'],
32
- [40n, 'vierzig'],
33
- [30n, 'dreißig'],
34
- [20n, 'zwanzig'],
35
- [19n, 'neunzehn'],
36
- [18n, 'achtzehn'],
37
- [17n, 'siebzehn'],
38
- [16n, 'sechzehn'],
39
- [15n, 'fünfzehn'],
40
- [14n, 'vierzehn'],
41
- [13n, 'dreizehn'],
42
- [12n, 'zwölf'],
43
- [11n, 'elf'],
44
- [10n, 'zehn'],
45
- [9n, 'neun'],
46
- [8n, 'acht'],
47
- [7n, 'sieben'],
48
- [6n, 'sechs'],
49
- [5n, 'fünf'],
50
- [4n, 'vier'],
51
- [3n, 'drei'],
52
- [2n, 'zwei'],
53
- [1n, 'eins'],
54
- [0n, 'null']
55
- ]
56
-
57
- /** Combines two word-sets with German compound formation and reversal rules. */
58
- combineWordSets (preceding, following) {
59
- let precedingWord = Object.keys(preceding)[0]
60
- let followingWord = Object.keys(following)[0]
61
- const precedingValue = Object.values(preceding)[0]
62
- const followingValue = Object.values(following)[0]
63
-
64
- // Handle form of 1: "eins" → "ein(e)" in certain contexts
65
- if (precedingValue === 1n) {
66
- if (followingValue === 100n || followingValue === 1000n) {
67
- return { [`ein${followingWord}`]: followingValue }
68
- }
69
- if (followingValue < 1_000_000n) {
70
- return following
71
- }
72
- precedingWord = 'eine'
147
+ function integerToWords (n) {
148
+ if (n === 0n) return ZERO
149
+
150
+ // Fast path: numbers < 1000 (direct lookup)
151
+ if (n < 1000n) {
152
+ return SEGMENTS[Number(n)]
153
+ }
154
+
155
+ // Fast path: numbers < 1,000,000 (thousands)
156
+ if (n < 1_000_000n) {
157
+ const thousands = Number(n / 1000n)
158
+ const remainder = Number(n % 1000n)
159
+
160
+ // Compound: "eintausendzweihundert" (no spaces)
161
+ let result = SEGMENTS_THOUSAND[thousands] + SCALES[0]
162
+
163
+ if (remainder > 0) {
164
+ result += SEGMENTS[remainder]
73
165
  }
74
166
 
75
- if (followingValue > precedingValue) {
76
- // Multiply: apply pluralization rules for millions
77
- if (followingValue >= 1_000_000n) {
78
- if (precedingValue > 1n) {
79
- followingWord += followingWord.at(-1) === 'e' ? 'n' : 'en'
167
+ return result
168
+ }
169
+
170
+ // For numbers >= 1,000,000, use scale decomposition
171
+ return buildLargeNumberWords(n)
172
+ }
173
+
174
+ /**
175
+ * Builds words for numbers >= 1,000,000.
176
+ *
177
+ * @param {bigint} n - Number >= 1,000,000
178
+ * @returns {string} German words
179
+ */
180
+ function buildLargeNumberWords (n) {
181
+ const numStr = n.toString()
182
+ const len = numStr.length
183
+
184
+ // Build segments of 3 digits from right to left
185
+ const segments = []
186
+ const segmentSize = 3
187
+
188
+ const remainderLen = len % segmentSize
189
+ let pos = 0
190
+ if (remainderLen > 0) {
191
+ segments.push(Number(numStr.slice(0, remainderLen)))
192
+ pos = remainderLen
193
+ }
194
+ while (pos < len) {
195
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
196
+ pos += segmentSize
197
+ }
198
+
199
+ // Convert segments to words
200
+ const parts = []
201
+ let scaleIndex = segments.length - 1
202
+
203
+ for (let i = 0; i < segments.length; i++) {
204
+ const segment = segments[i]
205
+
206
+ if (segment !== 0) {
207
+ if (scaleIndex === 0) {
208
+ // Units segment (no scale word)
209
+ parts.push({ words: SEGMENTS[segment], isScale: false, scaleLevel: 0 })
210
+ } else if (scaleIndex === 1) {
211
+ // Thousands: compound without space
212
+ const segWords = SEGMENTS_THOUSAND[segment]
213
+ parts.push({ words: segWords + SCALES[0], isScale: false, scaleLevel: 1 })
214
+ } else {
215
+ // Million+ : space around scale word
216
+ let segWords
217
+ if (segment === 1) {
218
+ segWords = 'eine' // "eine Million"
219
+ } else {
220
+ segWords = SEGMENTS[segment]
80
221
  }
81
- precedingWord += ' '
222
+ const scaleWord = segment === 1 ? SCALES[scaleIndex - 1] : SCALES_PLURAL[scaleIndex - 1]
223
+ parts.push({ words: segWords, isScale: false, scaleLevel: scaleIndex })
224
+ parts.push({ words: scaleWord, isScale: true, scaleLevel: scaleIndex })
82
225
  }
83
- return { [`${precedingWord}${followingWord}`]: precedingValue * followingValue }
84
226
  }
85
227
 
86
- // Add: handle special case of tens + units
87
- if (followingValue < 10n && precedingValue > 10n && precedingValue < 100n) {
88
- // German reverses tens and units (einundzwanzig = one and twenty)
89
- if (followingValue === 1n) {
90
- followingWord = 'ein'
228
+ scaleIndex--
229
+ }
230
+
231
+ // Join with German spacing rules: space around million+ scale words
232
+ return joinGermanParts(parts)
233
+ }
234
+
235
+ /**
236
+ * Joins parts with German spacing rules.
237
+ * Spaces only around million+ scale words.
238
+ *
239
+ * @param {Array} parts - Parts with metadata
240
+ * @returns {string} Joined string
241
+ */
242
+ function joinGermanParts (parts) {
243
+ if (parts.length === 0) return ZERO
244
+
245
+ let result = ''
246
+
247
+ for (let i = 0; i < parts.length; i++) {
248
+ const part = parts[i]
249
+ const prevPart = i > 0 ? parts[i - 1] : null
250
+
251
+ // Add space before if:
252
+ // - Current is a million+ scale word
253
+ // - Previous was a million+ scale word
254
+ if (i > 0) {
255
+ const needsSpace = part.isScale || (prevPart && prevPart.isScale)
256
+ if (needsSpace) {
257
+ result += ' '
91
258
  }
92
- const temp = followingWord
93
- followingWord = precedingWord
94
- precedingWord = `${temp}und`
95
- } else if (precedingValue >= 1_000_000n) {
96
- precedingWord += ' '
97
259
  }
98
260
 
99
- return { [`${precedingWord}${followingWord}`]: precedingValue + followingValue }
261
+ result += part.words
262
+ }
263
+
264
+ return result
265
+ }
266
+
267
+ /**
268
+ * Converts decimal digits to German words.
269
+ *
270
+ * @param {string} decimalPart - Decimal digits (without the point)
271
+ * @returns {string} German words for decimal part
272
+ */
273
+ function decimalPartToWords (decimalPart) {
274
+ let result = ''
275
+
276
+ // Handle leading zeros
277
+ let i = 0
278
+ while (i < decimalPart.length && decimalPart[i] === '0') {
279
+ if (result) result += ' '
280
+ result += ZERO
281
+ i++
100
282
  }
283
+
284
+ // Convert remainder as a single number
285
+ const remainder = decimalPart.slice(i)
286
+ if (remainder) {
287
+ if (result) result += ' '
288
+ result += integerToWords(BigInt(remainder))
289
+ }
290
+
291
+ return result
101
292
  }
293
+
294
+ /**
295
+ * Converts a numeric value to German words.
296
+ *
297
+ * This is the main public API. It accepts any valid numeric input
298
+ * (number, string, or bigint) and handles parsing internally.
299
+ *
300
+ * @param {number | string | bigint} value - The numeric value to convert
301
+ * @returns {string} The number in German words
302
+ * @throws {TypeError} If value is not a valid numeric type
303
+ * @throws {Error} If value is not a valid number format
304
+ *
305
+ * @example
306
+ * toWords(21) // 'einundzwanzig'
307
+ * toWords(1000) // 'eintausend'
308
+ * toWords(1000000) // 'eine Million'
309
+ */
310
+ function toWords (value) {
311
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
312
+
313
+ let result = ''
314
+
315
+ if (isNegative) {
316
+ result = NEGATIVE + ' '
317
+ }
318
+
319
+ result += integerToWords(integerPart)
320
+
321
+ if (decimalPart) {
322
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
323
+ }
324
+
325
+ return result
326
+ }
327
+
328
+ // ============================================================================
329
+ // Public API
330
+ // ============================================================================
331
+
332
+ export { toWords }
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Greek language converter.
2
+ * Converts a numeric value to Greek words.
3
3
  *
4
- * Supports:
5
- * - Space-separated number composition
6
- * - Implicit "one" (ένα) omission before scale words
7
- * - Digit-by-digit decimal reading
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Greek words
6
+ * @throws {TypeError} If value is not a valid numeric type
7
+ * @throws {Error} If value is not a valid number format
8
+ *
9
+ * @example
10
+ * toWords(21) // 'είκοσι ένα'
11
+ * toWords(1000) // 'χίλια'
12
+ * toWords('3.14') // 'τρία κόμμα ένα τέσσερα'
8
13
  */
9
- export class Greek extends GreedyScaleLanguage {
10
- scaleWords: (string | bigint)[][];
11
- /** Combines two word-sets with Greek space-separation rules. */
12
- combineWordSets(preceding: any, following: any): any;
13
- }
14
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js';
14
+ export function toWords(value: number | string | bigint): string;