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/da.js
CHANGED
|
@@ -1,167 +1,295 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Danish language converter - Functional Implementation
|
|
3
|
+
*
|
|
4
|
+
* A performance-optimized number-to-words converter using precomputed lookup tables.
|
|
5
|
+
*
|
|
6
|
+
* Key features:
|
|
7
|
+
* - Vigesimal (base-20) tens naming: halvtreds (50), treds (60), etc.
|
|
8
|
+
* - Units-before-tens: "enogtyve" (21) = one-and-twenty
|
|
9
|
+
* - Compound thousands: "ettusind", "firetusinde"
|
|
10
|
+
* - "og" conjunction after hundreds and thousands
|
|
11
|
+
* - Long scale for millions+
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Vocabulary (module-level constants)
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
const ONES = ['', 'et', 'to', 'tre', 'fire', 'fem', 'seks', 'syv', 'otte', 'ni']
|
|
21
|
+
// "en" form used in vigesimal pattern (X og Y) and before millions
|
|
22
|
+
const ONES_VIGESIMAL = ['', 'en', 'to', 'tre', 'fire', 'fem', 'seks', 'syv', 'otte', 'ni']
|
|
23
|
+
|
|
24
|
+
const TEENS = ['ti', 'elleve', 'tolv', 'tretten', 'fjorten', 'femten', 'seksten', 'sytten', 'atten', 'nitten']
|
|
25
|
+
|
|
26
|
+
// Danish vigesimal tens (base-20 derived names)
|
|
27
|
+
const TENS = ['', '', 'tyve', 'tredive', 'fyrre', 'halvtreds', 'treds', 'halvfjerds', 'firs', 'halvfems']
|
|
28
|
+
|
|
29
|
+
const HUNDRED = 'hundrede'
|
|
30
|
+
const THOUSAND = 'tusind'
|
|
31
|
+
|
|
32
|
+
const ZERO = 'nul'
|
|
33
|
+
const NEGATIVE = 'minus'
|
|
34
|
+
const DECIMAL_SEP = 'komma'
|
|
35
|
+
|
|
36
|
+
// Long scale: millioner, millarder, billioner, etc.
|
|
37
|
+
const SCALES = ['millioner', 'millarder', 'billioner', 'billarder', 'trillioner', 'trillarder', 'quadrillioner', 'quadrillarder']
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Precomputed Lookup Tables (built once at module load)
|
|
41
|
+
// ============================================================================
|
|
2
42
|
|
|
3
43
|
/**
|
|
4
|
-
*
|
|
5
|
-
* @property {boolean} [ordFlag=false] Enable ordinal number conversion.
|
|
44
|
+
* Builds segment word for 0-999.
|
|
6
45
|
*/
|
|
46
|
+
function buildSegment (n) {
|
|
47
|
+
if (n === 0) return ''
|
|
48
|
+
|
|
49
|
+
const ones = n % 10
|
|
50
|
+
const tens = Math.floor(n / 10) % 10
|
|
51
|
+
const hundreds = Math.floor(n / 100)
|
|
52
|
+
|
|
53
|
+
const parts = []
|
|
54
|
+
|
|
55
|
+
// Hundreds: "ethundrede", "tohundrede" (compound, no space)
|
|
56
|
+
if (hundreds > 0) {
|
|
57
|
+
parts.push(ONES[hundreds] + HUNDRED)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Tens and ones
|
|
61
|
+
const tensOnes = n % 100
|
|
62
|
+
|
|
63
|
+
if (tensOnes === 0) {
|
|
64
|
+
// Just hundreds
|
|
65
|
+
} else if (tensOnes < 10) {
|
|
66
|
+
// Single digit
|
|
67
|
+
parts.push(ONES[ones])
|
|
68
|
+
} else if (tensOnes < 20) {
|
|
69
|
+
// Teens
|
|
70
|
+
parts.push(TEENS[ones])
|
|
71
|
+
} else if (ones === 0) {
|
|
72
|
+
// Even tens
|
|
73
|
+
parts.push(TENS[tens])
|
|
74
|
+
} else {
|
|
75
|
+
// Units-before-tens: "enogtyve", "treogfyrre"
|
|
76
|
+
parts.push(ONES_VIGESIMAL[ones] + 'og' + TENS[tens])
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Combine with " og " between hundreds and remainder
|
|
80
|
+
if (parts.length === 2) {
|
|
81
|
+
return parts[0] + ' og ' + parts[1]
|
|
82
|
+
}
|
|
83
|
+
return parts[0] || ''
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Precompute all 1000 segment words (0-999)
|
|
87
|
+
const SEGMENTS = new Array(1000)
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < 1000; i++) {
|
|
90
|
+
SEGMENTS[i] = buildSegment(i)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Conversion Functions
|
|
95
|
+
// ============================================================================
|
|
7
96
|
|
|
8
97
|
/**
|
|
9
|
-
* Danish
|
|
10
|
-
*
|
|
11
|
-
* GreedyScaleLanguage with Danish-specific extensions:
|
|
12
|
-
* - Unique vigesimal (base-20) number system for 50-90
|
|
13
|
-
* - Special composition rules ("og" for "and" between units and tens)
|
|
14
|
-
* - Reverse digit order (e.g., "fem-og-tyve" = five-and-twenty = 25)
|
|
15
|
-
* - Support for ordinal numbers via ordFlag option
|
|
98
|
+
* Converts a non-negative integer to Danish words.
|
|
16
99
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* - Units-before-tens pattern (e.g., "tre-og-tyve" = 23)
|
|
20
|
-
* - "et" prefix for hundreds/thousands (not "en")
|
|
21
|
-
* - Optional ordinal number conversion via ordFlag option
|
|
22
|
-
* - Inline merge logic tailored for Danish ordering
|
|
100
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
101
|
+
* @returns {string} Danish words
|
|
23
102
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
[
|
|
30
|
-
[1_000_000_000_000_000_000_000_000n, 'quadrillioner'],
|
|
31
|
-
[1_000_000_000_000_000_000_000n, 'trillarder'],
|
|
32
|
-
[1_000_000_000_000_000_000n, 'trillioner'],
|
|
33
|
-
[1_000_000_000_000_000n, 'billarder'],
|
|
34
|
-
[1_000_000_000_000n, 'billioner'],
|
|
35
|
-
[1_000_000_000n, 'millarder'],
|
|
36
|
-
[1_000_000n, 'millioner'],
|
|
37
|
-
[1000n, 'tusind'],
|
|
38
|
-
[100n, 'hundrede'],
|
|
39
|
-
[90n, 'halvfems'],
|
|
40
|
-
[80n, 'firs'],
|
|
41
|
-
[70n, 'halvfjerds'],
|
|
42
|
-
[60n, 'treds'],
|
|
43
|
-
[50n, 'halvtreds'],
|
|
44
|
-
[40n, 'fyrre'],
|
|
45
|
-
[30n, 'tredive'],
|
|
46
|
-
[20n, 'tyve'],
|
|
47
|
-
[19n, 'nitten'],
|
|
48
|
-
[18n, 'atten'],
|
|
49
|
-
[17n, 'sytten'],
|
|
50
|
-
[16n, 'seksten'],
|
|
51
|
-
[15n, 'femten'],
|
|
52
|
-
[14n, 'fjorten'],
|
|
53
|
-
[13n, 'tretten'],
|
|
54
|
-
[12n, 'tolv'],
|
|
55
|
-
[11n, 'elleve'],
|
|
56
|
-
[10n, 'ti'],
|
|
57
|
-
[9n, 'ni'],
|
|
58
|
-
[8n, 'otte'],
|
|
59
|
-
[7n, 'syv'],
|
|
60
|
-
[6n, 'seks'],
|
|
61
|
-
[5n, 'fem'],
|
|
62
|
-
[4n, 'fire'],
|
|
63
|
-
[3n, 'tre'],
|
|
64
|
-
[2n, 'to'],
|
|
65
|
-
[1n, 'et'],
|
|
66
|
-
[0n, 'nul']
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Initializes the Danish converter with language-specific options.
|
|
71
|
-
*
|
|
72
|
-
* @param {DanishOptions} [options={}] Configuration options.
|
|
73
|
-
*/
|
|
74
|
-
constructor ({ ordFlag = false } = {}) {
|
|
75
|
-
super()
|
|
76
|
-
|
|
77
|
-
this.ordFlag = ordFlag
|
|
103
|
+
function integerToWords (n) {
|
|
104
|
+
if (n === 0n) return ZERO
|
|
105
|
+
|
|
106
|
+
// Fast path: numbers < 1000 (direct lookup)
|
|
107
|
+
if (n < 1000n) {
|
|
108
|
+
return SEGMENTS[Number(n)]
|
|
78
109
|
}
|
|
79
110
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
* Key Danish patterns:
|
|
85
|
-
* - Vigesimal tens: halvtreds(50), treds(60), halvfjerds(70), firs(80), halvfems(90)
|
|
86
|
-
* - Units-before-tens order with "og" (and): "tre-og-tyve" (3 and 20 = 25)
|
|
87
|
-
* - "et" prefix for hundreds/thousands (not "en")
|
|
88
|
-
* - Space separators for large magnitudes (≥ millions)
|
|
89
|
-
* - Ordinal support via this.ordFlag
|
|
90
|
-
*
|
|
91
|
-
* @param {Object} current The left operand as `{ word: bigint }`.
|
|
92
|
-
* @param {Object} next The right operand as `{ word: bigint }`.
|
|
93
|
-
* @returns {Object} Merged pair with combined word and resulting number (bigint).
|
|
94
|
-
*/
|
|
95
|
-
mergeScales (current, next) {
|
|
96
|
-
let cText = Object.keys(current)[0]
|
|
97
|
-
let nText = Object.keys(next)[0]
|
|
98
|
-
const cNumber = Object.values(current)[0] // BigInt (e.g., 1n, 100n, 1000n)
|
|
99
|
-
const nNumber = Object.values(next)[0] // BigInt (e.g., magnitude level like 100n, 1000n)
|
|
100
|
-
|
|
101
|
-
// Prepend "et" to hundreds and thousands (not "en") for proper Danish form
|
|
102
|
-
if (nNumber === 100n || nNumber === 1000n) {
|
|
103
|
-
next = { [`et${nText}`]: nNumber }
|
|
104
|
-
}
|
|
111
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
112
|
+
if (n < 1_000_000n) {
|
|
113
|
+
const thousands = Number(n / 1000n)
|
|
114
|
+
const remainder = Number(n % 1000n)
|
|
105
115
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
// Compound thousands: "ettusind", "firetusind"
|
|
117
|
+
let result = SEGMENTS[thousands] + THOUSAND
|
|
118
|
+
|
|
119
|
+
if (remainder > 0) {
|
|
120
|
+
// Add 'e' suffix and " og " for remainder: "firetusinde og ..."
|
|
121
|
+
result += 'e og ' + SEGMENTS[remainder]
|
|
112
122
|
}
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
return result
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
128
|
+
return buildLargeNumberWords(n)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Builds words for numbers >= 1,000,000.
|
|
133
|
+
*
|
|
134
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
135
|
+
* @returns {string} Danish words
|
|
136
|
+
*/
|
|
137
|
+
function buildLargeNumberWords (n) {
|
|
138
|
+
const numStr = n.toString()
|
|
139
|
+
const len = numStr.length
|
|
140
|
+
|
|
141
|
+
// Build segments of 3 digits from right to left
|
|
142
|
+
const segments = []
|
|
143
|
+
const segmentSize = 3
|
|
144
|
+
|
|
145
|
+
const remainderLen = len % segmentSize
|
|
146
|
+
let pos = 0
|
|
147
|
+
if (remainderLen > 0) {
|
|
148
|
+
segments.push(Number(numStr.slice(0, remainderLen)))
|
|
149
|
+
pos = remainderLen
|
|
150
|
+
}
|
|
151
|
+
while (pos < len) {
|
|
152
|
+
segments.push(Number(numStr.slice(pos, pos + segmentSize)))
|
|
153
|
+
pos += segmentSize
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Convert segments to words with scale tracking
|
|
157
|
+
// scaleIndex: 0 = units, 1 = thousands, 2 = millions, etc.
|
|
158
|
+
const parts = []
|
|
159
|
+
let scaleIndex = segments.length - 1
|
|
160
|
+
|
|
161
|
+
for (let i = 0; i < segments.length; i++) {
|
|
162
|
+
const segment = segments[i]
|
|
163
|
+
|
|
164
|
+
if (segment !== 0) {
|
|
165
|
+
const segmentWord = SEGMENTS[segment]
|
|
166
|
+
|
|
167
|
+
if (scaleIndex === 0) {
|
|
168
|
+
// Units segment
|
|
169
|
+
parts.push({ word: segmentWord, type: 'units' })
|
|
170
|
+
} else if (scaleIndex === 1) {
|
|
171
|
+
// Thousands - compound form
|
|
172
|
+
parts.push({ word: segmentWord + THOUSAND, type: 'thousand' })
|
|
173
|
+
} else {
|
|
174
|
+
// Millions+ - space-separated, use "en" for 1
|
|
175
|
+
const scaleWord = SCALES[scaleIndex - 2]
|
|
176
|
+
let numWord = segmentWord
|
|
177
|
+
// "et" → "en" before millions+
|
|
178
|
+
if (segment === 1) {
|
|
179
|
+
numWord = 'en'
|
|
180
|
+
}
|
|
181
|
+
parts.push({ word: numWord + ' ' + scaleWord, type: 'million' })
|
|
119
182
|
}
|
|
120
|
-
return { [`${cText}${nText}`]: cNumber * nNumber }
|
|
121
183
|
}
|
|
122
184
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (cNumber >= 100n && cNumber < 1000n) {
|
|
126
|
-
cText += ' og '
|
|
127
|
-
} else if (cNumber >= 1000n && cNumber <= 100_000n) {
|
|
128
|
-
// Special "e og" for thousands (e.g., "tusinde og tyve")
|
|
129
|
-
cText += 'e og '
|
|
130
|
-
}
|
|
185
|
+
scaleIndex--
|
|
186
|
+
}
|
|
131
187
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
188
|
+
// Join parts with Danish rules
|
|
189
|
+
return joinDanishParts(parts)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Joins parts with Danish spacing rules.
|
|
194
|
+
* - After thousands with remainder: "tusinde og"
|
|
195
|
+
* - Millions are space-separated
|
|
196
|
+
*
|
|
197
|
+
* @param {Array} parts - Parts with type metadata
|
|
198
|
+
* @returns {string} Joined string
|
|
199
|
+
*/
|
|
200
|
+
function joinDanishParts (parts) {
|
|
201
|
+
if (parts.length === 0) return ZERO
|
|
202
|
+
if (parts.length === 1) return parts[0].word
|
|
203
|
+
|
|
204
|
+
const result = []
|
|
205
|
+
|
|
206
|
+
for (let i = 0; i < parts.length; i++) {
|
|
207
|
+
const part = parts[i]
|
|
208
|
+
const nextPart = parts[i + 1]
|
|
209
|
+
|
|
210
|
+
if (part.type === 'thousand' && nextPart && nextPart.type === 'units') {
|
|
211
|
+
// Thousands followed by units: add "e og"
|
|
212
|
+
result.push(part.word + 'e og ' + nextPart.word)
|
|
213
|
+
i++ // Skip the units part
|
|
214
|
+
} else if (part.type === 'million') {
|
|
215
|
+
if (result.length > 0) {
|
|
216
|
+
result.push(' ')
|
|
217
|
+
}
|
|
218
|
+
result.push(part.word)
|
|
219
|
+
if (nextPart) {
|
|
220
|
+
result.push(' ')
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
if (result.length > 0 && !result[result.length - 1].endsWith(' ')) {
|
|
224
|
+
result.push(' ')
|
|
137
225
|
}
|
|
138
|
-
|
|
139
|
-
const temporary = nText
|
|
140
|
-
nText = cText
|
|
141
|
-
cText = temporary + 'og'
|
|
142
|
-
} else if (cNumber >= 1_000_000n) {
|
|
143
|
-
// Space for large magnitudes (millions+)
|
|
144
|
-
cText += ' '
|
|
226
|
+
result.push(part.word)
|
|
145
227
|
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return result.join('')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Converts decimal digits to Danish words.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
237
|
+
* @returns {string} Danish words for decimal part
|
|
238
|
+
*/
|
|
239
|
+
function decimalPartToWords (decimalPart) {
|
|
240
|
+
let result = ''
|
|
146
241
|
|
|
147
|
-
|
|
242
|
+
// Handle leading zeros
|
|
243
|
+
let i = 0
|
|
244
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
245
|
+
if (result) result += ' '
|
|
246
|
+
result += ZERO
|
|
247
|
+
i++
|
|
148
248
|
}
|
|
249
|
+
|
|
250
|
+
// Convert remainder as a single number
|
|
251
|
+
const remainder = decimalPart.slice(i)
|
|
252
|
+
if (remainder) {
|
|
253
|
+
if (result) result += ' '
|
|
254
|
+
result += integerToWords(BigInt(remainder))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return result
|
|
149
258
|
}
|
|
150
259
|
|
|
151
260
|
/**
|
|
152
|
-
* Converts a
|
|
261
|
+
* Converts a numeric value to Danish words.
|
|
153
262
|
*
|
|
154
|
-
* @param {number|string|bigint} value The
|
|
155
|
-
* @
|
|
156
|
-
* @
|
|
157
|
-
* @
|
|
158
|
-
* @throws {TypeError} If value is NaN or invalid type.
|
|
159
|
-
* @throws {Error} If value is an invalid number string.
|
|
263
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
264
|
+
* @returns {string} The number in Danish words
|
|
265
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
266
|
+
* @throws {Error} If value is not a valid number format
|
|
160
267
|
*
|
|
161
268
|
* @example
|
|
162
|
-
*
|
|
163
|
-
*
|
|
269
|
+
* toWords(21) // 'enogtyve'
|
|
270
|
+
* toWords(1000) // 'ettusind'
|
|
271
|
+
* toWords(1000000) // 'en millioner'
|
|
164
272
|
*/
|
|
165
|
-
|
|
166
|
-
|
|
273
|
+
function toWords (value) {
|
|
274
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
275
|
+
|
|
276
|
+
let result = ''
|
|
277
|
+
|
|
278
|
+
if (isNegative) {
|
|
279
|
+
result = NEGATIVE + ' '
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
result += integerToWords(integerPart)
|
|
283
|
+
|
|
284
|
+
if (decimalPart) {
|
|
285
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return result
|
|
167
289
|
}
|
|
290
|
+
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Public API
|
|
293
|
+
// ============================================================================
|
|
294
|
+
|
|
295
|
+
export { toWords }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a numeric value to German words.
|
|
3
|
+
*
|
|
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 German 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(21) // 'einundzwanzig'
|
|
14
|
+
* toWords(1000) // 'eintausend'
|
|
15
|
+
* toWords(1000000) // 'eine Million'
|
|
16
|
+
*/
|
|
17
|
+
export function toWords(value: number | string | bigint): string;
|