85 lines
2.5 KiB
TypeScript
85 lines
2.5 KiB
TypeScript
import { createContext, useContext, useState, useEffect, ReactNode } from "react";
|
|||
|
|
import { LocalizedString } from "@/types/booth";
|
||
|
|
|
||
|
|
export type Language = "en" | "zh";
|
||
|
|
|
||
|
|
interface I18nContextType {
|
||
|
|
language: Language;
|
||
|
|
setLanguage: (lang: Language) => void;
|
||
|
|
toggleLanguage: () => void;
|
||
|
|
t: (field: LocalizedString | string | undefined, fallback?: string) => string;
|
||
|
|
isWeChat: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
const I18nContext = createContext<I18nContextType | null>(null);
|
||
|
|
|
||
|
|
export function I18nProvider({ children }: { children: ReactNode }) {
|
||
|
|
const [language, setLanguageState] = useState<Language>("zh");
|
||
|
|
const [isWeChat, setIsWeChat] = useState<boolean>(false);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (typeof window !== "undefined") {
|
||
|
|
// 1. Detect WeChat browser environment
|
||
|
|
const ua = navigator.userAgent.toLowerCase();
|
||
|
|
const inWeChat = ua.includes("micromessenger");
|
||
|
|
setIsWeChat(inWeChat);
|
||
|
|
|
||
|
|
// 2. Check stored preference first
|
||
|
|
const stored = localStorage.getItem("booth_lang") as Language | null;
|
||
|
|
if (stored === "en" || stored === "zh") {
|
||
|
|
setLanguageState(stored);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 3. Fallback to system / browser language
|
||
|
|
const browserLang = (navigator.language || "").toLowerCase();
|
||
|
|
if (browserLang.startsWith("zh")) {
|
||
|
|
setLanguageState("zh");
|
||
|
|
} else {
|
||
|
|
setLanguageState("en");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const setLanguage = (lang: Language) => {
|
||
|
|
setLanguageState(lang);
|
||
|
|
if (typeof window !== "undefined") {
|
||
|
|
localStorage.setItem("booth_lang", lang);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const toggleLanguage = () => {
|
||
|
|
const nextLang = language === "zh" ? "en" : "zh";
|
||
|
|
setLanguage(nextLang);
|
||
|
|
};
|
||
|
|
|
||
|
|
const t = (field: LocalizedString | string | undefined, fallback: string = ""): string => {
|
||
|
|
if (!field) return fallback;
|
||
|
|
if (typeof field === "string") return field;
|
||
|
|
return field[language] || field.en || fallback;
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<I18nContext.Provider value={{ language, setLanguage, toggleLanguage, t, isWeChat }}>
|
||
|
|
{children}
|
||
|
|
</I18nContext.Provider>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useI18n() {
|
||
|
|
const context = useContext(I18nContext);
|
||
|
|
if (!context) {
|
||
|
|
return {
|
||
|
|
language: "zh" as Language,
|
||
|
|
setLanguage: () => {},
|
||
|
|
toggleLanguage: () => {},
|
||
|
|
t: (field: LocalizedString | string | undefined, fallback: string = "") => {
|
||
|
|
if (!field) return fallback;
|
||
|
|
if (typeof field === "string") return field;
|
||
|
|
return field.zh || field.en || fallback;
|
||
|
|
},
|
||
|
|
isWeChat: false,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return context;
|
||
|
|
}
|