update
This commit is contained in:
parent
39f7755468
commit
c40c40d3f5
1
dist/extract/extract.js
vendored
1
dist/extract/extract.js
vendored
@ -210,6 +210,7 @@ function extractAll({ dirPath, prefix }) {
|
||||
const langsPrefix = prefix ? prefix.replace(/^I18N\./, '') : null;
|
||||
// 获取目标文件下全部中文文案
|
||||
const allTargetStrs = findAllChineseText(dir);
|
||||
console.log(allTargetStrs);
|
||||
if (allTargetStrs.length === 0) {
|
||||
console.log(utils_1.highlightText('没有发现可替换的文案!'));
|
||||
return;
|
||||
|
2
dist/extract/extract.js.map
vendored
2
dist/extract/extract.js.map
vendored
File diff suppressed because one or more lines are too long
@ -6,40 +6,43 @@ require('ts-node').register({
|
||||
compilerOptions: {
|
||||
module: 'commonjs'
|
||||
}
|
||||
});
|
||||
import * as fs from 'fs';
|
||||
import { tsvFormatRows } from 'd3-dsv';
|
||||
import { getAllMessages, getProjectConfig } from './utils';
|
||||
import * as _ from 'lodash';
|
||||
})
|
||||
import * as fs from 'fs'
|
||||
import { tsvFormatRows } from 'd3-dsv'
|
||||
import { getTargetLangObjs, getProjectConfig } from './utils'
|
||||
import * as _ from 'lodash'
|
||||
|
||||
function exportMessages(file?: string, lang?: string) {
|
||||
const CONFIG = getProjectConfig();
|
||||
const langs = lang ? [lang] : CONFIG.distLangs;
|
||||
const CONFIG = getProjectConfig()
|
||||
const langs = lang ? [lang] : CONFIG.distLangs
|
||||
const srcLangObjs = getTargetLangObjs(CONFIG.srcLang)
|
||||
|
||||
langs.map(lang => {
|
||||
const allMessages = getAllMessages(CONFIG.srcLang);
|
||||
const existingTranslations = getAllMessages(
|
||||
// 已经存在的翻译文案
|
||||
const targetLangObjs = getTargetLangObjs(
|
||||
lang,
|
||||
(message, key) => !/[\u4E00-\u9FA5]/.test(allMessages[key]) || allMessages[key] !== message
|
||||
);
|
||||
const messagesToTranslate = Object.keys(allMessages)
|
||||
.filter(key => !existingTranslations.hasOwnProperty(key))
|
||||
(message, key) => !/[\u4E00-\u9FA5]/.test(srcLangObjs[key]) || srcLangObjs[key] !== message
|
||||
)
|
||||
// 待翻译的文案
|
||||
const messagesToTranslate = Object.keys(srcLangObjs)
|
||||
.filter(key => !targetLangObjs.hasOwnProperty(key))
|
||||
.map(key => {
|
||||
let message = allMessages[key];
|
||||
message = JSON.stringify(message).slice(1, -1);
|
||||
return [key, message];
|
||||
});
|
||||
// 把中文文案直接写回对应的文案
|
||||
let message = srcLangObjs[key]
|
||||
message = JSON.stringify(message).slice(1, -1)
|
||||
targetLangObjs[key] = message
|
||||
})
|
||||
|
||||
if (messagesToTranslate.length === 0) {
|
||||
console.log('All the messages have been translated.');
|
||||
return;
|
||||
console.log('未发现未翻译文案')
|
||||
return
|
||||
}
|
||||
|
||||
const content = tsvFormatRows(messagesToTranslate);
|
||||
const sourceFile = file || `./export-${lang}`;
|
||||
fs.writeFileSync(sourceFile, content);
|
||||
console.log(`Exported ${messagesToTranslate.length} message(s).`);
|
||||
});
|
||||
const content = tsvFormatRows(messagesToTranslate)
|
||||
const sourceFile = file || `./export-${lang}`
|
||||
fs.writeFileSync(sourceFile, content)
|
||||
console.log(`Exported ${messagesToTranslate.length} message(s).`)
|
||||
})
|
||||
}
|
||||
|
||||
export { exportMessages };
|
||||
export { exportMessages }
|
||||
|
@ -220,7 +220,7 @@ function extractAll({ dirPath, prefix }: { dirPath?: string; prefix?: string })
|
||||
// 去除I18N前缀,后续全局加
|
||||
const langsPrefix = prefix ? prefix.replace(/^I18N\./, '') : null
|
||||
|
||||
// 获取目标文件下全部中文文案
|
||||
// 获取目标文件下全部中文文案,并按文件夹归类
|
||||
const allTargetStrs = findAllChineseText(dir)
|
||||
if (allTargetStrs.length === 0) {
|
||||
console.log(highlightText('没有发现可替换的文案!'))
|
||||
@ -240,7 +240,9 @@ function extractAll({ dirPath, prefix }: { dirPath?: string; prefix?: string })
|
||||
|
||||
console.log('即将截取每个中文文案的前5位翻译生成key值,并替换中...\n')
|
||||
|
||||
// 对当前文件进行文案key生成和替换
|
||||
/**
|
||||
* 对当前文件进行文案key生成和替换
|
||||
*/
|
||||
const generateKeyAndReplace = async item => {
|
||||
const { texts, file: filePath } = item
|
||||
console.log(`${highlightText(filePath)} 替换中...`)
|
||||
@ -313,6 +315,11 @@ function extractAll({ dirPath, prefix }: { dirPath?: string; prefix?: string })
|
||||
failInfo(e.message)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 遍历每个文件夹中的中文文案
|
||||
* 不并发处理的原因是,不同文件的相同文案需要尽可能指向同一个文件,以减少整体国际化文件体积
|
||||
* 每个文件处理的时候需要判定之前处理过的文件的文案列表
|
||||
*/
|
||||
allTargetStrs
|
||||
.reduce((prev, current) => {
|
||||
return prev.then(() => {
|
||||
|
@ -11,7 +11,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as _ from 'lodash';
|
||||
import { tsvParseRows } from 'd3-dsv';
|
||||
import { getAllMessages, getProjectConfig, traverse } from './utils';
|
||||
import { getTargetLangObjs, getProjectConfig, traverse } from './utils';
|
||||
|
||||
const CONFIG = getProjectConfig();
|
||||
|
||||
@ -57,7 +57,7 @@ function writeMessagesToFile(messages: any, file: string, lang: string) {
|
||||
|
||||
function importMessages(file: string, lang: string) {
|
||||
let messagesToImport = getMessagesToImport(file);
|
||||
const allMessages = getAllMessages(CONFIG.srcLang);
|
||||
const allMessages = getTargetLangObjs(CONFIG.srcLang);
|
||||
messagesToImport = _.pickBy(messagesToImport, (message, key) => allMessages.hasOwnProperty(key));
|
||||
const keysByFiles = _.groupBy(Object.keys(messagesToImport), key => key.split('.')[0]);
|
||||
const messagesByFiles = _.mapValues(keysByFiles, (keys, file) => {
|
||||
|
187
src/utils.ts
187
src/utils.ts
@ -2,30 +2,30 @@
|
||||
* @author linhuiw
|
||||
* @desc 工具方法
|
||||
*/
|
||||
import * as path from 'path';
|
||||
import * as _ from 'lodash';
|
||||
import * as inquirer from 'inquirer';
|
||||
import * as fs from 'fs';
|
||||
import { pinyin } from 'pinyin-pro';
|
||||
import { PROJECT_CONFIG, CANARY_CONFIG_FILE } from './const';
|
||||
const colors = require('colors');
|
||||
import * as path from 'path'
|
||||
import * as _ from 'lodash'
|
||||
import * as inquirer from 'inquirer'
|
||||
import * as fs from 'fs'
|
||||
import { pinyin } from 'pinyin-pro'
|
||||
import { PROJECT_CONFIG, CANARY_CONFIG_FILE } from './const'
|
||||
const colors = require('colors')
|
||||
|
||||
function lookForFiles(dir: string, fileName: string): string {
|
||||
const files = fs.readdirSync(dir);
|
||||
const files = fs.readdirSync(dir)
|
||||
|
||||
for (let file of files) {
|
||||
const currName = path.join(dir, file);
|
||||
const info = fs.statSync(currName);
|
||||
const currName = path.join(dir, file)
|
||||
const info = fs.statSync(currName)
|
||||
if (info.isDirectory()) {
|
||||
if (file === '.git' || file === 'node_modules') {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
const result = lookForFiles(currName, fileName);
|
||||
const result = lookForFiles(currName, fileName)
|
||||
if (result) {
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
} else if (info.isFile() && file === fileName) {
|
||||
return currName;
|
||||
return currName
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -34,26 +34,26 @@ function lookForFiles(dir: string, fileName: string): string {
|
||||
* 获得项目配置信息
|
||||
*/
|
||||
function getProjectConfig() {
|
||||
const configFile = path.resolve(process.cwd(), `./${CANARY_CONFIG_FILE}`);
|
||||
let obj = PROJECT_CONFIG.defaultConfig;
|
||||
const configFile = path.resolve(process.cwd(), `./${CANARY_CONFIG_FILE}`)
|
||||
let obj = PROJECT_CONFIG.defaultConfig
|
||||
|
||||
if (configFile && fs.existsSync(configFile)) {
|
||||
obj = {
|
||||
...obj,
|
||||
...JSON.parse(fs.readFileSync(configFile, 'utf8'))
|
||||
};
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语言资源的根目录
|
||||
*/
|
||||
function getCanaryDir() {
|
||||
const config = getProjectConfig();
|
||||
const config = getProjectConfig()
|
||||
|
||||
if (config) {
|
||||
return config.canaryDir;
|
||||
return config.canaryDir
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,8 +62,8 @@ function getCanaryDir() {
|
||||
* @param lang
|
||||
*/
|
||||
function getLangDir(lang) {
|
||||
const langsDir = getCanaryDir();
|
||||
return path.resolve(langsDir, lang);
|
||||
const langsDir = getCanaryDir()
|
||||
return path.resolve(langsDir, lang)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,40 +73,47 @@ function traverse(obj, cb) {
|
||||
function traverseInner(obj, cb, path) {
|
||||
_.forEach(obj, (val, key) => {
|
||||
if (typeof val === 'string') {
|
||||
cb(val, [...path, key].join('.'));
|
||||
cb(val, [...path, key].join('.'))
|
||||
} else if (typeof val === 'object' && val !== null) {
|
||||
traverseInner(val, cb, [...path, key]);
|
||||
traverseInner(val, cb, [...path, key])
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
traverseInner(obj, cb, []);
|
||||
traverseInner(obj, cb, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有文案
|
||||
* 获取指定语言的全部文案
|
||||
* @return 示例:{ 'common.test': '测试', 'common.chinese': '中国' }
|
||||
*/
|
||||
function getAllMessages(lang: string, filter = (message: string, key: string) => true) {
|
||||
const srcLangDir = getLangDir(lang);
|
||||
let files = fs.readdirSync(srcLangDir);
|
||||
files = files.filter(file => file.endsWith('.ts') && file !== 'index.ts').map(file => path.resolve(srcLangDir, file));
|
||||
function getTargetLangObjs(lang: string, filter = (message: string, key: string) => true) {
|
||||
// 是否是JS项目
|
||||
const { isJsProj } = getProjectConfig()
|
||||
// 指定语言配置文件夹
|
||||
const langDir = getLangDir(lang)
|
||||
// 过滤文件并生成对应文件路径
|
||||
let files = fs.readdirSync(langDir)
|
||||
const tsFilter = file => file.endsWith('.ts') && file !== 'index.ts'
|
||||
const jsFilter = file => file.endsWith('.js') && file !== 'index.js'
|
||||
files = files.filter(isJsProj ? jsFilter : tsFilter).map(file => path.resolve(langDir, file))
|
||||
|
||||
const allMessages = files.map(file => {
|
||||
const { default: messages } = require(file);
|
||||
const fileNameWithoutExt = path.basename(file).split('.')[0];
|
||||
const flattenedMessages = {};
|
||||
console.log(fileNameWithoutExt, messages)
|
||||
traverse(messages, (message, path) => {
|
||||
const key = fileNameWithoutExt + '.' + path;
|
||||
// 读取所有文件的文案配置并拍平
|
||||
const langObjs = files.map(file => {
|
||||
const { default: messages } = require(file)
|
||||
const fileName = path.basename(file).split('.')[0]
|
||||
const flattenedMessages = {}
|
||||
traverse(messages, (message, keyPath) => {
|
||||
const key = fileName + '.' + keyPath
|
||||
if (filter(message, key)) {
|
||||
flattenedMessages[key] = message;
|
||||
flattenedMessages[key] = message
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return flattenedMessages;
|
||||
});
|
||||
return flattenedMessages
|
||||
})
|
||||
|
||||
return Object.assign({}, ...allMessages);
|
||||
return Object.assign({}, ...langObjs)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,15 +122,15 @@ function getAllMessages(lang: string, filter = (message: string, key: string) =>
|
||||
* @param times
|
||||
*/
|
||||
function retry(asyncOperation, times = 1) {
|
||||
let runTimes = 1;
|
||||
let runTimes = 1
|
||||
const handleReject = e => {
|
||||
if (runTimes++ < times) {
|
||||
return asyncOperation().catch(handleReject);
|
||||
return asyncOperation().catch(handleReject)
|
||||
} else {
|
||||
throw e;
|
||||
throw e
|
||||
}
|
||||
};
|
||||
return asyncOperation().catch(handleReject);
|
||||
}
|
||||
return asyncOperation().catch(handleReject)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,31 +141,31 @@ function retry(asyncOperation, times = 1) {
|
||||
function withTimeout(promise, ms) {
|
||||
const timeoutPromise = new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(`Promise timed out after ${ms} ms.`);
|
||||
}, ms);
|
||||
});
|
||||
return Promise.race([promise, timeoutPromise]);
|
||||
reject(`Promise timed out after ${ms} ms.`)
|
||||
}, ms)
|
||||
})
|
||||
return Promise.race([promise, timeoutPromise])
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用google翻译
|
||||
*/
|
||||
function translateText(text, toLang) {
|
||||
const CONFIG = getProjectConfig();
|
||||
const options = CONFIG.translateOptions;
|
||||
const { translate: googleTranslate } = require('google-translate')(CONFIG.googleApiKey, options);
|
||||
const CONFIG = getProjectConfig()
|
||||
const options = CONFIG.translateOptions
|
||||
const { translate: googleTranslate } = require('google-translate')(CONFIG.googleApiKey, options)
|
||||
return withTimeout(
|
||||
new Promise((resolve, reject) => {
|
||||
googleTranslate(text, 'zh', PROJECT_CONFIG.langMap[toLang], (err, translation) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(translation.translatedText);
|
||||
resolve(translation.translatedText)
|
||||
}
|
||||
});
|
||||
})
|
||||
}),
|
||||
5000
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,35 +184,35 @@ function translateWithBaiduPinyin(text: string, origin: string) {
|
||||
baiduTranslate(appId, appKey, 'en', 'zh')(text)
|
||||
.then(data => {
|
||||
if (data && data.trans_result) {
|
||||
const result = data.trans_result.map(item => item.dst) || [];
|
||||
resolve(result);
|
||||
const result = data.trans_result.map(item => item.dst) || []
|
||||
resolve(result)
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
reject(err)
|
||||
})
|
||||
}
|
||||
// Pinyin
|
||||
if (origin === 'Pinyin') {
|
||||
const result = pinyin(text, { toneType: 'none' });
|
||||
resolve(result.split('$'));
|
||||
const result = pinyin(text, { toneType: 'none' })
|
||||
resolve(result.split('$'))
|
||||
}
|
||||
}),
|
||||
3000
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return retry(_translateText, 3);
|
||||
return retry(_translateText, 3)
|
||||
}
|
||||
|
||||
function findMatchKey(langObj, text) {
|
||||
for (const key in langObj) {
|
||||
if (langObj[key] === text) {
|
||||
return key;
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,46 +222,46 @@ function findMatchKey(langObj, text) {
|
||||
*/
|
||||
function flatten(obj, prefix = '') {
|
||||
var propName = prefix ? prefix + '.' : '',
|
||||
ret = {};
|
||||
ret = {}
|
||||
|
||||
for (var attribute in obj) {
|
||||
var attr = attribute.replace(/-/g, '_');
|
||||
var attr = attribute.replace(/-/g, '_')
|
||||
if (_.isArray(obj[attr])) {
|
||||
var len = obj[attr].length;
|
||||
ret[attr] = obj[attr].join(',');
|
||||
var len = obj[attr].length
|
||||
ret[attr] = obj[attr].join(',')
|
||||
} else if (typeof obj[attr] === 'object') {
|
||||
_.extend(ret, flatten(obj[attr], propName + attr));
|
||||
_.extend(ret, flatten(obj[attr], propName + attr))
|
||||
} else {
|
||||
ret[propName + attr] = obj[attr];
|
||||
ret[propName + attr] = obj[attr]
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取翻译源类型
|
||||
*/
|
||||
async function getTranslateOriginType() {
|
||||
const { googleApiKey, baiduApiKey } = getProjectConfig();
|
||||
let translateType = ['Google', 'Baidu'];
|
||||
const { googleApiKey, baiduApiKey } = getProjectConfig()
|
||||
let translateType = ['Google', 'Baidu']
|
||||
if (!googleApiKey) {
|
||||
translateType = translateType.filter(item => item !== 'Google');
|
||||
translateType = translateType.filter(item => item !== 'Google')
|
||||
}
|
||||
if (!baiduApiKey || !baiduApiKey.appId || !baiduApiKey.appKey) {
|
||||
translateType = translateType.filter(item => item !== 'Baidu');
|
||||
translateType = translateType.filter(item => item !== 'Baidu')
|
||||
}
|
||||
if (translateType.length === 0) {
|
||||
console.log('请配置 googleApiKey 或 baiduApiKey ');
|
||||
console.log('请配置 googleApiKey 或 baiduApiKey ')
|
||||
return {
|
||||
pass: false,
|
||||
origin: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
if (translateType.length == 1) {
|
||||
return {
|
||||
pass: true,
|
||||
origin: translateType[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
const { origin } = await inquirer.prompt({
|
||||
type: 'list',
|
||||
@ -262,32 +269,32 @@ async function getTranslateOriginType() {
|
||||
message: '请选择使用的翻译源',
|
||||
default: 'Google',
|
||||
choices: ['Google', 'Baidu']
|
||||
});
|
||||
})
|
||||
return {
|
||||
pass: true,
|
||||
origin: origin
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功的提示
|
||||
*/
|
||||
function successInfo(message: string, needEnter = false) {
|
||||
console.log(`${needEnter ? '\n' : ''}successInfo: `, colors.green(message));
|
||||
console.log(`${needEnter ? '\n' : ''}successInfo: `, colors.green(message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败的提示
|
||||
*/
|
||||
function failInfo(message: string) {
|
||||
console.log('failInfo: ', colors.red(message));
|
||||
console.log('failInfo: ', colors.red(message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通提示
|
||||
*/
|
||||
function highlightText(message: string | number) {
|
||||
return colors.yellow(`${message}`);
|
||||
return colors.yellow(`${message}`)
|
||||
}
|
||||
|
||||
export {
|
||||
@ -296,7 +303,7 @@ export {
|
||||
traverse,
|
||||
retry,
|
||||
withTimeout,
|
||||
getAllMessages,
|
||||
getTargetLangObjs,
|
||||
getProjectConfig,
|
||||
translateText,
|
||||
findMatchKey,
|
||||
@ -307,4 +314,4 @@ export {
|
||||
successInfo,
|
||||
failInfo,
|
||||
highlightText
|
||||
};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user