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/nb.js
CHANGED
|
@@ -1,120 +1,295 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Norwegian Bokmål language converter - Functional Implementation
|
|
3
|
+
*
|
|
4
|
+
* A performance-optimized number-to-words converter using precomputed lookup tables.
|
|
5
|
+
*
|
|
6
|
+
* Key features:
|
|
7
|
+
* - Hyphenated tens+ones: "tjue-en" (21)
|
|
8
|
+
* - "og" conjunction after hundreds
|
|
9
|
+
* - Comma separator after thousands before hundreds
|
|
10
|
+
* - Short scale: million, milliard, billion, etc.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Vocabulary (module-level constants)
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
const ONES = ['', 'en', 'to', 'tre', 'fire', 'fem', 'seks', 'syv', 'åtte', 'ni']
|
|
20
|
+
|
|
21
|
+
const TEENS = ['ti', 'elleve', 'tolv', 'tretten', 'fjorten', 'femten', 'seksten', 'sytten', 'atten', 'nitten']
|
|
22
|
+
const TENS = ['', '', 'tjue', 'tretti', 'førti', 'femti', 'seksti', 'sytti', 'åtti', 'nitti']
|
|
23
|
+
|
|
24
|
+
const HUNDRED = 'hundre'
|
|
25
|
+
const THOUSAND = 'tusen'
|
|
26
|
+
|
|
27
|
+
const ZERO = 'null'
|
|
28
|
+
const NEGATIVE = 'minus'
|
|
29
|
+
const DECIMAL_SEP = 'komma'
|
|
30
|
+
|
|
31
|
+
// Short scale: million, milliard, billion, etc.
|
|
32
|
+
const SCALES = ['million', 'milliard', 'billion', 'billiard', 'kvintillion', 'sekstillion', 'septillion', 'oktillion']
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Precomputed Lookup Tables (built once at module load)
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds segment word for 0-999.
|
|
40
|
+
* Returns object with word and hasHundred flag.
|
|
41
|
+
*/
|
|
42
|
+
function buildSegment (n) {
|
|
43
|
+
if (n === 0) return { word: '', hasHundred: false }
|
|
44
|
+
|
|
45
|
+
const ones = n % 10
|
|
46
|
+
const tens = Math.floor(n / 10) % 10
|
|
47
|
+
const hundreds = Math.floor(n / 100)
|
|
48
|
+
|
|
49
|
+
const parts = []
|
|
50
|
+
let hasHundred = false
|
|
51
|
+
|
|
52
|
+
// Hundreds: "en hundre", "to hundre"
|
|
53
|
+
if (hundreds > 0) {
|
|
54
|
+
hasHundred = true
|
|
55
|
+
parts.push(ONES[hundreds] + ' ' + HUNDRED)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Tens and ones
|
|
59
|
+
const tensOnes = n % 100
|
|
60
|
+
|
|
61
|
+
if (tensOnes === 0) {
|
|
62
|
+
// Just hundreds
|
|
63
|
+
} else if (tensOnes < 10) {
|
|
64
|
+
// Single digit
|
|
65
|
+
parts.push(ONES[ones])
|
|
66
|
+
} else if (tensOnes < 20) {
|
|
67
|
+
// Teens
|
|
68
|
+
parts.push(TEENS[ones])
|
|
69
|
+
} else if (ones === 0) {
|
|
70
|
+
// Even tens
|
|
71
|
+
parts.push(TENS[tens])
|
|
72
|
+
} else {
|
|
73
|
+
// Hyphenated: "tjue-en"
|
|
74
|
+
parts.push(TENS[tens] + '-' + ONES[ones])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Combine with " og " between hundreds and remainder
|
|
78
|
+
if (parts.length === 2) {
|
|
79
|
+
return { word: parts[0] + ' og ' + parts[1], hasHundred: true }
|
|
80
|
+
}
|
|
81
|
+
return { word: parts[0] || '', hasHundred }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Precompute all 1000 segment words (0-999)
|
|
85
|
+
const SEGMENTS = new Array(1000)
|
|
86
|
+
const SEGMENTS_HAS_HUNDRED = new Array(1000)
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < 1000; i++) {
|
|
89
|
+
const result = buildSegment(i)
|
|
90
|
+
SEGMENTS[i] = result.word
|
|
91
|
+
SEGMENTS_HAS_HUNDRED[i] = result.hasHundred
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Conversion Functions
|
|
96
|
+
// ============================================================================
|
|
2
97
|
|
|
3
98
|
/**
|
|
4
|
-
* Norwegian
|
|
99
|
+
* Converts a non-negative integer to Norwegian Bokmål words.
|
|
5
100
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - "og" (and) for hundreds combinations
|
|
9
|
-
* - Comma separation for non-magnitude additions
|
|
10
|
-
* - Implicit '1' before tens and magnitudes
|
|
11
|
-
* - Space separators for large numbers
|
|
101
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
102
|
+
* @returns {string} Norwegian words
|
|
12
103
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
decimalSeparatorWord = 'komma'
|
|
16
|
-
zeroWord = 'null'
|
|
17
|
-
scaleWordPairs = [
|
|
18
|
-
[1_000_000_000_000_000_000_000_000_000_000_000n, 'quintillard'],
|
|
19
|
-
[1_000_000_000_000_000_000_000_000_000_000n, 'quintillion'],
|
|
20
|
-
[1_000_000_000_000_000_000_000_000_000n, 'quadrillard'],
|
|
21
|
-
[1_000_000_000_000_000_000_000_000n, 'quadrillion'],
|
|
22
|
-
[1_000_000_000_000_000_000_000n, 'trillard'],
|
|
23
|
-
[1_000_000_000_000_000_000n, 'trillion'],
|
|
24
|
-
[1_000_000_000_000_000n, 'billard'],
|
|
25
|
-
[1_000_000_000_000n, 'billion'],
|
|
26
|
-
[1_000_000_000n, 'millard'],
|
|
27
|
-
[1_000_000n, 'million'],
|
|
28
|
-
[1000n, 'tusen'],
|
|
29
|
-
[100n, 'hundre'],
|
|
30
|
-
[90n, 'nitti'],
|
|
31
|
-
[80n, 'åtti'],
|
|
32
|
-
[70n, 'sytti'],
|
|
33
|
-
[60n, 'seksti'],
|
|
34
|
-
[50n, 'femti'],
|
|
35
|
-
[40n, 'førti'],
|
|
36
|
-
[30n, 'tretti'],
|
|
37
|
-
[20n, 'tjue'],
|
|
38
|
-
[19n, 'nitten'],
|
|
39
|
-
[18n, 'atten'],
|
|
40
|
-
[17n, 'sytten'],
|
|
41
|
-
[16n, 'seksten'],
|
|
42
|
-
[15n, 'femten'],
|
|
43
|
-
[14n, 'fjorten'],
|
|
44
|
-
[13n, 'tretten'],
|
|
45
|
-
[12n, 'tolv'],
|
|
46
|
-
[11n, 'elleve'],
|
|
47
|
-
[10n, 'ti'],
|
|
48
|
-
[9n, 'ni'],
|
|
49
|
-
[8n, 'åtte'],
|
|
50
|
-
[7n, 'syv'],
|
|
51
|
-
[6n, 'seks'],
|
|
52
|
-
[5n, 'fem'],
|
|
53
|
-
[4n, 'fire'],
|
|
54
|
-
[3n, 'tre'],
|
|
55
|
-
[2n, 'to'],
|
|
56
|
-
[1n, 'en'],
|
|
57
|
-
[0n, 'null']
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Merges two adjacent word-number pairs according to Norwegian grammar rules.
|
|
62
|
-
*
|
|
63
|
-
* Norwegian-specific rules:
|
|
64
|
-
* - Implicit \"en\": `mergeScales({ 'en': 1n }, { 'hundre': 100n })` → `{ 'hundre': 100n }`
|
|
65
|
-
* - Hyphenation for compound tens: `mergeScales({ 'tjue': 20n }, { 'en': 1n })` → `{ 'tjue-en': 21n }`
|
|
66
|
-
* - \"og\" (and) after hundreds: `mergeScales({ 'hundre': 100n }, { 'en': 1n })` → `{ 'hundre og en': 101n }`
|
|
67
|
-
* - Space-separated for large numbers (thousands+)
|
|
68
|
-
* - Comma separator for non-magnitude additions (e.g., \"tusen, en\")
|
|
69
|
-
*
|
|
70
|
-
* @param {Object} leftPair The left operand as `{ word: BigInt }`.
|
|
71
|
-
* @param {Object} rightPair The right operand as `{ word: BigInt }`.
|
|
72
|
-
* @returns {Object} Merged pair with combined word and resulting numeric value.
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* mergeScales({ 'en': 1n }, { 'hundre': 100n }); // { 'hundre': 100n }
|
|
76
|
-
* mergeScales({ 'tjue': 20n }, { 'tre': 3n }); // { 'tjue-tre': 23n }
|
|
77
|
-
*/
|
|
78
|
-
// Norwegian merge rules mirror the former Scandinavian base logic
|
|
79
|
-
mergeScales (leftPair, rightPair) {
|
|
80
|
-
const leftWord = Object.keys(leftPair)[0]
|
|
81
|
-
const rightWord = Object.keys(rightPair)[0]
|
|
82
|
-
const leftNumber = Object.values(leftPair)[0]
|
|
83
|
-
const rightNumber = Object.values(rightPair)[0]
|
|
84
|
-
|
|
85
|
-
if (leftNumber === 1n && rightNumber < 100n) {
|
|
86
|
-
return rightPair
|
|
87
|
-
}
|
|
104
|
+
function integerToWords (n) {
|
|
105
|
+
if (n === 0n) return ZERO
|
|
88
106
|
|
|
89
|
-
|
|
90
|
-
|
|
107
|
+
// Fast path: numbers < 1000 (direct lookup)
|
|
108
|
+
if (n < 1000n) {
|
|
109
|
+
return SEGMENTS[Number(n)]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
113
|
+
if (n < 1_000_000n) {
|
|
114
|
+
const thousands = Number(n / 1000n)
|
|
115
|
+
const remainder = Number(n % 1000n)
|
|
116
|
+
|
|
117
|
+
let result = SEGMENTS[thousands] + ' ' + THOUSAND
|
|
118
|
+
|
|
119
|
+
if (remainder > 0) {
|
|
120
|
+
// Comma before hundreds, " og " before small numbers
|
|
121
|
+
if (SEGMENTS_HAS_HUNDRED[remainder]) {
|
|
122
|
+
result += ', ' + SEGMENTS[remainder]
|
|
123
|
+
} else {
|
|
124
|
+
result += ' og ' + SEGMENTS[remainder]
|
|
125
|
+
}
|
|
91
126
|
}
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
return result
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
132
|
+
return buildLargeNumberWords(n)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Builds words for numbers >= 1,000,000.
|
|
137
|
+
*
|
|
138
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
139
|
+
* @returns {string} Norwegian words
|
|
140
|
+
*/
|
|
141
|
+
function buildLargeNumberWords (n) {
|
|
142
|
+
const numStr = n.toString()
|
|
143
|
+
const len = numStr.length
|
|
144
|
+
|
|
145
|
+
// Build segments of 3 digits from right to left
|
|
146
|
+
const segments = []
|
|
147
|
+
const segmentSize = 3
|
|
148
|
+
|
|
149
|
+
const remainderLen = len % segmentSize
|
|
150
|
+
let pos = 0
|
|
151
|
+
if (remainderLen > 0) {
|
|
152
|
+
segments.push(Number(numStr.slice(0, remainderLen)))
|
|
153
|
+
pos = remainderLen
|
|
154
|
+
}
|
|
155
|
+
while (pos < len) {
|
|
156
|
+
segments.push(Number(numStr.slice(pos, pos + segmentSize)))
|
|
157
|
+
pos += segmentSize
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Convert segments to words
|
|
161
|
+
const parts = []
|
|
162
|
+
let scaleIndex = segments.length - 1
|
|
163
|
+
|
|
164
|
+
for (let i = 0; i < segments.length; i++) {
|
|
165
|
+
const segment = segments[i]
|
|
166
|
+
|
|
167
|
+
if (segment !== 0) {
|
|
168
|
+
const segmentWord = SEGMENTS[segment]
|
|
169
|
+
const hasHundred = SEGMENTS_HAS_HUNDRED[segment]
|
|
170
|
+
|
|
171
|
+
if (scaleIndex === 0) {
|
|
172
|
+
// Units segment
|
|
173
|
+
parts.push({ word: segmentWord, hasHundred, type: 'units' })
|
|
174
|
+
} else if (scaleIndex === 1) {
|
|
175
|
+
// Thousands
|
|
176
|
+
parts.push({ word: segmentWord + ' ' + THOUSAND, hasHundred: false, type: 'thousand' })
|
|
177
|
+
} else {
|
|
178
|
+
// Millions+
|
|
179
|
+
const scaleWord = SCALES[scaleIndex - 2]
|
|
180
|
+
parts.push({ word: segmentWord + ' ' + scaleWord, hasHundred: false, type: 'million' })
|
|
181
|
+
}
|
|
95
182
|
}
|
|
96
183
|
|
|
97
|
-
|
|
98
|
-
|
|
184
|
+
scaleIndex--
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Join parts with Norwegian rules
|
|
188
|
+
return joinNorwegianParts(parts)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Joins parts with Norwegian spacing and comma rules.
|
|
193
|
+
*
|
|
194
|
+
* @param {Array} parts - Parts with type metadata
|
|
195
|
+
* @returns {string} Joined string
|
|
196
|
+
*/
|
|
197
|
+
function joinNorwegianParts (parts) {
|
|
198
|
+
if (parts.length === 0) return ZERO
|
|
199
|
+
if (parts.length === 1) return parts[0].word
|
|
200
|
+
|
|
201
|
+
const result = []
|
|
202
|
+
|
|
203
|
+
for (let i = 0; i < parts.length; i++) {
|
|
204
|
+
const part = parts[i]
|
|
205
|
+
const nextPart = parts[i + 1]
|
|
206
|
+
|
|
207
|
+
result.push(part.word)
|
|
208
|
+
|
|
209
|
+
if (nextPart) {
|
|
210
|
+
if (part.type === 'thousand') {
|
|
211
|
+
// After thousands: comma if next has hundred, else " og "
|
|
212
|
+
if (nextPart.hasHundred) {
|
|
213
|
+
result.push(', ')
|
|
214
|
+
} else {
|
|
215
|
+
result.push(' og ')
|
|
216
|
+
}
|
|
217
|
+
} else if (part.type === 'million') {
|
|
218
|
+
// After millions: " og " for units without hundred, space otherwise
|
|
219
|
+
if (nextPart.type === 'units' && !nextPart.hasHundred) {
|
|
220
|
+
result.push(' og ')
|
|
221
|
+
} else {
|
|
222
|
+
result.push(' ')
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
result.push(' ')
|
|
226
|
+
}
|
|
99
227
|
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return result.join('')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Converts decimal digits to Norwegian words.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
237
|
+
* @returns {string} Norwegian words for decimal part
|
|
238
|
+
*/
|
|
239
|
+
function decimalPartToWords (decimalPart) {
|
|
240
|
+
let result = ''
|
|
100
241
|
|
|
101
|
-
|
|
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++
|
|
102
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
|
|
103
258
|
}
|
|
104
259
|
|
|
105
260
|
/**
|
|
106
|
-
* Converts a
|
|
261
|
+
* Converts a numeric value to Norwegian Bokmål words.
|
|
107
262
|
*
|
|
108
|
-
* @param {number|string|bigint} value The
|
|
109
|
-
* @
|
|
110
|
-
* @
|
|
111
|
-
* @throws {
|
|
112
|
-
* @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 Norwegian words
|
|
265
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
266
|
+
* @throws {Error} If value is not a valid number format
|
|
113
267
|
*
|
|
114
268
|
* @example
|
|
115
|
-
*
|
|
116
|
-
*
|
|
269
|
+
* toWords(21) // 'tjue-en'
|
|
270
|
+
* toWords(101) // 'en hundre og en'
|
|
271
|
+
* toWords(1000000) // 'en million'
|
|
117
272
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
120
289
|
}
|
|
290
|
+
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Public API
|
|
293
|
+
// ============================================================================
|
|
294
|
+
|
|
295
|
+
export { toWords }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a numeric value to Dutch 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
|
+
* @param {Object} [options] - Optional configuration
|
|
9
|
+
* @param {boolean} [options.accentOne=true] - Use "één" instead of "een"
|
|
10
|
+
* @param {boolean} [options.includeOptionalAnd=false] - Include "en" before small numbers
|
|
11
|
+
* @param {boolean} [options.noHundredPairing=false] - Disable hundred pairing (1104→duizend honderdvier)
|
|
12
|
+
* @returns {string} The number in Dutch words
|
|
13
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
14
|
+
* @throws {Error} If value is not a valid number format
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* toWords(21) // 'eenentwintig'
|
|
18
|
+
* toWords(1) // 'één'
|
|
19
|
+
* toWords(1, {accentOne: false}) // 'een'
|
|
20
|
+
* toWords(1104) // 'elfhonderd vier'
|
|
21
|
+
*/
|
|
22
|
+
export function toWords(value: number | string | bigint, options?: {
|
|
23
|
+
accentOne?: boolean | undefined;
|
|
24
|
+
includeOptionalAnd?: boolean | undefined;
|
|
25
|
+
noHundredPairing?: boolean | undefined;
|
|
26
|
+
}): string;
|