mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 00:35:32 +08:00
e02175e68d
Fixed an issue where clicking the language switcher would cause a brief flash and fail to persist the language change. The root cause was a dependency cycle in useSettingsForm where readPersistedLanguage depended on i18n.language, causing the initialization effect to re-run and reset state whenever the language changed. Changed the dependency from [i18n.language] to [i18n] since the i18n object itself is stable and doesn't change when the language changes, while the function can still access the current language value via closure.
159 lines
4.4 KiB
TypeScript
159 lines
4.4 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { useSettingsQuery } from "@/lib/query";
|
|
import type { Settings } from "@/types";
|
|
|
|
type Language = "zh" | "en";
|
|
|
|
export type SettingsFormState = Omit<Settings, "language"> & {
|
|
language: Language;
|
|
};
|
|
|
|
const normalizeLanguage = (lang?: string | null): Language => {
|
|
if (!lang) return "zh";
|
|
return lang === "en" ? "en" : "zh";
|
|
};
|
|
|
|
const sanitizeDir = (value?: string | null): string | undefined => {
|
|
if (!value) return undefined;
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
};
|
|
|
|
export interface UseSettingsFormResult {
|
|
settings: SettingsFormState | null;
|
|
isLoading: boolean;
|
|
initialLanguage: Language;
|
|
updateSettings: (updates: Partial<SettingsFormState>) => void;
|
|
resetSettings: (serverData: Settings | null) => void;
|
|
readPersistedLanguage: () => Language;
|
|
syncLanguage: (lang: Language) => void;
|
|
}
|
|
|
|
/**
|
|
* useSettingsForm - 表单状态管理
|
|
* 负责:
|
|
* - 表单数据状态
|
|
* - 表单字段更新
|
|
* - 语言同步
|
|
* - 表单重置
|
|
*/
|
|
export function useSettingsForm(): UseSettingsFormResult {
|
|
const { i18n } = useTranslation();
|
|
const { data, isLoading } = useSettingsQuery();
|
|
|
|
const [settingsState, setSettingsState] = useState<SettingsFormState | null>(
|
|
null,
|
|
);
|
|
|
|
const initialLanguageRef = useRef<Language>("zh");
|
|
|
|
const readPersistedLanguage = useCallback((): Language => {
|
|
if (typeof window !== "undefined") {
|
|
const stored = window.localStorage.getItem("language");
|
|
if (stored === "en" || stored === "zh") {
|
|
return stored;
|
|
}
|
|
}
|
|
return normalizeLanguage(i18n.language);
|
|
}, [i18n]);
|
|
|
|
const syncLanguage = useCallback(
|
|
(lang: Language) => {
|
|
const current = normalizeLanguage(i18n.language);
|
|
if (current !== lang) {
|
|
void i18n.changeLanguage(lang);
|
|
}
|
|
},
|
|
[i18n],
|
|
);
|
|
|
|
// 初始化设置数据
|
|
useEffect(() => {
|
|
if (!data) return;
|
|
|
|
const normalizedLanguage = normalizeLanguage(
|
|
data.language ?? readPersistedLanguage(),
|
|
);
|
|
|
|
const normalized: SettingsFormState = {
|
|
...data,
|
|
showInTray: data.showInTray ?? true,
|
|
minimizeToTrayOnClose: data.minimizeToTrayOnClose ?? true,
|
|
enableClaudePluginIntegration:
|
|
data.enableClaudePluginIntegration ?? false,
|
|
claudeConfigDir: sanitizeDir(data.claudeConfigDir),
|
|
codexConfigDir: sanitizeDir(data.codexConfigDir),
|
|
language: normalizedLanguage,
|
|
};
|
|
|
|
setSettingsState(normalized);
|
|
initialLanguageRef.current = normalizedLanguage;
|
|
syncLanguage(normalizedLanguage);
|
|
}, [data, readPersistedLanguage, syncLanguage]);
|
|
|
|
const updateSettings = useCallback(
|
|
(updates: Partial<SettingsFormState>) => {
|
|
setSettingsState((prev) => {
|
|
const base =
|
|
prev ??
|
|
({
|
|
showInTray: true,
|
|
minimizeToTrayOnClose: true,
|
|
enableClaudePluginIntegration: false,
|
|
language: readPersistedLanguage(),
|
|
} as SettingsFormState);
|
|
|
|
const next: SettingsFormState = {
|
|
...base,
|
|
...updates,
|
|
};
|
|
|
|
if (updates.language) {
|
|
const normalized = normalizeLanguage(updates.language);
|
|
next.language = normalized;
|
|
syncLanguage(normalized);
|
|
}
|
|
|
|
return next;
|
|
});
|
|
},
|
|
[readPersistedLanguage, syncLanguage],
|
|
);
|
|
|
|
const resetSettings = useCallback(
|
|
(serverData: Settings | null) => {
|
|
if (!serverData) return;
|
|
|
|
const normalizedLanguage = normalizeLanguage(
|
|
serverData.language ?? readPersistedLanguage(),
|
|
);
|
|
|
|
const normalized: SettingsFormState = {
|
|
...serverData,
|
|
showInTray: serverData.showInTray ?? true,
|
|
minimizeToTrayOnClose: serverData.minimizeToTrayOnClose ?? true,
|
|
enableClaudePluginIntegration:
|
|
serverData.enableClaudePluginIntegration ?? false,
|
|
claudeConfigDir: sanitizeDir(serverData.claudeConfigDir),
|
|
codexConfigDir: sanitizeDir(serverData.codexConfigDir),
|
|
language: normalizedLanguage,
|
|
};
|
|
|
|
setSettingsState(normalized);
|
|
syncLanguage(initialLanguageRef.current);
|
|
},
|
|
[readPersistedLanguage, syncLanguage],
|
|
);
|
|
|
|
return {
|
|
settings: settingsState,
|
|
isLoading,
|
|
initialLanguage: initialLanguageRef.current,
|
|
updateSettings,
|
|
resetSettings,
|
|
readPersistedLanguage,
|
|
syncLanguage,
|
|
};
|
|
}
|