kystudio/build/translate-extract.js (175 lines of code) (raw):
const fs = require('fs');
const path = require('path');
const npmPath = process.env.PWD;
const configs = parseArgConfig([
'--rootPath',
'--output',
'--configFile',
'--dismissNull'
]);
let cacheResult = null;
const translateMap = {};
travellingFiles(resolvePath(configs.rootPath), (filePath) => {
process.stdout.write(`Start translating ${filePath}... `);
// 翻译文件,输出翻译后的object
const tranlsateResult = translateFile(filePath);
// 非.vue和.js的文件会输出undefined,所以过滤掉
if (tranlsateResult !== undefined) {
if (!(configs.dismissNull && tranlsateResult === null)) {
translateMap[filePath] = tranlsateResult;
}
}
process.stdout.write(`Done.\n`);
});
if (!configs.bundles) {
process.stdout.write(`Writing translating file... `);
fs.writeFileSync(resolvePath(`${configs.output}.json`), JSON.stringify(translateMap, null, 2));
process.stdout.write(`Done.\n`);
} else {
process.stdout.write(`Writing translating file... `);
outputBundleFiles(translateMap, configs.bundles);
process.stdout.write(`Done.\n`);
}
// Functions
function parseArgConfig(argumentKeys = []) {
let config = {
rootPath: './src',
output: './message',
configFile: null,
bundles: null,
dismissNull: false,
};
// 读取arguments里面的配置
for (const key of argumentKeys) {
if (typeof config[key.replace('--', '')] === 'boolean') {
const keyIndex = process.argv.findIndex(arg => arg === key);
config = { ...config, [key.replace('--', '')]: keyIndex !== -1 };
} else {
const keyIndex = process.argv.findIndex(arg => arg === key);
const value = process.argv[keyIndex + 1];
if (value && keyIndex !== -1) {
config = { ...config, [key.replace('--', '')]: value };
}
}
}
// 如果arguments里面有配置文件,则用文件的配置覆盖arguments里面的配置
if (config.configFile) {
const fileConfig = readConfigFile(config.configFile);
config = { ...config, ...fileConfig };
}
return config;
}
function resolvePath(filePath) {
return path.resolve(npmPath, filePath);
}
function readConfigFile(filePath) {
return require(resolvePath(filePath))
}
function travellingFiles(filePath, callback) {
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
const children = fs.readdirSync(filePath);
for (const child of children) {
travellingFiles(path.resolve(filePath, child), callback);
}
} else if (stat.isFile()) {
callback(filePath);
}
}
function translateFile(filePath) {
switch (path.extname(filePath)) {
case '.vue':
return readVueTranslate(filePath);
case '.js':
return readJSTranslate(filePath);
default:
return undefined;
}
}
function readVueTranslate(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
const scriptRegex = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
const scriptContent = content.match(scriptRegex) && content.match(scriptRegex)[0];
if (scriptContent && /locales:\s*{/.test(scriptContent)) {
return findLocalesObj(`{${scriptContent.split(/locales:\s*{/)[1]}`);
}
return null;
}
function readJSTranslate(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
if (
content && (
/"en"\s*:\s*{/.test(content) ||
/en\s*:\s*{/.test(content) ||
/'en'\s*:\s*{/.test(content)
)
) {
return findLocalesObj(`{${content.split(/export\s*default\s*{/)[1]}`);
}
return null;
}
function removeLastChar(string = '', char = '') {
const lastIndex = string.lastIndexOf(char);
return lastIndex !== -1 ? string.slice(0, lastIndex - 1) : '';
}
function findLocalesObj(string = '') {
try {
eval(`cacheResult = ${string};`);
return cacheResult;
} catch {
const removedString = removeLastChar(string, '}');
return removedString ? findLocalesObj(removedString) : null;
}
}
function travellingBundles(currentBundle = {}, parentPath = '') {
let bundleMap = {};
for (const folder in currentBundle) {
const folderRule = currentBundle[folder];
if (Object.prototype.toString.call(folderRule) === '[object Object]') {
const subBundleMap = travellingBundles(folderRule, parentPath ? `${parentPath}/${folder}` : folder);
bundleMap = { ...bundleMap, ...subBundleMap };
} else {
bundleMap[parentPath ? `${parentPath}/${folder}` : folder] = folderRule;
}
}
return bundleMap;
}
function parseBundleConfigs(bundleConfigs) {
return Object
.entries(travellingBundles(bundleConfigs, configs.output))
.reduce((bundleMap, [bundlePath, bundleRule]) => ({
...bundleMap,
[resolvePath(bundlePath)]: bundleRule,
}), {});
}
function validateBundleRule(bundleRule, filePath) {
if (Object.prototype.toString.call(bundleRule) === '[object Array]') {
for (const itemRule of bundleRule) {
if (itemRule.test(filePath)) return true;
}
return false;
} else if (Object.prototype.toString.call(bundleRule) === '[object RegExp]') {
return bundleRule.test(filePath);
} else if (bundleRule === '*') {
return true;
}
return false;
}
function outputBundleFiles(translationMap, bundleConfigs) {
let outputBundleMap = {};
const bundleMap = parseBundleConfigs(bundleConfigs);
for (const [translatedFilePath, translateContent] of Object.entries(translationMap)) {
const [bundlePath] = Object.entries(bundleMap)
.find(([, rule]) => validateBundleRule(rule, translatedFilePath));
outputBundleMap = {
...outputBundleMap,
[`${bundlePath}.json`]: {
...outputBundleMap[`${bundlePath}.json`],
[translatedFilePath]: translateContent,
}
};
}
console.log(outputBundleMap);
for (const [bundlePath, bundleContent] of Object.entries(outputBundleMap)) {
const dirpath = path.dirname(bundlePath)
if (!fs.existsSync(dirpath)) {
fs.mkdirSync(dirpath);
}
fs.writeFileSync(bundlePath, JSON.stringify(bundleContent, null, 2));
}
}