bahttext 2.3.1 → 2.3.2
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/.eslintignore +0 -0
- package/.eslintrc +12 -0
- package/package.json +3 -4
- package/src/index.d.ts +27 -17
- package/src/index.js +215 -107
package/.eslintignore
ADDED
|
File without changes
|
package/.eslintrc
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bahttext",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Change number to Thai pronunciation string",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"typings": "src/index.d.ts",
|
|
@@ -37,14 +37,13 @@
|
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@stryker-mutator/core": "^8.7.1",
|
|
39
39
|
"@stryker-mutator/jest-runner": "^8.7.1",
|
|
40
|
-
"@to-da-moon/thai-baht-lib": "^0.0.12",
|
|
41
40
|
"baht": "^0.7.1",
|
|
41
|
+
"bahttext230": "npm:bahttext@2.3.0",
|
|
42
|
+
"bahttext231": "npm:bahttext@2.3.1",
|
|
42
43
|
"jest": "^29.7.0",
|
|
43
44
|
"jest-expect-message": "^1.1.3",
|
|
44
45
|
"semantic-release": "^24.2.2",
|
|
45
46
|
"standard": "^17.1.2",
|
|
46
|
-
"thai-baht-text": "^2.0.5",
|
|
47
|
-
"thai-baht-text-ts": "^1.1.0",
|
|
48
47
|
"typescript": "*"
|
|
49
48
|
},
|
|
50
49
|
"jest": {
|
package/src/index.d.ts
CHANGED
|
@@ -3,33 +3,43 @@ declare namespace _default {
|
|
|
3
3
|
}
|
|
4
4
|
export default _default;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Convert number → Thai Baht text (supports number or numeric string)
|
|
7
|
+
* Mirrors behaviour of previous implementation but with faster core.
|
|
7
8
|
*
|
|
8
9
|
* @public
|
|
9
|
-
* @param {number}
|
|
10
|
+
* @param {number|string} input
|
|
10
11
|
* @returns {string}
|
|
11
12
|
*/
|
|
12
|
-
export function bahttext(
|
|
13
|
+
export function bahttext(input: number | string): string;
|
|
13
14
|
/**
|
|
14
15
|
* @private
|
|
15
|
-
*
|
|
16
|
-
* @
|
|
16
|
+
* Handle numeric input and extract baht/satang values
|
|
17
|
+
* @param {number} input - numeric input (positive or negative)
|
|
18
|
+
* @returns {{baht: number, bahtStr: string, satang: number, isNegative: boolean} | false}
|
|
17
19
|
*/
|
|
18
|
-
export function
|
|
20
|
+
export function handleNumericInput(input: number): {
|
|
21
|
+
baht: number;
|
|
22
|
+
bahtStr: string;
|
|
23
|
+
satang: number;
|
|
24
|
+
isNegative: boolean;
|
|
25
|
+
} | false;
|
|
19
26
|
/**
|
|
20
27
|
* @private
|
|
21
|
-
*
|
|
22
|
-
* @param {string}
|
|
23
|
-
* @returns {string}
|
|
28
|
+
* Handle string input and extract baht/satang values
|
|
29
|
+
* @param {string} input - string input (numeric string, positive or negative)
|
|
30
|
+
* @returns {{baht: number, bahtStr: string, satang: number, isNegative: boolean} | false}
|
|
24
31
|
*/
|
|
25
|
-
export function
|
|
32
|
+
export function handleStringInput(input: string): {
|
|
33
|
+
baht: number;
|
|
34
|
+
bahtStr: string;
|
|
35
|
+
satang: number;
|
|
36
|
+
isNegative: boolean;
|
|
37
|
+
} | false;
|
|
26
38
|
/**
|
|
27
|
-
* bahtxtCombine baht and satang
|
|
28
|
-
* and also adding unit
|
|
29
|
-
*
|
|
30
39
|
* @private
|
|
31
|
-
*
|
|
32
|
-
* @param {
|
|
33
|
-
* @
|
|
40
|
+
* Format satang portion of the output
|
|
41
|
+
* @param {number} baht - baht amount
|
|
42
|
+
* @param {number} satang - satang amount
|
|
43
|
+
* @returns {string} formatted satang string
|
|
34
44
|
*/
|
|
35
|
-
export function
|
|
45
|
+
export function formatSatang(baht: number, satang: number): string;
|
package/src/index.js
CHANGED
|
@@ -1,150 +1,258 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const EMPTY = ''
|
|
2
|
+
const ONE = 'หนึ่ง'
|
|
3
|
+
const TWO = 'สอง'
|
|
4
|
+
const THREE_TO_NINE = ['สาม', 'สี่', 'ห้า', 'หก', 'เจ็ด', 'แปด', 'เก้า']
|
|
5
|
+
const ED = 'เอ็ด'
|
|
6
|
+
const YEE = 'ยี่'
|
|
7
|
+
const LAN = 'ล้าน'
|
|
8
|
+
|
|
9
|
+
// Index 0-5 correspond to units,สิบ,ร้อย,พัน,หมื่น,แสน
|
|
10
|
+
const DIGIT = [EMPTY, 'สิบ', 'ร้อย', 'พัน', 'หมื่น', 'แสน']
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
// Helpers to build word tables quickly
|
|
13
|
+
const ONES = [EMPTY, ED, TWO, ...THREE_TO_NINE]
|
|
14
|
+
const TENS = [
|
|
15
|
+
EMPTY,
|
|
16
|
+
...[EMPTY, YEE, ...THREE_TO_NINE].map(t => t + DIGIT[1]) // "สิบ" family
|
|
11
17
|
]
|
|
18
|
+
// 0-99 lookup (fast satang conversion)
|
|
19
|
+
const SUB_HUNDRED = TENS.flatMap(t => ONES.map(o => t + o))
|
|
20
|
+
// Special case: 1 should read "หนึ่ง" not "เอ็ด"
|
|
21
|
+
SUB_HUNDRED[1] = ONE
|
|
22
|
+
|
|
23
|
+
// Single-digit lookup for generic conversion (no suffix)
|
|
24
|
+
const SUB_TEN = [EMPTY, ONE, TWO, ...THREE_TO_NINE]
|
|
25
|
+
|
|
26
|
+
// Pre-compiled constants for frequently used combinations
|
|
27
|
+
const YEE_SIB = YEE + DIGIT[1] // "ยี่สิบ"
|
|
28
|
+
const SIB = DIGIT[1] // "สิบ"
|
|
29
|
+
|
|
30
|
+
// Default fallback text used throughout tests
|
|
31
|
+
const DEFAULT_RESULT = 'ศูนย์บาทถ้วน'
|
|
12
32
|
|
|
13
33
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
34
|
+
* Convert number → Thai Baht text (supports number or numeric string)
|
|
35
|
+
* Mirrors behaviour of previous implementation but with faster core.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
* @param {number|string} input
|
|
16
39
|
* @returns {string}
|
|
17
40
|
*/
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* @param {string|number[]} nums – string of digits ("123") or array \[1,2,3].
|
|
25
|
-
* @returns {string}
|
|
26
|
-
*/
|
|
27
|
-
let numStr = ''
|
|
28
|
-
|
|
29
|
-
if (typeof nums === 'string') {
|
|
30
|
-
numStr = nums
|
|
31
|
-
} else if (Array.isArray(nums)) {
|
|
32
|
-
numStr = nums.join('')
|
|
33
|
-
} else {
|
|
34
|
-
return ''
|
|
41
|
+
function bahttext (input) {
|
|
42
|
+
// Early invalidation – keep behaviour expected by test suite
|
|
43
|
+
if (input === null || input === undefined ||
|
|
44
|
+
typeof input === 'boolean' || Array.isArray(input) ||
|
|
45
|
+
(typeof input !== 'number' && typeof input !== 'string')) {
|
|
46
|
+
return DEFAULT_RESULT
|
|
35
47
|
}
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
const converted = convertInternal(input)
|
|
50
|
+
return converted === false ? DEFAULT_RESULT : converted
|
|
51
|
+
}
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
/**
|
|
54
|
+
* @private
|
|
55
|
+
* @param {string} numStr – e.g. "123" (no sign, may include leading zeros)
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function numberToWords (numStr) {
|
|
59
|
+
// Short-circuit trivial "0"
|
|
60
|
+
if (numStr === '0') return ''
|
|
42
61
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const overflowStr = numStr.slice(0, overflowIndex)
|
|
46
|
-
const remainingStr = numStr.slice(overflowIndex)
|
|
47
|
-
return bahtxtNum2Word(overflowStr) + 'ล้าน' + bahtxtNum2Word(remainingStr)
|
|
48
|
-
}
|
|
62
|
+
let output = EMPTY
|
|
63
|
+
const len = numStr.length
|
|
49
64
|
|
|
50
|
-
let result = ''
|
|
51
65
|
for (let i = 0; i < len; i++) {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
const d = numStr[i]
|
|
67
|
+
const di = len - i - 1 // distance from rightmost digit
|
|
68
|
+
const diMod = di % 6 // position inside a 6-digit group
|
|
69
|
+
|
|
70
|
+
if (d === '0') {
|
|
71
|
+
// skip zero – no wording
|
|
72
|
+
} else if (diMod === 1) {
|
|
73
|
+
// tens column inside group
|
|
74
|
+
if (d === '1') {
|
|
75
|
+
// "สิบ"
|
|
76
|
+
output += SIB
|
|
77
|
+
} else if (d === '2') {
|
|
78
|
+
// "ยี่สิบ"
|
|
79
|
+
output += YEE_SIB
|
|
80
|
+
} else {
|
|
81
|
+
const digitCode = d.charCodeAt(0) - 48
|
|
82
|
+
output += SUB_TEN[digitCode] + DIGIT[diMod]
|
|
83
|
+
}
|
|
84
|
+
} else if (diMod === 0 && d === '1' && i !== 0) {
|
|
85
|
+
// trailing 1 (in ones place, not first digit in overall number) → "เอ็ด"
|
|
86
|
+
// BUT: if it's a stand-alone digit in hundreds place (like 201), use "หนึ่ง"
|
|
87
|
+
const isStandAloneInHundreds = (di === 0 && len > 1 && numStr[i - 1] === '0')
|
|
88
|
+
output += isStandAloneInHundreds ? ONE : ED
|
|
89
|
+
} else {
|
|
90
|
+
const digitCode = d.charCodeAt(0) - 48 // faster than Number(d)
|
|
91
|
+
output += SUB_TEN[digitCode] + DIGIT[diMod]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// add "ล้าน" after finishing each 6-digit block (except last group)
|
|
95
|
+
if (diMod === 0 && di) {
|
|
96
|
+
output += LAN
|
|
57
97
|
}
|
|
58
98
|
}
|
|
59
99
|
|
|
60
|
-
return
|
|
100
|
+
return output
|
|
61
101
|
}
|
|
62
102
|
|
|
63
103
|
/**
|
|
64
104
|
* @private
|
|
65
|
-
*
|
|
66
|
-
* @param {
|
|
67
|
-
* @returns {string}
|
|
105
|
+
* Handle numeric input and extract baht/satang values
|
|
106
|
+
* @param {number} input - numeric input (positive or negative)
|
|
107
|
+
* @returns {{baht: number, bahtStr: string, satang: number, isNegative: boolean} | false}
|
|
68
108
|
*/
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
109
|
+
function handleNumericInput (input) {
|
|
110
|
+
if (!Number.isFinite(input)) return false
|
|
111
|
+
if (input < Number.MIN_SAFE_INTEGER || input > Number.MAX_SAFE_INTEGER) {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const isNegative = input < 0
|
|
116
|
+
if (isNegative) {
|
|
117
|
+
input = -input
|
|
72
118
|
}
|
|
73
|
-
|
|
119
|
+
|
|
120
|
+
const baht = Math.floor(input)
|
|
121
|
+
const satang = Number.isInteger(input)
|
|
122
|
+
? 0
|
|
123
|
+
: Math.floor(((input + Number.EPSILON * (baht || 1)) * 100) % 100)
|
|
124
|
+
const bahtStr = baht.toString() // slightly faster than String(baht)
|
|
125
|
+
|
|
126
|
+
return { baht, bahtStr, satang, isNegative }
|
|
74
127
|
}
|
|
75
128
|
|
|
76
129
|
/**
|
|
77
|
-
* bahtxtCombine baht and satang
|
|
78
|
-
* and also adding unit
|
|
79
|
-
*
|
|
80
130
|
* @private
|
|
81
|
-
*
|
|
82
|
-
* @param {string}
|
|
83
|
-
* @returns {string}
|
|
131
|
+
* Handle string input and extract baht/satang values
|
|
132
|
+
* @param {string} input - string input (numeric string, positive or negative)
|
|
133
|
+
* @returns {{baht: number, bahtStr: string, satang: number, isNegative: boolean} | false}
|
|
84
134
|
*/
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return satang + 'สตางค์'
|
|
135
|
+
function handleStringInput (input) {
|
|
136
|
+
let formatted = input.trim()
|
|
137
|
+
|
|
138
|
+
if (formatted.startsWith('-')) {
|
|
139
|
+
formatted = formatted.replace(/^-0+/, '-') // keep sign, drop leading zeros
|
|
140
|
+
if (formatted === '-') formatted = '0' // catch "-" or "-0"
|
|
92
141
|
} else {
|
|
93
|
-
|
|
142
|
+
formatted = formatted.replace(/^0+/, '') || '0' // combine with empty check
|
|
94
143
|
}
|
|
144
|
+
|
|
145
|
+
// Not a valid number?
|
|
146
|
+
if (formatted === '') formatted = '0'
|
|
147
|
+
let inputNum = Number(formatted)
|
|
148
|
+
if (Number.isNaN(inputNum)) return false
|
|
149
|
+
|
|
150
|
+
const isNegative = inputNum < 0
|
|
151
|
+
if (isNegative) {
|
|
152
|
+
inputNum = -inputNum
|
|
153
|
+
// remove sign for string slicing later
|
|
154
|
+
formatted = formatted.slice(1)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Safe-range check after absolute value
|
|
158
|
+
if (inputNum < Number.MIN_SAFE_INTEGER || inputNum > Number.MAX_SAFE_INTEGER) {
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let baht, bahtStr, satang, satangStr
|
|
163
|
+
|
|
164
|
+
// Handle decimal part by string slicing to avoid FP errors
|
|
165
|
+
const periodIdx = formatted.lastIndexOf('.')
|
|
166
|
+
if (periodIdx !== -1) {
|
|
167
|
+
bahtStr = formatted.slice(0, periodIdx) || '0'
|
|
168
|
+
baht = Number(bahtStr)
|
|
169
|
+
|
|
170
|
+
satangStr = formatted.slice(periodIdx + 1)
|
|
171
|
+
satang = satangStr
|
|
172
|
+
? Number(satangStr.slice(0, 2)) * (satangStr.length >= 2 ? 1 : [100, 10][satangStr.length])
|
|
173
|
+
: 0
|
|
174
|
+
} else {
|
|
175
|
+
baht = inputNum
|
|
176
|
+
bahtStr = formatted
|
|
177
|
+
satang = 0
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { baht, bahtStr, satang, isNegative }
|
|
95
181
|
}
|
|
96
182
|
|
|
97
183
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* @
|
|
101
|
-
* @param {number}
|
|
102
|
-
* @returns {string}
|
|
184
|
+
* @private
|
|
185
|
+
* Format satang portion of the output
|
|
186
|
+
* @param {number} baht - baht amount
|
|
187
|
+
* @param {number} satang - satang amount
|
|
188
|
+
* @returns {string} formatted satang string
|
|
103
189
|
*/
|
|
104
|
-
function
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return
|
|
190
|
+
function formatSatang (baht, satang) {
|
|
191
|
+
if (satang) {
|
|
192
|
+
let output = ''
|
|
193
|
+
if (baht) output += 'บาท'
|
|
194
|
+
output += SUB_HUNDRED[satang] + 'สตางค์'
|
|
195
|
+
return output
|
|
196
|
+
} else {
|
|
197
|
+
return 'บาทถ้วน'
|
|
112
198
|
}
|
|
199
|
+
}
|
|
113
200
|
|
|
114
|
-
|
|
115
|
-
|
|
201
|
+
/**
|
|
202
|
+
* @private
|
|
203
|
+
* Returns false when input cannot be parsed as numeric.
|
|
204
|
+
*/
|
|
205
|
+
function convertInternal (input) {
|
|
206
|
+
let baht, bahtStr, satang
|
|
207
|
+
let isNegative = false
|
|
116
208
|
|
|
117
|
-
|
|
209
|
+
if (typeof input === 'number') {
|
|
210
|
+
// Numeric input handling
|
|
211
|
+
const result = handleNumericInput(input)
|
|
212
|
+
if (result === false) return false
|
|
213
|
+
|
|
214
|
+
baht = result.baht
|
|
215
|
+
bahtStr = result.bahtStr
|
|
216
|
+
satang = result.satang
|
|
217
|
+
isNegative = result.isNegative
|
|
218
|
+
} else if (typeof input === 'string') {
|
|
219
|
+
// String input handling
|
|
220
|
+
const result = handleStringInput(input)
|
|
221
|
+
if (result === false) return false
|
|
222
|
+
|
|
223
|
+
baht = result.baht
|
|
224
|
+
bahtStr = result.bahtStr
|
|
225
|
+
satang = result.satang
|
|
226
|
+
isNegative = result.isNegative
|
|
227
|
+
} else {
|
|
228
|
+
// unreachable due to early guard, but for safety
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
118
231
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
232
|
+
// No value at all → zero
|
|
233
|
+
if (!baht && !satang) {
|
|
234
|
+
return DEFAULT_RESULT
|
|
235
|
+
}
|
|
122
236
|
|
|
123
|
-
//
|
|
124
|
-
let
|
|
125
|
-
let satang = bahtxtNum2Word(satangStr)
|
|
237
|
+
// Build output
|
|
238
|
+
let output = isNegative ? 'ลบ' : EMPTY
|
|
126
239
|
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
240
|
+
// Baht part – using fast converter
|
|
241
|
+
const bahtWords = numberToWords(bahtStr)
|
|
242
|
+
output += bahtWords
|
|
130
243
|
|
|
131
|
-
//
|
|
132
|
-
|
|
244
|
+
// Satang
|
|
245
|
+
output += formatSatang(baht, satang)
|
|
133
246
|
|
|
134
|
-
return
|
|
247
|
+
return output
|
|
135
248
|
}
|
|
136
249
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
bahtxtNum2Word,
|
|
144
|
-
bahtxtGrammarFix,
|
|
145
|
-
bahtxtCombine
|
|
146
|
-
}
|
|
147
|
-
exports.default = {
|
|
148
|
-
bahttext
|
|
149
|
-
}
|
|
250
|
+
module.exports = {
|
|
251
|
+
bahttext,
|
|
252
|
+
// Export private functions for testing
|
|
253
|
+
handleNumericInput,
|
|
254
|
+
handleStringInput,
|
|
255
|
+
formatSatang
|
|
150
256
|
}
|
|
257
|
+
|
|
258
|
+
exports.default = { bahttext }
|