xw-devtool-cli 1.0.44 → 1.0.46
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/README.md +628 -615
- package/README_EN.md +2 -2
- package/package.json +4 -2
- package/src/commands/clock.js +12 -5
- package/src/commands/countdown.js +51 -15
- package/src/commands/numberUpper.js +236 -0
- package/src/commands/stopwatch.js +12 -5
- package/src/index.js +5 -0
- package/src/locales/en.js +41 -2
- package/src/locales/zh.js +41 -2
package/README_EN.md
CHANGED
|
@@ -30,7 +30,7 @@ Key features include: Base64 encoding/decoding, image format conversion, image <
|
|
|
30
30
|
- **Timestamp**: Quickly get current millisecond timestamp.
|
|
31
31
|
- **Calculation**: Calculate date differences or offsets (Add/Subtract).
|
|
32
32
|
- **Console Clock**: Show real-time current time in terminal, press `Enter` or `Q` to go back.
|
|
33
|
-
- **Countdown Timer**:
|
|
33
|
+
- **Countdown Timer**: Select seconds/minutes/hours first, then enter the value to start; once it reaches zero, it keeps counting with a negative sign (such as `-00:00:05`).
|
|
34
34
|
- **Stopwatch**: Start from `00:00:00` and count up every second in terminal.
|
|
35
35
|
- **Dev Tools**:
|
|
36
36
|
- **URL Encode/Decode**
|
|
@@ -234,7 +234,7 @@ s. Settings (Language)
|
|
|
234
234
|
|
|
235
235
|
### Countdown Timer
|
|
236
236
|
- Select `Countdown Timer` in the menu.
|
|
237
|
-
-
|
|
237
|
+
- Select unit first (Seconds / Minutes / Hours), then enter the value.
|
|
238
238
|
- After reaching zero, the timer continues with a negative sign (for example `-00:00:01`).
|
|
239
239
|
- Press `Enter` or `Q` to go back.
|
|
240
240
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xw-devtool-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.46",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "基于node的开发者助手cli",
|
|
6
6
|
"main": "index.js",
|
|
@@ -73,7 +73,9 @@
|
|
|
73
73
|
"commit-message",
|
|
74
74
|
"conventional-commits",
|
|
75
75
|
"directory-tree",
|
|
76
|
-
"file-tree"
|
|
76
|
+
"file-tree",
|
|
77
|
+
"number-to-chinese-uppercase",
|
|
78
|
+
"rmb-uppercase"
|
|
77
79
|
],
|
|
78
80
|
"author": "npmxw",
|
|
79
81
|
"license": "ISC",
|
package/src/commands/clock.js
CHANGED
|
@@ -62,17 +62,24 @@ function fitText(text, width) {
|
|
|
62
62
|
|
|
63
63
|
function renderClockBlock() {
|
|
64
64
|
const { timeText, dateText } = getNowDisplay(getLocale());
|
|
65
|
-
const
|
|
65
|
+
const contentLines = [
|
|
66
|
+
i18next.t('clock.title'),
|
|
67
|
+
'',
|
|
68
|
+
`${i18next.t('clock.now')}${timeText}`,
|
|
69
|
+
dateText,
|
|
70
|
+
i18next.t('clock.exitTip')
|
|
71
|
+
];
|
|
72
|
+
const innerWidth = Math.max(34, ...contentLines.map((text) => getDisplayWidth(text)));
|
|
66
73
|
const top = `┌${'─'.repeat(innerWidth + 2)}┐`;
|
|
67
74
|
const bottom = `└${'─'.repeat(innerWidth + 2)}┘`;
|
|
68
75
|
const line = (text) => `│ ${fitText(text, innerWidth)} │`;
|
|
69
76
|
return [
|
|
70
77
|
top,
|
|
71
|
-
line(
|
|
78
|
+
line(contentLines[0]),
|
|
72
79
|
line(''),
|
|
73
|
-
line(
|
|
74
|
-
line(
|
|
75
|
-
line(
|
|
80
|
+
line(contentLines[2]),
|
|
81
|
+
line(contentLines[3]),
|
|
82
|
+
line(contentLines[4]),
|
|
76
83
|
bottom
|
|
77
84
|
];
|
|
78
85
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
2
|
import readline from 'readline';
|
|
3
3
|
import i18next from '../i18n.js';
|
|
4
|
+
import { selectFromMenu } from '../utils/menu.js';
|
|
4
5
|
|
|
5
6
|
function getCharDisplayWidth(char) {
|
|
6
7
|
if (/[\u0000-\u001f\u007f]/.test(char)) {
|
|
@@ -50,7 +51,7 @@ function formatSeconds(seconds) {
|
|
|
50
51
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
function
|
|
54
|
+
function parseAmount(input) {
|
|
54
55
|
const value = Number(input);
|
|
55
56
|
if (!Number.isFinite(value) || value < 0) {
|
|
56
57
|
return null;
|
|
@@ -60,43 +61,78 @@ function parseMinutes(input) {
|
|
|
60
61
|
|
|
61
62
|
function renderCountdownBlock(totalSeconds, elapsedSeconds) {
|
|
62
63
|
const remainSeconds = totalSeconds - elapsedSeconds;
|
|
63
|
-
const
|
|
64
|
+
const contentLines = [
|
|
65
|
+
i18next.t('countdown.title'),
|
|
66
|
+
'',
|
|
67
|
+
`${i18next.t('countdown.remaining')}${formatSeconds(remainSeconds)}`,
|
|
68
|
+
`${i18next.t('countdown.total')}${formatSeconds(totalSeconds)}`,
|
|
69
|
+
`${i18next.t('countdown.status')}${remainSeconds < 0 ? i18next.t('countdown.statusOvertime') : i18next.t('countdown.statusCounting')}`,
|
|
70
|
+
i18next.t('countdown.exitTip')
|
|
71
|
+
];
|
|
72
|
+
const innerWidth = Math.max(34, ...contentLines.map((text) => getDisplayWidth(text)));
|
|
64
73
|
const top = `┌${'─'.repeat(innerWidth + 2)}┐`;
|
|
65
74
|
const bottom = `└${'─'.repeat(innerWidth + 2)}┘`;
|
|
66
75
|
const line = (text) => `│ ${fitText(text, innerWidth)} │`;
|
|
67
|
-
const status = remainSeconds < 0 ? i18next.t('countdown.statusOvertime') : i18next.t('countdown.statusCounting');
|
|
68
76
|
|
|
69
77
|
return [
|
|
70
78
|
top,
|
|
71
|
-
line(
|
|
79
|
+
line(contentLines[0]),
|
|
72
80
|
line(''),
|
|
73
|
-
line(
|
|
74
|
-
line(
|
|
75
|
-
line(
|
|
76
|
-
line(
|
|
81
|
+
line(contentLines[2]),
|
|
82
|
+
line(contentLines[3]),
|
|
83
|
+
line(contentLines[4]),
|
|
84
|
+
line(contentLines[5]),
|
|
77
85
|
bottom
|
|
78
86
|
];
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
export async function countdownHandler() {
|
|
82
|
-
const
|
|
90
|
+
const unitOptions = [
|
|
91
|
+
{ name: i18next.t('countdown.unitSecond'), value: 'second' },
|
|
92
|
+
{ name: i18next.t('countdown.unitMinute'), value: 'minute' },
|
|
93
|
+
{ name: i18next.t('countdown.unitHour'), value: 'hour' }
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const unit = await selectFromMenu(
|
|
97
|
+
i18next.t('countdown.selectUnit'),
|
|
98
|
+
unitOptions,
|
|
99
|
+
true,
|
|
100
|
+
i18next.t('common.back')
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (unit === '__BACK__') {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const unitNameMap = {
|
|
108
|
+
second: i18next.t('countdown.unitSecond'),
|
|
109
|
+
minute: i18next.t('countdown.unitMinute'),
|
|
110
|
+
hour: i18next.t('countdown.unitHour')
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const { amountInput } = await inquirer.prompt([
|
|
83
114
|
{
|
|
84
115
|
type: 'input',
|
|
85
|
-
name: '
|
|
86
|
-
message: i18next.t('countdown.inputPrompt'),
|
|
116
|
+
name: 'amountInput',
|
|
117
|
+
message: i18next.t('countdown.inputPrompt', { unit: unitNameMap[unit] }),
|
|
87
118
|
default: '1',
|
|
88
119
|
validate: (input) => {
|
|
89
|
-
const value =
|
|
120
|
+
const value = parseAmount(input);
|
|
90
121
|
if (value === null) {
|
|
91
|
-
return i18next.t('countdown.
|
|
122
|
+
return i18next.t('countdown.invalidAmount');
|
|
92
123
|
}
|
|
93
124
|
return true;
|
|
94
125
|
}
|
|
95
126
|
}
|
|
96
127
|
]);
|
|
97
128
|
|
|
98
|
-
const
|
|
99
|
-
const
|
|
129
|
+
const amount = parseAmount(amountInput);
|
|
130
|
+
const multiplierMap = {
|
|
131
|
+
second: 1,
|
|
132
|
+
minute: 60,
|
|
133
|
+
hour: 3600
|
|
134
|
+
};
|
|
135
|
+
const totalSeconds = Math.round(amount * multiplierMap[unit]);
|
|
100
136
|
|
|
101
137
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
102
138
|
console.log(i18next.t('countdown.nonTtyTip', { value: formatSeconds(totalSeconds) }));
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import clipboardy from 'clipboardy';
|
|
3
|
+
import i18next from '../i18n.js';
|
|
4
|
+
import { read } from '../utils/clipboard.js';
|
|
5
|
+
import { selectFromMenu } from '../utils/menu.js';
|
|
6
|
+
|
|
7
|
+
const UPPER_DIGITS = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
|
|
8
|
+
const SECTION_UNITS = ['', '万', '亿', '兆', '京'];
|
|
9
|
+
const POSITION_UNITS = ['', '拾', '佰', '仟'];
|
|
10
|
+
|
|
11
|
+
function normalizeRawNumber(raw) {
|
|
12
|
+
return raw.replace(/,/g, '').trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isValidNumberText(text) {
|
|
16
|
+
return /^-?\d+(\.\d+)?$/.test(text);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function convertFourDigits(section) {
|
|
20
|
+
const padded = section.padStart(4, '0');
|
|
21
|
+
let result = '';
|
|
22
|
+
let needZero = false;
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < 4; i++) {
|
|
25
|
+
const num = Number(padded[i]);
|
|
26
|
+
const unit = POSITION_UNITS[3 - i];
|
|
27
|
+
if (num === 0) {
|
|
28
|
+
needZero = true;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (needZero && result) {
|
|
32
|
+
result += UPPER_DIGITS[0];
|
|
33
|
+
}
|
|
34
|
+
result += `${UPPER_DIGITS[num]}${unit}`;
|
|
35
|
+
needZero = false;
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function convertIntegerPart(integerText) {
|
|
41
|
+
const normalized = integerText.replace(/^0+/, '') || '0';
|
|
42
|
+
if (normalized === '0') {
|
|
43
|
+
return UPPER_DIGITS[0];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const sections = [];
|
|
47
|
+
for (let i = normalized.length; i > 0; i -= 4) {
|
|
48
|
+
const start = Math.max(0, i - 4);
|
|
49
|
+
sections.unshift(normalized.slice(start, i));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let result = '';
|
|
53
|
+
let previousZeroSection = false;
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < sections.length; i++) {
|
|
56
|
+
const section = sections[i];
|
|
57
|
+
const sectionValue = Number(section);
|
|
58
|
+
const sectionUnit = SECTION_UNITS[sections.length - 1 - i] || '';
|
|
59
|
+
|
|
60
|
+
if (sectionValue === 0) {
|
|
61
|
+
previousZeroSection = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (previousZeroSection && result) {
|
|
66
|
+
result += UPPER_DIGITS[0];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
result += `${convertFourDigits(section)}${sectionUnit}`;
|
|
70
|
+
previousZeroSection = false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result || UPPER_DIGITS[0];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeMoney(numberText) {
|
|
77
|
+
const isNegative = numberText.startsWith('-');
|
|
78
|
+
const absolute = isNegative ? numberText.slice(1) : numberText;
|
|
79
|
+
const [integerPartRaw, decimalPartRaw = ''] = absolute.split('.');
|
|
80
|
+
|
|
81
|
+
let integerPart = integerPartRaw.replace(/^0+/, '') || '0';
|
|
82
|
+
const decimal = decimalPartRaw.padEnd(3, '0').slice(0, 3);
|
|
83
|
+
let jiao = Number(decimal[0]);
|
|
84
|
+
let fen = Number(decimal[1]);
|
|
85
|
+
const third = Number(decimal[2]);
|
|
86
|
+
|
|
87
|
+
if (third >= 5) {
|
|
88
|
+
fen += 1;
|
|
89
|
+
if (fen === 10) {
|
|
90
|
+
fen = 0;
|
|
91
|
+
jiao += 1;
|
|
92
|
+
if (jiao === 10) {
|
|
93
|
+
jiao = 0;
|
|
94
|
+
integerPart = (BigInt(integerPart) + 1n).toString();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
isNegative,
|
|
101
|
+
integerPart,
|
|
102
|
+
jiao,
|
|
103
|
+
fen
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function convertNumberToChineseUpper(rawNumber) {
|
|
108
|
+
const normalized = normalizeRawNumber(rawNumber);
|
|
109
|
+
if (!isValidNumberText(normalized)) {
|
|
110
|
+
throw new Error(i18next.t('numberUpper.invalidNumber'));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const isNegative = normalized.startsWith('-');
|
|
114
|
+
const absolute = isNegative ? normalized.slice(1) : normalized;
|
|
115
|
+
const [integerPart, decimalPart = ''] = absolute.split('.');
|
|
116
|
+
const integerResult = convertIntegerPart(integerPart || '0');
|
|
117
|
+
const decimalResult = decimalPart
|
|
118
|
+
.split('')
|
|
119
|
+
.map((digit) => UPPER_DIGITS[Number(digit)])
|
|
120
|
+
.join('');
|
|
121
|
+
|
|
122
|
+
let result = integerResult;
|
|
123
|
+
if (decimalResult) {
|
|
124
|
+
result = `${result}${i18next.t('numberUpper.dot')}${decimalResult}`;
|
|
125
|
+
}
|
|
126
|
+
if (isNegative) {
|
|
127
|
+
result = `${i18next.t('numberUpper.negative')}${result}`;
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function convertNumberToRmbUpper(rawNumber) {
|
|
133
|
+
const normalized = normalizeRawNumber(rawNumber);
|
|
134
|
+
if (!isValidNumberText(normalized)) {
|
|
135
|
+
throw new Error(i18next.t('numberUpper.invalidNumber'));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const { isNegative, integerPart, jiao, fen } = normalizeMoney(normalized);
|
|
139
|
+
const integerResult = convertIntegerPart(integerPart);
|
|
140
|
+
let result = `${integerResult}${i18next.t('numberUpper.yuan')}`;
|
|
141
|
+
|
|
142
|
+
if (jiao === 0 && fen === 0) {
|
|
143
|
+
result += i18next.t('numberUpper.whole');
|
|
144
|
+
} else if (jiao === 0 && fen > 0) {
|
|
145
|
+
if (integerPart !== '0') {
|
|
146
|
+
result += UPPER_DIGITS[0];
|
|
147
|
+
}
|
|
148
|
+
result += `${UPPER_DIGITS[fen]}${i18next.t('numberUpper.fen')}`;
|
|
149
|
+
} else {
|
|
150
|
+
result += `${UPPER_DIGITS[jiao]}${i18next.t('numberUpper.jiao')}`;
|
|
151
|
+
if (fen > 0) {
|
|
152
|
+
result += `${UPPER_DIGITS[fen]}${i18next.t('numberUpper.fen')}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (isNegative) {
|
|
157
|
+
result = `${i18next.t('numberUpper.negative')}${result}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function resolveInputBySource(source) {
|
|
164
|
+
if (source === 'clipboard') {
|
|
165
|
+
const text = (await read()).trim();
|
|
166
|
+
if (!text) {
|
|
167
|
+
throw new Error(i18next.t('numberUpper.clipboardEmpty'));
|
|
168
|
+
}
|
|
169
|
+
return text;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const answer = await inquirer.prompt([
|
|
173
|
+
{
|
|
174
|
+
type: 'input',
|
|
175
|
+
name: 'number',
|
|
176
|
+
message: i18next.t('numberUpper.enterNumber'),
|
|
177
|
+
validate: (value) => {
|
|
178
|
+
const normalized = normalizeRawNumber(value);
|
|
179
|
+
if (!normalized) {
|
|
180
|
+
return i18next.t('numberUpper.emptyInput');
|
|
181
|
+
}
|
|
182
|
+
return isValidNumberText(normalized) ? true : i18next.t('numberUpper.invalidNumber');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
]);
|
|
186
|
+
return answer.number.trim();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function numberUpperHandler() {
|
|
190
|
+
const mode = await selectFromMenu(
|
|
191
|
+
i18next.t('numberUpper.modeTitle'),
|
|
192
|
+
[
|
|
193
|
+
{ name: i18next.t('numberUpper.modeDigit'), value: 'digit' },
|
|
194
|
+
{ name: i18next.t('numberUpper.modeRmb'), value: 'rmb' }
|
|
195
|
+
],
|
|
196
|
+
true,
|
|
197
|
+
i18next.t('common.back')
|
|
198
|
+
);
|
|
199
|
+
if (mode === '__BACK__') {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const source = await selectFromMenu(
|
|
204
|
+
i18next.t('numberUpper.sourceTitle'),
|
|
205
|
+
[
|
|
206
|
+
{ name: i18next.t('numberUpper.sourceClipboard'), value: 'clipboard' },
|
|
207
|
+
{ name: i18next.t('numberUpper.sourceManual'), value: 'manual' }
|
|
208
|
+
],
|
|
209
|
+
true,
|
|
210
|
+
i18next.t('common.back')
|
|
211
|
+
);
|
|
212
|
+
if (source === '__BACK__') {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const input = await resolveInputBySource(source);
|
|
218
|
+
if (input === '__BACK__') {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const normalized = normalizeRawNumber(input);
|
|
223
|
+
const result = mode === 'rmb'
|
|
224
|
+
? convertNumberToRmbUpper(normalized)
|
|
225
|
+
: convertNumberToChineseUpper(normalized);
|
|
226
|
+
console.log(`\n${i18next.t('numberUpper.resultLabel')} ${result}\n`);
|
|
227
|
+
try {
|
|
228
|
+
await clipboardy.write(result);
|
|
229
|
+
console.log(i18next.t('numberUpper.copied'));
|
|
230
|
+
} catch (copyError) {
|
|
231
|
+
console.error(i18next.t('numberUpper.copyFailed', { message: copyError.message }));
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error(error.message);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -47,18 +47,25 @@ function formatSeconds(seconds) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function renderStopwatchBlock(elapsedSeconds) {
|
|
50
|
-
const
|
|
50
|
+
const contentLines = [
|
|
51
|
+
i18next.t('stopwatch.title'),
|
|
52
|
+
'',
|
|
53
|
+
`${i18next.t('stopwatch.elapsed')}${formatSeconds(elapsedSeconds)}`,
|
|
54
|
+
`${i18next.t('stopwatch.status')}${i18next.t('stopwatch.statusRunning')}`,
|
|
55
|
+
i18next.t('stopwatch.exitTip')
|
|
56
|
+
];
|
|
57
|
+
const innerWidth = Math.max(34, ...contentLines.map((text) => getDisplayWidth(text)));
|
|
51
58
|
const top = `┌${'─'.repeat(innerWidth + 2)}┐`;
|
|
52
59
|
const bottom = `└${'─'.repeat(innerWidth + 2)}┘`;
|
|
53
60
|
const line = (text) => `│ ${fitText(text, innerWidth)} │`;
|
|
54
61
|
|
|
55
62
|
return [
|
|
56
63
|
top,
|
|
57
|
-
line(
|
|
64
|
+
line(contentLines[0]),
|
|
58
65
|
line(''),
|
|
59
|
-
line(
|
|
60
|
-
line(
|
|
61
|
-
line(
|
|
66
|
+
line(contentLines[2]),
|
|
67
|
+
line(contentLines[3]),
|
|
68
|
+
line(contentLines[4]),
|
|
62
69
|
bottom
|
|
63
70
|
];
|
|
64
71
|
}
|
package/src/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { saveConfig } from './utils/config.js';
|
|
|
6
6
|
|
|
7
7
|
import { urlHandler } from './commands/url.js';
|
|
8
8
|
import { baseConvertHandler } from './commands/baseConvert.js';
|
|
9
|
+
import { numberUpperHandler } from './commands/numberUpper.js';
|
|
9
10
|
import { base64Handler } from './commands/base64.js';
|
|
10
11
|
import { unicodeHandler } from './commands/unicode.js';
|
|
11
12
|
import { imgBase64Handler } from './commands/imgBase64.js';
|
|
@@ -77,6 +78,7 @@ function getFeatures() {
|
|
|
77
78
|
// Encode/Decode & Formatting
|
|
78
79
|
{ name: i18next.t('menu.features.url'), value: 'url' },
|
|
79
80
|
{ name: i18next.t('menu.features.baseConvert'), value: 'baseConvert' },
|
|
81
|
+
{ name: i18next.t('menu.features.numberUpper'), value: 'numberUpper' },
|
|
80
82
|
{ name: i18next.t('menu.features.base64'), value: 'base64' },
|
|
81
83
|
{ name: i18next.t('menu.features.unicode'), value: 'unicode' },
|
|
82
84
|
{ name: i18next.t('menu.features.htmlEntities'), value: 'htmlEntities' },
|
|
@@ -277,6 +279,9 @@ async function handleAction(action) {
|
|
|
277
279
|
case 'baseConvert':
|
|
278
280
|
await baseConvertHandler();
|
|
279
281
|
break;
|
|
282
|
+
case 'numberUpper':
|
|
283
|
+
await numberUpperHandler();
|
|
284
|
+
break;
|
|
280
285
|
case 'base64':
|
|
281
286
|
await base64Handler();
|
|
282
287
|
break;
|
package/src/locales/en.js
CHANGED
|
@@ -53,6 +53,7 @@ export default {
|
|
|
53
53
|
qrcodeDecode: 'QR Code Reader',
|
|
54
54
|
url: 'URL Encode/Decode',
|
|
55
55
|
baseConvert: 'Number Base Converter',
|
|
56
|
+
numberUpper: 'Number to Chinese Uppercase',
|
|
56
57
|
base64: 'String Encode/Decode (Base64)',
|
|
57
58
|
unicode: 'Unicode Encode/Decode',
|
|
58
59
|
htmlEntities: 'HTML Entity Encode/Decode',
|
|
@@ -142,8 +143,12 @@ export default {
|
|
|
142
143
|
},
|
|
143
144
|
countdown: {
|
|
144
145
|
title: 'Countdown Timer',
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
selectUnit: 'Select countdown unit',
|
|
147
|
+
unitSecond: 'Seconds',
|
|
148
|
+
unitMinute: 'Minutes',
|
|
149
|
+
unitHour: 'Hours',
|
|
150
|
+
inputPrompt: 'Enter countdown {{unit}} value:',
|
|
151
|
+
invalidAmount: 'Please enter a number greater than or equal to 0',
|
|
147
152
|
remaining: 'Remaining: ',
|
|
148
153
|
total: 'Total: ',
|
|
149
154
|
status: 'Status: ',
|
|
@@ -169,6 +174,40 @@ export default {
|
|
|
169
174
|
error: 'Invalid number for the selected base.',
|
|
170
175
|
copyPrompt: 'Select format to copy'
|
|
171
176
|
},
|
|
177
|
+
numberUpper: {
|
|
178
|
+
modeTitle: 'Select conversion mode',
|
|
179
|
+
modeDigit: 'Number to Chinese uppercase',
|
|
180
|
+
modeRmb: 'Number to RMB uppercase',
|
|
181
|
+
sourceTitle: 'Select input source',
|
|
182
|
+
sourceClipboard: 'Read from clipboard',
|
|
183
|
+
sourceFile: 'Read from local file',
|
|
184
|
+
sourceManual: 'Enter number manually',
|
|
185
|
+
fileInputMethod: 'Select file input method',
|
|
186
|
+
fileInputDialog: 'Select file (dialog)',
|
|
187
|
+
fileInputManual: 'Enter file path manually',
|
|
188
|
+
dialogUnavailable: 'File dialog is unavailable or canceled. Please enter the file path manually.',
|
|
189
|
+
enterFilePath: 'Enter number file path:',
|
|
190
|
+
fileNotFound: 'File does not exist.',
|
|
191
|
+
readFileError: 'Failed to read file: {{message}}',
|
|
192
|
+
clipboardEmpty: 'Clipboard is empty. Please copy the number first.',
|
|
193
|
+
emptyInput: 'Input cannot be empty.',
|
|
194
|
+
enterNumber: 'Enter number (supports negative and decimal):',
|
|
195
|
+
invalidNumber: 'Please enter a valid number (e.g. 123, -45.67)',
|
|
196
|
+
outputMode: 'Select output method',
|
|
197
|
+
outputClipboard: 'Copy to clipboard',
|
|
198
|
+
outputFile: 'Save to file',
|
|
199
|
+
enterOutputPath: 'Enter output file path:',
|
|
200
|
+
outputPathRequired: 'Output file path cannot be empty.',
|
|
201
|
+
negative: 'Negative ',
|
|
202
|
+
dot: ' point ',
|
|
203
|
+
yuan: ' Yuan',
|
|
204
|
+
jiao: ' Jiao',
|
|
205
|
+
fen: ' Fen',
|
|
206
|
+
whole: ' Only',
|
|
207
|
+
resultLabel: 'Result:',
|
|
208
|
+
copied: 'Copied to clipboard',
|
|
209
|
+
copyFailed: 'Failed to copy to clipboard: {{message}}'
|
|
210
|
+
},
|
|
172
211
|
jsonFormat: {
|
|
173
212
|
selectSource: 'Select JSON input source',
|
|
174
213
|
sourceClipboard: 'Read from clipboard',
|
package/src/locales/zh.js
CHANGED
|
@@ -53,6 +53,7 @@ export default {
|
|
|
53
53
|
qrcodeDecode: '二维码识别',
|
|
54
54
|
url: 'URL 编码/解码',
|
|
55
55
|
baseConvert: '进制转换工具',
|
|
56
|
+
numberUpper: '数字转大写',
|
|
56
57
|
base64: '字符串 编码/解码 (Base64)',
|
|
57
58
|
unicode: 'Unicode 编码/解码',
|
|
58
59
|
htmlEntities: 'HTML 实体 编码/解码',
|
|
@@ -142,8 +143,12 @@ export default {
|
|
|
142
143
|
},
|
|
143
144
|
countdown: {
|
|
144
145
|
title: '倒计时工具',
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
selectUnit: '请选择倒计时单位',
|
|
147
|
+
unitSecond: '秒',
|
|
148
|
+
unitMinute: '分钟',
|
|
149
|
+
unitHour: '小时',
|
|
150
|
+
inputPrompt: '请输入倒计时{{unit}}数值:',
|
|
151
|
+
invalidAmount: '请输入大于等于 0 的数字',
|
|
147
152
|
remaining: '剩余时间: ',
|
|
148
153
|
total: '倒计总时: ',
|
|
149
154
|
status: '当前状态: ',
|
|
@@ -169,6 +174,40 @@ export default {
|
|
|
169
174
|
error: '输入的数字对于所选进制无效。',
|
|
170
175
|
copyPrompt: '选择要复制的格式'
|
|
171
176
|
},
|
|
177
|
+
numberUpper: {
|
|
178
|
+
modeTitle: '选择转换模式',
|
|
179
|
+
modeDigit: '数字转中文大写',
|
|
180
|
+
modeRmb: '数字转人民币大写',
|
|
181
|
+
sourceTitle: '选择输入来源',
|
|
182
|
+
sourceClipboard: '从剪贴板读取',
|
|
183
|
+
sourceFile: '从本地文件读取',
|
|
184
|
+
sourceManual: '手动输入数字',
|
|
185
|
+
fileInputMethod: '选择文件读取方式',
|
|
186
|
+
fileInputDialog: '选择文件(对话框)',
|
|
187
|
+
fileInputManual: '手动输入文件路径',
|
|
188
|
+
dialogUnavailable: '文件对话框不可用或已取消,请手动输入文件路径。',
|
|
189
|
+
enterFilePath: '请输入数字文件路径:',
|
|
190
|
+
fileNotFound: '文件不存在。',
|
|
191
|
+
readFileError: '读取文件失败: {{message}}',
|
|
192
|
+
clipboardEmpty: '剪贴板为空,请先复制数字。',
|
|
193
|
+
emptyInput: '输入内容不能为空。',
|
|
194
|
+
enterNumber: '请输入数字(支持负数和小数):',
|
|
195
|
+
invalidNumber: '请输入有效数字(如 123、-45.67)',
|
|
196
|
+
outputMode: '选择输出方式',
|
|
197
|
+
outputClipboard: '复制到剪贴板',
|
|
198
|
+
outputFile: '保存到文件',
|
|
199
|
+
enterOutputPath: '请输入输出文件路径:',
|
|
200
|
+
outputPathRequired: '输出文件路径不能为空。',
|
|
201
|
+
negative: '负',
|
|
202
|
+
dot: '点',
|
|
203
|
+
yuan: '元',
|
|
204
|
+
jiao: '角',
|
|
205
|
+
fen: '分',
|
|
206
|
+
whole: '整',
|
|
207
|
+
resultLabel: '转换结果:',
|
|
208
|
+
copied: '已复制到剪贴板',
|
|
209
|
+
copyFailed: '复制到剪贴板失败: {{message}}'
|
|
210
|
+
},
|
|
172
211
|
jsonFormat: {
|
|
173
212
|
selectSource: '选择 JSON 输入来源',
|
|
174
213
|
sourceClipboard: '从剪贴板读取',
|