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" | "ja"; export type SettingsFormState = Omit & { language: Language; }; const normalizeLanguage = (lang?: string | null): Language => { if (!lang) return "zh"; const normalized = lang.toLowerCase(); return normalized === "en" || normalized === "ja" ? normalized : "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" || stored === "ja") { return stored as Language; } } 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, useAppWindowControls: data.useAppWindowControls ?? false, enableClaudePluginIntegration: data.enableClaudePluginIntegration ?? false, silentStartup: data.silentStartup ?? false, skipClaudeOnboarding: data.skipClaudeOnboarding ?? false, claudeConfigDir: sanitizeDir(data.claudeConfigDir), codexConfigDir: sanitizeDir(data.codexConfigDir), geminiConfigDir: sanitizeDir(data.geminiConfigDir), opencodeConfigDir: sanitizeDir(data.opencodeConfigDir), 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, useAppWindowControls: false, enableClaudePluginIntegration: false, skipClaudeOnboarding: 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, useAppWindowControls: serverData.useAppWindowControls ?? false, enableClaudePluginIntegration: serverData.enableClaudePluginIntegration ?? false, silentStartup: serverData.silentStartup ?? false, skipClaudeOnboarding: serverData.skipClaudeOnboarding ?? false, claudeConfigDir: sanitizeDir(serverData.claudeConfigDir), codexConfigDir: sanitizeDir(serverData.codexConfigDir), geminiConfigDir: sanitizeDir(serverData.geminiConfigDir), opencodeConfigDir: sanitizeDir(serverData.opencodeConfigDir), language: normalizedLanguage, }; setSettingsState(normalized); syncLanguage(initialLanguageRef.current); }, [readPersistedLanguage, syncLanguage], ); return { settings: settingsState, isLoading, initialLanguage: initialLanguageRef.current, updateSettings, resetSettings, readPersistedLanguage, syncLanguage, }; }