cnd-i18n-loader
Version:
cnd-i18n-loader 一键式实现项目的国际化 语言包 处理的过程
378 lines (371 loc) • 14.3 kB
JavaScript
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, '‚').replace(/\\"/g, '"');
//替换所有包含中文的普通字符串
statement = statement.replace(/(['"])(((?!\1).)*[\u4e00-\u9fa5]+((?!\1).)*)\1/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(${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
};
};
/**
* 导入模块的正则
* @param moduleName
* @return {RegExp}
*/
const generateImportModuleTestReg = (moduleName) => {
return new RegExp(`import[\\s\\t]+([^\\s\\t]+)[^'"]+["']${moduleName}['"]|(const|let|var)[\\s\\t]+([^\\s\\t]+)[^'"]+['"]${moduleName}['"]`, 'im');
};
/**
* JS部分需要注入实例
* @param content
* @return {string}
*/
const injectInstance = (content, vue) => {
let importContent = '';
let injectContent = '';
if (vue === 2) { //vue2注入
//判断是否注入vue
let matchVue = content.match(generateImportModuleTestReg('vue'));
let moduleVue = 'Vue';
if (!matchVue) {
importContent = `${importContent}
import Vue from 'vue';\n`;
} else {
moduleVue = matchVue[1] || matchVue[3];
}
injectContent = `${injectContent}`
if(content.indexOf('import i18n from') === -1){
injectContent += `
import i18n from '@/i18n';`
}
//若未绑定 $t ,则进行绑定
if (content.indexOf('const $t = i18n.t.bind(i18n)') < 0) {
injectContent += `
const $t = i18n.t.bind(i18n);
`;
}
} else { // vue3注入
if(content.indexOf('import i18n from') === -1){
injectContent += `
import i18n from '@/i18n';`
}
//若未绑定 $t ,则进行绑定
if (content.indexOf('const $t = i18n.global.t') < 0) {
injectContent += `
const $t = i18n.global.t;
`;
}
}
//将引入模块的内容放到内容区的最前面
content = `${importContent}${injectContent}${content}`;
let [lastImport] = content.match(/import(?!from).+from(?!from).+;?/gm).reverse();
if (lastImport) {
content = content.replace(lastImport, match => {
return `\n${match}\n`;
});
}
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>\/]+)((?:\s+[^<>="'\s]+(?:\s*=\s*(["']).*?\3|\{.*?\}|\[.*?\])?)*)\s*(\/?>)/gs,
(_, tag, attrs, sign, endTag) => {
// 属性换成一整行的形式
attrs = attrs
.replace(/\n\s*/g, " ") // 将换行符及前后的空白替换成空格
.replace(/\s+/g, " ") // 多余的空格替换为单个空格
//提取包含中文属性值的属性并进行替换
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}`
}
)
// 修正自闭合标签格式为 `<tag attrs />`
return `${tag}${attrs}${endTag === "/>" ? " />" : endTag}`
}
)
// 匹配查找 {{ 变量内 包含中文的 }}
content = content.replace(/({{)([^}}]*[\u4e00-\u9fa5]+[^{{]*)(}})/gm, (_, prevSign, value, afterSign) => {
return `${prevSign}${replaceStatement(needReplace, messages, value).content}${afterSign}`;
});
//匹配查找标签内容(包含中文的)并进行替换
content = content.replace(/(>)([^><]*[\u4e00-\u9fa5]+[^><]*)(<)/gm, (_, prevSign, value, afterSign) => {
value = Utils.trim(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(/>/g, ">").replace(/</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, vue = 2) => {
// 忽略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?/g, (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;
});
// 注释单行双重过滤
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) => {
content = content.replace(
/(<[^\/\s]+)((?:\s+[^<>="'\s]+(?:\s*=\s*(["']).*?\3)?)*)\s*(\/?>)/gm,
(_, tag, attrs, sign, 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, vue);
}
return content;
}