近期项目上有个需求,就是要进行一个多语言的翻译,要进行适配20+种语言,本来想使用@nuxtjs/i18n,但是发现不太适合我们的项目就放弃了,于是进行了一个自己造轮子
有几点需求:
- 文本国际化(Internationalization,缩写为i18n):词条id为本地维护的基础词条库对象,项目中进行自动切换。
- 语言切换功能:监听客户端多语言切换语言回调
- 动态文本替换:通过在前端代码中项目中自动切换,将动态生成的文本与翻译资源关联起来
- 动态加载语言文件,并且进行兜底,语言id适配以key.keys或者key_keys形式
于是开始动工,本来领导的意思是封装全局自定义指令来进行自动的翻译,但是由于引入了第三方组件库,所以pass(哪怕没引入也要pass,除非全部都是div,不去做封装,哈哈哈)
- 思路:
封装全局方法,挂载在vue上,然后写一个翻译的方法,全局方法里边调用翻译的方法,传语言id进行翻译
我们的语言包是这样的
lang/js/translate/zh-CN.json
{
"setting":{
"cancel":"取消",
"confirm": "确认",
}
}
第一步: 封装全局方法,进行挂载
export default ({ app }, inject) => {
inject('i', (name) => {
return ''
})
}
这里也可以使用这样的
Vue.prototype.$i = (name)=>{
return ''
}
封装了一个全局的方法,然后进行return,所以我们调用的时候就是$i('setting.cancel')
第二步:写一个翻译的方法,由于我们的语言文件是进行系统的翻译,会自动合到git里边的,在固定的文件夹下,所以这里我们使用动态引入的方法加载语言包
// getStorage为我们自己封装的localStorage.getItem('xxx')
// ...引入一些东西,比方vue,getStorage等
const language = (value) => {
// 如果当前加载的语言文件不在,就加载中文
let scriptName = getStorage('LANGUAGE') || 'zh-CN'
let lang
// 动态加载语言文件,做一个兼容的判断,万一当前的语言包不存在,做个兜底
try {
lang = require(`../lang/js/translate/${scriptName}/lang.json`)
} catch {
lang = require(`../lang/js/translate/zh-CN/lang.json`)
}
const language = require(`../lang/js/translate/zh-CN/lang.json`)
let translatedText
return translatedText
}
第三步:这里的头绪有了,接下来进行处理翻译
// 进行判断当前有没有传值,没有就返回一个空
if (!value) return ''
// 这里进行处理,上边说的适配_或者.
if (/[_.]/.test(value)) {
const arr = value.split(/[_]/.test(value) ? '_' : '.')
let text
try {
text = lang
for (let i = 0; i < arr.length; i++) {
text = text[arr[i]]
}
if (!text) {
throw new Error('text 查找不到')
}
} catch (err) {
// 如果错误显示中文
text = language
for (let i = 0; i < arr.length; i++) {
text = text[arr[i]]
}
}
translatedText = text
} else {
translatedText = lang[value] || value
}
然后我们这里就处理好了,就可以正常使用了,但是在使用的过程中,出现了一些问题,因为
nuxt是ssr的,但是在ssr的时候,是获取不到localStorage的,所以这里我做了一点特殊的处理,我在app.html中,获取当前语言,然后将参数拼接在url上,在调用$i的时候获取语言的参数,因为在刚才我们的封装全局方法的时候是可以获取当前路由的参数的app.context.query,如果上边注册方法用的prototype的话就使用$nuxt._route.query,作用是一样的,获取路由的参数,在将获取的参数传给翻译的方法,就不会出现在ssr时,因为找不到本地存储,而加载中文导致中文一闪而过的问题
话不多说,上代码,先看在app.html中处理url的
// 获取url中的参数
function getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
// 替换url中的参数
function replaceUrlParam(url, paramName, paramValue) {
// 构造一个正则表达式,用以匹配参数
const pattern = new RegExp('(' + paramName + '=).*?(&|$)');
// 如果URL中存在该参数,则进行替换
if (url.indexOf(paramName + '=') >= 0) {
url = url.replace(pattern, '$1' + paramValue + '$2');
} else {
// 不存在则添加
const include = url.includes('?');
url += (include ? '&' : '?') + paramName + '=' + paramValue;
}
return url;
}
// 获取当前的语言,因为我们是在app里边的,所以这样获取,
const lang = navigator.language || navigator.userLanguage
// 获取参数进行判断,替换url
if (getQueryString("lang") !== lang) {
location.replace(replaceUrlParam(location.href, "lang", lang))
}
// 获取页面的语言,进行存储
if (lang) {
localStorage.setItem('LANGUAGE', xxx)
}
然后再处理刚才的$i方法,
const language = (value, LANGUAGE) => {
// 如果当前加载的语言文件不在,就加载中文
let scriptName = LANGUAGE || getStorage('LANGUAGE') || 'zh-CN'
let lang
// 动态加载语言文件,做一个兼容的判断
try {
lang = require(`../lang/js/translate/${scriptName}/lang.json`)
} catch {
lang = require(`../lang/js/translate/zh-CN/lang.json`)
}
const language = require(`../lang/js/translate/zh-CN/lang.json`)
let translatedText
if (!value) return ''
if (/[_.]/.test(value)) {
const arr = value.split(/[_]/.test(value) ? '_' : '.')
let text
try {
text = lang
for (let i = 0; i < arr.length; i++) {
text = text[arr[i]]
}
if (!text) {
throw new Error('text 查找不到')
}
} catch (err) {
// 如果错误显示中文
text = language
for (let i = 0; i < arr.length; i++) {
text = text[arr[i]]
}
}
translatedText = text
} else {
translatedText = lang[value] || value
}
return translatedText
}
// 获取url的当前语言,然后进行全局设置,在ssr的时候,如果url上没有当前语言,那就使用本地存储的
export default ({ app }, inject) => {
inject('i', (name) => {
return language(name, app.context.query.lang)
})
}
到此为止大功告成,可以进行正常的切换,如果这是在用vue的话,没有ssr,能少很多步骤。