mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-15 22:40:42 +08:00
fix(i18n): prevent zh flash and normalize language caching #5632
This commit is contained in:
parent
9ce5d27d6e
commit
99dda5496e
@ -1,7 +1,11 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { changeLanguage, supportedLanguages } from "@/services/i18n";
|
||||
import {
|
||||
changeLanguage,
|
||||
resolveLanguage,
|
||||
supportedLanguages,
|
||||
} from "@/services/i18n";
|
||||
|
||||
import { useVerge } from "./use-verge";
|
||||
|
||||
@ -12,21 +16,23 @@ export const useI18n = () => {
|
||||
|
||||
const switchLanguage = useCallback(
|
||||
async (language: string) => {
|
||||
if (!supportedLanguages.includes(language)) {
|
||||
const targetLanguage = resolveLanguage(language);
|
||||
|
||||
if (!supportedLanguages.includes(targetLanguage)) {
|
||||
console.warn(`Unsupported language: ${language}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (i18n.language === language) {
|
||||
if (i18n.language === targetLanguage) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await changeLanguage(language);
|
||||
await changeLanguage(targetLanguage);
|
||||
|
||||
if (patchVerge) {
|
||||
await patchVerge({ language });
|
||||
await patchVerge({ language: targetLanguage });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to change language:", error);
|
||||
|
||||
70
src/main.tsx
70
src/main.tsx
@ -14,7 +14,14 @@ import { BaseErrorBoundary } from "./components/base";
|
||||
import { router } from "./pages/_routers";
|
||||
import { AppDataProvider } from "./providers/app-data-provider";
|
||||
import { WindowProvider } from "./providers/window";
|
||||
import { initializeLanguage } from "./services/i18n";
|
||||
import { getVergeConfig } from "./services/cmds";
|
||||
import {
|
||||
FALLBACK_LANGUAGE,
|
||||
cacheLanguage,
|
||||
getCachedLanguage,
|
||||
initializeLanguage,
|
||||
resolveLanguage,
|
||||
} from "./services/i18n";
|
||||
import {
|
||||
LoadingCacheProvider,
|
||||
ThemeModeProvider,
|
||||
@ -71,20 +78,67 @@ const initializeApp = () => {
|
||||
);
|
||||
};
|
||||
|
||||
initializeLanguage("zh").catch(console.error);
|
||||
initializeApp();
|
||||
const determineInitialLanguage = async () => {
|
||||
const cachedLanguage = getCachedLanguage();
|
||||
if (cachedLanguage) {
|
||||
return cachedLanguage;
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
try {
|
||||
const vergeConfig = await getVergeConfig();
|
||||
if (vergeConfig?.language) {
|
||||
const resolved = resolveLanguage(vergeConfig.language);
|
||||
cacheLanguage(resolved);
|
||||
return resolved;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"[main.tsx] Failed to read language from Verge config:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
const browserLanguage = resolveLanguage(
|
||||
typeof navigator !== "undefined" ? navigator.language : undefined,
|
||||
);
|
||||
cacheLanguage(browserLanguage);
|
||||
return browserLanguage;
|
||||
};
|
||||
|
||||
const bootstrap = async () => {
|
||||
const initialLanguage = await determineInitialLanguage();
|
||||
await initializeLanguage(initialLanguage);
|
||||
initializeApp();
|
||||
};
|
||||
|
||||
bootstrap().catch((error) => {
|
||||
console.error(
|
||||
"[main.tsx] App bootstrap failed, falling back to default language:",
|
||||
error,
|
||||
);
|
||||
initializeLanguage(FALLBACK_LANGUAGE)
|
||||
.catch((fallbackError) => {
|
||||
console.error(
|
||||
"[main.tsx] Fallback language initialization failed:",
|
||||
fallbackError,
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
initializeApp();
|
||||
});
|
||||
});
|
||||
|
||||
// Error handling
|
||||
window.addEventListener("error", (event) => {
|
||||
console.error("[main.tsx] 全局错误:", event.error);
|
||||
console.error("[main.tsx] Global error:", event.error);
|
||||
});
|
||||
|
||||
window.addEventListener("unhandledrejection", (event) => {
|
||||
console.error("[main.tsx] 未处理的Promise拒绝:", event.reason);
|
||||
console.error("[main.tsx] Unhandled promise rejection:", event.reason);
|
||||
});
|
||||
|
||||
// 页面关闭/刷新事件
|
||||
// Page close/refresh events
|
||||
window.addEventListener("beforeunload", () => {
|
||||
// 同步清理所有 WebSocket 实例, 防止内存泄漏
|
||||
// Clean up all WebSocket instances to prevent memory leaks
|
||||
MihomoWebSocket.cleanupAll();
|
||||
});
|
||||
|
||||
@ -17,7 +17,65 @@ export const supportedLanguages = [
|
||||
"zhtw",
|
||||
];
|
||||
|
||||
const FALLBACK_LANGUAGE = "zh";
|
||||
export const FALLBACK_LANGUAGE = "zh";
|
||||
const LANGUAGE_STORAGE_KEY = "verge-language";
|
||||
|
||||
const normalizeLanguage = (language?: string) =>
|
||||
language?.toLowerCase().replace(/_/g, "-");
|
||||
|
||||
export const resolveLanguage = (language?: string) => {
|
||||
const normalized = normalizeLanguage(language);
|
||||
if (!normalized) {
|
||||
return FALLBACK_LANGUAGE;
|
||||
}
|
||||
|
||||
if (normalized === "zh-tw") return "zhtw";
|
||||
if (normalized === "zh-cn") return "zh";
|
||||
|
||||
if (supportedLanguages.includes(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const baseLanguage = normalized.split("-")[0];
|
||||
if (supportedLanguages.includes(baseLanguage)) {
|
||||
return baseLanguage;
|
||||
}
|
||||
|
||||
return FALLBACK_LANGUAGE;
|
||||
};
|
||||
|
||||
const getLanguageStorage = () => {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
return window.localStorage;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const cacheLanguage = (language: string) => {
|
||||
const storage = getLanguageStorage();
|
||||
if (!storage) return;
|
||||
|
||||
try {
|
||||
storage.setItem(LANGUAGE_STORAGE_KEY, resolveLanguage(language));
|
||||
} catch (error) {
|
||||
console.warn("[i18n] Failed to cache language:", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getCachedLanguage = () => {
|
||||
const storage = getLanguageStorage();
|
||||
if (!storage) return undefined;
|
||||
|
||||
try {
|
||||
const cached = storage.getItem(LANGUAGE_STORAGE_KEY);
|
||||
return cached ? resolveLanguage(cached) : undefined;
|
||||
} catch (error) {
|
||||
console.warn("[i18n] Failed to read cached language:", error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
type LocaleModule = {
|
||||
default: Record<string, unknown>;
|
||||
@ -71,22 +129,27 @@ export const loadLanguage = async (language: string) => {
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources: {},
|
||||
lng: "zh",
|
||||
fallbackLng: "zh",
|
||||
lng: FALLBACK_LANGUAGE,
|
||||
fallbackLng: FALLBACK_LANGUAGE,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
export const changeLanguage = async (language: string) => {
|
||||
if (!i18n.hasResourceBundle(language, "translation")) {
|
||||
const resources = await loadLanguage(language);
|
||||
i18n.addResourceBundle(language, "translation", resources);
|
||||
const targetLanguage = resolveLanguage(language);
|
||||
|
||||
if (!i18n.hasResourceBundle(targetLanguage, "translation")) {
|
||||
const resources = await loadLanguage(targetLanguage);
|
||||
i18n.addResourceBundle(targetLanguage, "translation", resources);
|
||||
}
|
||||
|
||||
await i18n.changeLanguage(language);
|
||||
await i18n.changeLanguage(targetLanguage);
|
||||
cacheLanguage(targetLanguage);
|
||||
};
|
||||
|
||||
export const initializeLanguage = async (initialLanguage: string = "zh") => {
|
||||
export const initializeLanguage = async (
|
||||
initialLanguage: string = FALLBACK_LANGUAGE,
|
||||
) => {
|
||||
await changeLanguage(initialLanguage);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user