UNPKG

magic-i18n

Version:

magic-i18n 一键式实现项目的国际化 语言包 处理的过程

293 lines (288 loc) 11.4 kB
const Utils = require('./utils'); const zhReg = /^([\u4e00-\u9fa5]|[a-zA-Z])+$/; // 中英文正则 /** * 用来替换包含中文的语句,包括普通字符串和模板字符串 * @param needReplace * @param messages * @param statement * @param pSign * @return {{hasReplace: *, content: *}} */ const replaceStatement = (needReplace, messages, statement, pSign = '\'') => { let hasReplace = false; //替换所有包含中文的模板字符串 statement = statement.replace(/(`)(((?!\1).)*[\u4e00-\u9fa5]+((?!\1).)*)\1/gm, (_, sign, value) => { //先对内部的 ${} 中的内容进行参数传参替换 let [matchIndex, matchArr] = [0, []]; value = value.replace(/\${([^}]+)(})/gm, (_, value) => { matchArr.push(value); return `{${matchIndex++}}`; }); let key = Utils.Md5_16(value); //如果需要替换,且无对应替换值,则直接返回 if (needReplace && !messages[key]) { return _; }!needReplace && !messages[key] && (/.*[\u4e00-\u9fa5]+.*$/.test(value)) && (messages[key] = value); hasReplace = true; let cSign = pSign === '"' ? '\'' : '"'; if (!matchArr.length) { return `$t(${cSign}${key}${cSign}')`; } else { return `$t(${cSign}${key}${cSign},[${matchArr.toString()}])`; } }); //替换\' \" statement = statement.replace(/\\'/g, '&sbquo;').replace(/\\"/g, '&quot;'); //替换所有包含中文的普通字符串 statement = statement.replace(/(['"])(((?!\1).)*[\u4e00-\u9fa5]+((?!\1).)*)\1/gm, (_, sign, value) => { //将\' \"替换回来 // value = value.replace(/&sbquo;/g, '\'').replace(/&quot;/g, '\"'); // 清除两端空格 value = Utils.trim(value) let key = Utils.Md5_16(value); //如果需要替换,且无对应替换值,则直接返回 if (needReplace && !messages[key]) { return _; }!needReplace && !messages[key] && (/.*[\u4e00-\u9fa5]+.*$/.test(value)) && (messages[key] = value); hasReplace = true; return `$t(${sign}${key}${sign})`; }); //替换所有js内jsx语法普通字符串 statement = statement.replace(/(>)([^><]*[\u4e00-\u9fa5]+[^><]*)(<)/gm, (_, sign, value) => { // value = value.replace(/>/g, "'").replace(/</g, "'"); // 清除两端空格 value = Utils.trim(value) let key = Utils.Md5_16(value); //如果需要替换,且无对应替换值,则直接返回 if (needReplace && !messages[key]) { return _; }!needReplace && !messages[key] && (/.*[\u4e00-\u9fa5]+.*$/.test(value)) && (messages[key] = value); hasReplace = true; return `>{$t("${key}")}<`; }); return { content: statement, hasReplace }; }; /** * JS部分需要注入实例 * @param content * @return {string} */ const injectInstance = (content) => { let importContent = ''; let injectContent = ''; //若未绑定 $t ,则进行绑定 if (content.indexOf('const $t = i18n.global.t') < 0) { injectContent = ` import i18n from '@/i18n'; const $t = i18n.global.t; `; } //将引入模块的内容放到内容区的最前面 content = `${importContent}${content}`; let [lastImport] = content.match(/import(?!from).+from(?!from).+;?/gm).reverse(); content = content.replace(lastImport, match => { return ` ${match}\n ${injectContent} `; }); return content; }; /** * 生成模板部分 * @param messages * @param content * @param needReplace * @return {string} */ exports.generateTemplate = (messages, content, needReplace) => { // 忽略不需要替换中文的抓取 let ignores = {}; let ignoreIndex = 0; // 替换忽略的部分 if (content.indexOf('<i18n-ignore') > 0 && content.indexOf('i18n-ignore>') > 0) { content = content.replace(/(\<!--\s*\<i18n-ignore*(.|\s)+?\s*i18n-ignore>\s*-->)/gm, (match, p1, p2, p3, offset, str) => { //排除掉url协议部分 if (match.indexOf("<!-- ignore_") > -1) return match; if (offset > 0 && str[offset - 1] === ':') return match; let ignoresKey = `<!-- ignore_${ignoreIndex++} -->`; ignores[ignoresKey] = match; return ignoresKey; }); } //匹配查找tag中的属性并进行替换包含中文属性的部分 content = content.replace(/(<[^\/\s]+)([^<>]+)(\/?>)/gm, (_, tag, attrs, endTag) => { //提取包含中文属性值的属性并进行替换 attrs = attrs.replace(/([^\s]+)=(["'])(((?!\2).)*[\u4e00-\u9fa5]+((?!\2).)*)\2/gim, (_, attr, sign, value) => { if (attr.match(/^(v-|@)/) || 'class,style,src,href,width,height'.split(',').includes(attr.trim())) { //对于已经是v-开头的以及白名单内的属性,不进行替换 return `${attr}=${sign}${value}${sign}`; } if (attr.indexOf(':') === 0) { //对所有:开头的属性替换为v-bind: 模式 return `v-bind${attr}=${sign}${value}${sign}`; } else { //对所有的字符串属性替换为v-bind:模式 if (!['true', 'false'].includes(value) && isNaN(value)) { value = sign === '"' ? `'${value}'` : `"${value}"`; } return `v-bind:${attr}=${sign}${value}${sign}`; } }); //通过对v-bind属性中包含有中文的部分进行国际化替换 attrs = attrs.replace(/(v-bind:[^=]+=)(['"])(((?!\2).)+[\u4e00-\u9fa5]+((?!\2).)+)\2/gim, (_, attr, sign, value) => { return `${attr}${sign}${replaceStatement(needReplace, messages, value, sign).content}${sign}`; }); return `${tag}${attrs}${endTag}`; }); //匹配查找标签内容(包含中文的)并进行替换 content = content.replace(/(>)([^><]*[\u4e00-\u9fa5]+[^><]*)(<)/gm, (_, prevSign, value, afterSign) => { // 去除中间的所有空格和换行符号 value = value.replace(/\s+/g, "");; //将所有不在 {{}} 内的内容,用 {{}} 包裹起来 value = value.replace(/^((?!{{).)+/gm, value => { //前面部分 return `{{${JSON.stringify(value)}}}`; }); value = value.replace(/}}(((?!}}).)+)$/gm, (_, value) => { //后面部分 return `}}{{${JSON.stringify(value)}}}`; }); value = value.replace(/&gt;/g, ">").replace(/&lt;/g, "<"); //对所有的{{}}内的内容进行国际化替换 value = value.replace(/({{)(((?!\1|}}).)+)(}})/gm, (_, prevSign, value, $3, afterSign) => { return `${prevSign}${replaceStatement(needReplace, messages, value).content}${afterSign}`; }); // 过滤未转化而且有英文等内容的 value = value.replace(/({{")(((?!\1|}}).)+)("}})/gm, (_, prevSign, value, $3, afterSign) => { return value }); // return value; return `${prevSign}${value}${afterSign}`; }); //换回忽略部分 content = content.replace(/<!-- ignore_\d+ -->/gim, match => { return ignores[match]; }); return content; }; /** * 生成JS部分 * @param messages * @param content * @param needReplace * @return {string} */ exports.generateScript = (messages, content, needReplace) => { // 忽略js的抓取 let ignores = {}; let ignoreIndex = 0; // 替换忽略的部分 if (content.indexOf('<i18n-ignore') > 0 && content.indexOf('i18n-ignore>') > 0) { content = content.replace(/\<i18n-ignore*(.|\n|\r|\s)+?\s*i18n-ignore>/gim, (match, p1, p2, p3, offset, str) => { if (match.indexOf("/*ignore_") > -1) return match; let ignoresKey = `/*ignore_${ignoreIndex++}*/`; ignores[ignoresKey] = match; return ignoresKey; }); } //替换注释部分 let comments = {}; let commentsIndex = 0; let hasReplace = false; // '' // 过滤多行注释 content = content.replace(/(?:^|\n|\r)\s*\/\*[\s\S]*?\*\/\s*(?:\r|\n|$)/gim, (match, p1, p2, p3, offset, str) => { //排除掉url协议部分 if (offset > 0 && str[offset - 1] === ':') return match; let commentsKey = `/*comment_${commentsIndex++}*/`; comments[commentsKey] = match; return commentsKey; }); // 注释单行 content = content.replace(/(\/\*(.|\n|\r)*\*\/)|(\/\/.*)/gim, (match, p1, p2, p3, offset, str) => { //排除掉url协议部分 if (match.indexOf("/*comment_") > -1) return match; if (offset > 0 && str[offset - 1] === ':') return match; let commentsKey = `/*comment_${commentsIndex++}*/`; comments[commentsKey] = match; return commentsKey; }); //js内jsx语法 匹配查找tag中的属性并进行替换包含中文属性的部分 content = content.replace(/(<[^\/\s]+)([^<>]+)(\/?>)/gm, (_, tag, attrs, endTag) => { //提取包含中文属性值的属性并进行替换 attrs = attrs.replace(/([^\s]+)=(["'])(((?!\2).)*[\u4e00-\u9fa5]+((?!\2).)*)\2/gim, (_, attr, sign, value) => { if ('class,style,src,href,width,height'.split(',').includes(attr.trim())) { //排除css样式类,不进行替换 return `${attr}=${sign}${value}${sign}`; } return `${attr}={${sign}${value}${sign}}`; }); return `${tag}${attrs}${endTag}`; }); //对包含中文的部分进行替换操作 content = content.replace(/(['"`])(((?!\1).)*[\u4e00-\u9fa5]+((?!\1).)*)\1/gm, (value, prevSign, str, afterSign, p2, index) => { // 特殊中文处理 // 类型1 // <sa-button type="text">更多<i class="sa-icon-arrow-down"></i></sa-button> // 这种类型的特殊处理 ">更多<i class=" const prevIndex = value.indexOf('>') const nextIndex = value.indexOf('<') let prev = ''; let next = ''; // 类型1处理 if (prevIndex > -1 && nextIndex > -1 && nextIndex > prevIndex && value.indexOf('${') === -1) { // 过滤类型2 nextIndex > prevIndex // <span style="color: #52C718">已激活</span> // 过滤类型类型3 value.indexOf('${') === -1 // 当前已选中<span style="color: #008cfe; font-weight: bold;">${count}</span>个基础指标 if (value.indexOf('${') === -1) { const index = prevIndex + 1 prev = value.substr(0, index); next = value.substr(nextIndex, value.length - 1); value = value.substr(index, nextIndex - index); value = `"${value}"` } } let { hasReplace: replaced, content } = replaceStatement(needReplace, messages, value); hasReplace = hasReplace || replaced; if (prevIndex > -1 && nextIndex > -1 && nextIndex > prevIndex && value.indexOf('${') === -1) { // 类型1处理 content = prev + '{' + content + "}" + next; } return content; }); // 对js内jsx语法进行处理 匹配查找标签内容(包含中文的)并进行替换 content = content.replace(/(>)([^><]*[\u4e00-\u9fa5]+[^><]*)(<)/gm, (_, prevSign, value, afterSign) => { const regValue = value.replace(/\s*/g, "") if (!zhReg.test(regValue)) { return prevSign + value + afterSign; } value = prevSign + value + afterSign; let { hasReplace: replaced, content } = replaceStatement(needReplace, messages, value); hasReplace = hasReplace || replaced; return content; }); //换回注释部分 content = content.replace(/\/\*comment_\d+\*\//gim, match => { return comments[match]; }); //换回忽略部分 content = content.replace(/\/\*ignore_\d+\*\//gim, match => { return ignores[match]; }); //如果需要注入示例且已经有替换发生 if (needReplace && hasReplace) { content = injectInstance(content); } return content; };