Compare commits

..

4 Commits

Author SHA1 Message Date
Memory
7a79adef2e
Add Traditional Chinese (Taiwan) translation
* Add Traditional Chinese (Taiwan) translation

* Add common.loading translation
2025-12-09 23:48:28 +08:00
Memory
94f52cf636
feat: add mrs ruleset preview suppor 2025-12-09 23:33:38 +08:00
xmk23333
19ae63b253 Merge branch 'smart_core' of https://github.com/mihomo-party-org/clash-party into smart_core 2025-12-09 23:32:25 +08:00
xmk23333
34fdd21878 feat: add detailed error toast with copy functionality and refactor error handling 2025-12-09 23:32:13 +08:00
25 changed files with 939 additions and 102 deletions

View File

@ -14,7 +14,8 @@ export {
getProfileStr,
setProfileStr,
changeCurrentProfile,
updateProfileItem
updateProfileItem,
convertMrsRuleset
} from './profile'
export {
getOverrideConfig,

View File

@ -100,7 +100,7 @@ export async function addProfileItem(item: Partial<IProfileItem>): Promise<void>
export async function removeProfileItem(id: string): Promise<void> {
// 先清理自动更新定时器,防止已删除的订阅重新出现
await removeProfileUpdater(id)
const config = await getProfileConfig()
config.items = config.items?.filter((item) => item.id !== id)
let shouldRestart = false
@ -191,7 +191,7 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
timeout: subscriptionTimeout
})
}
// 检查状态码例如403
if (res.status < 200 || res.status >= 300) {
throw new Error(`Subscription failed: Request status code ${res.status}`)
@ -199,7 +199,7 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
const data = res.data
const headers = res.headers
// 校验是否为对象结构 (拦截 HTML字符串、普通文本、乱码)
const parsed = parse(data)
if (typeof parsed !== 'object' || parsed === null) {
@ -210,7 +210,7 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
if (!profile.proxies && !profile['proxy-providers']) {
throw new Error('Subscription failed: Profile missing proxies or providers')
}
if (headers['content-disposition'] && newItem.name === 'Remote File') {
newItem.name = parseFilename(headers['content-disposition'])
}
@ -329,3 +329,43 @@ export async function setFileStr(path: string, content: string): Promise<void> {
)
}
}
export async function convertMrsRuleset(filePath: string, behavior: string): Promise<string> {
const { exec } = await import('child_process')
const { promisify } = await import('util')
const execAsync = promisify(exec)
const { mihomoCorePath } = await import('../utils/dirs')
const { getAppConfig } = await import('./app')
const { tmpdir } = await import('os')
const { randomBytes } = await import('crypto')
const { unlink } = await import('fs/promises')
const { core = 'mihomo' } = await getAppConfig()
const corePath = mihomoCorePath(core)
const { diffWorkDir = false } = await getAppConfig()
const { current } = await getProfileConfig()
let fullPath: string
if (isAbsolutePath(filePath)) {
fullPath = filePath
} else {
fullPath = join(diffWorkDir ? mihomoProfileWorkDir(current) : mihomoWorkDir(), filePath)
}
const tempFileName = `mrs-convert-${randomBytes(8).toString('hex')}.txt`
const tempFilePath = join(tmpdir(), tempFileName)
try {
// 使用 mihomo convert-ruleset 命令转换 MRS 文件为 text 格式
// 命令格式: mihomo convert-ruleset <behavior> <format> <source>
await execAsync(`"${corePath}" convert-ruleset ${behavior} mrs "${fullPath}" "${tempFilePath}"`)
const content = await readFile(tempFilePath, 'utf-8')
await unlink(tempFilePath)
return content
} catch (error) {
try {
await unlink(tempFilePath)
} catch {}
throw error
}
}

View File

@ -48,7 +48,8 @@ import {
removeOverrideItem,
getOverride,
setOverride,
updateOverrideItem
updateOverrideItem,
convertMrsRuleset
} from '../config'
import {
startSubStoreFrontendServer,
@ -215,6 +216,7 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('getProfileStr', (_e, id) => ipcErrorWrapper(getProfileStr)(id))
ipcMain.handle('getFileStr', (_e, path) => ipcErrorWrapper(getFileStr)(path))
ipcMain.handle('setFileStr', (_e, path, str) => ipcErrorWrapper(setFileStr)(path, str))
ipcMain.handle('convertMrsRuleset', (_e, path, behavior) => ipcErrorWrapper(convertMrsRuleset)(path, behavior))
ipcMain.handle('setProfileStr', (_e, id, str) => ipcErrorWrapper(setProfileStr)(id, str))
ipcMain.handle('updateProfileItem', (_e, item) => ipcErrorWrapper(updateProfileItem)(item))
ipcMain.handle('changeCurrentProfile', (_e, id) => ipcErrorWrapper(changeCurrentProfile)(id))

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState, useCallback } from 'react'
import { createPortal } from 'react-dom'
import { IoCheckmark, IoClose, IoAlertSharp, IoInformationSharp } from 'react-icons/io5'
import { IoCheckmark, IoClose, IoAlertSharp, IoInformationSharp, IoCopy } from 'react-icons/io5'
type ToastType = 'success' | 'error' | 'warning' | 'info'
@ -11,6 +11,7 @@ interface ToastData {
message: string
duration?: number
exiting?: boolean
detailed?: boolean
}
type ToastListener = (toasts: ToastData[]) => void
@ -38,11 +39,18 @@ const removeToast = (id: string): void => {
notifyListeners()
}
const addDetailedToast = (type: ToastType, message: string, title?: string): void => {
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`
toasts = [...toasts.slice(-4), { id, type, message, title, duration: 8000, detailed: true }]
notifyListeners()
}
export const toast = {
success: (message: string, title?: string): void => addToast('success', message, title),
error: (message: string, title?: string): void => addToast('error', message, title, 1800),
warning: (message: string, title?: string): void => addToast('warning', message, title),
info: (message: string, title?: string): void => addToast('info', message, title)
info: (message: string, title?: string): void => addToast('info', message, title),
detailedError: (message: string, title?: string): void => addDetailedToast('error', message, title)
}
const ToastItem: React.FC<{
@ -50,6 +58,7 @@ const ToastItem: React.FC<{
onRemove: (id: string) => void
}> = ({ data, onRemove }) => {
useEffect(() => {
if (data.detailed) return
const duration = data.duration || 3500
const exitTimer = setTimeout(() => markExiting(data.id), duration - 200)
const removeTimer = setTimeout(() => onRemove(data.id), duration)
@ -57,7 +66,7 @@ const ToastItem: React.FC<{
clearTimeout(exitTimer)
clearTimeout(removeTimer)
}
}, [data.id, data.duration, onRemove])
}, [data.id, data.duration, data.detailed, onRemove])
const theme: Record<ToastType, { icon: React.ReactNode; bg: string; iconBg: string }> = {
success: {
@ -85,6 +94,66 @@ const ToastItem: React.FC<{
const { icon, iconBg } = theme[data.type]
const duration = data.duration || 3500
const handleClose = (): void => {
markExiting(data.id)
setTimeout(() => onRemove(data.id), 150)
}
const [copied, setCopied] = useState(false)
const handleCopy = async (): Promise<void> => {
await navigator.clipboard.writeText(data.message)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
}
if (data.detailed) {
return (
<div
className={`
relative flex flex-col gap-3 p-4
bg-content1 rounded-xl
shadow-xl border border-danger/30
${data.exiting ? 'toast-exit' : 'toast-enter'}
`}
style={{ width: 480 }}
>
<div className="flex items-center justify-between overflow-visible">
<div className="flex items-center gap-3">
<div className={`flex-shrink-0 w-8 h-8 ${iconBg} rounded-full flex items-center justify-center`}>
{icon}
</div>
<p className="text-base font-semibold text-foreground">{data.title || '错误'}</p>
</div>
<div className="relative" style={{ zIndex: 99999 }}>
<button
onClick={handleCopy}
className="p-2 rounded-lg hover:bg-default-200 transition-colors"
>
<div className="relative w-4 h-4">
<IoCopy className={`absolute inset-0 text-base text-foreground-500 transition-all duration-200 ${copied ? 'opacity-0 scale-50' : 'opacity-100 scale-100'}`} />
<IoCheckmark className={`absolute inset-0 text-base text-success transition-all duration-200 ${copied ? 'opacity-100 scale-100' : 'opacity-0 scale-50'}`} />
</div>
</button>
<div className={`absolute top-full mt-1 left-1/2 -translate-x-1/2 px-2 py-1 text-xs text-foreground bg-content2 border border-default-200 rounded shadow-md whitespace-nowrap transition-all duration-200 ${copied ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-1 pointer-events-none'}`} style={{ zIndex: 99999 }}>
</div>
</div>
</div>
<div className="bg-default-100 rounded-lg p-3 max-h-60 overflow-y-auto scrollbar-thin">
<pre className="text-xs text-foreground-600 whitespace-pre-wrap break-words font-mono select-text leading-relaxed">
{data.message}
</pre>
</div>
<button
onClick={handleClose}
className="self-end px-4 py-1.5 text-sm font-medium text-white bg-danger rounded-lg hover:bg-danger/90 transition-colors"
>
</button>
</div>
)
}
return (
<div
className={`
@ -109,10 +178,7 @@ const ToastItem: React.FC<{
</p>
</div>
<button
onClick={() => {
markExiting(data.id)
setTimeout(() => onRemove(data.id), 150)
}}
onClick={handleClose}
className="flex-shrink-0 p-1 rounded-full hover:bg-default-200/60 transition-colors"
>
<IoClose className="text-base text-foreground-400" />

View File

@ -25,7 +25,8 @@ const RuleProvider: React.FC = () => {
type: '',
title: '',
format: '',
privderType: ''
privderType: '',
behavior: ''
})
useEffect(() => {
if (showDetails.title) {
@ -37,11 +38,12 @@ const RuleProvider: React.FC = () => {
setShowDetails((prev) => ({
...prev,
show: true,
path: provider?.path || `rules/${getHash(provider?.url)}`
path: provider?.path || `rules/${getHash(provider?.url)}`,
behavior: provider?.behavior || 'domain'
}))
}
} catch {
setShowDetails((prev) => ({ ...prev, path: '' }))
setShowDetails((prev) => ({ ...prev, path: '', behavior: '' }))
}
}
fetchProviderPath(showDetails.title)
@ -94,7 +96,8 @@ const RuleProvider: React.FC = () => {
title={showDetails.title}
format={showDetails.format}
privderType={showDetails.privderType}
onClose={() => setShowDetails({ show: false, path: '', type: '', title: '', format: '', privderType: '' })}
behavior={showDetails.behavior}
onClose={() => setShowDetails({ show: false, path: '', type: '', title: '', format: '', privderType: '', behavior: '' })}
/>
)}
<SettingItem title={t('resources.ruleProviders.title')} divider>
@ -122,30 +125,29 @@ const RuleProvider: React.FC = () => {
>
<div className="flex h-[32px] leading-[32px] text-foreground-500">
<div>{dayjs(provider.updatedAt).fromNow()}</div>
{provider.format !== 'MrsRule' && (
<Button
isIconOnly
title={provider.vehicleType == 'File' ? t('common.editor.edit') : t('common.viewer.view')}
className="ml-2"
size="sm"
onPress={() => {
setShowDetails({
show: false,
privderType: 'rule-providers',
path: provider.name,
type: provider.vehicleType,
title: provider.name,
format: provider.format
})
}}
>
{provider.vehicleType == 'File' ? (
<MdEditDocument className={`text-lg`} />
) : (
<CgLoadbarDoc className={`text-lg`} />
)}
</Button>
)}
<Button
isIconOnly
title={provider.vehicleType == 'File' ? t('common.editor.edit') : t('common.viewer.view')}
className="ml-2"
size="sm"
onPress={() => {
setShowDetails({
show: false,
privderType: 'rule-providers',
path: provider.name,
type: provider.vehicleType,
title: provider.name,
format: provider.format,
behavior: provider.behavior || 'domain'
})
}}
>
{provider.vehicleType == 'File' ? (
<MdEditDocument className={`text-lg`} />
) : (
<CgLoadbarDoc className={`text-lg`} />
)}
</Button>
<Button
isIconOnly
title={t('common.updater.update')}

View File

@ -1,7 +1,7 @@
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
import React, { useEffect, useState } from 'react'
import { BaseEditor } from '../base/base-editor'
import { getFileStr, setFileStr } from '@renderer/utils/ipc'
import { getFileStr, setFileStr, convertMrsRuleset, getRuntimeConfig } from '@renderer/utils/ipc'
import yaml from 'js-yaml'
import { useTranslation } from 'react-i18next'
type Language = 'yaml' | 'javascript' | 'css' | 'json' | 'text'
@ -13,34 +13,60 @@ interface Props {
title: string
privderType: string
format?: string
behavior?: string
}
const Viewer: React.FC<Props> = (props) => {
const { t } = useTranslation()
const { type, path, title, format, privderType, onClose } = props
const { type, path, title, format, privderType, behavior, onClose } = props
const [currData, setCurrData] = useState('')
const [isLoading, setIsLoading] = useState(true)
let language: Language = !format || format === 'YamlRule' ? 'yaml' : 'text'
const getContent = async (): Promise<void> => {
let fileContent: React.SetStateAction<string>
if (type === 'Inline') {
fileContent = await getFileStr('config.yaml')
language = 'yaml'
} else {
fileContent = await getFileStr(path)
}
setIsLoading(true)
try {
const parsedYaml = yaml.load(fileContent)
if (privderType === 'proxy-providers') {
setCurrData(yaml.dump({
'proxies': parsedYaml[privderType][title].payload
}))
} else {
setCurrData(yaml.dump({
'rules': parsedYaml[privderType][title].payload
}))
let fileContent: React.SetStateAction<string>
if (format === 'MrsRule') {
language = 'text'
let ruleBehavior: string = behavior || 'domain'
if (!behavior) {
try {
const runtimeConfig = await getRuntimeConfig()
const provider = runtimeConfig['rule-providers']?.[title]
ruleBehavior = provider?.behavior || 'domain'
} catch {
ruleBehavior = 'domain'
}
}
fileContent = await convertMrsRuleset(path, ruleBehavior)
setCurrData(fileContent)
return
}
} catch (error) {
setCurrData(fileContent)
if (type === 'Inline') {
fileContent = await getFileStr('config.yaml')
language = 'yaml'
} else {
fileContent = await getFileStr(path)
}
try {
const parsedYaml = yaml.load(fileContent)
if (privderType === 'proxy-providers') {
setCurrData(yaml.dump({
'proxies': parsedYaml[privderType][title].payload
}))
} else {
setCurrData(yaml.dump({
'rules': parsedYaml[privderType][title].payload
}))
}
} catch (error) {
setCurrData(fileContent)
}
} finally {
setIsLoading(false)
}
}
@ -61,18 +87,24 @@ const Viewer: React.FC<Props> = (props) => {
<ModalContent className="h-full w-[calc(100%-100px)]">
<ModalHeader className="flex pb-0 app-drag">{title}</ModalHeader>
<ModalBody className="h-full">
<BaseEditor
language={language}
value={currData}
readOnly={type != 'File'}
onChange={(value) => setCurrData(value)}
/>
{isLoading ? (
<div className="flex items-center justify-center h-full">
<div className="text-foreground-500">{t('common.loading')}</div>
</div>
) : (
<BaseEditor
language={language}
value={currData}
readOnly={type != 'File' || format === 'MrsRule'}
onChange={(value) => setCurrData(value)}
/>
)}
</ModalBody>
<ModalFooter className="pt-0">
<Button size="sm" variant="light" onPress={onClose}>
{t('common.close')}
</Button>
{type == 'File' && (
{type == 'File' && format !== 'MrsRule' && (
<Button
size="sm"
color="primary"

View File

@ -121,13 +121,14 @@ const GeneralConfig: React.FC = () => {
selectedKeys={[language]}
aria-label={t('settings.language')}
onSelectionChange={async (v) => {
const newLang = Array.from(v)[0] as 'zh-CN' | 'en-US' | 'ru-RU' | 'fa-IR'
const newLang = Array.from(v)[0] as 'zh-CN' | 'zh-TW' | 'en-US' | 'ru-RU' | 'fa-IR'
await patchAppConfig({ language: newLang })
i18n.changeLanguage(newLang)
}}
>
<SelectItem key="zh-CN"></SelectItem>
<SelectItem key="en-US">English</SelectItem>
<SelectItem key="zh-CN"></SelectItem>
<SelectItem key="zh-TW"> ()</SelectItem>
<SelectItem key="ru-RU">Русский</SelectItem>
<SelectItem key="fa-IR">فارسی</SelectItem>
</Select>

View File

@ -1,5 +1,5 @@
import React, { createContext, useContext, ReactNode } from 'react'
import { toast } from '@renderer/components/base/toast'
import { showError } from '@renderer/utils/error-display'
import useSWR from 'swr'
import { getAppConfig, patchAppConfig as patch } from '@renderer/utils/ipc'
@ -18,7 +18,7 @@ export const AppConfigProvider: React.FC<{ children: ReactNode }> = ({ children
try {
await patch(value)
} catch (e) {
toast.error(String(e))
await showError(e, '更新应用配置失败')
} finally {
mutateAppConfig()
}

View File

@ -1,5 +1,5 @@
import React, { createContext, useContext, ReactNode } from 'react'
import { toast } from '@renderer/components/base/toast'
import { showError } from '@renderer/utils/error-display'
import useSWR from 'swr'
import { getControledMihomoConfig, patchControledMihomoConfig as patch } from '@renderer/utils/ipc'
@ -23,7 +23,7 @@ export const ControledMihomoConfigProvider: React.FC<{ children: ReactNode }> =
try {
await patch(value)
} catch (e) {
toast.error(String(e))
await showError(e, '更新内核配置失败')
} finally {
mutateControledMihomoConfig()
}

View File

@ -1,5 +1,5 @@
import React, { createContext, useContext, ReactNode } from 'react'
import { toast } from '@renderer/components/base/toast'
import { showError } from '@renderer/utils/error-display'
import useSWR from 'swr'
import {
getOverrideConfig,
@ -29,7 +29,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil
try {
await set(config)
} catch (e) {
toast.error(String(e))
await showError(e, '保存覆写配置失败')
} finally {
mutateOverrideConfig()
}
@ -39,7 +39,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil
try {
await add(item)
} catch (e) {
toast.error(String(e))
await showError(e, '添加覆写失败')
} finally {
mutateOverrideConfig()
}
@ -49,7 +49,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil
try {
await remove(id)
} catch (e) {
toast.error(String(e))
await showError(e, '删除覆写失败')
} finally {
mutateOverrideConfig()
}
@ -59,7 +59,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil
try {
await update(item)
} catch (e) {
toast.error(String(e))
await showError(e, '更新覆写失败')
} finally {
mutateOverrideConfig()
}

View File

@ -1,4 +1,5 @@
import React, { createContext, ReactNode, useContext } from 'react'
import { showError } from '@renderer/utils/error-display'
import { toast } from '@renderer/components/base/toast'
import useSWR from 'swr'
import {
@ -33,7 +34,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
try {
await set(config)
} catch (e) {
toast.error(String(e))
await showError(e, '保存配置失败')
} finally {
mutateProfileConfig()
window.electron.ipcRenderer.send('updateTrayMenu')
@ -44,7 +45,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
try {
await add(item)
} catch (e) {
toast.error(String(e))
await showError(e, '添加配置失败')
} finally {
mutateProfileConfig()
window.electron.ipcRenderer.send('updateTrayMenu')
@ -55,7 +56,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
try {
await remove(id)
} catch (e) {
toast.error(String(e))
await showError(e, '删除配置失败')
} finally {
mutateProfileConfig()
window.electron.ipcRenderer.send('updateTrayMenu')
@ -66,7 +67,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
try {
await update(item)
} catch (e) {
toast.error(String(e))
await showError(e, '更新配置失败')
} finally {
mutateProfileConfig()
window.electron.ipcRenderer.send('updateTrayMenu')
@ -108,7 +109,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
if (errorMsg.includes('reply was never sent')) {
setTimeout(() => mutateProfileConfig(), 1000)
} else {
toast.error(errorMsg, '切换配置失败')
await showError(errorMsg, '切换配置失败')
mutateProfileConfig()
}
} finally {

View File

@ -16,6 +16,7 @@
"common.auto": "Auto",
"common.default": "Default",
"common.close": "Close",
"common.loading": "Loading...",
"common.pinWindow": "Pin Window",
"common.next": "Next",
"common.prev": "Previous",

View File

@ -16,6 +16,7 @@
"common.auto": "خودکار",
"common.default": "پیش‌فرض",
"common.close": "بستن",
"common.loading": "در حال بارگذاری...",
"common.pinWindow": "پین کردن پنجره",
"common.next": "بعدی",
"common.prev": "قبلی",

View File

@ -16,6 +16,7 @@
"common.auto": "Авто",
"common.default": "По умолчанию",
"common.close": "Закрыть",
"common.loading": "Загрузка...",
"common.pinWindow": "Закрепить окно",
"common.next": "Далее",
"common.prev": "Назад",

View File

@ -16,6 +16,7 @@
"common.auto": "自动",
"common.default": "默认",
"common.close": "关闭",
"common.loading": "加载中...",
"common.pinWindow": "窗口置顶",
"common.next": "下一步",
"common.prev": "上一步",
@ -158,7 +159,7 @@
"mihomo.error.installCoreFailed": "安装内核失败",
"mihomo.enableSmartCore": "启用 Smart 内核",
"mihomo.enableSmartOverride": "使用自动 Smart 规则覆写",
"mihomo.smartOverrideTooltip": "使用 Party 自带的智能覆写脚本,替换 url-test 和 load-balance 为 Smart 规则组如果没有上述规则组则提取订阅中的所有节点并替换默认规则适合不想折腾的用户功能一键生效如果使用全局模式请选择名称为“Smart Group 的节点",
"mihomo.smartOverrideTooltip": "使用 Party 自带的智能覆写脚本,替换 url-test 和 load-balance 为 Smart 规则组如果没有上述规则组则提取订阅中的所有节点并替换默认规则适合不想折腾的用户功能一键生效如果使用全局模式请选择名称为“Smart Group的节点",
"mihomo.smartCoreUseLightGBM": "使用 LightGBM",
"mihomo.smartCoreCollectData": "收集数据",
"mihomo.smartCoreStrategy": "策略模式",

View File

@ -0,0 +1,632 @@
{
"common.settings": "設置",
"common.profiles": "配置",
"common.proxies": "代理",
"common.connections": "連接",
"common.dns": "DNS",
"common.tun": "TUN",
"common.save": "保存",
"common.cancel": "取消",
"common.edit": "編輯",
"common.viewer.view": "查看",
"common.editor.edit": "編輯",
"common.delete": "刪除",
"common.seconds": "秒",
"common.confirm": "確認",
"common.auto": "自動",
"common.default": "默認",
"common.close": "關閉",
"common.loading": "載入中...",
"common.pinWindow": "窗口置頂",
"common.next": "下一步",
"common.prev": "上一步",
"common.done": "完成",
"common.notification.restartRequired": "需要重新啟動應用以使更改生效",
"common.notification.systemProxyEnabled": "系統代理已啟用",
"common.notification.systemProxyDisabled": "系統代理已關閉",
"common.notification.tunEnabled": "TUN 已啟用",
"common.notification.tunDisabled": "TUN 已關閉",
"common.notification.ruleMode": "規則模式",
"common.notification.globalMode": "全局模式",
"common.notification.directMode": "直連模式",
"common.error.appCrash": "應用崩潰了 :( 請將以下信息提交給開發者以排查錯誤",
"common.error.copyErrorMessage": "複製報錯信息",
"common.error.invalidCron": "無效的 Cron 表達式",
"common.error.getBackupListFailed": "獲取備份列表失敗:{{error}}",
"common.error.restoreFailed": "恢復失敗:{{error}}",
"common.error.deleteFailed": "刪除失敗:{{error}}",
"common.error.shortcutRegistrationFailed": "快捷鍵註冊失敗",
"common.error.shortcutRegistrationFailedWithError": "快捷鍵註冊失敗:{{error}}",
"common.error.adminRequired": "首次啟動請以系統管理員身分執行",
"common.error.initFailed": "應用初始化失敗",
"core.highPrivilege.title": "檢測到高權限內核",
"core.highPrivilege.message": "檢測到運行中的高權限內核,需以系統管理員模式重新啟動應用程式以匹配權限,確定重新啟動?",
"common.updater.versionReady": "v{{version}} 版本就緒",
"common.updater.goToDownload": "前往下載",
"common.updater.update": "更新",
"common.refresh": "重新整理",
"settings.general": "通用設置",
"settings.mihomo": "Mihomo 設置",
"settings.language": "語言",
"settings.theme": "主題",
"settings.darkMode": "深色模式",
"settings.lightMode": "淺色模式",
"settings.autoStart": "開機自啟",
"settings.autoStart.permissions": "正在以管理員權限配置計畫任務請在UAC對話框中點擊'是'...",
"settings.autoCheckUpdate": "自動檢查更新",
"settings.silentStart": "靜默啟動",
"settings.autoQuitWithoutCore": "自動進入輕量模式",
"settings.autoQuitWithoutCoreTooltip": "關閉窗口後指定時間自動進入輕量模式",
"settings.subscriptionTimeout": "訂閱逾時時間",
"settings.autoQuitWithoutCoreDelay": "輕量模式自動啟用延遲",
"settings.envType": "環境變數類型",
"settings.showFloatingWindow": "顯示懸浮窗",
"settings.spinFloatingIcon": "根據網速旋轉懸浮窗圖標",
"settings.floatingWindowCompatMode": "懸浮窗兼容模式(推薦開啟)",
"settings.floatingWindowCompatModeTooltip": "禁用透明效果以避免在某些 Windows 系統上崩潰,建議保持開啟以確保穩定性",
"settings.disableTray": "禁用托盤圖標",
"settings.swapTrayClick": "交換托盤左右鍵功能",
"settings.proxyInTray": "在托盤菜單顯示代理信息",
"settings.showCurrentProxyInTray": "托盤菜單顯示當前代理",
"settings.disableTrayIconColor": "禁用托盤圖標顏色變化",
"settings.disableAnimations": "禁用動畫效果",
"settings.showTraffic_windows": "在任务列顯示網速",
"settings.showTraffic_mac": "在狀態欄顯示網速",
"settings.hideConnectionCardWave": "連接卡片純數字顯示",
"settings.showDockIcon": "顯示 Dock 圖標",
"settings.useWindowFrame": "使用系統標題欄",
"settings.triggerMainWindowBehavior": "托盤窗口觸發行為",
"settings.triggerMainWindowBehaviorShow": "總是顯示",
"settings.triggerMainWindowBehaviorToggle": "切換顯示/隱藏",
"settings.disableHardwareAcceleration": "禁用硬件加速",
"settings.disableHardwareAccelerationTooltip": "禁用硬件加速可以解決某些顯示卡驅動問題導致的渲染異常或崩潰,但可能會降低性能。修改後需要重啟應用程式生效",
"settings.hardwareAcceleration.confirm.title": "確認重啟應用程式",
"settings.hardwareAcceleration.confirm.content": "修改硬件加速設置需要重啟應用程式才能生效。是否立即重啟?",
"settings.backgroundColor": "背景顏色",
"settings.backgroundAuto": "自動",
"settings.backgroundDark": "深色",
"settings.backgroundLight": "淺色",
"settings.fetchTheme": "獲取主題",
"settings.importTheme": "匯入主題",
"settings.editTheme": "編輯主題",
"settings.selectTheme": "選擇主題",
"settings.links.docs": "官方文檔",
"settings.links.github": "GitHub 倉庫",
"settings.links.telegram": "Telegram 群組",
"settings.title": "應用設置",
"settings.webui.title": "WebUI 管理面板",
"settings.webui.manage": "管理面板",
"settings.webui.host": "主機",
"settings.webui.port": "連接埠",
"settings.webui.secret": "密鑰",
"settings.webui.noSecret": "無密鑰",
"settings.webui.panelName": "面板名稱",
"settings.webui.panelUrl": "面板URL",
"settings.webui.panelNamePlaceholder": "輸入面板名稱",
"settings.webui.panelUrlPlaceholder": "輸入面板URL",
"settings.webui.variableHint": "可用變量",
"settings.webui.addPanel": "添加面板",
"settings.webui.panels": "面板列表",
"settings.webui.restoreDefaults": "恢復默認",
"mihomo.title": "內核設置",
"mihomo.restart": "重啟內核",
"mihomo.memory": "記憶體使用",
"mihomo.userAgent": "訂閱 User Agent",
"mihomo.userAgentPlaceholder": "默認mihomo.party/v{{version}} (clash.meta)",
"mihomo.delayTest.url": "延遲測試 URL",
"mihomo.delayTest.urlPlaceholder": "默認http://www.gstatic.com/generate_204",
"mihomo.delayTest.concurrency": "延遲測試併發數",
"mihomo.delayTest.concurrencyPlaceholder": "默認50",
"mihomo.delayTest.timeout": "延遲測試逾時",
"mihomo.delayTest.timeoutPlaceholder": "默認5000",
"mihomo.gist.title": "同步運行時配置到 Gist",
"mihomo.gist.copyUrl": "複製 Gist URL",
"mihomo.gist.token": "GitHub Token",
"mihomo.proxyColumns.title": "代理顯示列數",
"mihomo.proxyColumns.auto": "自動",
"mihomo.proxyColumns.one": "一列",
"mihomo.proxyColumns.two": "两列",
"mihomo.proxyColumns.three": "三列",
"mihomo.proxyColumns.four": "四列",
"mihomo.cpuPriority.title": "核心進程優先級",
"mihomo.cpuPriority.realtime": "實時",
"mihomo.cpuPriority.high": "高",
"mihomo.cpuPriority.aboveNormal": "高於正常",
"mihomo.cpuPriority.normal": "正常",
"mihomo.cpuPriority.belowNormal": "低於正常",
"mihomo.cpuPriority.low": "低",
"mihomo.workDir.title": "不同訂閱使用獨立工作目錄",
"mihomo.workDir.tooltip": "啟用後可避免不同訂閱中存在相同名稱的代理組時發生衝突",
"mihomo.controlSniff": "控制域名嗅探",
"mihomo.autoCloseConnection": "自動關閉連接",
"mihomo.pauseSSID.title": "指定 Wi-Fi SSID 直連",
"mihomo.pauseSSID.placeholder": "輸入 SSID",
"mihomo.coreVersion": "內核版本",
"mihomo.upgradeCore": "升級內核",
"mihomo.coreAuthLost": "內核權限丟失",
"mihomo.coreUpgradeSuccess": "內核升級成功若要使用虛擬網卡Tun請到虛擬網卡頁面重新手動授權內核",
"mihomo.alreadyLatestVersion": "已經是最新版本",
"mihomo.selectCoreVersion": "選擇內核版本",
"mihomo.stableVersion": "穩定版",
"mihomo.alphaVersion": "預覽版",
"mihomo.smartVersion": "Smart 版",
"mihomo.specificVersion": "特定版本",
"mihomo.selectSpecificVersion": "選擇特定版本",
"mihomo.searchVersion": "搜索版本...",
"mihomo.installVersion": "安裝版本",
"mihomo.noVersionsFound": "未找到版本",
"mihomo.error.fetchTagsFailed": "獲取版本列表失敗",
"mihomo.error.installCoreFailed": "安裝內核失敗",
"mihomo.enableSmartCore": "啟用 Smart 內核",
"mihomo.enableSmartOverride": "使用自動 Smart 規則覆寫",
"mihomo.smartOverrideTooltip": "使用 Party 自帶的智能覆寫腳本,替換 url-test 和 load-balance 為 Smart 規則組如果沒有上述規則組則提取訂閱中的所有節點並替換默認規則適合不想折騰的用戶功能一鍵生效如果使用全局模式請選擇名稱為“Smart Group”的節點",
"mihomo.smartCoreUseLightGBM": "使用 LightGBM",
"mihomo.smartCoreCollectData": "收集數據",
"mihomo.smartCoreStrategy": "策略模式",
"mihomo.smartCoreStrategyStickySession": "粘性會話",
"mihomo.smartCoreStrategyRoundRobin": "輪詢",
"mihomo.smartCoreUseLightGBMTooltip": "使用預訓練的通用模型,可快速提升節點選擇效果,但可能不適合您的特定網絡環境",
"mihomo.smartCoreCollectDataTooltip": "收集您的網絡使用數據,可用於訓練更适合您的網絡環境的自定義模型(如果您不懂如何訓練模型,請關閉)",
"mihomo.smartCollectorSize": "數據收集文件大小MB",
"mihomo.smartCollectorSizeTooltip": "限制數據收集文件大小,默認為 100MB",
"mihomo.mixedPort": "混合埠",
"mihomo.confirm": "確認",
"mihomo.socksPort": "Socks 埠",
"mihomo.httpPort": "Http 埠",
"mihomo.redirPort": "Redir 埠",
"mihomo.tproxyPort": "Tproxy 埠",
"mihomo.externalController": "外部控制地址",
"mihomo.externalControllerSecret": "外部控制訪問密鑰",
"mihomo.ipv6": "IPv6",
"mihomo.allowLanConnection": "允許局域網連接",
"mihomo.allowedIpSegments": "允许連接的 IP 段",
"mihomo.disallowedIpSegments": "禁止連接的 IP 段",
"mihomo.userVerification": "用戶驗證",
"mihomo.skipAuthPrefixes": "允許跳過驗證的 IP 段",
"mihomo.useRttDelayTest": "使用 1-RTT 延遲測試",
"mihomo.tcpConcurrent": "TCP 併發",
"mihomo.storeSelectedNode": "存儲選擇節點",
"mihomo.storeFakeIp": "存儲 FakeIP",
"mihomo.disableLoopbackDetector": "禁用回環檢測器",
"mihomo.skipSafePathCheck": "禁用安全路徑檢查",
"mihomo.disableEmbedCA": "不使用內置 CA 證書",
"mihomo.disableSystemCA": "不使用系統 CA 證書",
"mihomo.logRetentionDays": "日誌保留天數",
"mihomo.logLevel": "日誌等級",
"mihomo.selectLogLevel": "選擇日誌等級",
"mihomo.silent": "靜默",
"mihomo.error": "錯誤",
"mihomo.warning": "警告",
"mihomo.info": "信息",
"mihomo.debug": "調試",
"mihomo.error.coreStartFailed": "內核啟動出錯",
"mihomo.error.profileCheckFailed": "配置檢查失敗",
"mihomo.error.externalControllerListenError": "外部控制監聽錯誤",
"mihomo.findProcess": "查找進程",
"mihomo.selectFindProcessMode": "選擇進程查找模式",
"mihomo.strict": "自動",
"mihomo.off": "關閉",
"mihomo.always": "開啟",
"mihomo.username.placeholder": "用戶名",
"mihomo.password.placeholder": "密碼",
"mihomo.ipSegment.placeholder": "IP 段",
"mihomo.interface.title": "網絡信息",
"substore.title": "Sub-Store",
"substore.checkUpdate": "檢查更新",
"substore.updating": "Sub-Store 更新中...",
"substore.updateCompleted": "Sub-Store 更新完成",
"substore.updateFailed": "Sub-Store 更新失敗",
"substore.downloadFailed": "下載 Sub-Store 文件失敗",
"substore.openInBrowser": "在瀏覽器中打開",
"substore.enable": "啟用 Sub-Store",
"substore.allowLan": "允许局域網訪問",
"substore.useCustomBackend": "使用自定義 Sub-Store 後端",
"substore.customBackendUrl.title": "自定義 Sub-Store 後端 URL",
"substore.customBackendUrl.placeholder": "必須包含協議",
"substore.useProxy": "為所有 Sub-Store 請求啟用代理",
"substore.sync.title": "定時同步訂閱/文件",
"substore.sync.placeholder": "Cron 表達式",
"substore.restore.title": "定時恢復配置",
"substore.restore.placeholder": "Cron 表達式",
"substore.backup.title": "定時備份配置",
"substore.backup.placeholder": "Cron 表達式",
"webdav.title": "WebDAV 備份",
"webdav.url": "WebDAV URL",
"webdav.dir": "WebDAV 備份目錄",
"webdav.username": "WebDAV 用戶名",
"webdav.password": "WebDAV 密碼",
"webdav.maxBackups": "最大備份數",
"webdav.noLimit": "不限制",
"webdav.backup": "備份",
"webdav.backup.cron.title": "定時備份配置",
"webdav.backup.cron.placeholder": "Cron 表達式",
"webdav.restore.title": "恢復備份",
"webdav.restore.noBackups": "還沒有備份",
"webdav.notification.backupSuccess.title": "備份成功",
"webdav.notification.backupSuccess.body": "備份文件已上傳到 WebDAV",
"localBackup.title": "備份與恢復",
"localBackup.export.title": "匯出本地備份",
"localBackup.export.button": "匯出備份",
"localBackup.import.title": "匯入本地備份",
"localBackup.import.button": "匯入備份",
"localBackup.import.confirm.title": "確認匯入",
"localBackup.import.confirm.body": "匯入本地備份將會覆蓋當前所有配置,確定要繼續嗎?",
"localBackup.notification.exportSuccess.title": "匯出成功",
"localBackup.notification.exportSuccess.body": "本地備份已匯出到指定位置",
"localBackup.notification.importSuccess.title": "匯入成功",
"localBackup.notification.importSuccess.body": "本地備份已成功匯入",
"shortcuts.title": "快捷鍵設置",
"shortcuts.toggleWindow": "打開/關閉窗口",
"shortcuts.toggleFloatingWindow": "打開/關閉懸浮窗",
"shortcuts.toggleSystemProxy": "打開/關閉系統代理",
"shortcuts.toggleTun": "打開/關閉 TUN",
"shortcuts.toggleRuleMode": "切換規則模式",
"shortcuts.toggleGlobalMode": "切換全局模式",
"shortcuts.toggleDirectMode": "切換直連模式",
"shortcuts.toggleLightMode": "切換輕量模式",
"shortcuts.restartApp": "重啟應用",
"shortcuts.input.placeholder": "點擊輸入快捷鍵",
"sider.title": "側邊欄設置",
"sider.cards.systemProxy": "系統代理",
"sider.cards.tun": "虛擬網卡",
"sider.cards.profiles": "訂閱管理",
"sider.cards.proxies": "代理組",
"sider.cards.rules": "規則",
"sider.cards.resources": "外部資源",
"sider.cards.override": "覆寫",
"sider.cards.connections": "連接",
"sider.cards.core": "內核設置",
"sider.cards.dns": "DNS覆寫",
"sider.cards.sniff": "嗅探覆寫",
"sider.cards.logs": "日誌",
"sider.cards.substore": "Sub-Store",
"sider.cards.config": "運行時配置",
"sider.cards.emptyProfile": "空白配置",
"sider.cards.viewRuntimeConfig": "查看運行時配置",
"sider.cards.remote": "遠程",
"sider.cards.local": "本地",
"sider.cards.trafficUsage": "流量使用進度",
"sider.cards.neverExpire": "長期有效",
"sider.cards.outbound.title": "出站模式",
"sider.cards.outbound.rule": "規則",
"sider.cards.outbound.global": "全局",
"sider.cards.outbound.direct": "直連",
"sider.size.large": "大",
"sider.size.small": "小",
"sider.size.hidden": "隱藏",
"actions.guide.title": "打開引導頁面",
"actions.guide.button": "打開引導頁",
"actions.update.title": "檢查更新",
"actions.update.button": "檢查更新",
"actions.update.upToDate.title": "當前已是最新版本",
"actions.update.upToDate.body": "無需更新",
"actions.reset.title": "重置軟件",
"actions.reset.button": "重置軟件",
"actions.reset.tooltip": "刪除所有配置,將軟件恢復初始狀態",
"actions.reset.confirm.title": "確認重置",
"actions.reset.confirm.content": "您確定要重置刪除所有配置,將軟件恢復初始狀態嗎?只有軟件運行不正常的時候才需重置,您的所有訂閱、覆寫、腳本將全部丟失!",
"actions.heapSnapshot.title": "創建堆快照",
"actions.heapSnapshot.button": "創建堆快照",
"actions.heapSnapshot.tooltip": "創建主進程堆快照,用於排查內存問題",
"actions.lightMode.title": "輕量模式",
"actions.lightMode.button": "輕量模式",
"actions.lightMode.tooltip": "完全退出軟件,只保留內核進程",
"actions.restartApp": "重啟應用",
"actions.quit.title": "退出應用",
"actions.quit.button": "退出應用",
"actions.version.title": "應用程式版本",
"theme.editor.title": "編輯主題",
"proxies.title": "代理組與節點",
"proxies.card.title": "代理組",
"proxies.delay.test": "測試",
"proxies.delay.timeout": "逾時",
"proxies.unpin": "取消固定",
"proxies.order.default": "默認",
"proxies.order.delay": "延遲",
"proxies.order.name": "名稱",
"proxies.mode.full": "詳細信息",
"proxies.mode.simple": "簡潔信息",
"proxies.mode.direct": "直連模式",
"proxies.search.placeholder": "搜索節點",
"proxies.locate": "定位到當前節點",
"sniffer.title": "域名嗅探設置",
"sniffer.enable": "啟用域名嗅探",
"sniffer.parsePureIP": "對未映射 IP 地址嗅探",
"sniffer.forceDNSMapping": "對真實 IP 映射嗅探",
"sniffer.overrideDestination": "覆蓋連接地址",
"sniffer.sniff.title": "HTTP 埠嗅探",
"sniffer.sniff.tls": "TLS 埠嗅探",
"sniffer.sniff.quic": "QUIC 埠嗅探",
"sniffer.sniff.ports.placeholder": "通訊埠號,使用英文逗號分割",
"sniffer.skipDomain.title": "跳過域名嗅探",
"sniffer.skipDomain.placeholder": "例:+.push.apple.com",
"sniffer.forceDomain.title": "強制域名嗅探",
"sniffer.forceDomain.placeholder": "例v2ex.com",
"sniffer.skipDstAddress.title": "跳過目標地址嗅探",
"sniffer.skipDstAddress.placeholder": "例1.1.1.1/32",
"sniffer.skipSrcAddress.title": "跳過來源地址嗅探",
"sniffer.skipSrcAddress.placeholder": "例192.168.1.1/24",
"sniffer.saveOnly": "僅保存",
"sysproxy.title": "系統代理",
"sysproxy.host.title": "代理主機",
"sysproxy.host.placeholder": "默認 127.0.0.1 若無特殊需求請勿修改",
"sysproxy.mode.title": "代理模式",
"sysproxy.mode.manual": "手動",
"sysproxy.mode.pac": "PAC",
"sysproxy.uwp.title": "UWP 工具",
"sysproxy.uwp.open": "打開 UWP 工具",
"sysproxy.pac.edit": "編輯 PAC 腳本",
"sysproxy.pacEditor.title": "編輯 PAC 腳本",
"sysproxy.bypass.title": "代理繞過",
"sysproxy.bypass.addDefault": "添加默認代理繞過",
"sysproxy.bypass.placeholder": "例: *.baidu.com",
"tun.title": "虛擬網卡",
"tun.firewall.title": "重設防火牆",
"tun.firewall.reset": "重設防火牆",
"tun.core.title": "手動授權內核",
"tun.core.auth": "手動授權內核",
"tun.dns.autoSet": "自動設置系統DNS",
"tun.stack.title": "Tun 模式堆棧",
"tun.device.title": "Tun 網卡名稱",
"tun.strictRoute": "嚴格路由",
"tun.autoRoute": "自動設置全局路由",
"tun.autoRedirect": "自動設置TCP重定向",
"tun.autoDetectInterface": "自動選擇流量出口接口",
"tun.dnsHijack": "DNS 劫持",
"tun.excludeAddress.title": "排除自定義網段",
"tun.excludeAddress.placeholder": "例: 172.20.0.0/16",
"tun.notifications.coreAuthSuccess": "內核授權成功",
"tun.notifications.firewallResetSuccess": "防火牆重設成功",
"tun.permissions.title": "需要系統管理員權限",
"tun.permissions.message": "啟用TUN模式需要系統管理員權限是否現在重啟應用獲取權限",
"tun.permissions.failed": "權限授權失敗",
"tun.permissions.restarting": "正在以系統管理員權限重啟應用請在UAC對話框中點擊'是'...",
"dns.title": "DNS 設置",
"dns.enable": "啟用 DNS",
"dns.enhancedMode.title": "域名映射模式",
"dns.enhancedMode.fakeIp": "虛假 IP",
"dns.enhancedMode.redirHost": "真實 IP",
"dns.enhancedMode.normal": "取消映射",
"dns.fakeIp.range": "回應範圍",
"dns.fakeIp.rangePlaceholder": "例198.18.0.1/16",
"dns.fakeIp.filter": "真實 IP 回應",
"dns.fakeIp.filterPlaceholder": "例:+.lan",
"dns.respectRules": "遵守規則",
"dns.defaultNameserver": "DNS 伺服器域名解析",
"dns.defaultNameserverPlaceholder": "例223.5.5.5,僅支持 IP",
"dns.proxyServerNameserver": "代理伺服器域名解析",
"dns.proxyServerNameserverPlaceholder": "例tls://dns.alidns.com",
"dns.nameserver": "默認解析伺服器",
"dns.nameserverPlaceholder": "例tls://dns.alidns.com",
"dns.directNameserver": "直連解析伺服器",
"dns.directNameserverPlaceholder": "例tls://dns.alidns.com",
"dns.nameserverPolicy.title": "覆蓋 DNS 策略",
"dns.nameserverPolicy.list": "DNS 策略列表",
"dns.nameserverPolicy.domainPlaceholder": "域名",
"dns.nameserverPolicy.serverPlaceholder": "DNS 伺服器",
"dns.systemHosts.title": "使用系統 Hosts",
"dns.customHosts.title": "自定義 Hosts",
"dns.customHosts.list": "Hosts 列表",
"dns.customHosts.domainPlaceholder": "域名",
"dns.customHosts.valuePlaceholder": "域名或 IP",
"dns.fallback": "回退伺服器",
"dns.fallbackPlaceholder": "例https://dns.alidns.com/dns-query",
"dns.fallbackFilter.title": "回退過濾設置",
"dns.fallbackFilter.geoip": "GeoIP 過濾",
"dns.fallbackFilter.geoipCode": "GeoIP 國家代碼",
"dns.fallbackFilter.ipcidr": "回退 IP CIDR",
"dns.fallbackFilter.ipcidrPlaceholder": "例240.0.0.0/4",
"dns.fallbackFilter.domain": "回退域名",
"dns.fallbackFilter.domainPlaceholder": "例:+.google.com",
"dns.saveOnly": "僅保存",
"profiles.title": "訂閱管理",
"profiles.input.placeholder": "請輸入您的訂閱網址",
"profiles.updateAll": "更新全部訂閱",
"profiles.useProxy": "代理",
"profiles.import": "匯入",
"profiles.open": "打開",
"profiles.new": "新建",
"profiles.newProfile": "新建訂閱",
"profiles.substore.visit": "訪問 Sub-Store",
"profiles.error.unsupportedFileType": "不支持的檔案類型",
"profiles.error.urlParamMissing": "缺少參數 url",
"profiles.error.importFailed": "訂閱匯入失敗",
"profiles.emptyProfile": "空白訂閱",
"profiles.viewRuntimeConfig": "查看當時運行時配置",
"profiles.neverExpire": "長期有效",
"profiles.remote": "遠程",
"profiles.local": "本地",
"profiles.trafficUsage": "流量使用進度",
"profiles.traffic.usage": "{{used}}/{{total}}",
"profiles.traffic.unlimited": "無限制",
"profiles.traffic.expired": "已過期",
"profiles.traffic.remainingDays": "剩餘 {{days}} 天",
"profiles.traffic.lastUpdate": "最後更新:{{time}}",
"profiles.editInfo.title": "編輯信息",
"profiles.editInfo.name": "名稱",
"profiles.editInfo.url": "訂閱地址",
"profiles.editInfo.authToken": "授權令牌",
"profiles.editInfo.authTokenPlaceholder": "Bearer token 或其他認證頭值",
"profiles.editInfo.useProxy": "使用代理更新",
"profiles.editInfo.interval": "更新間隔",
"profiles.editInfo.intervalPlaceholder": "例如30 或 '0 * * * *'",
"profiles.editInfo.intervalInvalid": "不合法",
"profiles.editInfo.intervalMinutes": "以分鐘為單位的定時間隔",
"profiles.editInfo.intervalCron": "有效的Cron表達式",
"profiles.editInfo.intervalHint": "請輸入數字或合法的Cron表達式0 * * * *",
"profiles.editInfo.fixedInterval": "固定更新間隔",
"profiles.editInfo.autoUpdate": "自動更新",
"profiles.editInfo.override.title": "覆寫",
"profiles.editInfo.override.global": "全局",
"profiles.editInfo.override.noAvailable": "沒有可用的覆寫",
"profiles.editInfo.override.add": "添加覆寫",
"profiles.editFile.title": "編輯訂閱",
"profiles.editFile.notice": "注意:此處編輯配置更新訂閱後會還原,如需要自定義配置請使用",
"profiles.editFile.override": "覆寫",
"profiles.editFile.feature": "功能",
"profiles.editRules.title": "編輯規則",
"profiles.editRules.ruleType": "規則類型",
"profiles.editRules.payload": "匹配內容",
"profiles.editRules.payloadPlaceholder": "請輸入匹配內容",
"profiles.editRules.proxy": "代理節點",
"profiles.editRules.proxyPlaceholder": "請選擇或輸入代理節點",
"profiles.editRules.addRule": "添加規則",
"profiles.editRules.addRulePrepend": "添加前置規則",
"profiles.editRules.addRuleAppend": "添加後置規則",
"profiles.editRules.instructions": "使用說明",
"profiles.editRules.instructions1": "1. 在下拉菜單中選擇規則類型",
"profiles.editRules.instructions2": "2. 輸入匹配內容",
"profiles.editRules.instructions3": "3. 選擇指定代理,然後點擊添加規則",
"profiles.editRules.currentRules": "當前規則",
"profiles.editRules.searchPlaceholder": "搜索規則...",
"profiles.editRules.noRules": "暫無規則",
"profiles.editRules.noMatchingRules": "沒有匹配的規則",
"profiles.editRules.saveError": "保存規則時出錯",
"profiles.editRules.noResolve": "跳過DNS解析 (no-resolve)",
"profiles.editRules.src": "匹配源IP (src)",
"profiles.openFile": "打開檔案",
"profiles.home": "主頁",
"profiles.notification.importSuccess": "訂閱匯入成功",
"resources.proxyProviders.title": "代理集合",
"resources.proxyProviders.updateAll": "更新全部",
"resources.ruleProviders.title": "規則集合",
"resources.ruleProviders.updateAll": "更新全部",
"outbound.title": "出站模式",
"outbound.modes.rule": "規則",
"outbound.modes.global": "全局",
"outbound.modes.direct": "直連",
"rules.title": "分流規則",
"rules.filter": "篩選過濾",
"override.title": "覆寫",
"override.import": "匯入",
"override.docs": "使用文檔",
"override.repository": "常用覆寫倉庫",
"override.unsupportedFileType": "不支持的檔案類型",
"override.actions.open": "打開",
"override.actions.newYaml": "新建 YAML",
"override.actions.newJs": "新建 JavaScript",
"override.defaultContent.yaml": "# https://mihomo.party/docs/guide/override/yaml",
"override.defaultContent.js": "// https://mihomo.party/docs/guide/override/javascript\nfunction main(config) {\n return config\n}",
"override.newFile.yaml": "新建YAML",
"override.newFile.js": "新建JS",
"override.editInfo.title": "編輯信息",
"override.editInfo.name": "名稱",
"override.editInfo.url": "地址",
"override.editInfo.global": "全局啟用",
"override.editFile.title": "編輯覆寫{{type}}",
"override.editFile.script": "腳本",
"override.editFile.config": "配置",
"override.execLog.title": "執行日誌",
"override.execLog.close": "關閉",
"override.menuItems.editInfo": "編輯信息",
"override.menuItems.editFile": "編輯檔案",
"override.menuItems.openFile": "打開檔案",
"override.menuItems.execLog": "執行日誌",
"override.menuItems.delete": "刪除",
"override.labels.global": "全局",
"connections.title": "連接",
"connections.upload": "上傳",
"connections.download": "下載",
"connections.closeAll": "關閉全部連接",
"connections.active": "活動中",
"connections.closed": "已關閉",
"connections.filter": "篩選過濾",
"connections.orderBy": "連接排序方式",
"connections.time": "時間",
"connections.uploadAmount": "上傳量",
"connections.downloadAmount": "下載量",
"connections.uploadSpeed": "上傳速度",
"connections.downloadSpeed": "下載速度",
"connections.detail.title": "連接詳情",
"connections.detail.establishTime": "連接建立時間",
"connections.detail.rule": "規則",
"connections.detail.proxyChain": "代理鏈",
"connections.detail.connectionType": "連接類型",
"connections.detail.host": "主機",
"connections.detail.sniffHost": "嗅探主機",
"connections.detail.processName": "進程名",
"connections.detail.processPath": "進程路徑",
"connections.detail.sourceIP": "來源IP",
"connections.detail.sourceGeoIP": "來源GeoIP",
"connections.detail.sourceASN": "來源ASN",
"connections.detail.destinationIP": "目標IP",
"connections.detail.destinationGeoIP": "目標GeoIP",
"connections.detail.destinationASN": "目標ASN",
"connections.detail.sourcePort": "來源埠",
"connections.detail.destinationPort": "目標埠",
"connections.detail.inboundIP": "入站IP",
"connections.detail.inboundPort": "入站埠",
"connections.detail.copyRule": "複製規則",
"connections.detail.inboundName": "入站名稱",
"connections.detail.inboundUser": "入站用戶",
"connections.detail.dscp": "DSCP",
"connections.detail.remoteDestination": "遠程目標",
"connections.detail.dnsMode": "DNS模式",
"connections.detail.specialProxy": "特殊代理",
"connections.detail.specialRules": "特殊規則",
"connections.detail.close": "關閉",
"connections.detail.status": "狀態",
"connections.pause": "暫停",
"connections.resume": "恢復",
"connections.table.switchToTable": "切換到表格視圖",
"connections.table.switchToList": "切換到列表視圖",
"connections.table.columns": "顯示列",
"connections.table.noData": "暫無數據",
"resources.geoData.geoip": "GeoIP 數據庫",
"resources.geoData.geosite": "GeoSite 數據庫",
"resources.geoData.mmdb": "MMDB 數據庫",
"resources.geoData.asn": "ASN 數據庫",
"resources.geoData.mode": "GeoData 數據模式",
"resources.geoData.autoUpdate": "自動更新",
"resources.geoData.updateInterval": "更新間隔(小時)",
"resources.geoData.updateSuccess": "GeoData 更新成功",
"logs.title": "實時日誌",
"logs.filter": "篩選過濾",
"logs.clear": "清空日誌",
"logs.autoScroll": "自動滾動",
"tray.showWindow": "顯示窗口",
"tray.showFloatingWindow": "顯示懸浮窗",
"tray.hideFloatingWindow": "關閉懸浮窗",
"tray.ruleMode": "規則模式",
"tray.globalMode": "全局模式",
"tray.directMode": "直連模式",
"tray.systemProxy": "系統代理",
"tray.tun": "虛擬網卡",
"tray.profiles": "訂閱配置",
"tray.openDirectories.title": "打開目錄",
"tray.openDirectories.appDir": "應用目錄",
"tray.openDirectories.workDir": "工作目錄",
"tray.openDirectories.coreDir": "內核目錄",
"tray.openDirectories.logDir": "日誌目錄",
"tray.copyEnv": "複製環境變數",
"guide.welcome.title": "歡迎使用 Clash Party",
"guide.welcome.description": "這是一份交互式使用教程,如果您已經完全熟悉本軟件的操作,可以直接點擊右上角關閉按鈕,後續您可以隨時從設置中打開本教程",
"guide.sider.title": "導航欄",
"guide.sider.description": "左側是應用的導航欄,兼顧儀錶盤功能,在這裡可以切換不同頁面,也可以概覽常用的狀態信息",
"guide.card.title": "卡片",
"guide.card.description": "點擊導航欄卡片可以跳轉到對應頁面,拖動導航欄卡片可以自由排列卡片順序",
"guide.main.title": "主要區域",
"guide.main.description": "右側是應用的主要區域,展示了導航欄所選頁面的內容",
"guide.profile.title": "訂閱管理",
"guide.profile.description": "訂閱管理卡片展示當前運行的訂閱配置信息,點擊進入訂閱管理頁面可以在這裡管理訂閱配置",
"guide.import.title": "訂閱匯入",
"guide.import.description": "Clash Party 支持多種訂閱匯入方式,在此輸入訂閱連結,點擊匯入即可匯入您的訂閱配置,如果您的訂閱需要代理才能更新,請勾选\"代理\"再點擊匯入,當然這需要已經有一個可以正常使用的訂閱才可以",
"guide.substore.title": "Sub-Store",
"guide.substore.description": "Clash Party 深度集成了 Sub-Store您可以點擊該按鈕進入 Sub-Store 或直接匯入您通過 Sub-Store 管理的訂閱Clash Party 默認使用內置的 Sub-Store 後端,如果您有自建的 Sub-Store 後端,可以在設置頁面中配置,如果您不使用 Sub-Store 也可以在設置頁面中關閉",
"guide.localProfile.title": "本地訂閱",
"guide.localProfile.description": "點擊\"+\"可以選擇本地檔案进行匯入或者直接新建空白配置進行編輯",
"guide.sysproxy.title": "系統代理",
"guide.sysproxy.description": "匯入訂閱之後,內核已經開始運行並監聽指定埠,此时您已經可以通過指定代理埠来使用代理了,如果您要使大部分應用自動使用該埠的代理,您還需要打開系統代理開關",
"guide.sysproxySetting.title": "系統代理設置",
"guide.sysproxySetting.description": "在此您可以進行系統代理相關設置,選擇代理模式,如果某些 Windows 應用不遵循系統代理,还可以使用\"UWP 工具\"解除本地回環限制,對於\"手動代理模式\"和\"PAC 代理模式\"的區別请自行Google",
"guide.tun.title": "虛擬網卡",
"guide.tun.description": "虛擬網卡,即同類軟件中常見的\"Tun 模式\",對於某些不遵循系統代理的應用程式,您可以打開虛擬網卡以讓內核接管所有流量",
"guide.tunSetting.title": "虛擬網卡設置",
"guide.tunSetting.description": "這裡可以更改虛擬網卡相關設置Clash Party 理論上已經完全解決權限問題如果您的虛擬網卡仍然不可用可以嘗試重設防火牆Windows或手動授權內核MacOS/Linux後重啟內核",
"guide.override.title": "覆寫",
"guide.override.description": "Clash Party 提供強大的覆寫功能,可以對您匯入的訂閱配置進行個性化修改,如添加規則、自定義代理組等,您可以直接匯入別人寫好的覆寫檔案,也可以自己動手編寫,<b>編輯好覆寫檔案一定要記得在需要覆寫的訂閱上啟用</b>,覆寫檔案的語法请參考 <a href=\"https://mihomo.party/docs/guide/override\" target=\"_blank\">官方文檔</a>",
"guide.dns.title": "DNS",
"guide.dns.description": "軟件默認使用應用程式的 DNS 設置覆蓋訂閱配置,如果您需要使用訂閱配置中的 DNS 設置,請關閉此功能,域名嗅探同理",
"guide.end.title": "教程結束",
"guide.end.description": "現在您已經瞭解了軟件的基本用法,匯入您的訂閱開始使用吧,祝您使用愉快!\n您還可以加入我們的官方 <a href=\"https://t.me/mihomo_party_group\" target=\"_blank\">Telegram 群組</a> 獲取最新資訊"
}

View File

@ -1,6 +1,6 @@
import { Button, Tab, Input, Switch, Tabs, Divider } from '@heroui/react'
import BasePage from '@renderer/components/base/base-page'
import { toast } from '@renderer/components/base/toast'
import { showErrorSync } from '@renderer/utils/error-display'
import { MdDeleteForever } from 'react-icons/md'
import SettingCard from '@renderer/components/base/base-setting-card'
import SettingItem from '@renderer/components/base/base-setting-item'
@ -146,7 +146,7 @@ const DNS: React.FC = () => {
await restartCore()
}
} catch (e) {
toast.error(String(e))
showErrorSync(e, 'DNS配置保存失败')
}
}

View File

@ -1,6 +1,7 @@
import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, useDisclosure, Spinner, Chip } from '@heroui/react'
import BasePage from '@renderer/components/base/base-page'
import { toast } from '@renderer/components/base/toast'
import { showError } from '@renderer/utils/error-display'
import SettingCard from '@renderer/components/base/base-setting-card'
import SettingItem from '@renderer/components/base/base-setting-item'
import { useAppConfig } from '@renderer/hooks/use-app-config'
@ -14,7 +15,6 @@ import {
restartCore,
startSubStoreBackendServer,
triggerSysProxy,
showDetailedError,
fetchMihomoTags,
installSpecificMihomoCore,
clearMihomoVersionCache
@ -306,11 +306,7 @@ const Mihomo: React.FC = () => {
const errorMessage = e instanceof Error ? e.message : String(e)
console.error('Core restart failed:', errorMessage)
if (errorMessage.includes('配置检查失败') || errorMessage.includes('Profile Check Failed')) {
await showDetailedError(t('mihomo.error.profileCheckFailed'), errorMessage)
} else {
toast.error(errorMessage)
}
await showError(errorMessage, t('mihomo.error.profileCheckFailed'))
} finally {
PubSub.publish('mihomo-core-changed')
}

View File

@ -1,6 +1,6 @@
import { Button, Divider, Input, Switch } from '@heroui/react'
import BasePage from '@renderer/components/base/base-page'
import { toast } from '@renderer/components/base/toast'
import { showErrorSync } from '@renderer/utils/error-display'
import SettingCard from '@renderer/components/base/base-setting-card'
import SettingItem from '@renderer/components/base/base-setting-item'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
@ -71,7 +71,7 @@ const Sniffer: React.FC = () => {
await restartCore()
}
} catch (e) {
toast.error(String(e))
showErrorSync(e, '嵅探配置保存失败')
}
}

View File

@ -1,6 +1,6 @@
import { Button, Input, Tab, Tabs } from '@heroui/react'
import BasePage from '@renderer/components/base/base-page'
import { toast } from '@renderer/components/base/toast'
import { showErrorSync } from '@renderer/utils/error-display'
import SettingCard from '@renderer/components/base/base-setting-card'
import SettingItem from '@renderer/components/base/base-setting-item'
import PacEditorModal from '@renderer/components/sysproxy/pac-editor-modal'
@ -106,7 +106,7 @@ const Sysproxy: React.FC = () => {
} catch (e) {
setValues({ ...values, enable: previousState })
setChanged(true)
toast.error(String(e))
showErrorSync(e, '系统代理设置失败')
await patchAppConfig({ sysProxy: { enable: false } })
}

View File

@ -1,6 +1,6 @@
import { Button, Input, Switch, Tab, Tabs } from '@heroui/react'
import BasePage from '@renderer/components/base/base-page'
import { toast } from '@renderer/components/base/toast'
import { showErrorSync } from '@renderer/utils/error-display'
import SettingCard from '@renderer/components/base/base-setting-card'
import SettingItem from '@renderer/components/base/base-setting-item'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
@ -113,7 +113,7 @@ const Tun: React.FC = () => {
new Notification(t('tun.notifications.firewallResetSuccess'))
await restartCore()
} catch (e) {
toast.error(String(e))
showErrorSync(e, '防火墙设置失败')
} finally {
setLoading(false)
}
@ -134,7 +134,7 @@ const Tun: React.FC = () => {
new Notification(t('tun.notifications.coreAuthSuccess'))
await restartCore()
} catch (e) {
toast.error(String(e))
showErrorSync(e, '内核授权失败')
}
}}
>

View File

@ -0,0 +1,52 @@
import { toast } from '@renderer/components/base/toast'
const DETAILED_ERROR_KEYWORDS = [
'yaml',
'YAML',
'config',
'profile',
'parse',
'syntax',
'invalid',
'failed to',
'connection refused',
'ECONNREFUSED',
'ETIMEDOUT',
'ENOTFOUND',
'certificate',
'SSL',
'TLS',
'Permission denied',
'Access denied',
'配置',
'解析',
'失败',
'权限',
'证书'
]
function shouldShowDetailedError(message: string): boolean {
if (message.length > 80) return true
if (message.includes('\n')) return true
return DETAILED_ERROR_KEYWORDS.some((keyword) => message.includes(keyword))
}
export async function showError(error: unknown, title?: string): Promise<void> {
const message = error instanceof Error ? error.message : String(error)
if (shouldShowDetailedError(message)) {
toast.detailedError(message, title || '错误')
} else {
toast.error(message, title)
}
}
export function showErrorSync(error: unknown, title?: string): void {
const message = error instanceof Error ? error.message : String(error)
if (shouldShowDetailedError(message)) {
toast.detailedError(message, title || '错误')
} else {
toast.error(message, title)
}
}

View File

@ -208,6 +208,10 @@ export async function setProfileStr(id: string, str: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setProfileStr', id, str))
}
export async function convertMrsRuleset(path: string, behavior: string): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('convertMrsRuleset', path, behavior))
}
export async function getOverrideConfig(force = false): Promise<IOverrideConfig> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverrideConfig', force))
}

View File

@ -1,6 +1,7 @@
import i18next from 'i18next'
import enUS from '../renderer/src/locales/en-US.json'
import zhCN from '../renderer/src/locales/zh-CN.json'
import zhTW from '../renderer/src/locales/zh-TW.json'
import ruRU from '../renderer/src/locales/ru-RU.json'
import faIR from '../renderer/src/locales/fa-IR.json'
@ -11,6 +12,9 @@ export const resources = {
'zh-CN': {
translation: zhCN
},
'zh-TW': {
translation: zhTW
},
'ru-RU': {
translation: ruRU
},
@ -36,4 +40,4 @@ export const initI18n = async (options = {}): Promise<typeof i18next> => {
return i18next
}
export default i18next
export default i18next

View File

@ -313,7 +313,7 @@ interface IAppConfig {
directModeShortcut?: string
restartAppShortcut?: string
quitWithoutCoreShortcut?: string
language?: 'zh-CN' | 'en-US' | 'ru-RU' | 'fa-IR'
language?: 'zh-CN' | 'zh-TW' | 'en-US' | 'ru-RU' | 'fa-IR'
triggerMainWindowBehavior?: 'show' | 'toggle'
showMixedPort?: number
enableMixedPort?: boolean