// Author: fruit.com.cn import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { getLocale } from '../../i18n'; import { detectLocaleFromIp } from '../../shared/lib/geo'; import { applySeo } from '../../shared/lib/seo'; const LocaleContext = createContext(null); const UiContext = createContext(null); const LANG_STORAGE_KEY = 'ymb.lang'; const SUPPORTED_LANGS = ['id', 'en', 'zh']; function readUrlLang() { const value = new URLSearchParams(window.location.search).get('lang'); return SUPPORTED_LANGS.includes(value) ? value : ''; } function readStoredLang() { try { const value = window.localStorage.getItem(LANG_STORAGE_KEY); return SUPPORTED_LANGS.includes(value) ? value : ''; } catch { return ''; } } function readNavigatorLang() { const code = (navigator.language || '').toLowerCase(); if (code.startsWith('id')) return 'id'; if (code.startsWith('zh')) return 'zh'; return 'en'; } function detectInitialLang() { return readUrlLang() || readStoredLang() || readNavigatorLang(); } export function AppProviders({ children }) { const [lang, setLangState] = useState(detectInitialLang); const [query, setQuery] = useState(''); const [sidebarOpen, setSidebarOpen] = useState(() => window.innerWidth > 760); const userSelectedRef = useRef(false); const locale = getLocale(lang); // 首次进入且没有明确偏好时,按 IP 归属地自动匹配语言 useEffect(() => { if (readUrlLang() || readStoredLang()) return undefined; let active = true; detectLocaleFromIp().then((detected) => { if (active && detected && !userSelectedRef.current) setLangState(detected); }); return () => { active = false; }; }, []); const setLang = useCallback((next) => { userSelectedRef.current = true; setLangState(next); try { window.localStorage.setItem(LANG_STORAGE_KEY, next); } catch { // 隐私模式下写入失败时仍保证本次会话生效 } }, []); useEffect(() => { applySeo(locale, window.location.pathname); }, [lang, locale]); const localeValue = useMemo(() => ({ lang, locale, setLang }), [lang, locale, setLang]); const uiValue = useMemo(() => ({ query, setQuery, sidebarOpen, setSidebarOpen }), [query, sidebarOpen]); return ( {children} ); } export function useLocale() { const value = useContext(LocaleContext); if (!value) throw new Error('useLocale must be used within AppProviders'); return value; } export function useUi() { const value = useContext(UiContext); if (!value) throw new Error('useUi must be used within AppProviders'); return value; }