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/ja.js
CHANGED
|
@@ -1,190 +1,259 @@
|
|
|
1
|
-
import AbstractLanguage from '../classes/abstract-language.js'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Japanese language converter
|
|
5
|
-
*
|
|
6
|
-
* Converts numbers to Japanese kanji numerals using the Sino-Japanese system:
|
|
7
|
-
* - Uses kanji characters (一, 二, 三, etc.)
|
|
8
|
-
* - Grouping by 万 (man, 10,000) and 億 (oku, 100,000,000)
|
|
9
|
-
* - Unique scale units: 兆 (chō, trillion), 京 (kei, 10^16)
|
|
10
|
-
* - Special rules for 1: omitted before 十 (10), 百 (100), 千 (1000), but kept for 万 and above
|
|
2
|
+
* Japanese language converter - Functional Implementation
|
|
11
3
|
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
* - Grouping by powers of 10,000 (万-based system, not 1,000)
|
|
15
|
-
* - Scale units: 万 (10^4), 億 (10^8), 兆 (10^12), 京 (10^16)
|
|
16
|
-
* - Special handling of 一 (one) prefix
|
|
17
|
-
* - Support for very large numbers up to 無量大数 (10^68)
|
|
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.
|
|
18
6
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* 2. Convert each group to kanji using special rules
|
|
22
|
-
* 3. Append appropriate scale word (万, 億, 兆, etc.)
|
|
23
|
-
* 4. Join all groups
|
|
7
|
+
* Key optimization: Precompute all segment values (0-9999) at module load.
|
|
8
|
+
* This eliminates all per-call string manipulation for segment conversion.
|
|
24
9
|
*
|
|
25
|
-
*
|
|
26
|
-
* -
|
|
27
|
-
* -
|
|
28
|
-
* -
|
|
10
|
+
* Japanese-specific rules (handled in precomputation):
|
|
11
|
+
* - Myriad (万-based) grouping: 4 digits per segment instead of 3
|
|
12
|
+
* - 一 omission: Omit "一" before 十, 百, 千 but NOT before 万 and higher scales
|
|
13
|
+
* - Kanji numerals: 零一二三四五六七八九
|
|
14
|
+
* - No spaces between characters
|
|
29
15
|
*/
|
|
30
|
-
export class Japanese extends AbstractLanguage {
|
|
31
|
-
negativeWord = 'マイナス'
|
|
32
|
-
decimalSeparatorWord = '点'
|
|
33
|
-
zeroWord = '零'
|
|
34
|
-
wordSeparator = '' // Japanese doesn't use spaces between characters
|
|
35
|
-
convertDecimalsPerDigit = true // Enable digit-by-digit decimal conversion
|
|
36
|
-
|
|
37
|
-
// Digits used for group conversion (1-9)
|
|
38
|
-
digits = ['一', '二', '三', '四', '五', '六', '七', '八', '九']
|
|
39
|
-
|
|
40
|
-
// Scale words for grouping by 10^4
|
|
41
|
-
scales = [
|
|
42
|
-
'', // 10^0 (ones)
|
|
43
|
-
'万', // 10^4 (man)
|
|
44
|
-
'億', // 10^8 (oku)
|
|
45
|
-
'兆', // 10^12 (chō)
|
|
46
|
-
'京', // 10^16 (kei)
|
|
47
|
-
'垓', // 10^20 (gai)
|
|
48
|
-
'秭', // 10^24 (jo/shi)
|
|
49
|
-
'穣', // 10^28 (jō)
|
|
50
|
-
'溝', // 10^32 (kō)
|
|
51
|
-
'澗', // 10^36 (kan)
|
|
52
|
-
'正', // 10^40 (sei)
|
|
53
|
-
'載', // 10^44 (sai)
|
|
54
|
-
'極', // 10^48 (goku)
|
|
55
|
-
'恒河沙', // 10^52 (gōgasha)
|
|
56
|
-
'阿僧祇', // 10^56 (asōgi)
|
|
57
|
-
'那由他', // 10^60 (nayuta)
|
|
58
|
-
'不可思議', // 10^64 (fukashigi)
|
|
59
|
-
'無量大数' // 10^68 (muryōtaisū)
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Converts a group of up to 4 digits to Japanese kanji.
|
|
64
|
-
* Handles special rules for omitting 一 before 十, 百, 千.
|
|
65
|
-
*
|
|
66
|
-
* Rule: Within a group, omit 一 before 十/百/千 EXCEPT when:
|
|
67
|
-
* - It's a lower group (not isTopGroup) AND
|
|
68
|
-
* - It would be the only character in that position
|
|
69
|
-
*
|
|
70
|
-
* @param {bigint} num - Number from 0 to 9999
|
|
71
|
-
* @param {boolean} isTopGroup - Whether this is the highest non-zero group
|
|
72
|
-
* @returns {string} Japanese kanji representation
|
|
73
|
-
*/
|
|
74
|
-
convertGroup (num, isTopGroup = false) {
|
|
75
|
-
if (num === 0n) return ''
|
|
76
|
-
|
|
77
|
-
const thousands = num / 1000n
|
|
78
|
-
const hundreds = (num % 1000n) / 100n
|
|
79
|
-
const tens = (num % 100n) / 10n
|
|
80
|
-
const ones = num % 10n
|
|
81
|
-
|
|
82
|
-
let result = ''
|
|
83
|
-
|
|
84
|
-
// Thousands (千)
|
|
85
|
-
if (thousands > 0n) {
|
|
86
|
-
// Always omit 一 before 千 when thousands === 1
|
|
87
|
-
if (thousands === 1n) {
|
|
88
|
-
result += '千'
|
|
89
|
-
} else {
|
|
90
|
-
result += this.digits[Number(thousands) - 1] + '千'
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
16
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
17
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Vocabulary (module-level constants)
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
// Ones words (1-9), index 0 unused
|
|
24
|
+
const ONES = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九']
|
|
25
|
+
|
|
26
|
+
// Scale words for powers of 10,000 (万-based system)
|
|
27
|
+
// Index 0 = 万 (10^4), 1 = 億 (10^8), 2 = 兆 (10^12), etc.
|
|
28
|
+
const SCALES = [
|
|
29
|
+
'万', // 10^4 (man)
|
|
30
|
+
'億', // 10^8 (oku)
|
|
31
|
+
'兆', // 10^12 (chō)
|
|
32
|
+
'京', // 10^16 (kei)
|
|
33
|
+
'垓', // 10^20 (gai)
|
|
34
|
+
'秭', // 10^24 (jo/shi)
|
|
35
|
+
'穣', // 10^28 (jō)
|
|
36
|
+
'溝', // 10^32 (kō)
|
|
37
|
+
'澗', // 10^36 (kan)
|
|
38
|
+
'正', // 10^40 (sei)
|
|
39
|
+
'載', // 10^44 (sai)
|
|
40
|
+
'極', // 10^48 (goku)
|
|
41
|
+
'恒河沙', // 10^52 (gōgasha)
|
|
42
|
+
'阿僧祇', // 10^56 (asōgi)
|
|
43
|
+
'那由他', // 10^60 (nayuta)
|
|
44
|
+
'不可思議', // 10^64 (fukashigi)
|
|
45
|
+
'無量大数' // 10^68 (muryōtaisū)
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
const ZERO = '零'
|
|
49
|
+
const NEGATIVE = 'マイナス'
|
|
50
|
+
const DECIMAL_SEP = '点'
|
|
51
|
+
|
|
52
|
+
// Internal scale words (within 4-digit segments)
|
|
53
|
+
const TEN = '十'
|
|
54
|
+
const HUNDRED = '百'
|
|
55
|
+
const THOUSAND = '千'
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// Precomputed Lookup Tables (built once at module load)
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Builds segment word for 0-9999 with 一 omission rules.
|
|
63
|
+
* - Omit 一 before 十, 百, 千
|
|
64
|
+
* Only used during table construction.
|
|
65
|
+
*/
|
|
66
|
+
function buildSegment (n) {
|
|
67
|
+
if (n === 0) return ''
|
|
68
|
+
|
|
69
|
+
const ones = n % 10
|
|
70
|
+
const tens = Math.floor(n / 10) % 10
|
|
71
|
+
const hundreds = Math.floor(n / 100) % 10
|
|
72
|
+
const thousands = Math.floor(n / 1000)
|
|
73
|
+
|
|
74
|
+
let result = ''
|
|
75
|
+
|
|
76
|
+
// Thousands (千) - omit 一 when 1
|
|
77
|
+
if (thousands > 0) {
|
|
78
|
+
if (thousands === 1) {
|
|
79
|
+
result += THOUSAND
|
|
80
|
+
} else {
|
|
81
|
+
result += ONES[thousands] + THOUSAND
|
|
102
82
|
}
|
|
83
|
+
}
|
|
103
84
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
result += this.digits[Number(tens) - 1] + '十'
|
|
111
|
-
}
|
|
85
|
+
// Hundreds (百) - omit 一 when 1
|
|
86
|
+
if (hundreds > 0) {
|
|
87
|
+
if (hundreds === 1) {
|
|
88
|
+
result += HUNDRED
|
|
89
|
+
} else {
|
|
90
|
+
result += ONES[hundreds] + HUNDRED
|
|
112
91
|
}
|
|
92
|
+
}
|
|
113
93
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
94
|
+
// Tens (十) - omit 一 when 1
|
|
95
|
+
if (tens > 0) {
|
|
96
|
+
if (tens === 1) {
|
|
97
|
+
result += TEN
|
|
98
|
+
} else {
|
|
99
|
+
result += ONES[tens] + TEN
|
|
117
100
|
}
|
|
101
|
+
}
|
|
118
102
|
|
|
119
|
-
|
|
103
|
+
// Ones
|
|
104
|
+
if (ones > 0) {
|
|
105
|
+
result += ONES[ones]
|
|
120
106
|
}
|
|
121
107
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
108
|
+
return result
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Precompute all 10000 segment words (0-9999)
|
|
112
|
+
// SEGMENTS[n] gives the Japanese word for n within a segment
|
|
113
|
+
const SEGMENTS = new Array(10000)
|
|
114
|
+
for (let i = 0; i < 10000; i++) {
|
|
115
|
+
SEGMENTS[i] = buildSegment(i)
|
|
116
|
+
}
|
|
132
117
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Conversion Functions
|
|
120
|
+
// ============================================================================
|
|
136
121
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Converts a non-negative integer to Japanese words.
|
|
124
|
+
*
|
|
125
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
126
|
+
* @returns {string} Japanese kanji words
|
|
127
|
+
*/
|
|
128
|
+
function integerToWords (n) {
|
|
129
|
+
if (n === 0n) return ZERO
|
|
130
|
+
|
|
131
|
+
// Fast path: numbers < 10000 (direct lookup)
|
|
132
|
+
if (n < 10000n) {
|
|
133
|
+
return SEGMENTS[Number(n)]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Fast path: numbers < 100,000,000 (万 range)
|
|
137
|
+
if (n < 100_000_000n) {
|
|
138
|
+
const man = Number(n / 10000n)
|
|
139
|
+
const remainder = Number(n % 10000n)
|
|
140
|
+
|
|
141
|
+
// For 万 and above, we need 一 before the scale word when segment is 1
|
|
142
|
+
let result
|
|
143
|
+
if (man === 1) {
|
|
144
|
+
result = '一' + SCALES[0] // 一万
|
|
145
|
+
} else {
|
|
146
|
+
result = SEGMENTS[man] + SCALES[0]
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
if (remainder > 0) {
|
|
150
|
+
result += SEGMENTS[remainder]
|
|
151
|
+
}
|
|
149
152
|
|
|
150
|
-
|
|
153
|
+
return result
|
|
154
|
+
}
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
// For numbers >= 100,000,000, use scale decomposition
|
|
157
|
+
return buildLargeNumberWords(n)
|
|
158
|
+
}
|
|
155
159
|
|
|
156
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Builds words for numbers >= 100,000,000.
|
|
162
|
+
* Uses BigInt modulo for 4-digit (myriad) segment extraction.
|
|
163
|
+
*
|
|
164
|
+
* @param {bigint} n - Number >= 100,000,000
|
|
165
|
+
* @returns {string} Japanese kanji words
|
|
166
|
+
*/
|
|
167
|
+
function buildLargeNumberWords (n) {
|
|
168
|
+
// Extract segments using BigInt modulo (faster than string slicing)
|
|
169
|
+
// Segments stored least-significant first (index 0 = units, 1 = 万, etc.)
|
|
170
|
+
const segments = []
|
|
171
|
+
let temp = n
|
|
172
|
+
while (temp > 0n) {
|
|
173
|
+
segments.push(Number(temp % 10000n))
|
|
174
|
+
temp = temp / 10000n
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Build result string directly (process from most-significant to least)
|
|
178
|
+
let result = ''
|
|
179
|
+
|
|
180
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
181
|
+
const segment = segments[i]
|
|
182
|
+
if (segment === 0) continue
|
|
157
183
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
result += '一' + this.scales[scale]
|
|
163
|
-
} else {
|
|
164
|
-
result += groupStr + this.scales[scale]
|
|
165
|
-
}
|
|
184
|
+
if (i > 0) {
|
|
185
|
+
// For scales >= 万, we need 一 before scale word when segment is 1
|
|
186
|
+
if (segment === 1) {
|
|
187
|
+
result += '一' + SCALES[i - 1]
|
|
166
188
|
} else {
|
|
167
|
-
result +=
|
|
189
|
+
result += SEGMENTS[segment] + SCALES[i - 1]
|
|
168
190
|
}
|
|
191
|
+
} else {
|
|
192
|
+
// Units segment (no scale word)
|
|
193
|
+
result += SEGMENTS[segment]
|
|
169
194
|
}
|
|
195
|
+
}
|
|
170
196
|
|
|
171
|
-
|
|
197
|
+
return result || ZERO
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Converts decimal digits to Japanese words (digit by digit).
|
|
202
|
+
*
|
|
203
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
204
|
+
* @returns {string} Japanese kanji words for decimal part
|
|
205
|
+
*/
|
|
206
|
+
function decimalPartToWords (decimalPart) {
|
|
207
|
+
let result = ''
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < decimalPart.length; i++) {
|
|
210
|
+
const digit = parseInt(decimalPart[i], 10)
|
|
211
|
+
if (digit === 0) {
|
|
212
|
+
result += ZERO
|
|
213
|
+
} else {
|
|
214
|
+
result += ONES[digit]
|
|
215
|
+
}
|
|
172
216
|
}
|
|
217
|
+
|
|
218
|
+
return result
|
|
173
219
|
}
|
|
174
220
|
|
|
175
221
|
/**
|
|
176
|
-
* Converts a value to
|
|
222
|
+
* Converts a numeric value to Japanese words.
|
|
177
223
|
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* @
|
|
224
|
+
* This is the main public API. It accepts any valid numeric input
|
|
225
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
226
|
+
*
|
|
227
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
228
|
+
* @returns {string} The number in Japanese kanji words
|
|
229
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
230
|
+
* @throws {Error} If value is not a valid number format
|
|
182
231
|
*
|
|
183
232
|
* @example
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
233
|
+
* toWords(42) // '四十二'
|
|
234
|
+
* toWords(10000) // '一万'
|
|
235
|
+
* toWords(100000000) // '一億'
|
|
187
236
|
*/
|
|
188
|
-
|
|
189
|
-
|
|
237
|
+
function toWords (value) {
|
|
238
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
239
|
+
|
|
240
|
+
let result = ''
|
|
241
|
+
|
|
242
|
+
if (isNegative) {
|
|
243
|
+
result = NEGATIVE
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
result += integerToWords(integerPart)
|
|
247
|
+
|
|
248
|
+
if (decimalPart) {
|
|
249
|
+
result += DECIMAL_SEP + decimalPartToWords(decimalPart)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return result
|
|
190
253
|
}
|
|
254
|
+
|
|
255
|
+
// ============================================================================
|
|
256
|
+
// Public API
|
|
257
|
+
// ============================================================================
|
|
258
|
+
|
|
259
|
+
export { toWords }
|
package/lib/languages/kn.js
CHANGED
|
@@ -1,71 +1,137 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Kannada language converter - Functional Implementation
|
|
3
|
+
*
|
|
4
|
+
* Self-contained converter for South Asian numbering.
|
|
5
|
+
*
|
|
6
|
+
* Key features:
|
|
7
|
+
* - Indian numbering system (ಸಾವಿರ, ಲಕ್ಷ, ಕೋಟಿ)
|
|
8
|
+
* - Kannada script
|
|
9
|
+
* - 3-2-2 grouping pattern (last 3 digits, then groups of 2)
|
|
10
|
+
* - Complete word forms for 0-99
|
|
11
|
+
* - Per-digit decimal reading
|
|
4
12
|
*/
|
|
5
13
|
|
|
6
|
-
import
|
|
14
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
7
15
|
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
56
|
-
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Vocabulary
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
const ZERO = 'ಸೊನ್ನೆ'
|
|
21
|
+
const NEGATIVE = 'ಋಣಾತ್ಮಕ'
|
|
22
|
+
const DECIMAL_SEP = 'ದಶಮಾಂಶ'
|
|
23
|
+
const HUNDRED = 'ನೂರು'
|
|
24
|
+
|
|
25
|
+
const BELOW_HUNDRED = [
|
|
26
|
+
'ಸೊನ್ನೆ', 'ಒಂದು', 'ಎರಡು', 'ಮೂರು', 'ನಾಲ್ಕು', 'ಐದು', 'ಆರು', 'ಏಳು', 'ಎಂಟು', 'ಒಂಬತ್ತು',
|
|
27
|
+
'ಹತ್ತು', 'ಹನ್ನೊಂದು', 'ಹನ್ನೆರಡು', 'ಹದಿಮೂರು', 'ಹದಿನಾಲ್ಕು', 'ಹದಿನೈದು', 'ಹದಿನಾರು', 'ಹದಿನೇಳು', 'ಹದಿನೆಂಟು', 'ಹತ್ತೊಂಬತ್ತು',
|
|
28
|
+
'ಇಪ್ಪತ್ತು', 'ಇಪ್ಪತ್ತೊಂದು', 'ಇಪ್ಪತ್ತೆರಡು', 'ಇಪ್ಪತ್ತಮೂರು', 'ಇಪ್ಪತ್ತನಾಲ್ಕು', 'ಇಪ್ಪತ್ತೈದು', 'ಇಪ್ಪತ್ತಾರು', 'ಇಪ್ಪತ್ತೇಳು', 'ಇಪ್ಪತ್ತೆಂಟು', 'ಇಪ್ಪತ್ತೊಂಬತ್ತು',
|
|
29
|
+
'ಮೂವತ್ತು', 'ಮೂವತ್ತೊಂದು', 'ಮೂವತ್ತೆರಡು', 'ಮೂವತ್ತಮೂರು', 'ಮೂವತ್ತನಾಲ್ಕು', 'ಮೂವತ್ತೈದು', 'ಮೂವತ್ತಾರು', 'ಮೂವತ್ತೇಳು', 'ಮೂವತ್ತೆಂಟು', 'ಮೂವತ್ತೊಂಬತ್ತು',
|
|
30
|
+
'ನಲವತ್ತು', 'ನಲವತ್ತೊಂದು', 'ನಲವತ್ತೆರಡು', 'ನಲವತ್ತಮೂರು', 'ನಲವತ್ತನಾಲ್ಕು', 'ನಲವತ್ತೈದು', 'ನಲವತ್ತಾರು', 'ನಲವತ್ತೇಳು', 'ನಲವತ್ತೆಂಟು', 'ನಲವತ್ತೊಂಬತ್ತು',
|
|
31
|
+
'ಐವತ್ತು', 'ಐವತ್ತೊಂದು', 'ಐವತ್ತೆರಡು', 'ಐವತ್ತಮೂರು', 'ಐವತ್ತನಾಲ್ಕು', 'ಐವತ್ತೈದು', 'ಐವತ್ತಾರು', 'ಐವತ್ತೇಳು', 'ಐವತ್ತೆಂಟು', 'ಐವತ್ತೊಂಬತ್ತು',
|
|
32
|
+
'ಅರವತ್ತು', 'ಅರವತ್ತೊಂದು', 'ಅರವತ್ತೆರಡು', 'ಅರವತ್ತಮೂರು', 'ಅರವತ್ತನಾಲ್ಕು', 'ಅರವತ್ತೈದು', 'ಅರವತ್ತಾರು', 'ಅರವತ್ತೇಳು', 'ಅರವತ್ತೆಂಟು', 'ಅರವತ್ತೊಂಬತ್ತು',
|
|
33
|
+
'ಎಪ್ಪತ್ತು', 'ಎಪ್ಪತ್ತೊಂದು', 'ಎಪ್ಪತ್ತೆರಡು', 'ಎಪ್ಪತ್ತಮೂರು', 'ಎಪ್ಪತ್ತನಾಲ್ಕು', 'ಎಪ್ಪತ್ತೈದು', 'ಎಪ್ಪತ್ತಾರು', 'ಎಪ್ಪತ್ತೇಳು', 'ಎಪ್ಪತ್ತೆಂಟು', 'ಎಪ್ಪತ್ತೊಂಬತ್ತು',
|
|
34
|
+
'ಎಂಬತ್ತು', 'ಎಂಬತ್ತೊಂದು', 'ಎಂಬತ್ತೆರಡು', 'ಎಂಬತ್ತಮೂರು', 'ಎಂಬತ್ತನಾಲ್ಕು', 'ಎಂಬತ್ತೈದು', 'ಎಂಬತ್ತಾರು', 'ಎಂಬತ್ತೇಳು', 'ಎಂಬತ್ತೆಂಟು', 'ಎಂಬತ್ತೊಂಬತ್ತು',
|
|
35
|
+
'ತೊಂಬತ್ತು', 'ತೊಂಬತ್ತೊಂದು', 'ತೊಂಬತ್ತೆರಡು', 'ತೊಂಬತ್ತಮೂರು', 'ತೊಂಬತ್ತನಾಲ್ಕು', 'ತೊಂಬತ್ತೈದು', 'ತೊಂಬತ್ತಾರು', 'ತೊಂಬತ್ತೇಳು', 'ತೊಂಬತ್ತೆಂಟು', 'ತೊಂಬತ್ತೊಂಬತ್ತು'
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
// Scale words: index 0 = units (empty), 1 = thousand, 2 = lakh, 3 = crore, etc.
|
|
39
|
+
const SCALE_WORDS = ['', 'ಸಾವಿರ', 'ಲಕ್ಷ', 'ಕೋಟಿ', 'ಅಬ್ಜ', 'ಖರ್ವ', 'ನೀಲ', 'ಪದ್ಮ', 'ಶಂಖ']
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Segment Splitting (inlined for performance)
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
function groupByThreeThenTwos (n) {
|
|
46
|
+
const numStr = n.toString()
|
|
47
|
+
if (numStr.length <= 3) return [Number(numStr)]
|
|
48
|
+
|
|
49
|
+
const segments = []
|
|
50
|
+
segments.unshift(Number(numStr.slice(-3)))
|
|
51
|
+
|
|
52
|
+
let remaining = numStr.slice(0, -3)
|
|
53
|
+
while (remaining.length > 0) {
|
|
54
|
+
segments.unshift(Number(remaining.slice(-2)))
|
|
55
|
+
remaining = remaining.slice(0, -2)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return segments
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function segmentToWords (n) {
|
|
62
|
+
if (n === 0) return ''
|
|
63
|
+
if (n < 100) return BELOW_HUNDRED[n]
|
|
64
|
+
|
|
65
|
+
const hundreds = Math.trunc(n / 100)
|
|
66
|
+
const remainder = n % 100
|
|
67
|
+
|
|
68
|
+
if (remainder === 0) {
|
|
69
|
+
return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED
|
|
70
|
+
}
|
|
71
|
+
return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED + ' ' + BELOW_HUNDRED[remainder]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Conversion Functions
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
function integerToWords (n) {
|
|
79
|
+
if (n === 0n) return ZERO
|
|
80
|
+
|
|
81
|
+
const segments = groupByThreeThenTwos(n)
|
|
82
|
+
const segmentCount = segments.length
|
|
83
|
+
const words = []
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < segmentCount; i++) {
|
|
86
|
+
const segmentValue = segments[i]
|
|
87
|
+
if (segmentValue === 0) continue
|
|
88
|
+
|
|
89
|
+
const scaleIndex = segmentCount - i - 1
|
|
90
|
+
words.push(segmentToWords(segmentValue))
|
|
91
|
+
if (scaleIndex > 0 && SCALE_WORDS[scaleIndex]) {
|
|
92
|
+
words.push(SCALE_WORDS[scaleIndex])
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return words.join(' ').trim()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function decimalPartToWords (decimalPart) {
|
|
100
|
+
// Per-digit decimal reading
|
|
101
|
+
const digits = []
|
|
102
|
+
for (const char of decimalPart) {
|
|
103
|
+
const d = parseInt(char, 10)
|
|
104
|
+
digits.push(d === 0 ? ZERO : BELOW_HUNDRED[d])
|
|
105
|
+
}
|
|
106
|
+
return digits.join(' ')
|
|
57
107
|
}
|
|
58
108
|
|
|
59
109
|
/**
|
|
60
|
-
* Converts a
|
|
61
|
-
*
|
|
62
|
-
* @param {
|
|
63
|
-
* @returns {string} The
|
|
64
|
-
* @example
|
|
65
|
-
* convertToWords(42) // 'ನಲವತ್ತೆರಡು'
|
|
66
|
-
* convertToWords(1000) // 'ಒಂದು ಸಾವಿರ'
|
|
67
|
-
* convertToWords(100000) // 'ಒಂದು ಲಕ್ಷ'
|
|
110
|
+
* Converts a numeric value to Kannada words.
|
|
111
|
+
*
|
|
112
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
113
|
+
* @returns {string} The number in Kannada words
|
|
68
114
|
*/
|
|
69
|
-
|
|
70
|
-
|
|
115
|
+
function toWords (value) {
|
|
116
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
117
|
+
|
|
118
|
+
let result = ''
|
|
119
|
+
|
|
120
|
+
if (isNegative) {
|
|
121
|
+
result = NEGATIVE + ' '
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
result += integerToWords(integerPart)
|
|
125
|
+
|
|
126
|
+
if (decimalPart) {
|
|
127
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result
|
|
71
131
|
}
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Exports
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
export { toWords }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a numeric value to Korean words.
|
|
3
|
+
*
|
|
4
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
5
|
+
* @returns {string} The number in Korean words
|
|
6
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
7
|
+
* @throws {Error} If value is not a valid number format
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* toWords(21) // '이십일'
|
|
11
|
+
* toWords(10000) // '만'
|
|
12
|
+
* toWords(1000000) // '백만'
|
|
13
|
+
*/
|
|
14
|
+
export function toWords(value: number | string | bigint): string;
|