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_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**: Enter countdown minutes to start timer; once it reaches zero, it keeps counting with a negative sign (such as `-00:00:05`).
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
- - Enter minutes to start the countdown.
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.44",
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",
@@ -62,17 +62,24 @@ function fitText(text, width) {
62
62
 
63
63
  function renderClockBlock() {
64
64
  const { timeText, dateText } = getNowDisplay(getLocale());
65
- const innerWidth = 34;
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(i18next.t('clock.title')),
78
+ line(contentLines[0]),
72
79
  line(''),
73
- line(`${i18next.t('clock.now')}${timeText}`),
74
- line(dateText),
75
- line(i18next.t('clock.exitTip')),
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 parseMinutes(input) {
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 innerWidth = 34;
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(i18next.t('countdown.title')),
79
+ line(contentLines[0]),
72
80
  line(''),
73
- line(`${i18next.t('countdown.remaining')}${formatSeconds(remainSeconds)}`),
74
- line(`${i18next.t('countdown.total')}${formatSeconds(totalSeconds)}`),
75
- line(`${i18next.t('countdown.status')}${status}`),
76
- line(i18next.t('countdown.exitTip')),
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 { minutesInput } = await inquirer.prompt([
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: 'minutesInput',
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 = parseMinutes(input);
120
+ const value = parseAmount(input);
90
121
  if (value === null) {
91
- return i18next.t('countdown.invalidMinutes');
122
+ return i18next.t('countdown.invalidAmount');
92
123
  }
93
124
  return true;
94
125
  }
95
126
  }
96
127
  ]);
97
128
 
98
- const minutes = parseMinutes(minutesInput);
99
- const totalSeconds = Math.round(minutes * 60);
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 innerWidth = 34;
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(i18next.t('stopwatch.title')),
64
+ line(contentLines[0]),
58
65
  line(''),
59
- line(`${i18next.t('stopwatch.elapsed')}${formatSeconds(elapsedSeconds)}`),
60
- line(`${i18next.t('stopwatch.status')}${i18next.t('stopwatch.statusRunning')}`),
61
- line(i18next.t('stopwatch.exitTip')),
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
- inputPrompt: 'Enter countdown minutes:',
146
- invalidMinutes: 'Please enter a number greater than or equal to 0',
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
- inputPrompt: '请输入倒计时分钟数:',
146
- invalidMinutes: '请输入大于等于 0 的数字分钟数',
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: '从剪贴板读取',