finish: extract

This commit is contained in:
zhaoyingbo 2022-10-14 17:07:31 +08:00
parent 9ddf81ab54
commit 39f7755468
27 changed files with 859 additions and 651 deletions

1
dist/const.js vendored
View File

@ -9,6 +9,7 @@ exports.CANARY_CONFIG_FILE = 'canary-config.json';
exports.PROJECT_CONFIG = {
dir: './.canary',
defaultConfig: {
isJsProj: false,
canaryDir: './.canary',
srcLang: 'zh-CN',
distLangs: ['en-US', 'zh-CN'],

2
dist/const.js.map vendored
View File

@ -1 +1 @@
{"version":3,"file":"const.js","sourceRoot":"","sources":["../src/const.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEU,QAAA,kBAAkB,GAAG,oBAAoB,CAAC;AAE1C,QAAA,cAAc,GAAG;IAC5B,GAAG,EAAE,WAAW;IAChB,aAAa,EAAE;QACb,SAAS,EAAE,WAAW;QACtB,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;QAC7B,YAAY,EAAE,EAAE;QAChB,WAAW,EAAE;YACX,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,EAAE;SACX;QACD,YAAY,EAAE;YACZ,CAAC,OAAO,CAAC,EAAE,IAAI;YACf,CAAC,OAAO,CAAC,EAAE,KAAK;SACjB;QACD,gBAAgB,EAAE;YAChB,eAAe,EAAE,EAAE;YACnB,cAAc,EAAE,EAAE;SACnB;QACD,sBAAsB,EAAE,QAAQ;QAChC,UAAU,EAAE,oCAAoC;QAChD,SAAS,EAAE,EAAE;QACb,UAAU,EAAE,EAAE;KACf;IACD,OAAO,EAAE;QACP,CAAC,OAAO,CAAC,EAAE,IAAI;QACf,CAAC,OAAO,CAAC,EAAE,IAAI;KAChB;IACD,WAAW,EAAE;;;;IAIX;IACF,UAAU,EAAE;;EAEZ;CACD,CAAC"}
{"version":3,"file":"const.js","sourceRoot":"","sources":["../src/const.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEU,QAAA,kBAAkB,GAAG,oBAAoB,CAAC;AAE1C,QAAA,cAAc,GAAG;IAC5B,GAAG,EAAE,WAAW;IAChB,aAAa,EAAE;QACb,QAAQ,EAAE,KAAK;QACf,SAAS,EAAE,WAAW;QACtB,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;QAC7B,YAAY,EAAE,EAAE;QAChB,WAAW,EAAE;YACX,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,EAAE;SACX;QACD,YAAY,EAAE;YACZ,CAAC,OAAO,CAAC,EAAE,IAAI;YACf,CAAC,OAAO,CAAC,EAAE,KAAK;SACjB;QACD,gBAAgB,EAAE;YAChB,eAAe,EAAE,EAAE;YACnB,cAAc,EAAE,EAAE;SACnB;QACD,sBAAsB,EAAE,QAAQ;QAChC,UAAU,EAAE,oCAAoC;QAChD,SAAS,EAAE,EAAE;QACb,UAAU,EAAE,EAAE;KACf;IACD,OAAO,EAAE;QACP,CAAC,OAAO,CAAC,EAAE,IAAI;QACf,CAAC,OAAO,CAAC,EAAE,IAAI;KAChB;IACD,WAAW,EAAE;;;;IAIX;IACF,UAAU,EAAE;;EAEZ;CACD,CAAC"}

View File

@ -1,6 +1,6 @@
"use strict";
/**
* @author doubledream
* @author zhaoyingbo
* @desc 提取指定文件夹下的中文
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
@ -26,7 +26,7 @@ const replace_1 = require("./replace");
const utils_2 = require("../utils");
const CONFIG = utils_2.getProjectConfig();
/**
* 剔除 kiwiDir 下的文件
* 剔除配置文件夹下的文件
*/
function removeLangsFiles(files) {
const langsDir = path.resolve(process.cwd(), CONFIG.canaryDir);
@ -39,19 +39,29 @@ function removeLangsFiles(files) {
* 递归匹配项目中所有的代码的中文
*/
function findAllChineseText(dir) {
const first = dir.split(',')[0];
// 使用逗号分割用户输入,可以混合输入文件和文件夹
const dirList = dir.split(' ');
let files = [];
if (file_1.isDirectory(first)) {
// 获取用户输入的全部文件
dirList.forEach(dir => {
const dirPath = path.resolve(process.cwd(), dir);
files = file_1.getSpecifiedFiles(dirPath, CONFIG.ignoreDir, CONFIG.ignoreFile);
}
else {
files = removeLangsFiles(dir.split(','));
}
const filterFiles = files.filter(file => {
return (file_1.isFile(file) && file.endsWith('.ts')) || (file_1.isFile(file) && file.endsWith('.js')) || file.endsWith('.tsx') || file.endsWith('.vue');
// 输入文件夹
if (file_1.isDirectory(dir)) {
files.push(...file_1.getSpecifiedFiles(dirPath, CONFIG.ignoreDir, CONFIG.ignoreFile));
}
// 输入文件
else {
files.push(dirPath);
}
});
const allTexts = filterFiles.reduce((pre, file) => {
// 过滤输入中包含的配置文件夹下的文件
files = removeLangsFiles(files);
// 过滤非代码文件
files = files.filter(file => {
return (file_1.isFile(file) && ['ts', 'js', 'tsx', 'jsx', 'vue'].includes(file_1.getSuffix(file)));
});
// 匹配代码文件中的中文
const allTexts = files.reduce((pre, file) => {
const code = file_1.readFile(file);
const texts = findChineseText_1.findChineseText(code, file);
// 调整文案顺序,保证从后面的文案往前替换,避免位置更新导致替换出错
@ -74,149 +84,180 @@ function getTransOriginText(text) {
return transOriginText;
}
/**
* @param currentFilename 文件路径
* @returns string[]
* 根据文件名和上级目录生成对应的前缀
* @param fileName 文件路径
* @returns 例如home.index. | ''
*/
function getSuggestion(currentFilename) {
let suggestion = [];
const suggestPageRegex = /\/pages\/\w+\/([^\/]+)\/([^\/\.]+)/;
if (currentFilename.includes('/pages/')) {
suggestion = currentFilename.match(suggestPageRegex);
function getFilePrefix(fileName) {
// 反斜杠路由替换成正斜杠
const forwardFileName = slash(fileName);
let filePrefix = [];
// 如果是在page下的目录直接匹配前缀
// oldRegex : \/pages\/\w+\/([^\/]+)\/([^\/\.]+)
const suggestPageRegex = /\/pages\/([^\/]+)\/([^\/\.]+)/;
if (forwardFileName.includes('/pages/')) {
filePrefix = forwardFileName.match(suggestPageRegex);
filePrefix && filePrefix.shift();
}
if (suggestion) {
suggestion.shift();
}
/** 如果没有匹配到 Key */
if (!(suggestion && suggestion.length)) {
const names = slash(currentFilename).split('/');
// 如果没有匹配到前缀
if (!(filePrefix && filePrefix.length)) {
const names = forwardFileName.split('/');
const fileName = _.last(names);
// 去除拓展名
const fileKey = fileName.split('.')[0].replace(new RegExp('-', 'g'), '_');
// 上级目录名
const dir = names[names.length - 2].replace(new RegExp('-', 'g'), '_');
if (dir === fileKey) {
suggestion = [dir];
filePrefix = [dir];
}
else {
suggestion = [dir, fileKey];
filePrefix = [dir, fileKey];
}
}
return suggestion;
return filePrefix.length ? `${filePrefix.join('.')}.` : '';
}
/**
* 统一处理key值已提取过的文案直接替换翻译后的key若相同加上出现次数
* @param currentFilename 文件路径
* @param filePath 文件路径
* @param langsPrefix 替换后的前缀
* @param translateTexts 翻译后的key值
* @param targetStrs 当前文件提取后的文案
* @returns any[] 最终可用于替换的key值和文案
*/
function getReplaceableStrs(currentFilename, langsPrefix, translateTexts, targetStrs) {
const finalLangObj = getLangData_1.getSuggestLangObj();
const virtualMemory = {};
const suggestion = getSuggestion(currentFilename);
const replaceableStrs = targetStrs.reduce((prev, curr, i) => {
const _text = curr.text;
let key = utils_1.findMatchKey(finalLangObj, _text);
function getLangKeys(filePath, langsPrefix, translateTexts, targetStrs) {
// 获取项目原语言的配置并拍平
const srcLangObj = getLangData_1.getSrcLangObj();
// 是区分是否是本函数生成而非原始数据从而确定是否需要needWrite
const genKeys = {};
// 为文案生成对应的键值
return targetStrs.reduce((prev, curr, i) => {
const { text } = curr;
// 如果已生成过对应的键则直接返回
if (genKeys[text]) {
return prev.concat({
target: curr,
key: genKeys[text],
needWrite: true
});
}
// 在原配置中找对应的键
let key = utils_1.findMatchKey(srcLangObj, text);
// 如果找到了就替换中划线为下划线并暂存
if (key) {
key = key.replace(/-/g, '_');
}
if (!virtualMemory[_text]) {
if (key) {
virtualMemory[_text] = key;
return prev.concat({
target: curr,
key,
needWrite: false
});
}
const transText = translateTexts[i] && _.camelCase(translateTexts[i]);
let transKey = `${suggestion.length ? suggestion.join('.') + '.' : ''}${transText}`;
transKey = transKey.replace(/-/g, '_');
if (langsPrefix) {
transKey = `${langsPrefix}.${transText}`;
}
let occurTime = 1;
// 防止出现前四位相同但是整体文案不同的情况
while (utils_1.findMatchValue(finalLangObj, transKey) !== _text &&
_.keys(finalLangObj).includes(`${transKey}${occurTime >= 2 ? occurTime : ''}`)) {
occurTime++;
}
if (occurTime >= 2) {
transKey = `${transKey}${occurTime}`;
}
virtualMemory[_text] = transKey;
finalLangObj[transKey] = _text;
return prev.concat({
target: curr,
key: transKey,
needWrite: true
key,
// 这里由于原语言配置就有键,所以不需要重写进配置文件
needWrite: false
});
}
// 没有生成过,原配置也没有,重新生成一个
// 获取翻译后的键并驼峰化
const transKey = translateTexts[i] && _.camelCase(translateTexts[i]);
// 如果用户定义了前缀就直接加上
if (langsPrefix) {
key = `${langsPrefix}.${transKey}`;
}
// 没前缀就加上文件名和上级目录作为前缀
else {
return prev.concat({
target: curr,
key: virtualMemory[_text],
needWrite: true
});
key = `${getFilePrefix(filePath)}${transKey}`;
}
key = key.replace(/-/g, '_');
// 防止出现前五位相同但是整体文案不同的情况
if (srcLangObj[key] && srcLangObj[key] !== text) {
// 已经存在的Key
const existKeys = _.keys(srcLangObj);
let occurTime = 2;
// 获取已经重复的次数
while (existKeys.includes(`${key}${occurTime}`)) {
occurTime += 1;
}
// 拼接重复次数例如common.index.key1
key = key + occurTime;
}
// 写进新生成的键列表
genKeys[text] = key;
// 写入原配置,方便后续比较重复
srcLangObj[key] = text;
return prev.concat({
target: curr,
key,
needWrite: true
});
}, []);
return replaceableStrs;
}
/**
* 递归匹配项目中所有的代码的中文
* @param {dirPath} 文件夹路径
* @returns
*/
function extractAll({ dirPath, prefix }) {
const dir = dirPath || './';
// 去除I18N
const langsPrefix = prefix ? prefix.replace(/^I18N\./, '') : null;
// 翻译源配置错误,则终止
const origin = CONFIG.defaultTranslateKeyApi || 'Pinyin';
if (!['Pinyin', 'Google', 'Baidu'].includes(CONFIG.defaultTranslateKeyApi)) {
console.log(`Kiwi 仅支持 ${utils_1.highlightText('Pinyin、Google、Baidu')},请修改 ${utils_1.highlightText('defaultTranslateKeyApi')} 配置项`);
// 翻译源配置校验
let origin = CONFIG.defaultTranslateKeyApi;
// 用户设置了空字符串
if (!origin) {
origin = 'Pinyin';
console.log(`配置文件未配置 ${utils_1.highlightText('defaultTranslateKeyApi')} , 使用默认值 ${utils_1.highlightText('Pinyin')}\n`);
}
// 对用户填入值进行校验
else if (!['Pinyin', 'Google', 'Baidu'].includes(origin)) {
console.log(`Canary 仅支持 ${utils_1.highlightText('Pinyin、Google、Baidu')},请修改 ${utils_1.highlightText('defaultTranslateKeyApi')} 配置项`);
return;
}
// 地址为用户输入默认为src文件夹
const dir = dirPath || './src';
// 去除I18N前缀后续全局加
const langsPrefix = prefix ? prefix.replace(/^I18N\./, '') : null;
// 获取目标文件下全部中文文案
const allTargetStrs = findAllChineseText(dir);
if (allTargetStrs.length === 0) {
console.log(utils_1.highlightText('没有发现可替换的文案!'));
return;
}
// 提示翻译源
if (CONFIG.defaultTranslateKeyApi === 'Pinyin') {
console.log(`当前使用 ${utils_1.highlightText('Pinyin')} 作为key值的翻译源若想得到更好的体验可配置 ${utils_1.highlightText('googleApiKey')}${utils_1.highlightText('baiduApiKey')},并切换 ${utils_1.highlightText('defaultTranslateKeyApi')}`);
if (origin === 'Pinyin') {
console.log(`\n当前使用 ${utils_1.highlightText('Pinyin')} 作为key值的翻译源若想得到更好的体验可配置 ${utils_1.highlightText('googleApiKey')}${utils_1.highlightText('baiduApiKey')},并切换 ${utils_1.highlightText('defaultTranslateKeyApi')}`);
}
else {
console.log(`当前使用 ${utils_1.highlightText(CONFIG.defaultTranslateKeyApi)} 作为key值的翻译源`);
console.log(`\n当前使用 ${utils_1.highlightText(origin)} 作为key值的翻译源`);
}
console.log('即将截取每个中文文案的前5位翻译生成key值并替换中...');
console.log('即将截取每个中文文案的前5位翻译生成key值并替换中...\n');
// 对当前文件进行文案key生成和替换
const generateKeyAndReplace = (item) => __awaiter(this, void 0, void 0, function* () {
const currentFilename = item.file;
console.log(`${currentFilename} 替换中...`);
// 过滤掉模板字符串内的中文,避免替换时出现异常
const targetStrs = item.texts.reduce((pre, strObj, i) => {
const { texts, file: filePath } = item;
console.log(`${utils_1.highlightText(filePath)} 替换中...`);
// 过滤掉模板字符串内的中文,避免替换时出现异常示例https://imoaix.cn/canary-clis/1.png
const targetStrs = texts.reduce((pre, strObj, i) => {
// 因为文案已经根据位置倒排,所以比较时只需要比较剩下的文案即可
const afterStrs = item.texts.slice(i + 1);
const afterStrs = texts.slice(i + 1);
// 如果是在模板字符串中的中文,其结束位置必然小于等于模板字符串本身的结束位置
if (afterStrs.some(obj => strObj.range.end <= obj.range.end)) {
return pre;
}
return pre.concat(strObj);
}, []);
const len = item.texts.length - targetStrs.length;
// 比对前后数量提示用户不存在过滤后长度为0的情况
const len = texts.length - targetStrs.length;
if (len > 0) {
console.log(colors.red(`存在 ${utils_1.highlightText(len)} 处文案无法替换,请避免在模板字符串的变量中嵌套中文`));
}
// 翻译后的文案
let translateTexts;
// 翻译中文文案百度和pinyin将文案拼接成一条统一翻译
if (origin !== 'Google') {
// 翻译中文文案百度和pinyin将文案进行拼接统一翻译
// 使用不同的分割符
const delimiter = origin === 'Baidu' ? '\n' : '$';
// 拼接字符串
const translateOriginTexts = targetStrs.reduce((prev, curr, i) => {
// 截取文案前5位仅包含中文和字母
const transOriginText = getTransOriginText(curr.text);
if (i === 0) {
return transOriginText;
}
return `${prev}${delimiter}${transOriginText}`;
}, []);
translateTexts = yield utils_1.translateKeyText(translateOriginTexts, origin);
// 翻译后的文案
translateTexts = yield utils_1.translateWithBaiduPinyin(translateOriginTexts, origin);
}
else {
// google并发性较好且未找到有效的分隔符故仍然逐个文案进行翻译
@ -226,24 +267,27 @@ function extractAll({ dirPath, prefix }) {
}, []);
[...translateTexts] = yield Promise.all(translatePromises);
}
// 翻译结果示例https://imoaix.cn/canary-clis/2.png
if (translateTexts.length === 0) {
utils_1.failInfo(`未得到翻译结果,${currentFilename}替换失败!`);
utils_1.failInfo(`未得到翻译结果,${filePath}替换失败!`);
return;
}
const replaceableStrs = getReplaceableStrs(currentFilename, langsPrefix, translateTexts, targetStrs);
yield replaceableStrs
.reduce((prev, obj) => {
// 统一处理Key值如翻译后结果相同加上出现次数结果示例https://imoaix.cn/canary-clis/3.png
const langKeys = getLangKeys(filePath, langsPrefix, translateTexts, targetStrs);
yield langKeys
.reduce((prev, { target, key, needWrite }) => {
return prev.then(() => {
return replace_1.replaceAndUpdate(currentFilename, obj.target, `I18N.${obj.key}`, false, obj.needWrite);
// 根据生成的键值对更新源码文件以及语言文件
return replace_1.replaceAndUpdate(filePath, target, `I18N.${key}`, false, needWrite);
});
}, Promise.resolve())
.then(() => {
// 添加 import I18N
if (!replace_1.hasImportI18N(currentFilename)) {
const code = replace_1.createImportI18N(currentFilename);
file_1.writeFile(currentFilename, code);
if (!replace_1.hasImportI18N(filePath)) {
const code = replace_1.createImportI18N(filePath);
file_1.writeFile(filePath, code);
}
utils_1.successInfo(`${currentFilename} 替换完成,共替换 ${targetStrs.length} 处文案!`);
utils_1.successInfo(`${filePath} 替换完成,共替换 ${targetStrs.length} 处文案!\n`);
})
.catch(e => {
utils_1.failInfo(e.message);

File diff suppressed because one or more lines are too long

12
dist/extract/file.js vendored
View File

@ -1,10 +1,10 @@
"use strict";
/**
* @author doubledream
* @author zhaoyingbo
* @desc 文件处理方法
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.isDirectory = exports.isFile = exports.writeFile = exports.readFile = exports.getSpecifiedFiles = void 0;
exports.getSuffix = exports.isDirectory = exports.isFile = exports.writeFile = exports.readFile = exports.getSpecifiedFiles = void 0;
const path = require("path");
const fs = require("fs");
/**
@ -71,4 +71,12 @@ function isDirectory(path) {
return fs.statSync(path).isDirectory();
}
exports.isDirectory = isDirectory;
/**
* 获取文件尾缀
* @param path
*/
function getSuffix(path) {
return path.split('.').pop();
}
exports.getSuffix = getSuffix;
//# sourceMappingURL=file.js.map

View File

@ -1 +1 @@
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/extract/file.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,6BAA6B;AAE7B,yBAAyB;AAEzB;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,GAAG,EAAE,eAAe,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE;IACnE,OAAO,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAChD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAClC,MAAM,WAAW,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;QAE1C,IAAI,WAAW,EAAE;YACf,OAAO,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC,CAAC;SAC3E;QAED,MAAM,iBAAiB,GACrB,CAAC,eAAe;YAChB,CAAC,eAAe;gBACd,CAAC,IAAI;qBACF,OAAO,CAAC,IAAI,CAAC;qBACb,KAAK,CAAC,GAAG,CAAC;qBACV,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC;QAClC,MAAM,YAAY,GAAG,CAAC,UAAU,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC;QAEvF,IAAI,MAAM,IAAI,iBAAiB,IAAI,YAAY,EAAE;YAC/C,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;SAC3B;QACD,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC;AAsCQ,8CAAiB;AApC1B;;;GAGG;AACH,SAAS,QAAQ,CAAC,QAAQ;IACxB,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC3B,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;KAC3C;AACH,CAAC;AA4B2B,4BAAQ;AA1BpC;;;GAGG;AACH,SAAS,SAAS,CAAC,QAAQ,EAAE,IAAI;IAC/B,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC3B,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;KAClC;AACH,CAAC;AAkBqC,8BAAS;AAhB/C;;;GAGG;AACH,SAAS,MAAM,CAAC,IAAI;IAClB,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;AACpC,CAAC;AAUgD,wBAAM;AARvD;;;GAGG;AACH,SAAS,WAAW,CAAC,IAAI;IACvB,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;AACzC,CAAC;AAEwD,kCAAW"}
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/extract/file.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,6BAA4B;AAE5B,yBAAwB;AAExB;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,GAAG,EAAE,eAAe,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE;IACnE,OAAO,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAChD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACjC,MAAM,WAAW,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;QACnD,MAAM,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAA;QAEzC,IAAI,WAAW,EAAE;YACf,OAAO,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC,CAAA;SAC1E;QAED,MAAM,iBAAiB,GACrB,CAAC,eAAe;YAChB,CAAC,eAAe;gBACd,CAAC,IAAI;qBACF,OAAO,CAAC,IAAI,CAAC;qBACb,KAAK,CAAC,GAAG,CAAC;qBACV,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAA;QACjC,MAAM,YAAY,GAAG,CAAC,UAAU,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,CAAA;QAEtF,IAAI,MAAM,IAAI,iBAAiB,IAAI,YAAY,EAAE;YAC/C,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;SAC1B;QACD,OAAO,KAAK,CAAA;IACd,CAAC,EAAE,EAAE,CAAC,CAAA;AACR,CAAC;AA8CQ,8CAAiB;AA5C1B;;;GAGG;AACH,SAAS,QAAQ,CAAC,QAAQ;IACxB,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC3B,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;KAC1C;AACH,CAAC;AAoC2B,4BAAQ;AAlCpC;;;GAGG;AACH,SAAS,SAAS,CAAC,QAAQ,EAAE,IAAI;IAC/B,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC3B,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;KACjC;AACH,CAAC;AA0BqC,8BAAS;AAxB/C;;;GAGG;AACH,SAAS,MAAM,CAAC,IAAI;IAClB,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAA;AACnC,CAAC;AAkBgD,wBAAM;AAhBvD;;;GAGG;AACH,SAAS,WAAW,CAAC,IAAI;IACvB,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;AACxC,CAAC;AAUwD,kCAAW;AARpE;;;GAGG;AACH,SAAS,SAAS,CAAC,IAAI;IACrB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAA;AAC9B,CAAC;AAEqE,8BAAS"}

View File

@ -4,16 +4,18 @@
* @desc 获取语言文件
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLangData = exports.getSuggestLangObj = void 0;
exports.getLangData = exports.getSrcLangObj = void 0;
const globby = require("globby");
const fs = require("fs");
const path = require("path");
const utils_1 = require("../utils");
const CONFIG = utils_1.getProjectConfig();
// 项目对应语言配置文件夹
const LANG_DIR = path.resolve(CONFIG.canaryDir, CONFIG.srcLang);
const I18N_GLOB = `${LANG_DIR}/**/*.ts`;
// 匹配文件
const I18N_GLOB = `${LANG_DIR}/**/*.${CONFIG.isJsProj ? 'js' : 'ts'}`;
/**
* 获取对应文件的语言
* 获取制定文件JSON内容
*/
function getLangData(fileName) {
if (fs.existsSync(fileName)) {
@ -29,8 +31,9 @@ exports.getLangData = getLangData;
*/
function getLangJson(fileName) {
const fileContent = fs.readFileSync(fileName, { encoding: 'utf8' });
let obj = fileContent.match(/export\s*default\s*({[\s\S]+);?$/)[1];
obj = obj.replace(/\s*;\s*$/, '');
let obj = fileContent.match(/export\s*default\s*({[\s\S]+)?$/)[1];
obj = obj.replace(/\s*\s*$/, '');
obj = obj.replace(';', '');
let jsObj = {};
try {
jsObj = eval('(' + obj + ')');
@ -41,18 +44,27 @@ function getLangJson(fileName) {
}
return jsObj;
}
/**
* 获取项目原语言的I18N内容
* @return 示例{ 'common.ts': { test: '测试', chinese: '中国' } }
*/
function getI18N() {
// 项目对应语言配置文件集合
const paths = globby.sync(I18N_GLOB);
const langObj = paths.reduce((prev, curr) => {
const filename = curr
.split('/')
.pop()
.replace(/\.tsx?$/, '');
if (filename.replace(/\.tsx?/, '') === 'index') {
.split('.')
.shift();
// 排除index文件
if (filename === 'index') {
return prev;
}
// 获取文件内容
const fileContent = getLangData(curr);
let jsObj = fileContent;
// 空对象,说明获取失败
if (Object.keys(jsObj).length === 0) {
console.log(`\`${curr}\` 解析失败,该文件包含的文案无法自动补全`);
}
@ -61,12 +73,11 @@ function getI18N() {
return langObj;
}
/**
* 获取全部语言, 展平
* 获取项目原语言的配置, 展平
* @return 示例{ 'common.test': '测试', 'common.chinese': '中国' }
*/
function getSuggestLangObj() {
const langObj = getI18N();
const finalLangObj = utils_1.flatten(langObj);
return finalLangObj;
function getSrcLangObj() {
return utils_1.flatten(getI18N());
}
exports.getSuggestLangObj = getSuggestLangObj;
exports.getSrcLangObj = getSrcLangObj;
//# sourceMappingURL=getLangData.js.map

View File

@ -1 +1 @@
{"version":3,"file":"getLangData.js","sourceRoot":"","sources":["../../src/extract/getLangData.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,iCAAiC;AACjC,yBAAyB;AACzB,6BAA6B;AAC7B,oCAAqD;AAErD,MAAM,MAAM,GAAG,wBAAgB,EAAE,CAAC;AAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;AAChE,MAAM,SAAS,GAAG,GAAG,QAAQ,UAAU,CAAC;AAExC;;GAEG;AACH,SAAS,WAAW,CAAC,QAAQ;IAC3B,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC3B,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC;KAC9B;SAAM;QACL,OAAO,EAAE,CAAC;KACX;AACH,CAAC;AAsD2B,kCAAW;AApDvC;;GAEG;AACH,SAAS,WAAW,CAAC,QAAQ;IAC3B,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACpE,IAAI,GAAG,GAAG,WAAW,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAClC,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI;QACF,KAAK,GAAG,IAAI,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;KAC/B;IAAC,OAAO,GAAG,EAAE;QACZ,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;KACpB;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,OAAO;IACd,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QAC1C,MAAM,QAAQ,GAAG,IAAI;aAClB,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,EAAE;aACL,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QAC1B,IAAI,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,OAAO,EAAE;YAC9C,OAAO,IAAI,CAAC;SACb;QAED,MAAM,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,KAAK,GAAG,WAAW,CAAC;QAExB,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE;YACnC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,wBAAwB,CAAC,CAAC;SAChD;QAED,uCACK,IAAI,KACP,CAAC,QAAQ,CAAC,EAAE,KAAK,IACjB;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IACP,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB;IACxB,MAAM,OAAO,GAAG,OAAO,EAAE,CAAC;IAC1B,MAAM,YAAY,GAAG,eAAO,CAAC,OAAO,CAAC,CAAC;IACtC,OAAO,YAAY,CAAC;AACtB,CAAC;AAEQ,8CAAiB"}
{"version":3,"file":"getLangData.js","sourceRoot":"","sources":["../../src/extract/getLangData.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,iCAAgC;AAChC,yBAAwB;AACxB,6BAA4B;AAC5B,oCAAoD;AAEpD,MAAM,MAAM,GAAG,wBAAgB,EAAE,CAAA;AACjC,cAAc;AACd,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;AAC/D,OAAO;AACP,MAAM,SAAS,GAAG,GAAG,QAAQ,SAAS,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;AAErE;;GAEG;AACH,SAAS,WAAW,CAAC,QAAQ;IAC3B,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC3B,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAA;KAC7B;SAAM;QACL,OAAO,EAAE,CAAA;KACV;AACH,CAAC;AA6DuB,kCAAW;AA3DnC;;GAEG;AACH,SAAS,WAAW,CAAC,QAAQ;IAC3B,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;IACnE,IAAI,GAAG,GAAG,WAAW,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC,CAAC,CAAA;IACjE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IAChC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;IAC1B,IAAI,KAAK,GAAG,EAAE,CAAA;IACd,IAAI;QACF,KAAK,GAAG,IAAI,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,CAAA;KAC9B;IAAC,OAAO,GAAG,EAAE;QACZ,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAChB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;KACnB;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;GAGG;AACH,SAAS,OAAO;IACd,eAAe;IACf,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QAC1C,MAAM,QAAQ,GAAG,IAAI;aAClB,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,EAAE;aACL,KAAK,CAAC,GAAG,CAAC;aACV,KAAK,EAAE,CAAA;QACV,YAAY;QACZ,IAAI,QAAQ,KAAK,OAAO,EAAE;YACxB,OAAO,IAAI,CAAA;SACZ;QACD,SAAS;QACT,MAAM,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;QACrC,IAAI,KAAK,GAAG,WAAW,CAAA;QACvB,aAAa;QACb,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE;YACnC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,wBAAwB,CAAC,CAAA;SAC/C;QAED,uCACK,IAAI,KACP,CAAC,QAAQ,CAAC,EAAE,KAAK,IAClB;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IACN,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa;IACpB,OAAO,eAAO,CAAC,OAAO,EAAE,CAAC,CAAA;AAC3B,CAAC;AAEQ,sCAAa"}

View File

@ -14,35 +14,6 @@ const getLangData_1 = require("./getLangData");
const utils_1 = require("../utils");
const CONFIG = utils_1.getProjectConfig();
const srcLangDir = utils_1.getLangDir(CONFIG.srcLang);
function updateLangFiles(keyValue, text, validateDuplicate) {
if (!_.startsWith(keyValue, 'I18N.')) {
return;
}
const [, filename, ...restPath] = keyValue.split('.');
const fullKey = restPath.join('.');
const targetFilename = `${srcLangDir}/${filename}.ts`;
if (!fs.existsSync(targetFilename)) {
fs.writeFileSync(targetFilename, generateNewLangFile(fullKey, text));
addImportToMainLangFile(filename);
utils_1.successInfo(`成功新建语言文件 ${targetFilename}`);
}
else {
// 清除 require 缓存,解决手动更新语言文件后再自动抽取,导致之前更新失效的问题
const mainContent = getLangData_1.getLangData(targetFilename);
const obj = mainContent;
if (Object.keys(obj).length === 0) {
utils_1.failInfo(`${filename} 解析失败,该文件包含的文案无法自动补全`);
}
if (validateDuplicate && _.get(obj, fullKey) !== undefined) {
utils_1.failInfo(`${targetFilename} 中已存在 key 为 \`${fullKey}\` 的翻译,请重新命名变量`);
throw new Error('duplicate');
}
// \n 会被自动转义成 \\n这里转回来
text = text.replace(/\\n/gm, '\n');
_.set(obj, fullKey, text);
fs.writeFileSync(targetFilename, prettierFile(`export default ${JSON.stringify(obj, null, 2)}`));
}
}
/**
* 使用 Prettier 格式化文件
* @param fileContent
@ -60,44 +31,101 @@ function prettierFile(fileContent) {
return fileContent;
}
}
/**
* 新建一个新的语言文件
* @param key 语言文件中的I80N值
* @param value 语言文件中的I18N值
*/
function generateNewLangFile(key, value) {
const obj = _.set({}, key, value);
return prettierFile(`export default ${JSON.stringify(obj, null, 2)}`);
}
/**
* 在语言index文件中新增语言文件引入
* @param newFilename 新的语言文件名
*/
function addImportToMainLangFile(newFilename) {
const indexFileSrc = `${srcLangDir}/index.${CONFIG.isJsProj ? 'js' : 'ts'}`;
let mainContent = '';
if (fs.existsSync(`${srcLangDir}/index.ts`)) {
mainContent = fs.readFileSync(`${srcLangDir}/index.ts`, 'utf8');
mainContent = mainContent.replace(/^(\s*import.*?;)$/m, `$1\nimport ${newFilename} from './${newFilename}';`);
if (/(}\);)/.test(mainContent)) {
if (/\,\n(}\);)/.test(mainContent)) {
/** 最后一行包含,号 */
mainContent = mainContent.replace(/(}\);)/, ` ${newFilename},\n$1`);
// 如果已经存在index文件则更新内容
if (fs.existsSync(indexFileSrc)) {
mainContent = fs.readFileSync(indexFileSrc, 'utf8');
mainContent = mainContent.replace(/^(\s*import.*?)$/m, `$1\nimport ${newFilename} from './${newFilename}'`);
if (/(}\))/.test(mainContent)) {
if (/\,\n(}\))/.test(mainContent)) {
// 最后一行包含,号
mainContent = mainContent.replace(/(}\))/, ` ${newFilename},\n$1`);
}
else {
/** 最后一行不包含,号 */
mainContent = mainContent.replace(/\n(}\);)/, `,\n ${newFilename},\n$1`);
// 最后一行不包含,号
mainContent = mainContent.replace(/\n(}\))/, `,\n ${newFilename},\n$1`);
}
}
// 兼容 export default { common };的写法
if (/(};)/.test(mainContent)) {
if (/\,\n(};)/.test(mainContent)) {
/** 最后一行包含,号 */
mainContent = mainContent.replace(/(};)/, ` ${newFilename},\n$1`);
// 兼容 export default { common }的写法
if (/(})/.test(mainContent)) {
if (/\,\n(})/.test(mainContent)) {
// 最后一行包含,号
mainContent = mainContent.replace(/(})/, ` ${newFilename},\n$1`);
}
else {
/** 最后一行不包含,号 */
mainContent = mainContent.replace(/\n(};)/, `,\n ${newFilename},\n$1`);
// 最后一行不包含,号
mainContent = mainContent.replace(/\n(})/, `,\n ${newFilename},\n$1`);
}
}
}
// 还未存在index文件生成初始值
else {
mainContent = `import ${newFilename} from './${newFilename}';\n\nexport default Object.assign({}, {\n ${newFilename},\n});`;
mainContent = `import ${newFilename} from './${newFilename}'\n\nexport default Object.assign({}, {\n ${newFilename},\n})`;
}
fs.writeFileSync(`${srcLangDir}/index.ts`, mainContent);
// 写入文件
fs.writeFileSync(indexFileSrc, mainContent);
}
/**
* 检查是否添加 import I18N 命令
* 更新语言文件
* @param key 代码文件中的I18N键
* @param val 语言文件中的I80N值
* @param validateDuplicate 是否校验重复
*/
function updateLangFiles(key, val, validateDuplicate) {
// 过滤掉不是I18N开头的Key
if (!_.startsWith(key, 'I18N.')) {
return;
}
// 拆分代码文件中的键
const [, filename, ...restPath] = key.split('.');
// 语言文件中的I18N键
const fullKey = restPath.join('.');
// 语言文件路径
const langFilePath = `${srcLangDir}/${filename}.${CONFIG.isJsProj ? 'js' : 'ts'}`;
// 还未生成对应的语言文件
if (!fs.existsSync(langFilePath)) {
// 生成新的语言文件内容并写入
fs.writeFileSync(langFilePath, generateNewLangFile(fullKey, val));
// 在index文件中增加语言文件引入
addImportToMainLangFile(filename);
utils_1.successInfo(`成功新建语言文件 ${langFilePath}`);
}
else {
// 清除 require 缓存,解决手动更新语言文件后再自动抽取,导致之前更新失效的问题
const mainContent = getLangData_1.getLangData(langFilePath);
// 解析失败会返回{},需要过滤下
if (Object.keys(mainContent).length === 0) {
utils_1.failInfo(`${filename} 解析失败,该文件包含的文案无法自动补全`);
}
// 校验是否有重复的Key
if (validateDuplicate && _.get(mainContent, fullKey) !== undefined) {
utils_1.failInfo(`${langFilePath} 中已存在 key 为 \`${fullKey}\` 的翻译,请重新命名变量`);
throw new Error('duplicate');
}
// \n 会被自动转义成 \\n这里转回来
val = val.replace(/\\n/gm, '\n');
// 写入语言文件
_.set(mainContent, fullKey, val);
fs.writeFileSync(langFilePath, prettierFile(`export default ${JSON.stringify(mainContent, null, 2)}`));
}
}
/**
* 检查代码文件是否添加过 import I18N 命令
* @param filePath 文件路径
*/
function hasImportI18N(filePath) {
@ -107,7 +135,7 @@ function hasImportI18N(filePath) {
function visit(node) {
if (node.kind === ts.SyntaxKind.ImportDeclaration) {
const importClause = node.importClause;
// import I18N from 'src/utils/I18N';
// import I18N from 'src/utils/I18N'
if (_.get(importClause, 'kind') === ts.SyntaxKind.ImportClause) {
if (importClause.name) {
if (importClause.name.escapedText === 'I18N') {
@ -116,7 +144,7 @@ function hasImportI18N(filePath) {
}
else {
const namedBindings = importClause.namedBindings;
// import { I18N } from 'src/utils/I18N';
// import { I18N } from 'src/utils/I18N'
if (namedBindings.kind === ts.SyntaxKind.NamedImports) {
namedBindings.elements.forEach(element => {
if (element.kind === ts.SyntaxKind.ImportSpecifier && _.get(element, 'name.escapedText') === 'I18N') {
@ -124,7 +152,7 @@ function hasImportI18N(filePath) {
}
});
}
// import * as I18N from 'src/utils/I18N';
// import * as I18N from 'src/utils/I18N'
if (namedBindings.kind === ts.SyntaxKind.NamespaceImport) {
if (_.get(namedBindings, 'name.escapedText') === 'I18N') {
hasImportI18N = true;
@ -145,16 +173,16 @@ exports.hasImportI18N = hasImportI18N;
function createImportI18N(filePath) {
const code = file_1.readFile(filePath);
const ast = ts.createSourceFile('', code, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TSX);
const isTsFile = _.endsWith(filePath, '.ts');
const isJsFile = _.endsWith(filePath, '.js');
const isTsxFile = _.endsWith(filePath, '.tsx');
const isNomalFile = ['ts', 'js', 'tsx', 'jsx'].includes(file_1.getSuffix(filePath));
const isVueFile = _.endsWith(filePath, '.vue');
if (isTsFile || isTsxFile || isJsFile) {
// 正常文件开头添加import
if (isNomalFile) {
const importStatement = `${CONFIG.importI18N}\n`;
const pos = ast.getStart(ast, false);
const updateCode = code.slice(0, pos) + importStatement + code.slice(pos);
return updateCode;
}
// Vue文件开头添加import
else if (isVueFile) {
const importStatement = `${CONFIG.importI18N}\n`;
const updateCode = code.replace(/<script>/g, `<script>\n${importStatement}`);
@ -163,66 +191,87 @@ function createImportI18N(filePath) {
}
exports.createImportI18N = createImportI18N;
/**
* 更新文件
* @param filePath 当前文件路径
* @param arg 目标字符串对象
* @param val 目标 key
* 更新语言文件以及源代码
* @param filePath 当前代码文件路径
* @param target 目标字符串对象
* @param key 目标 key
* @param validateDuplicate 是否校验文件中已经存在要写入的 key
* @param needWrite 是否只需要替换不需要更新 langs 文件
*/
function replaceAndUpdate(filePath, arg, val, validateDuplicate, needWrite = true) {
function replaceAndUpdate(filePath, target, key, validateDuplicate, needWrite = true) {
// 获取目标字段内容
const { text, range: { start, end }, isString } = target;
// 获取当前代码内容
const code = file_1.readFile(filePath);
const isHtmlFile = _.endsWith(filePath, '.html');
const isVueFile = _.endsWith(filePath, '.vue');
// 写回代码文件的内容
let newCode = code;
let finalReplaceText = arg.text;
const { start, end } = arg.range;
// 中文语言结果
let cnVal = text;
// 若是字符串,删掉两侧的引号
if (arg.isString) {
if (isString) {
// 写回代码文件的I18N键
let codeKey = key;
// 如果引号左侧是 等号,则可能是 jsx 的 props此时要替换成 {
const preTextStart = start - 1;
const [last2Char, last1Char] = code.slice(preTextStart, start + 1).split('');
let finalReplaceVal = val;
if (last2Char === '=') {
if (isHtmlFile) {
finalReplaceVal = '{{' + val + '}}';
codeKey = '{{' + key + '}}';
}
else if (isVueFile) {
finalReplaceVal = '{{' + val + '}}';
codeKey = '{{' + key + '}}';
}
else {
finalReplaceVal = '{' + val + '}';
codeKey = '{' + key + '}';
}
}
// 若是模板字符串,看看其中是否包含变量
if (last1Char === '`') {
const varInStr = arg.text.match(/(\$\{[^\}]+?\})/g);
// 正则可视化 https://c.runoob.com/front-end/7625/#!flags=&re=(%5C%24%5C%7B%5B%5E%5C%7D%5D%2B%3F%5C%7D)
// 匹配模板字符串内的全部变量内容
const varInStr = text.match(/(\$\{[^\}]+?\})/g);
// 如果存在变量
if (varInStr) {
/**
* 取出变量内容并用val{x}指代
* 示例[ 'val1: a', "val2: '全球每个人'" ]
*/
const kvPair = varInStr.map((str, index) => {
return `val${index + 1}: ${str.replace(/^\${([^\}]+)\}$/, '$1')}`;
});
finalReplaceVal = `I18N.template(${val}, { ${kvPair.join(',\n')} })`;
/**
* 包装模板字符串的I18N替换代码用于写回代码文件
* 示例I18N.template(I18N.home.index.shiZhongJianChiZuo, { val1: a, val2: '全球每个人' })
*/
codeKey = `I18N.template(${key}, { ${kvPair.join(',\n')} })`;
/**
* 使用val{x}替换掉原模板字符串的变量用于生成语言文件
* 示例始终坚持做{val1}的好产品{val2}都能享受科技带来的美好生活
*/
varInStr.forEach((str, index) => {
finalReplaceText = finalReplaceText.replace(str, `{val${index + 1}}`);
cnVal = cnVal.replace(str, `{val${index + 1}}`);
});
}
}
newCode = `${code.slice(0, start)}${finalReplaceVal}${code.slice(end)}`;
// 将I18N替换代码写回代码文件
newCode = `${code.slice(0, start)}${codeKey}${code.slice(end)}`;
}
else {
if (isHtmlFile || isVueFile) {
newCode = `${code.slice(0, start)}{{${val}}}${code.slice(end)}`;
newCode = `${code.slice(0, start)}{{${key}}}${code.slice(end)}`;
}
else {
newCode = `${code.slice(0, start)}{${val}}${code.slice(end)}`;
newCode = `${code.slice(0, start)}{${key}}${code.slice(end)}`;
}
}
try {
if (needWrite) {
// 更新语言文件
updateLangFiles(val, finalReplaceText, validateDuplicate);
updateLangFiles(key, cnVal, validateDuplicate);
}
// 若更新成功再替换代码
// // 若更新成功再替换代码
return file_1.writeFile(filePath, newCode);
}
catch (e) {

File diff suppressed because one or more lines are too long

6
dist/index.js vendored
View File

@ -128,12 +128,12 @@ if (commander.translate) {
}));
}
if (commander.extract) {
console.log(lodash_1.isString(commander.prefix));
if (commander.prefix === true) {
console.log('请指定翻译后文案 key 值的前缀 --prefix xxxx');
}
else if (lodash_1.isString(commander.prefix) && !new RegExp(/^I18N(\.[-_a-zA-Z1-9$]+)+$/).test(commander.prefix)) {
console.log('前缀必须以I18N开头,后续跟上字母、下滑线、破折号、$ 字符组成的变量名');
// 正则可视化 https://c.runoob.com/front-end/7625/#!flags=&re=%5EI18N(%5C.%5B_a-zA-Z1-9%24%5D%2B)%2B%24
else if (lodash_1.isString(commander.prefix) && !new RegExp(/^I18N(\.[_a-zA-Z1-9$]+)+$/).test(commander.prefix)) {
console.log('前缀必须以I18N开头,后续跟上字母、下滑线、破折号、$ 字符组成的变量名,如I18N.yingbo');
}
else {
const extractAllParams = {

2
dist/index.js.map vendored
View File

@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}

10
dist/init.js vendored
View File

@ -1,7 +1,7 @@
"use strict";
/**
* @author zhaoyingbo
* @desc 初始化 kiwi 项目的文件以及配置
* @desc 初始化 canary 项目的文件以及配置
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.initProject = void 0;
@ -28,10 +28,10 @@ function createConfigFile(existDir) {
fs.writeFile(configDir, JSON.stringify(config, null, 2), err => err && console.log(err));
}
/**
* 创建中文配置示例文件
* 创建中文语言示例文件
*/
function createCnFile() {
// 中文配置文件夹地址
// 中文语言文件夹地址
const cnDir = `${const_1.PROJECT_CONFIG.dir}/zh-CN`;
// 没有则创建
if (!fs.existsSync(cnDir)) {
@ -42,7 +42,7 @@ function createCnFile() {
}
/**
* 初始化国际化项目
* @param existDir 配置文件夹地址
* @param existDir 多语言文件夹地址
*/
function initProject(existDir) {
// 有用户输入的文件夹,不存在默认位置创建
@ -56,7 +56,7 @@ function initProject(existDir) {
}
// 创建配置文件
createConfigFile(existDir);
// 没有已经存在的配置文件夹,创建默认的配置文件示例
// 没有已经存在的多语言文件夹,创建默认的中文语言示例文件
if (!(existDir && fs.existsSync(existDir))) {
createCnFile();
}

2
dist/init.js.map vendored
View File

@ -1 +1 @@
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAGH,6BAA6B;AAC7B,yBAAyB;AACzB,mCAA6D;AAE7D;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,QAAiB;IACzC,aAAa;IACb,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,0BAAkB,EAAE,CAAC,CAAC;IACzE,iBAAiB;IACjB,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO;IACrC,MAAM,MAAM,qBAAQ,sBAAc,CAAC,aAAa,CAAE,CAAC;IACnD,SAAS;IACT,IAAI,QAAQ,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QACvC,MAAM,CAAC,SAAS,GAAG,QAAQ,CAAC;KAC7B;IACD,YAAY;IACZ,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED;;GAEG;AACH,SAAS,YAAY;IACnB,YAAY;IACZ,MAAM,KAAK,GAAG,GAAG,sBAAc,CAAC,GAAG,QAAQ,CAAC;IAC5C,QAAQ;IACR,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE;QACzB,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACpB,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,WAAW,EAAE,sBAAc,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9F,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,YAAY,EAAE,sBAAc,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;KAC/F;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,QAAiB;IACpC,sBAAsB;IACtB,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QACxC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACrC,EAAE,CAAC,SAAS,CAAC,sBAAc,CAAC,GAAG,CAAC,CAAC;KAClC;IACD,cAAc;SACT,IAAI,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,sBAAc,CAAC,GAAG,CAAC,EAAE;QACxD,EAAE,CAAC,SAAS,CAAC,sBAAc,CAAC,GAAG,CAAC,CAAC;KAClC;IACD,SAAS;IACT,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC3B,2BAA2B;IAC3B,IAAI,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE;QAC1C,YAAY,EAAE,CAAC;KAChB;AACH,CAAC;AAEQ,kCAAW"}
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAGH,6BAA4B;AAC5B,yBAAwB;AACxB,mCAA4D;AAE5D;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,QAAiB;IACzC,aAAa;IACb,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,0BAAkB,EAAE,CAAC,CAAA;IACxE,iBAAiB;IACjB,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAM;IACpC,MAAM,MAAM,qBAAQ,sBAAc,CAAC,aAAa,CAAE,CAAA;IAClD,SAAS;IACT,IAAI,QAAQ,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QACvC,MAAM,CAAC,SAAS,GAAG,QAAQ,CAAA;KAC5B;IACD,YAAY;IACZ,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;AAC1F,CAAC;AAED;;GAEG;AACH,SAAS,YAAY;IACnB,YAAY;IACZ,MAAM,KAAK,GAAG,GAAG,sBAAc,CAAC,GAAG,QAAQ,CAAA;IAC3C,QAAQ;IACR,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE;QACzB,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QACnB,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,WAAW,EAAE,sBAAc,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;QAC7F,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,YAAY,EAAE,sBAAc,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;KAC9F;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,QAAiB;IACpC,sBAAsB;IACtB,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QACxC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAA;QACpC,EAAE,CAAC,SAAS,CAAC,sBAAc,CAAC,GAAG,CAAC,CAAA;KACjC;IACD,cAAc;SACT,IAAI,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,sBAAc,CAAC,GAAG,CAAC,EAAE;QACxD,EAAE,CAAC,SAAS,CAAC,sBAAc,CAAC,GAAG,CAAC,CAAA;KACjC;IACD,SAAS;IACT,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAC1B,8BAA8B;IAC9B,IAAI,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE;QAC1C,YAAY,EAAE,CAAA;KACf;AACH,CAAC;AAEQ,kCAAW"}

2
dist/unused.js vendored
View File

@ -10,7 +10,7 @@ const path = require("path");
const utils_1 = require("./utils");
const lookingForString = '';
function findUnUsed() {
const srcLangDir = path.resolve(utils_1.getKiwiDir(), 'zh-CN');
const srcLangDir = path.resolve(utils_1.getCanaryDir(), 'zh-CN');
let files = fs.readdirSync(srcLangDir);
files = files.filter(file => file.endsWith('.ts') && file !== 'index.ts');
const unUnsedKeys = [];

2
dist/unused.js.map vendored
View File

@ -1 +1 @@
{"version":3,"file":"unused.js","sourceRoot":"","sources":["../src/unused.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"unused.js","sourceRoot":"","sources":["../src/unused.ts"],"names":[],"mappings":"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"}

24
dist/utils.js vendored
View File

@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.highlightText = exports.failInfo = exports.successInfo = exports.translateKeyText = exports.getTranslateOriginType = exports.lookForFiles = exports.flatten = exports.findMatchValue = exports.findMatchKey = exports.translateText = exports.getProjectConfig = exports.getAllMessages = exports.withTimeout = exports.retry = exports.traverse = exports.getLangDir = exports.getKiwiDir = void 0;
exports.highlightText = exports.failInfo = exports.successInfo = exports.translateWithBaiduPinyin = exports.getTranslateOriginType = exports.lookForFiles = exports.flatten = exports.findMatchKey = exports.translateText = exports.getProjectConfig = exports.getAllMessages = exports.withTimeout = exports.retry = exports.traverse = exports.getLangDir = exports.getCanaryDir = void 0;
/**
* @author linhuiw
* @desc 工具方法
@ -56,19 +56,19 @@ exports.getProjectConfig = getProjectConfig;
/**
* 获取语言资源的根目录
*/
function getKiwiDir() {
function getCanaryDir() {
const config = getProjectConfig();
if (config) {
return config.canaryDir;
}
}
exports.getKiwiDir = getKiwiDir;
exports.getCanaryDir = getCanaryDir;
/**
* 获取对应语言的目录位置
* @param lang
*/
function getLangDir(lang) {
const langsDir = getKiwiDir();
const langsDir = getCanaryDir();
return path.resolve(langsDir, lang);
}
exports.getLangDir = getLangDir;
@ -164,9 +164,9 @@ function translateText(text, toLang) {
}
exports.translateText = translateText;
/**
* 翻译中文
* 使用百度或者拼音翻译中文
*/
function translateKeyText(text, origin) {
function translateWithBaiduPinyin(text, origin) {
const CONFIG = getProjectConfig();
const { appId, appKey } = CONFIG.baiduApiKey;
const baiduTranslate = require('baidu-translate');
@ -194,20 +194,16 @@ function translateKeyText(text, origin) {
}
return retry(_translateText, 3);
}
exports.translateKeyText = translateKeyText;
exports.translateWithBaiduPinyin = translateWithBaiduPinyin;
function findMatchKey(langObj, text) {
for (const key in langObj) {
if (langObj[key] === text) {
return key;
}
}
return null;
return '';
}
exports.findMatchKey = findMatchKey;
function findMatchValue(langObj, key) {
return langObj[key];
}
exports.findMatchValue = findMatchValue;
/**
* 将对象拍平
* @param obj 原始对象
@ -274,8 +270,8 @@ exports.getTranslateOriginType = getTranslateOriginType;
/**
* 成功的提示
*/
function successInfo(message) {
console.log('successInfo: ', colors.green(message));
function successInfo(message, needEnter = false) {
console.log(`${needEnter ? '\n' : ''}successInfo: `, colors.green(message));
}
exports.successInfo = successInfo;
/**

2
dist/utils.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,7 @@ export const CANARY_CONFIG_FILE = 'canary-config.json';
export const PROJECT_CONFIG = {
dir: './.canary',
defaultConfig: {
isJsProj: false,
canaryDir: './.canary',
srcLang: 'zh-CN',
distLangs: ['en-US', 'zh-CN'],

View File

@ -1,69 +1,78 @@
/**
* @author doubledream
* @author zhaoyingbo
* @desc
*/
import * as _ from 'lodash';
import * as slash from 'slash2';
import * as path from 'path';
import * as colors from 'colors';
import * as _ from 'lodash'
import * as slash from 'slash2'
import * as path from 'path'
import * as colors from 'colors'
import { getSpecifiedFiles, readFile, writeFile, isFile, isDirectory } from './file';
import { findChineseText } from './findChineseText';
import { getSuggestLangObj } from './getLangData';
import { getSpecifiedFiles, readFile, writeFile, isFile, isDirectory, getSuffix } from './file'
import { findChineseText } from './findChineseText'
import { getSrcLangObj } from './getLangData'
import {
translateText,
findMatchKey,
findMatchValue,
translateKeyText,
translateWithBaiduPinyin,
successInfo,
failInfo,
highlightText
} from '../utils';
import { replaceAndUpdate, hasImportI18N, createImportI18N } from './replace';
import { getProjectConfig } from '../utils';
} from '../utils'
import { replaceAndUpdate, hasImportI18N, createImportI18N } from './replace'
import { getProjectConfig } from '../utils'
const CONFIG = getProjectConfig();
const CONFIG = getProjectConfig()
/**
* kiwiDir
*
*/
function removeLangsFiles(files: string[]) {
const langsDir = path.resolve(process.cwd(), CONFIG.canaryDir);
const langsDir = path.resolve(process.cwd(), CONFIG.canaryDir)
return files.filter(file => {
const completeFile = path.resolve(process.cwd(), file);
return !completeFile.includes(langsDir);
});
const completeFile = path.resolve(process.cwd(), file)
return !completeFile.includes(langsDir)
})
}
/**
*
*/
function findAllChineseText(dir: string) {
const first = dir.split(',')[0];
let files = [];
if (isDirectory(first)) {
const dirPath = path.resolve(process.cwd(), dir);
files = getSpecifiedFiles(dirPath, CONFIG.ignoreDir, CONFIG.ignoreFile);
} else {
files = removeLangsFiles(dir.split(','));
}
const filterFiles = files.filter(file => {
return (isFile(file) && file.endsWith('.ts')) || (isFile(file) && file.endsWith('.js')) || file.endsWith('.tsx') || file.endsWith('.vue');
});
const allTexts = filterFiles.reduce((pre, file) => {
const code = readFile(file);
const texts = findChineseText(code, file);
// 使用逗号分割用户输入,可以混合输入文件和文件夹
const dirList = dir.split(' ')
let files = []
// 获取用户输入的全部文件
dirList.forEach(dir => {
const dirPath = path.resolve(process.cwd(), dir)
// 输入文件夹
if (isDirectory(dir)) {
files.push(...getSpecifiedFiles(dirPath, CONFIG.ignoreDir, CONFIG.ignoreFile))
}
// 输入文件
else {
files.push(dirPath)
}
})
// 过滤输入中包含的配置文件夹下的文件
files = removeLangsFiles(files)
// 过滤非代码文件
files = files.filter(file => {
return (isFile(file) && ['ts', 'js', 'tsx', 'jsx', 'vue'].includes(getSuffix(file)))
})
// 匹配代码文件中的中文
const allTexts = files.reduce((pre, file) => {
const code = readFile(file)
const texts = findChineseText(code, file)
// 调整文案顺序,保证从后面的文案往前替换,避免位置更新导致替换出错
const sortTexts = _.sortBy(texts, obj => -obj.range.start);
const sortTexts = _.sortBy(texts, obj => -obj.range.start)
if (texts.length > 0) {
console.log(`${highlightText(file)} 发现 ${highlightText(texts.length)} 处中文文案`);
console.log(`${highlightText(file)} 发现 ${highlightText(texts.length)} 处中文文案`)
}
return texts.length > 0 ? pre.concat({ file, texts: sortTexts }) : pre;
}, []);
return allTexts;
return texts.length > 0 ? pre.concat({ file, texts: sortTexts }) : pre
}, [])
return allTexts
}
/**
@ -71,223 +80,251 @@ function findAllChineseText(dir: string) {
*/
function getTransOriginText(text: string) {
// 避免翻译的字符里包含数字或者特殊字符等情况,只过滤出汉字和字母
const reg = /[a-zA-Z\u4e00-\u9fa5]+/g;
const findText = text.match(reg) || [];
const transOriginText = findText ? findText.join('').slice(0, 5) : '中文符号';
const reg = /[a-zA-Z\u4e00-\u9fa5]+/g
const findText = text.match(reg) || []
const transOriginText = findText ? findText.join('').slice(0, 5) : '中文符号'
return transOriginText;
return transOriginText
}
/**
* @param currentFilename
* @returns string[]
*
* @param fileName
* @returns home.index. | ''
*/
function getSuggestion(currentFilename: string) {
let suggestion = [];
const suggestPageRegex = /\/pages\/\w+\/([^\/]+)\/([^\/\.]+)/;
if (currentFilename.includes('/pages/')) {
suggestion = currentFilename.match(suggestPageRegex);
function getFilePrefix(fileName: string) {
// 反斜杠路由替换成正斜杠
const forwardFileName = slash(fileName)
let filePrefix = []
// 如果是在page下的目录直接匹配前缀
// oldRegex : \/pages\/\w+\/([^\/]+)\/([^\/\.]+)
const suggestPageRegex = /\/pages\/([^\/]+)\/([^\/\.]+)/
if (forwardFileName.includes('/pages/')) {
filePrefix = forwardFileName.match(suggestPageRegex)
filePrefix && filePrefix.shift()
}
if (suggestion) {
suggestion.shift();
}
/** 如果没有匹配到 Key */
if (!(suggestion && suggestion.length)) {
const names = slash(currentFilename).split('/');
const fileName = _.last(names) as any;
const fileKey = fileName.split('.')[0].replace(new RegExp('-', 'g'), '_');
const dir = names[names.length - 2].replace(new RegExp('-', 'g'), '_');
// 如果没有匹配到前缀
if (!(filePrefix && filePrefix.length)) {
const names = forwardFileName.split('/')
const fileName = _.last(names) as any
// 去除拓展名
const fileKey = fileName.split('.')[0].replace(new RegExp('-', 'g'), '_')
// 上级目录名
const dir = names[names.length - 2].replace(new RegExp('-', 'g'), '_')
if (dir === fileKey) {
suggestion = [dir];
filePrefix = [dir]
} else {
suggestion = [dir, fileKey];
filePrefix = [dir, fileKey]
}
}
return suggestion;
return filePrefix.length ? `${filePrefix.join('.')}.` : ''
}
/**
* key值key若相同
* @param currentFilename
* @param filePath
* @param langsPrefix
* @param translateTexts key值
* @param targetStrs
* @returns any[] key值和文案
*/
function getReplaceableStrs(currentFilename: string, langsPrefix: string, translateTexts: string[], targetStrs: any[]) {
const finalLangObj = getSuggestLangObj();
const virtualMemory = {};
const suggestion = getSuggestion(currentFilename);
const replaceableStrs = targetStrs.reduce((prev, curr, i) => {
const _text = curr.text;
let key = findMatchKey(finalLangObj, _text);
if (key) {
key = key.replace(/-/g, '_');
}
if (!virtualMemory[_text]) {
if (key) {
virtualMemory[_text] = key;
return prev.concat({
target: curr,
key,
needWrite: false
});
}
const transText = translateTexts[i] && _.camelCase(translateTexts[i] as string);
let transKey = `${suggestion.length ? suggestion.join('.') + '.' : ''}${transText}`;
transKey = transKey.replace(/-/g, '_');
if (langsPrefix) {
transKey = `${langsPrefix}.${transText}`;
}
let occurTime = 1;
// 防止出现前四位相同但是整体文案不同的情况
while (
findMatchValue(finalLangObj, transKey) !== _text &&
_.keys(finalLangObj).includes(`${transKey}${occurTime >= 2 ? occurTime : ''}`)
) {
occurTime++;
}
if (occurTime >= 2) {
transKey = `${transKey}${occurTime}`;
}
virtualMemory[_text] = transKey;
finalLangObj[transKey] = _text;
function getLangKeys(filePath: string, langsPrefix: string, translateTexts: string[], targetStrs: any[]) {
// 获取项目原语言的配置并拍平
const srcLangObj = getSrcLangObj()
// 是区分是否是本函数生成而非原始数据从而确定是否需要needWrite
const genKeys = {}
// 为文案生成对应的键值
return targetStrs.reduce((prev, curr, i) => {
const { text } = curr
// 如果已生成过对应的键则直接返回
if (genKeys[text]) {
return prev.concat({
target: curr,
key: transKey,
key: genKeys[text],
needWrite: true
});
} else {
return prev.concat({
target: curr,
key: virtualMemory[_text],
needWrite: true
});
})
}
}, []);
return replaceableStrs;
// 在原配置中找对应的键
let key = findMatchKey(srcLangObj, text)
// 如果找到了就替换中划线为下划线并暂存
if (key) {
key = key.replace(/-/g, '_')
return prev.concat({
target: curr,
key,
// 这里由于原语言配置就有键,所以不需要重写进配置文件
needWrite: false
})
}
// 没有生成过,原配置也没有,重新生成一个
// 获取翻译后的键并驼峰化
const transKey = translateTexts[i] && _.camelCase(translateTexts[i] as string)
// 如果用户定义了前缀就直接加上
if (langsPrefix) {
key = `${langsPrefix}.${transKey}`
}
// 没前缀就加上文件名和上级目录作为前缀
else {
key = `${getFilePrefix(filePath)}${transKey}`
}
key = key.replace(/-/g, '_')
// 防止出现前五位相同但是整体文案不同的情况
if (srcLangObj[key] && srcLangObj[key] !== text) {
// 已经存在的Key
const existKeys = _.keys(srcLangObj)
let occurTime = 2
// 获取已经重复的次数
while (existKeys.includes(`${key}${occurTime}`)) {
occurTime += 1
}
// 拼接重复次数例如common.index.key1
key = key + occurTime
}
// 写进新生成的键列表
genKeys[text] = key
// 写入原配置,方便后续比较重复
srcLangObj[key] = text
return prev.concat({
target: curr,
key,
needWrite: true
})
}, [])
}
/**
*
* @param {dirPath}
* @returns
*/
function extractAll({ dirPath, prefix }: { dirPath?: string; prefix?: string }) {
const dir = dirPath || './';
// 去除I18N
const langsPrefix = prefix ? prefix.replace(/^I18N\./, '') : null;
// 翻译源配置错误,则终止
const origin = CONFIG.defaultTranslateKeyApi || 'Pinyin';
if (!['Pinyin', 'Google', 'Baidu'].includes(CONFIG.defaultTranslateKeyApi)) {
console.log(
`Kiwi 仅支持 ${highlightText('Pinyin、Google、Baidu')},请修改 ${highlightText('defaultTranslateKeyApi')} 配置项`
);
return;
// 翻译源配置校验
let origin = CONFIG.defaultTranslateKeyApi
// 用户设置了空字符串
if (!origin) {
origin = 'Pinyin'
console.log(`配置文件未配置 ${highlightText('defaultTranslateKeyApi')} , 使用默认值 ${highlightText('Pinyin')}\n`)
}
// 对用户填入值进行校验
else if (!['Pinyin', 'Google', 'Baidu'].includes(origin)) {
console.log(
`Canary 仅支持 ${highlightText('Pinyin、Google、Baidu')},请修改 ${highlightText('defaultTranslateKeyApi')} 配置项`
)
return
}
// 地址为用户输入默认为src文件夹
const dir = dirPath || './src'
// 去除I18N前缀后续全局加
const langsPrefix = prefix ? prefix.replace(/^I18N\./, '') : null
const allTargetStrs = findAllChineseText(dir);
// 获取目标文件下全部中文文案
const allTargetStrs = findAllChineseText(dir)
if (allTargetStrs.length === 0) {
console.log(highlightText('没有发现可替换的文案!'));
return;
console.log(highlightText('没有发现可替换的文案!'))
return
}
// 提示翻译源
if (CONFIG.defaultTranslateKeyApi === 'Pinyin') {
if (origin === 'Pinyin') {
console.log(
`当前使用 ${highlightText('Pinyin')} 作为key值的翻译源若想得到更好的体验可配置 ${highlightText(
`\n当前使用 ${highlightText('Pinyin')} 作为key值的翻译源若想得到更好的体验可配置 ${highlightText(
'googleApiKey'
)} ${highlightText('baiduApiKey')} ${highlightText('defaultTranslateKeyApi')}`
);
)
} else {
console.log(`当前使用 ${highlightText(CONFIG.defaultTranslateKeyApi)} 作为key值的翻译源`);
console.log(`\n当前使用 ${highlightText(origin)} 作为key值的翻译源`)
}
console.log('即将截取每个中文文案的前5位翻译生成key值并替换中...');
console.log('即将截取每个中文文案的前5位翻译生成key值并替换中...\n')
// 对当前文件进行文案key生成和替换
const generateKeyAndReplace = async item => {
const currentFilename = item.file;
console.log(`${currentFilename} 替换中...`);
// 过滤掉模板字符串内的中文,避免替换时出现异常
const targetStrs = item.texts.reduce((pre, strObj, i) => {
const { texts, file: filePath } = item
console.log(`${highlightText(filePath)} 替换中...`)
// 过滤掉模板字符串内的中文,避免替换时出现异常示例https://imoaix.cn/canary-clis/1.png
const targetStrs = texts.reduce((pre, strObj, i) => {
// 因为文案已经根据位置倒排,所以比较时只需要比较剩下的文案即可
const afterStrs = item.texts.slice(i + 1);
const afterStrs = texts.slice(i + 1)
// 如果是在模板字符串中的中文,其结束位置必然小于等于模板字符串本身的结束位置
if (afterStrs.some(obj => strObj.range.end <= obj.range.end)) {
return pre;
return pre
}
return pre.concat(strObj);
}, []);
const len = item.texts.length - targetStrs.length;
return pre.concat(strObj)
}, [])
// 比对前后数量提示用户不存在过滤后长度为0的情况
const len = texts.length - targetStrs.length
if (len > 0) {
console.log(colors.red(`存在 ${highlightText(len)} 处文案无法替换,请避免在模板字符串的变量中嵌套中文`));
console.log(colors.red(`存在 ${highlightText(len)} 处文案无法替换,请避免在模板字符串的变量中嵌套中文`))
}
let translateTexts;
// 翻译后的文案
let translateTexts
// 翻译中文文案百度和pinyin将文案拼接成一条统一翻译
if (origin !== 'Google') {
// 翻译中文文案百度和pinyin将文案进行拼接统一翻译
const delimiter = origin === 'Baidu' ? '\n' : '$';
// 使用不同的分割符
const delimiter = origin === 'Baidu' ? '\n' : '$'
// 拼接字符串
const translateOriginTexts = targetStrs.reduce((prev, curr, i) => {
const transOriginText = getTransOriginText(curr.text);
// 截取文案前5位仅包含中文和字母
const transOriginText = getTransOriginText(curr.text)
if (i === 0) {
return transOriginText;
return transOriginText
}
return `${prev}${delimiter}${transOriginText}`;
}, []);
translateTexts = await translateKeyText(translateOriginTexts, origin);
return `${prev}${delimiter}${transOriginText}`
}, [])
// 翻译后的文案
translateTexts = await translateWithBaiduPinyin(translateOriginTexts, origin)
} else {
// google并发性较好且未找到有效的分隔符故仍然逐个文案进行翻译
const translatePromises = targetStrs.reduce((prev, curr) => {
const transOriginText = getTransOriginText(curr.text);
return prev.concat(translateText(transOriginText, 'en_US'));
}, []);
const transOriginText = getTransOriginText(curr.text)
return prev.concat(translateText(transOriginText, 'en_US'))
}, [])
[...translateTexts] = await Promise.all(translatePromises);
;[...translateTexts] = await Promise.all(translatePromises)
}
// 翻译结果示例https://imoaix.cn/canary-clis/2.png
if (translateTexts.length === 0) {
failInfo(`未得到翻译结果,${currentFilename}替换失败!`);
return;
failInfo(`未得到翻译结果,${filePath}替换失败!`)
return
}
const replaceableStrs = getReplaceableStrs(currentFilename, langsPrefix, translateTexts, targetStrs);
await replaceableStrs
.reduce((prev, obj) => {
// 统一处理Key值如翻译后结果相同加上出现次数结果示例https://imoaix.cn/canary-clis/3.png
const langKeys = getLangKeys(filePath, langsPrefix, translateTexts, targetStrs)
await langKeys
.reduce((prev, {target, key, needWrite}) => {
return prev.then(() => {
return replaceAndUpdate(currentFilename, obj.target, `I18N.${obj.key}`, false, obj.needWrite);
});
// 根据生成的键值对更新源码文件以及语言文件
return replaceAndUpdate(filePath, target, `I18N.${key}`, false, needWrite)
})
}, Promise.resolve())
.then(() => {
// 添加 import I18N
if (!hasImportI18N(currentFilename)) {
const code = createImportI18N(currentFilename);
writeFile(currentFilename, code);
if (!hasImportI18N(filePath)) {
const code = createImportI18N(filePath)
writeFile(filePath, code)
}
successInfo(`${currentFilename} 替换完成,共替换 ${targetStrs.length} 处文案!`);
successInfo(`${filePath} 替换完成,共替换 ${targetStrs.length} 处文案!\n`)
})
.catch(e => {
failInfo(e.message);
});
};
failInfo(e.message)
})
}
allTargetStrs
.reduce((prev, current) => {
return prev.then(() => {
return generateKeyAndReplace(current);
});
return generateKeyAndReplace(current)
})
}, Promise.resolve())
.then(() => {
successInfo('全部替换完成!');
successInfo('全部替换完成!')
})
.catch((e: any) => {
failInfo(e.message);
});
failInfo(e.message)
})
}
export { extractAll };
export { extractAll }

View File

@ -1,11 +1,11 @@
/**
* @author doubledream
* @author zhaoyingbo
* @desc
*/
import * as path from 'path';
import * as _ from 'lodash';
import * as fs from 'fs';
import * as path from 'path'
import * as _ from 'lodash'
import * as fs from 'fs'
/**
*
@ -15,12 +15,12 @@ import * as fs from 'fs';
*/
function getSpecifiedFiles(dir, ignoreDirectory = '', ignoreFile = '') {
return fs.readdirSync(dir).reduce((files, file) => {
const name = path.join(dir, file);
const isDirectory = fs.statSync(name).isDirectory();
const isFile = fs.statSync(name).isFile();
const name = path.join(dir, file)
const isDirectory = fs.statSync(name).isDirectory()
const isFile = fs.statSync(name).isFile()
if (isDirectory) {
return files.concat(getSpecifiedFiles(name, ignoreDirectory, ignoreFile));
return files.concat(getSpecifiedFiles(name, ignoreDirectory, ignoreFile))
}
const isIgnoreDirectory =
@ -29,14 +29,14 @@ function getSpecifiedFiles(dir, ignoreDirectory = '', ignoreFile = '') {
!path
.dirname(name)
.split('/')
.includes(ignoreDirectory));
const isIgnoreFile = !ignoreFile || (ignoreFile && path.basename(name) !== ignoreFile);
.includes(ignoreDirectory))
const isIgnoreFile = !ignoreFile || (ignoreFile && path.basename(name) !== ignoreFile)
if (isFile && isIgnoreDirectory && isIgnoreFile) {
return files.concat(name);
return files.concat(name)
}
return files;
}, []);
return files
}, [])
}
/**
@ -45,7 +45,7 @@ function getSpecifiedFiles(dir, ignoreDirectory = '', ignoreFile = '') {
*/
function readFile(fileName) {
if (fs.existsSync(fileName)) {
return fs.readFileSync(fileName, 'utf-8');
return fs.readFileSync(fileName, 'utf-8')
}
}
@ -55,7 +55,7 @@ function readFile(fileName) {
*/
function writeFile(filePath, file) {
if (fs.existsSync(filePath)) {
fs.writeFileSync(filePath, file);
fs.writeFileSync(filePath, file)
}
}
@ -64,7 +64,7 @@ function writeFile(filePath, file) {
* @param path
*/
function isFile(path) {
return fs.statSync(path).isFile();
return fs.statSync(path).isFile()
}
/**
@ -72,7 +72,15 @@ function isFile(path) {
* @param path
*/
function isDirectory(path) {
return fs.statSync(path).isDirectory();
return fs.statSync(path).isDirectory()
}
export { getSpecifiedFiles, readFile, writeFile, isFile, isDirectory };
/**
*
* @param path
*/
function getSuffix(path) {
return path.split('.').pop()
}
export { getSpecifiedFiles, readFile, writeFile, isFile, isDirectory, getSuffix }

View File

@ -3,23 +3,25 @@
* @desc
*/
import * as globby from 'globby';
import * as fs from 'fs';
import * as path from 'path';
import { getProjectConfig, flatten } from '../utils';
import * as globby from 'globby'
import * as fs from 'fs'
import * as path from 'path'
import { getProjectConfig, flatten } from '../utils'
const CONFIG = getProjectConfig();
const LANG_DIR = path.resolve(CONFIG.canaryDir, CONFIG.srcLang);
const I18N_GLOB = `${LANG_DIR}/**/*.ts`;
const CONFIG = getProjectConfig()
// 项目对应语言配置文件夹
const LANG_DIR = path.resolve(CONFIG.canaryDir, CONFIG.srcLang)
// 匹配文件
const I18N_GLOB = `${LANG_DIR}/**/*.${CONFIG.isJsProj ? 'js' : 'ts'}`
/**
*
* JSON内容
*/
function getLangData(fileName) {
if (fs.existsSync(fileName)) {
return getLangJson(fileName);
return getLangJson(fileName)
} else {
return {};
return {}
}
}
@ -27,52 +29,59 @@ function getLangData(fileName) {
* Json
*/
function getLangJson(fileName) {
const fileContent = fs.readFileSync(fileName, { encoding: 'utf8' });
let obj = fileContent.match(/export\s*default\s*({[\s\S]+);?$/)[1];
obj = obj.replace(/\s*;\s*$/, '');
let jsObj = {};
const fileContent = fs.readFileSync(fileName, { encoding: 'utf8' })
let obj = fileContent.match(/export\s*default\s*({[\s\S]+)?$/)[1]
obj = obj.replace(/\s*\s*$/, '')
obj = obj.replace(';', '')
let jsObj = {}
try {
jsObj = eval('(' + obj + ')');
jsObj = eval('(' + obj + ')')
} catch (err) {
console.log(obj);
console.error(err);
console.log(obj)
console.error(err)
}
return jsObj;
return jsObj
}
/**
* I18N内容
* @return { 'common.ts': { test: '测试', chinese: '中国' } }
*/
function getI18N() {
const paths = globby.sync(I18N_GLOB);
// 项目对应语言配置文件集合
const paths = globby.sync(I18N_GLOB)
const langObj = paths.reduce((prev, curr) => {
const filename = curr
.split('/')
.pop()
.replace(/\.tsx?$/, '');
if (filename.replace(/\.tsx?/, '') === 'index') {
return prev;
.split('.')
.shift()
// 排除index文件
if (filename === 'index') {
return prev
}
const fileContent = getLangData(curr);
let jsObj = fileContent;
// 获取文件内容
const fileContent = getLangData(curr)
let jsObj = fileContent
// 空对象,说明获取失败
if (Object.keys(jsObj).length === 0) {
console.log(`\`${curr}\` 解析失败,该文件包含的文案无法自动补全`);
console.log(`\`${curr}\` 解析失败,该文件包含的文案无法自动补全`)
}
return {
...prev,
[filename]: jsObj
};
}, {});
return langObj;
}
}, {})
return langObj
}
/**
* ,
* ,
* @return { 'common.test': '测试', 'common.chinese': '中国' }
*/
function getSuggestLangObj() {
const langObj = getI18N();
const finalLangObj = flatten(langObj);
return finalLangObj;
function getSrcLangObj() {
return flatten(getI18N())
}
export { getSuggestLangObj, getLangData };
export { getSrcLangObj, getLangData }

View File

@ -3,49 +3,16 @@
* @desc
*/
import * as fs from 'fs-extra';
import * as _ from 'lodash';
import * as prettier from 'prettier';
import * as ts from 'typescript';
import { readFile, writeFile } from './file';
import { getLangData } from './getLangData';
import { getProjectConfig, getLangDir, successInfo, failInfo, highlightText } from '../utils';
import * as fs from 'fs-extra'
import * as _ from 'lodash'
import * as prettier from 'prettier'
import * as ts from 'typescript'
import { readFile, writeFile, getSuffix } from './file'
import { getLangData } from './getLangData'
import { getProjectConfig, getLangDir, successInfo, failInfo, highlightText } from '../utils'
const CONFIG = getProjectConfig();
const srcLangDir = getLangDir(CONFIG.srcLang);
function updateLangFiles(keyValue, text, validateDuplicate) {
if (!_.startsWith(keyValue, 'I18N.')) {
return;
}
const [, filename, ...restPath] = keyValue.split('.');
const fullKey = restPath.join('.');
const targetFilename = `${srcLangDir}/${filename}.ts`;
if (!fs.existsSync(targetFilename)) {
fs.writeFileSync(targetFilename, generateNewLangFile(fullKey, text));
addImportToMainLangFile(filename);
successInfo(`成功新建语言文件 ${targetFilename}`);
} else {
// 清除 require 缓存,解决手动更新语言文件后再自动抽取,导致之前更新失效的问题
const mainContent = getLangData(targetFilename);
const obj = mainContent;
if (Object.keys(obj).length === 0) {
failInfo(`${filename} 解析失败,该文件包含的文案无法自动补全`);
}
if (validateDuplicate && _.get(obj, fullKey) !== undefined) {
failInfo(`${targetFilename} 中已存在 key 为 \`${fullKey}\` 的翻译,请重新命名变量`);
throw new Error('duplicate');
}
// \n 会被自动转义成 \\n这里转回来
text = text.replace(/\\n/gm, '\n');
_.set(obj, fullKey, text);
fs.writeFileSync(targetFilename, prettierFile(`export default ${JSON.stringify(obj, null, 2)}`));
}
}
const CONFIG = getProjectConfig()
const srcLangDir = getLangDir(CONFIG.srcLang)
/**
* 使 Prettier
@ -57,83 +24,145 @@ function prettierFile(fileContent) {
parser: 'typescript',
trailingComma: 'all',
singleQuote: true
});
})
} catch (e) {
failInfo(`代码格式化报错!${e.toString()}\n代码为${fileContent}`);
return fileContent;
failInfo(`代码格式化报错!${e.toString()}\n代码为${fileContent}`)
return fileContent
}
}
function generateNewLangFile(key, value) {
const obj = _.set({}, key, value);
return prettierFile(`export default ${JSON.stringify(obj, null, 2)}`);
}
function addImportToMainLangFile(newFilename) {
let mainContent = '';
if (fs.existsSync(`${srcLangDir}/index.ts`)) {
mainContent = fs.readFileSync(`${srcLangDir}/index.ts`, 'utf8');
mainContent = mainContent.replace(/^(\s*import.*?;)$/m, `$1\nimport ${newFilename} from './${newFilename}';`);
if (/(}\);)/.test(mainContent)) {
if (/\,\n(}\);)/.test(mainContent)) {
/** 最后一行包含,号 */
mainContent = mainContent.replace(/(}\);)/, ` ${newFilename},\n$1`);
} else {
/** 最后一行不包含,号 */
mainContent = mainContent.replace(/\n(}\);)/, `,\n ${newFilename},\n$1`);
}
}
// 兼容 export default { common };的写法
if (/(};)/.test(mainContent)) {
if (/\,\n(};)/.test(mainContent)) {
/** 最后一行包含,号 */
mainContent = mainContent.replace(/(};)/, ` ${newFilename},\n$1`);
} else {
/** 最后一行不包含,号 */
mainContent = mainContent.replace(/\n(};)/, `,\n ${newFilename},\n$1`);
}
}
} else {
mainContent = `import ${newFilename} from './${newFilename}';\n\nexport default Object.assign({}, {\n ${newFilename},\n});`;
}
fs.writeFileSync(`${srcLangDir}/index.ts`, mainContent);
}
/**
* import I18N
*
* @param key I80N值
* @param value I18N值
*/
function generateNewLangFile(key, value) {
const obj = _.set({}, key, value)
return prettierFile(`export default ${JSON.stringify(obj, null, 2)}`)
}
/**
* index文件中新增语言文件引入
* @param newFilename
*/
function addImportToMainLangFile(newFilename) {
const indexFileSrc = `${srcLangDir}/index.${CONFIG.isJsProj ? 'js' : 'ts'}`
let mainContent = ''
// 如果已经存在index文件则更新内容
if (fs.existsSync(indexFileSrc)) {
mainContent = fs.readFileSync(indexFileSrc, 'utf8')
mainContent = mainContent.replace(/^(\s*import.*?)$/m, `$1\nimport ${newFilename} from './${newFilename}'`)
if (/(}\))/.test(mainContent)) {
if (/\,\n(}\))/.test(mainContent)) {
// 最后一行包含,号
mainContent = mainContent.replace(/(}\))/, ` ${newFilename},\n$1`)
} else {
// 最后一行不包含,号
mainContent = mainContent.replace(/\n(}\))/, `,\n ${newFilename},\n$1`)
}
}
// 兼容 export default { common }的写法
if (/(})/.test(mainContent)) {
if (/\,\n(})/.test(mainContent)) {
// 最后一行包含,号
mainContent = mainContent.replace(/(})/, ` ${newFilename},\n$1`)
} else {
// 最后一行不包含,号
mainContent = mainContent.replace(/\n(})/, `,\n ${newFilename},\n$1`)
}
}
}
// 还未存在index文件生成初始值
else {
mainContent = `import ${newFilename} from './${newFilename}'\n\nexport default Object.assign({}, {\n ${newFilename},\n})`
}
// 写入文件
fs.writeFileSync(indexFileSrc, mainContent)
}
/**
*
* @param key I18N键
* @param val I80N值
* @param validateDuplicate
*/
function updateLangFiles(key, val, validateDuplicate) {
// 过滤掉不是I18N开头的Key
if (!_.startsWith(key, 'I18N.')) {
return
}
// 拆分代码文件中的键
const [, filename, ...restPath] = key.split('.')
// 语言文件中的I18N键
const fullKey = restPath.join('.')
// 语言文件路径
const langFilePath = `${srcLangDir}/${filename}.${CONFIG.isJsProj ? 'js' : 'ts'}`
// 还未生成对应的语言文件
if (!fs.existsSync(langFilePath)) {
// 生成新的语言文件内容并写入
fs.writeFileSync(langFilePath, generateNewLangFile(fullKey, val))
// 在index文件中增加语言文件引入
addImportToMainLangFile(filename)
successInfo(`成功新建语言文件 ${langFilePath}`)
}
else {
// 清除 require 缓存,解决手动更新语言文件后再自动抽取,导致之前更新失效的问题
const mainContent = getLangData(langFilePath)
// 解析失败会返回{},需要过滤下
if (Object.keys(mainContent).length === 0) {
failInfo(`${filename} 解析失败,该文件包含的文案无法自动补全`)
}
// 校验是否有重复的Key
if (validateDuplicate && _.get(mainContent, fullKey) !== undefined) {
failInfo(`${langFilePath} 中已存在 key 为 \`${fullKey}\` 的翻译,请重新命名变量`)
throw new Error('duplicate')
}
// \n 会被自动转义成 \\n这里转回来
val = val.replace(/\\n/gm, '\n')
// 写入语言文件
_.set(mainContent, fullKey, val)
fs.writeFileSync(langFilePath, prettierFile(`export default ${JSON.stringify(mainContent, null, 2)}`))
}
}
/**
* import I18N
* @param filePath
*/
function hasImportI18N(filePath) {
const code = readFile(filePath);
const ast = ts.createSourceFile('', code, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TSX);
let hasImportI18N = false;
const code = readFile(filePath)
const ast = ts.createSourceFile('', code, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TSX)
let hasImportI18N = false
function visit(node) {
if (node.kind === ts.SyntaxKind.ImportDeclaration) {
const importClause = node.importClause;
const importClause = node.importClause
// import I18N from 'src/utils/I18N';
// import I18N from 'src/utils/I18N'
if (_.get(importClause, 'kind') === ts.SyntaxKind.ImportClause) {
if (importClause.name) {
if (importClause.name.escapedText === 'I18N') {
hasImportI18N = true;
hasImportI18N = true
}
} else {
const namedBindings = importClause.namedBindings;
// import { I18N } from 'src/utils/I18N';
const namedBindings = importClause.namedBindings
// import { I18N } from 'src/utils/I18N'
if (namedBindings.kind === ts.SyntaxKind.NamedImports) {
namedBindings.elements.forEach(element => {
if (element.kind === ts.SyntaxKind.ImportSpecifier && _.get(element, 'name.escapedText') === 'I18N') {
hasImportI18N = true;
hasImportI18N = true
}
});
})
}
// import * as I18N from 'src/utils/I18N';
// import * as I18N from 'src/utils/I18N'
if (namedBindings.kind === ts.SyntaxKind.NamespaceImport) {
if (_.get(namedBindings, 'name.escapedText') === 'I18N') {
hasImportI18N = true;
hasImportI18N = true
}
}
}
@ -141,9 +170,8 @@ function hasImportI18N(filePath) {
}
}
ts.forEachChild(ast, visit);
return hasImportI18N;
ts.forEachChild(ast, visit)
return hasImportI18N
}
/**
@ -151,89 +179,109 @@ function hasImportI18N(filePath) {
* @param filePath
*/
function createImportI18N(filePath) {
const code = readFile(filePath);
const ast = ts.createSourceFile('', code, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TSX);
const isTsFile = _.endsWith(filePath, '.ts');
const isJsFile = _.endsWith(filePath, '.js');
const isTsxFile = _.endsWith(filePath, '.tsx');
const isVueFile = _.endsWith(filePath, '.vue');
if (isTsFile || isTsxFile || isJsFile) {
const importStatement = `${CONFIG.importI18N}\n`;
const pos = ast.getStart(ast, false);
const updateCode = code.slice(0, pos) + importStatement + code.slice(pos);
const code = readFile(filePath)
const ast = ts.createSourceFile('', code, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TSX)
const isNomalFile = ['ts', 'js', 'tsx', 'jsx'].includes(getSuffix(filePath))
const isVueFile = _.endsWith(filePath, '.vue')
// 正常文件开头添加import
if (isNomalFile) {
const importStatement = `${CONFIG.importI18N}\n`
const pos = ast.getStart(ast, false)
const updateCode = code.slice(0, pos) + importStatement + code.slice(pos)
return updateCode;
} else if (isVueFile) {
const importStatement = `${CONFIG.importI18N}\n`;
const updateCode = code.replace(/<script>/g, `<script>\n${importStatement}`);
return updateCode;
return updateCode
}
// Vue文件开头添加import
else if (isVueFile) {
const importStatement = `${CONFIG.importI18N}\n`
const updateCode = code.replace(/<script>/g, `<script>\n${importStatement}`)
return updateCode
}
}
/**
*
* @param filePath
* @param arg
* @param val key
*
* @param filePath
* @param target
* @param key key
* @param validateDuplicate key
* @param needWrite langs
*/
function replaceAndUpdate(filePath, arg, val, validateDuplicate, needWrite = true) {
const code = readFile(filePath);
const isHtmlFile = _.endsWith(filePath, '.html');
const isVueFile = _.endsWith(filePath, '.vue');
let newCode = code;
let finalReplaceText = arg.text;
const { start, end } = arg.range;
function replaceAndUpdate(filePath, target, key, validateDuplicate, needWrite = true) {
// 获取目标字段内容
const { text, range: { start, end }, isString } = target
// 获取当前代码内容
const code = readFile(filePath)
const isHtmlFile = _.endsWith(filePath, '.html')
const isVueFile = _.endsWith(filePath, '.vue')
// 写回代码文件的内容
let newCode = code
// 中文语言结果
let cnVal = text
// 若是字符串,删掉两侧的引号
if (arg.isString) {
if (isString) {
// 写回代码文件的I18N键
let codeKey = key
// 如果引号左侧是 等号,则可能是 jsx 的 props此时要替换成 {
const preTextStart = start - 1;
const [last2Char, last1Char] = code.slice(preTextStart, start + 1).split('');
let finalReplaceVal = val;
const preTextStart = start - 1
const [last2Char, last1Char] = code.slice(preTextStart, start + 1).split('')
if (last2Char === '=') {
if (isHtmlFile) {
finalReplaceVal = '{{' + val + '}}';
codeKey = '{{' + key + '}}'
} else if (isVueFile) {
finalReplaceVal = '{{' + val + '}}';
codeKey = '{{' + key + '}}'
} else {
finalReplaceVal = '{' + val + '}';
codeKey = '{' + key + '}'
}
}
// 若是模板字符串,看看其中是否包含变量
if (last1Char === '`') {
const varInStr = arg.text.match(/(\$\{[^\}]+?\})/g);
// 正则可视化 https://c.runoob.com/front-end/7625/#!flags=&re=(%5C%24%5C%7B%5B%5E%5C%7D%5D%2B%3F%5C%7D)
// 匹配模板字符串内的全部变量内容
const varInStr = text.match(/(\$\{[^\}]+?\})/g)
// 如果存在变量
if (varInStr) {
/**
* val{x}
* [ 'val1: a', "val2: '全球每个人'" ]
*/
const kvPair = varInStr.map((str, index) => {
return `val${index + 1}: ${str.replace(/^\${([^\}]+)\}$/, '$1')}`;
});
finalReplaceVal = `I18N.template(${val}, { ${kvPair.join(',\n')} })`;
return `val${index + 1}: ${str.replace(/^\${([^\}]+)\}$/, '$1')}`
})
/**
* I18N替换代码
* I18N.template(I18N.home.index.shiZhongJianChiZuo, { val1: a, val2: '全球每个人' })
*/
codeKey = `I18N.template(${key}, { ${kvPair.join(',\n')} })`
/**
* 使val{x}
* {val1}{val2}
*/
varInStr.forEach((str, index) => {
finalReplaceText = finalReplaceText.replace(str, `{val${index + 1}}`);
});
cnVal = cnVal.replace(str, `{val${index + 1}}`)
})
}
}
newCode = `${code.slice(0, start)}${finalReplaceVal}${code.slice(end)}`;
// 将I18N替换代码写回代码文件
newCode = `${code.slice(0, start)}${codeKey}${code.slice(end)}`
} else {
if (isHtmlFile || isVueFile) {
newCode = `${code.slice(0, start)}{{${val}}}${code.slice(end)}`;
newCode = `${code.slice(0, start)}{{${key}}}${code.slice(end)}`
} else {
newCode = `${code.slice(0, start)}{${val}}${code.slice(end)}`;
newCode = `${code.slice(0, start)}{${key}}${code.slice(end)}`
}
}
try {
if (needWrite) {
// 更新语言文件
updateLangFiles(val, finalReplaceText, validateDuplicate);
updateLangFiles(key, cnVal, validateDuplicate)
}
// 若更新成功再替换代码
return writeFile(filePath, newCode);
// // 若更新成功再替换代码
return writeFile(filePath, newCode)
} catch (e) {
return Promise.reject(e.message);
return Promise.reject(e.message)
}
}
export { replaceAndUpdate, hasImportI18N, createImportI18N };
export { replaceAndUpdate, hasImportI18N, createImportI18N }

View File

@ -125,11 +125,12 @@ if (commander.translate) {
}
if (commander.extract) {
console.log(isString(commander.prefix));
if (commander.prefix === true) {
console.log('请指定翻译后文案 key 值的前缀 --prefix xxxx');
} else if (isString(commander.prefix) && !new RegExp(/^I18N(\.[-_a-zA-Z1-9$]+)+$/).test(commander.prefix)) {
console.log('前缀必须以I18N开头,后续跟上字母、下滑线、破折号、$ 字符组成的变量名');
}
// 正则可视化 https://c.runoob.com/front-end/7625/#!flags=&re=%5EI18N(%5C.%5B_a-zA-Z1-9%24%5D%2B)%2B%24
else if (isString(commander.prefix) && !new RegExp(/^I18N(\.[_a-zA-Z1-9$]+)+$/).test(commander.prefix)) {
console.log('前缀必须以I18N开头,后续跟上字母、下滑线、破折号、$ 字符组成的变量名,如I18N.yingbo');
} else {
const extractAllParams = {
prefix: isString(commander.prefix) && commander.prefix,

View File

@ -1,12 +1,12 @@
/**
* @author zhaoyingbo
* @desc kiwi
* @desc canary
*/
import * as _ from 'lodash';
import * as path from 'path';
import * as fs from 'fs';
import { PROJECT_CONFIG, CANARY_CONFIG_FILE } from './const';
import * as _ from 'lodash'
import * as path from 'path'
import * as fs from 'fs'
import { PROJECT_CONFIG, CANARY_CONFIG_FILE } from './const'
/**
*
@ -15,52 +15,52 @@ import { PROJECT_CONFIG, CANARY_CONFIG_FILE } from './const';
*/
function createConfigFile(existDir?: string) {
// 在根目录创建配置文件
const configDir = path.resolve(process.cwd(), `./${CANARY_CONFIG_FILE}`);
const configDir = path.resolve(process.cwd(), `./${CANARY_CONFIG_FILE}`)
// 如果已经有配置文件就不要动了
if (fs.existsSync(configDir)) return;
const config = { ...PROJECT_CONFIG.defaultConfig };
if (fs.existsSync(configDir)) return
const config = { ...PROJECT_CONFIG.defaultConfig }
// 有配置文件夹
if (existDir && fs.existsSync(existDir)) {
config.canaryDir = existDir;
config.canaryDir = existDir
}
// 创建新的配置文件
fs.writeFile(configDir, JSON.stringify(config, null, 2), err => err && console.log(err));
fs.writeFile(configDir, JSON.stringify(config, null, 2), err => err && console.log(err))
}
/**
*
*
*/
function createCnFile() {
// 中文配置文件夹地址
const cnDir = `${PROJECT_CONFIG.dir}/zh-CN`;
// 中文语言文件夹地址
const cnDir = `${PROJECT_CONFIG.dir}/zh-CN`
// 没有则创建
if (!fs.existsSync(cnDir)) {
fs.mkdirSync(cnDir);
fs.writeFile(`${cnDir}/index.ts`, PROJECT_CONFIG.zhIndexFile, err => err && console.log(err));
fs.writeFile(`${cnDir}/common.ts`, PROJECT_CONFIG.zhTestFile, err => err && console.log(err));
fs.mkdirSync(cnDir)
fs.writeFile(`${cnDir}/index.ts`, PROJECT_CONFIG.zhIndexFile, err => err && console.log(err))
fs.writeFile(`${cnDir}/common.ts`, PROJECT_CONFIG.zhTestFile, err => err && console.log(err))
}
}
/**
*
* @param existDir
* @param existDir
*/
function initProject(existDir?: string) {
// 有用户输入的文件夹,不存在默认位置创建
if (existDir && !fs.existsSync(existDir)) {
console.log('\n输入的目录不存在已为你生成默认文件夹');
fs.mkdirSync(PROJECT_CONFIG.dir);
console.log('\n输入的目录不存在已为你生成默认文件夹')
fs.mkdirSync(PROJECT_CONFIG.dir)
}
// 没有输入,默认位置创建
else if (!existDir && !fs.existsSync(PROJECT_CONFIG.dir)) {
fs.mkdirSync(PROJECT_CONFIG.dir);
fs.mkdirSync(PROJECT_CONFIG.dir)
}
// 创建配置文件
createConfigFile(existDir);
// 没有已经存在的配置文件夹,创建默认的配置文件示例
createConfigFile(existDir)
// 没有已经存在的多语言文件夹,创建默认的中文语言示例文件
if (!(existDir && fs.existsSync(existDir))) {
createCnFile();
createCnFile()
}
}
export { initProject };
export { initProject }

View File

@ -4,12 +4,12 @@
*/
import * as fs from 'fs';
import * as path from 'path';
import { getKiwiDir, getLangDir, traverse } from './utils';
import { getCanaryDir, getLangDir, traverse } from './utils';
const lookingForString = '';
function findUnUsed() {
const srcLangDir = path.resolve(getKiwiDir(), 'zh-CN');
const srcLangDir = path.resolve(getCanaryDir(), 'zh-CN');
let files = fs.readdirSync(srcLangDir);
files = files.filter(file => file.endsWith('.ts') && file !== 'index.ts');
const unUnsedKeys = [];

View File

@ -49,7 +49,7 @@ function getProjectConfig() {
/**
*
*/
function getKiwiDir() {
function getCanaryDir() {
const config = getProjectConfig();
if (config) {
@ -62,7 +62,7 @@ function getKiwiDir() {
* @param lang
*/
function getLangDir(lang) {
const langsDir = getKiwiDir();
const langsDir = getCanaryDir();
return path.resolve(langsDir, lang);
}
@ -162,12 +162,12 @@ function translateText(text, toLang) {
}
/**
*
* 使
*/
function translateKeyText(text: string, origin: string) {
const CONFIG = getProjectConfig();
const { appId, appKey } = CONFIG.baiduApiKey;
const baiduTranslate = require('baidu-translate');
function translateWithBaiduPinyin(text: string, origin: string) {
const CONFIG = getProjectConfig()
const { appId, appKey } = CONFIG.baiduApiKey
const baiduTranslate = require('baidu-translate')
function _translateText() {
return withTimeout(
@ -205,11 +205,7 @@ function findMatchKey(langObj, text) {
}
}
return null;
}
function findMatchValue(langObj, key) {
return langObj[key];
return '';
}
/**
@ -276,8 +272,8 @@ async function getTranslateOriginType() {
/**
*
*/
function successInfo(message: string) {
console.log('successInfo: ', colors.green(message));
function successInfo(message: string, needEnter = false) {
console.log(`${needEnter ? '\n' : ''}successInfo: `, colors.green(message));
}
/**
@ -295,7 +291,7 @@ function highlightText(message: string | number) {
}
export {
getKiwiDir,
getCanaryDir,
getLangDir,
traverse,
retry,
@ -304,11 +300,10 @@ export {
getProjectConfig,
translateText,
findMatchKey,
findMatchValue,
flatten,
lookForFiles,
getTranslateOriginType,
translateKeyText,
translateWithBaiduPinyin,
successInfo,
failInfo,
highlightText