import type { TranslationKey } from '@/types/generated/i18n-keys' export interface SettingEntry { id: string name: TranslationKey aliases: string section: TranslationKey path: TranslationKey prerequisite?: string target?: string fallback?: string dialog?: string platforms?: readonly string[] } type GroupDefaults = Pick & Partial< Pick > type EntryOptions = Partial> type GroupEntries = Partial> function group(defaults: GroupDefaults, entries: GroupEntries): SettingEntry[] { return ( Object.entries(entries) as [TranslationKey, string | EntryOptions][] ).map(([name, value]) => { const options = typeof value === 'string' ? { aliases: value } : value return { ...defaults, ...options, id: options.id ?? name, name, aliases: [options.aliases, defaults.aliases].filter(Boolean).join(' '), } }) } const system = { section: 'settings.sections.system.title', path: 'settings.sections.system.title', } satisfies GroupDefaults const tun = { section: 'settings.sections.system.title', path: 'settings.modals.tun.title', dialog: 'tun-viewer', } satisfies GroupDefaults const sysproxy = { section: 'settings.sections.system.title', path: 'settings.modals.sysproxy.title', dialog: 'sysproxy-viewer', fallback: 'settings.modals.sysproxy.fields.usePacMode', prerequisite: 'sysproxy-status', aliases: '代理 PAC bypass 绕过', } satisfies GroupDefaults const clash = { section: 'settings.sections.clash.title', path: 'settings.sections.clash.title', } satisfies GroupDefaults const networkInterface = { section: 'settings.sections.clash.title', path: 'settings.sections.clash.title', dialog: 'network-interface-viewer', aliases: 'network interface viewer', } satisfies GroupDefaults const dns = { section: 'settings.sections.clash.title', path: 'settings.modals.dns.dialog.title', dialog: 'dns-viewer', fallback: 'dns-mode', aliases: 'DNS 域名解析 DNS 域名解析', } satisfies GroupDefaults const dnsReset = { section: 'settings.sections.clash.title', path: 'settings.modals.dns.dialog.title', dialog: 'dns-viewer', aliases: 'dns viewer reset DNS 域名解析', } satisfies GroupDefaults const clashPort = { section: 'settings.sections.clash.title', path: 'settings.modals.clashPort.title', dialog: 'clash-port-viewer', } satisfies GroupDefaults const controller = { section: 'settings.sections.clash.title', path: 'settings.sections.externalController.title', dialog: 'controller-viewer', } satisfies GroupDefaults const externalControllerCors = { section: 'settings.sections.clash.title', path: 'settings.sections.externalCors.title', dialog: 'external-controller-cors', } satisfies GroupDefaults const webUi = { section: 'settings.sections.clash.title', path: 'settings.sections.clash.title', dialog: 'web-ui-viewer', aliases: 'web ui viewer', } satisfies GroupDefaults const webUiActions = { section: 'settings.sections.clash.title', path: 'settings.modals.webUI.title', dialog: 'web-ui-viewer', fallback: 'web-ui-viewer', } satisfies GroupDefaults const clashCore = { section: 'settings.sections.clash.title', path: 'settings.sections.clash.form.fields.clashCore', dialog: 'clash-core-viewer', fallback: 'core-variants', } satisfies GroupDefaults const coreVariants = { section: 'settings.sections.clash.title', path: 'settings.sections.clash.title', dialog: 'clash-core-viewer', aliases: 'core variants', } satisfies GroupDefaults const tunnels = { section: 'settings.sections.clash.title', path: 'settings.sections.clash.form.fields.tunnels.title', dialog: 'tunnels-viewer', fallback: 'tunnels-expand', } satisfies GroupDefaults const basic = { section: 'settings.components.verge.basic.title', path: 'settings.components.verge.basic.title', } satisfies GroupDefaults const theme = { section: 'settings.components.verge.basic.title', path: 'settings.components.verge.theme.title', dialog: 'theme-viewer', } satisfies GroupDefaults const layout = { section: 'settings.components.verge.basic.title', path: 'settings.components.verge.layout.title', dialog: 'layout-viewer', aliases: 'browse layout 布局 托盘', } satisfies GroupDefaults const layoutFields = { section: 'settings.components.verge.basic.title', path: 'settings.components.verge.basic.fields.layoutSetting', dialog: 'layout-viewer', aliases: 'layout 布局 托盘', } satisfies GroupDefaults const misc = { section: 'settings.components.verge.basic.title', path: 'settings.modals.misc.title', dialog: 'misc-viewer', aliases: 'misc 杂项', } satisfies GroupDefaults const hotkey = { section: 'settings.components.verge.basic.title', path: 'settings.modals.hotkey.title', dialog: 'hotkey-viewer', } satisfies GroupDefaults const advanced = { section: 'settings.components.verge.advanced.title', path: 'settings.components.verge.advanced.title', } satisfies GroupDefaults const backup = { section: 'settings.components.verge.advanced.title', path: 'settings.modals.backup.title', dialog: 'backup-viewer', aliases: 'backup 备份', } satisfies GroupDefaults const backupHistory = { section: 'settings.components.verge.advanced.title', path: 'settings.modals.backup.title', dialog: 'backup-history', fallback: 'backup-history', } satisfies GroupDefaults const backupWebdav = { section: 'settings.components.verge.advanced.title', path: 'settings.modals.backup.webdav.title', dialog: 'backup-webdav', fallback: 'webdav-state', } satisfies GroupDefaults const liteMode = { section: 'settings.components.verge.advanced.title', path: 'settings.modals.liteMode.title', dialog: 'lite-mode-viewer', } satisfies GroupDefaults const settingsCatalog: readonly SettingEntry[] = [ ...group(system, { 'settings.sections.system.toggles.tunMode': 'TUN 虚拟网卡 vpn', 'settings.sections.proxyControl.actions.uninstallService': { id: 'service-uninstall', fallback: 'settings.sections.system.toggles.tunMode', aliases: 'service uninstall', }, }), ...group(tun, { 'settings.modals.tun.fields.stack': 'stack TUN 虚拟网卡', 'settings.modals.tun.fields.dnsMode': 'dns mode TUN 虚拟网卡', 'settings.modals.tun.fields.device': 'device TUN 虚拟网卡', 'settings.modals.tun.fields.autoRoute': 'auto route TUN 虚拟网卡', 'settings.modals.tun.fields.autoRedirect': { aliases: 'auto redirect TUN 虚拟网卡', prerequisite: 'settings.modals.tun.fields.autoRoute', platforms: ['linux'], }, 'settings.modals.tun.fields.strictRoute': 'strict route TUN 虚拟网卡', 'settings.modals.tun.fields.autoDetectInterface': 'auto detect interface TUN 虚拟网卡', 'settings.modals.tun.fields.dnsHijack': 'dns hijack TUN 虚拟网卡', 'settings.modals.tun.fields.mtu': 'mtu TUN 虚拟网卡', 'settings.modals.tun.fields.routeExcludeAddress': { aliases: 'route exclude address TUN 虚拟网卡', prerequisite: 'settings.modals.tun.fields.autoRoute', }, 'shared.actions.resetToDefault': { id: 'tun-viewer-reset', aliases: 'tun viewer reset', }, }), ...group(system, { 'settings.sections.system.toggles.systemProxy': 'system proxy 系统代理', }), ...group(sysproxy, { 'settings.modals.sysproxy.fields.proxyHost': 'proxy host', 'settings.modals.sysproxy.fields.usePacMode': 'use pac mode', 'settings.modals.sysproxy.fields.proxyGuard': 'proxy guard', 'settings.modals.sysproxy.fields.guardDuration': 'guard duration', 'settings.modals.sysproxy.fields.alwaysUseDefaultBypass': 'always use default bypass', 'settings.modals.sysproxy.fields.enableBypassCheck': 'enable bypass check', 'settings.modals.sysproxy.fields.proxyBypass': 'proxy bypass', 'settings.modals.sysproxy.fields.pacScriptContent': 'pac script content', 'settings.modals.sysproxy.actions.editPac': 'edit pac', }), ...group(system, { 'settings.sections.system.fields.autoLaunch': 'auto launch ', 'settings.sections.system.fields.silentStart': 'silent start ', }), ...group(clash, { 'settings.sections.clash.form.fields.allowLan': 'allow lan ', }), ...group(networkInterface, { 'settings.modals.networkInterface.title': { id: 'network-interface-viewer', }, }), ...group(clash, { 'settings.sections.clash.form.fields.dnsOverwrite': 'dns overwrite ', }), ...group(dns, { 'settings.modals.dns.fields.enable': 'enable', 'settings.modals.dns.fields.listen': 'listen', 'settings.modals.dns.fields.enhancedMode': 'enhancedMode', 'settings.modals.dns.fields.fakeIpRange': 'fakeIpRange', 'settings.modals.dns.fields.fakeIpRange6': 'fakeIpRange6', 'settings.modals.dns.fields.fakeIpFilterMode': 'fakeIpFilterMode', 'settings.modals.dns.fields.ipv6.label': 'label', 'settings.modals.dns.fields.preferH3.label': 'label', 'settings.modals.dns.fields.respectRules.label': 'label', 'settings.modals.dns.fields.useHosts.label': 'label', 'settings.modals.dns.fields.useSystemHosts.label': 'label', 'settings.modals.dns.fields.directPolicy.label': 'label', 'settings.modals.dns.fields.defaultNameserver.label': 'label', 'settings.modals.dns.fields.nameserver.label': 'label', 'settings.modals.dns.fields.fallback.label': 'label', 'settings.modals.dns.fields.proxy.label': 'label', 'settings.modals.dns.fields.directNameserver.label': 'label', 'settings.modals.dns.fields.fakeIpFilter.label': 'label', 'settings.modals.dns.fields.nameserverPolicy.label': 'label', 'settings.modals.dns.fields.geoipFiltering.label': 'label', 'settings.modals.dns.fields.geoipCode': 'geoipCode', 'settings.modals.dns.fields.fallbackIpCidr.label': 'label', 'settings.modals.dns.fields.fallbackDomain.label': 'label', 'settings.modals.dns.fields.hosts.label': 'label', }), ...group(dnsReset, { 'shared.actions.resetToDefault': { id: 'dns-viewer-reset', }, }), ...group(clash, { 'settings.sections.clash.form.fields.ipv6': 'ipv6 ', 'settings.sections.clash.form.fields.unifiedDelay': 'unified delay ', 'settings.sections.clash.form.fields.logLevel': 'log level ', 'settings.sections.clash.form.fields.portConfig': 'port config ', }), ...group(clashPort, { 'settings.modals.clashPort.fields.socks': 'clash port viewer', 'settings.modals.clashPort.fields.http': 'clash port viewer', 'settings.modals.clashPort.fields.redir': { aliases: 'clash port viewer', platforms: ['macos', 'linux'], }, 'settings.modals.clashPort.fields.tproxy': { aliases: 'clash port viewer', platforms: ['linux'], }, 'settings.modals.clashPort.fields.mixed': 'clash port viewer', 'settings.modals.clashPort.actions.random': { id: 'port-random', aliases: 'port random', }, }), ...group(clash, { 'settings.sections.clash.form.fields.external': 'external ', }), ...group(controller, { 'settings.sections.externalController.fields.enable': 'enable ', 'settings.sections.externalController.fields.address': { aliases: 'address ', prerequisite: 'settings.sections.externalController.fields.enable', }, 'settings.sections.externalController.fields.secret': { aliases: 'secret ', prerequisite: 'settings.sections.externalController.fields.enable', }, }), ...group(externalControllerCors, { 'settings.sections.externalCors.fields.allowPrivateNetwork': 'allow private network ', 'settings.sections.externalCors.fields.allowedOrigins': 'allowed origins ', 'settings.sections.externalCors.actions.add': 'add ', }), ...group(clash, { 'settings.sections.clash.form.fields.webUI': 'web ui ', }), ...group(webUi, { 'settings.modals.webUI.title': { id: 'web-ui-viewer', }, }), ...group(webUiActions, { 'shared.actions.new': { id: 'web-ui-new', aliases: 'web ui new', }, 'shared.actions.edit': { id: 'web-ui-edit', aliases: 'web ui edit', }, 'shared.actions.delete': { id: 'web-ui-delete', aliases: 'web ui delete', }, 'settings.modals.webUI.actions.openUrl': { id: 'web-ui-open', aliases: 'web ui open', }, }), ...group(clash, { 'settings.sections.clash.form.fields.clashCore': 'clash core ', }), ...group(clashCore, { 'shared.actions.upgrade': { id: 'core-upgrade', aliases: 'core upgrade', }, 'shared.actions.restart': { id: 'core-restart', aliases: 'core restart', }, }), ...group(coreVariants, { 'settings.sections.clash.form.fields.clashCore': { id: 'core-variants', }, }), ...group(clash, { 'settings.sections.clash.form.fields.openUwpTool': { aliases: 'open uwp tool ', platforms: ['windows'], }, 'settings.sections.clash.form.fields.updateGeoData': 'update geo data ', 'settings.sections.clash.form.fields.tunnels.title': 'title ', }), ...group(tunnels, { 'settings.sections.clash.form.fields.tunnels.existing': 'existing ', 'settings.sections.clash.form.fields.tunnels.default': 'default ', 'settings.sections.clash.form.fields.tunnels.protocols': 'protocols ', 'settings.sections.clash.form.fields.tunnels.localAddr': 'local addr ', 'settings.sections.clash.form.fields.tunnels.localPort': 'local port ', 'settings.sections.clash.form.fields.tunnels.targetAddr': 'target addr ', 'settings.sections.clash.form.fields.tunnels.targetPort': 'target port ', 'settings.sections.clash.form.fields.tunnels.proxyGroup': 'proxy group ', 'settings.sections.clash.form.fields.tunnels.proxyNode': 'proxy node ', 'settings.sections.clash.form.fields.tunnels.actions.add': 'add ', }), ...group(basic, { 'settings.components.verge.basic.fields.language': 'language ', 'settings.components.verge.basic.fields.themeMode': 'theme mode ', 'settings.components.verge.basic.fields.trayClickEvent': { aliases: 'tray click event ', platforms: ['windows', 'macos'], }, 'settings.components.verge.basic.fields.copyEnvType': 'copy env type ', 'settings.components.verge.basic.fields.startPage': 'start page ', 'settings.components.verge.basic.fields.startupScript': 'startup script ', 'settings.components.verge.basic.fields.themeSetting': 'theme setting ', }), ...group(theme, { 'settings.components.verge.theme.actions.editCss': 'edit css theme 主题 外观', 'settings.components.verge.theme.fields.fontFamily': 'fontFamily theme 主题 外观', 'settings.components.verge.theme.fields.cssInjection': 'cssInjection theme 主题 外观', 'settings.components.verge.theme.fields.primaryColor': 'theme viewer', 'settings.components.verge.theme.fields.secondaryColor': 'theme viewer', 'settings.components.verge.theme.fields.primaryText': 'theme viewer', 'settings.components.verge.theme.fields.secondaryText': 'theme viewer', 'settings.components.verge.theme.fields.infoColor': 'theme viewer', 'settings.components.verge.theme.fields.warningColor': 'theme viewer', 'settings.components.verge.theme.fields.errorColor': 'theme viewer', 'settings.components.verge.theme.fields.successColor': 'theme viewer', }), ...group(basic, { 'settings.components.verge.basic.fields.layoutSetting': 'layout setting ', }), ...group(layout, { 'settings.components.verge.basic.actions.browse': '', }), ...group(layoutFields, { 'settings.components.verge.layout.fields.preferSystemTitlebar': 'preferSystemTitlebar', 'settings.components.verge.layout.fields.trafficGraph': 'trafficGraph', 'settings.components.verge.layout.fields.memoryUsage': 'memoryUsage', 'settings.components.verge.layout.fields.proxyGroupIcon': 'proxyGroupIcon', 'settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur': 'pauseRenderTrafficStatsOnBlur', 'settings.components.verge.layout.fields.toastPosition': 'toastPosition', 'settings.components.verge.layout.fields.hoverNavigator': 'hoverNavigator', 'settings.components.verge.layout.fields.hoverNavigatorDelay': 'hoverNavigatorDelay', 'settings.components.verge.layout.fields.navIcon': 'navIcon', 'settings.components.verge.layout.fields.collapseNavBar': 'collapseNavBar', 'settings.components.verge.layout.fields.trayIcon': { aliases: 'trayIcon', platforms: ['macos'], }, 'settings.components.verge.layout.fields.enableTraySpeed': { aliases: 'enableTraySpeed', platforms: ['macos'], }, 'settings.components.verge.layout.fields.proxyGroupsDisplayMode': 'proxyGroupsDisplayMode', 'settings.components.verge.layout.fields.showOutboundModesInline': 'showOutboundModesInline', 'settings.components.verge.layout.fields.commonTrayIcon': 'commonTrayIcon', 'settings.components.verge.layout.fields.systemProxyTrayIcon': 'systemProxyTrayIcon', 'settings.components.verge.layout.fields.tunTrayIcon': 'tunTrayIcon', }), ...group(basic, { 'settings.components.verge.basic.fields.misc': 'misc ', }), ...group(misc, { 'settings.modals.misc.fields.appLogLevel': 'app log level', 'settings.modals.misc.fields.appLogMaxSize': 'app log max size', 'settings.modals.misc.fields.appLogMaxCount': 'app log max count', 'settings.modals.misc.fields.autoCloseConnections': 'auto close connections', 'settings.modals.misc.fields.autoCheckUpdate': 'auto check update', 'settings.modals.misc.fields.enableBuiltinEnhanced': 'enable builtin enhanced', 'settings.modals.misc.fields.proxyLayoutColumns': 'proxy layout columns', 'settings.modals.misc.fields.autoLogClean': 'auto log clean', 'settings.modals.misc.fields.autoDelayDetection': 'auto delay detection', 'settings.modals.misc.fields.autoDelayDetectionInterval': { aliases: 'auto delay detection interval', prerequisite: 'settings.modals.misc.fields.autoDelayDetection', }, 'settings.modals.misc.fields.defaultLatencyTest': 'default latency test', 'settings.modals.misc.fields.defaultLatencyTimeout': 'default latency timeout', }), ...group(basic, { 'settings.components.verge.basic.fields.hotkeySetting': 'hotkey setting ', }), ...group(hotkey, { 'settings.modals.hotkey.functions.openOrCloseDashboard': 'hotkey viewer', 'settings.modals.hotkey.functions.rule': 'hotkey viewer', 'settings.modals.hotkey.functions.global': 'hotkey viewer', 'settings.modals.hotkey.functions.direct': 'hotkey viewer', 'settings.modals.hotkey.functions.toggleSystemProxy': 'hotkey viewer', 'settings.modals.hotkey.functions.toggleTunMode': 'hotkey viewer', 'settings.modals.hotkey.functions.entryLightweightMode': 'hotkey viewer', 'settings.modals.hotkey.functions.reactivateProfiles': 'hotkey viewer', 'settings.modals.hotkey.toggles.enableGlobal': 'settings.modals.hotkey.toggles.enableGlobal', }), ...group(advanced, { 'settings.components.verge.advanced.fields.backupSetting': 'backup setting ', }), ...group(backup, { 'settings.modals.backup.actions.backup': 'backup', }), ...group(backupHistory, { 'settings.modals.backup.actions.viewHistory': { aliases: 'view history backup 备份', target: 'backup-history', }, }), ...group(backup, { 'settings.modals.backup.actions.importBackup': 'import backup', }), ...group(backupWebdav, { 'settings.modals.backup.fields.webdavUrl': 'webdavUrl WebDAV 云备份', 'settings.modals.backup.fields.username': 'username WebDAV 云备份', }), ...group(backup, { 'settings.modals.backup.auto.scheduleLabel': 'scheduleLabel', 'settings.modals.backup.auto.intervalLabel': { aliases: 'intervalLabel', prerequisite: 'settings.modals.backup.auto.scheduleLabel', }, 'settings.modals.backup.auto.changeLabel': 'changeLabel', }), ...group(backupHistory, { 'settings.modals.backup.history.title': { aliases: '备份 历史 恢复 删除 restore delete history', target: 'backup-history', }, }), ...group(backupWebdav, { 'shared.labels.password': { id: 'webdav-password', aliases: 'webdav password', }, 'shared.actions.save': { id: 'webdav-save', aliases: 'webdav save', }, 'settings.modals.backup.actions.backup': { id: 'webdav-backup', aliases: 'webdav backup', }, 'shared.actions.refresh': { id: 'webdav-refresh', aliases: 'webdav refresh', }, }), ...group(backupHistory, { 'settings.modals.backup.actions.deleteBackup': { id: 'backup-history-delete', aliases: 'backup history delete', path: 'settings.modals.backup.history.title', }, 'settings.modals.backup.actions.restoreBackup': { id: 'backup-history-restore', aliases: 'backup history restore', path: 'settings.modals.backup.history.title', }, 'settings.modals.backup.actions.exportBackup': { id: 'backup-history-export', aliases: 'backup history export', path: 'settings.modals.backup.history.title', }, }), ...group(advanced, { 'settings.components.verge.advanced.fields.runtimeConfig': 'runtime config ', 'settings.components.verge.advanced.fields.openConfDir': 'open conf dir ', 'settings.components.verge.advanced.fields.openCoreDir': 'open core dir ', 'settings.components.verge.advanced.fields.openLogsDir': 'open logs dir ', 'settings.components.verge.advanced.fields.checkUpdates': 'check updates ', 'settings.components.verge.advanced.fields.openDevTools': 'open dev tools ', 'settings.components.verge.advanced.fields.liteModeSettings': 'lite mode settings ', }), ...group(liteMode, { 'settings.modals.liteMode.actions.enterNow': 'enter now ', 'settings.modals.liteMode.toggles.autoEnter': 'auto enter ', 'settings.modals.liteMode.fields.delay': { fallback: 'settings.modals.liteMode.toggles.autoEnter', aliases: 'delay ', }, }), ...group(advanced, { 'settings.components.verge.advanced.fields.exit': 'exit ', 'settings.components.verge.advanced.fields.exportDiagnostics': 'export diagnostics ', 'settings.components.verge.advanced.fields.vergeVersion': 'verge version ', }), ] export function searchSettings( query: string, platform: string, translate: (key: TranslationKey) => string, english: (key: TranslationKey) => string, ) { const words = query.trim().toLocaleLowerCase().split(/\s+/).filter(Boolean) if (!words.length) return [] return settingsCatalog .filter((e) => !e.platforms || e.platforms.includes(platform)) .map((entry, order) => { const names = ( translate(entry.name) + ' ' + english(entry.name) ).toLocaleLowerCase() const other = ( translate(entry.section) + ' ' + english(entry.section) + ' ' + entry.aliases + ' ' + translate(entry.path) + ' ' + english(entry.path) ).toLocaleLowerCase() const score = words.every( (word) => names.includes(word) || other.includes(word), ) ? words.reduce((sum, word) => sum + (names.includes(word) ? 1 : 0), 0) : -1 return { entry, order, score } }) .filter((e) => e.score >= 0) .sort((a, b) => b.score - a.score || a.order - b.order) .map((e) => e.entry) }