|
@@ -5,47 +5,64 @@ import { zh } from './locales/zh';
|
|
|
import { en } from './locales/en';
|
|
import { en } from './locales/en';
|
|
|
|
|
|
|
|
type Lang = 'zh' | 'en';
|
|
type Lang = 'zh' | 'en';
|
|
|
-// 获取字典的类型定义
|
|
|
|
|
-type Dictionary = typeof zh;
|
|
|
|
|
|
|
|
|
|
interface LanguageContextProps {
|
|
interface LanguageContextProps {
|
|
|
lang: Lang;
|
|
lang: Lang;
|
|
|
setLang: (lang: Lang) => void;
|
|
setLang: (lang: Lang) => void;
|
|
|
- t: (path: string) => string; // 核心翻译函数
|
|
|
|
|
|
|
+ t: (path: string) => string;
|
|
|
|
|
+ isLoaded: boolean; // 新增:标识语言是否加载完成
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const LanguageContext = createContext<LanguageContextProps | undefined>(undefined);
|
|
const LanguageContext = createContext<LanguageContextProps | undefined>(undefined);
|
|
|
|
|
|
|
|
export function LanguageProvider({ children }: { children: ReactNode }) {
|
|
export function LanguageProvider({ children }: { children: ReactNode }) {
|
|
|
- // 默认语言
|
|
|
|
|
|
|
+ // 默认初始状态设为 'zh',但在 useEffect 执行前我们不确定真实语言
|
|
|
const [lang, setLangState] = useState<Lang>('zh');
|
|
const [lang, setLangState] = useState<Lang>('zh');
|
|
|
|
|
+ const [isLoaded, setIsLoaded] = useState(false);
|
|
|
|
|
|
|
|
- // 初始化时读取本地存储
|
|
|
|
|
|
|
+ // 初始化逻辑:在客户端挂载后执行
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
|
|
+ // 1. 尝试从本地存储获取
|
|
|
const savedLang = localStorage.getItem('app_lang') as Lang;
|
|
const savedLang = localStorage.getItem('app_lang') as Lang;
|
|
|
- if (savedLang) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (savedLang && (savedLang === 'zh' || savedLang === 'en')) {
|
|
|
setLangState(savedLang);
|
|
setLangState(savedLang);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 2. 如果本地没有,尝试检测浏览器语言
|
|
|
|
|
+ // navigator.language 返回如 'zh-CN', 'en-US'
|
|
|
|
|
+ const browserLang = navigator.language.toLowerCase();
|
|
|
|
|
+ if (browserLang.startsWith('en')) {
|
|
|
|
|
+ setLangState('en');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setLangState('zh');
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 标记加载完成
|
|
|
|
|
+ setIsLoaded(true);
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
- // 切换语言时保存到本地
|
|
|
|
|
|
|
+ // 切换语言并保存
|
|
|
const setLang = (newLang: Lang) => {
|
|
const setLang = (newLang: Lang) => {
|
|
|
setLangState(newLang);
|
|
setLangState(newLang);
|
|
|
localStorage.setItem('app_lang', newLang);
|
|
localStorage.setItem('app_lang', newLang);
|
|
|
|
|
+ // 可选:切换语言时更新 HTML 标签的 lang 属性,利于 SEO 和 浏览器翻译插件识别
|
|
|
|
|
+ document.documentElement.lang = newLang === 'zh' ? 'zh-CN' : 'en';
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// 获取当前字典
|
|
// 获取当前字典
|
|
|
const dictionary = lang === 'zh' ? zh : en;
|
|
const dictionary = lang === 'zh' ? zh : en;
|
|
|
|
|
|
|
|
- // 翻译函数:支持嵌套路径,例如 t('common.save')
|
|
|
|
|
|
|
+ // 翻译函数
|
|
|
const t = (path: string) => {
|
|
const t = (path: string) => {
|
|
|
const keys = path.split('.');
|
|
const keys = path.split('.');
|
|
|
let current: any = dictionary;
|
|
let current: any = dictionary;
|
|
|
|
|
|
|
|
for (const key of keys) {
|
|
for (const key of keys) {
|
|
|
if (current[key] === undefined) {
|
|
if (current[key] === undefined) {
|
|
|
- console.warn(`Translation key not found: ${path}`);
|
|
|
|
|
- return path; // 如果找不到,返回 key 本身
|
|
|
|
|
|
|
+ // 开发模式下可以打印警告
|
|
|
|
|
+ // console.warn(`Translation key not found: ${path}`);
|
|
|
|
|
+ return path;
|
|
|
}
|
|
}
|
|
|
current = current[key];
|
|
current = current[key];
|
|
|
}
|
|
}
|
|
@@ -54,13 +71,18 @@ export function LanguageProvider({ children }: { children: ReactNode }) {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
- <LanguageContext.Provider value={{ lang, setLang, t }}>
|
|
|
|
|
|
|
+ <LanguageContext.Provider value={{ lang, setLang, t, isLoaded }}>
|
|
|
|
|
+ {/*
|
|
|
|
|
+ 为了避免 hydration mismatch(服务端渲染的内容和客户端初始内容不一致导致报错),
|
|
|
|
|
+ 我们可以选择在语言加载完成前显示 loading,或者直接渲染 children。
|
|
|
|
|
+ 这里直接渲染 children,用户可能会看到一瞬间的闪烁(从默认语言变到缓存语言),
|
|
|
|
|
+ 这是纯客户端方案的常见妥协。
|
|
|
|
|
+ */}
|
|
|
{children}
|
|
{children}
|
|
|
</LanguageContext.Provider>
|
|
</LanguageContext.Provider>
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 自定义 Hook,方便组件调用
|
|
|
|
|
export function useLanguage() {
|
|
export function useLanguage() {
|
|
|
const context = useContext(LanguageContext);
|
|
const context = useContext(LanguageContext);
|
|
|
if (!context) {
|
|
if (!context) {
|