td-octopus 0.2.2 → 0.2.3
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/package.json +1 -2
- package/src/transLang/single.js +128 -71
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "td-octopus",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "I18N tool",
|
|
5
5
|
"author": "Anthony Li",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"baidu-translate": "^1.3.0",
|
|
17
17
|
"colors": "^1.4.0",
|
|
18
18
|
"eslint": "^8.15.0",
|
|
19
|
-
"eslint-config-tongdun": "^1.1.11",
|
|
20
19
|
"eslint-plugin-prettier": "^4.2.1",
|
|
21
20
|
"eslint-plugin-td-rules-plugin": "^1.0.1",
|
|
22
21
|
"eslint-plugin-unused-imports": "^4.3.0",
|
package/src/transLang/single.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
const fs = require('fs');
|
|
3
2
|
const path = require('path');
|
|
4
3
|
const parser = require('@babel/parser');
|
|
@@ -9,36 +8,96 @@ const { ESLint } = require('eslint');
|
|
|
9
8
|
const prettier = require('eslint-plugin-prettier');
|
|
10
9
|
const unusedImports = require('eslint-plugin-unused-imports');
|
|
11
10
|
|
|
12
|
-
// =====
|
|
13
|
-
|
|
11
|
+
// ===== 工具函数:判断 key 是否匹配指定名称 =====
|
|
12
|
+
function isKeyNamed(node, name) {
|
|
13
|
+
if (t.isIdentifier(node)) {
|
|
14
|
+
return node.name === name;
|
|
15
|
+
}
|
|
14
16
|
if (t.isStringLiteral(node)) {
|
|
15
|
-
return node.value;
|
|
17
|
+
return node.value === name;
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function extractCnKeyDirect(node) {
|
|
23
|
+
if (t.isStringLiteral(node)) {
|
|
24
|
+
return node.value;
|
|
19
25
|
}
|
|
26
|
+
|
|
20
27
|
if (t.isMemberExpression(node)) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
let parts = [];
|
|
29
|
+
let current = node;
|
|
30
|
+
|
|
31
|
+
while (t.isMemberExpression(current)) {
|
|
32
|
+
if (current.computed) return null;
|
|
33
|
+
const prop = current.property;
|
|
34
|
+
if (!t.isIdentifier(prop)) return null;
|
|
35
|
+
parts.unshift(prop.name);
|
|
36
|
+
current = current.object;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (t.isIdentifier(current) && current.name === 'I18N') {
|
|
40
|
+
parts.unshift('I18N');
|
|
41
|
+
} else {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (parts.length === 4) {
|
|
46
|
+
return parts.join('.');
|
|
25
47
|
}
|
|
26
|
-
return `${objectStr}.${propertyStr}`;
|
|
27
48
|
}
|
|
28
|
-
|
|
49
|
+
|
|
50
|
+
return null;
|
|
29
51
|
}
|
|
30
52
|
|
|
31
|
-
|
|
53
|
+
// ===== 提取 cn 的 key(仅支持:字符串字面量 或 I18N.a.b.c 四层)=====
|
|
54
|
+
function extractCnKey(node, scope) {
|
|
55
|
+
// 情况1: 字符串字面量
|
|
56
|
+
if (t.isStringLiteral(node)) {
|
|
57
|
+
return node.value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 情况2: Identifier → 查找其定义
|
|
32
61
|
if (t.isIdentifier(node)) {
|
|
33
|
-
|
|
62
|
+
const binding = scope.getBinding(node.name);
|
|
63
|
+
|
|
64
|
+
if (binding && binding.path.isVariableDeclarator()) {
|
|
65
|
+
const init = binding.path.node.init;
|
|
66
|
+
|
|
67
|
+
if (init) {
|
|
68
|
+
// 递归解析初始化表达式(不再传 scope,避免无限递归)
|
|
69
|
+
return extractCnKeyDirect(init);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
34
73
|
}
|
|
74
|
+
|
|
75
|
+
// 情况3: 检查是否为 I18N.a.b.c(必须是四层 MemberExpression,非 computed)
|
|
76
|
+
return extractCnKeyDirect(node);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function extractEnValue(node, scope) {
|
|
80
|
+
// 直接是字符串字面量
|
|
35
81
|
if (t.isStringLiteral(node)) {
|
|
36
|
-
return node.value
|
|
82
|
+
return node.value;
|
|
37
83
|
}
|
|
38
|
-
|
|
84
|
+
|
|
85
|
+
// 是变量(Identifier),尝试回溯
|
|
86
|
+
if (t.isIdentifier(node)) {
|
|
87
|
+
const binding = scope.getBinding(node.name);
|
|
88
|
+
if (binding && binding.path.isVariableDeclarator()) {
|
|
89
|
+
const init = binding.path.node.init;
|
|
90
|
+
if (init && t.isStringLiteral(init)) {
|
|
91
|
+
return init.value;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 其他情况(表达式、模板、多变量等)不支持
|
|
97
|
+
return null;
|
|
39
98
|
}
|
|
40
99
|
|
|
41
|
-
// =====
|
|
100
|
+
// ===== 单文件转换函数 =====
|
|
42
101
|
const transformToChinese = (code, globalList) => {
|
|
43
102
|
let localList = {}; // 每个文件独立的 list
|
|
44
103
|
|
|
@@ -49,19 +108,22 @@ const transformToChinese = (code, globalList) => {
|
|
|
49
108
|
|
|
50
109
|
const langVariableNames = new Set();
|
|
51
110
|
|
|
52
|
-
//
|
|
111
|
+
// 第一阶段:收集 getLang / getLanguage 的变量名
|
|
53
112
|
traverse(ast, {
|
|
54
113
|
VariableDeclarator(path) {
|
|
55
114
|
const { id, init } = path.node;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
115
|
+
if (
|
|
116
|
+
t.isIdentifier(id) &&
|
|
117
|
+
t.isCallExpression(init) &&
|
|
118
|
+
t.isIdentifier(init.callee) &&
|
|
119
|
+
['getLanguage', 'getLang'].includes(init.callee.name)
|
|
120
|
+
) {
|
|
59
121
|
langVariableNames.add(id.name);
|
|
60
122
|
}
|
|
61
123
|
}
|
|
62
124
|
});
|
|
63
125
|
|
|
64
|
-
//
|
|
126
|
+
// 第二阶段:处理对象和成员表达式
|
|
65
127
|
traverse(ast, {
|
|
66
128
|
ObjectExpression(path) {
|
|
67
129
|
const props = path.node.properties;
|
|
@@ -70,53 +132,40 @@ const transformToChinese = (code, globalList) => {
|
|
|
70
132
|
const cnProp = props.find(
|
|
71
133
|
(prop) => t.isObjectProperty(prop) && !prop.computed && isKeyNamed(prop.key, 'cn') && !prop.method
|
|
72
134
|
);
|
|
73
|
-
|
|
74
135
|
const enProp = props.find(
|
|
75
136
|
(prop) => t.isObjectProperty(prop) && !prop.computed && isKeyNamed(prop.key, 'en') && !prop.method
|
|
76
137
|
);
|
|
77
|
-
|
|
78
|
-
// 仅当
|
|
79
|
-
if (cnProp && enProp
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
138
|
+
|
|
139
|
+
// 仅当 en 是字符串字面量,且 cn 符合提取规则时,才记录
|
|
140
|
+
if (cnProp && enProp) {
|
|
141
|
+
const enStr = extractEnValue(enProp.value, path.scope);
|
|
142
|
+
const cnKey = extractCnKey(cnProp.value, path.scope);
|
|
143
|
+
|
|
144
|
+
if (cnKey !== null && enStr !== null) {
|
|
145
|
+
localList[cnKey] = enStr;
|
|
146
|
+
}
|
|
85
147
|
}
|
|
86
148
|
},
|
|
87
149
|
|
|
88
150
|
MemberExpression(path) {
|
|
89
|
-
|
|
90
151
|
const node = path.node;
|
|
91
152
|
if (!node.computed) return;
|
|
92
|
-
|
|
93
|
-
// 处理 obj[langVar]
|
|
94
|
-
if (t.isIdentifier(node.property) && langVariableNames.has(node.property.name)) {
|
|
95
|
-
path.replaceWith(node.object);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
153
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
t.isCallExpression(node.property) &&
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
) {
|
|
105
|
-
path.replaceWith(node.object);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
},
|
|
154
|
+
const isLangAccess =
|
|
155
|
+
(t.isIdentifier(node.property) && langVariableNames.has(node.property.name)) ||
|
|
156
|
+
(t.isCallExpression(node.property) &&
|
|
157
|
+
t.isIdentifier(node.property.callee) &&
|
|
158
|
+
['getLang', 'getLanguage'].includes(node.property.callee.name));
|
|
109
159
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
parent.remove();
|
|
116
|
-
}
|
|
160
|
+
if (isLangAccess) {
|
|
161
|
+
// 替换为 obj['cn']
|
|
162
|
+
path.replaceWith(
|
|
163
|
+
t.memberExpression(node.object, t.stringLiteral('cn'), true)
|
|
164
|
+
);
|
|
117
165
|
}
|
|
118
166
|
},
|
|
119
167
|
|
|
168
|
+
// 清理无用的 lang 变量声明
|
|
120
169
|
VariableDeclarator(path) {
|
|
121
170
|
const { id } = path.node;
|
|
122
171
|
if (t.isIdentifier(id) && langVariableNames.has(id.name)) {
|
|
@@ -129,16 +178,26 @@ const transformToChinese = (code, globalList) => {
|
|
|
129
178
|
}
|
|
130
179
|
}
|
|
131
180
|
}
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
Identifier(path) {
|
|
184
|
+
if (langVariableNames.has(path.node.name)) {
|
|
185
|
+
const parent = path.parentPath;
|
|
186
|
+
if (parent.isExpressionStatement()) {
|
|
187
|
+
parent.remove();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
132
190
|
}
|
|
133
191
|
});
|
|
134
192
|
|
|
135
|
-
// 第三阶段:清理 getLanguage 导入
|
|
193
|
+
// 第三阶段:清理 getLang / getLanguage 导入
|
|
136
194
|
traverse(ast, {
|
|
137
195
|
ImportDeclaration(path) {
|
|
138
196
|
const specifiers = path.node.specifiers;
|
|
139
197
|
const newSpecifiers = specifiers.filter((spec) => {
|
|
140
198
|
if (t.isImportSpecifier(spec)) {
|
|
141
|
-
return !t.isIdentifier(spec.imported, { name: 'getLanguage' }) &&
|
|
199
|
+
return !t.isIdentifier(spec.imported, { name: 'getLanguage' }) &&
|
|
200
|
+
!t.isIdentifier(spec.imported, { name: 'getLang' });
|
|
142
201
|
}
|
|
143
202
|
return true;
|
|
144
203
|
});
|
|
@@ -157,17 +216,15 @@ const transformToChinese = (code, globalList) => {
|
|
|
157
216
|
comments: false
|
|
158
217
|
}).code;
|
|
159
218
|
|
|
160
|
-
//
|
|
161
|
-
|
|
219
|
+
// 合并到全局翻译表
|
|
162
220
|
Object.assign(globalList, localList);
|
|
163
221
|
return transformedCode;
|
|
164
|
-
}
|
|
222
|
+
};
|
|
165
223
|
|
|
166
|
-
// ===== ESLint
|
|
224
|
+
// ===== ESLint 实例(用于格式化和清理未使用导入)=====
|
|
167
225
|
const eslint = new ESLint({
|
|
168
226
|
fix: true,
|
|
169
227
|
cwd: process.cwd(),
|
|
170
|
-
useEslintrc: false,
|
|
171
228
|
plugins: {
|
|
172
229
|
'unused-imports': unusedImports
|
|
173
230
|
},
|
|
@@ -180,7 +237,6 @@ const eslint = new ESLint({
|
|
|
180
237
|
}
|
|
181
238
|
},
|
|
182
239
|
overrideConfig: {
|
|
183
|
-
extends: ["tongdun/react"],
|
|
184
240
|
plugins: ["td-rules-plugin"],
|
|
185
241
|
rules: {
|
|
186
242
|
'prettier/prettier': [
|
|
@@ -199,7 +255,7 @@ const eslint = new ESLint({
|
|
|
199
255
|
}
|
|
200
256
|
});
|
|
201
257
|
|
|
202
|
-
// ===== 递归读取所有 .js
|
|
258
|
+
// ===== 递归读取所有 .js 文件(排除 index.js)=====
|
|
203
259
|
const getAllJsFiles = (dir) => {
|
|
204
260
|
let results = [];
|
|
205
261
|
const files = fs.readdirSync(dir);
|
|
@@ -208,14 +264,13 @@ const getAllJsFiles = (dir) => {
|
|
|
208
264
|
const fullPath = path.join(dir, file);
|
|
209
265
|
const stat = fs.statSync(fullPath);
|
|
210
266
|
if (stat.isDirectory()) {
|
|
211
|
-
results = results.concat(getAllJsFiles(fullPath));
|
|
212
|
-
} else if (file.endsWith('.js') &&
|
|
267
|
+
results = results.concat(getAllJsFiles(fullPath));
|
|
268
|
+
} else if (file.endsWith('.js') && file !== 'index.js') {
|
|
213
269
|
results.push(fullPath);
|
|
214
270
|
}
|
|
215
271
|
}
|
|
216
|
-
|
|
217
272
|
return results;
|
|
218
|
-
}
|
|
273
|
+
};
|
|
219
274
|
|
|
220
275
|
// ===== 处理单个文件 =====
|
|
221
276
|
const processFile = async (filePath, globalList) => {
|
|
@@ -226,9 +281,11 @@ const processFile = async (filePath, globalList) => {
|
|
|
226
281
|
const finalCode = results[0].output || transformedCode;
|
|
227
282
|
|
|
228
283
|
fs.writeFileSync(filePath, finalCode, 'utf-8');
|
|
229
|
-
}
|
|
284
|
+
};
|
|
230
285
|
|
|
286
|
+
// ===== 导出接口 =====
|
|
231
287
|
module.exports = {
|
|
232
288
|
processFile,
|
|
233
|
-
getAllJsFiles
|
|
234
|
-
|
|
289
|
+
getAllJsFiles,
|
|
290
|
+
transformToChinese // 便于单元测试
|
|
291
|
+
};
|