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 & { 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) => 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( null, ); const initialLanguageRef = useRef("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) => { 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, }; }