i18n-transform-cli
Version:
这是一款能够自动将代码里的中文转成i18n国际化标记的命令行工具。当然,你也可以用它实现将中文语言包自动翻译成其他语言。适用于vue2、vue3和react
214 lines (213 loc) • 11.5 kB
JavaScript
;
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _Translator_provider, _Translator_targetLocale, _Translator_providerOptions, _Translator_textLengthLimit, _Translator_separator;
Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = __importDefault(require("fs-extra"));
const i18n_translate_utils_1 = require("i18n-translate-utils");
const getAbsolutePath_1 = require("./utils/getAbsolutePath");
const log_1 = __importDefault(require("./utils/log"));
const constants_1 = require("./utils/constants");
const getLang_1 = __importDefault(require("./utils/getLang"));
const stateManager_1 = __importDefault(require("./utils/stateManager"));
const saveLocaleFile_1 = require("./utils/saveLocaleFile");
const flatObjectDeep_1 = require("./utils/flatObjectDeep");
async function translateByGoogle(word, locale, options) {
var _a;
if (!options.google || !((_a = options.google) === null || _a === void 0 ? void 0 : _a.proxy)) {
log_1.default.error('翻译失败,当前翻译器为谷歌,请完善google配置参数');
process.exit(1);
}
try {
return await (0, i18n_translate_utils_1.googleTranslate)(word, 'zh-CN', locale, options.google.proxy);
}
catch (e) {
if (e.name === 'TooManyRequestsError') {
log_1.default.error('翻译失败,请求超过谷歌api调用次数限制');
}
else {
log_1.default.error('谷歌翻译请求出错', e);
}
return '';
}
}
async function translateByYoudao(word, locale, options) {
var _a, _b;
if (!options.youdao || !((_a = options.youdao) === null || _a === void 0 ? void 0 : _a.key) || !((_b = options.youdao) === null || _b === void 0 ? void 0 : _b.secret)) {
log_1.default.error('翻译失败,当前翻译器为有道,请完善youdao配置参数');
process.exit(1);
}
try {
return await (0, i18n_translate_utils_1.youdaoTranslate)(word, 'zh-CN', locale, options.youdao);
}
catch (e) {
log_1.default.error('有道翻译请求出错', e);
return '';
}
}
async function translateByBaidu(word, locale, options) {
var _a, _b;
if (!options.baidu || !((_a = options.baidu) === null || _a === void 0 ? void 0 : _a.key) || !((_b = options.baidu) === null || _b === void 0 ? void 0 : _b.secret)) {
log_1.default.error('翻译失败,当前翻译器为百度,请完善baidu配置参数');
process.exit(1);
}
try {
return await (0, i18n_translate_utils_1.baiduTranslate)(word, 'zh', locale, options.baidu);
}
catch (e) {
log_1.default.error('百度翻译请求出错', e);
return '';
}
}
async function default_1(localePath, locales, oldPrimaryLang, options) {
if (![constants_1.GOOGLE, constants_1.YOUDAO, constants_1.BAIDU].includes(options.translator || '')) {
log_1.default.error('翻译失败,请确认translator参数是否配置正确');
process.exit(1);
}
log_1.default.verbose('当前使用的翻译器:', options.translator);
const primaryLangPath = (0, getAbsolutePath_1.getAbsolutePath)(process.cwd(), localePath);
// 中文的翻译 json map
const newPrimaryLang = (0, flatObjectDeep_1.flatObjectDeep)((0, getLang_1.default)(primaryLangPath));
const localeFileType = stateManager_1.default.getToolConfig().localeFileType;
for (const targetLocale of locales) {
log_1.default.info(`正在翻译${targetLocale}语言包`);
const reg = new RegExp(`/[A-Za-z-]+.${localeFileType}`, 'g');
const targetPath = localePath.replace(reg, `/${targetLocale}.${localeFileType}`);
const targetLocalePath = (0, getAbsolutePath_1.getAbsolutePath)(process.cwd(), targetPath);
let oldTargetLangPack = {};
let newTargetLangPack = {};
if (fs_extra_1.default.existsSync(targetLocalePath)) {
oldTargetLangPack = (0, flatObjectDeep_1.flatObjectDeep)((0, getLang_1.default)(targetLocalePath));
}
else {
fs_extra_1.default.ensureFileSync(targetLocalePath);
}
const keyList = Object.keys(newPrimaryLang);
const willTranslateText = {};
for (const key of keyList) {
// 主语言同一个key的value不变,就复用原有的翻译结果
const oldLang = (0, flatObjectDeep_1.flatObjectDeep)(oldPrimaryLang);
const isNotChanged = oldLang[key] === newPrimaryLang[key];
if (isNotChanged && oldTargetLangPack[key]) {
newTargetLangPack[key] = oldTargetLangPack[key];
}
else {
willTranslateText[key] = newPrimaryLang[key];
}
}
console.log('willTranslateText', willTranslateText);
// 翻译新增键值对内容
const translator = new Translator({
provider: options.translator || constants_1.YOUDAO,
targetLocale,
providerOptions: options,
});
const incrementalTranslation = await translator.translate(willTranslateText);
newTargetLangPack = {
...newTargetLangPack,
...incrementalTranslation,
};
console.log('incrementalTranslation', incrementalTranslation);
// console.log('newTargetLangPack', newTargetLangPack);
// const fileContent = spreadObject(newTargetLangPack)
// console.log('fileContent', fileContent);
(0, saveLocaleFile_1.saveLocaleFile)(newTargetLangPack, targetLocalePath);
log_1.default.info(`完成${targetLocale}语言包翻译`);
}
}
exports.default = default_1;
class Translator {
constructor({ provider, targetLocale, providerOptions }) {
_Translator_provider.set(this, void 0);
_Translator_targetLocale.set(this, void 0);
_Translator_providerOptions.set(this, void 0);
_Translator_textLengthLimit.set(this, 5000);
_Translator_separator.set(this, '\n'); // 翻译文本拼接用的分隔符
switch (provider) {
case constants_1.YOUDAO:
__classPrivateFieldSet(this, _Translator_provider, translateByYoudao, "f");
break;
case constants_1.GOOGLE:
__classPrivateFieldSet(this, _Translator_provider, translateByGoogle, "f");
break;
case constants_1.BAIDU:
__classPrivateFieldSet(this, _Translator_provider, translateByBaidu, "f");
}
__classPrivateFieldSet(this, _Translator_targetLocale, targetLocale, "f");
__classPrivateFieldSet(this, _Translator_providerOptions, providerOptions, "f");
}
async translate(dictionary) {
const allTextArr = Object.keys(dictionary).map((key) => dictionary[key]);
let restTextBundleArr = allTextArr;
let startIndex = 0;
const result = [];
// 每轮循环,先判断key-value的字符数量
// 如果字符小于#textLengthLimit,以两倍速度递增,扩大翻译行数,以尽可能翻译更多的行数
// 如果字符大于#textLengthLimit,以两倍倍速度递减,扩小翻译行数,以尽可能翻译更多的行数
// 确定了行数后开始翻译,一直循环到翻译完所有行
while (startIndex < allTextArr.length && restTextBundleArr.length > 0) {
const maxTranslationCount = this.getMaxTranslationCount(restTextBundleArr);
const textBundleArr = allTextArr.slice(startIndex, startIndex + maxTranslationCount);
restTextBundleArr = allTextArr.slice(startIndex + maxTranslationCount);
startIndex = startIndex + maxTranslationCount;
const [res] = await Promise.all([
__classPrivateFieldGet(this, _Translator_provider, "f").call(this, textBundleArr.join(__classPrivateFieldGet(this, _Translator_separator, "f")), // 文本中可能有逗号,为了防止后面分割字符出错,使用\\$代替逗号
__classPrivateFieldGet(this, _Translator_targetLocale, "f"), __classPrivateFieldGet(this, _Translator_providerOptions, "f")),
new Promise((resolve) => setTimeout(resolve, 1000)), // 有道翻译接口限制每秒1次请求
]);
let resArr;
if (typeof res === 'object') {
resArr = res.map((item) => item.dst);
}
else {
resArr = res.split(__classPrivateFieldGet(this, _Translator_separator, "f"));
}
result.push(...resArr);
}
const incrementalTranslation = {};
Object.keys(dictionary).forEach((key, index) => {
// 翻译后有可能字符串前后会多出一个空格,这里做一下过滤
let translatedText = result[index] || '';
if (!dictionary[key].startsWith(' ') && translatedText.startsWith(' ')) {
translatedText = translatedText.slice(1);
}
if (!dictionary[key].endsWith(' ') && translatedText.endsWith(' ')) {
translatedText = translatedText.slice(0, -1);
}
incrementalTranslation[key] = translatedText;
});
return incrementalTranslation;
}
// 二分法查找最大翻译行数,不使用递归,避免异常情况下栈溢出
getMaxTranslationCount(textArr) {
const textNum = textArr.length;
let upper = textNum;
let lower = 1;
let pointer = 1;
while (upper - lower > 1) {
pointer = Math.floor((upper + lower) / 2);
const textBundleArr = textArr.slice(0, pointer);
const textBundleLength = textBundleArr.join(__classPrivateFieldGet(this, _Translator_separator, "f")).length;
if (textBundleLength <= __classPrivateFieldGet(this, _Translator_textLengthLimit, "f")) {
lower = Math.max(lower, pointer);
}
else {
upper = Math.min(upper, pointer);
}
}
return Math.max(pointer, 1);
}
}
_Translator_provider = new WeakMap(), _Translator_targetLocale = new WeakMap(), _Translator_providerOptions = new WeakMap(), _Translator_textLengthLimit = new WeakMap(), _Translator_separator = new WeakMap();