provider.tsx 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. "use client";
  2. import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
  3. import { DEFAULT_LANG, DICT, LANG_STORAGE_KEY, type I18nKey, type Lang } from "./dict";
  4. function isLang(x: string | null | undefined): x is Lang {
  5. return x === "zh-CN" || x === "en";
  6. }
  7. function getLangFromLocation(): Lang | null {
  8. if (typeof window === "undefined") return null;
  9. const url = new URL(window.location.href);
  10. const q = url.searchParams.get("lang");
  11. if (isLang(q)) return q;
  12. const stored = window.localStorage.getItem(LANG_STORAGE_KEY);
  13. if (isLang(stored)) return stored;
  14. return null;
  15. }
  16. type I18nContextValue = {
  17. lang: Lang;
  18. setLang: (lang: Lang) => void;
  19. t: (key: I18nKey) => string;
  20. };
  21. const I18nContext = createContext<I18nContextValue | null>(null);
  22. export function I18nProvider({ children }: { children: React.ReactNode }) {
  23. const [lang, setLangState] = useState<Lang>(DEFAULT_LANG);
  24. useEffect(() => {
  25. const initial = getLangFromLocation();
  26. if (initial) setLangState(initial);
  27. }, []);
  28. const setLang = useCallback((next: Lang) => {
  29. setLangState(next);
  30. if (typeof window === "undefined") return;
  31. window.localStorage.setItem(LANG_STORAGE_KEY, next);
  32. // 同步 URL(可分享)
  33. const url = new URL(window.location.href);
  34. url.searchParams.set("lang", next);
  35. window.history.replaceState(null, "", url.toString());
  36. }, []);
  37. const t = useCallback(
  38. (key: I18nKey) => {
  39. const v = DICT[lang]?.[key];
  40. if (typeof v === "string" && v) return v;
  41. return DICT[DEFAULT_LANG][key] ?? key;
  42. },
  43. [lang]
  44. );
  45. const value = useMemo(() => ({ lang, setLang, t }), [lang, setLang, t]);
  46. return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
  47. }
  48. export function useI18n(): I18nContextValue {
  49. const ctx = useContext(I18nContext);
  50. if (!ctx) {
  51. return {
  52. lang: DEFAULT_LANG,
  53. setLang: () => {},
  54. t: (key) => DICT[DEFAULT_LANG][key] ?? key,
  55. };
  56. }
  57. return ctx;
  58. }