mya-jinja
Version:
Support Jinja2 end template engine in mya
210 lines (189 loc) • 6.67 kB
JavaScript
/**
* i18n 方案相关配置
* todo: 考虑拆成插件 mya-jinja-i18n
*/
const fs = require('fs');
const path = require('path');
// const gettextPtn = /\b(gettext|_)\(['|"](.+?)['|"],*/g;
const gettextPtn1 = /\b(gettext|_)\(['](.+?)['],*/g;
const gettextPtn2 = /\b(gettext|_)\(["](.+?)["],*/g;
const mapPlaceholderPtn = /<!--\s*I18N_MAP_PLACEHOLDER\s*-->/;
const i18nBrowserLib = fs.readFileSync(path.join(__dirname, 'i18n-browser.js'), 'utf-8');
let langHtmlModuleId;
function collectGettext(content, callback) {
content.replace(gettextPtn1, (match, p1, key) => {
callback && callback(key);
});
content.replace(gettextPtn2, (match, p1, key) => {
callback && callback(key);
});
}
/**
* 提取前端代码中用到的 gettext,并生成翻译map
* @param ret.map
* @param ret.src
* @param ret.ids
*/
function getLangMap(ret, i18nMapTplName="lang.html") {
// lang.html
const namespace = fis.get('namespace');
const root = fis.project.getProjectPath();
const langHtml = fis.file.wrap(path.join(root, 'page', i18nMapTplName));
const files = ret.src;
let langMap = {};
Object.keys(files).forEach(subpath => {
let file = files[subpath];
if (file.extras && file.extras.isJsTpl) {
const content = file.getContent();
collectGettext(content, key => {
if (!langMap[key]) {
langMap[key] = `{{ gettexttpl('${key.replace(/'/g, "#quote&")}')|safe }}`;
}
});
}
});
const i18nBrowserScript = i18nBrowserLib
.replace('${langMap}', JSON.stringify(langMap))
.replace(/#quote&/g, "\\'");
langHtml.setContent(`<script>${i18nBrowserScript}</script>`);
ret.pkg[langHtml.subpath] = langHtml;
ret.map.res[langHtml.moduleId] = {
uri: langHtml.url,
type: 'html'
};
langHtmlModuleId = langHtml.moduleId;
}
/**
* 提取项目中用到的 gettext,生成原始待翻译map
*/
function getOriginalLangMap(ret) {
// lang.html
const root = fis.project.getProjectPath();
const mapFile = fis.file.wrap(path.join(root, 'lang.json'));
const files = ret.src;
let langMap = {};
Object.keys(files).forEach(subpath => {
let file = files[subpath];
if (file.extras && file.extras.isTpl) {
const content = file.getContent();
collectGettext(content, key => {
if (!langMap[key]) {
langMap[key] = key;
}
});
}
});
mapFile.setContent(JSON.stringify(langMap, null, 4));
ret.pkg[mapFile.subpath] = mapFile;
}
/**
* 构建过程插入,而非模板渲染时插入,好处是无序约定 mapTl 的 name
*/
function insertMap(ret, i18nMapTplName="lang.html", i18nAutoInsertMap) {
const files = ret.src;
const namespace = fis.get('namespace');
const moduleId = langHtmlModuleId || `${namespace}:page/${i18nMapTplName}`;
const mapTpl = `\n {% comp name="${moduleId}" %}`;
Object.keys(files).forEach(subpath => {
let file = files[subpath];
const content = file.getContent();
if (!file.isHtmlLike) {
return;
}
if (i18nAutoInsertMap === false) {
if (content.match(mapPlaceholderPtn)) {
let res = content.replace(mapPlaceholderPtn, mapTpl)
file.setContent(res);
}
} else if (content.indexOf('</head>') > -1) {
let res = content.replace('</head>', mapTpl + '\n</head>');
file.setContent(res);
}
});
}
module.exports = function(fis, opt, retMapPostpackager) {
fis.set('i18nMapTplName', opt.i18nMapTplName || 'lang.html');
const i18nMapTplName = fis.get('i18nMapTplName');
function postpackager(ret) {
getLangMap(ret, i18nMapTplName); // 输出模板文件
insertMap(ret, i18nMapTplName, opt.i18nAutoInsertMap); // 自动插入map模板到页面
retMapPostpackager(ret);
}
// ${srcPath} 在 mya/index.js 中配置
const i18nRules = {
'/${srcPath}/**.{js,tmpl,vue}': {
extras: { isJsTpl: true },
},
'/static/**.{js,tmpl}': {
extras: { isJsTpl: false }
},
'/${srcPath}/**.{js,html:js,vue:js,jsx}': { // tips: tmpl 已经被转化为js
postprocessor: function(content, file, conf) {
// TODO: 使用 ast 进行精准替换
return content.replace(/(\S\s*|\{\{\s*)?\b((gettext|_)\()/g, function(match, p1, p2) {
if (['.', '\'', '\"', '{{'].indexOf(p1.trim()) > -1) {
return match;
}
return `${p1}__M.${p2}`
});
}
},
// '/{common,*_common}/static/**.{js,tmpl}': {
// extras: { isJsTpl: false }
// },
'${i18nMapTplName}': {
release: '${build.templatePublishPath}/$0'
},
'/static/locale/(*.json)': {
release: '/template/mya_locale/${namespace}/$1'
},
'::package': {
postpackager: postpackager
},
'*.{png,jpg,jpeg,gif}': {
useMap: true,
},
};
// 用于收集 gettext,输出翻译文件
const i18nMediaRuels = {
'/${srcPath}/**.{js,html,tmpl,vue,jsx}': {
extras: { isTpl: true }
},
'/static/**.{js,tmpl}': {
extras: { isTpl: false }
},
// '/{common,*_common}/static/**.{js,tmpl}': {
// extras: { isTpl: false }
// },
'::package': {
postpackager: [postpackager, function(ret) {
getOriginalLangMap(ret); // 输出待翻译文件
}]
},
'lang.json': {
release: '$0'
},
'!lang.json': {
deploy: function(options, modified, total, next) {
total.splice(0, total.length);
next(); //由于是异步的如果后续还需要执行必须调用 next
}
}
};
const devRules = {
'::package': {
postpackager: postpackager
}
};
Object.keys(i18nRules).forEach(key => {
fis.match(key, i18nRules[key]);
});
Object.keys(i18nMediaRuels).forEach(key => {
fis.media('i18n').match(key, i18nMediaRuels[key]);
});
['develop', 'mock', 'prod'].forEach(function(media) {
Object.keys(devRules).forEach(function(key) {
fis.media(media).match(key, devRules[key], true); // 防止规则被覆盖
});
});
}