如何在firebase-messaging-sw.js中访问localStorage实现FCM通知多语言
报错原因
firebase-messaging-sw.js运行在独立的Service Worker线程上下文,没有主线程的window全局对象,而localStorage是挂载在window下的同步API,在Service Worker中完全不存在,因此会抛出localStorage is not defined的错误。
另外原代码还有一个隐藏bug:当推送title不是new order时,translate()直接返回body字符串,后续取.body属性会得到undefined,导致通知正文丢失。
实现方案
推荐用主线程同步语言配置 + Service Worker端IndexedDB持久化的方案,不需要改动后端推送逻辑,兼容性最好。
步骤1:主线程(Vue项目代码)同步语言配置
Service Worker只支持异步存储API,无法直接读localStorage,需要在用户切换语言、Service Worker注册完成两个时机,把当前选中的语言通过postMessage同步给Service Worker:
- 语言切换逻辑中加入同步代码
// 你原有的语言切换逻辑 const changeLang = async (selectedLang) => { localStorage.setItem('language', selectedLang) // 新增:同步语言配置给Service Worker if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.getRegistration() if (registration?.active) { registration.active.postMessage({ type: 'SET_LANG', lang: selectedLang }) } } }
- Service Worker注册成功后首次同步当前语言
// 你原有的注册Firebase Service Worker的逻辑 if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js') // 新增:首次注册时同步当前已存的语言 const currentLang = localStorage.getItem('language') || 'ar' if (registration.active) { registration.active.postMessage({ type: 'SET_LANG', lang: currentLang }) } }
步骤2:修改firebase-messaging-sw.js逻辑
在SW文件中加入极简IndexedDB存储工具,持久化主线程同步过来的语言配置,收到后台推送时读取配置做文案翻译:
// 开头加:极简IndexedDB键值对存储工具,无需引入第三方库 const DB_NAME = 'app-config' const STORE_NAME = 'settings' const openDB = () => { return new Promise((resolve, reject) => { const req = indexedDB.open(DB_NAME, 1) req.onupgradeneeded = () => req.result.createObjectStore(STORE_NAME) req.onsuccess = () => resolve(req.result) req.onerror = () => reject(req.error) }) } const setItem = async (key, value) => { const db = await openDB() return new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, 'readwrite') tx.objectStore(STORE_NAME).put(value, key) tx.oncomplete = () => resolve() tx.onerror = () => reject(tx.error) }) } const getItem = async (key) => { const db = await openDB() return new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, 'readonly') const req = tx.objectStore(STORE_NAME).get(key) req.onsuccess = () => resolve(req.result) req.onerror = () => reject(req.error) }) } // 监听主线程发来的配置更新 self.addEventListener('message', async (event) => { if (event.data?.type === 'SET_LANG') { await setItem('language', event.data.lang) } }) // 修改原有的onBackgroundMessage逻辑为异步 firebase.messaging().onBackgroundMessage(async (payload) => { const { title, body, icon, ...restPayload } = payload.data; // 读取持久化存储的语言,默认阿拉伯语 const savedLang = await getItem('language') || 'ar' const fr = savedLang === "fr"; const translate = () => { if (title == "new order") { return { title: fr?"Nouvelle commande":"طلبية جديدة", body: fr?`La commande a le code ${body}`:`الطلبية تحمل الرمز ${body}`, }; } else { // 修复原代码的bug,统一返回对象格式 return { title, body }; } }; const translated = translate() const options = { data: restPayload, body: translated.body, icon: "img/icons/android-chrome-192x192.png", badge: "img/icons/badge-48x48.png", vibrate: [400, 100, 400, 100, 400, 100, 400], dir: fr?"ltr":"rtl", lang: fr?"fr":"ar", }; return self.registration.showNotification(translated.title, options); });
可选方案:后端携带多语言文案
如果可以调整推送服务逻辑,也可以在发送FCM消息时直接把所有支持语言的文案放在payload中,SW端只需要读取本地存的语言,直接取对应字段即可,不需要在SW里维护翻译映射表,后续新增语言不需要重新发布Service Worker文件。
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

