2026-05-16 00:18:22 +08:00
|
|
|
import React, {
|
|
|
|
|
createContext,
|
|
|
|
|
useCallback,
|
|
|
|
|
useContext,
|
|
|
|
|
useMemo,
|
|
|
|
|
useState,
|
|
|
|
|
} from "react";
|
2026-06-01 15:09:58 +08:00
|
|
|
import { languageForHomePathname } from "./languageRoutes";
|
2026-06-01 15:49:15 +08:00
|
|
|
import type { Dict } from "./locales/types";
|
|
|
|
|
import { zhDict } from "./locales/zh-CN";
|
|
|
|
|
import { enDict } from "./locales/en";
|
|
|
|
|
import { koDict } from "./locales/ko";
|
2026-05-16 00:18:22 +08:00
|
|
|
|
2026-05-26 10:03:12 +08:00
|
|
|
export type Lang = "zh-CN" | "en" | "ja" | "ko" | "vi" | "id" | "ms";
|
2026-05-16 00:18:22 +08:00
|
|
|
|
2026-05-26 07:36:53 +08:00
|
|
|
const languageNames: Record<Lang, Dict> = {
|
2026-05-26 10:03:12 +08:00
|
|
|
"zh-CN": {
|
|
|
|
|
lang_zh_CN: "中文",
|
2026-05-16 00:18:22 +08:00
|
|
|
lang_en: "English",
|
2026-05-26 07:36:53 +08:00
|
|
|
lang_ja: "日本語",
|
|
|
|
|
lang_ko: "한국어",
|
|
|
|
|
lang_vi: "Tiếng Việt",
|
|
|
|
|
lang_id: "Bahasa Indonesia",
|
|
|
|
|
lang_ms: "Bahasa Melayu",
|
2026-05-16 00:18:22 +08:00
|
|
|
},
|
|
|
|
|
en: {
|
2026-05-26 10:03:12 +08:00
|
|
|
lang_zh_CN: "Chinese",
|
2026-05-16 00:18:22 +08:00
|
|
|
lang_en: "English",
|
2026-05-26 07:36:53 +08:00
|
|
|
lang_ja: "Japanese",
|
|
|
|
|
lang_ko: "Korean",
|
|
|
|
|
lang_vi: "Vietnamese",
|
|
|
|
|
lang_id: "Indonesian",
|
|
|
|
|
lang_ms: "Malay",
|
|
|
|
|
},
|
|
|
|
|
ja: {
|
2026-05-28 10:16:38 +08:00
|
|
|
brand: "ARK ライブラリー",
|
2026-05-29 23:49:59 +08:00
|
|
|
showMore: "すべて表示",
|
|
|
|
|
showLess: "閉じる",
|
2026-05-26 10:03:12 +08:00
|
|
|
lang_zh_CN: "中国語",
|
2026-05-26 07:36:53 +08:00
|
|
|
lang_en: "英語",
|
|
|
|
|
lang_ja: "日本語",
|
|
|
|
|
lang_ko: "韓国語",
|
|
|
|
|
lang_vi: "ベトナム語",
|
|
|
|
|
lang_id: "インドネシア語",
|
|
|
|
|
lang_ms: "マレー語",
|
2026-05-16 00:18:22 +08:00
|
|
|
},
|
2026-05-26 07:36:53 +08:00
|
|
|
ko: {
|
2026-05-28 10:16:38 +08:00
|
|
|
brand: "ARK 라이브러리",
|
2026-05-29 23:49:59 +08:00
|
|
|
showMore: "모두 보기",
|
|
|
|
|
showLess: "접기",
|
2026-05-26 10:03:12 +08:00
|
|
|
lang_zh_CN: "중국어",
|
2026-05-26 07:36:53 +08:00
|
|
|
lang_en: "영어",
|
|
|
|
|
lang_ja: "일본어",
|
|
|
|
|
lang_ko: "한국어",
|
|
|
|
|
lang_vi: "베트남어",
|
|
|
|
|
lang_id: "인도네시아어",
|
|
|
|
|
lang_ms: "말레이어",
|
|
|
|
|
},
|
|
|
|
|
vi: {
|
2026-05-28 10:16:38 +08:00
|
|
|
brand: "Thư viện ARK",
|
2026-05-29 23:49:59 +08:00
|
|
|
showMore: "Xem tất cả",
|
|
|
|
|
showLess: "Thu gọn",
|
2026-05-26 10:03:12 +08:00
|
|
|
lang_zh_CN: "Tiếng Trung",
|
2026-05-26 07:36:53 +08:00
|
|
|
lang_en: "Tiếng Anh",
|
|
|
|
|
lang_ja: "Tiếng Nhật",
|
|
|
|
|
lang_ko: "Tiếng Hàn",
|
|
|
|
|
lang_vi: "Tiếng Việt",
|
|
|
|
|
lang_id: "Tiếng Indonesia",
|
|
|
|
|
lang_ms: "Tiếng Mã Lai",
|
|
|
|
|
},
|
|
|
|
|
id: {
|
2026-05-28 10:16:38 +08:00
|
|
|
brand: "Perpustakaan ARK",
|
2026-05-29 23:49:59 +08:00
|
|
|
showMore: "Lihat semua",
|
|
|
|
|
showLess: "Tutup",
|
2026-05-26 10:03:12 +08:00
|
|
|
lang_zh_CN: "Bahasa Tionghoa",
|
2026-05-26 07:36:53 +08:00
|
|
|
lang_en: "Bahasa Inggris",
|
|
|
|
|
lang_ja: "Bahasa Jepang",
|
|
|
|
|
lang_ko: "Bahasa Korea",
|
|
|
|
|
lang_vi: "Bahasa Vietnam",
|
|
|
|
|
lang_id: "Bahasa Indonesia",
|
|
|
|
|
lang_ms: "Bahasa Melayu",
|
|
|
|
|
},
|
|
|
|
|
ms: {
|
2026-05-28 10:16:38 +08:00
|
|
|
brand: "Perpustakaan ARK",
|
2026-05-29 23:49:59 +08:00
|
|
|
showMore: "Lihat semua",
|
|
|
|
|
showLess: "Tutup",
|
2026-05-26 10:03:12 +08:00
|
|
|
lang_zh_CN: "Bahasa Cina",
|
2026-05-26 07:36:53 +08:00
|
|
|
lang_en: "Bahasa Inggeris",
|
|
|
|
|
lang_ja: "Bahasa Jepun",
|
|
|
|
|
lang_ko: "Bahasa Korea",
|
|
|
|
|
lang_vi: "Bahasa Vietnam",
|
|
|
|
|
lang_id: "Bahasa Indonesia",
|
|
|
|
|
lang_ms: "Bahasa Melayu",
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const dict: Record<Lang, Dict> = {
|
2026-05-26 10:03:12 +08:00
|
|
|
"zh-CN": { ...zhDict, ...languageNames["zh-CN"] },
|
2026-05-26 07:36:53 +08:00
|
|
|
en: { ...enDict, ...languageNames.en },
|
|
|
|
|
ja: { ...enDict, ...languageNames.ja },
|
2026-06-01 15:49:15 +08:00
|
|
|
ko: koDict,
|
2026-05-26 07:36:53 +08:00
|
|
|
vi: { ...enDict, ...languageNames.vi },
|
|
|
|
|
id: { ...enDict, ...languageNames.id },
|
|
|
|
|
ms: { ...enDict, ...languageNames.ms },
|
2026-05-16 00:18:22 +08:00
|
|
|
};
|
|
|
|
|
|
2026-05-26 07:36:53 +08:00
|
|
|
/** Fixed locale lookup (admin UI uses Simplified Chinese). */
|
2026-05-16 00:18:22 +08:00
|
|
|
export function tLang(lang: Lang, key: string): string {
|
2026-05-26 07:36:53 +08:00
|
|
|
return dict[lang][key] || dict.en[key] || key;
|
2026-05-16 00:18:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Ctx = { lang: Lang; setLang: (l: Lang) => void; t: (k: string) => string };
|
|
|
|
|
|
|
|
|
|
const I18nCtx = createContext<Ctx | null>(null);
|
|
|
|
|
|
|
|
|
|
const LANG_KEY = "ark_lang";
|
|
|
|
|
|
|
|
|
|
export function I18nProvider({ children }: { children: React.ReactNode }) {
|
|
|
|
|
const [lang, setLangState] = useState<Lang>(() => {
|
2026-06-01 15:09:58 +08:00
|
|
|
const routeLang = languageForHomePathname(window.location.pathname);
|
|
|
|
|
if (routeLang) return routeLang;
|
|
|
|
|
if (window.location.pathname === "/") return "en";
|
|
|
|
|
|
2026-05-26 07:36:53 +08:00
|
|
|
const s = localStorage.getItem(LANG_KEY);
|
2026-05-26 10:03:12 +08:00
|
|
|
if (s === "zh" || s === "zh-TW") return "zh-CN";
|
2026-05-26 07:36:53 +08:00
|
|
|
if (
|
2026-05-26 10:03:12 +08:00
|
|
|
s === "zh-CN" ||
|
2026-05-26 07:36:53 +08:00
|
|
|
s === "en" ||
|
|
|
|
|
s === "ja" ||
|
|
|
|
|
s === "ko" ||
|
|
|
|
|
s === "vi" ||
|
|
|
|
|
s === "id" ||
|
|
|
|
|
s === "ms"
|
|
|
|
|
)
|
|
|
|
|
return s;
|
|
|
|
|
return "en";
|
2026-05-16 00:18:22 +08:00
|
|
|
});
|
2026-06-01 15:09:58 +08:00
|
|
|
const setLang = useCallback((l: Lang) => {
|
2026-05-16 00:18:22 +08:00
|
|
|
localStorage.setItem(LANG_KEY, l);
|
|
|
|
|
setLangState(l);
|
2026-06-01 15:09:58 +08:00
|
|
|
}, []);
|
2026-05-16 00:18:22 +08:00
|
|
|
const t = useCallback(
|
2026-05-26 07:36:53 +08:00
|
|
|
(k: string) => dict[lang][k] || dict.en[k] || k,
|
2026-05-16 00:18:22 +08:00
|
|
|
[lang],
|
|
|
|
|
);
|
|
|
|
|
const v = useMemo(() => ({ lang, setLang, t }), [lang, t]);
|
|
|
|
|
return <I18nCtx.Provider value={v}>{children}</I18nCtx.Provider>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useI18n() {
|
|
|
|
|
const v = useContext(I18nCtx);
|
|
|
|
|
if (!v) throw new Error("I18nProvider missing");
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function langQuery(lang: Lang) {
|
2026-05-26 10:03:12 +08:00
|
|
|
return lang;
|
2026-05-16 00:18:22 +08:00
|
|
|
}
|