xw-devtool-cli 1.0.15 → 1.0.17
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 +25 -1
- package/package.json +2 -1
- package/src/commands/url.js +7 -6
- package/src/commands/vscodeSnippet.js +95 -0
- package/src/i18n.js +46 -0
- package/src/index.js +102 -39
- package/src/locales/en.js +46 -0
- package/src/locales/zh.js +46 -0
- package/src/utils/config.js +29 -0
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# xw-devtool-cli
|
|
2
2
|
|
|
3
|
+
**中文** | [English Documentation](./README_EN.md)
|
|
4
|
+
|
|
3
5
|
一个基于 Node.js 的开发者命令行工具箱,旨在提供开箱即用的常用开发工具,帮助开发者快速处理日常任务。
|
|
4
6
|
|
|
5
|
-
主要功能包括:Base64 编解码、图片格式转换、图片与 Base64 互转、Mock 数据生成、时间戳/日期格式化、时间计算、URL 编解码、UUID 生成、汉字转拼音、颜色转换、变量格式转换、哈希计算、二维码生成、特殊符号大全、Markdown
|
|
7
|
+
主要功能包括:Base64 编解码、图片格式转换、图片与 Base64 互转、Mock 数据生成、时间戳/日期格式化、时间计算、URL 编解码、UUID 生成、汉字转拼音、颜色转换、变量格式转换、哈希计算、二维码生成、特殊符号大全、Markdown 语法工具、VS Code 代码段生成等。所有结果均自动复制到剪贴板,极大提升开发效率。
|
|
6
8
|
|
|
7
9
|
## ✨ 功能特性
|
|
8
10
|
|
|
@@ -31,12 +33,16 @@
|
|
|
31
33
|
- **Emoji 输入**:支持分类查看和选择常用 Emoji,一键复制到剪贴板。
|
|
32
34
|
- **HTML 实体工具**:支持 HTML 实体编码与解码 (如 `&` <-> `&`)。
|
|
33
35
|
- **Markdown 语法工具**:提供常用 Markdown 语法模板 (Headers, Lists, Tables, Code 等),一键复制。
|
|
36
|
+
- **VS Code 代码段生成器**:将代码转换为 VS Code Snippet JSON,自动生成语法速查表注释。
|
|
34
37
|
- **占位图生成**:自定义尺寸、背景色、文字颜色和格式生成占位图。
|
|
35
38
|
- **便捷操作**:
|
|
36
39
|
- 支持文件选择对话框 (Windows)。
|
|
37
40
|
- 结果自动复制到剪贴板。
|
|
38
41
|
- 支持从剪贴板自动读取输入(部分工具支持直接回车读取)。
|
|
39
42
|
- 支持将大文本结果保存为文件。
|
|
43
|
+
- **多语言支持 (i18n)**:
|
|
44
|
+
- 支持 **中文** 和 **English**。
|
|
45
|
+
- 可通过菜单中的 `Settings` 切换,或使用命令行参数:`xw-devtool --zh` / `xw-devtool --en`。
|
|
40
46
|
|
|
41
47
|
## 📦 安装
|
|
42
48
|
|
|
@@ -58,6 +64,12 @@ npx xw-devtool-cli@latest
|
|
|
58
64
|
xw-devtool
|
|
59
65
|
```
|
|
60
66
|
|
|
67
|
+
或者指定语言启动:
|
|
68
|
+
```bash
|
|
69
|
+
xw-devtool --zh # 中文启动
|
|
70
|
+
xw-devtool --en # 英文启动 (Start in English)
|
|
71
|
+
```
|
|
72
|
+
|
|
61
73
|
启动后将显示交互式菜单,通过键盘输入对应的数字或字母选择功能:
|
|
62
74
|
|
|
63
75
|
```text
|
|
@@ -84,6 +96,8 @@ h. Mock Text
|
|
|
84
96
|
i. Special Characters (Symbols)
|
|
85
97
|
j. Emoji Picker
|
|
86
98
|
k. Markdown Snippets
|
|
99
|
+
l. VS Code Snippet Generator
|
|
100
|
+
s. Settings (Language)
|
|
87
101
|
0. Exit
|
|
88
102
|
=================================
|
|
89
103
|
```
|
|
@@ -245,6 +259,16 @@ k. Markdown Snippets
|
|
|
245
259
|
- **Others**: 引用、分割线、折叠详情等。
|
|
246
260
|
- 选择对应语法后,模板将自动复制到剪贴板。
|
|
247
261
|
|
|
262
|
+
### 21. VS Code 代码段生成器 (VS Code Snippet Generator)
|
|
263
|
+
- 选择 `l` 进入。
|
|
264
|
+
- **Generate Snippet**: 将任意代码转换为 VS Code 的 `.code-snippets` JSON 格式。
|
|
265
|
+
- 支持从 **剪贴板**、**文件** 或 **手动输入** 获取代码。
|
|
266
|
+
- 自动转义双引号和换行符,生成 `body` 数组。
|
|
267
|
+
- 输入 Snippet Name, Prefix (Trigger), Description。
|
|
268
|
+
- 结果自动复制到剪贴板,可直接粘贴到 VS Code 的 Snippet 配置文件中。
|
|
269
|
+
- **注意**:为防止长文本刷屏,结果**仅自动复制到剪贴板**,不会在终端打印。
|
|
270
|
+
- **提示**:生成的代码段会自动包含语法速查表 (Syntax Cheatsheet) 作为注释,方便参考。
|
|
271
|
+
|
|
248
272
|
## 📄 License
|
|
249
273
|
|
|
250
274
|
ISC
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xw-devtool-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "基于node的开发者助手cli",
|
|
6
6
|
"main": "index.js",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"commander": "^14.0.2",
|
|
68
68
|
"dayjs": "^1.11.19",
|
|
69
69
|
"he": "^1.2.0",
|
|
70
|
+
"i18next": "^25.7.3",
|
|
70
71
|
"inquirer": "^13.1.0",
|
|
71
72
|
"lorem-ipsum": "^2.0.8",
|
|
72
73
|
"pinyin": "^4.0.0",
|
package/src/commands/url.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
2
|
import { copy } from '../utils/clipboard.js';
|
|
3
3
|
import { selectFromMenu } from '../utils/menu.js';
|
|
4
|
+
import i18next from '../i18n.js';
|
|
4
5
|
|
|
5
6
|
export async function urlHandler() {
|
|
6
|
-
const mode = await selectFromMenu('
|
|
7
|
-
{ name: '
|
|
8
|
-
{ name: '
|
|
7
|
+
const mode = await selectFromMenu(i18next.t('url.title'), [
|
|
8
|
+
{ name: i18next.t('url.encode'), value: 'encode' },
|
|
9
|
+
{ name: i18next.t('url.decode'), value: 'decode' }
|
|
9
10
|
]);
|
|
10
11
|
|
|
11
12
|
const { input } = await inquirer.prompt([
|
|
12
13
|
{
|
|
13
14
|
type: 'input',
|
|
14
15
|
name: 'input',
|
|
15
|
-
message:
|
|
16
|
+
message: i18next.t('url.inputPrompt', { mode: mode === 'encode' ? i18next.t('url.encode') : i18next.t('url.decode') })
|
|
16
17
|
}
|
|
17
18
|
]);
|
|
18
19
|
|
|
@@ -23,9 +24,9 @@ export async function urlHandler() {
|
|
|
23
24
|
} else {
|
|
24
25
|
result = decodeURIComponent(input);
|
|
25
26
|
}
|
|
26
|
-
console.log(`\
|
|
27
|
+
console.log(`\n${i18next.t('url.result')}\n${result}\n`);
|
|
27
28
|
await copy(result);
|
|
28
29
|
} catch (e) {
|
|
29
|
-
console.error('
|
|
30
|
+
console.error(i18next.t('url.error'), e.message);
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { copy } from '../utils/clipboard.js';
|
|
3
|
+
import { selectFromMenu } from '../utils/menu.js';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
export async function vscodeSnippetHandler() {
|
|
7
|
+
await generateSnippet();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function generateSnippet() {
|
|
11
|
+
const inputSource = await selectFromMenu('VS Code Snippet Generator - Select Code Source', [
|
|
12
|
+
{ name: 'Clipboard (Paste automatically)', value: 'clipboard' },
|
|
13
|
+
{ name: 'Manual Input', value: 'manual' },
|
|
14
|
+
{ name: 'File', value: 'file' }
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
let code = '';
|
|
18
|
+
if (inputSource === 'clipboard') {
|
|
19
|
+
const clipboardy = (await import('clipboardy')).default;
|
|
20
|
+
code = await clipboardy.read();
|
|
21
|
+
} else if (inputSource === 'file') {
|
|
22
|
+
const { filePath } = await inquirer.prompt([
|
|
23
|
+
{
|
|
24
|
+
type: 'input',
|
|
25
|
+
name: 'filePath',
|
|
26
|
+
message: 'Enter file path:'
|
|
27
|
+
}
|
|
28
|
+
]);
|
|
29
|
+
try {
|
|
30
|
+
code = fs.readFileSync(filePath.replace(/"/g, ''), 'utf-8');
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error('Error reading file:', e.message);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
const { manualInput } = await inquirer.prompt([
|
|
37
|
+
{
|
|
38
|
+
type: 'editor',
|
|
39
|
+
name: 'manualInput',
|
|
40
|
+
message: 'Enter/Paste your code (Close editor to finish):'
|
|
41
|
+
}
|
|
42
|
+
]);
|
|
43
|
+
code = manualInput;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!code) {
|
|
47
|
+
console.log('No code provided.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const answers = await inquirer.prompt([
|
|
52
|
+
{
|
|
53
|
+
type: 'input',
|
|
54
|
+
name: 'name',
|
|
55
|
+
message: 'Snippet Name (e.g. "React Component"):',
|
|
56
|
+
validate: input => input ? true : 'Name is required'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: 'input',
|
|
60
|
+
name: 'prefix',
|
|
61
|
+
message: 'Snippet Prefix (Trigger) (e.g. "rfc"):',
|
|
62
|
+
validate: input => input ? true : 'Prefix is required'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: 'input',
|
|
66
|
+
name: 'description',
|
|
67
|
+
message: 'Description (optional):'
|
|
68
|
+
}
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// Convert code to array of lines
|
|
72
|
+
const lines = code.split(/\r?\n/);
|
|
73
|
+
|
|
74
|
+
// We construct the object
|
|
75
|
+
const snippetObj = {
|
|
76
|
+
[answers.name]: {
|
|
77
|
+
prefix: answers.prefix,
|
|
78
|
+
body: lines,
|
|
79
|
+
description: answers.description || answers.name
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const jsonOutput = JSON.stringify(snippetObj, null, 4);
|
|
84
|
+
|
|
85
|
+
// Add syntax cheatsheet as comments
|
|
86
|
+
const finalOutput = `// VS Code Snippet Syntax Help:
|
|
87
|
+
// 1. Tabstops: $1, $2... $0 (final cursor position)
|
|
88
|
+
// 2. Placeholders: \${1:default value}
|
|
89
|
+
// 3. Choices: \${1|one,two,three|}
|
|
90
|
+
// 4. Variables: $TM_FILENAME, $CURRENT_YEAR, $CLIPBOARD...
|
|
91
|
+
|
|
92
|
+
${jsonOutput}`;
|
|
93
|
+
|
|
94
|
+
await copy(finalOutput);
|
|
95
|
+
}
|
package/src/i18n.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import en from './locales/en.js';
|
|
3
|
+
import zh from './locales/zh.js';
|
|
4
|
+
import { loadConfig, saveConfig } from './utils/config.js';
|
|
5
|
+
|
|
6
|
+
const config = loadConfig();
|
|
7
|
+
|
|
8
|
+
let systemLocale = 'en';
|
|
9
|
+
try {
|
|
10
|
+
systemLocale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
11
|
+
} catch (e) {}
|
|
12
|
+
|
|
13
|
+
let language = config.language || systemLocale;
|
|
14
|
+
|
|
15
|
+
// Check command line arguments for language override
|
|
16
|
+
// Note: We check this before commander parses arguments because i18n is initialized at top-level
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
let newLang = null;
|
|
19
|
+
if (args.includes('--zh')) {
|
|
20
|
+
newLang = 'zh';
|
|
21
|
+
} else if (args.includes('--en')) {
|
|
22
|
+
newLang = 'en';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (newLang) {
|
|
26
|
+
language = newLang;
|
|
27
|
+
// Persist the language setting if changed via CLI
|
|
28
|
+
saveConfig({ language: newLang });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (language.toLowerCase().startsWith('zh')) {
|
|
32
|
+
language = 'zh';
|
|
33
|
+
} else {
|
|
34
|
+
language = 'en';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await i18next.init({
|
|
38
|
+
lng: language,
|
|
39
|
+
fallbackLng: 'en',
|
|
40
|
+
resources: {
|
|
41
|
+
en: { translation: en },
|
|
42
|
+
zh: { translation: zh }
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export default i18next;
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
|
-
import {
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import i18next from './i18n.js';
|
|
4
|
+
import { saveConfig } from './utils/config.js';
|
|
5
|
+
|
|
3
6
|
import { urlHandler } from './commands/url.js';
|
|
4
7
|
import { base64Handler } from './commands/base64.js';
|
|
5
8
|
import { unicodeHandler } from './commands/unicode.js';
|
|
@@ -20,15 +23,16 @@ import { emojiHandler } from './commands/emoji.js';
|
|
|
20
23
|
import { htmlEntitiesHandler } from './commands/htmlEntities.js';
|
|
21
24
|
import { placeholderImgHandler } from './commands/placeholderImg.js';
|
|
22
25
|
import { markdownHandler } from './commands/markdown.js';
|
|
26
|
+
import { vscodeSnippetHandler } from './commands/vscodeSnippet.js';
|
|
23
27
|
|
|
24
28
|
process.on('SIGINT', () => {
|
|
25
|
-
console.log('
|
|
29
|
+
console.log(`\n${i18next.t('menu.bye')}`);
|
|
26
30
|
process.exit(0);
|
|
27
31
|
});
|
|
28
32
|
process.on('unhandledRejection', (err) => {
|
|
29
33
|
const msg = (err && err.message) ? err.message : String(err);
|
|
30
34
|
if (msg.includes('SIGINT') || (err && err.name === 'ExitPromptError')) {
|
|
31
|
-
console.log('
|
|
35
|
+
console.log(`\n${i18next.t('menu.bye')}`);
|
|
32
36
|
process.exit(0);
|
|
33
37
|
} else {
|
|
34
38
|
console.error('Unhandled error:', err);
|
|
@@ -36,40 +40,50 @@ process.on('unhandledRejection', (err) => {
|
|
|
36
40
|
}
|
|
37
41
|
});
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
43
|
+
function getFeatures() {
|
|
44
|
+
return [
|
|
45
|
+
// Image Tools
|
|
46
|
+
{ name: i18next.t('menu.features.imgBase64'), value: 'imgBase64' },
|
|
47
|
+
{ name: i18next.t('menu.features.imgConvert'), value: 'imgConvert' },
|
|
48
|
+
{ name: i18next.t('menu.features.placeholderImg'), value: 'placeholderImg' },
|
|
49
|
+
{ name: i18next.t('menu.features.qrcode'), value: 'qrcode' },
|
|
50
|
+
|
|
51
|
+
// Encode/Decode & Formatting
|
|
52
|
+
{ name: i18next.t('menu.features.url'), value: 'url' },
|
|
53
|
+
{ name: i18next.t('menu.features.base64'), value: 'base64' },
|
|
54
|
+
{ name: i18next.t('menu.features.unicode'), value: 'unicode' },
|
|
55
|
+
{ name: i18next.t('menu.features.htmlEntities'), value: 'htmlEntities' },
|
|
56
|
+
{ name: i18next.t('menu.features.variableFormat'), value: 'variableFormat' },
|
|
57
|
+
{ name: i18next.t('menu.features.jsonFormat'), value: 'jsonFormat' },
|
|
58
|
+
{ name: i18next.t('menu.features.pinyin'), value: 'pinyin' },
|
|
59
|
+
|
|
60
|
+
// Utils (Time, Color, UUID, Hash)
|
|
61
|
+
{ name: i18next.t('menu.features.timeFormat'), value: 'timeFormat' },
|
|
62
|
+
{ name: i18next.t('menu.features.timeCalc'), value: 'timeCalc' },
|
|
63
|
+
{ name: i18next.t('menu.features.color'), value: 'color' },
|
|
64
|
+
{ name: i18next.t('menu.features.uuid'), value: 'uuid' },
|
|
65
|
+
{ name: i18next.t('menu.features.hashing'), value: 'hashing' },
|
|
66
|
+
|
|
67
|
+
// Content/Mocking
|
|
68
|
+
{ name: i18next.t('menu.features.mock'), value: 'mock' },
|
|
69
|
+
{ name: i18next.t('menu.features.specialChars'), value: 'specialChars' },
|
|
70
|
+
{ name: i18next.t('menu.features.emoji'), value: 'emoji' },
|
|
71
|
+
{ name: i18next.t('menu.features.markdown'), value: 'markdown' },
|
|
72
|
+
{ name: i18next.t('menu.features.vscodeSnippet'), value: 'vscodeSnippet' },
|
|
73
|
+
|
|
74
|
+
// Settings
|
|
75
|
+
{ name: i18next.t('menu.features.settings'), value: 'settings' }
|
|
76
|
+
];
|
|
77
|
+
}
|
|
68
78
|
|
|
69
79
|
async function main() {
|
|
80
|
+
const program = new Command();
|
|
81
|
+
|
|
70
82
|
program
|
|
71
83
|
.version('1.0.0')
|
|
72
84
|
.description('Developer Assistant CLI')
|
|
85
|
+
.option('--en', 'Set language to English')
|
|
86
|
+
.option('--zh', 'Set language to Chinese')
|
|
73
87
|
.action(async () => {
|
|
74
88
|
await showMenu();
|
|
75
89
|
});
|
|
@@ -95,20 +109,22 @@ function getFeatureIndex(key) {
|
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
async function showMenu() {
|
|
112
|
+
const features = getFeatures();
|
|
113
|
+
|
|
98
114
|
console.log('\n=================================');
|
|
99
|
-
console.log(
|
|
115
|
+
console.log(` ${i18next.t('menu.title')}`);
|
|
100
116
|
console.log('=================================');
|
|
101
117
|
features.forEach((feature, index) => {
|
|
102
118
|
console.log(`${getFeatureKey(index)}. ${feature.name}`);
|
|
103
119
|
});
|
|
104
|
-
console.log(
|
|
120
|
+
console.log(`0. ${i18next.t('menu.exit')}`);
|
|
105
121
|
console.log('=================================\n');
|
|
106
122
|
|
|
107
123
|
const { choice } = await inquirer.prompt([
|
|
108
124
|
{
|
|
109
125
|
type: 'input',
|
|
110
126
|
name: 'choice',
|
|
111
|
-
message:
|
|
127
|
+
message: i18next.t('menu.prompt'),
|
|
112
128
|
validate: (input) => {
|
|
113
129
|
if (input === '0') return true;
|
|
114
130
|
|
|
@@ -116,13 +132,13 @@ async function showMenu() {
|
|
|
116
132
|
if (index >= 0 && index < features.length) {
|
|
117
133
|
return true;
|
|
118
134
|
}
|
|
119
|
-
return '
|
|
135
|
+
return i18next.t('menu.invalid');
|
|
120
136
|
}
|
|
121
137
|
}
|
|
122
138
|
]);
|
|
123
139
|
|
|
124
140
|
if (choice === '0') {
|
|
125
|
-
console.log('
|
|
141
|
+
console.log(i18next.t('menu.bye'));
|
|
126
142
|
process.exit(0);
|
|
127
143
|
}
|
|
128
144
|
|
|
@@ -135,8 +151,6 @@ async function showMenu() {
|
|
|
135
151
|
console.error('Error:', error.message);
|
|
136
152
|
}
|
|
137
153
|
|
|
138
|
-
// Wait a bit or ask to continue?
|
|
139
|
-
// Usually just looping back is fine.
|
|
140
154
|
await showMenu();
|
|
141
155
|
}
|
|
142
156
|
|
|
@@ -181,6 +195,7 @@ async function handleAction(action) {
|
|
|
181
195
|
case 'jsonFormat':
|
|
182
196
|
await jsonFormatHandler();
|
|
183
197
|
break;
|
|
198
|
+
|
|
184
199
|
case 'hashing':
|
|
185
200
|
await hashingHandler();
|
|
186
201
|
break;
|
|
@@ -202,9 +217,57 @@ async function handleAction(action) {
|
|
|
202
217
|
case 'markdown':
|
|
203
218
|
await markdownHandler();
|
|
204
219
|
break;
|
|
220
|
+
case 'vscodeSnippet':
|
|
221
|
+
await vscodeSnippetHandler();
|
|
222
|
+
break;
|
|
223
|
+
case 'settings':
|
|
224
|
+
await handleSettings();
|
|
225
|
+
break;
|
|
205
226
|
default:
|
|
206
227
|
console.log('Feature not implemented yet.');
|
|
207
228
|
}
|
|
208
229
|
}
|
|
209
230
|
|
|
231
|
+
async function handleSettings() {
|
|
232
|
+
const languages = [
|
|
233
|
+
{ name: 'English', value: 'en' },
|
|
234
|
+
{ name: '中文 (Chinese)', value: 'zh' }
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
console.log(`\n${i18next.t('settings.selectLanguage')}:`);
|
|
238
|
+
languages.forEach((lang, index) => {
|
|
239
|
+
console.log(`${index + 1}. ${lang.name}`);
|
|
240
|
+
});
|
|
241
|
+
console.log(`0. ${i18next.t('settings.backToMenu')}`);
|
|
242
|
+
console.log(''); // Empty line
|
|
243
|
+
|
|
244
|
+
const { choice } = await inquirer.prompt([
|
|
245
|
+
{
|
|
246
|
+
type: 'input',
|
|
247
|
+
name: 'choice',
|
|
248
|
+
message: i18next.t('menu.prompt'),
|
|
249
|
+
validate: (input) => {
|
|
250
|
+
if (input === '0') return true;
|
|
251
|
+
const index = parseInt(input);
|
|
252
|
+
if (index >= 1 && index <= languages.length) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
return 'Invalid selection.';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
]);
|
|
259
|
+
|
|
260
|
+
if (choice === '0') {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const lang = languages[parseInt(choice) - 1].value;
|
|
265
|
+
|
|
266
|
+
saveConfig({ language: lang });
|
|
267
|
+
|
|
268
|
+
await i18next.changeLanguage(lang);
|
|
269
|
+
|
|
270
|
+
console.log(i18next.t('settings.saved'));
|
|
271
|
+
}
|
|
272
|
+
|
|
210
273
|
main();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
menu: {
|
|
3
|
+
title: 'xw-devtool-cli Menu',
|
|
4
|
+
exit: 'Exit',
|
|
5
|
+
prompt: 'Please enter the feature key (0-9, a-z):',
|
|
6
|
+
invalid: 'Invalid selection. Please enter a valid menu key.',
|
|
7
|
+
bye: 'Bye!',
|
|
8
|
+
features: {
|
|
9
|
+
imgBase64: 'Image <-> Base64',
|
|
10
|
+
imgConvert: 'Image Format Convert',
|
|
11
|
+
placeholderImg: 'Placeholder Image Generator',
|
|
12
|
+
qrcode: 'QR Code Generator',
|
|
13
|
+
url: 'URL Encode/Decode',
|
|
14
|
+
base64: 'String Encode/Decode (Base64)',
|
|
15
|
+
unicode: 'Unicode Encode/Decode',
|
|
16
|
+
htmlEntities: 'HTML Entity Encode/Decode',
|
|
17
|
+
variableFormat: 'Variable Format Converter',
|
|
18
|
+
jsonFormat: 'JSON Format (Minify/Prettify)',
|
|
19
|
+
pinyin: 'Chinese to Pinyin',
|
|
20
|
+
timeFormat: 'Time Format / Timestamp',
|
|
21
|
+
timeCalc: 'Time Calculation (Diff/Offset)',
|
|
22
|
+
color: 'Color Converter (Hex <-> RGB)',
|
|
23
|
+
uuid: 'Get UUID',
|
|
24
|
+
hashing: 'Hash Calculator (MD5/SHA/SM3)',
|
|
25
|
+
mock: 'Mock Text',
|
|
26
|
+
specialChars: 'Special Characters (Symbols)',
|
|
27
|
+
emoji: 'Emoji Picker',
|
|
28
|
+
markdown: 'Markdown Snippets',
|
|
29
|
+
vscodeSnippet: 'VS Code Snippet Generator',
|
|
30
|
+
settings: 'Settings / Language'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
settings: {
|
|
34
|
+
selectLanguage: 'Select Language',
|
|
35
|
+
saved: 'Language saved. Please restart the tool or continue using the menu.',
|
|
36
|
+
backToMenu: 'Back to Menu'
|
|
37
|
+
},
|
|
38
|
+
url: {
|
|
39
|
+
title: 'URL Encode/Decode',
|
|
40
|
+
encode: 'Encode',
|
|
41
|
+
decode: 'Decode',
|
|
42
|
+
inputPrompt: 'Enter URL to {{mode}}:',
|
|
43
|
+
result: 'Result:',
|
|
44
|
+
error: 'Error processing URL:'
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
menu: {
|
|
3
|
+
title: 'xw-devtool-cli 菜单',
|
|
4
|
+
exit: '退出',
|
|
5
|
+
prompt: '请输入功能键 (0-9, a-z):',
|
|
6
|
+
invalid: '无效选择,请输入有效的菜单键。',
|
|
7
|
+
bye: '再见!',
|
|
8
|
+
features: {
|
|
9
|
+
imgBase64: '图片 <-> Base64',
|
|
10
|
+
imgConvert: '图片格式转换',
|
|
11
|
+
placeholderImg: '占位图生成器',
|
|
12
|
+
qrcode: '二维码生成器',
|
|
13
|
+
url: 'URL 编码/解码',
|
|
14
|
+
base64: '字符串 编码/解码 (Base64)',
|
|
15
|
+
unicode: 'Unicode 编码/解码',
|
|
16
|
+
htmlEntities: 'HTML 实体 编码/解码',
|
|
17
|
+
variableFormat: '变量命名格式转换',
|
|
18
|
+
jsonFormat: 'JSON 格式化 (压缩/美化)',
|
|
19
|
+
pinyin: '汉字转拼音',
|
|
20
|
+
timeFormat: '时间格式化 / 时间戳',
|
|
21
|
+
timeCalc: '时间计算 (差值/偏移)',
|
|
22
|
+
color: '颜色转换 (Hex <-> RGB)',
|
|
23
|
+
uuid: '生成 UUID',
|
|
24
|
+
hashing: '哈希计算 (MD5/SHA/SM3)',
|
|
25
|
+
mock: 'Mock 文本生成',
|
|
26
|
+
specialChars: '特殊字符 (符号)',
|
|
27
|
+
emoji: 'Emoji 选择器',
|
|
28
|
+
markdown: 'Markdown 片段',
|
|
29
|
+
vscodeSnippet: 'VS Code 代码片段生成',
|
|
30
|
+
settings: '设置 / 语言 (Settings)'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
settings: {
|
|
34
|
+
selectLanguage: '选择语言',
|
|
35
|
+
saved: '语言已保存。请继续使用。',
|
|
36
|
+
backToMenu: '返回主菜单'
|
|
37
|
+
},
|
|
38
|
+
url: {
|
|
39
|
+
title: 'URL 编码/解码',
|
|
40
|
+
encode: '编码',
|
|
41
|
+
decode: '解码',
|
|
42
|
+
inputPrompt: '请输入要{{mode}}的 URL:',
|
|
43
|
+
result: '结果:',
|
|
44
|
+
error: '处理 URL 时出错:'
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const CONFIG_FILE = path.join(os.homedir(), '.xw-devtool-cli-config.json');
|
|
6
|
+
|
|
7
|
+
export function loadConfig() {
|
|
8
|
+
try {
|
|
9
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
10
|
+
const data = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
11
|
+
return JSON.parse(data);
|
|
12
|
+
}
|
|
13
|
+
} catch (e) {
|
|
14
|
+
// Ignore errors
|
|
15
|
+
}
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function saveConfig(config) {
|
|
20
|
+
try {
|
|
21
|
+
const current = loadConfig();
|
|
22
|
+
const newConfig = { ...current, ...config };
|
|
23
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2), 'utf8');
|
|
24
|
+
return true;
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.error('Failed to save config:', e);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|