jerry 4 месяцев назад
Родитель
Сommit
99f50718d1
1 измененных файлов с 34 добавлено и 12 удалено
  1. 34 12
      src/lib/i18n/LanguageContext.tsx

+ 34 - 12
src/lib/i18n/LanguageContext.tsx

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