td-octopus 0.2.4 → 0.2.6

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.
@@ -0,0 +1,15 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(node -e \"\nconst { transformToChinese } = require\\('./src/transLang/single.js'\\);\n\nconst testCode = \\\\`\nimport I18N from '@/utils/I18N';\n\nconst immuneTimePicker = \\(field\\) => {\n let params = {\n dateMap: {\n cn: {\n [I18N.trade.eventdetail.zuiJinTian4]: [],\n [I18N.lang.timepicker.zuiJinTian4]: [],\n [I18N.lang.timepicker.zuiJinTian3]: [],\n [I18N.lang.timepicker.zuiJinTian2]: [],\n [I18N.lang.timepicker.zuiJinTian]: []\n },\n en: {\n 'Recent 1 days': [],\n 'Recent 7 days': [],\n 'Recent 30 days': [],\n 'Recent 90 days': [],\n 'Recent 180 days': []\n }\n }\n };\n return params[field]['cn'];\n};\n\\\\`;\n\nconst globalList = {};\ntransformToChinese\\(testCode, globalList\\);\nconsole.log\\('结果:', JSON.stringify\\(globalList, null, 2\\)\\);\n\")",
5
+ "Bash(node debug-trans.js)",
6
+ "Bash(node debug-switch.js)",
7
+ "Bash(node -e \"\nconst { transformToChinese } = require\\('./src/transLang/single.js'\\);\n\nconst testCode = \\\\`\nimport I18N from '@/utils/I18N';\n\nconst getDeleteInfo = \\(name\\) => {\n let cn = I18N.lang.common.queRenShanChu + name + I18N.lang.common.ma2;\n let en = 'Confirm to delete \\\\\"' + name + '\\\\\"?';\n\n return cn;\n};\n\\\\`;\n\nconst globalList = {};\ntransformToChinese\\(testCode, globalList\\);\nconsole.log\\('结果:', JSON.stringify\\(globalList, null, 2\\)\\);\n\")",
8
+ "Bash(node -e \"\nconst { transformToChinese } = require\\('./src/transLang/single.js'\\);\n\nconst testCode = \\\\`\nimport I18N from '@/utils/I18N';\n\nconst test1 = \\(\\) => {\n // 单个 I18N 引用的拼接\n let cn = I18N.lang.common.hello + name;\n let en = 'Hello ' + name;\n return cn;\n};\n\nconst test2 = \\(\\) => {\n // 多个 I18N 引用\n let cn = I18N.lang.common.start + value + I18N.lang.common.end;\n let en = 'Start: ' + value + ' End';\n return cn;\n};\n\\\\`;\n\nconst globalList = {};\ntransformToChinese\\(testCode, globalList\\);\nconsole.log\\('结果:', JSON.stringify\\(globalList, null, 2\\)\\);\n\")",
9
+ "Bash(ping -c 3 10.57.80.253)",
10
+ "Bash(git config --list | grep -i \"url\\\\|proxy\\\\|insteadof\")",
11
+ "Bash(nslookup ssh.github.com)",
12
+ "Bash(ssh -T git@github.com 2>&1)"
13
+ ]
14
+ }
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "td-octopus",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "I18N tool",
5
5
  "author": "Anthony Li",
6
6
  "bin": {
@@ -10,6 +10,7 @@
10
10
  "dependencies": {
11
11
  "@babel/core": "^7.28.6",
12
12
  "@babel/parser": "^7.28.6",
13
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
13
14
  "@babel/traverse": "^7.28.6",
14
15
  "@babel/types": "^7.28.6",
15
16
  "axios": "^1.6.5",
@@ -34,7 +35,8 @@
34
35
  "update-notifier": "^4.1.0",
35
36
  "vue-template-compiler": "^2.6.14",
36
37
  "xlsx": "0.16.9",
37
- "yargs": "^15.3.1"
38
+ "yargs": "^15.3.1",
39
+ "@babel/plugin-proposal-decorators": "^7.29.0"
38
40
  },
39
41
  "keywords": [
40
42
  "I18N",
package/src/back/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * @Author: 郑泳健
4
4
  * @Date: 2022-06-01 13:56:18
5
5
  * @LastEditors: 郑泳健
6
- * @LastEditTime: 2025-11-11 10:10:55
6
+ * @LastEditTime: 2026-03-03 14:18:16
7
7
  */
8
8
  const path = require('path')
9
9
  const fs = require('fs')
@@ -63,6 +63,12 @@ function getFilePaths() {
63
63
  */
64
64
  function getTemplateValue(str) {
65
65
  try {
66
+ if (!str) return {};
67
+
68
+ // 移除外层的花括号,获取对象内容
69
+ const objStr = str.trim().replace(/^{/, '').replace(/}$/, '').trim();
70
+ if (!objStr) return {};
71
+
66
72
  const result = {};
67
73
  const regex = /(\w+)\s*:\s*/g;
68
74
  let match;
@@ -77,8 +83,8 @@ function getTemplateValue(str) {
77
83
  const char = objStr[i];
78
84
  if (char === '(') bracketCount++;
79
85
  if (char === ')') bracketCount--;
80
- if ((char === ',' && bracketCount === 0) || (char === '}' && bracketCount === 0)) {
81
- valueEnd = i;
86
+ if ((char === ',' && bracketCount === 0) || i === objStr.length - 1) {
87
+ valueEnd = i === objStr.length - 1 ? i + 1 : i;
82
88
  break;
83
89
  }
84
90
  }
package/src/pop/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * @Author: 郑泳健
4
4
  * @Date: 2024-12-12 15:00:24
5
5
  * @LastEditors: 郑泳健
6
- * @LastEditTime: 2025-11-19 15:38:18
6
+ * @LastEditTime: 2026-02-12 10:40:03
7
7
  */
8
8
  const path = require('path');
9
9
  const fs = require('fs');
@@ -45,7 +45,7 @@ function readJsFiles(folderPath) {
45
45
  } else if (path.extname(fullPath) === '.js') {
46
46
  // 如果是 .js 文件,读取文件内容
47
47
  let content = fs.readFileSync(fullPath, 'utf-8');
48
- const ast = parse(content, { sourceType: 'module', plugins: ['jsx', 'typescript'] });
48
+ const ast = parse(content, { sourceType: 'module', plugins: ['decorators-legacy', 'jsx', 'typescript'] });
49
49
  const output = generate(ast, { comments: false });
50
50
  content = output.code;
51
51
  jsContent += `\n/* File: ${fullPath} */\n${content}\n`;
@@ -73,7 +73,7 @@ function main() {
73
73
 
74
74
  if (!fs.existsSync(path.resolve(__dirname, '../octopus/zh-CN.js'))) {
75
75
  spinner.succeed(`查询完毕,共计丢失${lostKey.length}个,请在lostI18N.js中查看`);
76
- spinner.warn('请先执行otp storage');
76
+ spinner.warn('请先执行otp stash');
77
77
  return;
78
78
  }
79
79
  spinner.start(`查询完毕,共计丢失${lostKey.length}个,开始同步开始`);
@@ -105,7 +105,7 @@ function main() {
105
105
  autoImportJSFiles(path.resolve(process.cwd(), '.octopus/zh-CN'), cnJSON);
106
106
  autoImportJSFiles(path.resolve(process.cwd(), '.octopus/en-US'), enJSON);
107
107
  fs.rmSync(path.resolve(__dirname, '../octopus'), { recursive: true, force: true });
108
- spinner.succeed('同步完成,如果需要重新check,请先执行otp storage');
108
+ spinner.succeed('同步完成,如果需要重新check,请先执行otp stash');
109
109
  })();
110
110
  }
111
111
 
@@ -24,6 +24,38 @@ function extractCnKeyDirect(node) {
24
24
  return node.value;
25
25
  }
26
26
 
27
+ // 处理 I18N.template(I18N.lang.xxx.yyy, {...}) 形式
28
+ if (t.isCallExpression(node)) {
29
+ const { callee, arguments: args } = node;
30
+
31
+ // 检查是否为 I18N.template 调用
32
+ if (t.isMemberExpression(callee) &&
33
+ t.isIdentifier(callee.object, { name: 'I18N' }) &&
34
+ t.isIdentifier(callee.property, { name: 'template' }) &&
35
+ args.length >= 1) {
36
+
37
+ // 提取第一个参数(I18N.lang.xxx.yyy)
38
+ const firstArg = args[0];
39
+ if (t.isMemberExpression(firstArg)) {
40
+ let parts = [];
41
+ let current = firstArg;
42
+
43
+ while (t.isMemberExpression(current)) {
44
+ if (current.computed) return null;
45
+ const prop = current.property;
46
+ if (!t.isIdentifier(prop)) return null;
47
+ parts.unshift(prop.name);
48
+ current = current.object;
49
+ }
50
+
51
+ if (t.isIdentifier(current) && current.name === 'I18N') {
52
+ // 不包含 I18N,直接返回 lang.xxx.yyy
53
+ return parts.join('.');
54
+ }
55
+ }
56
+ }
57
+ }
58
+
27
59
  if (t.isMemberExpression(node)) {
28
60
  let parts = [];
29
61
  let current = node;
@@ -50,20 +82,191 @@ function extractCnKeyDirect(node) {
50
82
  return null;
51
83
  }
52
84
 
53
- // ===== 提取 cn key(仅支持:字符串字面量 或 I18N.a.b.c 四层)=====
85
+ // ===== TemplateLiteral 中提取所有部分 =====
86
+ function extractTemplateParts(node) {
87
+ const parts = [];
88
+
89
+ for (let i = 0; i < node.quasis.length; i++) {
90
+ // 添加字符串部分
91
+ if (node.quasis[i].value.raw) {
92
+ parts.push({ type: 'string', value: node.quasis[i].value.raw });
93
+ }
94
+
95
+ // 如果不是最后一个 quasi,添加表达式部分
96
+ if (i < node.expressions.length) {
97
+ const expr = node.expressions[i];
98
+ if (t.isIdentifier(expr)) {
99
+ parts.push({ type: 'variable', value: expr.name });
100
+ } else {
101
+ parts.push({ type: 'expr', value: expr });
102
+ }
103
+ }
104
+ }
105
+
106
+ return parts;
107
+ }
108
+
109
+ // ===== 从 BinaryExpression 中提取所有部分(I18N引用、字符串、变量)=====
110
+ function extractBinaryParts(node) {
111
+ const parts = [];
112
+
113
+ function traverse(n) {
114
+ if (t.isBinaryExpression(n) && n.operator === '+') {
115
+ traverse(n.left);
116
+ traverse(n.right);
117
+ } else if (t.isMemberExpression(n)) {
118
+ // I18N 引用
119
+ const key = extractCnKeyDirect(n);
120
+ if (key && key.includes('.')) {
121
+ parts.push({ type: 'i18n', value: key });
122
+ } else {
123
+ parts.push({ type: 'expr', value: n });
124
+ }
125
+ } else if (t.isStringLiteral(n)) {
126
+ // 字符串字面量
127
+ parts.push({ type: 'string', value: n.value });
128
+ } else if (t.isIdentifier(n)) {
129
+ // 变量
130
+ parts.push({ type: 'variable', value: n.name });
131
+ } else {
132
+ // 其他表达式
133
+ parts.push({ type: 'expr', value: n });
134
+ }
135
+ }
136
+
137
+ traverse(node);
138
+ return parts;
139
+ }
140
+
141
+ // ===== 尝试匹配 cn 和 en 的表达式,返回多个映射 =====
142
+ function matchBinaryExpressions(cnNode, enNode) {
143
+ // 提取 cn 部分
144
+ let cnParts;
145
+ if (t.isBinaryExpression(cnNode) && cnNode.operator === '+') {
146
+ cnParts = extractBinaryParts(cnNode);
147
+ } else {
148
+ return [];
149
+ }
150
+
151
+ // 提取 en 部分(可能是 BinaryExpression 或 TemplateLiteral)
152
+ let enParts;
153
+ if (t.isBinaryExpression(enNode) && enNode.operator === '+') {
154
+ enParts = extractBinaryParts(enNode);
155
+ } else if (t.isTemplateLiteral(enNode)) {
156
+ enParts = extractTemplateParts(enNode);
157
+ } else {
158
+ return [];
159
+ }
160
+
161
+ const mappings = [];
162
+
163
+ // 提取所有 I18N 引用
164
+ const i18nParts = cnParts.filter(p => p.type === 'i18n');
165
+
166
+ // 如果没有 I18N 引用,返回空
167
+ if (i18nParts.length === 0) {
168
+ return mappings;
169
+ }
170
+
171
+ // 如果只有一个 I18N 引用,将整个 en 表达式映射给它
172
+ if (i18nParts.length === 1) {
173
+ let enValue = '';
174
+ let varIndex = 1;
175
+ for (const part of enParts) {
176
+ if (part.type === 'string') {
177
+ enValue += part.value;
178
+ } else if (part.type === 'variable' || part.type === 'expr') {
179
+ enValue += `{val${varIndex}}`;
180
+ varIndex++;
181
+ }
182
+ }
183
+ mappings.push({ key: i18nParts[0].value, value: enValue });
184
+ return mappings;
185
+ }
186
+
187
+ // 多个 I18N 引用:使用变量位置作为锚点进行匹配
188
+ // 策略:找到 cn 和 en 中的共同变量,根据变量前后的位置匹配字符串
189
+
190
+ // 构建位置索引:记录每个 I18N 和字符串前后的变量
191
+ const cnMap = [];
192
+ const enMap = [];
193
+
194
+ // 记录 cn 中每个 I18N 前后的变量
195
+ for (let i = 0; i < cnParts.length; i++) {
196
+ const part = cnParts[i];
197
+ if (part.type === 'i18n') {
198
+ const prevVar = i > 0 && cnParts[i - 1].type === 'variable' ? cnParts[i - 1].value : null;
199
+ const nextVar = i < cnParts.length - 1 && cnParts[i + 1].type === 'variable' ? cnParts[i + 1].value : null;
200
+ cnMap.push({
201
+ index: i,
202
+ key: part.value,
203
+ prevVar,
204
+ nextVar,
205
+ position: prevVar ? 'after' : (nextVar ? 'before' : 'standalone')
206
+ });
207
+ }
208
+ }
209
+
210
+ // 记录 en 中每个字符串前后的变量
211
+ for (let i = 0; i < enParts.length; i++) {
212
+ const part = enParts[i];
213
+ if (part.type === 'string') {
214
+ const prevVar = i > 0 && enParts[i - 1].type === 'variable' ? enParts[i - 1].value : null;
215
+ const nextVar = i < enParts.length - 1 && enParts[i + 1].type === 'variable' ? enParts[i + 1].value : null;
216
+ enMap.push({
217
+ index: i,
218
+ value: part.value,
219
+ prevVar,
220
+ nextVar,
221
+ position: prevVar ? 'after' : (nextVar ? 'before' : 'standalone')
222
+ });
223
+ }
224
+ }
225
+
226
+ // 匹配:根据前后变量的位置进行匹配
227
+ for (const cnItem of cnMap) {
228
+ // 查找具有相同前后变量的 en 字符串
229
+ const matchedEnItem = enMap.find(enItem => {
230
+ // 优先匹配前后都相同的
231
+ if (cnItem.prevVar && cnItem.nextVar) {
232
+ return enItem.prevVar === cnItem.prevVar && enItem.nextVar === cnItem.nextVar;
233
+ }
234
+ // 匹配前一个变量相同的
235
+ if (cnItem.prevVar) {
236
+ return enItem.prevVar === cnItem.prevVar;
237
+ }
238
+ // 匹配后一个变量相同的
239
+ if (cnItem.nextVar) {
240
+ return enItem.nextVar === cnItem.nextVar;
241
+ }
242
+ // 都没有变量的情况
243
+ return cnItem.position === enItem.position;
244
+ });
245
+
246
+ if (matchedEnItem) {
247
+ mappings.push({ key: cnItem.key, value: matchedEnItem.value });
248
+ // 标记已使用,避免重复匹配
249
+ enMap.splice(enMap.indexOf(matchedEnItem), 1);
250
+ }
251
+ }
252
+
253
+ return mappings;
254
+ }
255
+
256
+ // ===== 提取 cn 的 key(支持:字符串字面量、I18N.a.b.c、BinaryExpression)=====
54
257
  function extractCnKey(node, scope) {
55
258
  // 情况1: 字符串字面量
56
259
  if (t.isStringLiteral(node)) {
57
260
  return node.value;
58
261
  }
59
-
60
- // 情况2: Identifier → 查找其定义
262
+
263
+ // 情况2: Identifier → 查找其定义
61
264
  if (t.isIdentifier(node)) {
62
265
  const binding = scope.getBinding(node.name);
63
-
266
+
64
267
  if (binding && binding.path.isVariableDeclarator()) {
65
268
  const init = binding.path.node.init;
66
-
269
+
67
270
  if (init) {
68
271
  // 递归解析初始化表达式(不再传 scope,避免无限递归)
69
272
  return extractCnKeyDirect(init);
@@ -72,8 +275,15 @@ function extractCnKey(node, scope) {
72
275
  return null;
73
276
  }
74
277
 
75
- // 情况3: 检查是否为 I18N.a.b.c(必须是四层 MemberExpression,非 computed)
76
- return extractCnKeyDirect(node);
278
+ // 情况3: BinaryExpression (字符串拼接) - 返回第一个 I18N 引用
279
+ if (t.isBinaryExpression(node) && node.operator === '+') {
280
+ const parts = extractBinaryParts(node);
281
+ const i18nPart = parts.find(p => p.type === 'i18n');
282
+ return i18nPart ? i18nPart.value : null;
283
+ }
284
+
285
+ // 情况4: 检查是否为 I18N.a.b.c(必须是四层 MemberExpression,非 computed)
286
+ return extractCnKeyDirect(node);
77
287
  }
78
288
 
79
289
  function extractEnValue(node, scope) {
@@ -82,6 +292,51 @@ function extractEnValue(node, scope) {
82
292
  return node.value;
83
293
  }
84
294
 
295
+ // 处理模板字符串:`xxx ${a} yyy ${b}` -> "xxx {val1} yyy {val2}"
296
+ if (t.isTemplateLiteral(node)) {
297
+ let result = '';
298
+ let varIndex = 1;
299
+
300
+ for (let i = 0; i < node.quasis.length; i++) {
301
+ result += node.quasis[i].value.raw;
302
+
303
+ // 如果不是最后一个 quasi,说明后面有变量表达式
304
+ if (i < node.expressions.length) {
305
+ result += `{val${varIndex}}`;
306
+ varIndex++;
307
+ }
308
+ }
309
+
310
+ return result;
311
+ }
312
+
313
+ // 处理字符串拼接:'str1' + var + 'str2' -> "str1{val1}str2"
314
+ if (t.isBinaryExpression(node) && node.operator === '+') {
315
+ let result = '';
316
+ let varIndex = 1;
317
+
318
+ function traverseBinary(n) {
319
+ if (t.isBinaryExpression(n) && n.operator === '+') {
320
+ traverseBinary(n.left);
321
+ traverseBinary(n.right);
322
+ } else if (t.isStringLiteral(n)) {
323
+ // 字符串字面量,直接拼接
324
+ result += n.value;
325
+ } else if (t.isIdentifier(n)) {
326
+ // 变量,替换为占位符
327
+ result += `{val${varIndex}}`;
328
+ varIndex++;
329
+ } else {
330
+ // 其他表达式(如函数调用等),也用占位符
331
+ result += `{val${varIndex}}`;
332
+ varIndex++;
333
+ }
334
+ }
335
+
336
+ traverseBinary(node);
337
+ return result;
338
+ }
339
+
85
340
  // 是变量(Identifier),尝试回溯
86
341
  if (t.isIdentifier(node)) {
87
342
  const binding = scope.getBinding(node.name);
@@ -90,10 +345,25 @@ function extractEnValue(node, scope) {
90
345
  if (init && t.isStringLiteral(init)) {
91
346
  return init.value;
92
347
  }
348
+ // 如果是模板字符串,递归处理
349
+ if (init && t.isTemplateLiteral(init)) {
350
+ let result = '';
351
+ let varIndex = 1;
352
+
353
+ for (let i = 0; i < init.quasis.length; i++) {
354
+ result += init.quasis[i].value.raw;
355
+ if (i < init.expressions.length) {
356
+ result += `{val${varIndex}}`;
357
+ varIndex++;
358
+ }
359
+ }
360
+
361
+ return result;
362
+ }
93
363
  }
94
364
  }
95
365
 
96
- // 其他情况(表达式、模板、多变量等)不支持
366
+ // 其他情况(表达式、多变量等)不支持
97
367
  return null;
98
368
  }
99
369
 
@@ -103,7 +373,7 @@ const transformToChinese = (code, globalList) => {
103
373
 
104
374
  const ast = parser.parse(code, {
105
375
  sourceType: 'module',
106
- plugins: ['jsx', 'typescript']
376
+ plugins: ['decorators-legacy', 'jsx', 'typescript']
107
377
  });
108
378
 
109
379
  const langVariableNames = new Set();
@@ -125,6 +395,124 @@ const transformToChinese = (code, globalList) => {
125
395
 
126
396
  // 第二阶段:处理对象和成员表达式
127
397
  traverse(ast, {
398
+ // 处理函数或块作用域内的连续 cn/en 变量声明
399
+ BlockStatement(path) {
400
+ const body = path.node.body;
401
+
402
+ // 查找所有变量声明
403
+ for (let i = 0; i < body.length; i++) {
404
+ const node = body[i];
405
+
406
+ // 跳过非变量声明
407
+ if (!t.isVariableDeclaration(node)) continue;
408
+
409
+ // 支持 cn/cnName 变量
410
+ const cnDeclarator = node.declarations.find(
411
+ (decl) => t.isIdentifier(decl.id) &&
412
+ (decl.id.name === 'cn' || decl.id.name === 'cnName') &&
413
+ decl.init
414
+ );
415
+
416
+ if (!cnDeclarator) continue;
417
+
418
+ // 在当前和后续语句中查找 en/enName 变量
419
+ let enDeclarator = null;
420
+ const cnVarName = cnDeclarator.id.name;
421
+ const enVarName = cnVarName === 'cn' ? 'en' : 'enName';
422
+
423
+ // 先在同一个声明中查找
424
+ enDeclarator = node.declarations.find(
425
+ (decl) => t.isIdentifier(decl.id, { name: enVarName }) && decl.init
426
+ );
427
+
428
+ // 如果同一声明中没找到,查找后续语句
429
+ if (!enDeclarator) {
430
+ for (let j = i + 1; j < body.length; j++) {
431
+ const nextNode = body[j];
432
+ if (t.isVariableDeclaration(nextNode)) {
433
+ enDeclarator = nextNode.declarations.find(
434
+ (decl) => t.isIdentifier(decl.id, { name: enVarName }) && decl.init
435
+ );
436
+ if (enDeclarator) break;
437
+ }
438
+ // 遇到 switch、if 等控制结构,停止搜索
439
+ if (t.isSwitchStatement(nextNode) || t.isIfStatement(nextNode)) {
440
+ break;
441
+ }
442
+ }
443
+ }
444
+
445
+ // 如果找到了 cn/cnName 和 en/enName,提取值
446
+ if (enDeclarator) {
447
+ const cnInit = cnDeclarator.init;
448
+ const enInit = enDeclarator.init;
449
+
450
+ // 特殊处理:如果 cn 是 BinaryExpression,en 是 BinaryExpression 或 TemplateLiteral
451
+ if (t.isBinaryExpression(cnInit) && cnInit.operator === '+' &&
452
+ (t.isBinaryExpression(enInit) && enInit.operator === '+' || t.isTemplateLiteral(enInit))) {
453
+
454
+ const mappings = matchBinaryExpressions(cnInit, enInit);
455
+ for (const mapping of mappings) {
456
+ localList[mapping.key.replace('I18N.', '')] = mapping.value;
457
+ }
458
+ } else {
459
+ // 常规处理
460
+ const cnKey = extractCnKey(cnInit, path.scope);
461
+ const enStr = extractEnValue(enInit, path.scope);
462
+
463
+ if (cnKey !== null && enStr !== null) {
464
+ localList[cnKey.replace('I18N.', '')] = enStr;
465
+ }
466
+ }
467
+ }
468
+ }
469
+ },
470
+
471
+ // 单独处理 SwitchStatement
472
+ SwitchStatement(path) {
473
+ path.node.cases.forEach((caseNode) => {
474
+ const consequent = caseNode.consequent;
475
+
476
+ let cnAssignment = null;
477
+ let enAssignment = null;
478
+
479
+ // 在每个 case 中查找 cnName = xxx 和 enName = xxx
480
+ for (const stmt of consequent) {
481
+ if (t.isExpressionStatement(stmt) && t.isAssignmentExpression(stmt.expression)) {
482
+ const { left, right } = stmt.expression;
483
+
484
+ if (t.isIdentifier(left, { name: 'cnName' }) || t.isIdentifier(left, { name: 'cn' })) {
485
+ cnAssignment = right;
486
+ }
487
+ if (t.isIdentifier(left, { name: 'enName' }) || t.isIdentifier(left, { name: 'en' })) {
488
+ enAssignment = right;
489
+ }
490
+ }
491
+ }
492
+
493
+ // 如果找到了 cnName 和 enName 的赋值,提取值
494
+ if (cnAssignment && enAssignment) {
495
+ // 特殊处理:如果 cn 是 BinaryExpression,en 是 BinaryExpression 或 TemplateLiteral
496
+ if (t.isBinaryExpression(cnAssignment) && cnAssignment.operator === '+' &&
497
+ (t.isBinaryExpression(enAssignment) && enAssignment.operator === '+' || t.isTemplateLiteral(enAssignment))) {
498
+
499
+ const mappings = matchBinaryExpressions(cnAssignment, enAssignment);
500
+ for (const mapping of mappings) {
501
+ localList[mapping.key.replace('I18N.', '')] = mapping.value;
502
+ }
503
+ } else {
504
+ // 常规处理
505
+ const cnKey = extractCnKey(cnAssignment, path.scope);
506
+ const enStr = extractEnValue(enAssignment, path.scope);
507
+
508
+ if (cnKey !== null && enStr !== null) {
509
+ localList[cnKey.replace('I18N.', '')] = enStr;
510
+ }
511
+ }
512
+ }
513
+ });
514
+ },
515
+
128
516
  ObjectExpression(path) {
129
517
  const props = path.node.properties;
130
518
  if (props.length === 0) return;
@@ -135,27 +523,119 @@ const transformToChinese = (code, globalList) => {
135
523
  const enProp = props.find(
136
524
  (prop) => t.isObjectProperty(prop) && !prop.computed && isKeyNamed(prop.key, 'en') && !prop.method
137
525
  );
138
-
139
- // 仅当 en 是字符串字面量,且 cn 符合提取规则时,才记录
140
- if (cnProp && enProp) {
526
+
527
+ // 情况1: 简单的 { cn: xxx, en: yyy } 结构
528
+ if (cnProp && enProp && !t.isObjectExpression(cnProp.value) && !t.isObjectExpression(enProp.value)) {
141
529
  const enStr = extractEnValue(enProp.value, path.scope);
142
530
  const cnKey = extractCnKey(cnProp.value, path.scope);
143
-
531
+
144
532
  if (cnKey !== null && enStr !== null) {
145
533
  localList[cnKey.replace('I18N.', '')] = enStr;
146
534
  }
147
535
  }
536
+
537
+ // 情况2: 嵌套对象结构 { cn: { [I18N.xxx]: value }, en: { 'key': value } }
538
+ if (cnProp && enProp && t.isObjectExpression(cnProp.value) && t.isObjectExpression(enProp.value)) {
539
+ const cnProps = cnProp.value.properties;
540
+ const enProps = enProp.value.properties;
541
+
542
+ // 提取 cn 对象的计算属性 key
543
+ const cnKeys = [];
544
+ for (const prop of cnProps) {
545
+ if (t.isObjectProperty(prop) && prop.computed) {
546
+ // 提取计算属性中的 I18N 引用
547
+ const cnKey = extractCnKeyDirect(prop.key);
548
+ if (cnKey) {
549
+ cnKeys.push({ key: cnKey, index: cnKeys.length });
550
+ }
551
+ }
552
+ }
553
+
554
+ // 提取 en 对象的字符串 key(支持计算属性和普通属性)
555
+ const enKeys = [];
556
+ for (const prop of enProps) {
557
+ if (t.isObjectProperty(prop)) {
558
+ let enKeyValue = null;
559
+
560
+ // 计算属性: ['string']
561
+ if (prop.computed && t.isStringLiteral(prop.key)) {
562
+ enKeyValue = prop.key.value;
563
+ }
564
+ // 普通字符串属性: 'string' 或 "string"
565
+ else if (!prop.computed && t.isStringLiteral(prop.key)) {
566
+ enKeyValue = prop.key.value;
567
+ }
568
+ // 普通标识符属性: identifier (如 Today)
569
+ else if (!prop.computed && t.isIdentifier(prop.key)) {
570
+ enKeyValue = prop.key.name;
571
+ }
572
+
573
+ if (enKeyValue) {
574
+ enKeys.push({ key: enKeyValue, index: enKeys.length });
575
+ }
576
+ }
577
+ }
578
+
579
+ // 按索引位置匹配
580
+ for (let i = 0; i < Math.min(cnKeys.length, enKeys.length); i++) {
581
+ const cnKey = cnKeys[i].key;
582
+ const enKey = enKeys[i].key;
583
+ if (cnKey && enKey) {
584
+ localList[cnKey.replace('I18N.', '')] = enKey;
585
+ }
586
+ }
587
+ }
588
+ },
589
+
590
+ // 处理三元表达式:getLanguage() === 'cn' ? cn : en
591
+ ConditionalExpression(path) {
592
+ const { test, consequent, alternate } = path.node;
593
+
594
+ // 检查是否为 getLanguage() === 'cn' 或 'cn' === getLanguage()
595
+ let isLanguageCheck = false;
596
+ if (t.isBinaryExpression(test) && test.operator === '===') {
597
+ const isLeftLangCall =
598
+ t.isCallExpression(test.left) &&
599
+ t.isIdentifier(test.left.callee) &&
600
+ ['getLang', 'getLanguage'].includes(test.left.callee.name);
601
+ const isRightCn = t.isStringLiteral(test.right, { value: 'cn' });
602
+
603
+ const isRightLangCall =
604
+ t.isCallExpression(test.right) &&
605
+ t.isIdentifier(test.right.callee) &&
606
+ ['getLang', 'getLanguage'].includes(test.right.callee.name);
607
+ const isLeftCn = t.isStringLiteral(test.left, { value: 'cn' });
608
+
609
+ isLanguageCheck = (isLeftLangCall && isRightCn) || (isRightLangCall && isLeftCn);
610
+ }
611
+
612
+ // 如果是语言检查,直接替换为 consequent(cn 分支)
613
+ if (isLanguageCheck) {
614
+ path.replaceWith(consequent);
615
+ }
148
616
  },
149
617
 
150
618
  MemberExpression(path) {
151
619
  const node = path.node;
152
620
  if (!node.computed) return;
153
621
 
154
- const isLangAccess =
155
- (t.isIdentifier(node.property) && langVariableNames.has(node.property.name)) ||
156
- (t.isCallExpression(node.property) &&
622
+ // 检查是否为语言访问
623
+ let isLangAccess = false;
624
+
625
+ // 情况1: 已收集的 lang 变量名
626
+ if (t.isIdentifier(node.property) && langVariableNames.has(node.property.name)) {
627
+ isLangAccess = true;
628
+ }
629
+ // 情况2: 直接调用 getLang/getLanguage
630
+ else if (t.isCallExpression(node.property) &&
157
631
  t.isIdentifier(node.property.callee) &&
158
- ['getLang', 'getLanguage'].includes(node.property.callee.name));
632
+ ['getLang', 'getLanguage'].includes(node.property.callee.name)) {
633
+ isLangAccess = true;
634
+ }
635
+ // 情况3: 未定义的 lang 标识符(不管作用域)
636
+ else if (t.isIdentifier(node.property) && node.property.name === 'lang') {
637
+ isLangAccess = true;
638
+ }
159
639
 
160
640
  if (isLangAccess) {
161
641
  // 替换为 obj['cn']
@@ -165,6 +645,37 @@ const transformToChinese = (code, globalList) => {
165
645
  }
166
646
  },
167
647
 
648
+ // 处理可选链:obj?.[lang]
649
+ OptionalMemberExpression(path) {
650
+ const node = path.node;
651
+ if (!node.computed) return;
652
+
653
+ // 检查是否为语言访问
654
+ let isLangAccess = false;
655
+
656
+ // 情况1: 已收集的 lang 变量名
657
+ if (t.isIdentifier(node.property) && langVariableNames.has(node.property.name)) {
658
+ isLangAccess = true;
659
+ }
660
+ // 情况2: 直接调用 getLang/getLanguage
661
+ else if (t.isCallExpression(node.property) &&
662
+ t.isIdentifier(node.property.callee) &&
663
+ ['getLang', 'getLanguage'].includes(node.property.callee.name)) {
664
+ isLangAccess = true;
665
+ }
666
+ // 情况3: 未定义的 lang 标识符(不管作用域)
667
+ else if (t.isIdentifier(node.property) && node.property.name === 'lang') {
668
+ isLangAccess = true;
669
+ }
670
+
671
+ if (isLangAccess) {
672
+ // 替换为 obj?.['cn']
673
+ path.replaceWith(
674
+ t.optionalMemberExpression(node.object, t.stringLiteral('cn'), true, node.optional)
675
+ );
676
+ }
677
+ },
678
+
168
679
  // 清理无用的 lang 变量声明
169
680
  VariableDeclarator(path) {
170
681
  const { id } = path.node;
@@ -256,14 +767,26 @@ const eslint = new ESLint({
256
767
  });
257
768
 
258
769
  // ===== 递归读取所有 .js 文件(排除 index.js)=====
259
- const getAllJsFiles = (dir) => {
770
+ const getAllJsFiles = (pathInput) => {
771
+ // 检查路径是文件还是目录
772
+ const stat = fs.statSync(pathInput);
773
+
774
+ // 如果是文件,直接返回(只要是 .js 文件)
775
+ if (stat.isFile()) {
776
+ if (pathInput.endsWith('.js')) {
777
+ return [pathInput];
778
+ }
779
+ return [];
780
+ }
781
+
782
+ // 如果是目录,递归处理
260
783
  let results = [];
261
- const files = fs.readdirSync(dir);
784
+ const files = fs.readdirSync(pathInput);
262
785
 
263
786
  for (const file of files) {
264
- const fullPath = path.join(dir, file);
265
- const stat = fs.statSync(fullPath);
266
- if (stat.isDirectory()) {
787
+ const fullPath = path.join(pathInput, file);
788
+ const fileStat = fs.statSync(fullPath);
789
+ if (fileStat.isDirectory()) {
267
790
  results = results.concat(getAllJsFiles(fullPath));
268
791
  } else if (file.endsWith('.js') && file !== 'index.js') {
269
792
  results.push(fullPath);