373fc0496c
统一简化多个页面里的CopyButton调用,删除不再需要的空回调参数 * feat: 添加clearCookies函数的单元测试并修复cookie域名处理逻辑 * feat: 增强 data URI 处理,支持带参数的前缀并更新相关测试 * fix: 修正 AGENTS.md 中 TypeScript 类型检查命令的描述 * feat: 添加 settings.local.json 文件以配置 Bash 权限 * perf: 预设背景色避免 Popup 弹窗白屏闪烁 * perf: 避免图标过早实例化,传递组件引用而非 JSX 节点 * feat: 添加 useLazyTranslation 和 preloadNamespaces 函数以支持动态加载 i18n 命名空间 * feat: 使用 useLazyTranslation 替换 useTranslation 以支持懒加载翻译 * feat: 添加 PageSkeleton 组件及其测试用例以支持页面加载骨架屏 * feat: 使用骨架屏替换加载状态指示器,优化用户体验 * feat: 优化 CopyButton 组件的复制功能,添加定时器管理复制状态 * feat: 调整 chunk 大小警告阈值以优化构建性能
119 lines
3.3 KiB
TypeScript
119 lines
3.3 KiB
TypeScript
import i18n from 'i18next';
|
|
import { initReactI18next } from 'react-i18next';
|
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
|
import { storageUtil } from '@/utils/chromeStorage';
|
|
import dayjs from 'dayjs';
|
|
|
|
// 同步加载全局命名空间(所有页面都需要)
|
|
import commonZh from './locales/zh/common.json';
|
|
import featuresZh from './locales/zh/features.json';
|
|
import commonEn from './locales/en/common.json';
|
|
import featuresEn from './locales/en/features.json';
|
|
|
|
const resources = {
|
|
zh: {
|
|
common: commonZh,
|
|
features: featuresZh,
|
|
},
|
|
en: {
|
|
common: commonEn,
|
|
features: featuresEn,
|
|
},
|
|
};
|
|
|
|
export const SUPPORTED_LANGUAGES = ['zh', 'en'] as const;
|
|
export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
|
|
|
|
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';
|
|
};
|
|
|
|
/**
|
|
* 校验语言是否受支持
|
|
*/
|
|
const isValidLanguage = (lng: unknown): lng is SupportedLanguage => {
|
|
return typeof lng === 'string' && (SUPPORTED_LANGUAGES as readonly string[]).includes(lng);
|
|
};
|
|
|
|
/**
|
|
* 同步从 localStorage 获取语言快照(用于消除异步加载产生的首屏闪烁)
|
|
*/
|
|
const getSyncLanguageSnapshot = (): SupportedLanguage | null => {
|
|
try {
|
|
const val = localStorage.getItem(LANGUAGE_SNAPSHOT_KEY);
|
|
if (!val) return null;
|
|
const parsed = JSON.parse(val) as unknown;
|
|
return isValidLanguage(parsed) ? parsed : null;
|
|
} catch (error) {
|
|
console.error('解析语言同步快照失败:', error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// 自定义 Chrome Storage 探测器
|
|
const chromeStorageDetector = {
|
|
name: 'chromeStorage',
|
|
lookup() {
|
|
// 同步初始化已通过 getSyncLanguageSnapshot + init 的 lng 参数处理
|
|
return undefined;
|
|
},
|
|
cacheUserLanguage(lng: string) {
|
|
storageUtil.set(LANGUAGE_STORAGE_KEY, lng);
|
|
},
|
|
};
|
|
|
|
const detector = new LanguageDetector();
|
|
detector.addDetector(chromeStorageDetector);
|
|
|
|
const syncLng = getSyncLanguageSnapshot();
|
|
|
|
i18n
|
|
.use(detector)
|
|
.use(initReactI18next)
|
|
.init({
|
|
resources,
|
|
fallbackLng: 'en',
|
|
lng: syncLng || undefined,
|
|
ns: ['common', 'features'],
|
|
defaultNS: 'common',
|
|
debug: false,
|
|
interpolation: {
|
|
escapeValue: false,
|
|
},
|
|
detection: {
|
|
order: ['chromeStorage', 'navigator'],
|
|
caches: ['chromeStorage'],
|
|
},
|
|
});
|
|
|
|
// 监听语言变化:同步 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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
});
|
|
|
|
export default i18n;
|