文章目录
- 引入
- 问题演示
- 补充逻辑
- 注意
- 封装缓存工具类
- 补充状态管理
- 调整多语言初始化
- 调整多语言切换组件
- 解决方案
- 思路整理
- 渲染进程监听语言切换
- 主进程创建多语言切换处理
- 语言切换组件通知主进程语言切换
- 最终实现效果演示
引入
我们之前在这篇文章中集成了 多语言切换,但随着我们项目越来越复杂,单页面已经无法满足我们的需求,我们需要多个窗口去进行页面展示,此时会暴露很多问题,例如多窗口时,某个窗口切换了语言,其他窗口并不会同步切换
demo项目地址
问题演示
我们在src\components\demo\Index.vue页面中也显示一个多语言文本:
<template>
<h1>{{ $t(langMap.app_title) }}</h1>
</template>
<script setup lang="ts">
import langMap from "@/locales/langMap";
</script>
问题如下:
补充逻辑
注意
这里主要是对之前多语言文章的补充,之前写的是个极简的整合,没有考虑很多,如只需要解决多窗口同步问题可直接跳过!!
封装缓存工具类
首先为了记住我们当前所选语言,避免每次重启重新选择,我们将语言持久化在本地,这里参考这篇博客,封装一个缓存工具类:
- src\utils\cacheUtils.ts
/* localstorage封装,缓存工具类 参考:https://blog.csdn.net/w544924116/article/details/120906411
/** key前缀 */
const keyPrefix = 'app_cahce$_';
/**
* @param value 内容
* @param addTime 存入时间
* @param expires 有效时间
*/
interface valObjParams {
value: any;
addTime: number;
expires: number;
}
interface StorageInterface {
/**
* 设置localStorage
* @param value 内容
* @param expires 有效时间 单位 s
*/
set: (key: string, value: any, expires?: number) => void;
/** 获取localStorage,会自动转json */
get: (key: string) => any;
/** 是否含有key */
has: (key: string) => boolean;
/** 移除 */
remove: (key: string) => void;
/** 移除全部缓存 */
clear: () => void;
/** 移除自己前缀的全部缓存 */
clearSelf: () => void;
}
const storage: StorageInterface = {
set: () => {},
get: () => '',
has: () => false,
remove: () => {},
clear: () => {},
clearSelf: () => {}
};
/**
* 是否过期
*/
const isFresh = (valObj: valObjParams) => {
const now = new Date().getTime();
return valObj.addTime + valObj.expires >= now;
};
/* 给key值添加前缀 */
const addPrefix = (key: string) => {
return `${keyPrefix}${key}`;
};
/* 加方法 */
const extend = (s: Storage) => {
return {
set(key: string, value: any, expires?: number) {
const skey = addPrefix(key);
if (expires) {
expires *= 1000; // 将过期时间从微秒转为秒
s.setItem(
skey,
JSON.stringify({
value,
addTime: new Date().getTime(),
expires
})
);
} else {
const val = JSON.stringify(value);
s.setItem(skey, val);
}
if (value === undefined || value === null) {
s.removeItem(skey);
}
},
get(key: string) {
const skey = addPrefix(key);
const item = JSON.parse(s.getItem(skey) as any);
// 如果有addTime的值,说明设置了失效时间
if (item && item.addTime) {
if (isFresh(item)) {
return item.value;
}
/* 缓存过期,清除缓存,返回null */
s.removeItem(skey);
return null;
}
return item;
},
has(key: string) {
const skey = addPrefix(key);
return !!s.getItem(skey);
},
remove: (key: string) => {
const skey = addPrefix(key);
s.removeItem(skey);
},
clear: () => {
s.clear();
},
clearSelf: () => {
const arr = Array.from({ length: s.length }, (_, i) => s.key(i)).filter(
str => str?.startsWith(keyPrefix)
);
arr.forEach(str => s.removeItem(str as string));
}
};
};
Object.assign(storage, extend(window.localStorage));
export default storage;
补充状态管理
我们可以创建一个appStore用来保存整个应用的全局状态:
import { defineStore } from "pinia";
import cacheUtils from "@/utils/cacheUtils";
/**应用相关状态管理 */
export const useAppStore = defineStore("appStore", {
state() {
return {
lang: cacheUtils.get("lang") || "zhCn", // app的语言
};
},
});
调整多语言初始化
- 补充从缓存中取初始值
- src\locales\index.ts
import { createI18n } from "vue-i18n";
import en from "./packages/en";
import zhCn from "./packages/zh-cn";
import cacheUtils from "@/utils/cacheUtils";
// 初始化i18n
const i18n = createI18n({
legacy: false, // 解决Not available in legacy mode报错
globalInjection: true, // 全局模式,可以直接使用 $t
locale: cacheUtils.get("lang") || "zhCn", // 从本地缓存中取语言,如果没有 默认为中文
fallbackLocale: "en", // set fallback locale
messages: {
en,
zhCn,
},
});
export default i18n;
调整多语言切换组件
- 我们在语言切换的时候补充状态更新、缓存设置
import cacheUtils from "@/utils/cacheUtils";
import { useAppStore } from "@/store/modules/appStore";
const appStore = useAppStore();
// 切换语言
function handleCommand(lang: string) {
i18n.locale.value = lang;
// 设置缓存的值
cacheUtils.set("lang", lang);
// 更新全局状态
appStore.lang = lang;
}
解决方案
思路整理
我们可以在多语言初始化的时候,让渲染进程监听多语言改变消息,然后主进程创建一个多语言改变handle,然后在语言切换组件中当语言切换时告知主进程语言切换了,并传参当前的语言,接着主进程的handle遍历所有窗口,除通知主进程的窗口外的其他窗口都触发 多语言改变通知,然后窗口自行更新即可。
渲染进程监听语言切换
1.我们在多语言初始化时监听多语言切换通知:
- 调整src\locales\index.ts代码:
import { useAppStore } from "@/store/modules/appStore";
import { ipcRenderer } from "electron";
// ......
// 注意,因为 pinia还没初始化就进行取值会有问题,所以这里我们单独暴露一个方法,在 src/main.ts中的 app.mount("#app").$nextTick 中调用
// 初始化语言监听
export function initLangListener() {
const appStore = useAppStore();
// 监听语言切换时,同步本窗口更新
ipcRenderer.on("lang:change", (event, lang: string) => {
i18n.global.locale.value = lang;
appStore.lang = lang;
});
}
2.在src/main.ts中执行初始化监听:
import { initLangListener } from "@/locales";
// .....
app.mount("#app").$nextTick(() => {
postMessage({ payload: "removeLoading" }, "*");
// 初始化多语言切换监听
initLangListener();
});
主进程创建多语言切换处理
主进程中创建多语言处理监听,我们在electron\main\index.ts中补充代码:
/**语言修改同步 */
ipcMain.handle("lang:change", (event, lang) => {
// 通知所有窗口同步更改语言
for (const currentWin of BrowserWindow.getAllWindows()) {
const webContentsId = currentWin.webContents.id;
// 这里排除掉发送通知的窗口
if (webContentsId !== event.sender.id) {
currentWin.webContents.send("lang:change", lang);
}
}
});
语言切换组件通知主进程语言切换
当语言切换时,我们需要通知主进程告诉其他窗口同步修改,所以我们调整 多语言切换组件:
import { ipcRenderer } from 'electron';
// 多语言切换时
const handleCommand = (lang: string) => {
// ...
// 主进程通知其他窗口同步修改语言
ipcRenderer.invoke('lang:change', lang);
};