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/cs.js
CHANGED
|
@@ -1,212 +1,339 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Czech 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
|
+
* Czech-specific rules (handled in precomputation):
|
|
11
|
+
* - Three-form pluralization: 1 = singular, 2-4 = few, 5+ = many
|
|
12
|
+
* - Irregular hundreds: sto, dvě stě, tři sta, čtyři sta, pět set...
|
|
13
|
+
* - Gender: dva (masc) vs dvě (fem) for 2
|
|
14
|
+
* - Omit "one" before scale words: "tisíc" not "jedna tisíc"
|
|
15
|
+
* - Dynamic decimal separator: celá/celé/celých based on integer
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Vocabulary (module-level constants)
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
// Ones words (masculine form)
|
|
25
|
+
const ONES = ['', 'jedna', 'dva', 'tři', 'čtyři', 'pět', 'šest', 'sedm', 'osm', 'devět']
|
|
26
|
+
|
|
27
|
+
// Teens (10-19)
|
|
28
|
+
const TEENS = ['deset', 'jedenáct', 'dvanáct', 'třináct', 'čtrnáct', 'patnáct', 'šestnáct', 'sedmnáct', 'osmnáct', 'devatenáct']
|
|
29
|
+
|
|
30
|
+
// Tens (20-90)
|
|
31
|
+
const TENS = ['', '', 'dvacet', 'třicet', 'čtyřicet', 'padesát', 'šedesát', 'sedmdesát', 'osmdesát', 'devadesát']
|
|
32
|
+
|
|
33
|
+
// Irregular hundreds
|
|
34
|
+
const HUNDREDS = ['', 'sto', 'dvě stě', 'tři sta', 'čtyři sta', 'pět set', 'šest set', 'sedm set', 'osm set', 'devět set']
|
|
35
|
+
|
|
36
|
+
// Scale plural forms [singular, few (2-4), many (5+)]
|
|
37
|
+
const PLURAL_FORMS = {
|
|
38
|
+
1: ['tisíc', 'tisíce', 'tisíc'], // 10^3
|
|
39
|
+
2: ['milion', 'miliony', 'milionů'], // 10^6
|
|
40
|
+
3: ['miliarda', 'miliardy', 'miliard'], // 10^9
|
|
41
|
+
4: ['bilion', 'biliony', 'bilionů'], // 10^12
|
|
42
|
+
5: ['biliarda', 'biliardy', 'biliard'], // 10^15
|
|
43
|
+
6: ['trilion', 'triliony', 'trilionů'], // 10^18
|
|
44
|
+
7: ['triliarda', 'triliardy', 'triliard'], // 10^21
|
|
45
|
+
8: ['kvadrilion', 'kvadriliony', 'kvadrilionů'], // 10^24
|
|
46
|
+
9: ['kvadriliarda', 'kvadriliardy', 'kvadriliard'] // 10^27
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ZERO = 'nula'
|
|
50
|
+
const NEGATIVE = 'mínus'
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Precomputed Lookup Tables (built once at module load)
|
|
54
|
+
// ============================================================================
|
|
2
55
|
|
|
3
56
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
57
|
+
* Builds segment word for 0-999 (masculine, default form).
|
|
58
|
+
* Only used during table construction.
|
|
6
59
|
*/
|
|
60
|
+
function buildSegment (n) {
|
|
61
|
+
if (n === 0) return ''
|
|
62
|
+
|
|
63
|
+
const ones = n % 10
|
|
64
|
+
const tens = Math.floor(n / 10) % 10
|
|
65
|
+
const hundreds = Math.floor(n / 100)
|
|
66
|
+
|
|
67
|
+
const parts = []
|
|
68
|
+
|
|
69
|
+
// Hundreds (irregular)
|
|
70
|
+
if (hundreds > 0) {
|
|
71
|
+
parts.push(HUNDREDS[hundreds])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Tens and ones
|
|
75
|
+
if (tens === 1) {
|
|
76
|
+
// Teens
|
|
77
|
+
parts.push(TEENS[ones])
|
|
78
|
+
} else if (tens >= 2) {
|
|
79
|
+
parts.push(TENS[tens])
|
|
80
|
+
if (ones > 0) {
|
|
81
|
+
parts.push(ONES[ones])
|
|
82
|
+
}
|
|
83
|
+
} else if (ones > 0) {
|
|
84
|
+
parts.push(ONES[ones])
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return parts.join(' ')
|
|
88
|
+
}
|
|
7
89
|
|
|
8
90
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Implements Czech number words using the Slavic language pattern:
|
|
12
|
-
* - Czech number words (jedna, dva, tři, čtyři, pět...)
|
|
13
|
-
* - Slavic three-form pluralization (tisíc/tisíce/tisíc)
|
|
14
|
-
* - Gender agreement for numbers 1-2
|
|
15
|
-
* - Czech-specific number word endings
|
|
16
|
-
*
|
|
17
|
-
* Key Features:
|
|
18
|
-
* - Three-form pluralization system shared across Slavic languages
|
|
19
|
-
* * Form 1 (singular): 1 (e.g., "tisíc")
|
|
20
|
-
* * Form 2 (few): 2-4, 22-24, 32-34... excluding teens (e.g., "tisíce")
|
|
21
|
-
* * Form 3 (many): all other numbers (e.g., "tisíc")
|
|
22
|
-
* - Chunk-based decomposition (splits into groups of 3 digits: ones, thousands, millions, etc.)
|
|
23
|
-
* - Large number handling via thousands[] array with indexed [singular, few, many] forms
|
|
24
|
-
*
|
|
25
|
-
* Inherits from SlavicLanguage:
|
|
26
|
-
* - Complex pluralization rules (one/few/many forms)
|
|
27
|
-
* - Group-based large number handling (chunk decomposition via splitByX())
|
|
28
|
-
* - Proper declension patterns via pluralize() method
|
|
91
|
+
* Builds segment word for 0-999 with feminine hundreds.
|
|
92
|
+
* Hundreds use irregular forms (dvě stě, tři sta) but ones remain masculine.
|
|
29
93
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
ones =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
9: 'devět'
|
|
94
|
+
function buildSegmentWithHundreds (n) {
|
|
95
|
+
if (n === 0) return ''
|
|
96
|
+
|
|
97
|
+
const ones = n % 10
|
|
98
|
+
const tens = Math.floor(n / 10) % 10
|
|
99
|
+
const hundreds = Math.floor(n / 100)
|
|
100
|
+
|
|
101
|
+
const parts = []
|
|
102
|
+
|
|
103
|
+
// Hundreds use the irregular HUNDREDS array (already has "dvě stě" etc.)
|
|
104
|
+
if (hundreds > 0) {
|
|
105
|
+
parts.push(HUNDREDS[hundreds])
|
|
43
106
|
}
|
|
44
107
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
9: 'devatenáct'
|
|
108
|
+
// Tens and ones use masculine form
|
|
109
|
+
if (tens === 1) {
|
|
110
|
+
parts.push(TEENS[ones])
|
|
111
|
+
} else if (tens >= 2) {
|
|
112
|
+
parts.push(TENS[tens])
|
|
113
|
+
if (ones > 0) {
|
|
114
|
+
parts.push(ONES[ones]) // masculine
|
|
115
|
+
}
|
|
116
|
+
} else if (ones > 0) {
|
|
117
|
+
parts.push(ONES[ones]) // masculine
|
|
56
118
|
}
|
|
57
119
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
120
|
+
return parts.join(' ')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Precompute all 1000 segment words (0-999) - masculine form
|
|
124
|
+
const SEGMENTS = new Array(1000)
|
|
125
|
+
for (let i = 0; i < 1000; i++) {
|
|
126
|
+
SEGMENTS[i] = buildSegment(i)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Precompute all 1000 segment words with hundreds (irregular hundreds forms)
|
|
130
|
+
const SEGMENTS_WITH_HUNDREDS = new Array(1000)
|
|
131
|
+
for (let i = 0; i < 1000; i++) {
|
|
132
|
+
SEGMENTS_WITH_HUNDREDS[i] = buildSegmentWithHundreds(i)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ============================================================================
|
|
136
|
+
// Helper Functions
|
|
137
|
+
// ============================================================================
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Czech pluralization: 1 = singular, 2-4 = few, else = many.
|
|
141
|
+
* Teens (11-19) always use "many" form.
|
|
142
|
+
*
|
|
143
|
+
* @param {bigint} n - The number
|
|
144
|
+
* @param {string[]} forms - [singular, few, many]
|
|
145
|
+
* @returns {string} The appropriate form
|
|
146
|
+
*/
|
|
147
|
+
function pluralize (n, forms) {
|
|
148
|
+
if (n === 1n) return forms[0]
|
|
149
|
+
|
|
150
|
+
const lastDigit = n % 10n
|
|
151
|
+
const lastTwoDigits = n % 100n
|
|
152
|
+
|
|
153
|
+
// 2-4, but not 12-14 (teens use "many")
|
|
154
|
+
if (lastDigit >= 2n && lastDigit <= 4n && (lastTwoDigits < 10n || lastTwoDigits > 20n)) {
|
|
155
|
+
return forms[1]
|
|
67
156
|
}
|
|
68
157
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
158
|
+
return forms[2]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Gets the decimal separator word based on integer part.
|
|
163
|
+
* celá (0-1), celé (2-4), celých (5+)
|
|
164
|
+
*/
|
|
165
|
+
function getDecimalSeparator (integerPart) {
|
|
166
|
+
if (integerPart === 0n || integerPart === 1n) {
|
|
167
|
+
return 'celá'
|
|
168
|
+
} else if (integerPart >= 2n && integerPart <= 4n) {
|
|
169
|
+
return 'celé'
|
|
170
|
+
} else {
|
|
171
|
+
return 'celých'
|
|
79
172
|
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// Conversion Functions
|
|
177
|
+
// ============================================================================
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Converts a non-negative integer to Czech words.
|
|
181
|
+
*
|
|
182
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
183
|
+
* @returns {string} Czech words
|
|
184
|
+
*/
|
|
185
|
+
function integerToWords (n) {
|
|
186
|
+
if (n === 0n) return ZERO
|
|
80
187
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
3: ['miliarda', 'miliardy', 'miliard'], // 10^ 9
|
|
85
|
-
4: ['bilion', 'biliony', 'bilionů'], // 10^ 12
|
|
86
|
-
5: ['biliarda', 'biliardy', 'biliard'], // 10^ 15
|
|
87
|
-
6: ['trilion', 'triliony', 'trilionů'], // 10^ 18
|
|
88
|
-
7: ['triliarda', 'triliardy', 'triliard'], // 10^ 21
|
|
89
|
-
8: ['kvadrilion', 'kvadriliony', 'kvadrilionů'], // 10^ 24
|
|
90
|
-
9: ['kvadriliarda', 'kvadriliardy', 'kvadriliard'], // 10^ 27
|
|
91
|
-
10: ['quintillion', 'quintilliony', 'quintillionů'] // 10^ 30
|
|
188
|
+
// Fast path: numbers < 1000 (direct lookup)
|
|
189
|
+
if (n < 1000n) {
|
|
190
|
+
return SEGMENTS[Number(n)]
|
|
92
191
|
}
|
|
93
192
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
193
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
194
|
+
if (n < 1_000_000n) {
|
|
195
|
+
const thousands = n / 1000n
|
|
196
|
+
const remainder = Number(n % 1000n)
|
|
197
|
+
|
|
198
|
+
const scaleWord = pluralize(thousands, PLURAL_FORMS[1])
|
|
199
|
+
|
|
200
|
+
let result
|
|
201
|
+
if (thousands === 1n) {
|
|
202
|
+
// Omit "one" before tisíc
|
|
203
|
+
result = scaleWord
|
|
104
204
|
} else {
|
|
105
|
-
|
|
205
|
+
result = SEGMENTS[Number(thousands)] + ' ' + scaleWord
|
|
106
206
|
}
|
|
207
|
+
|
|
208
|
+
if (remainder > 0) {
|
|
209
|
+
// Use form with irregular hundreds (for "dvě stě" etc.)
|
|
210
|
+
result += ' ' + SEGMENTS_WITH_HUNDREDS[remainder]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return result
|
|
107
214
|
}
|
|
108
215
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* @param {SlavicOptions} [options={}] Configuration options (inherited from SlavicLanguage).
|
|
113
|
-
*/
|
|
114
|
-
constructor (options = {}) {
|
|
115
|
-
super(options)
|
|
216
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
217
|
+
return buildLargeNumberWords(n)
|
|
218
|
+
}
|
|
116
219
|
|
|
117
|
-
|
|
118
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Builds words for numbers >= 1,000,000.
|
|
222
|
+
* Uses BigInt division for faster segment extraction.
|
|
223
|
+
*
|
|
224
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
225
|
+
* @returns {string} Czech words
|
|
226
|
+
*/
|
|
227
|
+
function buildLargeNumberWords (n) {
|
|
228
|
+
// Extract segments using BigInt division (faster than string slicing)
|
|
229
|
+
// Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
|
|
230
|
+
const segmentValues = []
|
|
231
|
+
let temp = n
|
|
232
|
+
while (temp > 0n) {
|
|
233
|
+
segmentValues.push(temp % 1000n)
|
|
234
|
+
temp = temp / 1000n
|
|
119
235
|
}
|
|
120
236
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*
|
|
124
|
-
* Czech three-form system:
|
|
125
|
-
* - Form 1 (singular): exactly n=1 (e.g., "tisíc")
|
|
126
|
-
* - Form 2 (few): n ends in 2-4, excluding teens (22-24, 32-34...) (e.g., "tisíce")
|
|
127
|
-
* - Form 3 (many): all other numbers (e.g., "tisíc" for 0, 5+)
|
|
128
|
-
*
|
|
129
|
-
* @param {bigint} n The number to classify.
|
|
130
|
-
* @param {Array<string>} forms Array of [singular, few, many] word forms.
|
|
131
|
-
* @returns {string} The appropriate form for the number n.
|
|
132
|
-
*/
|
|
133
|
-
pluralize (n, forms) {
|
|
134
|
-
if (n === 1n) {
|
|
135
|
-
return forms[0]
|
|
136
|
-
}
|
|
237
|
+
// Build result string directly
|
|
238
|
+
let result = ''
|
|
137
239
|
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
240
|
+
for (let i = segmentValues.length - 1; i >= 0; i--) {
|
|
241
|
+
const segment = segmentValues[i]
|
|
242
|
+
if (segment === 0n) continue
|
|
141
243
|
|
|
142
|
-
if (
|
|
143
|
-
return forms[1]
|
|
144
|
-
}
|
|
244
|
+
if (result) result += ' '
|
|
145
245
|
|
|
146
|
-
|
|
147
|
-
|
|
246
|
+
if (i === 0) {
|
|
247
|
+
// Units segment (no scale word) - use form with irregular hundreds
|
|
248
|
+
result += SEGMENTS_WITH_HUNDREDS[Number(segment)]
|
|
249
|
+
} else {
|
|
250
|
+
// Scale word needed
|
|
251
|
+
const forms = PLURAL_FORMS[i]
|
|
252
|
+
if (forms) {
|
|
253
|
+
const scaleWord = pluralize(segment, forms)
|
|
148
254
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
* 3. Join all words with spaces
|
|
160
|
-
*
|
|
161
|
-
* Example: 1234 → chunks [1, 234] → "jeden tisíc dvěstěčtyřicet"
|
|
162
|
-
* - Chunk 1 (index=1, thousands): "jeden" + "tisíc"
|
|
163
|
-
* - Chunk 234 (index=0): "dvě stě" + "třicet" + "čtyři"
|
|
164
|
-
*
|
|
165
|
-
* Special case: When chunk=1 at thousands+ levels, omit the ones word to avoid
|
|
166
|
-
* redundancy with the pluralized magnitude (e.g., just "tisíc" not "jeden tisíc").
|
|
167
|
-
*
|
|
168
|
-
* @param {bigint} number The whole number to convert.
|
|
169
|
-
* @returns {string} The number expressed in Czech words.
|
|
170
|
-
*/
|
|
171
|
-
convertWholePart (number) {
|
|
172
|
-
if (number === 0n) {
|
|
173
|
-
return this.zeroWord
|
|
174
|
-
}
|
|
175
|
-
const words = []
|
|
176
|
-
const chunks = this.splitByX(number.toString(), 3)
|
|
177
|
-
let index = chunks.length
|
|
178
|
-
for (const x of chunks) {
|
|
179
|
-
index--
|
|
180
|
-
if (x === 0n) continue
|
|
181
|
-
const [n1, n2, n3] = this.getDigits(x)
|
|
182
|
-
if (n3 > 0n) {
|
|
183
|
-
words.push(this.hundreds[n3])
|
|
184
|
-
}
|
|
185
|
-
if (n2 > 1n) {
|
|
186
|
-
words.push(this.twenties[n2])
|
|
187
|
-
}
|
|
188
|
-
if (n2 === 1n) {
|
|
189
|
-
words.push(this.tens[n1])
|
|
190
|
-
} else if (n1 > 0n && !(index > 0 && x === 1n)) {
|
|
191
|
-
words.push(this.ones[n1])
|
|
192
|
-
}
|
|
193
|
-
if (index > 0) {
|
|
194
|
-
words.push(this.pluralize(x, this.thousands[index]))
|
|
255
|
+
if (segment === 1n) {
|
|
256
|
+
// Omit "one" before scale words
|
|
257
|
+
result += scaleWord
|
|
258
|
+
} else {
|
|
259
|
+
// Use masculine form for multiplier before scale words
|
|
260
|
+
result += SEGMENTS[Number(segment)] + ' ' + scaleWord
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
// Fallback for very large scales without defined forms
|
|
264
|
+
result += SEGMENTS[Number(segment)]
|
|
195
265
|
}
|
|
196
266
|
}
|
|
197
|
-
return words.join(' ')
|
|
198
267
|
}
|
|
268
|
+
|
|
269
|
+
return result
|
|
199
270
|
}
|
|
200
271
|
|
|
201
272
|
/**
|
|
202
|
-
* Converts
|
|
273
|
+
* Converts decimal digits to Czech words.
|
|
203
274
|
*
|
|
204
|
-
* @param {
|
|
205
|
-
* @
|
|
206
|
-
* @returns {string} The number expressed in Czech words.
|
|
207
|
-
* @throws {TypeError} If value is NaN or invalid type.
|
|
208
|
-
* @throws {Error} If value is an invalid number string.
|
|
275
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
276
|
+
* @returns {string} Czech words for decimal part
|
|
209
277
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
278
|
+
function decimalPartToWords (decimalPart) {
|
|
279
|
+
let result = ''
|
|
280
|
+
|
|
281
|
+
// Handle leading zeros
|
|
282
|
+
let i = 0
|
|
283
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
284
|
+
if (result) result += ' '
|
|
285
|
+
result += ZERO
|
|
286
|
+
i++
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Convert remainder as a single number
|
|
290
|
+
const remainder = decimalPart.slice(i)
|
|
291
|
+
if (remainder) {
|
|
292
|
+
if (result) result += ' '
|
|
293
|
+
result += integerToWords(BigInt(remainder))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return result
|
|
212
297
|
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Converts a numeric value to Czech words.
|
|
301
|
+
*
|
|
302
|
+
* This is the main public API. It accepts any valid numeric input
|
|
303
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
304
|
+
*
|
|
305
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
306
|
+
* @returns {string} The number in Czech words
|
|
307
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
308
|
+
* @throws {Error} If value is not a valid number format
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* toWords(21) // 'dvacet jedna'
|
|
312
|
+
* toWords(1000) // 'tisíc'
|
|
313
|
+
* toWords(2000) // 'dva tisíce'
|
|
314
|
+
* toWords(5000) // 'pět tisíc'
|
|
315
|
+
*/
|
|
316
|
+
function toWords (value) {
|
|
317
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
318
|
+
|
|
319
|
+
let result = ''
|
|
320
|
+
|
|
321
|
+
if (isNegative) {
|
|
322
|
+
result = NEGATIVE + ' '
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
result += integerToWords(integerPart)
|
|
326
|
+
|
|
327
|
+
if (decimalPart) {
|
|
328
|
+
const separator = getDecimalSeparator(integerPart)
|
|
329
|
+
result += ' ' + separator + ' ' + decimalPartToWords(decimalPart)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return result
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ============================================================================
|
|
336
|
+
// Public API
|
|
337
|
+
// ============================================================================
|
|
338
|
+
|
|
339
|
+
export { toWords }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a numeric value to Danish words.
|
|
3
|
+
*
|
|
4
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
5
|
+
* @returns {string} The number in Danish 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) // 'enogtyve'
|
|
11
|
+
* toWords(1000) // 'ettusind'
|
|
12
|
+
* toWords(1000000) // 'en millioner'
|
|
13
|
+
*/
|
|
14
|
+
export function toWords(value: number | string | bigint): string;
|