前言
本文针对国际化翻译数据多,并存放在服务器的情况,对优化进行了思考,并写了了一个插件github.com/jmx16449196…,供大家参考或使用。抛砖引玉,如果有需要改进的地方,希望大家指正。
需求背景
我司单页面应用中的国际化功能存在两个可优化的点:
-
国际化翻译数据存在了数据库中。每次切换语种或重新加载页面,都需要请求一次接口,接口大概会耽误1s,十分影响用户体验,特别是首屏加载。
-
由于所有数据都存放在数据库,所以首屏加载时,请求接口前都没有翻译数据可用。
优化方案
针对第一个问题,使用缓存
针对第二个问题,我把翻译数据分为了两份:
静态翻译数据:写死在js中,用于存在一些基础的翻译数据,即使断网了,用户也可以看到正常的页面。
动态翻译数据:从接口获得,这部分数据可以让用户通过相关配置页面去修改。
优化后的页面在进入时,可以分为以下阶段:

关键代码
// 创建实例并调用
const translateManager = new TranslateManager({/*传参*/})
translateManager.update('en', (res) => {
// 触发相应的更新视图的逻辑
// 例如在vue中,使用了vue-i18n,你可以
i18n.setLocaleMessage(res.data);
});
class TranslateManager {
STORAGE_KEY: string
expireTime: number
requestFn: Function | null
staticTranslateData: object
/**
* 构造函数
* @param params
*/
constructor(params: constructorParams) {
this.expireTime = params.expireTime || Infinity; // 前端缓存有效时间
this.STORAGE_KEY = params.storageKey || 'TranslateManager';
this.requestFn = params.requestFn;
this.staticTranslateData = params.staticTranslateData || {};
}
setRequestFn(fn: Function) {
this.requestFn = fn;
}
/**
* 设置缓存
* @param {String} language
*/
setCache(language: string, data: object) {
// 请求完成后缓存
const storageData = jsonParse(localStorage.getItem(this.STORAGE_KEY)) || {};
storageData[language] = data;
storageData['time'] = new Date().getTime();
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(storageData));
}
/**
* 从缓存中读取
* @param language 语种
*/
getCache(language: string) {
const storageData = jsonParse(localStorage.getItem(this.STORAGE_KEY)) || {};
if (storageData && storageData['time'] && storageData[language]) {
const time = storageData['time'];
const now = new Date().getTime();
// 缓存只有两小时
if (now - time <= this.expireTime) {
return storageData[language] || null;
}
}
return null;
}
/**
* 获取后端的静态国际化数据
*/
getDynamicTranslateData(language: string) {
if (!this.requestFn) {
console.error('请先执行setRequestFn!');
return Promise.reject();
}
return this.requestFn({language}).then((res: object) => {
this.setCache(language, res);
return res;
});
}
/**
* 获取前后端交集后的国际化数据
* @param {*} language
*/
getMergeTranslateData(language: string) {
// 没缓存就发起请求
return this.getDynamicTranslateData(language).then((res: any) => {
const data = this.staticTranslateData[language] || {};
// 部分国际化写在了前端,把前端的国际化文件合并到后端返回的数据中
merge(res, data)
if (isEmptyResult(res)) {
return Promise.reject(new Error('locale empty !!'))
}
return res;
})
}
/**
* 主方法
* @param locale 语种
* @param callback 回调函数,用于订制自己触发的渲染逻辑
*/
update(locale: string, callback: Function) {
let staticData = this.staticTranslateData[locale];
const cacheData = this.getCache(locale) || {};
// 使用静态数据和缓存数据的并集,触发第一次视图更新
merge(cacheData, staticData);
callback(cacheData, 'first');
// 返回静态和动态数据的合集
return this.getMergeTranslateData(locale).then((res: object) => {
// 获取数据是否和动态数据不一致
if (res && !isEqual(res, cacheData)) {
callback(res, 'second')
}
})
}
}
延伸
使用了上面的做法,已经很好的达到了预期效果。就在写这篇文章的时候,突然发现还有继续深入优化的方向:可以把动态数据划分不同的模块,分步加载。根据自身业务的逻辑,比如可以根据用户权限。当我用户权限低只可以看主页时,我只加载主页模块的翻译数据即可;当我用户权限高,并进入了相应页面,此时再去下载对应的模块数据。
总结
本次提供的优化方案思路并不难,只是实现起来需要些时间,我根据本次优化的思路造了个轮子,使用十分方便,内含使用demo和注释,欢迎star:github.com/jmx16449196…。如果觉得本文对你有所帮助,求点赞一下,谢谢~
