td-web-cli 0.1.32 → 0.1.34

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,7 +47,8 @@ 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
- - 图片压缩工具。[详细功能说明请点击这里](https://github.com/qianyuzzf/td-web-cli/blob/master/docs/i18n/compressImage.md)
50
+ - 多语言 JSON 文件插入工具。[详细功能说明请点击这里](https://github.com/qianyuzzf/td-web-cli/blob/master/docs/i18n/jsonInsert.md)
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
 
53
54
  ---
@@ -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,7 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * 主函数:从插入目录按指定 keys 批量插入到源目录
4
+ * @param program Commander 实例(保留扩展可能)
5
+ */
6
+ export declare function jsonInsert(program: Command): Promise<void>;
7
+ //# 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;AAoFpC;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,OAAO,iBA0JhD"}
@@ -0,0 +1,176 @@
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
+ * 将指定 keys 从插入对象合并到源对象
7
+ * @param baseObj 源 JSON 对象(会被原地修改)
8
+ * @param insertObj 待插入的 JSON 对象
9
+ * @param keys 需要插入的键数组
10
+ * @param langKey 当前语言标识(用于日志)
11
+ * @param strategy 冲突处理策略:直接覆盖或手动选择
12
+ * @returns 返回修改后的源对象(与 baseObj 同一引用)
13
+ */
14
+ async function insertKeysIntoObject(baseObj, insertObj, keys, langKey, strategy) {
15
+ for (const key of keys) {
16
+ // 插入文件中不存在该键 → 跳过
17
+ if (!(key in insertObj)) {
18
+ logger.warn(`键 "${key}" 在插入文件中不存在,已跳过`, true);
19
+ continue;
20
+ }
21
+ const insertVal = insertObj[key];
22
+ // 源文件中不存在该键 → 直接新增
23
+ if (!(key in baseObj)) {
24
+ baseObj[key] = insertVal;
25
+ logger.info(`新增键: ${key}`, true);
26
+ continue;
27
+ }
28
+ // 键已存在且值相同 → 跳过
29
+ if (baseObj[key] === insertVal) {
30
+ logger.info(`键 "${key}" 值相同,已跳过`, true);
31
+ continue;
32
+ }
33
+ // 键已存在且值不同 → 根据策略处理
34
+ logger.info(`键 "${key}" 已存在且值不同`, true);
35
+ if (strategy === 'overwrite') {
36
+ // 直接覆盖
37
+ baseObj[key] = insertVal;
38
+ logger.info(`已用插入值覆盖键 "${key}"`, true);
39
+ }
40
+ else {
41
+ // 手动选择
42
+ const choice = await select({
43
+ message: `请选择要保留的值:`,
44
+ choices: [
45
+ { name: `源文件值: ${baseObj[key]}`, value: 'base' },
46
+ { name: `插入文件值: ${insertVal}`, value: 'insert' },
47
+ new Separator(), // 分割线,方便未来扩展更多功能
48
+ ],
49
+ default: 'base',
50
+ loop: true,
51
+ });
52
+ if (choice === 'insert') {
53
+ baseObj[key] = insertVal;
54
+ logger.info(`已用插入值覆盖键 "${key}"`, true);
55
+ }
56
+ else {
57
+ logger.info(`保留源文件值,键 "${key}" 未更改`, true);
58
+ }
59
+ }
60
+ }
61
+ return baseObj;
62
+ }
63
+ /**
64
+ * 主函数:从插入目录按指定 keys 批量插入到源目录
65
+ * @param program Commander 实例(保留扩展可能)
66
+ */
67
+ export async function jsonInsert(program) {
68
+ try {
69
+ // 获取源目录路径
70
+ const srcDir = await input({
71
+ message: '请输入源 JSON 文件夹路径(含语言子文件夹,如 cn/translate.json):',
72
+ validate: validatePathInput,
73
+ });
74
+ // 获取待插入目录路径
75
+ const insertDir = await input({
76
+ message: '请输入待插入 JSON 文件夹路径(含语言子文件夹,如 cn/translate.json):',
77
+ validate: validatePathInput,
78
+ });
79
+ // 获取需要插入的 keys
80
+ const keysInput = await input({
81
+ message: '请输入需要插入的 JSON key(多个 key 请用英文逗号分隔):',
82
+ validate: (value) => {
83
+ if (!value.trim())
84
+ return '键不能为空';
85
+ return true;
86
+ },
87
+ });
88
+ // 冲突处理策略选择
89
+ const conflictStrategy = await select({
90
+ message: '当目标键已存在且值不同时,请选择处理方式:',
91
+ choices: [
92
+ { name: '直接覆盖(始终使用插入文件值)', value: 'overwrite' },
93
+ { name: '手动选择(针对每个冲突键提示)', value: 'manual' },
94
+ new Separator(),
95
+ ],
96
+ default: 'manual',
97
+ loop: true,
98
+ });
99
+ // 路径标准化
100
+ const srcPath = normalizeGitBashPath(srcDir);
101
+ const insertPath = normalizeGitBashPath(insertDir);
102
+ // 解析并清洗 keys
103
+ const keys = keysInput
104
+ .split(',')
105
+ .map((k) => k.trim())
106
+ .filter((k) => k.length > 0);
107
+ if (keys.length === 0) {
108
+ logger.info('未输入有效 key,操作取消');
109
+ return;
110
+ }
111
+ logger.info(`源目录: ${srcPath}`);
112
+ logger.info(`插入目录: ${insertPath}`);
113
+ logger.info(`待插入 key: ${keys.join(', ')}`);
114
+ logger.info(`冲突处理策略: ${conflictStrategy === 'overwrite' ? '直接覆盖' : '手动选择'}`);
115
+ // 获取共同的顶层语言子文件夹
116
+ const srcLangDirs = fs
117
+ .readdirSync(srcPath)
118
+ .filter((f) => fs.statSync(path.join(srcPath, f)).isDirectory());
119
+ const insertLangDirs = fs
120
+ .readdirSync(insertPath)
121
+ .filter((f) => fs.statSync(path.join(insertPath, f)).isDirectory());
122
+ const commonLangDirs = srcLangDirs.filter((lang) => insertLangDirs.includes(lang));
123
+ if (commonLangDirs.length === 0) {
124
+ logger.info('没有发现相同语言文件夹,操作取消');
125
+ return;
126
+ }
127
+ logger.info(`发现 ${commonLangDirs.length} 个共同语言文件夹: ${commonLangDirs.join(', ')}`, true);
128
+ // 逐语言文件夹处理
129
+ for (const langKey of commonLangDirs) {
130
+ logger.info(`${'='.repeat(60)}`, true);
131
+ logger.info(`处理语言: ${langKey}`, true);
132
+ const srcLangPath = path.join(srcPath, langKey);
133
+ const insertLangPath = path.join(insertPath, langKey);
134
+ const srcJsonFiles = getJsonFilesInLangDir(srcLangPath);
135
+ const insertJsonFiles = getJsonFilesInLangDir(insertLangPath);
136
+ // 找出该语言下共同存在的 JSON 文件
137
+ const commonJsonFiles = srcJsonFiles.filter((file) => insertJsonFiles.includes(file));
138
+ if (commonJsonFiles.length === 0) {
139
+ logger.info(`语言【${langKey}】下没有共同的 JSON 文件,跳过`, true);
140
+ continue;
141
+ }
142
+ logger.info(`发现 ${commonJsonFiles.length} 个共同 JSON 文件: ${commonJsonFiles.join(', ')}`, true);
143
+ // 逐文件插入
144
+ for (const jsonFile of commonJsonFiles) {
145
+ logger.info(`处理文件: ${jsonFile}`, true);
146
+ const srcFile = path.join(srcLangPath, jsonFile);
147
+ const insertFile = path.join(insertLangPath, jsonFile);
148
+ if (!fs.existsSync(srcFile) || !fs.existsSync(insertFile)) {
149
+ logger.warn(`文件缺失,跳过: ${jsonFile}`, true);
150
+ continue;
151
+ }
152
+ // 读取 JSON
153
+ const srcJson = readJsonFile(srcFile);
154
+ const insertJson = readJsonFile(insertFile);
155
+ const beforeCount = Object.keys(srcJson).length;
156
+ logger.info(`源文件原有键数: ${beforeCount}`, true);
157
+ // 执行插入
158
+ const updated = await insertKeysIntoObject(srcJson, insertJson, keys, langKey, conflictStrategy);
159
+ const afterCount = Object.keys(updated).length;
160
+ logger.info(`插入后键数: ${afterCount}`, true);
161
+ // 写回源文件
162
+ writeJsonFile(srcFile, updated);
163
+ logger.info(`文件 ${jsonFile} 已更新`, true);
164
+ }
165
+ logger.info(`语言【${langKey}】处理完成`, true);
166
+ }
167
+ logger.info(`${'='.repeat(60)}`, true);
168
+ logger.info(`所有插入操作完成!`, true);
169
+ logger.info(`源目录已更新: ${srcPath}`, true);
170
+ }
171
+ catch (error) {
172
+ loggerError(error, logger);
173
+ console.error('程序执行时发生异常,已记录日志,程序已退出');
174
+ process.exit(1);
175
+ }
176
+ }
@@ -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.32",
3
+ "version": "0.1.34",
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",