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.
- package/CHANGELOG.md +49 -0
- package/README.md +183 -156
- package/dist/languages/am-Latn.js +3 -0
- package/dist/languages/am-Latn.js.map +1 -0
- package/dist/languages/am.js +3 -0
- package/dist/languages/am.js.map +1 -0
- package/dist/languages/ar.js +3 -2
- package/dist/languages/ar.js.map +1 -1
- package/dist/languages/az.js +3 -2
- package/dist/languages/az.js.map +1 -1
- package/dist/languages/bn.js +3 -2
- package/dist/languages/bn.js.map +1 -1
- package/dist/languages/cs.js +3 -2
- package/dist/languages/cs.js.map +1 -1
- package/dist/languages/da.js +3 -2
- package/dist/languages/da.js.map +1 -1
- package/dist/languages/de.js +3 -2
- package/dist/languages/de.js.map +1 -1
- package/dist/languages/el.js +3 -2
- package/dist/languages/el.js.map +1 -1
- package/dist/languages/en.js +3 -2
- package/dist/languages/en.js.map +1 -1
- package/dist/languages/es.js +3 -2
- package/dist/languages/es.js.map +1 -1
- package/dist/languages/fa.js +3 -2
- package/dist/languages/fa.js.map +1 -1
- package/dist/languages/fi.js +3 -0
- package/dist/languages/fi.js.map +1 -0
- package/dist/languages/fil.js +3 -2
- package/dist/languages/fil.js.map +1 -1
- package/dist/languages/fr-BE.js +3 -2
- package/dist/languages/fr-BE.js.map +1 -1
- package/dist/languages/fr.js +3 -2
- package/dist/languages/fr.js.map +1 -1
- package/dist/languages/gu.js +3 -2
- package/dist/languages/gu.js.map +1 -1
- package/dist/languages/ha.js +3 -0
- package/dist/languages/ha.js.map +1 -0
- package/dist/languages/hbo.js +3 -0
- package/dist/languages/hbo.js.map +1 -0
- package/dist/languages/he.js +3 -2
- package/dist/languages/he.js.map +1 -1
- package/dist/languages/hi.js +3 -2
- package/dist/languages/hi.js.map +1 -1
- package/dist/languages/hr.js +3 -2
- package/dist/languages/hr.js.map +1 -1
- package/dist/languages/hu.js +3 -2
- package/dist/languages/hu.js.map +1 -1
- package/dist/languages/id.js +3 -2
- package/dist/languages/id.js.map +1 -1
- package/dist/languages/it.js +3 -2
- package/dist/languages/it.js.map +1 -1
- package/dist/languages/ja.js +3 -2
- package/dist/languages/ja.js.map +1 -1
- package/dist/languages/kn.js +3 -2
- package/dist/languages/kn.js.map +1 -1
- package/dist/languages/ko.js +3 -2
- package/dist/languages/ko.js.map +1 -1
- package/dist/languages/lt.js +3 -2
- package/dist/languages/lt.js.map +1 -1
- package/dist/languages/lv.js +3 -2
- package/dist/languages/lv.js.map +1 -1
- package/dist/languages/mr.js +3 -2
- package/dist/languages/mr.js.map +1 -1
- package/dist/languages/ms.js +3 -2
- package/dist/languages/ms.js.map +1 -1
- package/dist/languages/nb.js +3 -2
- package/dist/languages/nb.js.map +1 -1
- package/dist/languages/nl.js +3 -2
- package/dist/languages/nl.js.map +1 -1
- package/dist/languages/pa.js +3 -0
- package/dist/languages/pa.js.map +1 -0
- package/dist/languages/pl.js +3 -2
- package/dist/languages/pl.js.map +1 -1
- package/dist/languages/pt.js +3 -2
- package/dist/languages/pt.js.map +1 -1
- package/dist/languages/ro.js +3 -2
- package/dist/languages/ro.js.map +1 -1
- package/dist/languages/ru.js +3 -2
- package/dist/languages/ru.js.map +1 -1
- package/dist/languages/sr-Cyrl.js +3 -0
- package/dist/languages/sr-Cyrl.js.map +1 -0
- package/dist/languages/sr-Latn.js +3 -2
- package/dist/languages/sr-Latn.js.map +1 -1
- package/dist/languages/sv.js +3 -2
- package/dist/languages/sv.js.map +1 -1
- package/dist/languages/sw.js +3 -2
- package/dist/languages/sw.js.map +1 -1
- package/dist/languages/ta.js +3 -2
- package/dist/languages/ta.js.map +1 -1
- package/dist/languages/te.js +3 -2
- package/dist/languages/te.js.map +1 -1
- package/dist/languages/th.js +3 -2
- package/dist/languages/th.js.map +1 -1
- package/dist/languages/tr.js +3 -2
- package/dist/languages/tr.js.map +1 -1
- package/dist/languages/uk.js +3 -2
- package/dist/languages/uk.js.map +1 -1
- package/dist/languages/ur.js +3 -2
- package/dist/languages/ur.js.map +1 -1
- package/dist/languages/vi.js +3 -2
- package/dist/languages/vi.js.map +1 -1
- package/dist/languages/zh-Hans.js +3 -2
- package/dist/languages/zh-Hans.js.map +1 -1
- package/dist/languages/zh-Hant.js +3 -0
- package/dist/languages/zh-Hant.js.map +1 -0
- package/dist/n2words.js +3 -2
- package/dist/n2words.js.map +1 -1
- package/lib/languages/am-Latn.d.ts +7 -0
- package/lib/languages/am-Latn.js +164 -0
- package/lib/languages/am.d.ts +7 -0
- package/lib/languages/am.js +164 -0
- package/lib/languages/ar.d.ts +17 -0
- package/lib/languages/ar.js +171 -209
- package/lib/languages/az.d.ts +7 -0
- package/lib/languages/az.js +167 -49
- package/lib/languages/bn.d.ts +7 -0
- package/lib/languages/bn.js +142 -123
- package/lib/languages/cs.d.ts +18 -0
- package/lib/languages/cs.js +303 -176
- package/lib/languages/da.d.ts +14 -0
- package/lib/languages/da.js +267 -139
- package/lib/languages/de.d.ts +17 -0
- package/lib/languages/de.js +310 -113
- package/lib/languages/el.d.ts +14 -0
- package/lib/languages/el.js +225 -98
- package/lib/languages/en.d.ts +17 -0
- package/lib/languages/en.js +235 -102
- package/lib/languages/es.d.ts +21 -0
- package/lib/languages/es.js +307 -125
- package/lib/languages/fa.d.ts +7 -0
- package/lib/languages/fa.js +115 -108
- package/lib/languages/fi.d.ts +14 -0
- package/lib/languages/fi.js +245 -0
- package/lib/languages/fil.d.ts +7 -0
- package/lib/languages/fil.js +199 -139
- package/lib/languages/fr-BE.d.ts +11 -0
- package/lib/languages/fr-BE.js +287 -48
- package/lib/languages/fr.d.ts +21 -0
- package/lib/languages/fr.js +343 -119
- package/lib/languages/gu.d.ts +7 -0
- package/lib/languages/gu.js +125 -144
- package/lib/languages/ha.d.ts +7 -0
- package/lib/languages/ha.js +230 -0
- package/lib/languages/hbo.d.ts +13 -0
- package/lib/languages/hbo.js +300 -0
- package/lib/languages/he.d.ts +13 -0
- package/lib/languages/he.js +230 -283
- package/lib/languages/hi.d.ts +7 -0
- package/lib/languages/hi.js +142 -123
- package/lib/languages/hr.d.ts +11 -0
- package/lib/languages/hr.js +190 -129
- package/lib/languages/hu.d.ts +7 -0
- package/lib/languages/hu.js +194 -133
- package/lib/languages/id.d.ts +7 -0
- package/lib/languages/id.js +167 -140
- package/lib/languages/it.d.ts +19 -0
- package/lib/languages/it.js +337 -108
- package/lib/languages/ja.d.ts +17 -0
- package/lib/languages/ja.js +224 -155
- package/lib/languages/kn.d.ts +7 -0
- package/lib/languages/kn.js +128 -62
- package/lib/languages/ko.d.ts +14 -0
- package/lib/languages/ko.js +250 -70
- package/lib/languages/lt.d.ts +18 -0
- package/lib/languages/lt.js +287 -148
- package/lib/languages/lv.d.ts +18 -0
- package/lib/languages/lv.js +291 -123
- package/lib/languages/mr.d.ts +7 -0
- package/lib/languages/mr.js +125 -144
- package/lib/languages/ms.d.ts +7 -0
- package/lib/languages/ms.js +171 -112
- package/lib/languages/nb.d.ts +14 -0
- package/lib/languages/nb.js +275 -100
- package/lib/languages/nl.d.ts +26 -0
- package/lib/languages/nl.js +307 -174
- package/lib/languages/pa.d.ts +7 -0
- package/lib/languages/pa.js +163 -0
- package/lib/languages/pl.d.ts +22 -0
- package/lib/languages/pl.js +299 -158
- package/lib/languages/pt.d.ts +17 -0
- package/lib/languages/pt.js +279 -120
- package/lib/languages/ro.d.ts +18 -0
- package/lib/languages/ro.js +214 -337
- package/lib/languages/ru.d.ts +11 -0
- package/lib/languages/ru.js +219 -95
- package/lib/languages/sr-Cyrl.d.ts +11 -0
- package/lib/languages/sr-Cyrl.js +215 -0
- package/lib/languages/sr-Latn.d.ts +11 -0
- package/lib/languages/sr-Latn.js +190 -132
- package/lib/languages/sv.d.ts +14 -0
- package/lib/languages/sv.js +280 -103
- package/lib/languages/sw.d.ts +7 -0
- package/lib/languages/sw.js +135 -103
- package/lib/languages/ta.d.ts +7 -0
- package/lib/languages/ta.js +133 -205
- package/lib/languages/te.d.ts +7 -0
- package/lib/languages/te.js +148 -213
- package/lib/languages/th.d.ts +7 -0
- package/lib/languages/th.js +139 -101
- package/lib/languages/tr.d.ts +18 -0
- package/lib/languages/tr.js +246 -66
- package/lib/languages/uk.d.ts +11 -0
- package/lib/languages/uk.js +197 -101
- package/lib/languages/ur.d.ts +7 -0
- package/lib/languages/ur.js +160 -123
- package/lib/languages/vi.d.ts +17 -0
- package/lib/languages/vi.js +287 -164
- package/lib/languages/zh-Hans.d.ts +11 -0
- package/lib/languages/zh-Hans.js +159 -142
- package/lib/languages/zh-Hant.d.ts +11 -0
- package/lib/languages/zh-Hant.js +202 -0
- package/lib/n2words.d.ts +53 -0
- package/lib/n2words.js +91 -227
- package/lib/utils/is-plain-object.d.ts +13 -0
- package/lib/utils/is-plain-object.js +17 -0
- package/lib/utils/parse-numeric.d.ts +17 -0
- package/lib/utils/parse-numeric.js +108 -0
- package/lib/utils/validate-options.d.ts +8 -0
- package/lib/utils/validate-options.js +16 -0
- package/package.json +118 -67
- package/dist/languages/pa-Guru.js +0 -2
- package/dist/languages/pa-Guru.js.map +0 -1
- package/lib/classes/abstract-language.js +0 -261
- package/lib/classes/greedy-scale-language.js +0 -195
- package/lib/classes/slavic-language.js +0 -251
- package/lib/classes/south-asian-language.js +0 -161
- package/lib/classes/turkic-language.js +0 -63
- package/lib/languages/pa-Guru.js +0 -126
- package/typings/classes/abstract-language.d.ts +0 -144
- package/typings/classes/greedy-scale-language.d.ts +0 -148
- package/typings/classes/slavic-language.d.ts +0 -145
- package/typings/classes/south-asian-language.d.ts +0 -101
- package/typings/classes/turkic-language.d.ts +0 -42
- package/typings/languages/ar.d.ts +0 -93
- package/typings/languages/az.d.ts +0 -25
- package/typings/languages/bn.d.ts +0 -1
- package/typings/languages/cs.d.ts +0 -120
- package/typings/languages/da.d.ts +0 -53
- package/typings/languages/de.d.ts +0 -26
- package/typings/languages/el.d.ts +0 -11
- package/typings/languages/en.d.ts +0 -30
- package/typings/languages/es.d.ts +0 -43
- package/typings/languages/fa.d.ts +0 -81
- package/typings/languages/fil.d.ts +0 -12
- package/typings/languages/fr-BE.d.ts +0 -41
- package/typings/languages/fr.d.ts +0 -43
- package/typings/languages/gu.d.ts +0 -12
- package/typings/languages/he.d.ts +0 -197
- package/typings/languages/hi.d.ts +0 -1
- package/typings/languages/hr.d.ts +0 -110
- package/typings/languages/hu.d.ts +0 -37
- package/typings/languages/id.d.ts +0 -69
- package/typings/languages/it.d.ts +0 -51
- package/typings/languages/ja.d.ts +0 -58
- package/typings/languages/kn.d.ts +0 -11
- package/typings/languages/ko.d.ts +0 -25
- package/typings/languages/lt.d.ts +0 -110
- package/typings/languages/lv.d.ts +0 -99
- package/typings/languages/mr.d.ts +0 -12
- package/typings/languages/ms.d.ts +0 -37
- package/typings/languages/nb.d.ts +0 -27
- package/typings/languages/nl.d.ts +0 -65
- package/typings/languages/pa-Guru.d.ts +0 -1
- package/typings/languages/pl.d.ts +0 -116
- package/typings/languages/pt.d.ts +0 -39
- package/typings/languages/ro.d.ts +0 -229
- package/typings/languages/ru.d.ts +0 -108
- package/typings/languages/sr-Latn.d.ts +0 -98
- package/typings/languages/sv.d.ts +0 -30
- package/typings/languages/sw.d.ts +0 -1
- package/typings/languages/ta.d.ts +0 -1
- package/typings/languages/te.d.ts +0 -1
- package/typings/languages/th.d.ts +0 -1
- package/typings/languages/tr.d.ts +0 -46
- package/typings/languages/uk.d.ts +0 -117
- package/typings/languages/ur.d.ts +0 -1
- package/typings/languages/vi.d.ts +0 -116
- package/typings/languages/zh-Hans.d.ts +0 -57
- package/typings/n2words.d.ts +0 -177
package/lib/languages/de.js
CHANGED
|
@@ -1,135 +1,332 @@
|
|
|
1
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
[
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
[
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
261
|
+
result += part.words
|
|
117
262
|
}
|
|
263
|
+
|
|
264
|
+
return result
|
|
118
265
|
}
|
|
119
266
|
|
|
120
267
|
/**
|
|
121
|
-
* Converts
|
|
268
|
+
* Converts decimal digits to German words.
|
|
122
269
|
*
|
|
123
|
-
* @param {
|
|
124
|
-
* @
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
*
|
|
131
|
-
*
|
|
306
|
+
* toWords(21) // 'einundzwanzig'
|
|
307
|
+
* toWords(1000) // 'eintausend'
|
|
308
|
+
* toWords(1000000) // 'eine Million'
|
|
132
309
|
*/
|
|
133
|
-
|
|
134
|
-
|
|
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;
|