@tenado/i18n-cli
Version:
i18n-cli是一个自动国际化脚本,通过执行命令,自动提取代码里面的中文,自动调用百度或谷歌翻译接口,自动将翻译结果以 key-value 形式存入*.json 语言包里
282 lines (279 loc) • 10.1 kB
JavaScript
const { declare } = require("@babel/helper-plugin-utils");
const generate = require("@babel/generator").default;
const isChinese = require("../utils/isChinese.js");
const generateKey = require("../utils/generateKey.js");
const regex = require("../utils/regex.js");
module.exports = declare((api, options) => {
api.assertVersion(7);
const { localData, needTranslate } = options;
const {
i18nObject,
i18nImport,
i18nMethod,
keyShowOrigin,
needImport = true,
ignoreText,
ignoreMethods,
isVueTemplate,
} = options.options;
const replaceExp = (api, path, value, label) => {
const expressionParams = path.isTemplateLiteral()
? path.node.expressions.map((item) => generate(item).code)
: null;
const methodName = isVueTemplate
? `$${i18nMethod}`
: i18nObject
? `${i18nObject}.${i18nMethod}`
: i18nMethod;
let replaceExpression = api.template.ast(
`${methodName}('${value}'${
expressionParams ? "," + "[" + expressionParams.join(",") + "]" : ""
}${keyShowOrigin ? ",'" + label + "'" : ""}
)`
).expression;
if (
path.findParent((p) => p.isJSXAttribute()) &&
!path.findParent((p) => p.isJSXExpressionContainer())
) {
replaceExpression = api.types.JSXExpressionContainer(replaceExpression);
}
return replaceExpression;
};
const replaceJsx = (api, path, value, label) => {
const expressionParams = path.isTemplateLiteral()
? path.node.expressions.map((item) => generate(item).code)
: null;
const methodName = isVueTemplate
? `$${i18nMethod}`
: i18nObject
? `${i18nObject}.${i18nMethod}`
: i18nMethod;
let replaceExpression = api.template.ast(
`${methodName}('${value}'${
expressionParams ? "," + "[" + expressionParams.join(",") + "]" : ""
}${keyShowOrigin ? ",'" + label + "'" : ""}
)`
).expression;
return api.types.JSXExpressionContainer(replaceExpression);
};
const cacheKeyFunc = (key, value) => {
if (!localData[key]) {
needTranslate[key] = value;
options.options.hasTransform = true;
}
};
return {
visitor: {
Program: {
enter(path, state) {
// 判断是否已经引入了多语言包
path.traverse({
ImportDeclaration(p) {
const source = p.node.source.value;
if (source === i18nObject) {
state.imported = true;
}
},
});
// 如果注释包含ignoreText,则不转换
path.traverse({
"StringLiteral|TemplateLiteral"(path) {
let leadingComments = path.node.leadingComments;
// 如果是对象属性的值,例如default: "测试"
if (path?.parent?.type === "ObjectProperty") {
leadingComments = path?.parent?.leadingComments;
}
// 如果是赋值语句,例如this.text = "测试"
if (path?.parent?.type === "AssignmentExpression") {
leadingComments = path?.parentPath?.parent?.leadingComments;
}
if (leadingComments) {
path.node.leadingComments = leadingComments.filter(
(comment, index) => {
if (comment.value.includes(ignoreText)) {
path.node.skipTransform = true;
return false;
}
return true;
}
);
}
if (path.findParent((p) => p.isImportDeclaration())) {
path.node.skipTransform = true;
}
// 如果中文在类型声明里面,则跳过转换
if (path.parent.type === "TSLiteralType") {
path.node.skipTransform = true;
}
},
});
},
// 如果有引用,则不需要重新引用
// 如果没有需要翻译的汉字,则不需要引用
// 如果配置不需要引用,则不需要引用
exit(path, state) {
if (!state.imported && state.shouldImport && needImport) {
const ast = api.template.ast(`${i18nImport}`);
path.node.body.unshift(ast);
}
},
},
// 支持vue里面{{ "测试卷" }}、v-tooltip="'你好'"类型的翻译
DirectiveLiteral(path) {
if (path.node.skipTransform) return;
const label = path.node.value;
if (isChinese(label)) {
const key = generateKey(label, options.options);
cacheKeyFunc(key, label);
const t = api.types;
const methodName = isVueTemplate
? `$${i18nMethod}`
: `${i18nObject}.${i18nMethod}`;
const callExpression = t.callExpression(t.identifier(methodName), [
t.stringLiteral(key),
]);
const newStatement = t.expressionStatement(callExpression);
path.parentPath.insertBefore(newStatement);
path.parentPath.remove();
path.skip();
}
},
StringLiteral(path, state) {
if (path.node.skipTransform) return;
const label = path.node.value;
const isHtmlAutoCloseTag = regex.htmlTagAutoClose?.test(label);
// 如果中文作为函数的参数,则跳过
// if(path?.parent?.type === "CallExpression" && path?.parent?.callee?.type !== "MemberExpression"){
// path.skip();
// }
// path.key值为key,说明对象的key是中文,需要跳过
if (isChinese(label) && path.key !== "key" && !isHtmlAutoCloseTag) {
const key = generateKey(label, options.options);
cacheKeyFunc(key, label);
const expression = replaceExp(api, path, key, label);
path.replaceWith(expression);
path.skip();
state.shouldImport = true;
}
},
TemplateLiteral(path, state) {
if (path.node.skipTransform) return;
let index = 0;
// 处理quasis转换为变量bug,quasis表示变量两边的内容
// 如${a1}a${a2}${a3}${a4}b => 在变量两边存在5个quasis位置
// 如a${a2}b => 在变量两边存在2个quasis位置
// 如果存在连续的空quasis,则在非空的quasis之前添加一个空quasis
const quasis = path.get("quasis");
const newQuasis = [];
let lastQuasis = null;
let isContinueEmptyQuasis = false;
quasis.forEach((item, index) => {
if (!!item.node.value.raw) {
if (isContinueEmptyQuasis) {
newQuasis.push(quasis[index - 1]);
}
newQuasis.push(item);
lastQuasis = item;
isContinueEmptyQuasis = false;
} else {
if (!lastQuasis?.node?.value?.raw && lastQuasis !== null) {
isContinueEmptyQuasis = true;
} else {
isContinueEmptyQuasis = false;
}
newQuasis.push(item);
lastQuasis = item;
}
});
const label = newQuasis
.map((item) => {
if (!!item.node.value.raw) {
return item.node.value.raw;
} else {
let result = "{" + index + "}";
index++;
return result;
}
})
.join("");
const isHtmlAutoCloseTag = regex.htmlTagAutoClose?.test(label);
if (label && isChinese(label) && !isHtmlAutoCloseTag) {
const key = generateKey(label, options.options);
cacheKeyFunc(key, label);
const expression = replaceExp(api, path, key, label);
path.replaceWith(expression);
path.skip();
state.shouldImport = true;
}
},
CallExpression(path, state) {
if (path.node.skipTransform) return;
const {
type,
name,
object = {},
property = {},
} = path.node.callee ?? {};
const methodName = isVueTemplate ? `$${i18nMethod}` : i18nMethod;
// 如果是已经翻译的
if (type === "Identifier" && name === methodName) {
path.skip();
}
if (
type === "MemberExpression" &&
object.name === i18nObject &&
property.name === methodName
) {
path.skip();
}
// 如果包含忽略的方法名
if (type === "Identifier" && ignoreMethods.includes(name)) {
path.skip();
}
const _MemberExpression = `${object.name}.${property.name}`;
if (
type === "MemberExpression" &&
ignoreMethods.includes(_MemberExpression)
) {
path.skip();
}
},
ObjectExpression(path, state) {
if (path.node.skipTransform) return;
const label = path.node.value;
if (isChinese(label)) {
const key = generateKey(label, options.options);
cacheKeyFunc(key, label);
const expression = replaceExp(api, path, key, label);
path.replaceWith(expression);
path.skip();
state.shouldImport = true;
}
},
JSXText(path, state) {
if (path.node.skipTransform) return;
const label = path?.node?.value?.trim();
if (isChinese(label)) {
const key = generateKey(label, options.options);
cacheKeyFunc(key, label);
const expression = replaceJsx(api, path, key, label);
path.replaceWith(expression);
path.skip();
state.shouldImport = true;
}
},
JSXAttribute(path, state) {
if (path.node.skipTransform) return;
const label = path.node.value?.value;
if (isChinese(label)) {
const key = generateKey(label, options.options);
cacheKeyFunc(key, label);
const expression = replaceJsx(api, path, key, label);
path.node.value = expression;
path.skip();
state.shouldImport = true;
}
},
},
};
});