diff --git a/src/i18n/__tests__/index.test.tsx b/src/i18n/__tests__/index.test.tsx
new file mode 100644
index 00000000..270937d3
--- /dev/null
+++ b/src/i18n/__tests__/index.test.tsx
@@ -0,0 +1,50 @@
+import { render, screen, waitFor } from "@testing-library/react";
+import { beforeEach, describe, expect, it } from "vitest";
+import { I18nProvider, useI18n } from "@/i18n";
+
+function LanguageProbe() {
+ const { language, t } = useI18n();
+ return (
+
+ {language}
+ {t("language.label")}
+
+ );
+}
+
+describe("I18nProvider", () => {
+ beforeEach(() => {
+ window.localStorage.clear();
+ document.documentElement.lang = "";
+ });
+
+ it("defaults first-time visitors to Simplified Chinese", async () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId("language")).toHaveTextContent("zh-CN");
+ expect(screen.getByTestId("label")).toHaveTextContent("语言");
+ await waitFor(() => {
+ expect(window.localStorage.getItem("popiart-node-language")).toBe("zh-CN");
+ expect(document.documentElement.lang).toBe("zh-CN");
+ });
+ });
+
+ it("keeps an existing saved language preference", async () => {
+ window.localStorage.setItem("popiart-node-language", "ja");
+
+ render(
+
+
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByTestId("language")).toHaveTextContent("ja");
+ expect(screen.getByTestId("label")).toHaveTextContent("言語");
+ });
+ });
+});
diff --git a/src/i18n/index.tsx b/src/i18n/index.tsx
index a4b09180..7328e52e 100644
--- a/src/i18n/index.tsx
+++ b/src/i18n/index.tsx
@@ -8,6 +8,7 @@ type Dictionary = typeof en;
export type TranslationKey = keyof Dictionary;
const STORAGE_KEY = "popiart-node-language";
+const DEFAULT_LANGUAGE: Language = "zh-CN";
export const LANGUAGE_OPTIONS: { value: Language; label: string }[] = [
{ value: "en", label: "English" },
@@ -2442,15 +2443,6 @@ const dictionaries: Record = {
ja,
};
-function detectLanguage(): Language {
- if (typeof navigator === "undefined") return "en";
- const language = navigator.language.toLowerCase();
- if (language.startsWith("ja")) return "ja";
- if (language.includes("tw") || language.includes("hk") || language.includes("hant")) return "zh-TW";
- if (language.startsWith("zh")) return "zh-CN";
- return "en";
-}
-
function interpolate(text: string, values?: Record) {
if (!values) return text;
return text.replace(/\{(\w+)\}/g, (_, key) => String(values[key] ?? `{${key}}`));
@@ -2465,11 +2457,11 @@ interface I18nContextValue {
const I18nContext = createContext(null);
export function I18nProvider({ children }: { children: React.ReactNode }) {
- const [language, setLanguageState] = useState("en");
+ const [language, setLanguageState] = useState(DEFAULT_LANGUAGE);
useEffect(() => {
const stored = window.localStorage.getItem(STORAGE_KEY) as Language | null;
- setLanguageState(stored && stored in dictionaries ? stored : detectLanguage());
+ setLanguageState(stored && stored in dictionaries ? stored : DEFAULT_LANGUAGE);
}, []);
useEffect(() => {
diff --git a/src/utils/__tests__/loginRequiredModal.test.ts b/src/utils/__tests__/loginRequiredModal.test.ts
new file mode 100644
index 00000000..6ca13232
--- /dev/null
+++ b/src/utils/__tests__/loginRequiredModal.test.ts
@@ -0,0 +1,26 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+describe("openLoginRequiredModal", () => {
+ beforeEach(() => {
+ vi.resetModules();
+ });
+
+ it("uses the Ant Design 6 mask.closable option instead of deprecated maskClosable", async () => {
+ const confirm = vi.fn(() => ({
+ destroy: vi.fn(),
+ update: vi.fn(),
+ }));
+
+ vi.doMock("antd", () => ({
+ Modal: { confirm },
+ }));
+
+ const { openLoginRequiredModal } = await import("@/utils/loginRequiredModal");
+ openLoginRequiredModal();
+
+ expect(confirm).toHaveBeenCalledWith(expect.objectContaining({
+ mask: { closable: false },
+ }));
+ expect(confirm.mock.calls[0][0]).not.toHaveProperty("maskClosable");
+ });
+});
diff --git a/src/utils/loginRequiredModal.ts b/src/utils/loginRequiredModal.ts
index 40ceb3e6..634044f1 100644
--- a/src/utils/loginRequiredModal.ts
+++ b/src/utils/loginRequiredModal.ts
@@ -29,7 +29,7 @@ export function openLoginRequiredModal() {
width: 360,
icon: null,
keyboard: false,
- maskClosable: false,
+ mask: { closable: false },
onOk: () => {
window.location.assign(LOGIN_REDIRECT_URL);
},