@agentscope-ai/i18n 0.1.2
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 +545 -0
- package/lib/cli.js +224 -0
- package/lib/core/ast-processor.js +75 -0
- package/lib/core/file-processor.js +397 -0
- package/lib/core/translator.js +924 -0
- package/lib/i18n.ts +134 -0
- package/lib/index.js +12 -0
- package/lib/parse-jsx.js +380 -0
- package/lib/services/dashscope.js +118 -0
- package/lib/services/dashscopeMT.js +182 -0
- package/lib/utils/ast-utils.js +100 -0
- package/lib/utils/cli-utils.js +19 -0
- package/lib/utils/excel-utils.js +124 -0
- package/lib/utils/file-utils.js +280 -0
- package/lib/utils/medusa.js +228 -0
- package/lib/utils/translation-utils.js +572 -0
- package/package.json +66 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const queryString = require('querystring');
|
|
4
|
+
const babylon = require('@babel/parser');
|
|
5
|
+
const traverse = require('@babel/traverse').default;
|
|
6
|
+
const generator = require('@babel/generator').default;
|
|
7
|
+
const axios = require('axios');
|
|
8
|
+
|
|
9
|
+
// Medusa 配置
|
|
10
|
+
const MEDUSA = {
|
|
11
|
+
host: 'http://mcms-portal.alibaba-inc.com',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// 获取 Medusa 数据的函数
|
|
15
|
+
async function getMedusaData(url) {
|
|
16
|
+
try {
|
|
17
|
+
const response = await axios.get(url, {
|
|
18
|
+
// 处理自签名证书问题
|
|
19
|
+
httpsAgent: new (require('https').Agent)({
|
|
20
|
+
rejectUnauthorized: false,
|
|
21
|
+
keepAlive: false,
|
|
22
|
+
}),
|
|
23
|
+
// 设置超时时间
|
|
24
|
+
timeout: 30000,
|
|
25
|
+
});
|
|
26
|
+
return response.data;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('获取 Medusa 数据失败:', error.message);
|
|
29
|
+
if (error.response) {
|
|
30
|
+
console.error('HTTP 状态码:', error.response.status);
|
|
31
|
+
console.error('响应数据:', error.response.data);
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 包文件名映射
|
|
38
|
+
const PACKAGE_FILE_NAME = {
|
|
39
|
+
json: (lang) => `${lang}.json`,
|
|
40
|
+
android: (lang) => `strings_${lang}.xml`,
|
|
41
|
+
ios: (lang) => `Localizable_${lang}.strings`,
|
|
42
|
+
aliyun: (lang) => `${lang}.json`,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// 记录函数
|
|
46
|
+
const recorder = (type, action, data) => {
|
|
47
|
+
console.log(`[${type}] ${action}:`, data);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// 格式化函数
|
|
51
|
+
const format = async ({ text, filePath }) => {
|
|
52
|
+
// 简单的格式化,实际项目可以使用 prettier 或其他格式化工具
|
|
53
|
+
try {
|
|
54
|
+
const prettier = require('prettier');
|
|
55
|
+
return prettier.format(text, {
|
|
56
|
+
parser: 'babel',
|
|
57
|
+
singleQuote: true,
|
|
58
|
+
semi: true,
|
|
59
|
+
trailingComma: 'es5',
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
// 如果 prettier 不可用,返回原始文本
|
|
63
|
+
return text;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* get language list under app name
|
|
69
|
+
* @param {string} appName medusa app name
|
|
70
|
+
*/
|
|
71
|
+
async function fetchLanguageList(appName) {
|
|
72
|
+
const url = `${MEDUSA.host}/openapi/languageList?appName=${appName}`;
|
|
73
|
+
const list = await getMedusaData(url);
|
|
74
|
+
|
|
75
|
+
return list.data;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* fetch language package from medusa
|
|
80
|
+
* @param {string} appName medusa app name
|
|
81
|
+
* @param {string} tag medusa tag name
|
|
82
|
+
* @param {string} type package type , for example android, ios, json
|
|
83
|
+
* @param {string} lang language tag , for example zh,en
|
|
84
|
+
*/
|
|
85
|
+
async function fetchPackage(appName, tag, type, lang) {
|
|
86
|
+
if (type === 'json') type = 'aliyun'; // TODO: medusa has a bug for json type, use aliyun instead
|
|
87
|
+
const query = {
|
|
88
|
+
appName,
|
|
89
|
+
tagName: tag,
|
|
90
|
+
type,
|
|
91
|
+
language: lang,
|
|
92
|
+
};
|
|
93
|
+
const url = `${MEDUSA.host}/openapi/export?${queryString.stringify(query)}`;
|
|
94
|
+
const langPackage = await getMedusaData(url);
|
|
95
|
+
|
|
96
|
+
return langPackage;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function getPackage(info, config) {
|
|
100
|
+
const standardLangTag = info.locale.replace('_', '-').toLowerCase();
|
|
101
|
+
|
|
102
|
+
const medusaAppName = config.medusa.appName;
|
|
103
|
+
const medusaTagName = config.medusa.tag;
|
|
104
|
+
let langPackage = await fetchPackage(medusaAppName, medusaTagName, config.type, info.locale);
|
|
105
|
+
if (typeof langPackage === 'object') {
|
|
106
|
+
langPackage = JSON.stringify(langPackage, null, 2);
|
|
107
|
+
}
|
|
108
|
+
let dir = path.resolve(config.cwd, config.path);
|
|
109
|
+
if (config.type === 'json') {
|
|
110
|
+
const stringsPath = path.resolve(dir, 'strings');
|
|
111
|
+
if (fs.existsSync(stringsPath)) {
|
|
112
|
+
dir = stringsPath;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const langPath = path.resolve(dir, PACKAGE_FILE_NAME[config.type](standardLangTag));
|
|
117
|
+
|
|
118
|
+
// 处理现有文件
|
|
119
|
+
let finalPackage = langPackage;
|
|
120
|
+
if (fs.existsSync(langPath)) {
|
|
121
|
+
console.log(` [信息] 发现现有文件: ${langPath}`);
|
|
122
|
+
|
|
123
|
+
if (config.mergeExisting !== false) {
|
|
124
|
+
try {
|
|
125
|
+
const existingContent = fs.readFileSync(langPath, { encoding: 'utf8' });
|
|
126
|
+
let existingPackage = {};
|
|
127
|
+
|
|
128
|
+
if (config.type === 'json') {
|
|
129
|
+
existingPackage = JSON.parse(existingContent);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 合并现有内容和新内容(新内容优先)
|
|
133
|
+
if (typeof langPackage === 'string') {
|
|
134
|
+
const newPackage = JSON.parse(langPackage);
|
|
135
|
+
const mergedPackage = { ...existingPackage, ...newPackage };
|
|
136
|
+
finalPackage = JSON.stringify(mergedPackage, null, 2);
|
|
137
|
+
console.log(
|
|
138
|
+
` [合并] 合并了 ${Object.keys(existingPackage).length} 个现有条目和 ${Object.keys(newPackage).length} 个新条目`,
|
|
139
|
+
);
|
|
140
|
+
} else {
|
|
141
|
+
finalPackage = { ...existingPackage, ...langPackage };
|
|
142
|
+
console.log(` [合并] 合并了现有内容和新内容`);
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.log(` [警告] 无法解析现有文件,将覆盖: ${error.message}`);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
console.log(` [覆盖] 配置为覆盖现有文件`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fs.ensureFileSync(langPath);
|
|
153
|
+
fs.writeFileSync(langPath, finalPackage, { encoding: 'utf8' });
|
|
154
|
+
return {
|
|
155
|
+
package: finalPackage,
|
|
156
|
+
langTag: standardLangTag,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// auto import i18n/string/index.js content
|
|
161
|
+
async function autoImport(requestList, config) {
|
|
162
|
+
const indexFilePath = path.resolve(config.cwd, config.path, 'strings', 'index.js');
|
|
163
|
+
if (!fs.existsSync(indexFilePath)) return;
|
|
164
|
+
const oldFile = fs.readFileSync(indexFilePath, { encoding: 'utf8' });
|
|
165
|
+
const ast = babylon.parse(oldFile);
|
|
166
|
+
const properties = [];
|
|
167
|
+
traverse(ast, {
|
|
168
|
+
ObjectProperty(nodePath) {
|
|
169
|
+
properties.push(nodePath.node.key.value);
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
requestList.forEach((l) => {
|
|
173
|
+
if (properties.indexOf(l.langTag) === -1) {
|
|
174
|
+
const expression = `name={"${l.langTag}":require("./${l.langTag}.json")}`;
|
|
175
|
+
const ConfigAst = babylon.parseExpression(expression);
|
|
176
|
+
ast.program.body[0].expression.right.properties.push(ConfigAst.right.properties[0]);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
const configJS = generator(ast).code;
|
|
180
|
+
const prettyCode = await format({ text: configJS, filePath: indexFilePath });
|
|
181
|
+
fs.writeFileSync(indexFilePath, prettyCode);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function run(config) {
|
|
185
|
+
// download file from medusa
|
|
186
|
+
const medusaAppName = config.medusa.appName;
|
|
187
|
+
const langList = await fetchLanguageList(medusaAppName);
|
|
188
|
+
const requestList = langList.map((l) => {
|
|
189
|
+
return getPackage(l, config);
|
|
190
|
+
});
|
|
191
|
+
const result = await Promise.all(requestList);
|
|
192
|
+
await autoImport(result, config);
|
|
193
|
+
if (config.type === 'json') {
|
|
194
|
+
// output all language
|
|
195
|
+
const allLang = {};
|
|
196
|
+
result.forEach((l) => {
|
|
197
|
+
if (typeof l.package === 'string' && l.package) {
|
|
198
|
+
allLang[l.langTag] = JSON.parse(l.package);
|
|
199
|
+
} else {
|
|
200
|
+
allLang[l.langTag] = l.package;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
const allPath = path.resolve(config.cwd, config.path, 'strings.json');
|
|
204
|
+
if (fs.pathExistsSync(allPath)) {
|
|
205
|
+
// TODO add increment notification
|
|
206
|
+
fs.writeJSONSync(allPath, allLang, { spaces: 2 });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let copyNumber = 0;
|
|
211
|
+
result.forEach((l) => {
|
|
212
|
+
copyNumber += Object.keys(l.package).length;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
recorder('must', 'import', {
|
|
216
|
+
copywriting: copyNumber,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = {
|
|
221
|
+
fetchLanguageList,
|
|
222
|
+
fetchPackage,
|
|
223
|
+
getPackage,
|
|
224
|
+
autoImport,
|
|
225
|
+
run,
|
|
226
|
+
MEDUSA,
|
|
227
|
+
PACKAGE_FILE_NAME,
|
|
228
|
+
};
|