n2words 1.24.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 (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  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 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  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 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  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 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -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 +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  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 +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,135 +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
+ // ============================================================================
2
50
 
3
51
  /**
4
- * German language converter.
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
+ }
89
+
90
+ /**
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
- * Handles German grammatical features:
7
- * - "eins" vs "ein" and "eine" forms for 1
8
- * - Compound words without separators (e.g., "einundzwanzig" = 21)
9
- * - Million/Billion pluralization
10
- * - Special characters (e.g., "ü", "ö", "ß")
144
+ * @param {bigint} n - Non-negative integer to convert
145
+ * @returns {string} German words
11
146
  */
12
- export class German extends GreedyScaleLanguage {
13
- negativeWord = 'minus'
14
- decimalSeparatorWord = 'komma'
15
- zeroWord = 'null'
16
- scaleWordPairs = [
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
- /**
58
- * Merges two adjacent word-number pairs according to German grammar rules.
59
- *
60
- * German-specific rules:
61
- * - Implicit "eins": `mergeScales({ 'eins': 1n }, { 'hundert': 100n })` → `{ 'einhundert': 100n }`
62
- * - Compound words without separators (e.g., "einundzwanzig" = 21)
63
- * - Special handling for forms of 1 (eins/ein/eine) depending on context
64
- * - Pluralization of millions and higher: "Millionen", "Milliarden"
65
- * - Reordering of tens and units: `mergeScales({ 'zwanzig': 20n }, { 'eins': 1n })` → `{ 'einundzwanzig': 21n }`
66
- *
67
- * @param {Object} currentPair The left operand as `{ word: BigInt }`.
68
- * @param {Object} nextPair The right operand as `{ word: BigInt }`.
69
- * @returns {Object} Merged pair with combined word and resulting numeric value.
70
- *
71
- * @example
72
- * mergeScales({ 'eins': 1n }, { 'hundert': 100n }); // { 'einhundert': 100n }
73
- * mergeScales({ 'zwanzig': 20n }, { 'drei': 3n }); // { 'dreiundzwanzig': 23n }
74
- */
75
- mergeScales (currentPair, nextPair) {
76
- let currentWord = Object.keys(currentPair)[0]
77
- let nextWord = Object.keys(nextPair)[0]
78
- const currentNumber = Object.values(currentPair)[0]
79
- const nextNumber = Object.values(nextPair)[0]
80
-
81
- // Handle form of 1: "eins" → "ein(e)" in certain contexts
82
- if (currentNumber === 1n) {
83
- if (nextNumber === 100n || nextNumber === 1000n) {
84
- return { [`ein${nextWord}`]: nextNumber }
85
- }
86
- if (nextNumber < 1_000_000n) {
87
- return nextPair
88
- }
89
- currentWord = '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]
90
165
  }
91
166
 
92
- if (nextNumber > currentNumber) {
93
- // Multiply: apply pluralization rules for millions
94
- if (nextNumber >= 1_000_000n) {
95
- if (currentNumber > 1n) {
96
- nextWord += nextWord.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]
97
221
  }
98
- currentWord += ' '
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 })
99
225
  }
100
- return { [`${currentWord}${nextWord}`]: currentNumber * nextNumber }
101
226
  }
102
227
 
103
- // Add: handle special case of tens + units
104
- if (nextNumber < 10n && currentNumber > 10n && currentNumber < 100n) {
105
- // German reverses tens and units (einundzwanzig = one and twenty)
106
- if (nextNumber === 1n) {
107
- nextWord = '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 += ' '
108
258
  }
109
- const temp = nextWord
110
- nextWord = currentWord
111
- currentWord = `${temp}und`
112
- } else if (currentNumber >= 1_000_000n) {
113
- currentWord += ' '
114
259
  }
115
260
 
116
- return { [`${currentWord}${nextWord}`]: currentNumber + nextNumber }
261
+ result += part.words
117
262
  }
263
+
264
+ return result
118
265
  }
119
266
 
120
267
  /**
121
- * Converts a number to German cardinal (written) form.
268
+ * Converts decimal digits to German words.
122
269
  *
123
- * @param {number|string|bigint} value The number to convert.
124
- * @param {Object} [options] Conversion options (see German class options).
125
- * @returns {string} The number expressed in German words.
126
- * @throws {TypeError} If value is NaN or invalid type.
127
- * @throws {Error} If value is an invalid number string.
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++
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
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
128
304
  *
129
305
  * @example
130
- * convertToWords(42); // 'zweiundvierzig'
131
- * convertToWords('1.5'); // 'eins komma fünf'
306
+ * toWords(21) // 'einundzwanzig'
307
+ * toWords(1000) // 'eintausend'
308
+ * toWords(1000000) // 'eine Million'
132
309
  */
133
- export default function convertToWords (value, options = {}) {
134
- return new German(options).convertToWords(value)
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
135
326
  }
327
+
328
+ // ============================================================================
329
+ // Public API
330
+ // ============================================================================
331
+
332
+ export { toWords }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Converts a numeric value to Greek words.
3
+ *
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') // 'τρία κόμμα ένα τέσσερα'
13
+ */
14
+ export function toWords(value: number | string | bigint): string;