td-web-cli 0.1.33 → 0.1.35

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 CHANGED
@@ -47,6 +47,7 @@ td-web-cli
47
47
  - 多语言 JSON 转 Excel 工具。[详细功能说明请点击这里](https://github.com/qianyuzzf/td-web-cli/blob/master/docs/i18n/json2excel.md)
48
48
  - 多语言 Excel 转 JSON 及语言检测工具。[详细功能说明请点击这里](https://github.com/qianyuzzf/td-web-cli/blob/master/docs/i18n/excel2json.md)
49
49
  - 多语言 JSON 文件合并工具。[详细功能说明请点击这里](https://github.com/qianyuzzf/td-web-cli/blob/master/docs/i18n/jsonMerge.md)
50
+ - 多语言 JSON 文件插入工具。[详细功能说明请点击这里](https://github.com/qianyuzzf/td-web-cli/blob/master/docs/i18n/jsonInsert.md)
50
51
  - 图片压缩工具。[详细功能说明请点击这里](https://github.com/qianyuzzf/td-web-cli/blob/master/docs/image/compressImage.md)
51
52
  - 节假日查询与提醒工具。[详细功能说明请点击这里](https://github.com/qianyuzzf/td-web-cli/blob/master/docs/tools/getHolidayTime.md)
52
53
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/i18n/extractEntry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2UpC;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,iBAuLlD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/i18n/extractEntry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4UpC;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,iBA0KlD"}
@@ -4,7 +4,7 @@ import fs from 'fs';
4
4
  import { fileURLToPath } from 'url';
5
5
  import path from 'path';
6
6
  import { minimatch } from 'minimatch';
7
- import { getTimestamp, logger, loggerError, normalizeError, normalizeGitBashPath, } from '../../../utils/index.js';
7
+ import { getTimestamp, logger, loggerError, normalizeError, normalizeGitBashPath, validatePathInput, } from '../../../utils/index.js';
8
8
  // AST 解析相关
9
9
  import babelParser from '@babel/parser';
10
10
  import traverse from '@babel/traverse';
@@ -296,20 +296,7 @@ export async function extractEntry(program) {
296
296
  // 1. 输入项目根目录
297
297
  const answer = await input({
298
298
  message: '请输入项目根目录:',
299
- validate: (value) => {
300
- const cleaned = value.trim().replace(/^['"]|['"]$/g, '');
301
- if (cleaned.length === 0) {
302
- return '路径不能为空';
303
- }
304
- const normalizedPath = normalizeGitBashPath(cleaned);
305
- if (!fs.existsSync(normalizedPath)) {
306
- return '目录不存在,请输入有效路径';
307
- }
308
- if (!fs.statSync(normalizedPath).isDirectory()) {
309
- return '请输入一个目录路径';
310
- }
311
- return true;
312
- },
299
+ validate: validatePathInput,
313
300
  });
314
301
  const rootDir = normalizeGitBashPath(answer);
315
302
  // 2. 交互式配置忽略模式(支持目录和文件,使用 glob 通配符)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/i18n/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,iBAiF1C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/i18n/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,iBA2F1C"}
@@ -4,6 +4,7 @@ import { excel2json } from './excel2json/index.js';
4
4
  import { json2excel } from './json2excel/index.js';
5
5
  import { jsonMerge } from './jsonMerge/index.js';
6
6
  import { extractEntry } from './extractEntry/index.js';
7
+ import { jsonInsert } from './jsonInsert/index.js';
7
8
  /**
8
9
  * 国际化模块主入口
9
10
  * 提供多个国际化相关功能的交互式选择
@@ -34,6 +35,11 @@ export async function i18n(program) {
34
35
  value: 'jsonMerge',
35
36
  description: '合并多个JSON格式的词条信息文件',
36
37
  },
38
+ {
39
+ name: 'JSON插入',
40
+ value: 'jsonInsert',
41
+ description: '插入词条信息到JSON文件中',
42
+ },
37
43
  ];
38
44
  // 交互式选择需要执行的功能
39
45
  const answer = await select({
@@ -74,6 +80,11 @@ export async function i18n(program) {
74
80
  await jsonMerge(program);
75
81
  logger.info(`${selectedModule.name}功能执行完成`);
76
82
  break;
83
+ case 'jsonInsert':
84
+ logger.info(`${selectedModule.name}功能开始执行`);
85
+ await jsonInsert(program);
86
+ logger.info(`${selectedModule.name}功能执行完成`);
87
+ break;
77
88
  default:
78
89
  logger.warn(`${selectedModule.name}功能暂未实现,程序已退出`);
79
90
  process.exit(0);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/i18n/json2excel/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+CpC;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,OAAO,iBAsIhD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/i18n/json2excel/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgDpC;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,OAAO,iBAqIhD"}
@@ -3,7 +3,7 @@ import XLSX from 'xlsx';
3
3
  import fs from 'fs';
4
4
  import { fileURLToPath } from 'url';
5
5
  import path from 'path';
6
- import { getTimestamp, logger, loggerError, normalizeError, normalizeGitBashPath, } from '../../../utils/index.js';
6
+ import { getTimestamp, logger, loggerError, normalizeError, normalizeGitBashPath, validatePathInput, } from '../../../utils/index.js';
7
7
  const __filename = fileURLToPath(import.meta.url);
8
8
  const __dirname = path.dirname(__filename);
9
9
  /**
@@ -53,20 +53,7 @@ export async function json2excel(program) {
53
53
  // 交互式输入 JSON 根目录
54
54
  const answer = await input({
55
55
  message: '请输入存放多语言 JSON 的根目录:',
56
- validate: (value) => {
57
- const cleaned = value.trim().replace(/^['"]|['"]$/g, '');
58
- if (cleaned.length === 0) {
59
- return '路径不能为空';
60
- }
61
- const normalizedPath = normalizeGitBashPath(cleaned);
62
- if (!fs.existsSync(normalizedPath)) {
63
- return '目录不存在,请输入有效路径';
64
- }
65
- if (!fs.statSync(normalizedPath).isDirectory()) {
66
- return '请输入一个目录路径';
67
- }
68
- return true;
69
- },
56
+ validate: validatePathInput,
70
57
  });
71
58
  const rootDir = normalizeGitBashPath(answer);
72
59
  try {
@@ -117,14 +104,14 @@ export async function json2excel(program) {
117
104
  const header = [];
118
105
  // 第一列名称:优先使用默认语言的第一个名称,否则用 defaultKey
119
106
  const defaultLangNames = i18nConfig.langs[defaultLang];
120
- const firstColName = (defaultLangNames && defaultLangNames.length > 0)
107
+ const firstColName = defaultLangNames && defaultLangNames.length > 0
121
108
  ? defaultLangNames[0]
122
109
  : defaultLang;
123
110
  header.push(firstColName);
124
111
  const nonDefaultLangs = availableLangs.filter((lang) => lang !== defaultLang);
125
112
  for (const lang of nonDefaultLangs) {
126
113
  const names = i18nConfig.langs[lang];
127
- const colName = (names && names.length > 0) ? names[0] : lang;
114
+ const colName = names && names.length > 0 ? names[0] : lang;
128
115
  header.push(colName);
129
116
  }
130
117
  // 构建 Excel 数据行
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * 主函数:从插入目录按指定 keys 批量插入到被插入目录
4
+ */
5
+ export declare function jsonInsert(program: Command): Promise<void>;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/i18n/jsonInsert/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0HpC;;GAEG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,OAAO,iBAoHhD"}
@@ -0,0 +1,174 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { input, select, Separator } from '@inquirer/prompts';
4
+ import { logger, loggerError, normalizeGitBashPath, readJsonFile, writeJsonFile, getJsonFilesInLangDir, validatePathInput, } from '../../../utils/index.js';
5
+ /** 获取目录下的一级子文件夹名称 */
6
+ function getSubDirectories(dirPath) {
7
+ return fs.readdirSync(dirPath).filter((name) => fs.statSync(path.join(dirPath, name)).isDirectory());
8
+ }
9
+ /** 校验 JSON 文件路径输入 */
10
+ function validateJsonFileInput(value) {
11
+ const cleaned = value.trim().replace(/^['"]|['"]$/g, '');
12
+ if (!cleaned)
13
+ return '路径不能为空';
14
+ const normalized = normalizeGitBashPath(cleaned);
15
+ if (!fs.existsSync(normalized))
16
+ return '文件不存在,请输入有效路径';
17
+ if (!fs.statSync(normalized).isFile())
18
+ return '请输入 JSON 文件路径';
19
+ if (!normalized.endsWith('.json'))
20
+ return '请输入 .json 文件路径';
21
+ return true;
22
+ }
23
+ /**
24
+ * 从 JSON 文件中提取待插入的 key 列表
25
+ * 支持对象(取顶层 key)或字符串数组两种格式
26
+ */
27
+ function extractKeysFromJsonFile(filePath) {
28
+ const content = fs.readFileSync(filePath, 'utf-8');
29
+ let parsed;
30
+ try {
31
+ parsed = JSON.parse(content);
32
+ }
33
+ catch {
34
+ throw new Error(`JSON 文件解析失败: ${filePath}`);
35
+ }
36
+ if (Array.isArray(parsed)) {
37
+ const keys = parsed
38
+ .filter((item) => typeof item === 'string')
39
+ .map((key) => key.trim())
40
+ .filter((key) => key.length > 0);
41
+ return [...new Set(keys)];
42
+ }
43
+ if (parsed && typeof parsed === 'object') {
44
+ return Object.keys(parsed);
45
+ }
46
+ throw new Error('JSON 文件格式无效,需为对象或字符串数组');
47
+ }
48
+ /**
49
+ * 将指定 keys 从插入对象合并到被插入对象
50
+ */
51
+ async function insertKeysIntoObject(baseObj, insertObj, keys, strategy) {
52
+ for (const key of keys) {
53
+ if (!(key in insertObj)) {
54
+ logger.warn(`键 "${key}" 在插入文件中不存在,已跳过`, true);
55
+ continue;
56
+ }
57
+ const insertVal = insertObj[key];
58
+ if (!(key in baseObj)) {
59
+ baseObj[key] = insertVal;
60
+ logger.info(`新增键: ${key}`, true);
61
+ continue;
62
+ }
63
+ if (baseObj[key] === insertVal) {
64
+ logger.info(`键 "${key}" 值相同,已跳过`, true);
65
+ continue;
66
+ }
67
+ logger.info(`键 "${key}" 已存在且值不同`, true);
68
+ if (strategy === 'overwrite') {
69
+ baseObj[key] = insertVal;
70
+ logger.info(`已用插入值覆盖键 "${key}"`, true);
71
+ continue;
72
+ }
73
+ const choice = await select({
74
+ message: `请选择要保留的值:`,
75
+ choices: [
76
+ { name: `被插入文件值: ${baseObj[key]}`, value: 'base' },
77
+ { name: `插入文件值: ${insertVal}`, value: 'insert' },
78
+ new Separator(),
79
+ ],
80
+ default: 'base',
81
+ loop: true,
82
+ });
83
+ if (choice === 'insert') {
84
+ baseObj[key] = insertVal;
85
+ logger.info(`已用插入值覆盖键 "${key}"`, true);
86
+ }
87
+ else {
88
+ logger.info(`保留被插入文件值,键 "${key}" 未更改`, true);
89
+ }
90
+ }
91
+ return baseObj;
92
+ }
93
+ /**
94
+ * 主函数:从插入目录按指定 keys 批量插入到被插入目录
95
+ */
96
+ export async function jsonInsert(program) {
97
+ try {
98
+ const srcDir = await input({
99
+ message: '请输入被插入 JSON 文件夹路径(含语言子文件夹,如 cn/translate.json):',
100
+ validate: validatePathInput,
101
+ });
102
+ const insertDir = await input({
103
+ message: '请输入待插入 JSON 文件夹路径(含语言子文件夹,如 cn/translate.json):',
104
+ validate: validatePathInput,
105
+ });
106
+ const keysFileInput = await input({
107
+ message: '请输入 key 来源 JSON 文件路径(从中读取需要插入的 key):',
108
+ validate: validateJsonFileInput,
109
+ });
110
+ const conflictStrategy = await select({
111
+ message: '当目标键已存在且值不同时,请选择处理方式:',
112
+ choices: [
113
+ { name: '直接覆盖(始终使用插入文件值)', value: 'overwrite' },
114
+ { name: '手动选择(针对每个冲突键提示)', value: 'manual' },
115
+ new Separator(),
116
+ ],
117
+ default: 'manual',
118
+ loop: true,
119
+ });
120
+ const srcPath = normalizeGitBashPath(srcDir);
121
+ const insertPath = normalizeGitBashPath(insertDir);
122
+ const keysFilePath = normalizeGitBashPath(keysFileInput);
123
+ const keys = extractKeysFromJsonFile(keysFilePath);
124
+ if (keys.length === 0) {
125
+ logger.info('key 来源文件中未找到有效 key,操作取消');
126
+ return;
127
+ }
128
+ logger.info(`被插入目录: ${srcPath}`);
129
+ logger.info(`插入目录: ${insertPath}`);
130
+ logger.info(`key 来源文件: ${keysFilePath}`);
131
+ logger.info(`待插入 key: ${keys.join(', ')}`);
132
+ logger.info(`冲突处理策略: ${conflictStrategy === 'overwrite' ? '直接覆盖' : '手动选择'}`);
133
+ const commonLangDirs = getSubDirectories(srcPath).filter((lang) => getSubDirectories(insertPath).includes(lang));
134
+ if (commonLangDirs.length === 0) {
135
+ logger.info('没有发现相同语言文件夹,操作取消');
136
+ return;
137
+ }
138
+ logger.info(`发现 ${commonLangDirs.length} 个共同语言文件夹: ${commonLangDirs.join(', ')}`, true);
139
+ for (const langKey of commonLangDirs) {
140
+ logger.info(`${'='.repeat(60)}`, true);
141
+ logger.info(`处理语言: ${langKey}`, true);
142
+ const srcLangPath = path.join(srcPath, langKey);
143
+ const insertLangPath = path.join(insertPath, langKey);
144
+ const insertJsonFiles = getJsonFilesInLangDir(insertLangPath);
145
+ const commonJsonFiles = getJsonFilesInLangDir(srcLangPath).filter((file) => insertJsonFiles.includes(file));
146
+ if (commonJsonFiles.length === 0) {
147
+ logger.info(`语言【${langKey}】下没有共同的 JSON 文件,跳过`, true);
148
+ continue;
149
+ }
150
+ logger.info(`发现 ${commonJsonFiles.length} 个共同 JSON 文件: ${commonJsonFiles.join(', ')}`, true);
151
+ for (const jsonFile of commonJsonFiles) {
152
+ logger.info(`处理文件: ${jsonFile}`, true);
153
+ const srcFile = path.join(srcLangPath, jsonFile);
154
+ const insertFile = path.join(insertLangPath, jsonFile);
155
+ const srcJson = readJsonFile(srcFile);
156
+ const insertJson = readJsonFile(insertFile);
157
+ logger.info(`被插入文件原有键数: ${Object.keys(srcJson).length}`, true);
158
+ const updated = await insertKeysIntoObject(srcJson, insertJson, keys, conflictStrategy);
159
+ logger.info(`插入后键数: ${Object.keys(updated).length}`, true);
160
+ writeJsonFile(srcFile, updated);
161
+ logger.info(`文件 ${jsonFile} 已更新`, true);
162
+ }
163
+ logger.info(`语言【${langKey}】处理完成`, true);
164
+ }
165
+ logger.info(`${'='.repeat(60)}`, true);
166
+ logger.info(`所有插入操作完成!`, true);
167
+ logger.info(`被插入目录已更新: ${srcPath}`, true);
168
+ }
169
+ catch (error) {
170
+ loggerError(error, logger);
171
+ console.error('程序执行时发生异常,已记录日志,程序已退出');
172
+ process.exit(1);
173
+ }
174
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/i18n/jsonMerge/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsGpC;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,iBA4J/C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/i18n/jsonMerge/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,iBAkI/C"}
@@ -1,87 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { input, select, Separator } from '@inquirer/prompts';
4
- import { logger, loggerError, normalizeError, normalizeGitBashPath, } from '../../../utils/index.js';
5
- /**
6
- * 读取JSON文件内容,返回对象,文件不存在返回空对象
7
- * @param filePath JSON文件路径
8
- * @returns 解析后的对象,出错或不存在返回空对象
9
- */
10
- function readJsonFile(filePath) {
11
- if (!fs.existsSync(filePath)) {
12
- return {};
13
- }
14
- try {
15
- const content = fs.readFileSync(filePath, 'utf-8');
16
- return JSON.parse(content);
17
- }
18
- catch (error) {
19
- logger.error(`读取JSON文件失败: ${filePath},错误: ${normalizeError(error).message}`);
20
- return {};
21
- }
22
- }
23
- /**
24
- * 写入JSON文件,格式化缩进2格
25
- * @param filePath 写入文件路径
26
- * @param data 写入的对象数据
27
- */
28
- function writeJsonFile(filePath, data) {
29
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
30
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
31
- }
32
- /**
33
- * 合并两个JSON对象,检测重复key,重复时通过交互让用户选择保留哪个值
34
- * @param baseObj 源JSON对象(被合并到此对象)
35
- * @param mergeObj 待合并JSON对象
36
- * @param langKey 当前语言标识,用于日志提示
37
- * @returns 合并后的JSON对象
38
- */
39
- async function mergeJsonObjects(baseObj, mergeObj, langKey) {
40
- const entries = Object.entries(mergeObj);
41
- for (const [key, val] of entries) {
42
- if (key in baseObj) {
43
- if (baseObj[key] === val) {
44
- // 值相同,无需处理,继续下一个键
45
- continue;
46
- }
47
- // 值不同,需要用户交互选择保留哪个值
48
- logger.info(`发现冲突: 键【${key}】`, true);
49
- const choice = await select({
50
- message: `请选择要保留的值:`,
51
- choices: [
52
- { name: `源文件值: ${baseObj[key]}`, value: 'base' },
53
- { name: `合并文件值: ${val}`, value: 'merge' },
54
- new Separator(),
55
- ],
56
- default: 'base',
57
- loop: true,
58
- });
59
- if (choice === 'merge') {
60
- baseObj[key] = val;
61
- }
62
- // 如果选择保留base,则保持不变
63
- }
64
- else {
65
- // 新键,直接添加
66
- baseObj[key] = val;
67
- }
68
- }
69
- return baseObj;
70
- }
71
- /**
72
- * 获取指定语言文件夹下的所有JSON文件名列表
73
- * @param dirPath 语言文件夹路径
74
- * @returns JSON文件名数组
75
- */
76
- function getJsonFilesInLangDir(dirPath) {
77
- if (!fs.existsSync(dirPath)) {
78
- return [];
79
- }
80
- return fs.readdirSync(dirPath).filter((fileName) => {
81
- const fullPath = path.join(dirPath, fileName);
82
- return fs.statSync(fullPath).isFile() && fileName.endsWith('.json');
83
- });
84
- }
3
+ import { input } from '@inquirer/prompts';
4
+ import { logger, loggerError, normalizeGitBashPath, readJsonFile, writeJsonFile, mergeJsonObjects, getJsonFilesInLangDir, validatePathInput, } from '../../../utils/index.js';
85
5
  /**
86
6
  * 主函数:合并两个目录下相同语言文件夹的JSON文件
87
7
  * @param program commander命令行实例(暂未使用,可扩展)
@@ -91,32 +11,12 @@ export async function jsonMerge(program) {
91
11
  // 交互输入源目录路径
92
12
  const srcDir = await input({
93
13
  message: '请输入源JSON文件夹路径(含语言子文件夹,如cn/translate.json):',
94
- validate: (value) => {
95
- const cleaned = value.trim().replace(/^['"]|['"]$/g, '');
96
- if (cleaned.length === 0) {
97
- return '路径不能为空';
98
- }
99
- const normalizedPath = normalizeGitBashPath(cleaned);
100
- if (!fs.existsSync(normalizedPath)) {
101
- return '文件不存在,请输入有效路径';
102
- }
103
- return true;
104
- },
14
+ validate: validatePathInput,
105
15
  });
106
16
  // 交互输入待合并目录路径
107
17
  const mergeDir = await input({
108
18
  message: '请输入待合并JSON文件夹路径(含语言子文件夹,如cn/translate.json):',
109
- validate: (value) => {
110
- const cleaned = value.trim().replace(/^['"]|['"]$/g, '');
111
- if (cleaned.length === 0) {
112
- return '路径不能为空';
113
- }
114
- const normalizedPath = normalizeGitBashPath(cleaned);
115
- if (!fs.existsSync(normalizedPath)) {
116
- return '文件不存在,请输入有效路径';
117
- }
118
- return true;
119
- },
19
+ validate: validatePathInput,
120
20
  });
121
21
  const srcPath = normalizeGitBashPath(srcDir);
122
22
  const mergePath = normalizeGitBashPath(mergeDir);
@@ -172,5 +172,37 @@ export declare function normalizeGitBashPath(inputPath: string): string;
172
172
  * @returns 去除引号后的字符串
173
173
  */
174
174
  export declare function trimQuotes(str: string): string;
175
+ /**
176
+ * 读取JSON文件内容,返回对象,文件不存在返回空对象
177
+ * @param filePath JSON文件路径
178
+ * @returns 解析后的对象,出错或不存在返回空对象
179
+ */
180
+ export declare function readJsonFile(filePath: string): Record<string, any>;
181
+ /**
182
+ * 写入JSON文件,格式化缩进2格
183
+ * @param filePath 写入文件路径
184
+ * @param data 写入的对象数据
185
+ */
186
+ export declare function writeJsonFile(filePath: string, data: Record<string, any>): void;
187
+ /**
188
+ * 合并两个JSON对象,检测重复key,重复时通过交互让用户选择保留哪个值
189
+ * @param baseObj 源JSON对象(被合并到此对象)
190
+ * @param mergeObj 待合并JSON对象
191
+ * @param langKey 当前语言标识,用于日志提示
192
+ * @returns 合并后的JSON对象
193
+ */
194
+ export declare function mergeJsonObjects(baseObj: Record<string, any>, mergeObj: Record<string, any>, langKey: string): Promise<Record<string, any>>;
195
+ /**
196
+ * 获取指定语言文件夹下的所有JSON文件名列表
197
+ * @param dirPath 语言文件夹路径
198
+ * @returns JSON文件名数组
199
+ */
200
+ export declare function getJsonFilesInLangDir(dirPath: string): string[];
201
+ /**
202
+ * 通用的路径输入验证函数
203
+ * @param value 用户输入
204
+ * @returns 验证通过返回 true,否则返回错误信息字符串
205
+ */
206
+ export declare function validatePathInput(value: string): true | string;
175
207
  export {};
176
208
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
+ import { select, Separator } from '@inquirer/prompts';
4
5
  import { getData, postData } from '../api/index.js';
5
6
  import api from '../api/interface.js';
6
7
  /**
@@ -315,3 +316,102 @@ export function trimQuotes(str) {
315
316
  }
316
317
  return str;
317
318
  }
319
+ /**
320
+ * 读取JSON文件内容,返回对象,文件不存在返回空对象
321
+ * @param filePath JSON文件路径
322
+ * @returns 解析后的对象,出错或不存在返回空对象
323
+ */
324
+ export function readJsonFile(filePath) {
325
+ if (!fs.existsSync(filePath)) {
326
+ return {};
327
+ }
328
+ try {
329
+ const content = fs.readFileSync(filePath, 'utf-8');
330
+ return JSON.parse(content);
331
+ }
332
+ catch (error) {
333
+ logger.error(`读取JSON文件失败: ${filePath},错误: ${normalizeError(error).message}`);
334
+ return {};
335
+ }
336
+ }
337
+ /**
338
+ * 写入JSON文件,格式化缩进2格
339
+ * @param filePath 写入文件路径
340
+ * @param data 写入的对象数据
341
+ */
342
+ export function writeJsonFile(filePath, data) {
343
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
344
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
345
+ }
346
+ /**
347
+ * 合并两个JSON对象,检测重复key,重复时通过交互让用户选择保留哪个值
348
+ * @param baseObj 源JSON对象(被合并到此对象)
349
+ * @param mergeObj 待合并JSON对象
350
+ * @param langKey 当前语言标识,用于日志提示
351
+ * @returns 合并后的JSON对象
352
+ */
353
+ export async function mergeJsonObjects(baseObj, mergeObj, langKey) {
354
+ const entries = Object.entries(mergeObj);
355
+ for (const [key, val] of entries) {
356
+ if (key in baseObj) {
357
+ if (baseObj[key] === val) {
358
+ // 值相同,无需处理,继续下一个键
359
+ continue;
360
+ }
361
+ // 值不同,需要用户交互选择保留哪个值
362
+ logger.info(`发现冲突: 键【${key}】`, true);
363
+ const choice = await select({
364
+ message: `请选择要保留的值:`,
365
+ choices: [
366
+ { name: `源文件值: ${baseObj[key]}`, value: 'base' },
367
+ { name: `合并文件值: ${val}`, value: 'merge' },
368
+ new Separator(),
369
+ ],
370
+ default: 'base',
371
+ loop: true,
372
+ });
373
+ if (choice === 'merge') {
374
+ baseObj[key] = val;
375
+ }
376
+ // 如果选择保留base,则保持不变
377
+ }
378
+ else {
379
+ // 新键,直接添加
380
+ baseObj[key] = val;
381
+ }
382
+ }
383
+ return baseObj;
384
+ }
385
+ /**
386
+ * 获取指定语言文件夹下的所有JSON文件名列表
387
+ * @param dirPath 语言文件夹路径
388
+ * @returns JSON文件名数组
389
+ */
390
+ export function getJsonFilesInLangDir(dirPath) {
391
+ if (!fs.existsSync(dirPath)) {
392
+ return [];
393
+ }
394
+ return fs.readdirSync(dirPath).filter((fileName) => {
395
+ const fullPath = path.join(dirPath, fileName);
396
+ return fs.statSync(fullPath).isFile() && fileName.endsWith('.json');
397
+ });
398
+ }
399
+ /**
400
+ * 通用的路径输入验证函数
401
+ * @param value 用户输入
402
+ * @returns 验证通过返回 true,否则返回错误信息字符串
403
+ */
404
+ export function validatePathInput(value) {
405
+ const cleaned = value.trim().replace(/^['"]|['"]$/g, '');
406
+ if (!cleaned) {
407
+ return '路径不能为空';
408
+ }
409
+ const normalized = normalizeGitBashPath(cleaned);
410
+ if (!fs.existsSync(normalized)) {
411
+ return '目录不存在,请输入有效路径';
412
+ }
413
+ if (!fs.statSync(normalized).isDirectory()) {
414
+ return '请输入一个目录路径';
415
+ }
416
+ return true;
417
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "td-web-cli",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "description": "A CLI tool for efficiency",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
package/setting.json CHANGED
@@ -2,25 +2,25 @@
2
2
  "i18n": {
3
3
  "defaultKey": "en",
4
4
  "langs": {
5
- "cn": ["简体中文"],
6
- "zh": ["繁体中文"],
7
- "en": ["英语"],
8
- "it": ["意大利语"],
9
- "brpt": ["巴西葡语"],
10
- "uk": ["乌克兰语"],
11
- "ru": ["俄语"],
12
- "es": ["欧洲西语"],
13
- "hu": ["匈牙利语"],
14
- "pl": ["波兰语"],
15
- "tr": ["土耳其语"],
16
- "de": ["德语"],
17
- "fr": ["法语"],
18
- "ro": ["罗马尼亚语"],
19
- "ko": ["韩语"],
20
- "cs": ["捷克语"],
21
- "laes": ["拉美西语"],
22
- "pt": ["欧洲葡语"],
23
- "nl": ["荷兰语"]
5
+ "cn": ["简体中文", "中文简体", "简体", "中文"],
6
+ "zh": ["繁体中文", "繁体", "繁中", "台湾繁体", "繁體中文", "繁體"],
7
+ "en": ["英语", "英文"],
8
+ "it": ["意大利语", "意大利"],
9
+ "brpt": ["巴西葡语", "巴西葡萄牙语"],
10
+ "uk": ["乌克兰语", "乌克兰"],
11
+ "ru": ["俄语", "俄罗斯"],
12
+ "es": ["欧洲西语", "欧洲西班牙语", "西班牙语", "西班牙"],
13
+ "hu": ["匈牙利语", "匈牙利"],
14
+ "pl": ["波兰语", "波兰"],
15
+ "tr": ["土耳其语", "土耳其"],
16
+ "de": ["德语", "德国"],
17
+ "fr": ["法语", "法国"],
18
+ "ro": ["罗马尼亚语", "罗马尼亚"],
19
+ "ko": ["韩语", "韩国"],
20
+ "cs": ["捷克语", "捷克"],
21
+ "laes": ["拉美西语", "美西"],
22
+ "pt": ["欧洲葡语", "欧洲葡萄牙语"],
23
+ "nl": ["荷兰语", "荷兰"]
24
24
  },
25
25
  "longCodes": {
26
26
  "cn": "zh-CN",