- Add dictionary-based i18n system (en/zh) with React Context provider - Extract all hardcoded text from landing components into translation dictionaries - Auto-detect browser language, persist preference in localStorage - Add language switcher (EN/中文) to footer - Add Noto Serif SC font for Chinese serif headings - Rewrite about page Chinese copy with user-provided translation
51 lines
1.4 KiB
TypeScript
51 lines
1.4 KiB
TypeScript
"use client";
|
|
|
|
import { createContext, useContext, useState, useCallback } from "react";
|
|
import { en } from "./en";
|
|
import { zh } from "./zh";
|
|
import type { LandingDict, Locale } from "./types";
|
|
|
|
const dictionaries: Record<Locale, LandingDict> = { en, zh };
|
|
|
|
const STORAGE_KEY = "multica-locale";
|
|
|
|
function getInitialLocale(): Locale {
|
|
if (typeof window === "undefined") return "en";
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
if (stored === "en" || stored === "zh") return stored;
|
|
// Detect from browser language
|
|
const lang = navigator.language;
|
|
if (lang.startsWith("zh")) return "zh";
|
|
return "en";
|
|
}
|
|
|
|
type LocaleContextValue = {
|
|
locale: Locale;
|
|
t: LandingDict;
|
|
setLocale: (locale: Locale) => void;
|
|
};
|
|
|
|
const LocaleContext = createContext<LocaleContextValue | null>(null);
|
|
|
|
export function LocaleProvider({ children }: { children: React.ReactNode }) {
|
|
const [locale, setLocaleState] = useState<Locale>(getInitialLocale);
|
|
|
|
const setLocale = useCallback((l: Locale) => {
|
|
setLocaleState(l);
|
|
localStorage.setItem(STORAGE_KEY, l);
|
|
}, []);
|
|
|
|
return (
|
|
<LocaleContext.Provider
|
|
value={{ locale, t: dictionaries[locale], setLocale }}
|
|
>
|
|
{children}
|
|
</LocaleContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useLocale() {
|
|
const ctx = useContext(LocaleContext);
|
|
if (!ctx) throw new Error("useLocale must be used within LocaleProvider");
|
|
return ctx;
|
|
}
|