feat: integrate Sensei Spook mascot in 4 stances, speech bubbles, and style updates
This commit is contained in:
commit
e04672db2a
104 files changed
+8262
No files matched your search
@@ -0,0 +1,85 @@
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user