mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +08:00
Compare commits
4 Commits
ddd0077a61
...
7a79adef2e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a79adef2e | ||
|
|
94f52cf636 | ||
|
|
19ae63b253 | ||
|
|
34fdd21878 |
@ -14,7 +14,8 @@ export {
|
||||
getProfileStr,
|
||||
setProfileStr,
|
||||
changeCurrentProfile,
|
||||
updateProfileItem
|
||||
updateProfileItem,
|
||||
convertMrsRuleset
|
||||
} from './profile'
|
||||
export {
|
||||
getOverrideConfig,
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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,7 +125,6 @@ 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')}
|
||||
@ -135,7 +137,8 @@ const RuleProvider: React.FC = () => {
|
||||
path: provider.name,
|
||||
type: provider.vehicleType,
|
||||
title: provider.name,
|
||||
format: provider.format
|
||||
format: provider.format,
|
||||
behavior: provider.behavior || 'domain'
|
||||
})
|
||||
}}
|
||||
>
|
||||
@ -145,7 +148,6 @@ const RuleProvider: React.FC = () => {
|
||||
<CgLoadbarDoc className={`text-lg`} />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
isIconOnly
|
||||
title={t('common.updater.update')}
|
||||
|
||||
@ -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,15 +13,38 @@ 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> => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
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
|
||||
}
|
||||
|
||||
if (type === 'Inline') {
|
||||
fileContent = await getFileStr('config.yaml')
|
||||
language = 'yaml'
|
||||
@ -42,6 +65,9 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
} catch (error) {
|
||||
setCurrData(fileContent)
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -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">
|
||||
{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'}
|
||||
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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
"common.auto": "خودکار",
|
||||
"common.default": "پیشفرض",
|
||||
"common.close": "بستن",
|
||||
"common.loading": "در حال بارگذاری...",
|
||||
"common.pinWindow": "پین کردن پنجره",
|
||||
"common.next": "بعدی",
|
||||
"common.prev": "قبلی",
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
"common.auto": "Авто",
|
||||
"common.default": "По умолчанию",
|
||||
"common.close": "Закрыть",
|
||||
"common.loading": "Загрузка...",
|
||||
"common.pinWindow": "Закрепить окно",
|
||||
"common.next": "Далее",
|
||||
"common.prev": "Назад",
|
||||
|
||||
@ -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": "策略模式",
|
||||
|
||||
632
src/renderer/src/locales/zh-TW.json
Normal file
632
src/renderer/src/locales/zh-TW.json
Normal 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> 獲取最新資訊"
|
||||
}
|
||||
@ -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配置保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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')
|
||||
}
|
||||
|
||||
@ -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, '嵅探配置保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 } })
|
||||
}
|
||||
|
||||
@ -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, '内核授权失败')
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
52
src/renderer/src/utils/error-display.ts
Normal file
52
src/renderer/src/utils/error-display.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
2
src/shared/types.d.ts
vendored
2
src/shared/types.d.ts
vendored
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user