n2words 2.0.0 → 3.1.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 +64 -0
- package/README.md +86 -188
- 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 -0
- package/dist/languages/ar.js.map +1 -0
- package/dist/languages/az.js +3 -0
- package/dist/languages/az.js.map +1 -0
- package/dist/languages/bn.js +3 -0
- package/dist/languages/bn.js.map +1 -0
- package/dist/languages/cs.js +3 -0
- package/dist/languages/cs.js.map +1 -0
- package/dist/languages/da.js +3 -0
- package/dist/languages/da.js.map +1 -0
- package/dist/languages/de.js +3 -0
- package/dist/languages/de.js.map +1 -0
- package/dist/languages/el.js +3 -0
- package/dist/languages/el.js.map +1 -0
- package/dist/languages/en.js +3 -0
- package/dist/languages/en.js.map +1 -0
- package/dist/languages/es.js +3 -0
- package/dist/languages/es.js.map +1 -0
- package/dist/languages/fa.js +3 -0
- package/dist/languages/fa.js.map +1 -0
- package/dist/languages/fi.js +3 -0
- package/dist/languages/fi.js.map +1 -0
- package/dist/languages/fil.js +3 -0
- package/dist/languages/fil.js.map +1 -0
- package/dist/languages/fr-BE.js +3 -0
- package/dist/languages/fr-BE.js.map +1 -0
- package/dist/languages/fr.js +3 -0
- package/dist/languages/fr.js.map +1 -0
- package/dist/languages/gu.js +3 -0
- package/dist/languages/gu.js.map +1 -0
- 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 -0
- package/dist/languages/he.js.map +1 -0
- package/dist/languages/hi.js +3 -0
- package/dist/languages/hi.js.map +1 -0
- package/dist/languages/hr.js +3 -0
- package/dist/languages/hr.js.map +1 -0
- package/dist/languages/hu.js +3 -0
- package/dist/languages/hu.js.map +1 -0
- package/dist/languages/id.js +3 -0
- package/dist/languages/id.js.map +1 -0
- package/dist/languages/it.js +3 -0
- package/dist/languages/it.js.map +1 -0
- package/dist/languages/ja.js +3 -0
- package/dist/languages/ja.js.map +1 -0
- package/dist/languages/ka.js +3 -0
- package/dist/languages/ka.js.map +1 -0
- package/dist/languages/kn.js +3 -0
- package/dist/languages/kn.js.map +1 -0
- package/dist/languages/ko.js +3 -0
- package/dist/languages/ko.js.map +1 -0
- package/dist/languages/lt.js +3 -0
- package/dist/languages/lt.js.map +1 -0
- package/dist/languages/lv.js +3 -0
- package/dist/languages/lv.js.map +1 -0
- package/dist/languages/mr.js +3 -0
- package/dist/languages/mr.js.map +1 -0
- package/dist/languages/ms.js +3 -0
- package/dist/languages/ms.js.map +1 -0
- package/dist/languages/nb.js +3 -0
- package/dist/languages/nb.js.map +1 -0
- package/dist/languages/nl.js +3 -0
- package/dist/languages/nl.js.map +1 -0
- package/dist/languages/pa.js +3 -0
- package/dist/languages/pa.js.map +1 -0
- package/dist/languages/pl.js +3 -0
- package/dist/languages/pl.js.map +1 -0
- package/dist/languages/pt.js +3 -0
- package/dist/languages/pt.js.map +1 -0
- package/dist/languages/ro.js +3 -0
- package/dist/languages/ro.js.map +1 -0
- package/dist/languages/ru.js +3 -0
- package/dist/languages/ru.js.map +1 -0
- package/dist/languages/sr-Cyrl.js +3 -0
- package/dist/languages/sr-Cyrl.js.map +1 -0
- package/dist/languages/sr-Latn.js +3 -0
- package/dist/languages/sr-Latn.js.map +1 -0
- package/dist/languages/sv.js +3 -0
- package/dist/languages/sv.js.map +1 -0
- package/dist/languages/sw.js +3 -0
- package/dist/languages/sw.js.map +1 -0
- package/dist/languages/ta.js +3 -0
- package/dist/languages/ta.js.map +1 -0
- package/dist/languages/te.js +3 -0
- package/dist/languages/te.js.map +1 -0
- package/dist/languages/th.js +3 -0
- package/dist/languages/th.js.map +1 -0
- package/dist/languages/tr.js +3 -0
- package/dist/languages/tr.js.map +1 -0
- package/dist/languages/uk.js +3 -0
- package/dist/languages/uk.js.map +1 -0
- package/dist/languages/ur.js +3 -0
- package/dist/languages/ur.js.map +1 -0
- package/dist/languages/vi.js +3 -0
- package/dist/languages/vi.js.map +1 -0
- package/dist/languages/yo.js +3 -0
- package/dist/languages/yo.js.map +1 -0
- package/dist/languages/zh-Hans.js +3 -0
- package/dist/languages/zh-Hans.js.map +1 -0
- package/dist/languages/zh-Hant.js +3 -0
- package/dist/languages/zh-Hant.js.map +1 -0
- package/dist/n2words.js +2 -2
- package/dist/n2words.js.map +1 -1
- package/lib/languages/am-Latn.d.ts +7 -0
- package/lib/languages/am-Latn.js +159 -0
- package/lib/languages/am.d.ts +7 -0
- package/lib/languages/am.js +159 -0
- package/lib/languages/ar.d.ts +14 -27
- package/lib/languages/ar.js +175 -129
- package/lib/languages/az.d.ts +4 -9
- package/lib/languages/az.js +166 -37
- package/lib/languages/bn.d.ts +4 -8
- package/lib/languages/bn.js +159 -124
- package/lib/languages/cs.d.ts +15 -85
- package/lib/languages/cs.js +293 -114
- package/lib/languages/da.d.ts +11 -12
- package/lib/languages/da.js +269 -101
- package/lib/languages/de.d.ts +14 -11
- package/lib/languages/de.js +305 -86
- package/lib/languages/el.d.ts +11 -11
- package/lib/languages/el.js +224 -78
- package/lib/languages/en.d.ts +14 -13
- package/lib/languages/en.js +228 -72
- package/lib/languages/es.d.ts +18 -12
- package/lib/languages/es.js +297 -103
- package/lib/languages/fa.d.ts +4 -44
- package/lib/languages/fa.js +112 -122
- package/lib/languages/fi.d.ts +14 -0
- package/lib/languages/fi.js +238 -0
- package/lib/languages/fil.d.ts +4 -13
- package/lib/languages/fil.js +196 -106
- package/lib/languages/fr-BE.d.ts +8 -8
- package/lib/languages/fr-BE.js +285 -19
- package/lib/languages/fr.d.ts +18 -12
- package/lib/languages/fr.js +339 -89
- package/lib/languages/gu.d.ts +4 -8
- package/lib/languages/gu.js +143 -125
- package/lib/languages/ha.d.ts +7 -0
- package/lib/languages/ha.js +225 -0
- package/lib/languages/hbo.d.ts +10 -110
- package/lib/languages/hbo.js +245 -214
- package/lib/languages/he.d.ts +10 -77
- package/lib/languages/he.js +231 -172
- package/lib/languages/hi.d.ts +4 -8
- package/lib/languages/hi.js +163 -124
- package/lib/languages/hr.d.ts +8 -77
- package/lib/languages/hr.js +200 -89
- package/lib/languages/hu.d.ts +4 -19
- package/lib/languages/hu.js +198 -119
- package/lib/languages/id.d.ts +4 -34
- package/lib/languages/id.js +166 -129
- package/lib/languages/it.d.ts +16 -34
- package/lib/languages/it.js +307 -97
- package/lib/languages/ja.d.ts +14 -14
- package/lib/languages/ja.js +221 -111
- package/lib/languages/ka.d.ts +17 -0
- package/lib/languages/ka.js +291 -0
- package/lib/languages/kn.d.ts +4 -8
- package/lib/languages/kn.js +143 -35
- package/lib/languages/ko.d.ts +11 -11
- package/lib/languages/ko.js +250 -49
- package/lib/languages/lt.d.ts +15 -67
- package/lib/languages/lt.js +287 -122
- package/lib/languages/lv.d.ts +15 -67
- package/lib/languages/lv.js +288 -106
- package/lib/languages/mr.d.ts +4 -8
- package/lib/languages/mr.js +143 -125
- package/lib/languages/ms.d.ts +4 -28
- package/lib/languages/ms.js +166 -116
- package/lib/languages/nb.d.ts +11 -9
- package/lib/languages/nb.js +272 -87
- package/lib/languages/nl.d.ts +23 -13
- package/lib/languages/nl.js +299 -133
- package/lib/languages/pa.d.ts +4 -8
- package/lib/languages/pa.js +151 -124
- package/lib/languages/pl.d.ts +19 -77
- package/lib/languages/pl.js +294 -87
- package/lib/languages/pt.d.ts +14 -26
- package/lib/languages/pt.js +272 -92
- package/lib/languages/ro.d.ts +15 -155
- package/lib/languages/ro.js +219 -235
- package/lib/languages/ru.d.ts +8 -82
- package/lib/languages/ru.js +239 -90
- package/lib/languages/sr-Cyrl.d.ts +8 -77
- package/lib/languages/sr-Cyrl.js +197 -89
- package/lib/languages/sr-Latn.d.ts +8 -77
- package/lib/languages/sr-Latn.js +197 -89
- package/lib/languages/sv.d.ts +11 -11
- package/lib/languages/sv.js +278 -74
- package/lib/languages/sw.d.ts +4 -36
- package/lib/languages/sw.js +133 -106
- package/lib/languages/ta.d.ts +4 -17
- package/lib/languages/ta.js +143 -202
- package/lib/languages/te.d.ts +4 -19
- package/lib/languages/te.js +133 -196
- package/lib/languages/th.d.ts +4 -14
- package/lib/languages/th.js +135 -91
- package/lib/languages/tr.d.ts +15 -9
- package/lib/languages/tr.js +245 -49
- package/lib/languages/uk.d.ts +8 -82
- package/lib/languages/uk.js +206 -78
- package/lib/languages/ur.d.ts +4 -8
- package/lib/languages/ur.js +151 -124
- package/lib/languages/vi.d.ts +14 -69
- package/lib/languages/vi.js +278 -129
- package/lib/languages/yo.d.ts +7 -0
- package/lib/languages/yo.js +303 -0
- package/lib/languages/zh-Hans.d.ts +8 -18
- package/lib/languages/zh-Hans.js +163 -92
- package/lib/languages/zh-Hant.d.ts +8 -18
- package/lib/languages/zh-Hant.js +181 -90
- package/lib/n2words.d.ts +55 -209
- package/lib/n2words.js +115 -530
- 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 +26 -14
- package/dist/ArabicConverter.js +0 -3
- package/dist/ArabicConverter.js.map +0 -1
- package/dist/AzerbaijaniConverter.js +0 -3
- package/dist/AzerbaijaniConverter.js.map +0 -1
- package/dist/BanglaConverter.js +0 -3
- package/dist/BanglaConverter.js.map +0 -1
- package/dist/BiblicalHebrewConverter.js +0 -3
- package/dist/BiblicalHebrewConverter.js.map +0 -1
- package/dist/CroatianConverter.js +0 -3
- package/dist/CroatianConverter.js.map +0 -1
- package/dist/CzechConverter.js +0 -3
- package/dist/CzechConverter.js.map +0 -1
- package/dist/DanishConverter.js +0 -3
- package/dist/DanishConverter.js.map +0 -1
- package/dist/DutchConverter.js +0 -3
- package/dist/DutchConverter.js.map +0 -1
- package/dist/EnglishConverter.js +0 -3
- package/dist/EnglishConverter.js.map +0 -1
- package/dist/FilipinoConverter.js +0 -3
- package/dist/FilipinoConverter.js.map +0 -1
- package/dist/FrenchBelgiumConverter.js +0 -3
- package/dist/FrenchBelgiumConverter.js.map +0 -1
- package/dist/FrenchConverter.js +0 -3
- package/dist/FrenchConverter.js.map +0 -1
- package/dist/GermanConverter.js +0 -3
- package/dist/GermanConverter.js.map +0 -1
- package/dist/GreekConverter.js +0 -3
- package/dist/GreekConverter.js.map +0 -1
- package/dist/GujaratiConverter.js +0 -3
- package/dist/GujaratiConverter.js.map +0 -1
- package/dist/HebrewConverter.js +0 -3
- package/dist/HebrewConverter.js.map +0 -1
- package/dist/HindiConverter.js +0 -3
- package/dist/HindiConverter.js.map +0 -1
- package/dist/HungarianConverter.js +0 -3
- package/dist/HungarianConverter.js.map +0 -1
- package/dist/IndonesianConverter.js +0 -3
- package/dist/IndonesianConverter.js.map +0 -1
- package/dist/ItalianConverter.js +0 -3
- package/dist/ItalianConverter.js.map +0 -1
- package/dist/JapaneseConverter.js +0 -3
- package/dist/JapaneseConverter.js.map +0 -1
- package/dist/KannadaConverter.js +0 -3
- package/dist/KannadaConverter.js.map +0 -1
- package/dist/KoreanConverter.js +0 -3
- package/dist/KoreanConverter.js.map +0 -1
- package/dist/LatvianConverter.js +0 -3
- package/dist/LatvianConverter.js.map +0 -1
- package/dist/LithuanianConverter.js +0 -3
- package/dist/LithuanianConverter.js.map +0 -1
- package/dist/MalayConverter.js +0 -3
- package/dist/MalayConverter.js.map +0 -1
- package/dist/MarathiConverter.js +0 -3
- package/dist/MarathiConverter.js.map +0 -1
- package/dist/NorwegianBokmalConverter.js +0 -3
- package/dist/NorwegianBokmalConverter.js.map +0 -1
- package/dist/PersianConverter.js +0 -3
- package/dist/PersianConverter.js.map +0 -1
- package/dist/PolishConverter.js +0 -3
- package/dist/PolishConverter.js.map +0 -1
- package/dist/PortugueseConverter.js +0 -3
- package/dist/PortugueseConverter.js.map +0 -1
- package/dist/PunjabiConverter.js +0 -3
- package/dist/PunjabiConverter.js.map +0 -1
- package/dist/RomanianConverter.js +0 -3
- package/dist/RomanianConverter.js.map +0 -1
- package/dist/RussianConverter.js +0 -3
- package/dist/RussianConverter.js.map +0 -1
- package/dist/SerbianCyrillicConverter.js +0 -3
- package/dist/SerbianCyrillicConverter.js.map +0 -1
- package/dist/SerbianLatinConverter.js +0 -3
- package/dist/SerbianLatinConverter.js.map +0 -1
- package/dist/SimplifiedChineseConverter.js +0 -3
- package/dist/SimplifiedChineseConverter.js.map +0 -1
- package/dist/SpanishConverter.js +0 -3
- package/dist/SpanishConverter.js.map +0 -1
- package/dist/SwahiliConverter.js +0 -3
- package/dist/SwahiliConverter.js.map +0 -1
- package/dist/SwedishConverter.js +0 -3
- package/dist/SwedishConverter.js.map +0 -1
- package/dist/TamilConverter.js +0 -3
- package/dist/TamilConverter.js.map +0 -1
- package/dist/TeluguConverter.js +0 -3
- package/dist/TeluguConverter.js.map +0 -1
- package/dist/ThaiConverter.js +0 -3
- package/dist/ThaiConverter.js.map +0 -1
- package/dist/TraditionalChineseConverter.js +0 -3
- package/dist/TraditionalChineseConverter.js.map +0 -1
- package/dist/TurkishConverter.js +0 -3
- package/dist/TurkishConverter.js.map +0 -1
- package/dist/UkrainianConverter.js +0 -3
- package/dist/UkrainianConverter.js.map +0 -1
- package/dist/UrduConverter.js +0 -3
- package/dist/UrduConverter.js.map +0 -1
- package/dist/VietnameseConverter.js +0 -3
- package/dist/VietnameseConverter.js.map +0 -1
- package/lib/classes/abstract-language.d.ts +0 -178
- package/lib/classes/abstract-language.js +0 -268
- package/lib/classes/greedy-scale-language.d.ts +0 -109
- package/lib/classes/greedy-scale-language.js +0 -201
- package/lib/classes/slavic-language.d.ts +0 -148
- package/lib/classes/slavic-language.js +0 -281
- package/lib/classes/south-asian-language.d.ts +0 -70
- package/lib/classes/south-asian-language.js +0 -154
- package/lib/classes/turkic-language.d.ts +0 -26
- package/lib/classes/turkic-language.js +0 -59
package/lib/languages/vi.d.ts
CHANGED
|
@@ -1,72 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vietnamese
|
|
2
|
+
* Converts a numeric value to Vietnamese words.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
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 Vietnamese 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(42) // 'bốn mươi hai'
|
|
14
|
+
* toWords(101) // 'một trăm lẻ một'
|
|
15
|
+
* toWords(1000000) // 'một triệu'
|
|
8
16
|
*/
|
|
9
|
-
export
|
|
10
|
-
belowTwentyWords: {
|
|
11
|
-
0: string;
|
|
12
|
-
1: string;
|
|
13
|
-
2: string;
|
|
14
|
-
3: string;
|
|
15
|
-
4: string;
|
|
16
|
-
5: string;
|
|
17
|
-
6: string;
|
|
18
|
-
7: string;
|
|
19
|
-
8: string;
|
|
20
|
-
9: string;
|
|
21
|
-
10: string;
|
|
22
|
-
11: string;
|
|
23
|
-
12: string;
|
|
24
|
-
13: string;
|
|
25
|
-
14: string;
|
|
26
|
-
15: string;
|
|
27
|
-
16: string;
|
|
28
|
-
17: string;
|
|
29
|
-
18: string;
|
|
30
|
-
19: string;
|
|
31
|
-
};
|
|
32
|
-
twentiesWords: {
|
|
33
|
-
20: string;
|
|
34
|
-
30: string;
|
|
35
|
-
40: string;
|
|
36
|
-
50: string;
|
|
37
|
-
60: string;
|
|
38
|
-
70: string;
|
|
39
|
-
80: string;
|
|
40
|
-
90: string;
|
|
41
|
-
};
|
|
42
|
-
scaleWords: {
|
|
43
|
-
1: string;
|
|
44
|
-
2: string;
|
|
45
|
-
3: string;
|
|
46
|
-
4: string;
|
|
47
|
-
5: string;
|
|
48
|
-
6: string;
|
|
49
|
-
7: string;
|
|
50
|
-
8: string;
|
|
51
|
-
9: string;
|
|
52
|
-
10: string;
|
|
53
|
-
11: string;
|
|
54
|
-
12: string;
|
|
55
|
-
13: string;
|
|
56
|
-
14: string;
|
|
57
|
-
15: string;
|
|
58
|
-
16: string;
|
|
59
|
-
17: string;
|
|
60
|
-
18: string;
|
|
61
|
-
19: string;
|
|
62
|
-
20: string;
|
|
63
|
-
};
|
|
64
|
-
/** Convert numbers less than 100 to Vietnamese words. */
|
|
65
|
-
convertLess100(number: any): any;
|
|
66
|
-
/** Convert numbers less than 1000 to Vietnamese words. */
|
|
67
|
-
convertLess1000(number: any): any;
|
|
68
|
-
/** Convert numbers greater than 1000 to Vietnamese words. */
|
|
69
|
-
convertMore1000(number: any): any;
|
|
70
|
-
integerToWords(integerPart: any): any;
|
|
71
|
-
}
|
|
72
|
-
import { AbstractLanguage } from '../classes/abstract-language.js';
|
|
17
|
+
export function toWords(value: number | string | bigint): string;
|
package/lib/languages/vi.js
CHANGED
|
@@ -1,147 +1,296 @@
|
|
|
1
|
-
import { AbstractLanguage } from '../classes/abstract-language.js'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Vietnamese language converter
|
|
2
|
+
* Vietnamese language converter - Functional Implementation
|
|
3
|
+
*
|
|
4
|
+
* Self-contained module with its own input validation, ready for subpath exports.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* - Special pronunciation
|
|
8
|
-
* - "Lẻ" (odd/extra) when tens place is zero
|
|
9
|
-
* - Vietnamese
|
|
6
|
+
* Vietnamese-specific rules:
|
|
7
|
+
* - Special pronunciation: "lăm" for 5 in tens position, "mốt" for final 1
|
|
8
|
+
* - "Lẻ" (odd/extra) marker when tens place is zero after hundreds/scales
|
|
9
|
+
* - Short scale system with Vietnamese words (nghìn, triệu, tỷ)
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
5
|
|
56
|
-
|
|
57
|
-
7: 'Sextillion',
|
|
58
|
-
8: 'Septillion',
|
|
59
|
-
9: 'Octillion',
|
|
60
|
-
10: 'Nonillion',
|
|
61
|
-
11: 'Decillion',
|
|
62
|
-
12: 'Undecillion',
|
|
63
|
-
13: 'Duodecillion',
|
|
64
|
-
14: 'Tredecillion',
|
|
65
|
-
15: 'Quattuordecillion',
|
|
66
|
-
16: 'Sexdecillion',
|
|
67
|
-
17: 'Septendecillion',
|
|
68
|
-
18: 'Octodecillion',
|
|
69
|
-
19: 'Novemdecillion',
|
|
70
|
-
20: 'Vigintillion'
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Convert numbers less than 100 to Vietnamese words. */
|
|
74
|
-
convertLess100 (number) {
|
|
75
|
-
const unitsPart = number % 10
|
|
76
|
-
const tensPart = number - unitsPart
|
|
77
|
-
const tensPartText = this.twentiesWords[tensPart]
|
|
78
|
-
if (unitsPart === 0) {
|
|
79
|
-
return tensPartText
|
|
80
|
-
}
|
|
81
|
-
const unitsPartText = this.belowTwentyWords[unitsPart]
|
|
82
|
-
let suffix = unitsPartText
|
|
83
|
-
if (unitsPart === 1) {
|
|
84
|
-
suffix = 'mốt'
|
|
85
|
-
}
|
|
86
|
-
if (unitsPart === 5) {
|
|
87
|
-
suffix = 'lăm'
|
|
88
|
-
}
|
|
89
|
-
return tensPartText + ' ' + suffix
|
|
11
|
+
|
|
12
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Vocabulary (module-level constants)
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
// Base vocabulary for building lookup tables
|
|
19
|
+
const ONES = ['không', 'một', 'hai', 'ba', 'bốn', 'năm', 'sáu', 'bảy', 'tám', 'chín']
|
|
20
|
+
|
|
21
|
+
// Scale words indexed by scale level (0 = units, 1 = thousands, etc.)
|
|
22
|
+
const SCALES = [
|
|
23
|
+
'', 'nghìn', 'triệu', 'tỷ', 'nghìn tỷ', 'trăm nghìn tỷ',
|
|
24
|
+
'Quintillion', 'Sextillion', 'Septillion', 'Octillion',
|
|
25
|
+
'Nonillion', 'Decillion', 'Undecillion', 'Duodecillion',
|
|
26
|
+
'Tredecillion', 'Quattuordecillion', 'Sexdecillion',
|
|
27
|
+
'Septendecillion', 'Octodecillion', 'Novemdecillion', 'Vigintillion'
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const HUNDRED = 'trăm'
|
|
31
|
+
const ZERO = 'không'
|
|
32
|
+
const NEGATIVE = 'âm'
|
|
33
|
+
const DECIMAL_SEP = 'phẩy'
|
|
34
|
+
const LE = 'lẻ' // "odd/extra" marker for gaps
|
|
35
|
+
|
|
36
|
+
// Special forms
|
|
37
|
+
const MOT_FINAL = 'mốt' // 1 in tens position (21, 31, etc.)
|
|
38
|
+
const LAM = 'lăm' // 5 in tens position (25, 35, etc.)
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Segment Building
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Builds word for 0-99 with special forms (mốt, lăm).
|
|
46
|
+
*/
|
|
47
|
+
function buildBelowHundred (n) {
|
|
48
|
+
if (n === 0) return ONES[0]
|
|
49
|
+
if (n < 10) return ONES[n]
|
|
50
|
+
|
|
51
|
+
// Teens: 10-19
|
|
52
|
+
if (n < 20) {
|
|
53
|
+
const ones = n - 10
|
|
54
|
+
if (ones === 0) return 'mười'
|
|
55
|
+
if (ones === 5) return 'mười lăm'
|
|
56
|
+
return 'mười ' + ONES[ones]
|
|
90
57
|
}
|
|
91
58
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
59
|
+
// 20-99
|
|
60
|
+
const ones = n % 10
|
|
61
|
+
const tens = Math.floor(n / 10)
|
|
62
|
+
const tensWord = ONES[tens] + ' mươi'
|
|
63
|
+
|
|
64
|
+
if (ones === 0) return tensWord
|
|
65
|
+
if (ones === 1) return tensWord + ' ' + MOT_FINAL
|
|
66
|
+
if (ones === 5) return tensWord + ' ' + LAM
|
|
67
|
+
return tensWord + ' ' + ONES[ones]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Builds segment word for 0-999.
|
|
72
|
+
*/
|
|
73
|
+
function buildSegment (n) {
|
|
74
|
+
if (n === 0) return ''
|
|
75
|
+
|
|
76
|
+
const hundreds = Math.floor(n / 100)
|
|
77
|
+
const remainder = n % 100
|
|
78
|
+
|
|
79
|
+
let result = ''
|
|
80
|
+
|
|
81
|
+
if (hundreds > 0) {
|
|
82
|
+
result = ONES[hundreds] + ' ' + HUNDRED
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (remainder > 0) {
|
|
86
|
+
if (remainder < 10) {
|
|
87
|
+
// Single digit after hundreds needs "lẻ"
|
|
88
|
+
if (result) {
|
|
89
|
+
result += ' ' + LE + ' '
|
|
90
|
+
// Use "năm" not "lăm" after lẻ
|
|
91
|
+
result += remainder === 5 ? 'năm' : ONES[remainder]
|
|
106
92
|
} else {
|
|
107
|
-
|
|
93
|
+
result = ONES[remainder]
|
|
108
94
|
}
|
|
95
|
+
} else {
|
|
96
|
+
// 10-99 after hundreds
|
|
97
|
+
if (result) result += ' '
|
|
98
|
+
result += buildBelowHundred(remainder)
|
|
109
99
|
}
|
|
110
|
-
if (tensUnitsPart >= 10) {
|
|
111
|
-
words.push(this.integerToWords(tensUnitsPart))
|
|
112
|
-
}
|
|
113
|
-
return words.join(' ')
|
|
114
100
|
}
|
|
115
101
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
102
|
+
return result
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Builds "lẻ" prefixed word for small remainders (1-99) after scale words.
|
|
107
|
+
*/
|
|
108
|
+
function buildLeSegment (n) {
|
|
109
|
+
if (n === 0) return ''
|
|
110
|
+
if (n < 10) {
|
|
111
|
+
// Use "năm" not "lăm" after lẻ
|
|
112
|
+
return LE + ' ' + (n === 5 ? 'năm' : ONES[n])
|
|
113
|
+
}
|
|
114
|
+
return LE + ' ' + buildBelowHundred(n)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Conversion Functions
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Converts a non-negative integer to Vietnamese words.
|
|
123
|
+
*
|
|
124
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
125
|
+
* @returns {string} Vietnamese words
|
|
126
|
+
*/
|
|
127
|
+
function integerToWords (n) {
|
|
128
|
+
if (n === 0n) return ZERO
|
|
129
|
+
|
|
130
|
+
// Fast path: numbers < 100
|
|
131
|
+
if (n < 100n) {
|
|
132
|
+
return buildBelowHundred(Number(n))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Fast path: numbers < 1000
|
|
136
|
+
if (n < 1000n) {
|
|
137
|
+
return buildSegment(Number(n))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
141
|
+
if (n < 1_000_000n) {
|
|
142
|
+
const thousands = Number(n / 1000n)
|
|
143
|
+
const remainder = Number(n % 1000n)
|
|
144
|
+
|
|
145
|
+
const thousandsWords = buildSegment(thousands) + ' ' + SCALES[1]
|
|
146
|
+
|
|
147
|
+
if (remainder === 0) {
|
|
148
|
+
return thousandsWords
|
|
124
149
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
words.push('lẻ')
|
|
130
|
-
}
|
|
131
|
-
words.push(this.integerToWords(r))
|
|
150
|
+
|
|
151
|
+
// Check if remainder needs "lẻ" marker (< 100)
|
|
152
|
+
if (remainder < 100) {
|
|
153
|
+
return thousandsWords + ' ' + buildLeSegment(remainder)
|
|
132
154
|
}
|
|
133
|
-
|
|
155
|
+
|
|
156
|
+
return thousandsWords + ' ' + buildSegment(remainder)
|
|
134
157
|
}
|
|
135
158
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
159
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
160
|
+
return buildLargeNumberWords(n)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Builds words for numbers >= 1,000,000.
|
|
165
|
+
*
|
|
166
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
167
|
+
* @returns {string} Vietnamese words
|
|
168
|
+
*/
|
|
169
|
+
function buildLargeNumberWords (n) {
|
|
170
|
+
const numStr = n.toString()
|
|
171
|
+
const len = numStr.length
|
|
172
|
+
|
|
173
|
+
// Build segments of 3 digits from right to left
|
|
174
|
+
const segments = []
|
|
175
|
+
const segmentSize = 3
|
|
176
|
+
|
|
177
|
+
const remainderLen = len % segmentSize
|
|
178
|
+
let pos = 0
|
|
179
|
+
if (remainderLen > 0) {
|
|
180
|
+
segments.push(Number(numStr.slice(0, remainderLen)))
|
|
181
|
+
pos = remainderLen
|
|
182
|
+
}
|
|
183
|
+
while (pos < len) {
|
|
184
|
+
segments.push(Number(numStr.slice(pos, pos + segmentSize)))
|
|
185
|
+
pos += segmentSize
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Convert segments to words
|
|
189
|
+
const parts = []
|
|
190
|
+
let scaleIndex = segments.length - 1
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < segments.length; i++) {
|
|
193
|
+
const segment = segments[i]
|
|
194
|
+
if (segment !== 0) {
|
|
195
|
+
const words = buildSegment(segment)
|
|
196
|
+
if (words) {
|
|
197
|
+
if (scaleIndex > 0) {
|
|
198
|
+
parts.push(words + ' ' + SCALES[scaleIndex])
|
|
199
|
+
} else {
|
|
200
|
+
parts.push(words)
|
|
201
|
+
}
|
|
144
202
|
}
|
|
145
203
|
}
|
|
204
|
+
scaleIndex--
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Join with "lẻ" logic for small remainders
|
|
208
|
+
const partsLen = parts.length
|
|
209
|
+
if (partsLen === 0) return ZERO
|
|
210
|
+
if (partsLen === 1) return parts[0]
|
|
211
|
+
|
|
212
|
+
// Check if final segment needs "lẻ" marker (remainder <= 99 after scale word)
|
|
213
|
+
const lastSegment = segments[segments.length - 1]
|
|
214
|
+
if (lastSegment > 0 && lastSegment <= 99) {
|
|
215
|
+
// Last segment is small (no hundreds), needs "lẻ" after scale word
|
|
216
|
+
let result = parts[0]
|
|
217
|
+
for (let i = 1; i < partsLen - 1; i++) {
|
|
218
|
+
result += ' ' + parts[i]
|
|
219
|
+
}
|
|
220
|
+
return result + ' ' + buildLeSegment(lastSegment)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Join with spaces
|
|
224
|
+
let result = parts[0]
|
|
225
|
+
for (let i = 1; i < partsLen; i++) {
|
|
226
|
+
result += ' ' + parts[i]
|
|
227
|
+
}
|
|
228
|
+
return result
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Converts decimal digits to Vietnamese words.
|
|
233
|
+
*
|
|
234
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
235
|
+
* @returns {string} Vietnamese words for decimal part
|
|
236
|
+
*/
|
|
237
|
+
function decimalPartToWords (decimalPart) {
|
|
238
|
+
let result = ''
|
|
239
|
+
|
|
240
|
+
// Handle leading zeros
|
|
241
|
+
let i = 0
|
|
242
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
243
|
+
if (result) result += ' '
|
|
244
|
+
result += ZERO
|
|
245
|
+
i++
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Convert remainder as a single number
|
|
249
|
+
const remainder = decimalPart.slice(i)
|
|
250
|
+
if (remainder) {
|
|
251
|
+
if (result) result += ' '
|
|
252
|
+
result += integerToWords(BigInt(remainder))
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return result
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Converts a numeric value to Vietnamese words.
|
|
260
|
+
*
|
|
261
|
+
* This is the main public API. It accepts any valid numeric input
|
|
262
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
263
|
+
*
|
|
264
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
265
|
+
* @returns {string} The number in Vietnamese words
|
|
266
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
267
|
+
* @throws {Error} If value is not a valid number format
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* toWords(42) // 'bốn mươi hai'
|
|
271
|
+
* toWords(101) // 'một trăm lẻ một'
|
|
272
|
+
* toWords(1000000) // 'một triệu'
|
|
273
|
+
*/
|
|
274
|
+
function toWords (value) {
|
|
275
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
276
|
+
|
|
277
|
+
let result = ''
|
|
278
|
+
|
|
279
|
+
if (isNegative) {
|
|
280
|
+
result = NEGATIVE + ' '
|
|
146
281
|
}
|
|
282
|
+
|
|
283
|
+
result += integerToWords(integerPart)
|
|
284
|
+
|
|
285
|
+
if (decimalPart) {
|
|
286
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return result
|
|
147
290
|
}
|
|
291
|
+
|
|
292
|
+
// ============================================================================
|
|
293
|
+
// Public API
|
|
294
|
+
// ============================================================================
|
|
295
|
+
|
|
296
|
+
export { toWords }
|