diff --git a/i18n/index.ts b/i18n/index.ts index a6e9e00..e467f98 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -1,10 +1,14 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; +import type { CustomDetector } from 'i18next-browser-languagedetector'; // 💡 1. 引入官方强类型探测器接口 import LanguageDetector from 'i18next-browser-languagedetector'; import { storageUtil } from '@/utils/chromeStorage'; import dayjs from 'dayjs'; -// 同步加载全局命名空间(所有页面都需要) +// 导入 Day.js 本地化语言包 +import 'dayjs/locale/zh-cn'; + +// 同步加载全局核心命名空间 import commonZh from './locales/zh/common.json'; import featuresZh from './locales/zh/features.json'; import commonEn from './locales/en/common.json'; @@ -28,21 +32,22 @@ const LANGUAGE_STORAGE_KEY = 'app/language'; const LANGUAGE_SNAPSHOT_KEY = 'snapshot/app/language'; /** - * 将任意语言标识归一化为受支持的语言代码 + * 将任意语言标识归一化为受支持的核心代码 */ export const normalizeLanguage = (lng: string): SupportedLanguage => { - return lng.startsWith('zh') ? 'zh' : 'en'; + if (!lng) return 'en'; + return lng.toLowerCase().startsWith('zh') ? 'zh' : 'en'; }; /** - * 校验语言是否受支持 + * 严格校验语言安全边界 */ const isValidLanguage = (lng: unknown): lng is SupportedLanguage => { return typeof lng === 'string' && (SUPPORTED_LANGUAGES as readonly string[]).includes(lng); }; /** - * 同步从 localStorage 获取语言快照(用于消除异步加载产生的首屏闪烁) + * 同步从 localStorage 获取语言快照(消除异步闪烁) */ const getSyncLanguageSnapshot = (): SupportedLanguage | null => { try { @@ -51,20 +56,24 @@ const getSyncLanguageSnapshot = (): SupportedLanguage | null => { const parsed = JSON.parse(val) as unknown; return isValidLanguage(parsed) ? parsed : null; } catch (error) { - console.error('解析语言同步快照失败:', error); + console.error('[i18n] Failed to parse sync language snapshot from localStorage:', error); return null; } }; -// 自定义 Chrome Storage 探测器 -const chromeStorageDetector = { +// 💡 2. 强类型接口重塑:显式绑定 CustomDetector 类型, +// 告诉 TS 编译器这些方法将被全局 Languagedetector 框架隐式调用,彻底治愈“未使用函数”报错! +const chromeStorageDetector: CustomDetector = { name: 'chromeStorage', lookup() { - // 同步初始化已通过 getSyncLanguageSnapshot + init 的 lng 参数处理 return undefined; }, cacheUserLanguage(lng: string) { - storageUtil.set(LANGUAGE_STORAGE_KEY, lng); + const target = normalizeLanguage(lng); + // 💡 修复点:对异步写盘操作追加 void 算子或 catch,吞掉 Promise 被忽略警告 + storageUtil.set(LANGUAGE_STORAGE_KEY, target).catch((err) => { + console.error('[i18n Detector Error] Failed to write back language state:', err); + }); }, }; @@ -73,7 +82,8 @@ detector.addDetector(chromeStorageDetector); const syncLng = getSyncLanguageSnapshot(); -i18n +// 💡 3. 修复点:对 i18n.init() 返回的异步 Promise 前方追加 void 斩断依赖链,放行编译 +void i18n .use(detector) .use(initReactI18next) .init({ @@ -92,27 +102,43 @@ i18n }, }); -// 监听语言变化:同步 Day.js 和 localStorage 快照 +// 监听语言变更 i18n.on('languageChanged', (lng) => { const normalizedLng = normalizeLanguage(lng); dayjs.locale(normalizedLng === 'zh' ? 'zh-cn' : 'en'); localStorage.setItem(LANGUAGE_SNAPSHOT_KEY, JSON.stringify(normalizedLng)); }); -// 初始化时从存储中恢复语言 -storageUtil.get(LANGUAGE_STORAGE_KEY).then((lng) => { - const targetLng = lng || syncLng; +// 初始化时从长期异步存储中恢复校准 +storageUtil + .get(LANGUAGE_STORAGE_KEY) + .then((lng) => { + const rawTargetLng = lng || syncLng; - if (targetLng && isValidLanguage(targetLng) && targetLng !== i18n.language) { - i18n.changeLanguage(targetLng); - } else if (!targetLng) { - // 第一次运行,归一化并持久化初始语言 - const initialLng = normalizeLanguage(i18n.language); - storageUtil.set(LANGUAGE_STORAGE_KEY, initialLng); - if (initialLng !== i18n.language) { - i18n.changeLanguage(initialLng); + if (!rawTargetLng) { + const initialLng = normalizeLanguage(i18n.language); + + // 💡 修复点:对初始化同步写盘追加安全的 Promise .catch() 异常隔离防护罩 + storageUtil.set(LANGUAGE_STORAGE_KEY, initialLng).catch((err) => { + console.error('[i18n Init Error] Persistent sync collapsed:', err); + }); + + if (initialLng !== normalizeLanguage(i18n.language)) { + // 💡 修复点:对 changeLanguage 异步微任务进行显式 void 断链安全隔离 + void i18n.changeLanguage(initialLng); + } + return; } - } -}); + + const targetLng = normalizeLanguage(String(rawTargetLng)); + + if (isValidLanguage(targetLng) && targetLng !== normalizeLanguage(i18n.language)) { + // 💡 修复点:对 changeLanguage 异步微任务进行显式 void 断链安全隔离 + void i18n.changeLanguage(targetLng); + } + }) + .catch((err) => { + console.error('[i18n Context Error] Async local storage lookup collapsed:', err); + }); export default i18n;