UNPKG

mya-jinja

Version:

Support Jinja2 end template engine in mya

210 lines (189 loc) 6.67 kB
/** * 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); // 防止规则被覆盖 }); }); }