i18n-auto-react
Version:
基于百度翻译API服务的自动翻译插件
220 lines (216 loc) • 9.02 kB
JavaScript
;
var path = require('path');
var fs = require('fs');
var utils_index = require('../utils/index.js');
var axios = require('axios');
var crypto = require('crypto');
var t = require('@babel/types');
var _traverse = require('@babel/traverse');
var utils_disableRule = require('../utils/disableRule.js');
var utils_tpl = require('../utils/tpl.js');
var utils_log = require('../utils/log.js');
const traverse = utils_index.getDefault(_traverse);
class Translate {
config;
languages = {};
newLanguages = {};
chinese = {};
translateTable = {}; // 存放待翻译列表,按语种为key值存放
translateSource = {}; // 按语种为key值存放当前语种已有的语料清单(含已翻译的语料)
constructor(config) {
this.config = config;
}
readLanguagesConfig() {
const { languages, __rootPath, output, template = utils_tpl.defaultTpl } = this.config;
languages.forEach((name) => {
const { dir, ext = 'js' } = output;
let dirPath = path.resolve(__rootPath, dir, `${name}.${ext}`);
if (!fs.existsSync(dirPath)) {
utils_index.createLanguageFile(dirPath, template);
this.languages[name] = {};
}
else {
this.languages[name] = utils_index.readLanguages(name, this.config);
}
this.newLanguages[name] = [];
});
}
readFile() {
const dir = this.config.entry;
if (typeof dir === 'string') {
this.readFiles(dir);
}
else if (Array.isArray(dir)) {
dir.forEach((d) => this.readFiles(d));
}
}
readFiles(dir) {
const __rootPath = this.config.__rootPath;
const dirPath = path.resolve(__rootPath, dir);
utils_index.scanFile(dirPath, this.config, (path) => {
utils_log.logger.info(`发现文件: ${path}`);
const code = fs.readFileSync(path, { encoding: 'utf8' });
this.babelOpt(code, path);
});
}
babelOpt(code, file) {
const importInfo = this.config.importInfo;
const ast = utils_index.babelParse(code);
if (!ast)
return;
const _this = this;
// 注释禁用
let disableRule = new utils_disableRule.DisableRule(ast.comments || []);
if (disableRule.entireFileDisabled)
return;
traverse(ast, {
// jsx文本: <div>花飘万家雪</div>
JSXText(path) {
if (utils_index.isCallExpression(path, importInfo))
return;
if (disableRule.test(path.node.loc))
return;
if (utils_tpl.zhExt.test(path.toString())) {
const value = path.toString().trim();
const id = utils_index.md5Hash(value);
_this.validateKey(value, id, file);
}
},
// 模板文本: `花飘万家雪${xxx}`
TemplateLiteral(path) {
if (utils_index.isCallExpression(path, importInfo))
return;
if (disableRule.test(path.node.loc))
return;
if (utils_tpl.zhExt.test(path.toString())) {
const node = path.node;
let isCh = false;
node.quasis?.forEach?.((item) => utils_tpl.zhExt.test(item.value.raw) && (isCh = true));
if (isCh) {
let i = 0;
let value = path
.toString()
.replace(/^`|`$/g, '')
.replace(utils_index.tplRegexp, () => `{{@${++i}}}`);
const id = utils_index.md5Hash(value);
_this.validateKey(value, id, file);
}
}
},
// 普通文本: '花飘万家雪'
StringLiteral(path) {
// 如果父节点是 ts 则不处理, 如 type AA = '你好' | '大家好'
if (t.isTSLiteralType(path.parent))
return;
if (utils_index.isCallExpression(path, importInfo))
return;
if (disableRule.test(path.node.loc))
return;
if (utils_tpl.zhExt.test(path.toString())) {
const value = path.node.value.toString();
const id = utils_index.md5Hash(value);
_this.validateKey(value, id, file);
}
}
});
}
validateKey(zh, key, file) {
for (let languageKey in this.languages) {
if (!this.languages[languageKey][key] &&
!this.newLanguages[languageKey].find((n) => n.id === key)) {
this.newLanguages[languageKey].push({
chinese: zh,
id: key,
file
});
}
}
}
createChineseFile() {
let newData = (this.newLanguages.zh || []).reduce((p, c) => ((p[c.id] = c.chinese), p), {});
const { output, template = utils_tpl.defaultTpl, __rootPath } = this.config;
let curFilePath = path.resolve(__rootPath, output.dir, `zh.${output.ext || 'js'}`);
utils_index.createLanguageFile(curFilePath, template, Object.assign(this.languages.zh, newData));
}
// 分割语料,按50个次分割
sliceWords(words) {
const len = words.length;
const res = [];
let i = 0;
const resLen = 50;
while (i < len) {
res.push(words.slice(i, i + resLen));
i += resLen;
}
return res;
}
formatChinese(word) {
return word.chinese.replace(/(\{\{(\@[0-9]+?)\}\})/g, (...args) => args[2]);
}
async translate() {
const { server, output, template = utils_tpl.defaultTpl, __rootPath } = this.config;
const curQps = server.qps || 1; // 翻译API有QPS限制s
for (let curLanguage in this.newLanguages) {
if (curLanguage === 'zh')
continue;
const words = this.newLanguages[curLanguage];
words.length && utils_log.logger.info(`开始翻译 ${curLanguage}`, 'green', true);
// 词条分成50个来翻译
const list = this.sliceWords(words);
const newData = {};
for (let item of list) {
if (!item.length)
continue;
const data = await this.requestTranslate(item.map((n) => this.formatChinese(n)).join('\n'), curLanguage);
for (let i = 0; i < data.length; i++) {
const target = item.find((n) => this.formatChinese(n) === data[i].src);
const translated = data[i].dst.replace(/\@\s*([0-9]+)/g, (_$1, $2) => `{{@${$2}}}`);
if (target) {
utils_log.logger.info(`${target.chinese} => ${translated}`, 'white');
newData[target.id] = translated;
}
}
const sleepTime = Math.round(1000 / curQps);
if (sleepTime > 0)
await utils_index.sleep(sleepTime);
}
let curFilePath = path.resolve(__rootPath, output.dir, `${curLanguage}.${output.ext || 'js'}`);
utils_index.createLanguageFile(curFilePath, template, Object.assign(this.languages[curLanguage], newData));
}
}
async requestTranslate(word, targetLanguage) {
const { appId, key } = this.config.server;
const salt = Date.now().toString();
const from = 'zh';
const to = targetLanguage || 'en';
const md5 = crypto.createHash('md5');
// @ts-ignore
md5.update(Buffer.from(appId + word + salt + key));
const sign = md5.digest('hex');
const res = await axios({
method: 'POST',
baseURL: 'http://api.fanyi.baidu.com',
url: '/api/trans/vip/translate',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: { q: word, from, to, salt, sign, appid: appId }
});
let trans_result = '';
if (res.status === 200) {
if (res.data.error_code) {
let msg = utils_tpl.ERROE_CODE_MAP[res.data.error_code];
utils_log.logger.error(msg);
throw new Error(res.data.error_msg);
}
trans_result = res.data.trans_result;
}
return trans_result;
}
async run() {
this.readLanguagesConfig(); // 读取旧语言包
this.readFile(); // 扫描文件中的中文
this.createChineseFile(); // 更新中文语言包
await this.translate(); // 翻译其他语言
utils_log.logger.info('翻译完成!', 'green', true);
}
}
exports.Translate = Translate;