Compare commits
95 Commits
@@ -1,4 +1 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
pnpm pretty-quick --staged
|
pnpm pretty-quick --staged
|
||||||
|
|||||||
73
UPDATELOG.md
73
UPDATELOG.md
@@ -1,12 +1,77 @@
|
|||||||
## v2.0.1
|
## v2.0.3
|
||||||
|
|
||||||
### Notice
|
### Notice
|
||||||
|
|
||||||
- 强烈建议完全删除 1.x 老版本再安装此版本!
|
- !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
|
||||||
|
- 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了:巨量改进与性能、稳定性提升,目前Clash Verge Rev已经有了比肩cfw的健壮性;而且更强大易用!
|
||||||
|
- 由于更改了服务安装逻辑,每次更新安装需要输入系统密码卸载老版本服务和安装新版本服务,以后可以丝滑使用tun(虚拟网卡)模式
|
||||||
|
|
||||||
|
### 2.0.3相对于2.0.2改进修复了:
|
||||||
|
|
||||||
|
1. 修复VLess-URL识别网络类型错误 f400f90 #2126
|
||||||
|
2. 新增系统代理绕过文本校验 c71e18e
|
||||||
|
3. 修复脚本编辑器UI显示不正确 6197249 #2267
|
||||||
|
4. 修复Shift热键无效 589324b #2278
|
||||||
|
5. 新增nushell环境变量复制 d233a84
|
||||||
|
6. 修复全局扩展脚本无法覆写DNS d22b37c #2235
|
||||||
|
7. 切换到系统代理相对于稳定的版本 38745d4
|
||||||
|
8. 修改fake-ip-range网段 0e3b631
|
||||||
|
9. 修复窗口隐藏后WebSocket未断开连接,减小内存风险 b42d13f
|
||||||
|
10. 改进系统代理绕过设置 c5c840d
|
||||||
|
11. 修复i18n翻译文本缺失 b149084
|
||||||
|
12. 修复双击托盘图标打开面板 f839d3b #2346
|
||||||
|
13. 修复Windows10窗口白色边框 4f6ca40 #2425
|
||||||
|
14. 修复Windows窗口状态恢复 4f6ca40
|
||||||
|
15. 改进保存配置文件自动重启Mihomo内核 0669f7a
|
||||||
|
16. 改进更新托盘图标性能 d9291d4
|
||||||
|
17. 修复保存配置后代理列表未更新 542baf9 #2460
|
||||||
|
18. 新增MacOS托盘显示实时速率,可在"界面设置"中关闭 1b2f1b6
|
||||||
|
19. 新增托盘菜单显示已设置的快捷键 eeff4d4
|
||||||
|
20. 新增重载配置文件错误响应"400"时显示更多错误信息 c5989d2 #2492
|
||||||
|
21. 修复GUI代理状态与菜单显示不一致 13b63b5 #2502
|
||||||
|
22. 新增默认语言跟随系统语言(无语言支持即为英语),添加了阿拉伯语、印尼语、鞑靼语支持 9655f77 #2940
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Meta(mihomo)内核升级 1.19.1
|
||||||
|
- 增加更多语言和托盘语言跟随
|
||||||
|
- MacOS增加状态栏速率显示
|
||||||
|
- 托盘显示快捷键
|
||||||
|
- 重载配置文件错误响应"400"时显示更多错误信息
|
||||||
|
- 改进保存配置文件自动重启Mihomo内核
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- 改进更新托盘图标性能
|
||||||
|
- 窗口隐藏后WebSocket断开连接
|
||||||
|
|
||||||
|
## v2.0.2
|
||||||
|
|
||||||
|
### Notice
|
||||||
|
|
||||||
|
- !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
|
||||||
- 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了:巨量改进与性能、稳定性提升,目前Clash Verge Rev已经有了比肩cfw的健壮性;而且更强大易用!
|
- 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了:巨量改进与性能、稳定性提升,目前Clash Verge Rev已经有了比肩cfw的健壮性;而且更强大易用!
|
||||||
- 由于更改了服务安装逻辑,Mac/Linux 首次安装需要输入系统密码卸载和安装服务,以后可以丝滑使用 tun(虚拟网卡)模式
|
- 由于更改了服务安装逻辑,Mac/Linux 首次安装需要输入系统密码卸载和安装服务,以后可以丝滑使用 tun(虚拟网卡)模式
|
||||||
- 因 Tauri 2.0 底层 bug,关闭窗口后保留webview进程,优点是再次打开面板更快,缺点是内存使用略有增加
|
- 因 Tauri 2.0 底层 bug,关闭窗口后保留webview进程,优点是再次打开面板更快,缺点是内存使用略有增加
|
||||||
|
|
||||||
|
### 2.0.2相对于2.0.1改进了:
|
||||||
|
|
||||||
|
- MacOS 下自定义图标可以支持彩色、单色切换
|
||||||
|
- 修正了 Linux 下多个内核僵尸进程的问题
|
||||||
|
- 修正了 DNS ipv6 强制覆盖的逻辑
|
||||||
|
- 修改了 MacOS tun 模式下覆盖设置 dns 字段的问题
|
||||||
|
- 修正了 MacOS tray 图标不会随代理模式更改的问题
|
||||||
|
- 静默启动下重复运行会出现多个实例的bug
|
||||||
|
- 安装的时候自动删除历史残留启动项
|
||||||
|
- Tun模式默认是还用内核推荐的 mixed 堆栈
|
||||||
|
- 改进了默认窗口大小(启动软件窗口不会那么小了)
|
||||||
|
- 改进了 WebDAV 备份超时时间机制
|
||||||
|
- 测试菜单添加滚动条
|
||||||
|
- 改进和修正了 Tun 模式下对设置的覆盖逻辑
|
||||||
|
- 修复了打开配置出错的问题
|
||||||
|
- 修复了配置文件无法拖拽添加的问题
|
||||||
|
- 改善了浅色模式的对比度
|
||||||
|
|
||||||
### 2.0.1相对于2.0.0改进了:
|
### 2.0.1相对于2.0.0改进了:
|
||||||
|
|
||||||
- 无法从 2.0rc和2.0.0 升级的问题(已经安装了2.0版本的需手动下载安装)
|
- 无法从 2.0rc和2.0.0 升级的问题(已经安装了2.0版本的需手动下载安装)
|
||||||
@@ -24,7 +89,7 @@
|
|||||||
|
|
||||||
- 重大框架升级:使用 Tauri 2.0(巨量改进与性能提升)
|
- 重大框架升级:使用 Tauri 2.0(巨量改进与性能提升)
|
||||||
- 出现 bug 到 issues 中提出;以后不再接受1.x版本的bug反馈。
|
- 出现 bug 到 issues 中提出;以后不再接受1.x版本的bug反馈。
|
||||||
- 强烈建议完全删除 1.x 老版本再安装此版本
|
- 强烈建议完全删除 1.x 老版本再安装此版本 !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
@@ -75,7 +140,7 @@
|
|||||||
### Known issues
|
### Known issues
|
||||||
|
|
||||||
- Windows 下窗口大小无法记忆(等待上游修复)
|
- Windows 下窗口大小无法记忆(等待上游修复)
|
||||||
- Webdav 备份因为安全性和兼容性问题,暂不支持同步 Webdav 服务器地址和登录信息;跨平台配置同步
|
- Webdav 备份因为安全性和兼容性问题,暂不支持跨平台配置同步
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
76
package.json
76
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clash-verge",
|
"name": "clash-verge",
|
||||||
"version": "2.0.1",
|
"version": "2.0.3",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env RUST_BACKTRACE=1 tauri dev",
|
"dev": "cross-env RUST_BACKTRACE=1 tauri dev",
|
||||||
@@ -18,55 +18,55 @@
|
|||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.1.0",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@juggle/resize-observer": "^3.4.0",
|
"@juggle/resize-observer": "^3.4.0",
|
||||||
"@mui/icons-material": "^6.1.6",
|
"@mui/icons-material": "^6.3.0",
|
||||||
"@mui/lab": "5.0.0-alpha.149",
|
"@mui/lab": "5.0.0-alpha.149",
|
||||||
"@mui/material": "^6.1.6",
|
"@mui/material": "^6.3.0",
|
||||||
"@mui/x-data-grid": "^7.22.2",
|
"@mui/x-data-grid": "^7.23.5",
|
||||||
"@tauri-apps/api": "2.1.1",
|
"@tauri-apps/api": "2.1.1",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "2.0.0",
|
"@tauri-apps/plugin-clipboard-manager": "^2.2.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.0.1",
|
"@tauri-apps/plugin-dialog": "^2.2.0",
|
||||||
"@tauri-apps/plugin-fs": "^2.0.2",
|
"@tauri-apps/plugin-fs": "^2.2.0",
|
||||||
"@tauri-apps/plugin-global-shortcut": "^2.0.0",
|
"@tauri-apps/plugin-global-shortcut": "^2.2.0",
|
||||||
"@tauri-apps/plugin-notification": "^2.0.0",
|
"@tauri-apps/plugin-notification": "^2.2.0",
|
||||||
"@tauri-apps/plugin-process": "^2.0.0",
|
"@tauri-apps/plugin-process": "^2.2.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.0.1",
|
"@tauri-apps/plugin-shell": "2.2.0",
|
||||||
"@tauri-apps/plugin-updater": "^2.0.0",
|
"@tauri-apps/plugin-updater": "2.3.0",
|
||||||
"@types/json-schema": "^7.0.15",
|
"@types/json-schema": "^7.0.15",
|
||||||
"ahooks": "^3.8.1",
|
"ahooks": "^3.8.4",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.9",
|
||||||
"cli-color": "^2.0.4",
|
"cli-color": "^2.0.4",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"foxact": "^0.2.41",
|
"foxact": "^0.2.43",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
"i18next": "^23.16.5",
|
"i18next": "^23.16.8",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"monaco-editor": "^0.52.0",
|
"monaco-editor": "^0.52.2",
|
||||||
"monaco-yaml": "^5.2.3",
|
"monaco-yaml": "^5.2.3",
|
||||||
"nanoid": "^5.0.8",
|
"nanoid": "^5.0.9",
|
||||||
"peggy": "^4.1.1",
|
"peggy": "^4.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-error-boundary": "^4.1.2",
|
"react-error-boundary": "^4.1.2",
|
||||||
"react-hook-form": "^7.53.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-i18next": "^15.1.1",
|
"react-i18next": "^15.4.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-monaco-editor": "^0.56.2",
|
"react-monaco-editor": "^0.56.2",
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router-dom": "^6.28.1",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"react-virtuoso": "^4.12.0",
|
"react-virtuoso": "^4.12.3",
|
||||||
"sockette": "^2.0.6",
|
"sockette": "^2.0.6",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.3.0",
|
||||||
"tar": "^7.4.3",
|
"tar": "^7.4.3",
|
||||||
"types-pac": "^1.0.3",
|
"types-pac": "^1.0.3",
|
||||||
"zustand": "^5.0.1"
|
"zustand": "^5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/github": "^6.0.0",
|
"@actions/github": "^6.0.0",
|
||||||
@@ -74,22 +74,22 @@
|
|||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.18",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.5",
|
||||||
"@types/react-transition-group": "^4.4.11",
|
"@types/react-transition-group": "^4.4.12",
|
||||||
"@vitejs/plugin-legacy": "^5.4.3",
|
"@vitejs/plugin-legacy": "^5.4.3",
|
||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"https-proxy-agent": "^7.0.5",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"meta-json-schema": "^1.18.10",
|
"meta-json-schema": "^1.19.1",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.4.2",
|
||||||
"pretty-quick": "^4.0.0",
|
"pretty-quick": "^4.0.0",
|
||||||
"sass": "^1.81.0",
|
"sass": "^1.83.0",
|
||||||
"terser": "^5.36.0",
|
"terser": "^5.37.0",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^5.4.11",
|
"vite": "^5.4.11",
|
||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vite-plugin-svgr": "^4.3.0"
|
"vite-plugin-svgr": "^4.3.0"
|
||||||
|
|||||||
1685
pnpm-lock.yaml
generated
1685
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -398,6 +398,34 @@ const resolveServicePermission = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 在 resolveResource 函数后添加新函数
|
||||||
|
async function resolveLocales() {
|
||||||
|
const srcLocalesDir = path.join(cwd, "src/locales");
|
||||||
|
const targetLocalesDir = path.join(cwd, "src-tauri/resources/locales");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 确保目标目录存在
|
||||||
|
await fsp.mkdir(targetLocalesDir, { recursive: true });
|
||||||
|
|
||||||
|
// 读取所有语言文件
|
||||||
|
const files = await fsp.readdir(srcLocalesDir);
|
||||||
|
|
||||||
|
// 复制每个文件
|
||||||
|
for (const file of files) {
|
||||||
|
const srcPath = path.join(srcLocalesDir, file);
|
||||||
|
const targetPath = path.join(targetLocalesDir, file);
|
||||||
|
|
||||||
|
await fsp.copyFile(srcPath, targetPath);
|
||||||
|
log_success(`Copied locale file: ${file}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success("All locale files copied successfully");
|
||||||
|
} catch (err) {
|
||||||
|
log_error("Error copying locale files:", err.message);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* main
|
* main
|
||||||
*/
|
*/
|
||||||
@@ -488,8 +516,8 @@ const tasks = [
|
|||||||
{
|
{
|
||||||
name: "service_chmod",
|
name: "service_chmod",
|
||||||
func: resolveServicePermission,
|
func: resolveServicePermission,
|
||||||
retry: 1,
|
retry: 5,
|
||||||
unixOnly: true,
|
unixOnly: platform === "linux" || platform === "darwin",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "windows-sysproxy",
|
name: "windows-sysproxy",
|
||||||
@@ -509,15 +537,20 @@ const tasks = [
|
|||||||
retry: 5,
|
retry: 5,
|
||||||
macosOnly: true,
|
macosOnly: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "locales",
|
||||||
|
func: resolveLocales,
|
||||||
|
retry: 2,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
async function runTask() {
|
async function runTask() {
|
||||||
const task = tasks.shift();
|
const task = tasks.shift();
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
if (task.winOnly && platform !== "win32") return runTask();
|
|
||||||
if (task.linuxOnly && platform !== "linux") return runTask();
|
|
||||||
if (task.unixOnly && platform === "win32") return runTask();
|
if (task.unixOnly && platform === "win32") return runTask();
|
||||||
|
if (task.winOnly && platform !== "win32") return runTask();
|
||||||
if (task.macosOnly && platform !== "darwin") return runTask();
|
if (task.macosOnly && platform !== "darwin") return runTask();
|
||||||
|
if (task.linuxOnly && platform !== "linux") return runTask();
|
||||||
|
|
||||||
for (let i = 0; i < task.retry; i++) {
|
for (let i = 0; i < task.retry; i++) {
|
||||||
try {
|
try {
|
||||||
@@ -532,4 +565,3 @@ async function runTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runTask();
|
runTask();
|
||||||
runTask();
|
|
||||||
|
|||||||
700
src-tauri/Cargo.lock
generated
700
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "clash-verge"
|
name = "clash-verge"
|
||||||
version = "2.0.1"
|
version = "2.0.3"
|
||||||
description = "clash verge"
|
description = "clash verge"
|
||||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
@@ -9,6 +9,9 @@ default-run = "clash-verge"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
[package.metadata.bundle]
|
||||||
|
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.0.3", features = [] }
|
tauri-build = { version = "2.0.3", features = [] }
|
||||||
|
|
||||||
@@ -22,8 +25,8 @@ dunce = "1.0"
|
|||||||
log4rs = "1"
|
log4rs = "1"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
sysinfo = "0.32.0"
|
sysinfo = "0.33.0"
|
||||||
boa_engine = "0.19.1"
|
boa_engine = "0.20.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
once_cell = "1.19"
|
once_cell = "1.19"
|
||||||
@@ -35,7 +38,10 @@ window-shadows = { version = "0.2.2" }
|
|||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
|
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
|
||||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", branch = "main" }
|
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", rev = "3d748b5" }
|
||||||
|
image = "0.24"
|
||||||
|
imageproc = "0.23"
|
||||||
|
rusttype = "0.9"
|
||||||
tauri = { version = "2.1.1", features = [
|
tauri = { version = "2.1.1", features = [
|
||||||
"protocol-asset",
|
"protocol-asset",
|
||||||
"devtools",
|
"devtools",
|
||||||
@@ -44,13 +50,13 @@ tauri = { version = "2.1.1", features = [
|
|||||||
"image-png",
|
"image-png",
|
||||||
] }
|
] }
|
||||||
network-interface = { version = "2.0.0", features = ["serde"] }
|
network-interface = { version = "2.0.0", features = ["serde"] }
|
||||||
tauri-plugin-shell = "2.0.2"
|
tauri-plugin-shell = "2.2.0"
|
||||||
tauri-plugin-dialog = "2.0.2"
|
tauri-plugin-dialog = "2.2.0"
|
||||||
tauri-plugin-fs = "2.0.2"
|
tauri-plugin-fs = "2.2.0"
|
||||||
tauri-plugin-notification = "2.0.1"
|
tauri-plugin-notification = "2.2.0"
|
||||||
tauri-plugin-process = "2.0.1"
|
tauri-plugin-process = "2.2.0"
|
||||||
tauri-plugin-clipboard-manager = "2.0.1"
|
tauri-plugin-clipboard-manager = "2.2.0"
|
||||||
tauri-plugin-deep-link = "2.0.1"
|
tauri-plugin-deep-link = "2.2.0"
|
||||||
tauri-plugin-devtools = "2.0.0-rc"
|
tauri-plugin-devtools = "2.0.0-rc"
|
||||||
url = "2.5.2"
|
url = "2.5.2"
|
||||||
zip = "2.2.0"
|
zip = "2.2.0"
|
||||||
@@ -58,6 +64,9 @@ reqwest_dav = "0.1.14"
|
|||||||
aes-gcm = { version = "0.10.3", features = ["std"] }
|
aes-gcm = { version = "0.10.3", features = ["std"] }
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
getrandom = "0.2"
|
getrandom = "0.2"
|
||||||
|
tokio-tungstenite = "0.26.1"
|
||||||
|
futures = "0.3"
|
||||||
|
sys-locale = "0.3.1"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
runas = "=1.2.0"
|
runas = "=1.2.0"
|
||||||
@@ -70,10 +79,10 @@ url = "2.5.2"
|
|||||||
users = "0.11.0"
|
users = "0.11.0"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-autostart = "2.0.0-rc"
|
tauri-plugin-autostart = "2.2.0"
|
||||||
tauri-plugin-global-shortcut = "2.0.1"
|
tauri-plugin-global-shortcut = "2.2.0"
|
||||||
tauri-plugin-updater = "2.0.2"
|
tauri-plugin-updater = "2.3.0"
|
||||||
tauri-plugin-window-state = "2.0.2"
|
tauri-plugin-window-state = "2.2.0"
|
||||||
#openssl
|
#openssl
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
BIN
src-tauri/assets/fonts/SFCompact.ttf
Normal file
BIN
src-tauri/assets/fonts/SFCompact.ttf
Normal file
Binary file not shown.
@@ -16,6 +16,12 @@
|
|||||||
"identifier": "fs:scope",
|
"identifier": "fs:scope",
|
||||||
"allow": ["$APPDATA/**", "$RESOURCE/../**", "**"]
|
"allow": ["$APPDATA/**", "$RESOURCE/../**", "**"]
|
||||||
},
|
},
|
||||||
|
"fs:allow-app-read",
|
||||||
|
"fs:allow-app-read-recursive",
|
||||||
|
"fs:allow-appcache-read",
|
||||||
|
"fs:allow-appcache-read-recursive",
|
||||||
|
"fs:allow-appconfig-read",
|
||||||
|
"fs:allow-appconfig-read-recursive",
|
||||||
"core:window:allow-create",
|
"core:window:allow-create",
|
||||||
"core:window:allow-center",
|
"core:window:allow-center",
|
||||||
"core:window:allow-request-user-attention",
|
"core:window:allow-request-user-attention",
|
||||||
|
|||||||
14
src-tauri/packages/macos/entitlements.plist
Normal file
14
src-tauri/packages/macos/entitlements.plist
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<false/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>io.github.clash-verge-rev.clash-verge-rev</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.security.inherit</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -766,6 +766,34 @@ Section Install
|
|||||||
!insertmacro CheckIfAppIsRunning
|
!insertmacro CheckIfAppIsRunning
|
||||||
!insertmacro CheckAllVergeProcesses
|
!insertmacro CheckAllVergeProcesses
|
||||||
|
|
||||||
|
; 清理自启动注册表项
|
||||||
|
DetailPrint "Cleaning auto-launch registry entries..."
|
||||||
|
|
||||||
|
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||||
|
|
||||||
|
SetRegView 64
|
||||||
|
; 清理旧版本的注册表项 (Clash Verge)
|
||||||
|
ReadRegStr $R2 HKCU "$R1" "Clash Verge"
|
||||||
|
${If} $R2 != ""
|
||||||
|
DeleteRegValue HKCU "$R1" "Clash Verge"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
ReadRegStr $R2 HKLM "$R1" "Clash Verge"
|
||||||
|
${If} $R2 != ""
|
||||||
|
DeleteRegValue HKLM "$R1" "Clash Verge"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
; 清理新版本的注册表项 (clash-verge)
|
||||||
|
ReadRegStr $R2 HKCU "$R1" "clash-verge"
|
||||||
|
${If} $R2 != ""
|
||||||
|
DeleteRegValue HKCU "$R1" "clash-verge"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
ReadRegStr $R2 HKLM "$R1" "clash-verge"
|
||||||
|
${If} $R2 != ""
|
||||||
|
DeleteRegValue HKLM "$R1" "clash-verge"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
; Delete old files before installation
|
; Delete old files before installation
|
||||||
; Delete clash-verge.desktop
|
; Delete clash-verge.desktop
|
||||||
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
|
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
|
||||||
@@ -891,6 +919,35 @@ Section Uninstall
|
|||||||
!insertmacro CheckIfAppIsRunning
|
!insertmacro CheckIfAppIsRunning
|
||||||
!insertmacro CheckAllVergeProcesses
|
!insertmacro CheckAllVergeProcesses
|
||||||
!insertmacro RemoveVergeService
|
!insertmacro RemoveVergeService
|
||||||
|
|
||||||
|
; 清理自启动注册表项
|
||||||
|
DetailPrint "Cleaning auto-launch registry entries..."
|
||||||
|
|
||||||
|
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
|
||||||
|
|
||||||
|
SetRegView 64
|
||||||
|
; 清理旧版本的注册表项 (Clash Verge)
|
||||||
|
ReadRegStr $R2 HKCU "$R1" "Clash Verge"
|
||||||
|
${If} $R2 != ""
|
||||||
|
DeleteRegValue HKCU "$R1" "Clash Verge"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
ReadRegStr $R2 HKLM "$R1" "Clash Verge"
|
||||||
|
${If} $R2 != ""
|
||||||
|
DeleteRegValue HKLM "$R1" "Clash Verge"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
; 清理新版本的注册表项 (clash-verge)
|
||||||
|
ReadRegStr $R2 HKCU "$R1" "clash-verge"
|
||||||
|
${If} $R2 != ""
|
||||||
|
DeleteRegValue HKCU "$R1" "clash-verge"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
ReadRegStr $R2 HKLM "$R1" "clash-verge"
|
||||||
|
${If} $R2 != ""
|
||||||
|
DeleteRegValue HKLM "$R1" "clash-verge"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
; Delete the app directory and its content from disk
|
; Delete the app directory and its content from disk
|
||||||
; Copy main executable
|
; Copy main executable
|
||||||
Delete "$INSTDIR\${MAINBINARYNAME}.exe"
|
Delete "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
feat,
|
feat,
|
||||||
utils::{dirs, help},
|
utils::{dirs, help},
|
||||||
};
|
};
|
||||||
use crate::{ret_err, wrap_err};
|
use crate::{log_err, ret_err, wrap_err};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use network_interface::NetworkInterface;
|
use network_interface::NetworkInterface;
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
@@ -28,6 +28,7 @@ pub fn get_profiles() -> CmdResult<IProfiles> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn enhance_profiles() -> CmdResult {
|
pub async fn enhance_profiles() -> CmdResult {
|
||||||
wrap_err!(CoreManager::global().update_config().await)?;
|
wrap_err!(CoreManager::global().update_config().await)?;
|
||||||
|
log_err!(tray::Tray::global().update_tooltip());
|
||||||
handle::Handle::refresh_clash();
|
handle::Handle::refresh_clash();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult {
|
|||||||
match CoreManager::global().update_config().await {
|
match CoreManager::global().update_config().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
handle::Handle::refresh_clash();
|
handle::Handle::refresh_clash();
|
||||||
let _ = handle::Handle::update_systray_part();
|
let _ = tray::Tray::global().update_tooltip();
|
||||||
Config::profiles().apply();
|
Config::profiles().apply();
|
||||||
wrap_err!(Config::profiles().data().save_file())?;
|
wrap_err!(Config::profiles().data().save_file())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ impl IClashTemp {
|
|||||||
let mut map = Mapping::new();
|
let mut map = Mapping::new();
|
||||||
let mut tun = Mapping::new();
|
let mut tun = Mapping::new();
|
||||||
tun.insert("enable".into(), false.into());
|
tun.insert("enable".into(), false.into());
|
||||||
tun.insert("stack".into(), "gvisor".into());
|
tun.insert("stack".into(), "mixed".into());
|
||||||
tun.insert("auto-route".into(), true.into());
|
tun.insert("auto-route".into(), true.into());
|
||||||
tun.insert("strict-route".into(), false.into());
|
tun.insert("strict-route".into(), false.into());
|
||||||
tun.insert("auto-detect-interface".into(), true.into());
|
tun.insert("auto-detect-interface".into(), true.into());
|
||||||
@@ -156,17 +156,20 @@ impl IClashTemp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn guard_mixed_port(config: &Mapping) -> u16 {
|
pub fn guard_mixed_port(config: &Mapping) -> u16 {
|
||||||
let mut port = config
|
let raw_value = config.get("mixed-port");
|
||||||
.get("mixed-port")
|
|
||||||
|
let mut port = raw_value
|
||||||
.and_then(|value| match value {
|
.and_then(|value| match value {
|
||||||
Value::String(val_str) => val_str.parse().ok(),
|
Value::String(val_str) => val_str.parse().ok(),
|
||||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.unwrap_or(7897);
|
.unwrap_or(7897);
|
||||||
|
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
port = 7897;
|
port = 7897;
|
||||||
}
|
}
|
||||||
|
|
||||||
port
|
port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::config::DEFAULT_PAC;
|
use crate::config::DEFAULT_PAC;
|
||||||
use crate::config::{deserialize_encrypted, serialize_encrypted};
|
use crate::config::{deserialize_encrypted, serialize_encrypted};
|
||||||
|
use crate::utils::i18n;
|
||||||
use crate::utils::{dirs, help};
|
use crate::utils::{dirs, help};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
@@ -175,6 +176,8 @@ pub struct IVerge {
|
|||||||
default
|
default
|
||||||
)]
|
)]
|
||||||
pub webdav_password: Option<String>,
|
pub webdav_password: Option<String>,
|
||||||
|
|
||||||
|
pub enable_tray_speed: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
@@ -202,6 +205,21 @@ pub struct IVergeTheme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IVerge {
|
impl IVerge {
|
||||||
|
fn get_system_language() -> String {
|
||||||
|
let sys_lang = sys_locale::get_locale()
|
||||||
|
.unwrap_or_else(|| String::from("en"))
|
||||||
|
.to_lowercase();
|
||||||
|
|
||||||
|
let lang_code = sys_lang.split(['_', '-']).next().unwrap_or("en");
|
||||||
|
let supported_languages = i18n::get_supported_languages();
|
||||||
|
|
||||||
|
if supported_languages.contains(&lang_code.to_string()) {
|
||||||
|
lang_code.to_string()
|
||||||
|
} else {
|
||||||
|
String::from("en")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
match dirs::verge_path().and_then(|path| help::read_yaml::<IVerge>(&path)) {
|
match dirs::verge_path().and_then(|path| help::read_yaml::<IVerge>(&path)) {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
@@ -215,7 +233,7 @@ impl IVerge {
|
|||||||
pub fn template() -> Self {
|
pub fn template() -> Self {
|
||||||
Self {
|
Self {
|
||||||
clash_core: Some("verge-mihomo".into()),
|
clash_core: Some("verge-mihomo".into()),
|
||||||
language: Some("zh".into()),
|
language: Some(Self::get_system_language()),
|
||||||
theme_mode: Some("system".into()),
|
theme_mode: Some("system".into()),
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
env_type: Some("bash".into()),
|
env_type: Some("bash".into()),
|
||||||
@@ -260,6 +278,7 @@ impl IVerge {
|
|||||||
webdav_url: None,
|
webdav_url: None,
|
||||||
webdav_username: None,
|
webdav_username: None,
|
||||||
webdav_password: None,
|
webdav_password: None,
|
||||||
|
enable_tray_speed: Some(true),
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,6 +358,7 @@ impl IVerge {
|
|||||||
patch!(webdav_url);
|
patch!(webdav_url);
|
||||||
patch!(webdav_username);
|
patch!(webdav_username);
|
||||||
patch!(webdav_password);
|
patch!(webdav_password);
|
||||||
|
patch!(enable_tray_speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 在初始化前尝试拿到单例端口的值
|
/// 在初始化前尝试拿到单例端口的值
|
||||||
@@ -425,6 +445,7 @@ pub struct IVergeResponse {
|
|||||||
pub webdav_url: Option<String>,
|
pub webdav_url: Option<String>,
|
||||||
pub webdav_username: Option<String>,
|
pub webdav_username: Option<String>,
|
||||||
pub webdav_password: Option<String>,
|
pub webdav_password: Option<String>,
|
||||||
|
pub enable_tray_speed: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<IVerge> for IVergeResponse {
|
impl From<IVerge> for IVergeResponse {
|
||||||
@@ -485,6 +506,7 @@ impl From<IVerge> for IVergeResponse {
|
|||||||
webdav_url: verge.webdav_url,
|
webdav_url: verge.webdav_url,
|
||||||
webdav_username: verge.webdav_username,
|
webdav_username: verge.webdav_username,
|
||||||
webdav_password: verge.webdav_password,
|
webdav_password: verge.webdav_password,
|
||||||
|
enable_tray_speed: verge.enable_tray_speed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,114 +4,184 @@ use anyhow::Error;
|
|||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use reqwest_dav::list_cmd::{ListEntity, ListFile};
|
use reqwest_dav::list_cmd::{ListEntity, ListFile};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::env::{consts::OS, temp_dir};
|
use std::env::{consts::OS, temp_dir};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time::timeout;
|
||||||
use zip::write::SimpleFileOptions;
|
use zip::write::SimpleFileOptions;
|
||||||
|
|
||||||
|
const TIMEOUT_UPLOAD: u64 = 300; // 上传超时 5 分钟
|
||||||
|
const TIMEOUT_DOWNLOAD: u64 = 300; // 下载超时 5 分钟
|
||||||
|
const TIMEOUT_LIST: u64 = 3; // 列表超时 30 秒
|
||||||
|
const TIMEOUT_DELETE: u64 = 3; // 删除超时 30 秒
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct WebDavConfig {
|
||||||
|
url: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
||||||
|
enum Operation {
|
||||||
|
Upload,
|
||||||
|
Download,
|
||||||
|
List,
|
||||||
|
Delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operation {
|
||||||
|
fn timeout(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
Operation::Upload => TIMEOUT_UPLOAD,
|
||||||
|
Operation::Download => TIMEOUT_DOWNLOAD,
|
||||||
|
Operation::List => TIMEOUT_LIST,
|
||||||
|
Operation::Delete => TIMEOUT_DELETE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WebDavClient {
|
pub struct WebDavClient {
|
||||||
client: Arc<Mutex<Option<reqwest_dav::Client>>>,
|
config: Arc<Mutex<Option<WebDavConfig>>>,
|
||||||
|
clients: Arc<Mutex<HashMap<Operation, reqwest_dav::Client>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebDavClient {
|
impl WebDavClient {
|
||||||
pub fn global() -> &'static WebDavClient {
|
pub fn global() -> &'static WebDavClient {
|
||||||
static WEBDAV_CLIENT: OnceCell<WebDavClient> = OnceCell::new();
|
static WEBDAV_CLIENT: OnceCell<WebDavClient> = OnceCell::new();
|
||||||
WEBDAV_CLIENT.get_or_init(|| WebDavClient {
|
WEBDAV_CLIENT.get_or_init(|| WebDavClient {
|
||||||
client: Arc::new(Mutex::new(None)),
|
config: Arc::new(Mutex::new(None)),
|
||||||
|
clients: Arc::new(Mutex::new(HashMap::new())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_client(&self) -> Result<reqwest_dav::Client, Error> {
|
async fn get_client(&self, op: Operation) -> Result<reqwest_dav::Client, Error> {
|
||||||
if self.client.lock().is_none() {
|
// 先尝试从缓存获取
|
||||||
let verge = Config::verge().latest().clone();
|
{
|
||||||
if verge.webdav_url.is_none()
|
let clients = self.clients.lock();
|
||||||
|| verge.webdav_username.is_none()
|
if let Some(client) = clients.get(&op) {
|
||||||
|| verge.webdav_password.is_none()
|
return Ok(client.clone());
|
||||||
{
|
|
||||||
let msg =
|
|
||||||
"Unable to create web dav client, please make sure the webdav config is correct"
|
|
||||||
.to_string();
|
|
||||||
log::error!(target: "app","{}",msg);
|
|
||||||
return Err(anyhow::Error::msg(msg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = verge.webdav_url.unwrap_or_default();
|
|
||||||
let username = verge.webdav_username.unwrap_or_default();
|
|
||||||
let password = verge.webdav_password.unwrap_or_default();
|
|
||||||
let url = url.trim_end_matches('/');
|
|
||||||
let client = reqwest_dav::ClientBuilder::new()
|
|
||||||
.set_agent(
|
|
||||||
reqwest::Client::builder()
|
|
||||||
.danger_accept_invalid_certs(true)
|
|
||||||
.timeout(std::time::Duration::from_secs(3))
|
|
||||||
.build()
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.set_host(url.to_owned())
|
|
||||||
.set_auth(reqwest_dav::Auth::Basic(
|
|
||||||
username.to_owned(),
|
|
||||||
password.to_owned(),
|
|
||||||
))
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
if (client
|
|
||||||
.list(dirs::BACKUP_DIR, reqwest_dav::Depth::Number(0))
|
|
||||||
.await)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
client.mkcol(dirs::BACKUP_DIR).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
*self.client.lock() = Some(client.clone());
|
|
||||||
}
|
}
|
||||||
Ok(self.client.lock().clone().unwrap())
|
|
||||||
|
// 获取或创建配置
|
||||||
|
let config = {
|
||||||
|
let mut lock = self.config.lock();
|
||||||
|
if let Some(cfg) = lock.as_ref() {
|
||||||
|
cfg.clone()
|
||||||
|
} else {
|
||||||
|
let verge = Config::verge().latest().clone();
|
||||||
|
if verge.webdav_url.is_none()
|
||||||
|
|| verge.webdav_username.is_none()
|
||||||
|
|| verge.webdav_password.is_none()
|
||||||
|
{
|
||||||
|
let msg = "Unable to create web dav client, please make sure the webdav config is correct".to_string();
|
||||||
|
return Err(anyhow::Error::msg(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = WebDavConfig {
|
||||||
|
url: verge
|
||||||
|
.webdav_url
|
||||||
|
.unwrap_or_default()
|
||||||
|
.trim_end_matches('/')
|
||||||
|
.to_string(),
|
||||||
|
username: verge.webdav_username.unwrap_or_default(),
|
||||||
|
password: verge.webdav_password.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
*lock = Some(config.clone());
|
||||||
|
config
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建新的客户端
|
||||||
|
let client = reqwest_dav::ClientBuilder::new()
|
||||||
|
.set_agent(
|
||||||
|
reqwest::Client::builder()
|
||||||
|
.danger_accept_invalid_certs(true)
|
||||||
|
.timeout(Duration::from_secs(op.timeout()))
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.set_host(config.url)
|
||||||
|
.set_auth(reqwest_dav::Auth::Basic(config.username, config.password))
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
// 确保备份目录存在
|
||||||
|
let list_result = client
|
||||||
|
.list(dirs::BACKUP_DIR, reqwest_dav::Depth::Number(0))
|
||||||
|
.await;
|
||||||
|
if list_result.is_err() {
|
||||||
|
client.mkcol(dirs::BACKUP_DIR).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存客户端
|
||||||
|
{
|
||||||
|
let mut clients = self.clients.lock();
|
||||||
|
clients.insert(op, client.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&self) {
|
pub fn reset(&self) {
|
||||||
if !self.client.lock().is_none() {
|
*self.config.lock() = None;
|
||||||
self.client.lock().take();
|
self.clients.lock().clear();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn upload(&self, file_path: PathBuf, file_name: String) -> Result<(), Error> {
|
pub async fn upload(&self, file_path: PathBuf, file_name: String) -> Result<(), Error> {
|
||||||
let client = self.get_client().await?;
|
let client = self.get_client(Operation::Upload).await?;
|
||||||
let webdav_path: String = format!("{}/{}", dirs::BACKUP_DIR, file_name);
|
let webdav_path: String = format!("{}/{}", dirs::BACKUP_DIR, file_name);
|
||||||
client
|
let fut = client.put(webdav_path.as_ref(), fs::read(file_path)?);
|
||||||
.put(webdav_path.as_ref(), fs::read(file_path)?)
|
timeout(Duration::from_secs(TIMEOUT_UPLOAD), fut).await??;
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download(&self, filename: String, storage_path: PathBuf) -> Result<(), Error> {
|
pub async fn download(&self, filename: String, storage_path: PathBuf) -> Result<(), Error> {
|
||||||
let client = self.get_client().await?;
|
let client = self.get_client(Operation::Download).await?;
|
||||||
let path = format!("{}/{}", dirs::BACKUP_DIR, filename);
|
let path = format!("{}/{}", dirs::BACKUP_DIR, filename);
|
||||||
let response = client.get(path.as_str()).await?;
|
|
||||||
let content = response.bytes().await?;
|
let fut = async {
|
||||||
fs::write(&storage_path, &content)?;
|
let response = client.get(path.as_str()).await?;
|
||||||
|
let content = response.bytes().await?;
|
||||||
|
fs::write(&storage_path, &content)?;
|
||||||
|
Ok::<(), Error>(())
|
||||||
|
};
|
||||||
|
|
||||||
|
timeout(Duration::from_secs(TIMEOUT_DOWNLOAD), fut).await??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list(&self) -> Result<Vec<ListFile>, Error> {
|
pub async fn list(&self) -> Result<Vec<ListFile>, Error> {
|
||||||
let client = self.get_client().await?;
|
let client = self.get_client(Operation::List).await?;
|
||||||
let path = format!("{}/", dirs::BACKUP_DIR);
|
let path = format!("{}/", dirs::BACKUP_DIR);
|
||||||
let files = client
|
|
||||||
.list(path.as_str(), reqwest_dav::Depth::Number(1))
|
let fut = async {
|
||||||
.await?;
|
let files = client
|
||||||
let mut final_files = Vec::new();
|
.list(path.as_str(), reqwest_dav::Depth::Number(1))
|
||||||
for file in files {
|
.await?;
|
||||||
if let ListEntity::File(file) = file {
|
let mut final_files = Vec::new();
|
||||||
final_files.push(file);
|
for file in files {
|
||||||
|
if let ListEntity::File(file) = file {
|
||||||
|
final_files.push(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok::<Vec<ListFile>, Error>(final_files)
|
||||||
Ok(final_files)
|
};
|
||||||
|
|
||||||
|
timeout(Duration::from_secs(TIMEOUT_LIST), fut).await?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, file_name: String) -> Result<(), Error> {
|
pub async fn delete(&self, file_name: String) -> Result<(), Error> {
|
||||||
let client = self.get_client().await?;
|
let client = self.get_client(Operation::Delete).await?;
|
||||||
let path = format!("{}/{}", dirs::BACKUP_DIR, file_name);
|
let path = format!("{}/{}", dirs::BACKUP_DIR, file_name);
|
||||||
client.delete(&path).await?;
|
|
||||||
|
let fut = client.delete(&path);
|
||||||
|
timeout(Duration::from_secs(TIMEOUT_DELETE), fut).await??;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub struct Rate {
|
||||||
|
pub up: u64,
|
||||||
|
pub down: u64,
|
||||||
|
}
|
||||||
|
|
||||||
/// PUT /configs
|
/// PUT /configs
|
||||||
/// path 是绝对路径
|
/// path 是绝对路径
|
||||||
pub async fn put_configs(path: &str) -> Result<()> {
|
pub async fn put_configs(path: &str) -> Result<()> {
|
||||||
@@ -21,7 +27,9 @@ pub async fn put_configs(path: &str) -> Result<()> {
|
|||||||
match response.status().as_u16() {
|
match response.status().as_u16() {
|
||||||
204 => Ok(()),
|
204 => Ok(()),
|
||||||
status => {
|
status => {
|
||||||
bail!("failed to put configs with status \"{status}\"")
|
let body = response.text().await?;
|
||||||
|
// print!("failed to put configs with status \"{}\"\n{}\n{}", status, url, body);
|
||||||
|
bail!("failed to put configs with status \"{status}\"\n{url}\n{body}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,6 +131,13 @@ pub fn parse_check_output(log: String) -> String {
|
|||||||
log
|
log
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub fn get_traffic_ws_url() -> Result<String> {
|
||||||
|
let (url, _) = clash_client_info()?;
|
||||||
|
let ws_url = url.replace("http://", "ws://") + "/traffic";
|
||||||
|
Ok(ws_url)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_check_output() {
|
fn test_parse_check_output() {
|
||||||
let str1 = r#"xxxx\n time="2022-11-18T20:42:58+08:00" level=error msg="proxy 0: 'alpn' expected type 'string', got unconvertible type '[]interface {}'""#;
|
let str1 = r#"xxxx\n time="2022-11-18T20:42:58+08:00" level=error msg="proxy 0: 'alpn' expected type 'string', got unconvertible type '[]interface {}'""#;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use super::tray::Tray;
|
||||||
use crate::config::*;
|
use crate::config::*;
|
||||||
use crate::core::{clash_api, handle, service};
|
use crate::core::{clash_api, handle, service};
|
||||||
use crate::log_err;
|
use crate::log_err;
|
||||||
@@ -76,7 +77,6 @@ impl CoreManager {
|
|||||||
service::stop_core_by_service().await?;
|
service::stop_core_by_service().await?;
|
||||||
}
|
}
|
||||||
*running = false;
|
*running = false;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,8 +94,13 @@ impl CoreManager {
|
|||||||
if service::check_service().await.is_ok() {
|
if service::check_service().await.is_ok() {
|
||||||
log::info!(target: "app", "try to run core in service mode");
|
log::info!(target: "app", "try to run core in service mode");
|
||||||
service::run_core_by_service(&config_path).await?;
|
service::run_core_by_service(&config_path).await?;
|
||||||
*running = true;
|
|
||||||
}
|
}
|
||||||
|
// 流量订阅
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
log_err!(Tray::global().subscribe_traffic().await);
|
||||||
|
|
||||||
|
*running = true;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use super::tray::Tray;
|
|
||||||
use crate::log_err;
|
use crate::log_err;
|
||||||
use anyhow::Result;
|
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -65,12 +63,6 @@ impl Handle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// update the system tray state
|
|
||||||
pub fn update_systray_part() -> Result<()> {
|
|
||||||
Tray::update_part()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_is_exiting(&self) {
|
pub fn set_is_exiting(&self) {
|
||||||
let mut is_exiting = self.is_exiting.write();
|
let mut is_exiting = self.is_exiting.write();
|
||||||
*is_exiting = true;
|
*is_exiting = true;
|
||||||
|
|||||||
@@ -24,10 +24,11 @@ pub struct Sysopt {
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;<local>";
|
static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;<local>";
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
static DEFAULT_BYPASS: &str = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,::1";
|
static DEFAULT_BYPASS: &str =
|
||||||
|
"localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,::1";
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
static DEFAULT_BYPASS: &str =
|
static DEFAULT_BYPASS: &str =
|
||||||
"127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
|
"127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,localhost,*.local,*.crashlytics.com,<local>";
|
||||||
|
|
||||||
fn get_bypass() -> String {
|
fn get_bypass() -> String {
|
||||||
let use_default = Config::verge().latest().use_default_bypass.unwrap_or(true);
|
let use_default = Config::verge().latest().use_default_bypass.unwrap_or(true);
|
||||||
|
|||||||
@@ -1,28 +1,72 @@
|
|||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub mod speed_rate;
|
||||||
|
use crate::core::clash_api::Rate;
|
||||||
use crate::{
|
use crate::{
|
||||||
cmds,
|
cmds,
|
||||||
config::Config,
|
config::Config,
|
||||||
feat, t,
|
feat, resolve,
|
||||||
utils::{
|
utils::resolve::VERSION,
|
||||||
dirs,
|
utils::{dirs, i18n::t},
|
||||||
resolve::{self, VERSION},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
|
||||||
use tauri::AppHandle;
|
|
||||||
use tauri::{
|
|
||||||
menu::CheckMenuItem,
|
|
||||||
tray::{MouseButton, MouseButtonState, TrayIconEvent, TrayIconId},
|
|
||||||
};
|
|
||||||
use tauri::{
|
|
||||||
menu::{MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
|
|
||||||
Wry,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use futures::StreamExt;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub use speed_rate::{SpeedRate, Traffic};
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tauri::menu::CheckMenuItem;
|
||||||
|
use tauri::AppHandle;
|
||||||
|
use tauri::{
|
||||||
|
menu::{MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
|
||||||
|
tray::{MouseButton, MouseButtonState, TrayIconEvent, TrayIconId},
|
||||||
|
Wry,
|
||||||
|
};
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
use super::handle;
|
use super::handle;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub struct Tray {
|
||||||
|
pub speed_rate: Arc<Mutex<Option<SpeedRate>>>,
|
||||||
|
shutdown_tx: Arc<RwLock<Option<broadcast::Sender<()>>>>,
|
||||||
|
is_subscribed: Arc<RwLock<bool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
pub struct Tray {}
|
pub struct Tray {}
|
||||||
|
|
||||||
impl Tray {
|
impl Tray {
|
||||||
pub fn create_systray() -> Result<()> {
|
pub fn global() -> &'static Tray {
|
||||||
|
static TRAY: OnceCell<Tray> = OnceCell::new();
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
return TRAY.get_or_init(|| Tray {
|
||||||
|
speed_rate: Arc::new(Mutex::new(None)),
|
||||||
|
shutdown_tx: Arc::new(RwLock::new(None)),
|
||||||
|
is_subscribed: Arc::new(RwLock::new(false)),
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
return TRAY.get_or_init(|| Tray {});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&self) -> Result<()> {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
let mut speed_rate = self.speed_rate.lock();
|
||||||
|
*speed_rate = Some(SpeedRate::new());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_systray(&self) -> Result<()> {
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
let tray_incon_id = TrayIconId::new("main");
|
let tray_incon_id = TrayIconId::new("main");
|
||||||
let tray = app_handle.tray_by_id(&tray_incon_id).unwrap();
|
let tray = app_handle.tray_by_id(&tray_incon_id).unwrap();
|
||||||
@@ -65,10 +109,12 @@ impl Tray {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_part() -> Result<()> {
|
/// 更新托盘菜单
|
||||||
|
pub fn update_menu(&self) -> Result<()> {
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
let use_zh = { Config::verge().latest().language == Some("zh".into()) };
|
let verge = Config::verge().latest().clone();
|
||||||
let version = VERSION.get().unwrap();
|
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||||
|
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||||
let mode = {
|
let mode = {
|
||||||
Config::clash()
|
Config::clash()
|
||||||
.latest()
|
.latest()
|
||||||
@@ -79,38 +125,42 @@ impl Tray {
|
|||||||
.to_owned()
|
.to_owned()
|
||||||
};
|
};
|
||||||
|
|
||||||
let verge = Config::verge().latest().clone();
|
|
||||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
|
||||||
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
|
||||||
let common_tray_icon = verge.common_tray_icon.as_ref().unwrap_or(&false);
|
|
||||||
let sysproxy_tray_icon = verge.sysproxy_tray_icon.as_ref().unwrap_or(&false);
|
|
||||||
let tun_tray_icon = verge.tun_tray_icon.as_ref().unwrap_or(&false);
|
|
||||||
let tray = app_handle.tray_by_id("main").unwrap();
|
let tray = app_handle.tray_by_id("main").unwrap();
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let tray_icon = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
|
|
||||||
|
|
||||||
let _ = tray.set_menu(Some(create_tray_menu(
|
let _ = tray.set_menu(Some(create_tray_menu(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
Some(mode.as_str()),
|
Some(mode.as_str()),
|
||||||
*system_proxy,
|
*system_proxy,
|
||||||
*tun_mode,
|
*tun_mode,
|
||||||
)?));
|
)?));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新托盘图标
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub fn update_icon(&self, rate: Option<Rate>) -> Result<()> {
|
||||||
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
|
let verge = Config::verge().latest().clone();
|
||||||
|
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||||
|
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||||
|
|
||||||
|
let common_tray_icon = verge.common_tray_icon.as_ref().unwrap_or(&false);
|
||||||
|
let sysproxy_tray_icon = verge.sysproxy_tray_icon.as_ref().unwrap_or(&false);
|
||||||
|
let tun_tray_icon = verge.tun_tray_icon.as_ref().unwrap_or(&false);
|
||||||
|
|
||||||
|
let tray = app_handle.tray_by_id("main").unwrap();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let mut use_custom_icon = false;
|
let tray_icon = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
|
||||||
#[allow(unused)]
|
|
||||||
let mut indication_icon = if *system_proxy && !*tun_mode {
|
let icon_bytes = if *system_proxy && !*tun_mode {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let mut icon = match tray_icon.as_str() {
|
let mut icon = match tray_icon.as_str() {
|
||||||
"colorful" => {
|
"colorful" => include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
|
||||||
use_custom_icon = true;
|
_ => include_bytes!("../../../icons/tray-icon-sys-mono.ico").to_vec(),
|
||||||
include_bytes!("../../icons/tray-icon-sys.ico").to_vec()
|
|
||||||
}
|
|
||||||
_ => include_bytes!("../../icons/tray-icon-sys-mono.ico").to_vec(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
let mut icon = include_bytes!("../../icons/tray-icon-sys.ico").to_vec();
|
let mut icon = include_bytes!("../../../icons/tray-icon-sys.ico").to_vec();
|
||||||
if *sysproxy_tray_icon {
|
if *sysproxy_tray_icon {
|
||||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
||||||
let png_path = icon_dir_path.join("sysproxy.png");
|
let png_path = icon_dir_path.join("sysproxy.png");
|
||||||
@@ -120,24 +170,17 @@ impl Tray {
|
|||||||
} else if png_path.exists() {
|
} else if png_path.exists() {
|
||||||
icon = std::fs::read(png_path).unwrap();
|
icon = std::fs::read(png_path).unwrap();
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
use_custom_icon = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
icon
|
icon
|
||||||
} else if *tun_mode {
|
} else if *tun_mode {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let mut icon = match tray_icon.as_str() {
|
let mut icon = match tray_icon.as_str() {
|
||||||
"colorful" => {
|
"colorful" => include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
|
||||||
use_custom_icon = true;
|
_ => include_bytes!("../../../icons/tray-icon-tun-mono.ico").to_vec(),
|
||||||
include_bytes!("../../icons/tray-icon-tun.ico").to_vec()
|
|
||||||
}
|
|
||||||
_ => include_bytes!("../../icons/tray-icon-tun-mono.ico").to_vec(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
let mut icon = include_bytes!("../../icons/tray-icon-tun.ico").to_vec();
|
let mut icon = include_bytes!("../../../icons/tray-icon-tun.ico").to_vec();
|
||||||
if *tun_tray_icon {
|
if *tun_tray_icon {
|
||||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
||||||
let png_path = icon_dir_path.join("tun.png");
|
let png_path = icon_dir_path.join("tun.png");
|
||||||
@@ -147,24 +190,17 @@ impl Tray {
|
|||||||
} else if png_path.exists() {
|
} else if png_path.exists() {
|
||||||
icon = std::fs::read(png_path).unwrap();
|
icon = std::fs::read(png_path).unwrap();
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
use_custom_icon = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
icon
|
icon
|
||||||
} else {
|
} else {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let mut icon = match tray_icon.as_str() {
|
let mut icon = match tray_icon.as_str() {
|
||||||
"colorful" => {
|
"colorful" => include_bytes!("../../../icons/tray-icon.ico").to_vec(),
|
||||||
use_custom_icon = true;
|
_ => include_bytes!("../../../icons/tray-icon-mono.ico").to_vec(),
|
||||||
include_bytes!("../../icons/tray-icon.ico").to_vec()
|
|
||||||
}
|
|
||||||
_ => include_bytes!("../../icons/tray-icon-mono.ico").to_vec(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
let mut icon = include_bytes!("../../icons/tray-icon.ico").to_vec();
|
let mut icon = include_bytes!("../../../icons/tray-icon.ico").to_vec();
|
||||||
if *common_tray_icon {
|
if *common_tray_icon {
|
||||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
||||||
let png_path = icon_dir_path.join("common.png");
|
let png_path = icon_dir_path.join("common.png");
|
||||||
@@ -174,26 +210,46 @@ impl Tray {
|
|||||||
} else if png_path.exists() {
|
} else if png_path.exists() {
|
||||||
icon = std::fs::read(png_path).unwrap();
|
icon = std::fs::read(png_path).unwrap();
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
use_custom_icon = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
icon
|
icon
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
if use_custom_icon {
|
let enable_tray_speed = Config::verge().latest().enable_tray_speed.unwrap_or(true);
|
||||||
let _ = tray.set_icon_as_template(false);
|
let is_template =
|
||||||
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&indication_icon)?));
|
crate::utils::help::is_monochrome_image_from_bytes(&icon_bytes).unwrap_or(false);
|
||||||
|
|
||||||
|
let icon_bytes = if enable_tray_speed {
|
||||||
|
let rate = rate.or_else(|| {
|
||||||
|
self.speed_rate
|
||||||
|
.lock()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|speed_rate| speed_rate.get_curent_rate())
|
||||||
|
});
|
||||||
|
SpeedRate::add_speed_text(icon_bytes, rate)?
|
||||||
} else {
|
} else {
|
||||||
let _ = tray.set_icon_as_template(true);
|
icon_bytes
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
|
||||||
|
let _ = tray.set_icon_as_template(is_template);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&indication_icon)?));
|
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新托盘提示
|
||||||
|
pub fn update_tooltip(&self) -> Result<()> {
|
||||||
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
|
let version = VERSION.get().unwrap();
|
||||||
|
|
||||||
|
let verge = Config::verge().latest().clone();
|
||||||
|
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||||
|
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||||
|
|
||||||
let switch_map = {
|
let switch_map = {
|
||||||
let mut map = std::collections::HashMap::new();
|
let mut map = std::collections::HashMap::new();
|
||||||
@@ -213,17 +269,93 @@ impl Tray {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let tray = app_handle.tray_by_id("main").unwrap();
|
||||||
let _ = tray.set_tooltip(Some(&format!(
|
let _ = tray.set_tooltip(Some(&format!(
|
||||||
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
|
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
|
||||||
t!("SysProxy", "系统代理", use_zh),
|
t("SysProxy"),
|
||||||
switch_map[system_proxy],
|
switch_map[system_proxy],
|
||||||
t!("TUN", "Tun模式", use_zh),
|
t("TUN"),
|
||||||
switch_map[tun_mode],
|
switch_map[tun_mode],
|
||||||
t!("Profile", "当前订阅", use_zh),
|
t("Profile"),
|
||||||
current_profile_name
|
current_profile_name
|
||||||
)));
|
)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_part(&self) -> Result<()> {
|
||||||
|
self.update_menu()?;
|
||||||
|
self.update_icon(None)?;
|
||||||
|
self.update_tooltip()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 订阅流量数据
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub async fn subscribe_traffic(&self) -> Result<()> {
|
||||||
|
log::info!(target: "app", "subscribe traffic");
|
||||||
|
|
||||||
|
// 如果已经订阅,先取消订阅
|
||||||
|
if *self.is_subscribed.read() {
|
||||||
|
self.unsubscribe_traffic();
|
||||||
|
}
|
||||||
|
|
||||||
|
let (shutdown_tx, shutdown_rx) = broadcast::channel(1);
|
||||||
|
*self.shutdown_tx.write() = Some(shutdown_tx);
|
||||||
|
*self.is_subscribed.write() = true;
|
||||||
|
|
||||||
|
let speed_rate = Arc::clone(&self.speed_rate);
|
||||||
|
let is_subscribed = Arc::clone(&self.is_subscribed);
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
let mut shutdown = shutdown_rx;
|
||||||
|
|
||||||
|
'outer: loop {
|
||||||
|
match Traffic::get_traffic_stream().await {
|
||||||
|
Ok(mut stream) => loop {
|
||||||
|
tokio::select! {
|
||||||
|
Some(traffic) = stream.next() => {
|
||||||
|
if let Ok(traffic) = traffic {
|
||||||
|
let guard = speed_rate.lock();
|
||||||
|
let enable_tray_speed: bool = Config::verge().latest().enable_tray_speed.unwrap_or(true);
|
||||||
|
if !enable_tray_speed {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(sr) = guard.as_ref() {
|
||||||
|
if let Some(rate) = sr.update_and_check_changed(traffic.up, traffic.down) {
|
||||||
|
let _ = Tray::global().update_icon(Some(rate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = shutdown.recv() => break 'outer,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(target: "app", "Failed to get traffic stream: {}", e);
|
||||||
|
// 如果获取流失败,等待一段时间后重试
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
|
||||||
|
// 检查是否应该继续重试
|
||||||
|
if !*is_subscribed.read() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 取消订阅 traffic 数据
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub fn unsubscribe_traffic(&self) {
|
||||||
|
log::info!(target: "app", "unsubscribe traffic");
|
||||||
|
*self.is_subscribed.write() = false;
|
||||||
|
if let Some(tx) = self.shutdown_tx.write().take() {
|
||||||
|
drop(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_tray_menu(
|
fn create_tray_menu(
|
||||||
@@ -233,81 +365,90 @@ fn create_tray_menu(
|
|||||||
tun_mode_enabled: bool,
|
tun_mode_enabled: bool,
|
||||||
) -> Result<tauri::menu::Menu<Wry>> {
|
) -> Result<tauri::menu::Menu<Wry>> {
|
||||||
let mode = mode.unwrap_or("");
|
let mode = mode.unwrap_or("");
|
||||||
let use_zh = { Config::verge().latest().language == Some("zh".into()) };
|
|
||||||
let version = VERSION.get().unwrap();
|
let version = VERSION.get().unwrap();
|
||||||
|
let hotkeys = Config::verge()
|
||||||
|
.latest()
|
||||||
|
.hotkeys
|
||||||
|
.as_ref()
|
||||||
|
.map(|h| {
|
||||||
|
h.iter()
|
||||||
|
.filter_map(|item| {
|
||||||
|
let mut parts = item.split(',');
|
||||||
|
match (parts.next(), parts.next()) {
|
||||||
|
(Some(func), Some(key)) => Some((func.to_string(), key.to_string())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<std::collections::HashMap<String, String>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let open_window = &MenuItem::with_id(
|
let open_window = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"open_window",
|
"open_window",
|
||||||
t!("Dashboard", "打开面板", use_zh),
|
t("Dashboard"),
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
hotkeys.get("open_or_close_dashboard").map(|s| s.as_str()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let rule_mode = &CheckMenuItem::with_id(
|
let rule_mode = &CheckMenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"rule_mode",
|
"rule_mode",
|
||||||
t!("Rule Mode", "规则模式", use_zh),
|
t("Rule Mode"),
|
||||||
true,
|
true,
|
||||||
mode == "rule",
|
mode == "rule",
|
||||||
None::<&str>,
|
hotkeys.get("clash_mode_rule").map(|s| s.as_str()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let global_mode = &CheckMenuItem::with_id(
|
let global_mode = &CheckMenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"global_mode",
|
"global_mode",
|
||||||
t!("Global Mode", "全局模式", use_zh),
|
t("Global Mode"),
|
||||||
true,
|
true,
|
||||||
mode == "global",
|
mode == "global",
|
||||||
None::<&str>,
|
hotkeys.get("clash_mode_global").map(|s| s.as_str()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let direct_mode = &CheckMenuItem::with_id(
|
let direct_mode = &CheckMenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"direct_mode",
|
"direct_mode",
|
||||||
t!("Direct Mode", "直连模式", use_zh),
|
t("Direct Mode"),
|
||||||
true,
|
true,
|
||||||
mode == "direct",
|
mode == "direct",
|
||||||
None::<&str>,
|
hotkeys.get("clash_mode_direct").map(|s| s.as_str()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let system_proxy = &CheckMenuItem::with_id(
|
let system_proxy = &CheckMenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"system_proxy",
|
"system_proxy",
|
||||||
t!("System Proxy", "系统代理", use_zh),
|
t("System Proxy"),
|
||||||
true,
|
true,
|
||||||
system_proxy_enabled,
|
system_proxy_enabled,
|
||||||
None::<&str>,
|
hotkeys.get("toggle_system_proxy").map(|s| s.as_str()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let tun_mode = &CheckMenuItem::with_id(
|
let tun_mode = &CheckMenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"tun_mode",
|
"tun_mode",
|
||||||
t!("TUN Mode", "Tun模式", use_zh),
|
t("TUN Mode"),
|
||||||
true,
|
true,
|
||||||
tun_mode_enabled,
|
tun_mode_enabled,
|
||||||
None::<&str>,
|
hotkeys.get("toggle_tun_mode").map(|s| s.as_str()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let copy_env = &MenuItem::with_id(
|
let copy_env =
|
||||||
app_handle,
|
&MenuItem::with_id(app_handle, "copy_env", t("Copy Env"), true, None::<&str>).unwrap();
|
||||||
"copy_env",
|
|
||||||
t!("Copy Env", "复制环境变量", use_zh),
|
|
||||||
true,
|
|
||||||
None::<&str>,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let open_app_dir = &MenuItem::with_id(
|
let open_app_dir = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"open_app_dir",
|
"open_app_dir",
|
||||||
t!("Conf Dir", "配置目录", use_zh),
|
t("Conf Dir"),
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)
|
)
|
||||||
@@ -316,7 +457,7 @@ fn create_tray_menu(
|
|||||||
let open_core_dir = &MenuItem::with_id(
|
let open_core_dir = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"open_core_dir",
|
"open_core_dir",
|
||||||
t!("Core Dir", "内核目录", use_zh),
|
t("Core Dir"),
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)
|
)
|
||||||
@@ -325,15 +466,16 @@ fn create_tray_menu(
|
|||||||
let open_logs_dir = &MenuItem::with_id(
|
let open_logs_dir = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"open_logs_dir",
|
"open_logs_dir",
|
||||||
t!("Logs Dir", "日志目录", use_zh),
|
t("Logs Dir"),
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let open_dir = &Submenu::with_id_and_items(
|
let open_dir = &Submenu::with_id_and_items(
|
||||||
app_handle,
|
app_handle,
|
||||||
"open_dir",
|
"open_dir",
|
||||||
t!("Open Dir", "打开目录", use_zh),
|
t("Open Dir"),
|
||||||
true,
|
true,
|
||||||
&[open_app_dir, open_core_dir, open_logs_dir],
|
&[open_app_dir, open_core_dir, open_logs_dir],
|
||||||
)
|
)
|
||||||
@@ -342,7 +484,7 @@ fn create_tray_menu(
|
|||||||
let restart_clash = &MenuItem::with_id(
|
let restart_clash = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"restart_clash",
|
"restart_clash",
|
||||||
t!("Restart Clash Core", "重启Clash内核", use_zh),
|
t("Restart Clash Core"),
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)
|
)
|
||||||
@@ -351,7 +493,7 @@ fn create_tray_menu(
|
|||||||
let restart_app = &MenuItem::with_id(
|
let restart_app = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"restart_app",
|
"restart_app",
|
||||||
t!("Restart App", "重启App", use_zh),
|
t("Restart App"),
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)
|
)
|
||||||
@@ -360,7 +502,7 @@ fn create_tray_menu(
|
|||||||
let app_version = &MenuItem::with_id(
|
let app_version = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"app_version",
|
"app_version",
|
||||||
format!("Version {version}"),
|
format!("{} {version}", t("Verge Version")),
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)
|
)
|
||||||
@@ -369,20 +511,14 @@ fn create_tray_menu(
|
|||||||
let more = &Submenu::with_id_and_items(
|
let more = &Submenu::with_id_and_items(
|
||||||
app_handle,
|
app_handle,
|
||||||
"more",
|
"more",
|
||||||
t!("More", "更多", use_zh),
|
t("More"),
|
||||||
true,
|
true,
|
||||||
&[restart_clash, restart_app, app_version],
|
&[restart_clash, restart_app, app_version],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let quit = &MenuItem::with_id(
|
let quit =
|
||||||
app_handle,
|
&MenuItem::with_id(app_handle, "quit", t("Exit"), true, Some("CmdOrControl+Q")).unwrap();
|
||||||
"quit",
|
|
||||||
t!("Quit", "退出", use_zh),
|
|
||||||
true,
|
|
||||||
Some("CmdOrControl+Q"),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let separator = &PredefinedMenuItem::separator(app_handle).unwrap();
|
let separator = &PredefinedMenuItem::separator(app_handle).unwrap();
|
||||||
|
|
||||||
181
src-tauri/src/core/tray/speed_rate.rs
Normal file
181
src-tauri/src/core/tray/speed_rate.rs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
use crate::core::clash_api::{get_traffic_ws_url, Rate};
|
||||||
|
use crate::utils::help::format_bytes_speed;
|
||||||
|
use anyhow::Result;
|
||||||
|
use futures::Stream;
|
||||||
|
use image::{ImageBuffer, Rgba};
|
||||||
|
use imageproc::drawing::draw_text_mut;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use rusttype::{Font, Scale};
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SpeedRate {
|
||||||
|
rate: Arc<Mutex<(Rate, Rate)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpeedRate {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
rate: Arc::new(Mutex::new((Rate::default(), Rate::default()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_and_check_changed(&self, up: u64, down: u64) -> Option<Rate> {
|
||||||
|
let mut rates = self.rate.lock();
|
||||||
|
let (current, previous) = &mut *rates;
|
||||||
|
|
||||||
|
*previous = current.clone();
|
||||||
|
current.up = up;
|
||||||
|
current.down = down;
|
||||||
|
|
||||||
|
if previous != current {
|
||||||
|
Some(current.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_curent_rate(&self) -> Option<Rate> {
|
||||||
|
let rates = self.rate.lock();
|
||||||
|
let (current, _) = &*rates;
|
||||||
|
Some(current.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_speed_text(icon: Vec<u8>, rate: Option<Rate>) -> Result<Vec<u8>> {
|
||||||
|
let rate = rate.unwrap_or(Rate { up: 0, down: 0 });
|
||||||
|
let img = image::load_from_memory(&icon)?;
|
||||||
|
let (width, height) = (img.width(), img.height());
|
||||||
|
|
||||||
|
let mut image = ImageBuffer::new((width as f32 * 4.0) as u32, height);
|
||||||
|
image::imageops::replace(&mut image, &img, 0, 0);
|
||||||
|
|
||||||
|
let font =
|
||||||
|
Font::try_from_bytes(include_bytes!("../../../assets/fonts/SFCompact.ttf")).unwrap();
|
||||||
|
|
||||||
|
// 修改颜色和阴影参数
|
||||||
|
let text_color = Rgba([255u8, 255u8, 255u8, 255u8]); // 纯白色
|
||||||
|
let shadow_color = Rgba([0u8, 0u8, 0u8, 180u8]); // 半透明黑色阴影
|
||||||
|
let base_size = height as f32 * 0.5;
|
||||||
|
let scale = Scale::uniform(base_size);
|
||||||
|
|
||||||
|
let up_text = format_bytes_speed(rate.up);
|
||||||
|
let down_text = format_bytes_speed(rate.down);
|
||||||
|
|
||||||
|
// 计算文本位置(保持不变)
|
||||||
|
let up_width = font
|
||||||
|
.layout(&up_text, scale, rusttype::Point { x: 0.0, y: 0.0 })
|
||||||
|
.map(|g| g.position().x + g.unpositioned().h_metrics().advance_width)
|
||||||
|
.last()
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
|
||||||
|
let down_width = font
|
||||||
|
.layout(&down_text, scale, rusttype::Point { x: 0.0, y: 0.0 })
|
||||||
|
.map(|g| g.position().x + g.unpositioned().h_metrics().advance_width)
|
||||||
|
.last()
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
|
||||||
|
let right_margin = 8;
|
||||||
|
let canvas_width = width * 4;
|
||||||
|
let up_x = canvas_width as f32 - up_width - right_margin as f32;
|
||||||
|
let down_x = canvas_width as f32 - down_width - right_margin as f32;
|
||||||
|
|
||||||
|
// 添加阴影效果
|
||||||
|
let shadow_offset = 1; // 阴影偏移量
|
||||||
|
|
||||||
|
// 绘制上行速率(先画阴影,再画文字)
|
||||||
|
draw_text_mut(
|
||||||
|
&mut image,
|
||||||
|
shadow_color,
|
||||||
|
up_x as i32 + shadow_offset,
|
||||||
|
1 + shadow_offset,
|
||||||
|
scale,
|
||||||
|
&font,
|
||||||
|
&up_text,
|
||||||
|
);
|
||||||
|
draw_text_mut(
|
||||||
|
&mut image,
|
||||||
|
text_color,
|
||||||
|
up_x as i32,
|
||||||
|
1,
|
||||||
|
scale,
|
||||||
|
&font,
|
||||||
|
&up_text,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制下行速率(先画阴影,再画文字)
|
||||||
|
draw_text_mut(
|
||||||
|
&mut image,
|
||||||
|
shadow_color,
|
||||||
|
down_x as i32 + shadow_offset,
|
||||||
|
height as i32 - (base_size as i32) - 1 + shadow_offset,
|
||||||
|
scale,
|
||||||
|
&font,
|
||||||
|
&down_text,
|
||||||
|
);
|
||||||
|
draw_text_mut(
|
||||||
|
&mut image,
|
||||||
|
text_color,
|
||||||
|
down_x as i32,
|
||||||
|
height as i32 - (base_size as i32) - 1,
|
||||||
|
scale,
|
||||||
|
&font,
|
||||||
|
&down_text,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut bytes: Vec<u8> = Vec::new();
|
||||||
|
let mut cursor = Cursor::new(&mut bytes);
|
||||||
|
image.write_to(&mut cursor, image::ImageFormat::Png)?;
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Traffic {
|
||||||
|
pub up: u64,
|
||||||
|
pub down: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Traffic {
|
||||||
|
pub async fn get_traffic_stream() -> Result<impl Stream<Item = Result<Traffic, anyhow::Error>>>
|
||||||
|
{
|
||||||
|
use futures::stream::{self, StreamExt};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
let stream = Box::pin(
|
||||||
|
stream::unfold((), |_| async {
|
||||||
|
loop {
|
||||||
|
let ws_url = get_traffic_ws_url().unwrap();
|
||||||
|
|
||||||
|
match tokio_tungstenite::connect_async(&ws_url).await {
|
||||||
|
Ok((ws_stream, _)) => {
|
||||||
|
log::info!(target: "app", "traffic ws connection established");
|
||||||
|
return Some((
|
||||||
|
ws_stream.map(|msg| {
|
||||||
|
msg.map_err(anyhow::Error::from).and_then(|msg: Message| {
|
||||||
|
let data = msg.into_text()?;
|
||||||
|
let json: serde_json::Value = serde_json::from_str(&data)?;
|
||||||
|
Ok(Traffic {
|
||||||
|
up: json["up"].as_u64().unwrap_or(0),
|
||||||
|
down: json["down"].as_u64().unwrap_or(0),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(target: "app", "traffic ws connection failed: {e}");
|
||||||
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,19 +29,52 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
|||||||
let mut dns_val = dns_val.map_or(Mapping::new(), |val| {
|
let mut dns_val = dns_val.map_or(Mapping::new(), |val| {
|
||||||
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||||
});
|
});
|
||||||
|
let ipv6_key = Value::from("ipv6");
|
||||||
|
let ipv6_val = config
|
||||||
|
.get(&ipv6_key)
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
if enable {
|
if enable {
|
||||||
revise!(dns_val, "enable", true);
|
revise!(dns_val, "enable", true);
|
||||||
revise!(dns_val, "ipv6", true);
|
revise!(dns_val, "ipv6", ipv6_val);
|
||||||
revise!(dns_val, "enhanced-mode", "fake-ip");
|
revise!(dns_val, "enhanced-mode", "fake-ip");
|
||||||
revise!(dns_val, "fake-ip-range", "198.18.0.1/16");
|
revise!(dns_val, "fake-ip-range", "172.29.0.1/16");
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
crate::utils::resolve::restore_public_dns().await;
|
crate::utils::resolve::restore_public_dns().await;
|
||||||
crate::utils::resolve::set_public_dns("8.8.8.8".to_string()).await;
|
crate::utils::resolve::set_public_dns("223.6.6.6".to_string()).await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
revise!(dns_val, "enhanced-mode", "redir-host");
|
revise!(
|
||||||
|
dns_val,
|
||||||
|
"enable",
|
||||||
|
dns_val
|
||||||
|
.get("enable")
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.unwrap_or(true)
|
||||||
|
);
|
||||||
|
|
||||||
|
revise!(dns_val, "ipv6", ipv6_val);
|
||||||
|
|
||||||
|
revise!(
|
||||||
|
dns_val,
|
||||||
|
"enhanced-mode",
|
||||||
|
dns_val
|
||||||
|
.get("enhanced-mode")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("redir-host")
|
||||||
|
);
|
||||||
|
|
||||||
|
revise!(
|
||||||
|
dns_val,
|
||||||
|
"fake-ip-range",
|
||||||
|
dns_val
|
||||||
|
.get("fake-ip-range")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("172.29.0.1/16")
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
crate::utils::resolve::restore_public_dns().await;
|
crate::utils::resolve::restore_public_dns().await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,16 @@ use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
|||||||
// 打开面板
|
// 打开面板
|
||||||
pub fn open_or_close_dashboard() {
|
pub fn open_or_close_dashboard() {
|
||||||
if let Some(window) = handle::Handle::global().get_window() {
|
if let Some(window) = handle::Handle::global().get_window() {
|
||||||
if let Ok(true) = window.is_focused() {
|
// 如果窗口存在,则切换其显示状态
|
||||||
|
if window.is_visible().unwrap_or(false) {
|
||||||
let _ = window.hide();
|
let _ = window.hide();
|
||||||
return;
|
} else {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
resolve::create_window();
|
||||||
}
|
}
|
||||||
resolve::create_window();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重启clash
|
// 重启clash
|
||||||
@@ -72,7 +76,8 @@ pub fn change_clash_mode(mode: String) {
|
|||||||
|
|
||||||
if Config::clash().data().save_config().is_ok() {
|
if Config::clash().data().save_config().is_ok() {
|
||||||
handle::Handle::refresh_clash();
|
handle::Handle::refresh_clash();
|
||||||
log_err!(handle::Handle::update_systray_part());
|
log_err!(tray::Tray::global().update_menu());
|
||||||
|
log_err!(tray::Tray::global().update_icon(None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => log::error!(target: "app", "{err}"),
|
Err(err) => log::error!(target: "app", "{err}"),
|
||||||
@@ -121,14 +126,6 @@ pub fn quit(code: Option<i32>) {
|
|||||||
handle::Handle::global().set_is_exiting();
|
handle::Handle::global().set_is_exiting();
|
||||||
resolve::resolve_reset();
|
resolve::resolve_reset();
|
||||||
log_err!(handle::Handle::global().get_window().unwrap().close());
|
log_err!(handle::Handle::global().get_window().unwrap().close());
|
||||||
match app_handle.save_window_state(StateFlags::all()) {
|
|
||||||
Ok(_) => {
|
|
||||||
log::info!(target: "app", "window state saved successfully");
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!(target: "app", "failed to save window state: {}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
app_handle.exit(code.unwrap_or(0));
|
app_handle.exit(code.unwrap_or(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,16 +138,15 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
|||||||
if patch.get("secret").is_some() || patch.get("external-controller").is_some() {
|
if patch.get("secret").is_some() || patch.get("external-controller").is_some() {
|
||||||
Config::generate().await?;
|
Config::generate().await?;
|
||||||
CoreManager::global().restart_core().await?;
|
CoreManager::global().restart_core().await?;
|
||||||
handle::Handle::refresh_clash();
|
|
||||||
} else {
|
} else {
|
||||||
if patch.get("mode").is_some() {
|
if patch.get("mode").is_some() {
|
||||||
log_err!(handle::Handle::update_systray_part());
|
log_err!(tray::Tray::global().update_menu());
|
||||||
|
log_err!(tray::Tray::global().update_icon(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
Config::runtime().latest().patch_config(patch);
|
Config::runtime().latest().patch_config(patch);
|
||||||
update_core_config(false).await?;
|
CoreManager::global().update_config().await?;
|
||||||
}
|
}
|
||||||
|
handle::Handle::refresh_clash();
|
||||||
<Result<()>>::Ok(())
|
<Result<()>>::Ok(())
|
||||||
};
|
};
|
||||||
match res {
|
match res {
|
||||||
@@ -198,16 +194,23 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
|||||||
let socks_port = patch.verge_socks_port;
|
let socks_port = patch.verge_socks_port;
|
||||||
let http_enabled = patch.verge_http_enabled;
|
let http_enabled = patch.verge_http_enabled;
|
||||||
let http_port = patch.verge_port;
|
let http_port = patch.verge_port;
|
||||||
|
let enable_tray_speed = patch.enable_tray_speed;
|
||||||
|
|
||||||
let res: std::result::Result<(), anyhow::Error> = {
|
let res: std::result::Result<(), anyhow::Error> = {
|
||||||
let mut should_restart_core = false;
|
let mut should_restart_core = false;
|
||||||
let mut should_update_clash_config = false;
|
let mut should_update_clash_config = false;
|
||||||
let mut should_update_launch = false;
|
let mut should_update_launch = false;
|
||||||
let mut should_update_sysproxy = false;
|
let mut should_update_sysproxy = false;
|
||||||
let mut should_update_systray_part = false;
|
let mut should_update_systray_icon = false;
|
||||||
|
let mut should_update_hotkey = false;
|
||||||
|
let mut should_update_systray_menu = false;
|
||||||
|
let mut should_update_systray_tooltip = false;
|
||||||
|
|
||||||
if tun_mode.is_some() {
|
if tun_mode.is_some() {
|
||||||
should_update_clash_config = true;
|
should_update_clash_config = true;
|
||||||
|
should_update_systray_menu = true;
|
||||||
|
should_update_systray_tooltip = true;
|
||||||
|
should_update_systray_icon = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
@@ -230,31 +233,44 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
|||||||
if auto_launch.is_some() {
|
if auto_launch.is_some() {
|
||||||
should_update_launch = true;
|
should_update_launch = true;
|
||||||
}
|
}
|
||||||
if system_proxy.is_some()
|
|
||||||
|| proxy_bypass.is_some()
|
if system_proxy.is_some() {
|
||||||
|| mixed_port.is_some()
|
should_update_sysproxy = true;
|
||||||
|| pac.is_some()
|
should_update_systray_menu = true;
|
||||||
|| pac_content.is_some()
|
should_update_systray_tooltip = true;
|
||||||
{
|
should_update_systray_icon = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxy_bypass.is_some() || pac_content.is_some() || pac.is_some() {
|
||||||
should_update_sysproxy = true;
|
should_update_sysproxy = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if language.is_some()
|
if language.is_some() {
|
||||||
|| system_proxy.is_some()
|
should_update_systray_menu = true;
|
||||||
|| tun_mode.is_some()
|
}
|
||||||
|| common_tray_icon.is_some()
|
if common_tray_icon.is_some()
|
||||||
|| sysproxy_tray_icon.is_some()
|
|| sysproxy_tray_icon.is_some()
|
||||||
|| tun_tray_icon.is_some()
|
|| tun_tray_icon.is_some()
|
||||||
|| tray_icon.is_some()
|
|| tray_icon.is_some()
|
||||||
{
|
{
|
||||||
should_update_systray_part = true;
|
should_update_systray_icon = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if patch.hotkeys.is_some() {
|
||||||
|
should_update_hotkey = true;
|
||||||
|
should_update_systray_menu = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable_tray_speed.is_some() {
|
||||||
|
should_update_systray_icon = true;
|
||||||
|
}
|
||||||
|
|
||||||
if should_restart_core {
|
if should_restart_core {
|
||||||
Config::generate().await?;
|
|
||||||
CoreManager::global().restart_core().await?;
|
CoreManager::global().restart_core().await?;
|
||||||
}
|
}
|
||||||
if should_update_clash_config {
|
if should_update_clash_config {
|
||||||
update_core_config(false).await?;
|
CoreManager::global().update_config().await?;
|
||||||
|
handle::Handle::refresh_clash();
|
||||||
}
|
}
|
||||||
if should_update_launch {
|
if should_update_launch {
|
||||||
sysopt::Sysopt::global().update_launch()?;
|
sysopt::Sysopt::global().update_launch()?;
|
||||||
@@ -264,14 +280,21 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
|||||||
sysopt::Sysopt::global().update_sysproxy().await?;
|
sysopt::Sysopt::global().update_sysproxy().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hotkeys) = patch.hotkeys {
|
if should_update_hotkey {
|
||||||
hotkey::Hotkey::global().update(hotkeys)?;
|
hotkey::Hotkey::global().update(patch.hotkeys.unwrap())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_update_systray_part {
|
if should_update_systray_menu {
|
||||||
handle::Handle::update_systray_part()?;
|
tray::Tray::global().update_menu()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if should_update_systray_icon {
|
||||||
|
tray::Tray::global().update_icon(None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_update_systray_tooltip {
|
||||||
|
tray::Tray::global().update_tooltip()?;
|
||||||
|
}
|
||||||
<Result<()>>::Ok(())
|
<Result<()>>::Ok(())
|
||||||
};
|
};
|
||||||
match res {
|
match res {
|
||||||
@@ -320,31 +343,20 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
|
|||||||
};
|
};
|
||||||
|
|
||||||
if should_update {
|
if should_update {
|
||||||
update_core_config(true).await?;
|
match CoreManager::global().update_config().await {
|
||||||
|
Ok(_) => {
|
||||||
|
handle::Handle::refresh_clash();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
handle::Handle::notice_message("set_config::error", format!("{err}"));
|
||||||
|
log::error!(target: "app", "{err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 更新订阅
|
|
||||||
async fn update_core_config(notice: bool) -> Result<()> {
|
|
||||||
match CoreManager::global().update_config().await {
|
|
||||||
Ok(_) => {
|
|
||||||
handle::Handle::refresh_clash();
|
|
||||||
if notice {
|
|
||||||
handle::Handle::notice_message("set_config::ok", "ok");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
if notice {
|
|
||||||
handle::Handle::notice_message("set_config::error", format!("{err}"));
|
|
||||||
}
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// copy env variable
|
/// copy env variable
|
||||||
pub fn copy_clash_env() {
|
pub fn copy_clash_env() {
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
@@ -356,6 +368,8 @@ pub fn copy_clash_env() {
|
|||||||
format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}");
|
format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}");
|
||||||
let cmd: String = format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}");
|
let cmd: String = format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}");
|
||||||
let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"");
|
let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"");
|
||||||
|
let nu: String =
|
||||||
|
format!("load-env {{ http_proxy: \"{http_proxy}\", https_proxy: \"{http_proxy}\" }}");
|
||||||
|
|
||||||
let cliboard = app_handle.clipboard();
|
let cliboard = app_handle.clipboard();
|
||||||
let env_type = { Config::verge().latest().env_type.clone() };
|
let env_type = { Config::verge().latest().env_type.clone() };
|
||||||
@@ -374,6 +388,7 @@ pub fn copy_clash_env() {
|
|||||||
"bash" => cliboard.write_text(sh).unwrap_or_default(),
|
"bash" => cliboard.write_text(sh).unwrap_or_default(),
|
||||||
"cmd" => cliboard.write_text(cmd).unwrap_or_default(),
|
"cmd" => cliboard.write_text(cmd).unwrap_or_default(),
|
||||||
"powershell" => cliboard.write_text(ps).unwrap_or_default(),
|
"powershell" => cliboard.write_text(ps).unwrap_or_default(),
|
||||||
|
"nushell" => cliboard.write_text(nu).unwrap_or_default(),
|
||||||
_ => log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}"),
|
_ => log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -485,9 +500,9 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
|||||||
|
|
||||||
log_err!(
|
log_err!(
|
||||||
patch_verge(IVerge {
|
patch_verge(IVerge {
|
||||||
webdav_url: webdav_url,
|
webdav_url,
|
||||||
webdav_username: webdav_username,
|
webdav_username,
|
||||||
webdav_password: webdav_password,
|
webdav_password,
|
||||||
..IVerge::default()
|
..IVerge::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ mod feat;
|
|||||||
mod utils;
|
mod utils;
|
||||||
use crate::core::hotkey;
|
use crate::core::hotkey;
|
||||||
use crate::utils::{resolve, resolve::resolve_scheme, server};
|
use crate::utils::{resolve, resolve::resolve_scheme, server};
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
use tauri::Listener;
|
|
||||||
use tauri_plugin_autostart::MacosLauncher;
|
use tauri_plugin_autostart::MacosLauncher;
|
||||||
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
|
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
// 单例检测
|
// 单例检测
|
||||||
@@ -48,30 +47,22 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||||
{
|
{
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
log_err!(app.deep_link().register_all());
|
log_err!(app.deep_link().register_all());
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
app.deep_link().on_open_url(|event| {
|
||||||
app.listen("deep-link://new-url", |event| {
|
tauri::async_runtime::spawn(async move {
|
||||||
tauri::async_runtime::spawn(async move {
|
if let Some(url) = event.urls().first() {
|
||||||
let payload = event.payload();
|
log_err!(resolve_scheme(url.to_string()).await);
|
||||||
log_err!(resolve_scheme(payload.to_owned()).await);
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
tauri::async_runtime::block_on(async move {
|
tauri::async_runtime::block_on(async move {
|
||||||
resolve::resolve_setup(app).await;
|
resolve::resolve_setup(app).await;
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
{
|
|
||||||
let argvs: Vec<String> = std::env::args().collect();
|
|
||||||
if argvs.len() > 1 {
|
|
||||||
log_err!(resolve_scheme(argvs[1].to_owned()).await);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -86,7 +77,6 @@ pub fn run() {
|
|||||||
cmds::open_core_dir,
|
cmds::open_core_dir,
|
||||||
cmds::get_portable_flag,
|
cmds::get_portable_flag,
|
||||||
cmds::get_network_interfaces,
|
cmds::get_network_interfaces,
|
||||||
// cmds::kill_sidecar,
|
|
||||||
cmds::restart_core,
|
cmds::restart_core,
|
||||||
cmds::restart_app,
|
cmds::restart_app,
|
||||||
// clash
|
// clash
|
||||||
@@ -109,7 +99,6 @@ pub fn run() {
|
|||||||
cmds::open_devtools,
|
cmds::open_devtools,
|
||||||
cmds::exit_app,
|
cmds::exit_app,
|
||||||
cmds::get_network_interfaces_info,
|
cmds::get_network_interfaces_info,
|
||||||
// cmds::update_hotkeys,
|
|
||||||
// profile
|
// profile
|
||||||
cmds::get_profiles,
|
cmds::get_profiles,
|
||||||
cmds::enhance_profiles,
|
cmds::enhance_profiles,
|
||||||
@@ -146,7 +135,6 @@ pub fn run() {
|
|||||||
tauri::RunEvent::ExitRequested { api, code, .. } => {
|
tauri::RunEvent::ExitRequested { api, code, .. } => {
|
||||||
if code.is_none() {
|
if code.is_none() {
|
||||||
api.prevent_exit();
|
api.prevent_exit();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use nanoid::nanoid;
|
|||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use serde_yaml::{Mapping, Value};
|
use serde_yaml::{Mapping, Value};
|
||||||
use std::{fs, path::PathBuf, str::FromStr};
|
use std::{fs, path::PathBuf, str::FromStr};
|
||||||
use tauri_plugin_shell::ShellExt;
|
|
||||||
|
|
||||||
/// read data from yaml as struct T
|
/// read data from yaml as struct T
|
||||||
pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
|
pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
|
||||||
@@ -99,44 +98,24 @@ pub fn get_last_part_and_decode(url: &str) -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// open file
|
/// open file
|
||||||
/// try to use vscode first, if not found then use system default app
|
pub fn open_file(_: tauri::AppHandle, path: PathBuf) -> Result<()> {
|
||||||
pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> {
|
open::that_detached(path.as_os_str())?;
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let code = "Visual Studio Code";
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
let code = "code";
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
let vscode_exists = {
|
|
||||||
use std::process::Command;
|
|
||||||
Command::new("where").arg("code").output().is_ok()
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let vscode_exists = {
|
|
||||||
use std::process::Command;
|
|
||||||
Command::new("which").arg("code").output().is_ok()
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
let vscode_exists = {
|
|
||||||
use std::process::Command;
|
|
||||||
Command::new("which").arg("code").output().is_ok()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果 VS Code 存在就用它打开,否则用系统默认程序
|
|
||||||
if vscode_exists {
|
|
||||||
if let Err(err) = open::with(&path.as_os_str(), code) {
|
|
||||||
log::error!(target: "app", "Failed to open with VS Code: {}", err);
|
|
||||||
app.shell().open(path.to_string_lossy(), None)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
app.shell().open(path.to_string_lossy(), None)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub fn is_monochrome_image_from_bytes(data: &[u8]) -> anyhow::Result<bool> {
|
||||||
|
let img = image::load_from_memory(data)?;
|
||||||
|
let rgb_img = img.to_rgb8();
|
||||||
|
|
||||||
|
for pixel in rgb_img.pixels() {
|
||||||
|
if pixel[0] != pixel[1] || pixel[1] != pixel[2] {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn linux_elevator() -> String {
|
pub fn linux_elevator() -> String {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -222,22 +201,35 @@ macro_rules! t {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
/// 将字节数转换为可读的流量字符串
|
||||||
fn test_parse_value() {
|
/// 支持 B/s、KB/s、MB/s、GB/s 的自动转换
|
||||||
let test_1 = "upload=111; download=2222; total=3333; expire=444";
|
///
|
||||||
let test_2 = "attachment; filename=Clash.yaml";
|
/// # Examples
|
||||||
|
/// ```
|
||||||
assert_eq!(parse_str::<usize>(test_1, "upload").unwrap(), 111);
|
/// assert_eq!(format_bytes_speed(1000), "1000B/s");
|
||||||
assert_eq!(parse_str::<usize>(test_1, "download").unwrap(), 2222);
|
/// assert_eq!(format_bytes_speed(1024), "1.0KB/s");
|
||||||
assert_eq!(parse_str::<usize>(test_1, "total").unwrap(), 3333);
|
/// assert_eq!(format_bytes_speed(1024 * 1024), "1.0MB/s");
|
||||||
assert_eq!(parse_str::<usize>(test_1, "expire").unwrap(), 444);
|
/// ```
|
||||||
assert_eq!(
|
#[cfg(target_os = "macos")]
|
||||||
parse_str::<String>(test_2, "filename").unwrap(),
|
pub fn format_bytes_speed(speed: u64) -> String {
|
||||||
format!("Clash.yaml")
|
if speed < 1024 {
|
||||||
);
|
format!("{}B/s", speed)
|
||||||
|
} else if speed < 1024 * 1024 {
|
||||||
assert_eq!(parse_str::<usize>(test_1, "aaa"), None);
|
format!("{:.1}KB/s", speed as f64 / 1024.0)
|
||||||
assert_eq!(parse_str::<usize>(test_1, "upload1"), None);
|
} else if speed < 1024 * 1024 * 1024 {
|
||||||
assert_eq!(parse_str::<usize>(test_1, "expire1"), None);
|
format!("{:.1}MB/s", speed as f64 / 1024.0 / 1024.0)
|
||||||
assert_eq!(parse_str::<usize>(test_2, "attachment"), None);
|
} else {
|
||||||
|
format!("{:.1}GB/s", speed as f64 / 1024.0 / 1024.0 / 1024.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_bytes_speed() {
|
||||||
|
assert_eq!(format_bytes_speed(0), "0B/s");
|
||||||
|
assert_eq!(format_bytes_speed(1023), "1023B/s");
|
||||||
|
assert_eq!(format_bytes_speed(1024), "1.0KB/s");
|
||||||
|
assert_eq!(format_bytes_speed(1024 * 1024), "1.0MB/s");
|
||||||
|
assert_eq!(format_bytes_speed(1024 * 1024 * 1024), "1.0GB/s");
|
||||||
|
assert_eq!(format_bytes_speed(1024 * 500), "500.0KB/s");
|
||||||
|
assert_eq!(format_bytes_speed(1024 * 1024 * 2), "2.0MB/s");
|
||||||
}
|
}
|
||||||
|
|||||||
88
src-tauri/src/utils/i18n.rs
Normal file
88
src-tauri/src/utils/i18n.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
use crate::config::Config;
|
||||||
|
use crate::utils::dirs;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::{collections::HashMap, fs, path::PathBuf};
|
||||||
|
use sys_locale;
|
||||||
|
|
||||||
|
const DEFAULT_LANGUAGE: &str = "zh";
|
||||||
|
|
||||||
|
fn get_locales_dir() -> Option<PathBuf> {
|
||||||
|
dirs::app_resources_dir()
|
||||||
|
.map(|resource_path| resource_path.join("locales"))
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_supported_languages() -> Vec<String> {
|
||||||
|
let mut languages = Vec::new();
|
||||||
|
|
||||||
|
if let Some(locales_dir) = get_locales_dir() {
|
||||||
|
if let Ok(entries) = fs::read_dir(locales_dir) {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
if let Some(file_name) = entry.file_name().to_str() {
|
||||||
|
if let Some(lang) = file_name.strip_suffix(".json") {
|
||||||
|
languages.push(lang.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if languages.is_empty() {
|
||||||
|
languages.push(DEFAULT_LANGUAGE.to_string());
|
||||||
|
}
|
||||||
|
languages
|
||||||
|
}
|
||||||
|
|
||||||
|
static TRANSLATIONS: Lazy<HashMap<String, Value>> = Lazy::new(|| {
|
||||||
|
let mut translations = HashMap::new();
|
||||||
|
|
||||||
|
if let Some(locales_dir) = get_locales_dir() {
|
||||||
|
for lang in get_supported_languages() {
|
||||||
|
let file_path = locales_dir.join(format!("{}.json", lang));
|
||||||
|
if let Ok(content) = fs::read_to_string(file_path) {
|
||||||
|
if let Ok(json) = serde_json::from_str(&content) {
|
||||||
|
translations.insert(lang.to_string(), json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
translations
|
||||||
|
});
|
||||||
|
|
||||||
|
fn get_system_language() -> String {
|
||||||
|
sys_locale::get_locale()
|
||||||
|
.map(|locale| locale.to_lowercase())
|
||||||
|
.and_then(|locale| locale.split(['_', '-']).next().map(String::from))
|
||||||
|
.filter(|lang| get_supported_languages().contains(lang))
|
||||||
|
.unwrap_or_else(|| DEFAULT_LANGUAGE.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn t(key: &str) -> String {
|
||||||
|
let current_lang = Config::verge()
|
||||||
|
.latest()
|
||||||
|
.language
|
||||||
|
.as_deref()
|
||||||
|
.map(String::from)
|
||||||
|
.unwrap_or_else(get_system_language);
|
||||||
|
|
||||||
|
if let Some(text) = TRANSLATIONS
|
||||||
|
.get(¤t_lang)
|
||||||
|
.and_then(|trans| trans.get(key))
|
||||||
|
.and_then(|val| val.as_str())
|
||||||
|
{
|
||||||
|
return text.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_lang != DEFAULT_LANGUAGE {
|
||||||
|
if let Some(text) = TRANSLATIONS
|
||||||
|
.get(DEFAULT_LANGUAGE)
|
||||||
|
.and_then(|trans| trans.get(key))
|
||||||
|
.and_then(|val| val.as_str())
|
||||||
|
{
|
||||||
|
return text.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key.to_string()
|
||||||
|
}
|
||||||
@@ -193,12 +193,7 @@ pub fn init_resources() -> Result<()> {
|
|||||||
let _ = fs::create_dir_all(&res_dir);
|
let _ = fs::create_dir_all(&res_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
|
let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
let file_list: [&str; 0] = [];
|
|
||||||
|
|
||||||
// copy the resource file
|
// copy the resource file
|
||||||
// if the source file is newer than the destination file, copy it over
|
// if the source file is newer than the destination file, copy it over
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ pub mod init;
|
|||||||
pub mod resolve;
|
pub mod resolve;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod tmpl;
|
pub mod tmpl;
|
||||||
|
pub mod i18n;
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
use crate::config::IVerge;
|
use crate::config::IVerge;
|
||||||
use crate::utils::error;
|
use crate::utils::error;
|
||||||
use crate::{config::Config, config::PrfItem, core::*, utils::init, utils::server};
|
use crate::{config::Config, config::PrfItem, core::*, utils::init, utils::server};
|
||||||
use crate::{log_err, trace_err, wrap_err};
|
use crate::{log_err, wrap_err};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use percent_encoding::percent_decode_str;
|
use percent_encoding::percent_decode_str;
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
use tauri::{App, Manager};
|
use tauri::{App, Manager};
|
||||||
use tauri_plugin_window_state::{StateFlags, WindowExt};
|
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
//#[cfg(not(target_os = "linux"))]
|
//#[cfg(not(target_os = "linux"))]
|
||||||
@@ -93,7 +92,8 @@ pub async fn resolve_setup(app: &mut App) {
|
|||||||
server::embed_server();
|
server::embed_server();
|
||||||
|
|
||||||
log::trace!(target: "app", "init system tray");
|
log::trace!(target: "app", "init system tray");
|
||||||
log_err!(tray::Tray::create_systray());
|
log_err!(tray::Tray::global().init());
|
||||||
|
log_err!(tray::Tray::global().create_systray());
|
||||||
|
|
||||||
let silent_start = { Config::verge().data().enable_silent_start };
|
let silent_start = { Config::verge().data().enable_silent_start };
|
||||||
if !silent_start.unwrap_or(false) {
|
if !silent_start.unwrap_or(false) {
|
||||||
@@ -103,7 +103,7 @@ pub async fn resolve_setup(app: &mut App) {
|
|||||||
log_err!(sysopt::Sysopt::global().update_sysproxy().await);
|
log_err!(sysopt::Sysopt::global().update_sysproxy().await);
|
||||||
log_err!(sysopt::Sysopt::global().init_guard_sysproxy());
|
log_err!(sysopt::Sysopt::global().init_guard_sysproxy());
|
||||||
|
|
||||||
log_err!(handle::Handle::update_systray_part());
|
log_err!(tray::Tray::global().update_part());
|
||||||
log_err!(hotkey::Hotkey::global().init());
|
log_err!(hotkey::Hotkey::global().init());
|
||||||
log_err!(timer::Timer::global().init());
|
log_err!(timer::Timer::global().init());
|
||||||
}
|
}
|
||||||
@@ -111,6 +111,9 @@ pub async fn resolve_setup(app: &mut App) {
|
|||||||
/// reset system proxy
|
/// reset system proxy
|
||||||
pub fn resolve_reset() {
|
pub fn resolve_reset() {
|
||||||
tauri::async_runtime::block_on(async move {
|
tauri::async_runtime::block_on(async move {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
tray::Tray::global().unsubscribe_traffic();
|
||||||
|
|
||||||
log_err!(sysopt::Sysopt::global().reset_sysproxy().await);
|
log_err!(sysopt::Sysopt::global().reset_sysproxy().await);
|
||||||
log_err!(CoreManager::global().stop_core().await);
|
log_err!(CoreManager::global().stop_core().await);
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -123,68 +126,58 @@ pub fn create_window() {
|
|||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
|
|
||||||
if let Some(window) = handle::Handle::global().get_window() {
|
if let Some(window) = handle::Handle::global().get_window() {
|
||||||
trace_err!(window.show(), "set win visible");
|
if window.is_minimized().unwrap_or(false) {
|
||||||
trace_err!(window.set_focus(), "set win focus");
|
let _ = window.unminimize();
|
||||||
|
}
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let builder = tauri::WebviewWindowBuilder::new(
|
#[cfg(target_os = "windows")]
|
||||||
|
let _ = tauri::WebviewWindowBuilder::new(
|
||||||
|
&app_handle,
|
||||||
|
"main".to_string(),
|
||||||
|
tauri::WebviewUrl::App("index.html".into()),
|
||||||
|
)
|
||||||
|
.title("Clash Verge")
|
||||||
|
.visible(false)
|
||||||
|
.inner_size(890.0, 700.0)
|
||||||
|
.min_inner_size(620.0, 550.0)
|
||||||
|
.decorations(false)
|
||||||
|
.maximizable(true)
|
||||||
|
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
|
||||||
|
.transparent(true)
|
||||||
|
.shadow(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let _ = tauri::WebviewWindowBuilder::new(
|
||||||
|
&app_handle,
|
||||||
|
"main".to_string(),
|
||||||
|
tauri::WebviewUrl::App("index.html".into()),
|
||||||
|
)
|
||||||
|
.decorations(true)
|
||||||
|
.hidden_title(true)
|
||||||
|
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||||
|
.inner_size(890.0, 700.0)
|
||||||
|
.min_inner_size(620.0, 550.0)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let _ = tauri::WebviewWindowBuilder::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
"main".to_string(),
|
"main".to_string(),
|
||||||
tauri::WebviewUrl::App("index.html".into()),
|
tauri::WebviewUrl::App("index.html".into()),
|
||||||
)
|
)
|
||||||
.title("Clash Verge")
|
.title("Clash Verge")
|
||||||
.visible(false)
|
.decorations(false)
|
||||||
.fullscreen(false)
|
.inner_size(890.0, 700.0)
|
||||||
.min_inner_size(600.0, 520.0);
|
.min_inner_size(620.0, 550.0)
|
||||||
|
.transparent(true)
|
||||||
#[cfg(target_os = "windows")]
|
.build()
|
||||||
let window = builder
|
.unwrap();
|
||||||
.decorations(false)
|
|
||||||
.maximizable(true)
|
|
||||||
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
|
|
||||||
.transparent(true)
|
|
||||||
.visible(false)
|
|
||||||
.build().unwrap();
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let window = builder
|
|
||||||
.decorations(true)
|
|
||||||
.hidden_title(true)
|
|
||||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
let window = builder
|
|
||||||
.decorations(false)
|
|
||||||
.transparent(true)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
match window.restore_state(StateFlags::all()) {
|
|
||||||
Ok(_) => {
|
|
||||||
log::info!(target: "app", "window state restored successfully");
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!(target: "app", "failed to restore window state: {}", e);
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
window
|
|
||||||
.set_size(tauri::Size::Physical(tauri::PhysicalSize {
|
|
||||||
width: 800,
|
|
||||||
height: 636,
|
|
||||||
}))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
window
|
|
||||||
.set_size(tauri::Size::Physical(tauri::PhysicalSize {
|
|
||||||
width: 800,
|
|
||||||
height: 642,
|
|
||||||
}))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn resolve_scheme(param: String) -> Result<()> {
|
pub async fn resolve_scheme(param: String) -> Result<()> {
|
||||||
@@ -230,6 +223,8 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
|
|||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
let uid = item.uid.clone().unwrap();
|
let uid = item.uid.clone().unwrap();
|
||||||
let _ = wrap_err!(Config::profiles().data().append_item(item));
|
let _ = wrap_err!(Config::profiles().data().append_item(item));
|
||||||
|
handle::Handle::notice_message("import_sub_url::ok", uid);
|
||||||
|
|
||||||
app_handle
|
app_handle
|
||||||
.notification()
|
.notification()
|
||||||
.builder()
|
.builder()
|
||||||
@@ -237,10 +232,9 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
|
|||||||
.body("Import profile success")
|
.body("Import profile success")
|
||||||
.show()
|
.show()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
handle::Handle::notice_message("import_sub_url::ok", uid);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
handle::Handle::notice_message("import_sub_url::error", e.to_string());
|
||||||
app_handle
|
app_handle
|
||||||
.notification()
|
.notification()
|
||||||
.builder()
|
.builder()
|
||||||
@@ -248,8 +242,6 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
|
|||||||
.body(format!("Import profile failed: {e}"))
|
.body(format!("Import profile failed: {e}"))
|
||||||
.show()
|
.show()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
handle::Handle::notice_message("import_sub_url::error", e.to_string());
|
|
||||||
bail!("Failed to add subscriptions: {e}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
"resources": ["resources"],
|
"resources": ["resources", "resources/locales/*"],
|
||||||
"publisher": "Clash Verge Rev",
|
"publisher": "Clash Verge Rev",
|
||||||
"externalBin": ["sidecar/verge-mihomo", "sidecar/verge-mihomo-alpha"],
|
"externalBin": ["sidecar/verge-mihomo", "sidecar/verge-mihomo-alpha"],
|
||||||
"copyright": "GNU General Public License v3.0",
|
"copyright": "GNU General Public License v3.0",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"devUrl": "http://localhost:3000/"
|
"devUrl": "http://localhost:3000/"
|
||||||
},
|
},
|
||||||
"productName": "Clash Verge",
|
"productName": "Clash Verge",
|
||||||
"version": "2.0.1",
|
"version": "2.0.3",
|
||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
|
|||||||
@@ -3,13 +3,12 @@
|
|||||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["app", "dmg"],
|
"targets": ["app", "dmg"],
|
||||||
"resources": ["resources"],
|
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"frameworks": [],
|
"frameworks": [],
|
||||||
"minimumSystemVersion": "10.15",
|
"minimumSystemVersion": "10.15",
|
||||||
"exceptionDomain": "",
|
"exceptionDomain": "",
|
||||||
"signingIdentity": null,
|
"signingIdentity": null,
|
||||||
"entitlements": null,
|
"entitlements": "packages/macos/entitlements.plist",
|
||||||
"dmg": {
|
"dmg": {
|
||||||
"background": "images/background.png",
|
"background": "images/background.png",
|
||||||
"appPosition": {
|
"appPosition": {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const Switch = styled((props: SwitchProps) => (
|
|||||||
},
|
},
|
||||||
"& .MuiSwitch-track": {
|
"& .MuiSwitch-track": {
|
||||||
borderRadius: 26 / 2,
|
borderRadius: 26 / 2,
|
||||||
backgroundColor: theme.palette.mode === "light" ? "#E9E9EA" : "#39393D",
|
backgroundColor: theme.palette.mode === "light" ? "#BBBBBB" : "#39393D",
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transition: theme.transitions.create(["background-color"], {
|
transition: theme.transitions.create(["background-color"], {
|
||||||
duration: 500,
|
duration: 500,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const LayoutTraffic = () => {
|
|||||||
const { server = "", secret = "" } = clashInfo!;
|
const { server = "", secret = "" } = clashInfo!;
|
||||||
|
|
||||||
const s = createSockette(
|
const s = createSockette(
|
||||||
`ws://${server}/traffic?token=${encodeURIComponent(secret)}`,
|
`ws://${server}${secret ? `/traffic?token=${encodeURIComponent(secret)}` : "/traffic"}`,
|
||||||
{
|
{
|
||||||
onmessage(event) {
|
onmessage(event) {
|
||||||
const data = JSON.parse(event.data) as ITrafficItem;
|
const data = JSON.parse(event.data) as ITrafficItem;
|
||||||
@@ -86,7 +86,7 @@ export const LayoutTraffic = () => {
|
|||||||
const { server = "", secret = "" } = clashInfo!;
|
const { server = "", secret = "" } = clashInfo!;
|
||||||
|
|
||||||
const s = createSockette(
|
const s = createSockette(
|
||||||
`ws://${server}/memory?token=${encodeURIComponent(secret)}`,
|
`ws://${server}${secret ? `/memory?token=${encodeURIComponent(secret)}` : "/memory"}`,
|
||||||
{
|
{
|
||||||
onmessage(event) {
|
onmessage(event) {
|
||||||
const data = JSON.parse(event.data) as MemoryUsage;
|
const data = JSON.parse(event.data) as MemoryUsage;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { IconButton, Fade } from "@mui/material";
|
import { IconButton, Fade, SxProps, Theme } from "@mui/material";
|
||||||
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
|
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ScrollTopButton = ({ onClick, show }: Props) => {
|
export const ScrollTopButton = ({ onClick, show, sx }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Fade in={show}>
|
<Fade in={show}>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -26,6 +27,7 @@ export const ScrollTopButton = ({ onClick, show }: Props) => {
|
|||||||
: "rgba(0,0,0,0.2)",
|
: "rgba(0,0,0,0.2)",
|
||||||
},
|
},
|
||||||
visibility: show ? "visible" : "hidden",
|
visibility: show ? "visible" : "hidden",
|
||||||
|
...sx,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<KeyboardArrowUpIcon />
|
<KeyboardArrowUpIcon />
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export const useCustomTheme = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// css
|
// css
|
||||||
const backgroundColor = mode === "light" ? "#f0f0f0" : "#2e303d";
|
const backgroundColor = mode === "light" ? "#ECECEC" : "#2e303d";
|
||||||
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
|
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
|
||||||
const scrollColor = mode === "light" ? "#90939980" : "#54545480";
|
const scrollColor = mode === "light" ? "#90939980" : "#54545480";
|
||||||
const dividerColor =
|
const dividerColor =
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const editorDidMount = async (
|
const editorDidMount = async (
|
||||||
editor: monaco.editor.IStandaloneCodeEditor
|
editor: monaco.editor.IStandaloneCodeEditor,
|
||||||
) => {
|
) => {
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
|
|||||||
fontFamily: `Fira Code, JetBrains Mono, Roboto Mono, "Source Code Pro", Consolas, Menlo, Monaco, monospace, "Courier New", "Apple Color Emoji"${
|
fontFamily: `Fira Code, JetBrains Mono, Roboto Mono, "Source Code Pro", Consolas, Menlo, Monaco, monospace, "Courier New", "Apple Color Emoji"${
|
||||||
getSystem() === "windows" ? ", twemoji mozilla" : ""
|
getSystem() === "windows" ? ", twemoji mozilla" : ""
|
||||||
}`,
|
}`,
|
||||||
fontLigatures: true, // 连字符
|
fontLigatures: false, // 连字符
|
||||||
smoothScrolling: true, // 平滑滚动
|
smoothScrolling: true, // 平滑滚动
|
||||||
}}
|
}}
|
||||||
editorWillMount={editorWillMount}
|
editorWillMount={editorWillMount}
|
||||||
|
|||||||
@@ -104,14 +104,14 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
<BaseDialog
|
<BaseDialog
|
||||||
open={open}
|
open={open}
|
||||||
title={t("Backup Setting")}
|
title={t("Backup Setting")}
|
||||||
contentSx={{ width: 600, maxHeight: 800 }}
|
// contentSx={{ width: 600, maxHeight: 800 }}
|
||||||
okBtn={t("")}
|
okBtn={t("")}
|
||||||
cancelBtn={t("Close")}
|
cancelBtn={t("Close")}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
disableOk
|
disableOk
|
||||||
>
|
>
|
||||||
<Box sx={{ maxWidth: 800 }}>
|
<Box>
|
||||||
<BaseLoadingOverlay isLoading={isLoading} />
|
<BaseLoadingOverlay isLoading={isLoading} />
|
||||||
<Paper elevation={2} sx={{ padding: 2 }}>
|
<Paper elevation={2} sx={{ padding: 2 }}>
|
||||||
<BackupConfigViewer
|
<BackupConfigViewer
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
styled,
|
styled,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
|
Box,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
|
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
|
||||||
@@ -163,6 +164,21 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</Item>
|
</Item>
|
||||||
)}
|
)}
|
||||||
|
{OS === "macos" && (
|
||||||
|
<Item>
|
||||||
|
<ListItemText primary={t("Enable Tray Speed")} />
|
||||||
|
<GuardState
|
||||||
|
value={verge?.enable_tray_speed ?? true}
|
||||||
|
valueProps="checked"
|
||||||
|
onCatch={onError}
|
||||||
|
onFormat={onSwitchFormat}
|
||||||
|
onChange={(e) => onChangeData({ enable_tray_speed: e })}
|
||||||
|
onGuard={(e) => patchVerge({ enable_tray_speed: e })}
|
||||||
|
>
|
||||||
|
<Switch edge="end" />
|
||||||
|
</GuardState>
|
||||||
|
</Item>
|
||||||
|
)}
|
||||||
|
|
||||||
<Item>
|
<Item>
|
||||||
<ListItemText primary={t("Common Tray Icon")} />
|
<ListItemText primary={t("Common Tray Icon")} />
|
||||||
|
|||||||
@@ -103,7 +103,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
primary={t("Auto Close Connections")}
|
primary={t("Auto Close Connections")}
|
||||||
sx={{ maxWidth: "fit-content" }}
|
sx={{ maxWidth: "fit-content" }}
|
||||||
/>
|
/>
|
||||||
<TooltipIcon title={t("Auto Close Connections Info")} />
|
<TooltipIcon
|
||||||
|
title={t("Auto Close Connections Info")}
|
||||||
|
sx={{ opacity: "0.7" }}
|
||||||
|
/>
|
||||||
<Switch
|
<Switch
|
||||||
edge="end"
|
edge="end"
|
||||||
checked={values.autoCloseConnection}
|
checked={values.autoCloseConnection}
|
||||||
@@ -130,7 +133,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
primary={t("Enable Builtin Enhanced")}
|
primary={t("Enable Builtin Enhanced")}
|
||||||
sx={{ maxWidth: "fit-content" }}
|
sx={{ maxWidth: "fit-content" }}
|
||||||
/>
|
/>
|
||||||
<TooltipIcon title={t("Enable Builtin Enhanced Info")} />
|
<TooltipIcon
|
||||||
|
title={t("Enable Builtin Enhanced Info")}
|
||||||
|
sx={{ opacity: "0.7" }}
|
||||||
|
/>
|
||||||
<Switch
|
<Switch
|
||||||
edge="end"
|
edge="end"
|
||||||
checked={values.enableBuiltinEnhanced}
|
checked={values.enableBuiltinEnhanced}
|
||||||
@@ -196,7 +202,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
primary={t("Default Latency Test")}
|
primary={t("Default Latency Test")}
|
||||||
sx={{ maxWidth: "fit-content" }}
|
sx={{ maxWidth: "fit-content" }}
|
||||||
/>
|
/>
|
||||||
<TooltipIcon title={t("Default Latency Test Info")} />
|
<TooltipIcon
|
||||||
|
title={t("Default Latency Test Info")}
|
||||||
|
sx={{ opacity: "0.7" }}
|
||||||
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import { forwardRef, useImperativeHandle, useMemo, useState } from "react";
|
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
|
||||||
import { useLockFn } from "ahooks";
|
import { BaseFieldset } from "@/components/base/base-fieldset";
|
||||||
import { useTranslation } from "react-i18next";
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
|
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||||
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
|
import { getAutotemProxy, getSystemProxy } from "@/services/cmds";
|
||||||
|
import getSystem from "@/utils/get-system";
|
||||||
|
import { EditRounded } from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
@@ -9,16 +15,10 @@ import {
|
|||||||
styled,
|
styled,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
Button,
|
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useLockFn } from "ahooks";
|
||||||
import { getSystemProxy, getAutotemProxy } from "@/services/cmds";
|
import { forwardRef, useImperativeHandle, useMemo, useState } from "react";
|
||||||
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
|
import { useTranslation } from "react-i18next";
|
||||||
import { EditRounded } from "@mui/icons-material";
|
|
||||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
|
||||||
import { BaseFieldset } from "@/components/base/base-fieldset";
|
|
||||||
import getSystem from "@/utils/get-system";
|
|
||||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
|
||||||
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
|
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
|
||||||
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
|
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
|
||||||
}`;
|
}`;
|
||||||
@@ -90,6 +90,16 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
pac_content: pac_file_content ?? DEFAULT_PAC,
|
pac_content: pac_file_content ?? DEFAULT_PAC,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const defaultBypass = () => {
|
||||||
|
if (isWindows) {
|
||||||
|
return "localhost;127.*;192.168.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;<local>";
|
||||||
|
}
|
||||||
|
if (getSystem() === "linux") {
|
||||||
|
return "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,::1";
|
||||||
|
}
|
||||||
|
return "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,localhost,*.local,*.crashlytics.com,<local>";
|
||||||
|
};
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
@@ -112,6 +122,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
Notice.error(t("Proxy Daemon Duration Cannot be Less than 1 Second"));
|
Notice.error(t("Proxy Daemon Duration Cannot be Less than 1 Second"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (value.bypass && !validReg.test(value.bypass)) {
|
||||||
|
Notice.error(t("Invalid Bypass Format"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const patch: Partial<IVergeConfig> = {};
|
const patch: Partial<IVergeConfig> = {};
|
||||||
|
|
||||||
@@ -124,6 +138,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
if (value.bypass !== system_proxy_bypass) {
|
if (value.bypass !== system_proxy_bypass) {
|
||||||
patch.system_proxy_bypass = value.bypass;
|
patch.system_proxy_bypass = value.bypass;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.pac !== proxy_auto_config) {
|
if (value.pac !== proxy_auto_config) {
|
||||||
patch.proxy_auto_config = value.pac;
|
patch.proxy_auto_config = value.pac;
|
||||||
}
|
}
|
||||||
@@ -133,10 +148,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
if (value.pac_content !== pac_file_content) {
|
if (value.pac_content !== pac_file_content) {
|
||||||
patch.pac_file_content = value.pac_content;
|
patch.pac_file_content = value.pac_content;
|
||||||
}
|
}
|
||||||
if (value.bypass && !validReg.test(value.bypass)) {
|
|
||||||
Notice.error(t("Invalid Bypass Format"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await patchVerge(patch);
|
await patchVerge(patch);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -166,8 +178,8 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
? t("Enabled")
|
? t("Enabled")
|
||||||
: t("Disabled")
|
: t("Disabled")
|
||||||
: sysproxy?.enable
|
: sysproxy?.enable
|
||||||
? t("Enabled")
|
? t("Enabled")
|
||||||
: t("Disabled")}
|
: t("Disabled")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
{!value.pac && (
|
{!value.pac && (
|
||||||
@@ -202,7 +214,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
primary={t("Proxy Guard")}
|
primary={t("Proxy Guard")}
|
||||||
sx={{ maxWidth: "fit-content" }}
|
sx={{ maxWidth: "fit-content" }}
|
||||||
/>
|
/>
|
||||||
<TooltipIcon title={t("Proxy Guard Info")} />
|
<TooltipIcon title={t("Proxy Guard Info")} sx={{ opacity: "0.7" }} />
|
||||||
<Switch
|
<Switch
|
||||||
edge="end"
|
edge="end"
|
||||||
disabled={!enabled}
|
disabled={!enabled}
|
||||||
@@ -215,7 +227,6 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
<ListItem sx={{ padding: "5px 2px" }}>
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
<ListItemText primary={t("Guard Duration")} />
|
<ListItemText primary={t("Guard Duration")} />
|
||||||
<TextField
|
<TextField
|
||||||
autoComplete="new-password"
|
|
||||||
disabled={!enabled}
|
disabled={!enabled}
|
||||||
size="small"
|
size="small"
|
||||||
value={value.duration}
|
value={value.duration}
|
||||||
@@ -242,11 +253,11 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
{!value.pac && (
|
|
||||||
|
{!value.pac && !value.use_default && (
|
||||||
<>
|
<>
|
||||||
<ListItemText primary={t("Proxy Bypass")} />
|
<ListItemText primary={t("Proxy Bypass")} />
|
||||||
<TextField
|
<TextField
|
||||||
autoComplete="new-password"
|
|
||||||
error={value.bypass ? !validReg.test(value.bypass) : false}
|
error={value.bypass ? !validReg.test(value.bypass) : false}
|
||||||
disabled={!enabled}
|
disabled={!enabled}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -258,20 +269,25 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
setValue((v) => ({ ...v, bypass: e.target.value }));
|
setValue((v) => ({ ...v, bypass: e.target.value }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!value.pac && value.use_default && (
|
||||||
|
<>
|
||||||
<ListItemText primary={t("Bypass")} />
|
<ListItemText primary={t("Bypass")} />
|
||||||
<FlexBox>
|
<FlexBox>
|
||||||
<TextField
|
<TextField
|
||||||
autoComplete="new-password"
|
|
||||||
disabled={true}
|
disabled={true}
|
||||||
size="small"
|
size="small"
|
||||||
multiline
|
multiline
|
||||||
rows={4}
|
rows={4}
|
||||||
sx={{ width: "100%" }}
|
sx={{ width: "100%" }}
|
||||||
value={sysproxy?.bypass || "-"}
|
value={defaultBypass()}
|
||||||
/>
|
/>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{value.pac && (
|
{value.pac && (
|
||||||
<>
|
<>
|
||||||
<ListItem sx={{ padding: "5px 2px", alignItems: "start" }}>
|
<ListItem sx={{ padding: "5px 2px", alignItems: "start" }}>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [values, setValues] = useState({
|
const [values, setValues] = useState({
|
||||||
stack: "gvisor",
|
stack: "mixed",
|
||||||
device: "Mihomo",
|
device: "Mihomo",
|
||||||
autoRoute: true,
|
autoRoute: true,
|
||||||
autoDetectInterface: true,
|
autoDetectInterface: true,
|
||||||
@@ -35,7 +35,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
open: () => {
|
open: () => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setValues({
|
setValues({
|
||||||
stack: clash?.tun.stack ?? "gvisor",
|
stack: clash?.tun.stack ?? "mixed",
|
||||||
device: clash?.tun.device ?? "Mihomo",
|
device: clash?.tun.device ?? "Mihomo",
|
||||||
autoRoute: clash?.tun["auto-route"] ?? true,
|
autoRoute: clash?.tun["auto-route"] ?? true,
|
||||||
autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
|
autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
|
||||||
@@ -64,7 +64,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
...(old! || {}),
|
...(old! || {}),
|
||||||
tun,
|
tun,
|
||||||
}),
|
}),
|
||||||
false
|
false,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await enhanceProfiles();
|
await enhanceProfiles();
|
||||||
@@ -89,7 +89,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
let tun = {
|
let tun = {
|
||||||
stack: "gvisor",
|
stack: "mixed",
|
||||||
device: "Mihomo",
|
device: "Mihomo",
|
||||||
"auto-route": true,
|
"auto-route": true,
|
||||||
"auto-detect-interface": true,
|
"auto-detect-interface": true,
|
||||||
@@ -98,7 +98,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
mtu: 1500,
|
mtu: 1500,
|
||||||
};
|
};
|
||||||
setValues({
|
setValues({
|
||||||
stack: "gvisor",
|
stack: "mixed",
|
||||||
device: "Mihomo",
|
device: "Mihomo",
|
||||||
autoRoute: true,
|
autoRoute: true,
|
||||||
autoDetectInterface: true,
|
autoDetectInterface: true,
|
||||||
@@ -112,7 +112,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
...(old! || {}),
|
...(old! || {}),
|
||||||
tun,
|
tun,
|
||||||
}),
|
}),
|
||||||
false
|
false,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -209,7 +209,12 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
<SettingItem
|
<SettingItem
|
||||||
onClick={invoke_uwp_tool}
|
onClick={invoke_uwp_tool}
|
||||||
label={t("Open UWP tool")}
|
label={t("Open UWP tool")}
|
||||||
extra={<TooltipIcon title={t("Open UWP tool Info")} />}
|
extra={
|
||||||
|
<TooltipIcon
|
||||||
|
title={t("Open UWP tool Info")}
|
||||||
|
sx={{ opacity: "0.7" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import getSystem from "@/utils/get-system";
|
|||||||
import { routers } from "@/pages/_routers";
|
import { routers } from "@/pages/_routers";
|
||||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
import { ContentCopyRounded } from "@mui/icons-material";
|
import { ContentCopyRounded } from "@mui/icons-material";
|
||||||
|
import { languages } from "@/services/i18n";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onError?: (err: Error) => void;
|
onError?: (err: Error) => void;
|
||||||
@@ -35,6 +36,19 @@ interface Props {
|
|||||||
|
|
||||||
const OS = getSystem();
|
const OS = getSystem();
|
||||||
|
|
||||||
|
const languageOptions = Object.entries(languages).map(([code, _]) => {
|
||||||
|
const labels: { [key: string]: string } = {
|
||||||
|
en: "English",
|
||||||
|
ru: "Русский",
|
||||||
|
zh: "中文",
|
||||||
|
fa: "فارسی",
|
||||||
|
tt: "Татар",
|
||||||
|
id: "Bahasa Indonesia",
|
||||||
|
ar: "العربية",
|
||||||
|
};
|
||||||
|
return { code, label: labels[code] };
|
||||||
|
});
|
||||||
|
|
||||||
const SettingVerge = ({ onError }: Props) => {
|
const SettingVerge = ({ onError }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -96,10 +110,11 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
onGuard={(e) => patchVerge({ language: e })}
|
onGuard={(e) => patchVerge({ language: e })}
|
||||||
>
|
>
|
||||||
<Select size="small" sx={{ width: 110, "> div": { py: "7.5px" } }}>
|
<Select size="small" sx={{ width: 110, "> div": { py: "7.5px" } }}>
|
||||||
<MenuItem value="zh">中文</MenuItem>
|
{languageOptions.map(({ code, label }) => (
|
||||||
<MenuItem value="en">English</MenuItem>
|
<MenuItem key={code} value={code}>
|
||||||
<MenuItem value="ru">Русский</MenuItem>
|
{label}
|
||||||
<MenuItem value="fa">فارسی</MenuItem>
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
@@ -150,6 +165,7 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
<Select size="small" sx={{ width: 140, "> div": { py: "7.5px" } }}>
|
<Select size="small" sx={{ width: 140, "> div": { py: "7.5px" } }}>
|
||||||
<MenuItem value="bash">Bash</MenuItem>
|
<MenuItem value="bash">Bash</MenuItem>
|
||||||
<MenuItem value="cmd">CMD</MenuItem>
|
<MenuItem value="cmd">CMD</MenuItem>
|
||||||
|
<MenuItem value="nushell">Nushell</MenuItem>
|
||||||
<MenuItem value="powershell">PowerShell</MenuItem>
|
<MenuItem value="powershell">PowerShell</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</GuardState>
|
</GuardState>
|
||||||
@@ -257,7 +273,16 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
label={t("Runtime Config")}
|
label={t("Runtime Config")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingItem onClick={openAppDir} label={t("Open Conf Dir")} />
|
<SettingItem
|
||||||
|
onClick={openAppDir}
|
||||||
|
label={t("Open Conf Dir")}
|
||||||
|
extra={
|
||||||
|
<TooltipIcon
|
||||||
|
title={t("Open Conf Dir Info")}
|
||||||
|
sx={{ opacity: "0.7" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<SettingItem onClick={openCoreDir} label={t("Open Core Dir")} />
|
<SettingItem onClick={openCoreDir} label={t("Open Core Dir")} />
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { createSockette } from "../utils/websocket";
|
|||||||
import { useClashInfo } from "./use-clash";
|
import { useClashInfo } from "./use-clash";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
import { useVisibility } from "./use-visibility";
|
||||||
|
|
||||||
const MAX_LOG_NUM = 1000;
|
const MAX_LOG_NUM = 1000;
|
||||||
|
|
||||||
@@ -69,9 +70,10 @@ export const useLogData = (logLevel: LogLevel) => {
|
|||||||
const { clashInfo } = useClashInfo();
|
const { clashInfo } = useClashInfo();
|
||||||
const [enableLog] = useEnableLog();
|
const [enableLog] = useEnableLog();
|
||||||
const { logs, appendLog } = useLogStore();
|
const { logs, appendLog } = useLogStore();
|
||||||
|
const pageVisible = useVisibility();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!enableLog || !clashInfo) return;
|
if (!enableLog || !clashInfo || !pageVisible) return;
|
||||||
|
|
||||||
const { server = "", secret = "" } = clashInfo;
|
const { server = "", secret = "" } = clashInfo;
|
||||||
const wsUrl = buildWSUrl(server, secret, logLevel);
|
const wsUrl = buildWSUrl(server, secret, logLevel);
|
||||||
|
|||||||
438
src/locales/ar.json
Normal file
438
src/locales/ar.json
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
{
|
||||||
|
"millis": "ميلي ثانية",
|
||||||
|
"seconds": "ثواني",
|
||||||
|
"mins": "دقائق",
|
||||||
|
"Back": "رجوع",
|
||||||
|
"Close": "إغلاق",
|
||||||
|
"Cancel": "إلغاء",
|
||||||
|
"Confirm": "تأكيد",
|
||||||
|
"Maximize": "تكبير",
|
||||||
|
"Minimize": "تصغير",
|
||||||
|
"Format document": "تنسيق المستند",
|
||||||
|
"Empty": "فارغ",
|
||||||
|
"New": "جديد",
|
||||||
|
"Edit": "تعديل",
|
||||||
|
"Save": "حفظ",
|
||||||
|
"Delete": "حذف",
|
||||||
|
"Enable": "تمكين",
|
||||||
|
"Disable": "تعطيل",
|
||||||
|
"Label-Proxies": "الوكلاء",
|
||||||
|
"Label-Profiles": "الملفات الشخصية",
|
||||||
|
"Label-Connections": "الاتصالات",
|
||||||
|
"Label-Rules": "القواعد",
|
||||||
|
"Label-Logs": "السجلات",
|
||||||
|
"Label-Test": "اختبار",
|
||||||
|
"Label-Settings": "الإعدادات",
|
||||||
|
"Proxies": "الوكلاء",
|
||||||
|
"Proxy Groups": "مجموعات الوكلاء",
|
||||||
|
"Proxy Provider": "مزود الوكيل",
|
||||||
|
"Update All": "تحديث الكل",
|
||||||
|
"Update At": "التحديث عند",
|
||||||
|
"rule": "قاعدة",
|
||||||
|
"global": "عالمي",
|
||||||
|
"direct": "مباشر",
|
||||||
|
"script": "سكريبت",
|
||||||
|
"Location": "الموقع",
|
||||||
|
"Delay check": "فحص التأخير",
|
||||||
|
"Sort by default": "الترتيب الافتراضي",
|
||||||
|
"Sort by delay": "الترتيب حسب التأخير",
|
||||||
|
"Sort by name": "الترتيب حسب الاسم",
|
||||||
|
"Delay check URL": "رابط فحص التأخير",
|
||||||
|
"Delay check to cancel fixed": "فحص التأخير لإلغاء الثابت",
|
||||||
|
"Proxy basic": "إعدادات الوكيل الأساسية",
|
||||||
|
"Proxy detail": "تفاصيل الوكيل",
|
||||||
|
"Profiles": "الملفات الشخصية",
|
||||||
|
"Update All Profiles": "تحديث جميع الملفات الشخصية",
|
||||||
|
"View Runtime Config": "عرض تكوين وقت التشغيل",
|
||||||
|
"Reactivate Profiles": "إعادة تنشيط الملفات الشخصية",
|
||||||
|
"Paste": "لصق",
|
||||||
|
"Profile URL": "رابط الملف الشخصي",
|
||||||
|
"Import": "استيراد",
|
||||||
|
"From": "من",
|
||||||
|
"Update Time": "وقت التحديث",
|
||||||
|
"Used / Total": "المستخدم / الإجمالي",
|
||||||
|
"Expire Time": "وقت الانتهاء",
|
||||||
|
"Create Profile": "إنشاء ملف شخصي",
|
||||||
|
"Edit Profile": "تعديل الملف الشخصي",
|
||||||
|
"Edit Proxies": "تعديل الوكلاء",
|
||||||
|
"Use newlines for multiple uri": "استخدم أسطرًا جديدة لعدّة عناوين URI (يدعم التشفير Base64)",
|
||||||
|
"Edit Rules": "تعديل القواعد",
|
||||||
|
"Rule Type": "نوع القاعدة",
|
||||||
|
"Rule Content": "محتوى القاعدة",
|
||||||
|
"Proxy Policy": "سياسة الوكيل",
|
||||||
|
"No Resolve": "لا يوجد حل",
|
||||||
|
"Prepend Rule": "إضافة قاعدة في البداية",
|
||||||
|
"Append Rule": "إضافة قاعدة في النهاية",
|
||||||
|
"Prepend Group": "إضافة مجموعة في البداية",
|
||||||
|
"Append Group": "إضافة مجموعة في النهاية",
|
||||||
|
"Prepend Proxy": "إضافة وكيل في البداية",
|
||||||
|
"Append Proxy": "إضافة وكيل في النهاية",
|
||||||
|
"Rule Condition Required": "شرط القاعدة مطلوب",
|
||||||
|
"Invalid Rule": "قاعدة غير صالحة",
|
||||||
|
"Advanced": "متقدم",
|
||||||
|
"Visualization": "تصور",
|
||||||
|
"DOMAIN": "مطابقة اسم المجال الكامل",
|
||||||
|
"DOMAIN-SUFFIX": "مطابقة لاحقة المجال",
|
||||||
|
"DOMAIN-KEYWORD": "مطابقة كلمة مفتاحية في المجال",
|
||||||
|
"DOMAIN-REGEX": "مطابقة المجال باستخدام التعبيرات العادية",
|
||||||
|
"GEOSITE": "مطابقة المجالات ضمن Geosite",
|
||||||
|
"GEOIP": "مطابقة رمز البلد لعنوان IP",
|
||||||
|
"SRC-GEOIP": "مطابقة رمز البلد لعنوان IP المصدر",
|
||||||
|
"IP-ASN": "مطابقة ASN لعنوان IP",
|
||||||
|
"SRC-IP-ASN": "مطابقة ASN لعنوان IP المصدر",
|
||||||
|
"IP-CIDR": "مطابقة نطاق عنوان IP",
|
||||||
|
"IP-CIDR6": "مطابقة نطاق عناوين IPv6",
|
||||||
|
"SRC-IP-CIDR": "مطابقة نطاق عنوان IP المصدر",
|
||||||
|
"IP-SUFFIX": "مطابقة لاحقة عنوان IP",
|
||||||
|
"SRC-IP-SUFFIX": "مطابقة لاحقة عنوان IP المصدر",
|
||||||
|
"SRC-PORT": "مطابقة نطاق المنفذ المصدر",
|
||||||
|
"DST-PORT": "مطابقة نطاق المنفذ الوجهة",
|
||||||
|
"IN-PORT": "مطابقة المنفذ الوارد",
|
||||||
|
"DSCP": "علامة DSCP (لـ tproxy على UDP فقط)",
|
||||||
|
"PROCESS-NAME": "مطابقة اسم العملية (اسم حزمة Android)",
|
||||||
|
"PROCESS-PATH": "مطابقة المسار الكامل للعملية",
|
||||||
|
"PROCESS-NAME-REGEX": "مطابقة اسم العملية باستخدام التعبيرات العادية (اسم حزمة Android)",
|
||||||
|
"PROCESS-PATH-REGEX": "مطابقة المسار الكامل للعملية باستخدام التعبيرات العادية",
|
||||||
|
"NETWORK": "مطابقة بروتوكول النقل (TCP/UDP)",
|
||||||
|
"UID": "مطابقة معرف المستخدم في Linux",
|
||||||
|
"IN-TYPE": "مطابقة نوع الإدخال",
|
||||||
|
"IN-USER": "مطابقة اسم المستخدم للإدخال",
|
||||||
|
"IN-NAME": "مطابقة اسم الإدخال",
|
||||||
|
"SUB-RULE": "قاعدة فرعية",
|
||||||
|
"RULE-SET": "مطابقة مجموعة القواعد",
|
||||||
|
"AND": "منطقي AND",
|
||||||
|
"OR": "منطقي OR",
|
||||||
|
"NOT": "منطقي NOT",
|
||||||
|
"MATCH": "مطابقة جميع الطلبات",
|
||||||
|
"DIRECT": "البيانات تخرج مباشرة",
|
||||||
|
"REJECT": "رفض الطلبات",
|
||||||
|
"REJECT-DROP": "تجاهل الطلبات",
|
||||||
|
"PASS": "تخطي هذه القاعدة عند المطابقة",
|
||||||
|
"Edit Groups": "تعديل مجموعات الوكلاء",
|
||||||
|
"Group Type": "نوع المجموعة",
|
||||||
|
"select": "اختيار الوكيل يدويًا",
|
||||||
|
"url-test": "اختيار الوكيل بناءً على تأخير اختبار الرابط",
|
||||||
|
"fallback": "التبديل إلى وكيل آخر عند حدوث خطأ",
|
||||||
|
"load-balance": "توزيع التحميل بين الوكلاء",
|
||||||
|
"relay": "التمرير عبر سلسلة الوكلاء المحددة",
|
||||||
|
"Group Name": "اسم المجموعة",
|
||||||
|
"Use Proxies": "استخدام الوكلاء",
|
||||||
|
"Use Provider": "استخدام المزود",
|
||||||
|
"Health Check Url": "رابط فحص الصحة",
|
||||||
|
"Expected Status": "الحالة المتوقعة",
|
||||||
|
"Interval": "الفاصل الزمني",
|
||||||
|
"Lazy": "كسول",
|
||||||
|
"Timeout": "مهلة",
|
||||||
|
"Max Failed Times": "الحد الأقصى لمحاولات الفشل",
|
||||||
|
"Interface Name": "اسم الواجهة",
|
||||||
|
"Routing Mark": "علامة التوجيه",
|
||||||
|
"Include All": "تضمين جميع الوكلاء والمزودين",
|
||||||
|
"Include All Providers": "تضمين جميع المزودين",
|
||||||
|
"Include All Proxies": "تضمين جميع الوكلاء",
|
||||||
|
"Exclude Filter": "استبعاد المرشح",
|
||||||
|
"Exclude Type": "استبعاد النوع",
|
||||||
|
"Disable UDP": "تعطيل UDP",
|
||||||
|
"Hidden": "مخفي",
|
||||||
|
"Group Name Required": "اسم المجموعة مطلوب",
|
||||||
|
"Group Name Already Exists": "اسم المجموعة موجود بالفعل",
|
||||||
|
"Extend Config": "توسيع الإعدادات",
|
||||||
|
"Extend Script": "توسيع السكربت",
|
||||||
|
"Global Merge": "دمج عالمي للإعدادات",
|
||||||
|
"Global Script": "سكريبت عالمي",
|
||||||
|
"Type": "النوع",
|
||||||
|
"Name": "الاسم",
|
||||||
|
"Descriptions": "الوصف",
|
||||||
|
"Subscription URL": "رابط الاشتراك",
|
||||||
|
"Update Interval": "فاصل التحديث",
|
||||||
|
"Choose File": "اختر ملف",
|
||||||
|
"Use System Proxy": "استخدام وكيل النظام",
|
||||||
|
"Use Clash Proxy": "استخدام وكيل Clash",
|
||||||
|
"Accept Invalid Certs (Danger)": "قبول الشهادات غير الصالحة (خطر)",
|
||||||
|
"Refresh": "تحديث",
|
||||||
|
"Home": "الصفحة الرئيسية",
|
||||||
|
"Select": "اختيار",
|
||||||
|
"Edit Info": "تعديل المعلومات",
|
||||||
|
"Edit File": "تعديل الملف",
|
||||||
|
"Open File": "فتح الملف",
|
||||||
|
"Update": "تحديث",
|
||||||
|
"Update(Proxy)": "تحديث (الوكيل)",
|
||||||
|
"Confirm deletion": "تأكيد الحذف",
|
||||||
|
"This operation is not reversible": "لا يمكن التراجع عن هذه العملية",
|
||||||
|
"Script Console": "وحدة التحكم للسكريبت",
|
||||||
|
"To Top": "إلى الأعلى",
|
||||||
|
"To End": "إلى النهاية",
|
||||||
|
"Connections": "الاتصالات",
|
||||||
|
"Table View": "عرض الجدول",
|
||||||
|
"List View": "عرض القائمة",
|
||||||
|
"Close All": "إغلاق الكل",
|
||||||
|
"Default": "افتراضي",
|
||||||
|
"Download Speed": "سرعة التنزيل",
|
||||||
|
"Upload Speed": "سرعة الرفع",
|
||||||
|
"Host": "المضيف",
|
||||||
|
"Downloaded": "تم التنزيل",
|
||||||
|
"Uploaded": "تم الرفع",
|
||||||
|
"DL Speed": "سرعة التنزيل",
|
||||||
|
"UL Speed": "سرعة الرفع",
|
||||||
|
"Chains": "السلاسل",
|
||||||
|
"Rule": "قاعدة",
|
||||||
|
"Process": "عملية",
|
||||||
|
"Time": "الوقت",
|
||||||
|
"Source": "المصدر",
|
||||||
|
"Destination IP": "عنوان IP الوجهة",
|
||||||
|
"Close Connection": "إغلاق الاتصال",
|
||||||
|
"Rules": "القواعد",
|
||||||
|
"Rule Provider": "مزود القواعد",
|
||||||
|
"Logs": "السجلات",
|
||||||
|
"Pause": "إيقاف مؤقت",
|
||||||
|
"Clear": "مسح",
|
||||||
|
"Test": "اختبار",
|
||||||
|
"Test All": "اختبار الكل",
|
||||||
|
"Create Test": "إنشاء اختبار",
|
||||||
|
"Edit Test": "تعديل الاختبار",
|
||||||
|
"Icon": "أيقونة",
|
||||||
|
"Test URL": "رابط الاختبار",
|
||||||
|
"Settings": "الإعدادات",
|
||||||
|
"System Setting": "إعدادات النظام",
|
||||||
|
"Tun Mode": "وضع TUN",
|
||||||
|
"Reset to Default": "إعادة تعيين إلى الافتراضي",
|
||||||
|
"Tun Mode Info": "وضع TUN (بطاقة شبكة افتراضية): يلتقط كل حركة المرور في النظام. عند تمكينه، لا حاجة لتفعيل وكيل النظام.",
|
||||||
|
"Stack": "مكدس TUN",
|
||||||
|
"System and Mixed Can Only be Used in Service Mode": "لا يمكن استخدام النظام والمختلط إلا في وضع الخدمة",
|
||||||
|
"Device": "اسم الجهاز",
|
||||||
|
"Auto Route": "توجيه تلقائي",
|
||||||
|
"Strict Route": "توجيه صارم",
|
||||||
|
"Auto Detect Interface": "الكشف التلقائي عن الواجهة",
|
||||||
|
"DNS Hijack": "اختطاف DNS",
|
||||||
|
"MTU": "وحدة الإرسال القصوى",
|
||||||
|
"Service Mode": "وضع الخدمة",
|
||||||
|
"Service Mode Info": "يرجى تثبيت وضع الخدمة قبل تمكين وضع TUN. يمكن للعمليات الأساسية التي يبدأها وضع الخدمة الحصول على إذن لتثبيت بطاقة الشبكة الافتراضية (TUN).",
|
||||||
|
"Current State": "الحالة الحالية",
|
||||||
|
"pending": "معلق",
|
||||||
|
"installed": "مثبت",
|
||||||
|
"uninstall": "إلغاء التثبيت",
|
||||||
|
"active": "نشط",
|
||||||
|
"unknown": "غير معروف",
|
||||||
|
"Information: Please make sure that the Clash Verge Service is installed and enabled": "معلومة: يرجى التأكد من تثبيت وتشغيل خدمة Clash Verge",
|
||||||
|
"Install": "تثبيت",
|
||||||
|
"Uninstall": "إلغاء التثبيت",
|
||||||
|
"Disable Service Mode": "تعطيل وضع الخدمة",
|
||||||
|
"System Proxy": "وكيل النظام",
|
||||||
|
"System Proxy Info": "عند التمكين، سيتم تعديل إعدادات الوكيل في نظام التشغيل. إذا فشل التمكين، فقم بتعديل إعدادات الوكيل في النظام يدويًا.",
|
||||||
|
"System Proxy Setting": "إعداد وكيل النظام",
|
||||||
|
"Current System Proxy": "الوكيل الحالي للنظام",
|
||||||
|
"Enable status": "حالة التمكين:",
|
||||||
|
"Enabled": "ممكّن",
|
||||||
|
"Disabled": "معطّل",
|
||||||
|
"Server Addr": "عنوان الخادم:",
|
||||||
|
"Not available": "غير متوفر",
|
||||||
|
"Proxy Guard": "حماية الوكيل",
|
||||||
|
"Proxy Guard Info": "عند التمكين، يمنع برامج أخرى من تعديل إعدادات وكيل النظام",
|
||||||
|
"Guard Duration": "مدة الحماية",
|
||||||
|
"Always use Default Bypass": "استخدام التخطي الافتراضي دائمًا",
|
||||||
|
"Proxy Bypass": "إعدادات تخطي الوكيل:",
|
||||||
|
"Bypass": "تخطي:",
|
||||||
|
"Use PAC Mode": "استخدام وضع PAC",
|
||||||
|
"PAC Script Content": "محتوى سكريبت PAC",
|
||||||
|
"PAC URL": "رابط PAC:",
|
||||||
|
"Auto Launch": "إطلاق تلقائي",
|
||||||
|
"Silent Start": "بدء صامت",
|
||||||
|
"Silent Start Info": "بدء البرنامج في الخلفية دون عرض الواجهة",
|
||||||
|
"TG Channel": "قناة تيليجرام",
|
||||||
|
"Manual": "دليل",
|
||||||
|
"Github Repo": "مستودع Github",
|
||||||
|
"Clash Setting": "إعدادات Clash",
|
||||||
|
"Allow Lan": "السماح بالشبكة المحلية",
|
||||||
|
"Network Interface": "واجهة الشبكة",
|
||||||
|
"Ip Address": "عنوان IP",
|
||||||
|
"Mac Address": "عنوان MAC",
|
||||||
|
"IPv6": "IPv6",
|
||||||
|
"Unified Delay": "تأخير موحد",
|
||||||
|
"Unified Delay Info": "عند تفعيل التأخير الموحد، سيتم إجراء اختبارين للتأخير لتقليل الفروقات الناتجة عن مفاوضات الاتصال",
|
||||||
|
"Log Level": "مستوى السجلات",
|
||||||
|
"Port Config": "تكوين المنافذ",
|
||||||
|
"Random Port": "منفذ عشوائي",
|
||||||
|
"Mixed Port": "منفذ مختلط",
|
||||||
|
"Socks Port": "منفذ SOCKS",
|
||||||
|
"Http Port": "منفذ HTTP(S)",
|
||||||
|
"Redir Port": "منفذ إعادة التوجيه",
|
||||||
|
"Tproxy Port": "منفذ Tproxy",
|
||||||
|
"External": "خارجي",
|
||||||
|
"External Controller": "وحدة التحكم الخارجية",
|
||||||
|
"Core Secret": "المفتاح السري للنواة",
|
||||||
|
"Recommended": "موصى به",
|
||||||
|
"Open URL": "فتح الرابط",
|
||||||
|
"Replace host, port, secret with %host, %port, %secret": "استبدل المضيف والمنفذ والمفتاح بـ %host و%port و%secret",
|
||||||
|
"Support %host, %port, %secret": "يدعم %host و%port و%secret",
|
||||||
|
"Clash Core": "نواة Clash",
|
||||||
|
"Upgrade": "ترقية",
|
||||||
|
"Restart": "إعادة التشغيل",
|
||||||
|
"Release Version": "إصدار مستقر",
|
||||||
|
"Alpha Version": "إصدار ألفا",
|
||||||
|
"Please Enable Service Mode": "يرجى تثبيت وتفعيل وضع الخدمة أولاً",
|
||||||
|
"Please enter your root password": "يرجى إدخال كلمة مرور الرووت",
|
||||||
|
"Grant": "منح الصلاحيات",
|
||||||
|
"Open UWP tool": "فتح أداة UWP",
|
||||||
|
"Open UWP tool Info": "منذ نظام ويندوز 8، يتم تقييد تطبيقات UWP من الوصول المباشر إلى المضيف المحلي. هذه الأداة تتيح تجاوز هذا التقييد",
|
||||||
|
"Update GeoData": "تحديث البيانات الجغرافية",
|
||||||
|
"Verge Setting": "إعدادات Verge",
|
||||||
|
"Language": "اللغة",
|
||||||
|
"Theme Mode": "وضع السمة",
|
||||||
|
"theme.light": "سمة فاتحة",
|
||||||
|
"theme.dark": "سمة داكنة",
|
||||||
|
"theme.system": "سمة النظام",
|
||||||
|
"Tray Click Event": "حدث النقر على الأيقونة في شريط المهام",
|
||||||
|
"Show Main Window": "إظهار النافذة الرئيسية",
|
||||||
|
"Copy Env Type": "نسخ نوع البيئة",
|
||||||
|
"Copy Success": "تم النسخ بنجاح",
|
||||||
|
"Start Page": "صفحة البدء",
|
||||||
|
"Startup Script": "سكريبت بدء التشغيل",
|
||||||
|
"Browse": "استعراض",
|
||||||
|
"Theme Setting": "إعدادات السمة",
|
||||||
|
"Primary Color": "اللون الأساسي",
|
||||||
|
"Secondary Color": "اللون الثانوي",
|
||||||
|
"Primary Text": "النص الأساسي",
|
||||||
|
"Secondary Text": "النص الثانوي",
|
||||||
|
"Info Color": "لون المعلومات",
|
||||||
|
"Warning Color": "لون التحذير",
|
||||||
|
"Error Color": "لون الخطأ",
|
||||||
|
"Success Color": "لون النجاح",
|
||||||
|
"Font Family": "عائلة الخط",
|
||||||
|
"CSS Injection": "حقن CSS",
|
||||||
|
"Layout Setting": "إعدادات التخطيط",
|
||||||
|
"Traffic Graph": "مخطط حركة المرور",
|
||||||
|
"Memory Usage": "استهلاك الذاكرة",
|
||||||
|
"Memory Cleanup": "انقر لتنظيف الذاكرة",
|
||||||
|
"Proxy Group Icon": "أيقونة مجموعة الوكلاء",
|
||||||
|
"Nav Icon": "أيقونة التنقل",
|
||||||
|
"Monochrome": "أحادي اللون",
|
||||||
|
"Colorful": "ملون",
|
||||||
|
"Tray Icon": "أيقونة شريط المهام",
|
||||||
|
"Common Tray Icon": "أيقونة شريط مهام عامة",
|
||||||
|
"System Proxy Tray Icon": "أيقونة شريط المهام لوكيل النظام",
|
||||||
|
"Tun Tray Icon": "أيقونة شريط المهام لـ TUN",
|
||||||
|
"Miscellaneous": "متفرقات",
|
||||||
|
"App Log Level": "مستوى سجلات التطبيق",
|
||||||
|
"Auto Close Connections": "إغلاق الاتصالات تلقائيًا",
|
||||||
|
"Auto Close Connections Info": "إنهاء الاتصالات القائمة عند تغيير اختيار مجموعة الوكيل أو وضع الوكيل",
|
||||||
|
"Auto Check Update": "فحص التحديث تلقائيًا",
|
||||||
|
"Enable Builtin Enhanced": "تفعيل التحسين المدمج",
|
||||||
|
"Enable Builtin Enhanced Info": "معالجة توافق ملف التكوين",
|
||||||
|
"Proxy Layout Columns": "أعمدة عرض الوكيل",
|
||||||
|
"Auto Columns": "أعمدة تلقائية",
|
||||||
|
"Auto Log Clean": "تنظيف السجلات تلقائيًا",
|
||||||
|
"Never Clean": "عدم التنظيف أبدًا",
|
||||||
|
"Retain _n Days": "الاحتفاظ لمدة {{n}} يومًا",
|
||||||
|
"Default Latency Test": "اختبار التأخير الافتراضي",
|
||||||
|
"Default Latency Test Info": "يُستخدم فقط لاختبار طلب HTTP العميل. لن يؤثر على ملف التكوين",
|
||||||
|
"Default Latency Timeout": "مهلة التأخير الافتراضية",
|
||||||
|
"Hotkey Setting": "إعدادات الاختصارات",
|
||||||
|
"open_or_close_dashboard": "فتح/إغلاق لوحة التحكم",
|
||||||
|
"clash_mode_rule": "وضع القواعد",
|
||||||
|
"clash_mode_global": "الوضع العالمي",
|
||||||
|
"clash_mode_direct": "الوضع المباشر",
|
||||||
|
"toggle_system_proxy": "تفعيل/تعطيل وكيل النظام",
|
||||||
|
"toggle_tun_mode": "تفعيل/تعطيل وضع TUN",
|
||||||
|
"Backup Setting": "إعداد النسخ الاحتياطي",
|
||||||
|
"Runtime Config": "تكوين وقت التشغيل",
|
||||||
|
"Open Conf Dir": "فتح مجلد التكوين",
|
||||||
|
"Open Conf Dir Info": "إذا عمل البرنامج بشكل غير طبيعي، قم بالنسخ الاحتياطي ثم حذف جميع الملفات في هذا المجلد ثم أعد تشغيل البرنامج",
|
||||||
|
"Open Core Dir": "فتح مجلد النواة",
|
||||||
|
"Open Logs Dir": "فتح مجلد السجلات",
|
||||||
|
"Check for Updates": "التحقق من وجود تحديثات",
|
||||||
|
"Go to Release Page": "الانتقال إلى صفحة الإصدارات",
|
||||||
|
"Portable Updater Error": "الإصدار المحمول لا يدعم التحديث داخل التطبيق. يرجى التنزيل والاستبدال يدويًا",
|
||||||
|
"Break Change Update Error": "هذا الإصدار هو تحديث رئيسي ولا يدعم التحديث داخل التطبيق. يرجى إلغاء التثبيت وتنزيل الإصدار الجديد وتثبيته يدويًا",
|
||||||
|
"Open Dev Tools": "أدوات المطور",
|
||||||
|
"Exit": "خروج",
|
||||||
|
"Verge Version": "إصدار Verge",
|
||||||
|
"ReadOnly": "للقراءة فقط",
|
||||||
|
"ReadOnlyMessage": "لا يمكن التعديل في محرر القراءة فقط",
|
||||||
|
"Filter": "تصفية",
|
||||||
|
"Filter conditions": "شروط التصفية",
|
||||||
|
"Match Case": "مطابقة الحالة",
|
||||||
|
"Match Whole Word": "مطابقة الكلمة بأكملها",
|
||||||
|
"Use Regular Expression": "استخدام التعبيرات العادية",
|
||||||
|
"Profile Imported Successfully": "تم استيراد الملف الشخصي بنجاح",
|
||||||
|
"Profile Switched": "تم التبديل إلى الملف الشخصي",
|
||||||
|
"Profile Reactivated": "تم إعادة تنشيط الملف الشخصي",
|
||||||
|
"Only YAML Files Supported": "لا يتم دعم سوى ملفات YAML",
|
||||||
|
"Settings Applied": "تم تطبيق الإعدادات",
|
||||||
|
"Service Installed Successfully": "تم تثبيت الخدمة بنجاح",
|
||||||
|
"Service Uninstalled Successfully": "تم إلغاء تثبيت الخدمة بنجاح",
|
||||||
|
"Proxy Daemon Duration Cannot be Less than 1 Second": "لا يمكن أن تقل مدة خادم الوكيل عن ثانية واحدة",
|
||||||
|
"Invalid Bypass Format": "تنسيق التخطي غير صالح",
|
||||||
|
"Clash Port Modified": "تم تعديل منفذ Clash",
|
||||||
|
"Port Conflict": "تعارض في المنفذ",
|
||||||
|
"Restart Application to Apply Modifications": "أعد تشغيل التطبيق لتطبيق التعديلات",
|
||||||
|
"External Controller Address Modified": "تم تعديل عنوان وحدة التحكم الخارجية",
|
||||||
|
"Permissions Granted Successfully for _clash Core": "تم منح الأذونات بنجاح لـ {{core}} Core",
|
||||||
|
"Core Version Updated": "تم تحديث إصدار النواة",
|
||||||
|
"Clash Core Restarted": "تم إعادة تشغيل نواة Clash",
|
||||||
|
"Switched to _clash Core": "تم التبديل إلى {{core}} Core",
|
||||||
|
"GeoData Updated": "تم تحديث البيانات الجغرافية",
|
||||||
|
"Currently on the Latest Version": "أنت على أحدث إصدار حاليًا",
|
||||||
|
"Import Subscription Successful": "تم استيراد الاشتراك بنجاح",
|
||||||
|
"WebDAV Server URL": "عنوان خادم WebDAV",
|
||||||
|
"Username": "اسم المستخدم",
|
||||||
|
"Password": "كلمة المرور",
|
||||||
|
"Backup": "نسخ احتياطي",
|
||||||
|
"Filename": "اسم الملف",
|
||||||
|
"Actions": "الإجراءات",
|
||||||
|
"Restore": "استعادة",
|
||||||
|
"No Backups": "لا توجد نسخ احتياطية متاحة",
|
||||||
|
"WebDAV URL Required": "لا يمكن ترك رابط WebDAV فارغًا",
|
||||||
|
"Invalid WebDAV URL": "تنسيق رابط WebDAV غير صالح",
|
||||||
|
"Username Required": "لا يمكن ترك اسم المستخدم فارغًا",
|
||||||
|
"Password Required": "لا يمكن ترك كلمة المرور فارغة",
|
||||||
|
"Failed to Fetch Backups": "فشل في جلب ملفات النسخ الاحتياطي",
|
||||||
|
"WebDAV Config Saved": "تم حفظ إعدادات WebDAV بنجاح",
|
||||||
|
"WebDAV Config Save Failed": "فشل حفظ إعدادات WebDAV: {{error}}",
|
||||||
|
"Backup Created": "تم إنشاء النسخة الاحتياطية بنجاح",
|
||||||
|
"Backup Failed": "فشل في النسخ الاحتياطي: {{error}}",
|
||||||
|
"Delete Backup": "حذف النسخة الاحتياطية",
|
||||||
|
"Restore Backup": "استعادة النسخة الاحتياطية",
|
||||||
|
"Backup Time": "وقت النسخ الاحتياطي",
|
||||||
|
"Confirm to delete this backup file?": "هل تريد بالتأكيد حذف ملف النسخة الاحتياطية هذا؟",
|
||||||
|
"Confirm to restore this backup file?": "هل تريد بالتأكيد استعادة ملف النسخة الاحتياطية هذا؟",
|
||||||
|
"Restore Success, App will restart in 1s": "تمت الاستعادة بنجاح، سيعاد تشغيل التطبيق خلال ثانية واحدة",
|
||||||
|
"Failed to fetch backup files": "فشل في جلب ملفات النسخ الاحتياطي",
|
||||||
|
"Profile": "الملف الشخصي",
|
||||||
|
"Help": "مساعدة",
|
||||||
|
"About": "حول",
|
||||||
|
"Theme": "السمة",
|
||||||
|
"TUN Mode": "وضع TUN",
|
||||||
|
"Main Window": "النافذة الرئيسية",
|
||||||
|
"Group Icon": "أيقونة المجموعة",
|
||||||
|
"Menu Icon": "أيقونة القائمة",
|
||||||
|
"System Proxy Bypass": "تخطي وكيل النظام",
|
||||||
|
"PAC File": "ملف PAC",
|
||||||
|
"Web UI": "واجهة الويب",
|
||||||
|
"Hotkeys": "اختصارات لوحة المفاتيح",
|
||||||
|
"Auto Close Connection": "إغلاق الاتصال تلقائيًا",
|
||||||
|
"Enable Built-in Enhanced": "تفعيل التحسين المدمج",
|
||||||
|
"Proxy Layout Column": "عمود عرض الوكيل",
|
||||||
|
"Test List": "قائمة الاختبارات",
|
||||||
|
"Enable Random Port": "تفعيل المنفذ العشوائي",
|
||||||
|
"Verge Mixed Port": "منفذ Verge المختلط",
|
||||||
|
"Verge Socks Port": "منفذ Verge SOCKS",
|
||||||
|
"Verge Redir Port": "منفذ إعادة التوجيه لـ Verge",
|
||||||
|
"Verge Tproxy Port": "منفذ Tproxy لـ Verge",
|
||||||
|
"Verge Port": "منفذ Verge",
|
||||||
|
"Verge HTTP Enabled": "تمكين Verge HTTP",
|
||||||
|
"WebDAV URL": "رابط WebDAV",
|
||||||
|
"WebDAV Username": "اسم المستخدم لـ WebDAV",
|
||||||
|
"WebDAV Password": "كلمة مرور WebDAV",
|
||||||
|
"Copy Env": "نسخ البيئة",
|
||||||
|
"Conf Dir": "مجلد الإعدادات",
|
||||||
|
"Core Dir": "مجلد النواة",
|
||||||
|
"Logs Dir": "مجلد السجلات",
|
||||||
|
"Open Dir": "فتح المجلد",
|
||||||
|
"Restart Clash Core": "إعادة تشغيل نواة Clash",
|
||||||
|
"Restart App": "إعادة تشغيل التطبيق",
|
||||||
|
"More": "المزيد",
|
||||||
|
"Dashboard": "لوحة التحكم",
|
||||||
|
"Rule Mode": "وضع القواعد",
|
||||||
|
"Global Mode": "الوضع العالمي",
|
||||||
|
"Direct Mode": "الوضع المباشر",
|
||||||
|
"Enable Tray Speed": "تفعيل سرعة التراي"
|
||||||
|
}
|
||||||
@@ -229,6 +229,7 @@
|
|||||||
"Proxy Guard Info": "Enable to prevent other software from modifying the operating system's proxy settings",
|
"Proxy Guard Info": "Enable to prevent other software from modifying the operating system's proxy settings",
|
||||||
"Guard Duration": "Guard Duration",
|
"Guard Duration": "Guard Duration",
|
||||||
"Always use Default Bypass": "Always use Default Bypass",
|
"Always use Default Bypass": "Always use Default Bypass",
|
||||||
|
"Use Bypass Check": "Use Bypass Check",
|
||||||
"Proxy Bypass": "Proxy Bypass Settings: ",
|
"Proxy Bypass": "Proxy Bypass Settings: ",
|
||||||
"Bypass": "Bypass: ",
|
"Bypass": "Bypass: ",
|
||||||
"Use PAC Mode": "Use PAC Mode",
|
"Use PAC Mode": "Use PAC Mode",
|
||||||
@@ -249,6 +250,7 @@
|
|||||||
"Unified Delay": "Unified Delay",
|
"Unified Delay": "Unified Delay",
|
||||||
"Unified Delay Info": "When unified delay is turned on, two delay tests will be performed to eliminate the delay differences between different types of nodes caused by connection handshakes, etc",
|
"Unified Delay Info": "When unified delay is turned on, two delay tests will be performed to eliminate the delay differences between different types of nodes caused by connection handshakes, etc",
|
||||||
"Log Level": "Log Level",
|
"Log Level": "Log Level",
|
||||||
|
"Log Level Info": "This parameter is valid only for kernel log files in the log directory Service folder",
|
||||||
"Port Config": "Port Config",
|
"Port Config": "Port Config",
|
||||||
"Random Port": "Random Port",
|
"Random Port": "Random Port",
|
||||||
"Mixed Port": "Mixed Port",
|
"Mixed Port": "Mixed Port",
|
||||||
@@ -333,8 +335,10 @@
|
|||||||
"toggle_system_proxy": "Enable/Disable System Proxy",
|
"toggle_system_proxy": "Enable/Disable System Proxy",
|
||||||
"toggle_tun_mode": "Enable/Disable Tun Mode",
|
"toggle_tun_mode": "Enable/Disable Tun Mode",
|
||||||
"Backup Setting": "Backup Setting",
|
"Backup Setting": "Backup Setting",
|
||||||
|
"Backup Setting Info": "Support WebDAV backup configuration files",
|
||||||
"Runtime Config": "Runtime Config",
|
"Runtime Config": "Runtime Config",
|
||||||
"Open Conf Dir": "Open Conf Dir",
|
"Open Conf Dir": "Open Conf Dir",
|
||||||
|
"Open Conf Dir Info": "If the software runs abnormally, BACKUP and delete all files in this folder than restart the software",
|
||||||
"Open Core Dir": "Open Core Dir",
|
"Open Core Dir": "Open Core Dir",
|
||||||
"Open Logs Dir": "Open Logs Dir",
|
"Open Logs Dir": "Open Logs Dir",
|
||||||
"Check for Updates": "Check for Updates",
|
"Check for Updates": "Check for Updates",
|
||||||
@@ -352,7 +356,6 @@
|
|||||||
"Match Whole Word": "Match Whole Word",
|
"Match Whole Word": "Match Whole Word",
|
||||||
"Use Regular Expression": "Use Regular Expression",
|
"Use Regular Expression": "Use Regular Expression",
|
||||||
"Profile Imported Successfully": "Profile Imported Successfully",
|
"Profile Imported Successfully": "Profile Imported Successfully",
|
||||||
"Clash Config Updated": "Clash Config Updated",
|
|
||||||
"Profile Switched": "Profile Switched",
|
"Profile Switched": "Profile Switched",
|
||||||
"Profile Reactivated": "Profile Reactivated",
|
"Profile Reactivated": "Profile Reactivated",
|
||||||
"Only YAML Files Supported": "Only YAML Files Supported",
|
"Only YAML Files Supported": "Only YAML Files Supported",
|
||||||
@@ -395,5 +398,44 @@
|
|||||||
"Confirm to delete this backup file?": "Confirm to delete this backup file?",
|
"Confirm to delete this backup file?": "Confirm to delete this backup file?",
|
||||||
"Confirm to restore this backup file?": "Confirm to restore this backup file?",
|
"Confirm to restore this backup file?": "Confirm to restore this backup file?",
|
||||||
"Restore Success, App will restart in 1s": "Restore Success, App will restart in 1s",
|
"Restore Success, App will restart in 1s": "Restore Success, App will restart in 1s",
|
||||||
"Failed to fetch backup files": "Failed to fetch backup files"
|
"Failed to fetch backup files": "Failed to fetch backup files",
|
||||||
|
"Profile": "Profile",
|
||||||
|
"Help": "Help",
|
||||||
|
"About": "About",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"TUN Mode": "TUN Mode",
|
||||||
|
"Main Window": "Main Window",
|
||||||
|
"Group Icon": "Group Icon",
|
||||||
|
"Menu Icon": "Menu Icon",
|
||||||
|
"System Proxy Bypass": "System Proxy Bypass",
|
||||||
|
"PAC File": "PAC File",
|
||||||
|
"Web UI": "Web UI",
|
||||||
|
"Hotkeys": "Hotkeys",
|
||||||
|
"Auto Close Connection": "Auto Close Connection",
|
||||||
|
"Enable Built-in Enhanced": "Enable Built-in Enhanced",
|
||||||
|
"Proxy Layout Column": "Proxy Layout Column",
|
||||||
|
"Test List": "Test List",
|
||||||
|
"Enable Random Port": "Enable Random Port",
|
||||||
|
"Verge Mixed Port": "Verge Mixed Port",
|
||||||
|
"Verge Socks Port": "Verge Socks Port",
|
||||||
|
"Verge Redir Port": "Verge Redir Port",
|
||||||
|
"Verge Tproxy Port": "Verge Tproxy Port",
|
||||||
|
"Verge Port": "Verge Port",
|
||||||
|
"Verge HTTP Enabled": "Verge HTTP Enabled",
|
||||||
|
"WebDAV URL": "WebDAV URL",
|
||||||
|
"WebDAV Username": "WebDAV Username",
|
||||||
|
"WebDAV Password": "WebDAV Password",
|
||||||
|
"Copy Env": "Copy Env",
|
||||||
|
"Conf Dir": "Conf Dir",
|
||||||
|
"Core Dir": "Core Dir",
|
||||||
|
"Logs Dir": "Logs Dir",
|
||||||
|
"Open Dir": "Open Dir",
|
||||||
|
"Restart Clash Core": "Restart Clash Core",
|
||||||
|
"Restart App": "Restart App",
|
||||||
|
"More": "More",
|
||||||
|
"Dashboard": "Dashboard",
|
||||||
|
"Rule Mode": "Rule Mode",
|
||||||
|
"Global Mode": "Global Mode",
|
||||||
|
"Direct Mode": "Direct Mode",
|
||||||
|
"Enable Tray Speed": "Enable Tray Speed"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
"select": "انتخاب پروکسی به صورت دستی",
|
"select": "انتخاب پروکسی به صورت دستی",
|
||||||
"url-test": "انتخاب پروکسی بر اساس تأخیر آزمایش URL",
|
"url-test": "انتخاب پروکسی بر اساس تأخیر آزمایش URL",
|
||||||
"fallback": "تعویض به پروکسی دیگر در صورت بروز خطا",
|
"fallback": "تعویض به پروکسی دیگر در صورت بروز خطا",
|
||||||
"load-balance": "توزیع <EFBFBD><EFBFBD>روکسی بر اساس توازن بار",
|
"load-balance": "توزیع پراکسی بر اساس توازن بار",
|
||||||
"relay": "عبور از زنجیره پروکسی تعریف شده",
|
"relay": "عبور از زنجیره پروکسی تعریف شده",
|
||||||
"Group Name": "نام گروه",
|
"Group Name": "نام گروه",
|
||||||
"Use Proxies": "استفاده از پروکسیها",
|
"Use Proxies": "استفاده از پروکسیها",
|
||||||
@@ -229,6 +229,7 @@
|
|||||||
"Proxy Guard Info": "امکان جلوگیری از نرمافزارهای دیگر از تغییر تنظیمات پروکسی سیستم عامل را فعال کنید",
|
"Proxy Guard Info": "امکان جلوگیری از نرمافزارهای دیگر از تغییر تنظیمات پروکسی سیستم عامل را فعال کنید",
|
||||||
"Guard Duration": "مدت محافظت",
|
"Guard Duration": "مدت محافظت",
|
||||||
"Always use Default Bypass": "همیشه از دور زدن پیشفرض استفاده کنید",
|
"Always use Default Bypass": "همیشه از دور زدن پیشفرض استفاده کنید",
|
||||||
|
"Use Bypass Check": "استخدم التحقق من التحايل",
|
||||||
"Proxy Bypass": "دور زدن پراکسی: ",
|
"Proxy Bypass": "دور زدن پراکسی: ",
|
||||||
"Bypass": "دور زدن: ",
|
"Bypass": "دور زدن: ",
|
||||||
"Use PAC Mode": "استفاده از حالت PAC",
|
"Use PAC Mode": "استفاده از حالت PAC",
|
||||||
@@ -337,6 +338,7 @@
|
|||||||
"Backup Setting Info": "از فایل های پیکربندی پشتیبان WebDAV پشتیبانی می کند",
|
"Backup Setting Info": "از فایل های پیکربندی پشتیبان WebDAV پشتیبانی می کند",
|
||||||
"Runtime Config": "پیکربندی زمان اجرا",
|
"Runtime Config": "پیکربندی زمان اجرا",
|
||||||
"Open Conf Dir": "باز کردن پوشه برنامه",
|
"Open Conf Dir": "باز کردن پوشه برنامه",
|
||||||
|
"Open Conf Dir Info": "اگر نرمافزار بهطور غیرعادی اجرا میشود، از تمام فایلهای موجود در این پوشه نسخه پشتیبان تهیه و پاک کنید تا نرمافزار را مجدداً راهاندازی کنید",
|
||||||
"Open Core Dir": "باز کردن پوشه هسته",
|
"Open Core Dir": "باز کردن پوشه هسته",
|
||||||
"Open Logs Dir": "باز کردن پوشه لاگها",
|
"Open Logs Dir": "باز کردن پوشه لاگها",
|
||||||
"Check for Updates": "بررسی برای بهروزرسانیها",
|
"Check for Updates": "بررسی برای بهروزرسانیها",
|
||||||
@@ -354,7 +356,6 @@
|
|||||||
"Match Whole Word": "تطبیق کل کلمه",
|
"Match Whole Word": "تطبیق کل کلمه",
|
||||||
"Use Regular Expression": "استفاده از عبارت منظم",
|
"Use Regular Expression": "استفاده از عبارت منظم",
|
||||||
"Profile Imported Successfully": "پروفایل با موفقیت وارد شد",
|
"Profile Imported Successfully": "پروفایل با موفقیت وارد شد",
|
||||||
"Clash Config Updated": "پیکربندی Clash بهروزرسانی شد",
|
|
||||||
"Profile Switched": "پروفایل تغییر یافت",
|
"Profile Switched": "پروفایل تغییر یافت",
|
||||||
"Profile Reactivated": "پروفایل مجدداً فعال شد",
|
"Profile Reactivated": "پروفایل مجدداً فعال شد",
|
||||||
"Only YAML Files Supported": "فقط فایلهای YAML پشتیبانی میشوند",
|
"Only YAML Files Supported": "فقط فایلهای YAML پشتیبانی میشوند",
|
||||||
@@ -397,5 +398,38 @@
|
|||||||
"Confirm to delete this backup file?": "آیا از حذف این فایل پشتیبان اطمینان دارید؟",
|
"Confirm to delete this backup file?": "آیا از حذف این فایل پشتیبان اطمینان دارید؟",
|
||||||
"Confirm to restore this backup file?": "آیا از بازیابی این فایل پشتیبان اطمینان دارید؟",
|
"Confirm to restore this backup file?": "آیا از بازیابی این فایل پشتیبان اطمینان دارید؟",
|
||||||
"Restore Success, App will restart in 1s": "بازیابی با موفقیت انجام شد، برنامه در 1 ثانیه راهاندازی مجدد میشود",
|
"Restore Success, App will restart in 1s": "بازیابی با موفقیت انجام شد، برنامه در 1 ثانیه راهاندازی مجدد میشود",
|
||||||
"Failed to fetch backup files": "دریافت فایلهای پشتیبان ناموفق بود"
|
"Failed to fetch backup files": "دریافت فایلهای پشتیبان ناموفق بود",
|
||||||
|
"Profile": "پروفایل",
|
||||||
|
"Help": "راهنما",
|
||||||
|
"About": "درباره",
|
||||||
|
"Theme": "پوسته",
|
||||||
|
"Main Window": "پنجره اصلی",
|
||||||
|
"Group Icon": "آیکون گروه",
|
||||||
|
"Menu Icon": "آیکون منو",
|
||||||
|
"PAC File": "فایل PAC",
|
||||||
|
"Web UI": "رابط وب",
|
||||||
|
"Hotkeys": "کلیدهای میانبر",
|
||||||
|
"Verge Mixed Port": "پورت ترکیبی Verge",
|
||||||
|
"Verge Socks Port": "پورت Socks Verge",
|
||||||
|
"Verge Redir Port": "پورت تغییر مسیر Verge",
|
||||||
|
"Verge Tproxy Port": "پورت Tproxy Verge",
|
||||||
|
"Verge Port": "پورت Verge",
|
||||||
|
"Verge HTTP Enabled": "HTTP Verge فعال",
|
||||||
|
"WebDAV URL": "آدرس WebDAV",
|
||||||
|
"WebDAV Username": "نام کاربری WebDAV",
|
||||||
|
"WebDAV Password": "رمز عبور WebDAV",
|
||||||
|
"Dashboard": "داشبورد",
|
||||||
|
"Restart App": "راهاندازی مجدد برنامه",
|
||||||
|
"Restart Clash Core": "راهاندازی مجدد هسته Clash",
|
||||||
|
"TUN Mode": "حالت TUN",
|
||||||
|
"Copy Env": "کپی متغیرهای محیطی",
|
||||||
|
"Conf Dir": "پوشه پیکربندی",
|
||||||
|
"Core Dir": "پوشه هسته",
|
||||||
|
"Logs Dir": "پوشه لاگها",
|
||||||
|
"Open Dir": "باز کردن پوشه",
|
||||||
|
"More": "بیشتر",
|
||||||
|
"Rule Mode": "حالت قوانین",
|
||||||
|
"Global Mode": "حالت جهانی",
|
||||||
|
"Direct Mode": "حالت مستقیم",
|
||||||
|
"Enable Tray Speed": "فعال کردن سرعت ترای"
|
||||||
}
|
}
|
||||||
|
|||||||
434
src/locales/id.json
Normal file
434
src/locales/id.json
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
{
|
||||||
|
"millis": "milidetik",
|
||||||
|
"seconds": "detik",
|
||||||
|
"mins": "menit",
|
||||||
|
"Back": "Kembali",
|
||||||
|
"Close": "Tutup",
|
||||||
|
"Cancel": "Batal",
|
||||||
|
"Confirm": "Konfirmasi",
|
||||||
|
"Maximize": "Maksimalkan",
|
||||||
|
"Minimize": "Minimalkan",
|
||||||
|
"Format document": "Format dokumen",
|
||||||
|
"Empty": "Kosong",
|
||||||
|
"New": "Baru",
|
||||||
|
"Edit": "Ubah",
|
||||||
|
"Save": "Simpan",
|
||||||
|
"Delete": "Hapus",
|
||||||
|
"Enable": "Aktifkan",
|
||||||
|
"Disable": "Nonaktifkan",
|
||||||
|
"Label-Proxies": "Proksi",
|
||||||
|
"Label-Profiles": "Profil",
|
||||||
|
"Label-Connections": "Koneksi",
|
||||||
|
"Label-Rules": "Aturan",
|
||||||
|
"Label-Logs": "Log",
|
||||||
|
"Label-Test": "Tes",
|
||||||
|
"Label-Settings": "Pengaturan",
|
||||||
|
"Dashboard": "Dasbor",
|
||||||
|
"Profile": "Profil",
|
||||||
|
"Help": "Bantuan",
|
||||||
|
"About": "Tentang",
|
||||||
|
"Theme": "Tema",
|
||||||
|
"Main Window": "Jendela Utama",
|
||||||
|
"Group Icon": "Ikon Grup",
|
||||||
|
"Menu Icon": "Ikon Menu",
|
||||||
|
"PAC File": "Berkas PAC",
|
||||||
|
"Web UI": "Antarmuka Web",
|
||||||
|
"Hotkeys": "Pintasan",
|
||||||
|
"Verge Mixed Port": "Port Campuran Verge",
|
||||||
|
"Verge Socks Port": "Port Socks Verge",
|
||||||
|
"Verge Redir Port": "Port Pengalihan Verge",
|
||||||
|
"Verge Tproxy Port": "Port Tproxy Verge",
|
||||||
|
"Verge Port": "Port Verge",
|
||||||
|
"Verge HTTP Enabled": "HTTP Verge Diaktifkan",
|
||||||
|
"WebDAV URL": "URL WebDAV",
|
||||||
|
"WebDAV Username": "Nama Pengguna WebDAV",
|
||||||
|
"WebDAV Password": "Kata Sandi WebDAV",
|
||||||
|
"Restart App": "Mulai Ulang Aplikasi",
|
||||||
|
"Restart Clash Core": "Mulai Ulang Core Clash",
|
||||||
|
"TUN Mode": "Mode TUN",
|
||||||
|
"Copy Env": "Salin Env",
|
||||||
|
"Conf Dir": "Direktori Konfigurasi",
|
||||||
|
"Core Dir": "Direktori Core",
|
||||||
|
"Logs Dir": "Direktori Log",
|
||||||
|
"Open Dir": "Buka Direktori",
|
||||||
|
"More": "Lainnya",
|
||||||
|
"Rule Mode": "Mode Aturan",
|
||||||
|
"Global Mode": "Mode Global",
|
||||||
|
"Direct Mode": "Mode Langsung",
|
||||||
|
"Proxies": "Proksi",
|
||||||
|
"Proxy Groups": "Grup Proksi",
|
||||||
|
"Proxy Provider": "Penyedia Proksi",
|
||||||
|
"Update All": "Perbarui Semua",
|
||||||
|
"Update At": "Diperbarui Pada",
|
||||||
|
"rule": "aturan",
|
||||||
|
"global": "global",
|
||||||
|
"direct": "langsung",
|
||||||
|
"script": "skrip",
|
||||||
|
"Location": "Lokasi",
|
||||||
|
"Delay check": "Periksa Keterlambatan",
|
||||||
|
"Sort by default": "Urutkan secara default",
|
||||||
|
"Sort by delay": "Urutkan berdasarkan keterlambatan",
|
||||||
|
"Sort by name": "Urutkan berdasarkan nama",
|
||||||
|
"Delay check URL": "URL Periksa Keterlambatan",
|
||||||
|
"Delay check to cancel fixed": "Periksa keterlambatan untuk membatalkan tetap",
|
||||||
|
"Proxy basic": "Dasar Proksi",
|
||||||
|
"Proxy detail": "Detail Proksi",
|
||||||
|
"Profiles": "Profil",
|
||||||
|
"Update All Profiles": "Perbarui Semua Profil",
|
||||||
|
"View Runtime Config": "Lihat Konfigurasi Runtime",
|
||||||
|
"Reactivate Profiles": "Reaktivasi Profil",
|
||||||
|
"Paste": "Tempel",
|
||||||
|
"Profile URL": "URL Profil",
|
||||||
|
"Import": "Impor",
|
||||||
|
"From": "Dari",
|
||||||
|
"Update Time": "Waktu Pembaruan",
|
||||||
|
"Used / Total": "Digunakan / Total",
|
||||||
|
"Expire Time": "Waktu Kedaluwarsa",
|
||||||
|
"Create Profile": "Buat Profil",
|
||||||
|
"Edit Profile": "Ubah Profil",
|
||||||
|
"Edit Proxies": "Ubah Proksi",
|
||||||
|
"Use newlines for multiple uri": "Gunakan baris baru untuk beberapa URI (mendukung pengkodean Base64)",
|
||||||
|
"Edit Rules": "Ubah Aturan",
|
||||||
|
"Rule Type": "Jenis Aturan",
|
||||||
|
"Rule Content": "Konten Aturan",
|
||||||
|
"Proxy Policy": "Kebijakan Proksi",
|
||||||
|
"No Resolve": "Tidak Menyelesaikan",
|
||||||
|
"Prepend Rule": "Tambahkan Aturan di Awal",
|
||||||
|
"Append Rule": "Tambahkan Aturan di Akhir",
|
||||||
|
"Prepend Group": "Tambahkan Grup di Awal",
|
||||||
|
"Append Group": "Tambahkan Grup di Akhir",
|
||||||
|
"Prepend Proxy": "Tambahkan Proksi di Awal",
|
||||||
|
"Append Proxy": "Tambahkan Proksi di Akhir",
|
||||||
|
"Rule Condition Required": "Kondisi Aturan Diperlukan",
|
||||||
|
"Invalid Rule": "Aturan Tidak Valid",
|
||||||
|
"Advanced": "Lanjutan",
|
||||||
|
"Visualization": "Visualisasi",
|
||||||
|
"DOMAIN": "Cocok dengan nama domain lengkap",
|
||||||
|
"DOMAIN-SUFFIX": "Cocok dengan sufiks domain",
|
||||||
|
"DOMAIN-KEYWORD": "Cocok dengan kata kunci domain",
|
||||||
|
"DOMAIN-REGEX": "Cocok dengan domain menggunakan ekspresi reguler",
|
||||||
|
"GEOSITE": "Cocok dengan domain dalam Geosite",
|
||||||
|
"GEOIP": "Cocok dengan kode negara alamat IP",
|
||||||
|
"SRC-GEOIP": "Cocok dengan kode negara alamat IP sumber",
|
||||||
|
"IP-ASN": "Cocok dengan ASN alamat IP",
|
||||||
|
"SRC-IP-ASN": "Cocok dengan ASN alamat IP sumber",
|
||||||
|
"IP-CIDR": "Cocok dengan rentang alamat IP",
|
||||||
|
"IP-CIDR6": "Cocok dengan rentang alamat IPv6",
|
||||||
|
"SRC-IP-CIDR": "Cocok dengan rentang alamat IP sumber",
|
||||||
|
"IP-SUFFIX": "Cocok dengan rentang sufiks alamat IP",
|
||||||
|
"SRC-IP-SUFFIX": "Cocok dengan rentang sufiks alamat IP sumber",
|
||||||
|
"SRC-PORT": "Cocok dengan rentang port sumber",
|
||||||
|
"DST-PORT": "Cocok dengan rentang port tujuan",
|
||||||
|
"IN-PORT": "Cocok dengan port masuk",
|
||||||
|
"DSCP": "Penandaan DSCP (hanya untuk tproxy UDP masuk)",
|
||||||
|
"PROCESS-NAME": "Cocok dengan nama proses (nama paket Android)",
|
||||||
|
"PROCESS-PATH": "Cocok dengan jalur proses lengkap",
|
||||||
|
"PROCESS-NAME-REGEX": "Cocok dengan nama proses lengkap menggunakan ekspresi reguler (nama paket Android)",
|
||||||
|
"PROCESS-PATH-REGEX": "Cocok dengan jalur proses lengkap menggunakan ekspresi reguler",
|
||||||
|
"NETWORK": "Cocok dengan protokol transportasi (tcp/udp)",
|
||||||
|
"UID": "Cocok dengan ID PENGGUNA Linux",
|
||||||
|
"IN-TYPE": "Cocok dengan jenis masuk",
|
||||||
|
"IN-USER": "Cocok dengan nama pengguna masuk",
|
||||||
|
"IN-NAME": "Cocok dengan nama masuk",
|
||||||
|
"SUB-RULE": "Sub-aturan",
|
||||||
|
"RULE-SET": "Cocok dengan set aturan",
|
||||||
|
"AND": "Logika DAN",
|
||||||
|
"OR": "Logika ATAU",
|
||||||
|
"NOT": "Logika TIDAK",
|
||||||
|
"MATCH": "Cocok dengan semua permintaan",
|
||||||
|
"DIRECT": "Data langsung keluar",
|
||||||
|
"REJECT": "Mencegat permintaan",
|
||||||
|
"REJECT-DROP": "Membuang permintaan",
|
||||||
|
"PASS": "Lewati aturan ini saat cocok",
|
||||||
|
"Edit Groups": "Ubah Grup Proksi",
|
||||||
|
"Group Type": "Jenis Grup",
|
||||||
|
"select": "Pilih proksi secara manual",
|
||||||
|
"url-test": "Pilih proksi berdasarkan keterlambatan tes URL",
|
||||||
|
"fallback": "Beralih ke proksi lain saat terjadi kesalahan",
|
||||||
|
"load-balance": "Distribusikan proksi berdasarkan penyeimbangan beban",
|
||||||
|
"relay": "Lewatkan melalui rantai proksi yang ditentukan",
|
||||||
|
"Group Name": "Nama Grup",
|
||||||
|
"Use Proxies": "Gunakan Proksi",
|
||||||
|
"Use Provider": "Gunakan Penyedia",
|
||||||
|
"Health Check Url": "URL Pemeriksaan Kesehatan",
|
||||||
|
"Expected Status": "Status yang Diharapkan",
|
||||||
|
"Interval": "Interval",
|
||||||
|
"Lazy": "Malas",
|
||||||
|
"Timeout": "Waktu Habis",
|
||||||
|
"Max Failed Times": "Jumlah Gagal Maksimal",
|
||||||
|
"Interface Name": "Nama Antarmuka",
|
||||||
|
"Routing Mark": "Tanda Routing",
|
||||||
|
"Include All": "Sertakan Semua Proksi dan Penyedia",
|
||||||
|
"Include All Providers": "Sertakan Semua Penyedia",
|
||||||
|
"Include All Proxies": "Sertakan Semua Proksi",
|
||||||
|
"Exclude Filter": "Kecualikan Filter",
|
||||||
|
"Exclude Type": "Kecualikan Jenis",
|
||||||
|
"Disable UDP": "Nonaktifkan UDP",
|
||||||
|
"Hidden": "Tersembunyi",
|
||||||
|
"Group Name Required": "Nama Grup Diperlukan",
|
||||||
|
"Group Name Already Exists": "Nama Grup Sudah Ada",
|
||||||
|
"Extend Config": "Perluas Konfigurasi",
|
||||||
|
"Extend Script": "Perluas Skrip",
|
||||||
|
"Global Merge": "Perluas Konfigurasi Global",
|
||||||
|
"Global Script": "Perluas Skrip Global",
|
||||||
|
"Type": "Jenis",
|
||||||
|
"Name": "Nama",
|
||||||
|
"Descriptions": "Deskripsi",
|
||||||
|
"Subscription URL": "URL Langganan",
|
||||||
|
"Update Interval": "Interval Pembaruan",
|
||||||
|
"Choose File": "Pilih Berkas",
|
||||||
|
"Use System Proxy": "Gunakan Proksi Sistem",
|
||||||
|
"Use Clash Proxy": "Gunakan Proksi Clash",
|
||||||
|
"Accept Invalid Certs (Danger)": "Terima Sertifikat Tidak Valid (Bahaya)",
|
||||||
|
"Refresh": "Segarkan",
|
||||||
|
"Home": "Beranda",
|
||||||
|
"Select": "Pilih",
|
||||||
|
"Edit Info": "Ubah Info",
|
||||||
|
"Edit File": "Ubah Berkas",
|
||||||
|
"Open File": "Buka Berkas",
|
||||||
|
"Update": "Perbarui",
|
||||||
|
"Update(Proxy)": "Perbarui (Proksi)",
|
||||||
|
"Confirm deletion": "Konfirmasi penghapusan",
|
||||||
|
"This operation is not reversible": "Operasi ini tidak dapat dibatalkan",
|
||||||
|
"Script Console": "Konsol Skrip",
|
||||||
|
"To Top": "Ke Atas",
|
||||||
|
"To End": "Ke Bawah",
|
||||||
|
"Connections": "Koneksi",
|
||||||
|
"Table View": "Tampilan Tabel",
|
||||||
|
"List View": "Tampilan Daftar",
|
||||||
|
"Close All": "Tutup Semua",
|
||||||
|
"Default": "Default",
|
||||||
|
"Download Speed": "Kecepatan Unduh",
|
||||||
|
"Upload Speed": "Kecepatan Unggah",
|
||||||
|
"Host": "Host",
|
||||||
|
"Downloaded": "Diunduh",
|
||||||
|
"Uploaded": "Diunggah",
|
||||||
|
"DL Speed": "Kecepatan Unduh",
|
||||||
|
"UL Speed": "Kecepatan Unggah",
|
||||||
|
"Chains": "Rantai",
|
||||||
|
"Rule": "Aturan",
|
||||||
|
"Process": "Proses",
|
||||||
|
"Time": "Waktu",
|
||||||
|
"Source": "Sumber",
|
||||||
|
"Destination IP": "IP Tujuan",
|
||||||
|
"Close Connection": "Tutup Koneksi",
|
||||||
|
"Rules": "Aturan",
|
||||||
|
"Rule Provider": "Penyedia Aturan",
|
||||||
|
"Logs": "Log",
|
||||||
|
"Pause": "Jeda",
|
||||||
|
"Clear": "Bersihkan",
|
||||||
|
"Test": "Tes",
|
||||||
|
"Test All": "Tes Semua",
|
||||||
|
"Create Test": "Buat Tes",
|
||||||
|
"Edit Test": "Ubah Tes",
|
||||||
|
"Icon": "Ikon",
|
||||||
|
"Test URL": "URL Tes",
|
||||||
|
"Settings": "Pengaturan",
|
||||||
|
"System Setting": "Pengaturan Sistem",
|
||||||
|
"Tun Mode": "Mode Tun (NIC Virtual)",
|
||||||
|
"Reset to Default": "Setel Ulang ke Default",
|
||||||
|
"Tun Mode Info": "Mode Tun (NIC Virtual): Menangkap semua lalu lintas sistem, saat diaktifkan, tidak perlu mengaktifkan proksi sistem.",
|
||||||
|
"Stack": "Tumpukan Tun",
|
||||||
|
"System and Mixed Can Only be Used in Service Mode": "Sistem dan Campuran Hanya Dapat Digunakan dalam Mode Layanan",
|
||||||
|
"Device": "Nama Perangkat",
|
||||||
|
"Auto Route": "Rute Otomatis",
|
||||||
|
"Strict Route": "Rute Ketat",
|
||||||
|
"Auto Detect Interface": "Deteksi Antarmuka Otomatis",
|
||||||
|
"DNS Hijack": "Pembajakan DNS",
|
||||||
|
"MTU": "Unit Transmisi Maksimum",
|
||||||
|
"Service Mode": "Mode Layanan",
|
||||||
|
"Service Mode Info": "Harap instal mode layanan sebelum mengaktifkan mode TUN. Proses kernel yang dimulai oleh layanan dapat memperoleh izin untuk menginstal kartu jaringan virtual (mode TUN)",
|
||||||
|
"Current State": "Status Saat Ini",
|
||||||
|
"pending": "tertunda",
|
||||||
|
"installed": "terinstal",
|
||||||
|
"uninstall": "dicopot",
|
||||||
|
"active": "aktif",
|
||||||
|
"unknown": "tidak diketahui",
|
||||||
|
"Information: Please make sure that the Clash Verge Service is installed and enabled": "Informasi: Harap pastikan bahwa Layanan Clash Verge terinstal dan diaktifkan",
|
||||||
|
"Install": "Instal",
|
||||||
|
"Uninstall": "Copot",
|
||||||
|
"Disable Service Mode": "Nonaktifkan Mode Layanan",
|
||||||
|
"System Proxy": "Proksi Sistem",
|
||||||
|
"System Proxy Info": "Aktifkan untuk mengubah pengaturan proksi sistem operasi. Jika pengaktifan gagal, ubah pengaturan proksi sistem operasi secara manual",
|
||||||
|
"System Proxy Setting": "Pengaturan Proksi Sistem",
|
||||||
|
"Current System Proxy": "Proksi Sistem Saat Ini",
|
||||||
|
"Enable status": "Status Pengaktifan:",
|
||||||
|
"Enabled": "Diaktifkan",
|
||||||
|
"Disabled": "Dinonaktifkan",
|
||||||
|
"Server Addr": "Alamat Server: ",
|
||||||
|
"Not available": "Tidak tersedia",
|
||||||
|
"Proxy Guard": "Penjaga Proksi",
|
||||||
|
"Proxy Guard Info": "Aktifkan untuk mencegah perangkat lunak lain mengubah pengaturan proksi sistem operasi",
|
||||||
|
"Guard Duration": "Durasi Penjagaan",
|
||||||
|
"Always use Default Bypass": "Selalu gunakan Bypass Default",
|
||||||
|
"Proxy Bypass": "Pengaturan Bypass Proksi: ",
|
||||||
|
"Bypass": "Bypass: ",
|
||||||
|
"Use PAC Mode": "Gunakan Mode PAC",
|
||||||
|
"PAC Script Content": "Konten Skrip PAC",
|
||||||
|
"PAC URL": "URL PAC: ",
|
||||||
|
"Auto Launch": "Peluncuran Otomatis",
|
||||||
|
"Silent Start": "Mulai Senyap",
|
||||||
|
"Silent Start Info": "Mulai program dalam mode latar belakang tanpa menampilkan panel",
|
||||||
|
"TG Channel": "Saluran Telegram",
|
||||||
|
"Manual": "Manual",
|
||||||
|
"Github Repo": "Repositori Github",
|
||||||
|
"Clash Setting": "Pengaturan Clash",
|
||||||
|
"Allow Lan": "Izinkan LAN",
|
||||||
|
"Network Interface": "Antarmuka Jaringan",
|
||||||
|
"Ip Address": "Alamat IP",
|
||||||
|
"Mac Address": "Alamat MAC",
|
||||||
|
"IPv6": "IPv6",
|
||||||
|
"Unified Delay": "Keterlambatan Terpadu",
|
||||||
|
"Unified Delay Info": "Saat keterlambatan terpadu diaktifkan, dua tes keterlambatan akan dilakukan untuk menghilangkan perbedaan keterlambatan antara berbagai jenis node yang disebabkan oleh jabat tangan koneksi, dll.",
|
||||||
|
"Log Level": "Tingkat Log",
|
||||||
|
"Log Level Info": "Ini hanya berlaku untuk file log kernel di folder layanan di direktori log.",
|
||||||
|
"Port Config": "Konfigurasi Port",
|
||||||
|
"Random Port": "Port Acak",
|
||||||
|
"Mixed Port": "Port Campuran",
|
||||||
|
"Socks Port": "Port Socks",
|
||||||
|
"Http Port": "Port Http(s)",
|
||||||
|
"Redir Port": "Port Redir",
|
||||||
|
"Tproxy Port": "Port Tproxy",
|
||||||
|
"External": "Eksternal",
|
||||||
|
"External Controller": "Alamat Pengendali Eksternal",
|
||||||
|
"Core Secret": "Rahasia Inti",
|
||||||
|
"Recommended": "Direkomendasikan",
|
||||||
|
"Open URL": "Buka URL",
|
||||||
|
"Replace host, port, secret with %host, %port, %secret": "Ganti host, port, rahasia dengan %host, %port, %secret",
|
||||||
|
"Support %host, %port, %secret": "Dukung %host, %port, %secret",
|
||||||
|
"Clash Core": "Inti Clash",
|
||||||
|
"Upgrade": "Tingkatkan",
|
||||||
|
"Restart": "Mulai Ulang",
|
||||||
|
"Release Version": "Versi Rilis",
|
||||||
|
"Alpha Version": "Versi Alpha",
|
||||||
|
"Please Enable Service Mode": "Harap Instal dan Aktifkan Mode Layanan Terlebih Dahulu",
|
||||||
|
"Please enter your root password": "Harap masukkan kata sandi root Anda",
|
||||||
|
"Grant": "Izinkan",
|
||||||
|
"Open UWP tool": "Buka alat UWP",
|
||||||
|
"Open UWP tool Info": "Sejak Windows 8, aplikasi UWP (seperti Microsoft Store) dibatasi dari mengakses layanan jaringan host lokal secara langsung, dan alat ini dapat digunakan untuk melewati pembatasan ini",
|
||||||
|
"Update GeoData": "Perbarui GeoData",
|
||||||
|
"Verge Setting": "Pengaturan Verge",
|
||||||
|
"Language": "Bahasa",
|
||||||
|
"Theme Mode": "Mode Tema",
|
||||||
|
"theme.light": "Terang",
|
||||||
|
"theme.dark": "Gelap",
|
||||||
|
"theme.system": "Sistem",
|
||||||
|
"Tray Click Event": "Acara Klik Tray",
|
||||||
|
"Show Main Window": "Tampilkan Jendela Utama",
|
||||||
|
"Copy Env Type": "Salin Jenis Env",
|
||||||
|
"Copy Success": "Salin Berhasil",
|
||||||
|
"Start Page": "Halaman Mulai",
|
||||||
|
"Startup Script": "Skrip Startup",
|
||||||
|
"Browse": "Jelajahi",
|
||||||
|
"Theme Setting": "Pengaturan Tema",
|
||||||
|
"Primary Color": "Warna Utama",
|
||||||
|
"Secondary Color": "Warna Sekunder",
|
||||||
|
"Primary Text": "Teks Utama",
|
||||||
|
"Secondary Text": "Teks Sekunder",
|
||||||
|
"Info Color": "Warna Info",
|
||||||
|
"Warning Color": "Warna Peringatan",
|
||||||
|
"Error Color": "Warna Kesalahan",
|
||||||
|
"Success Color": "Warna Keberhasilan",
|
||||||
|
"Font Family": "Keluarga Font",
|
||||||
|
"CSS Injection": "Injeksi CSS",
|
||||||
|
"Layout Setting": "Pengaturan Tata Letak",
|
||||||
|
"Traffic Graph": "Grafik Lalu Lintas",
|
||||||
|
"Memory Usage": "Penggunaan Memori",
|
||||||
|
"Memory Cleanup": "Ketuk untuk membersihkan memori",
|
||||||
|
"Proxy Group Icon": "Ikon Grup Proksi",
|
||||||
|
"Nav Icon": "Ikon Navigasi",
|
||||||
|
"Monochrome": "Monokrom",
|
||||||
|
"Colorful": "Berwarna",
|
||||||
|
"Tray Icon": "Ikon Tray",
|
||||||
|
"Common Tray Icon": "Ikon Tray Umum",
|
||||||
|
"System Proxy Tray Icon": "Ikon Tray Proksi Sistem",
|
||||||
|
"Tun Tray Icon": "Ikon Tray Tun",
|
||||||
|
"Miscellaneous": "Lain-lain",
|
||||||
|
"App Log Level": "Tingkat Log Aplikasi",
|
||||||
|
"Auto Close Connections": "Tutup Koneksi Otomatis",
|
||||||
|
"Auto Close Connections Info": "Hentikan koneksi yang sudah ada saat pemilihan grup proksi atau mode proksi berubah",
|
||||||
|
"Auto Check Update": "Periksa Pembaruan Otomatis",
|
||||||
|
"Enable Builtin Enhanced": "Aktifkan Peningkatan Bawaan",
|
||||||
|
"Enable Builtin Enhanced Info": "Penanganan kompatibilitas untuk file konfigurasi",
|
||||||
|
"Proxy Layout Columns": "Kolom Tata Letak Proksi",
|
||||||
|
"Auto Columns": "Kolom Otomatis",
|
||||||
|
"Auto Log Clean": "Pembersihan Log Otomatis",
|
||||||
|
"Never Clean": "Jangan Pernah Bersihkan",
|
||||||
|
"Retain _n Days": "Simpan {{n}} Hari",
|
||||||
|
"Default Latency Test": "Tes Latensi Default",
|
||||||
|
"Default Latency Test Info": "Digunakan hanya untuk pengujian permintaan klien HTTP dan tidak akan mempengaruhi file konfigurasi",
|
||||||
|
"Default Latency Timeout": "Waktu Habis Latensi Default",
|
||||||
|
"Hotkey Setting": "Pengaturan Pintasan",
|
||||||
|
"open_or_close_dashboard": "Buka/Tutup Dasbor",
|
||||||
|
"clash_mode_rule": "Mode Aturan",
|
||||||
|
"clash_mode_global": "Mode Global",
|
||||||
|
"clash_mode_direct": "Mode Langsung",
|
||||||
|
"toggle_system_proxy": "Aktifkan/Nonaktifkan Proksi Sistem",
|
||||||
|
"toggle_tun_mode": "Aktifkan/Nonaktifkan Mode Tun",
|
||||||
|
"Backup Setting": "Pengaturan Cadangan",
|
||||||
|
"Backup Setting Info": "Mendukung file konfigurasi cadangan WebDAV",
|
||||||
|
"Runtime Config": "Konfigurasi Runtime",
|
||||||
|
"Open Conf Dir": "Buka Direktori Konfigurasi",
|
||||||
|
"Open Conf Dir Info": "Jika perangkat lunak berjalan tidak normal, CADANGKAN dan hapus semua file di folder ini lalu mulai ulang perangkat lunak",
|
||||||
|
"Open Core Dir": "Buka Direktori Core",
|
||||||
|
"Open Logs Dir": "Buka Direktori Log",
|
||||||
|
"Check for Updates": "Periksa Pembaruan",
|
||||||
|
"Go to Release Page": "Pergi ke Halaman Rilis",
|
||||||
|
"Portable Updater Error": "Versi portabel tidak mendukung pembaruan dalam aplikasi. Harap unduh dan ganti secara manual",
|
||||||
|
"Break Change Update Error": "Versi ini adalah pembaruan besar dan tidak mendukung pembaruan dalam aplikasi. Harap hapus instalasi dan unduh serta instal versi baru secara manual",
|
||||||
|
"Open Dev Tools": "Buka Alat Pengembang",
|
||||||
|
"Exit": "Keluar",
|
||||||
|
"Verge Version": "Versi Verge",
|
||||||
|
"ReadOnly": "Hanya Baca",
|
||||||
|
"ReadOnlyMessage": "Tidak dapat mengedit di editor hanya baca",
|
||||||
|
"Filter": "Filter",
|
||||||
|
"Filter conditions": "Kondisi Filter",
|
||||||
|
"Match Case": "Cocokkan Kasus",
|
||||||
|
"Match Whole Word": "Cocokkan Kata Utuh",
|
||||||
|
"Use Regular Expression": "Gunakan Ekspresi Reguler",
|
||||||
|
"Profile Imported Successfully": "Profil Berhasil Diimpor",
|
||||||
|
"Profile Switched": "Profil Beralih",
|
||||||
|
"Profile Reactivated": "Profil Diaktifkan Kembali",
|
||||||
|
"Only YAML Files Supported": "Hanya File YAML yang Didukung",
|
||||||
|
"Settings Applied": "Pengaturan Diterapkan",
|
||||||
|
"Service Installed Successfully": "Layanan Berhasil Diinstal",
|
||||||
|
"Service Uninstalled Successfully": "Layanan Berhasil Dicopot",
|
||||||
|
"Proxy Daemon Duration Cannot be Less than 1 Second": "Durasi Daemon Proksi Tidak Boleh Kurang dari 1 Detik",
|
||||||
|
"Invalid Bypass Format": "Format Bypass Tidak Valid",
|
||||||
|
"Clash Port Modified": "Port Clash Diubah",
|
||||||
|
"Port Conflict": "Konflik Port",
|
||||||
|
"Restart Application to Apply Modifications": "Mulai Ulang Aplikasi untuk Menerapkan Modifikasi",
|
||||||
|
"External Controller Address Modified": "Alamat Pengendali Eksternal Diubah",
|
||||||
|
"Permissions Granted Successfully for _clash Core": "Izin Berhasil Diberikan untuk Core {{core}}",
|
||||||
|
"Core Version Updated": "Versi Core Diperbarui",
|
||||||
|
"Clash Core Restarted": "Core Clash Dimulai Ulang",
|
||||||
|
"Switched to _clash Core": "Beralih ke Core {{core}}",
|
||||||
|
"GeoData Updated": "GeoData Diperbarui",
|
||||||
|
"Currently on the Latest Version": "Saat ini pada Versi Terbaru",
|
||||||
|
"Import Subscription Successful": "Berlangganan Berhasil Diimpor",
|
||||||
|
"WebDAV Server URL": "URL Server WebDAV",
|
||||||
|
"Username": "Nama Pengguna",
|
||||||
|
"Password": "Kata Sandi",
|
||||||
|
"Backup": "Cadangan",
|
||||||
|
"Filename": "Nama Berkas",
|
||||||
|
"Actions": "Tindakan",
|
||||||
|
"Restore": "Pulihkan",
|
||||||
|
"No Backups": "Tidak ada cadangan yang tersedia",
|
||||||
|
"WebDAV URL Required": "URL WebDAV tidak boleh kosong",
|
||||||
|
"Invalid WebDAV URL": "Format URL WebDAV tidak valid",
|
||||||
|
"Username Required": "Nama pengguna tidak boleh kosong",
|
||||||
|
"Password Required": "Kata sandi tidak boleh kosong",
|
||||||
|
"Failed to Fetch Backups": "Gagal mengambil file cadangan",
|
||||||
|
"WebDAV Config Saved": "Konfigurasi WebDAV berhasil disimpan",
|
||||||
|
"WebDAV Config Save Failed": "Gagal menyimpan konfigurasi WebDAV: {{error}}",
|
||||||
|
"Backup Created": "Cadangan berhasil dibuat",
|
||||||
|
"Backup Failed": "Cadangan gagal: {{error}}",
|
||||||
|
"Delete Backup": "Hapus Cadangan",
|
||||||
|
"Restore Backup": "Pulihkan Cadangan",
|
||||||
|
"Backup Time": "Waktu Cadangan",
|
||||||
|
"Confirm to delete this backup file?": "Konfirmasi untuk menghapus file cadangan ini?",
|
||||||
|
"Confirm to restore this backup file?": "Konfirmasi untuk memulihkan file cadangan ini?",
|
||||||
|
"Restore Success, App will restart in 1s": "Pemulihan Berhasil, Aplikasi akan dimulai ulang dalam 1 detik",
|
||||||
|
"Failed to fetch backup files": "Gagal mengambil file cadangan",
|
||||||
|
"Enable Tray Speed": "Aktifkan Tray Speed"
|
||||||
|
}
|
||||||
@@ -229,6 +229,7 @@
|
|||||||
"Proxy Guard Info": "Включите эту функцию чтобы предотвратить изменение настроек прокси-сервера операционной системы другим программным обеспечением",
|
"Proxy Guard Info": "Включите эту функцию чтобы предотвратить изменение настроек прокси-сервера операционной системы другим программным обеспечением",
|
||||||
"Guard Duration": "Период защиты",
|
"Guard Duration": "Период защиты",
|
||||||
"Always use Default Bypass": "Всегда использовать стандартное обходное решение",
|
"Always use Default Bypass": "Всегда использовать стандартное обходное решение",
|
||||||
|
"Use Bypass Check": "Используйте проверку обхода",
|
||||||
"Proxy Bypass": "Игнорирование прокси: ",
|
"Proxy Bypass": "Игнорирование прокси: ",
|
||||||
"Bypass": "Игнорирование: ",
|
"Bypass": "Игнорирование: ",
|
||||||
"Use PAC Mode": "Используйте режим PAC",
|
"Use PAC Mode": "Используйте режим PAC",
|
||||||
@@ -337,6 +338,7 @@
|
|||||||
"Backup Setting Info": "Поддерживает файлы конфигурации резервного копирования WebDAV",
|
"Backup Setting Info": "Поддерживает файлы конфигурации резервного копирования WebDAV",
|
||||||
"Runtime Config": "Используемый конфиг",
|
"Runtime Config": "Используемый конфиг",
|
||||||
"Open Conf Dir": "Открыть папку приложения",
|
"Open Conf Dir": "Открыть папку приложения",
|
||||||
|
"Open Conf Dir Info": "Если программное обеспечение работает ненормально, сделайте резервную копию и удалите все файлы в этой папке, а затем перезапустите программное обеспечение",
|
||||||
"Open Core Dir": "Открыть папку ядра",
|
"Open Core Dir": "Открыть папку ядра",
|
||||||
"Open Logs Dir": "Открыть папку логов",
|
"Open Logs Dir": "Открыть папку логов",
|
||||||
"Check for Updates": "Проверить обновления",
|
"Check for Updates": "Проверить обновления",
|
||||||
@@ -354,7 +356,6 @@
|
|||||||
"Match Whole Word": "Полное совпадение слова",
|
"Match Whole Word": "Полное совпадение слова",
|
||||||
"Use Regular Expression": "Использовать регулярные выражения",
|
"Use Regular Expression": "Использовать регулярные выражения",
|
||||||
"Profile Imported Successfully": "Профиль успешно импортирован",
|
"Profile Imported Successfully": "Профиль успешно импортирован",
|
||||||
"Clash Config Updated": "Clash конфигурация Обновлена",
|
|
||||||
"Profile Switched": "Профиль изменен",
|
"Profile Switched": "Профиль изменен",
|
||||||
"Profile Reactivated": "Профиль повторно активирован",
|
"Profile Reactivated": "Профиль повторно активирован",
|
||||||
"Only YAML Files Supported": "Поддерживаются только файлы YAML",
|
"Only YAML Files Supported": "Поддерживаются только файлы YAML",
|
||||||
@@ -397,5 +398,38 @@
|
|||||||
"Confirm to delete this backup file?": "Вы уверены, что хотите удалить этот файл резервной копии?",
|
"Confirm to delete this backup file?": "Вы уверены, что хотите удалить этот файл резервной копии?",
|
||||||
"Confirm to restore this backup file?": "Вы уверены, что хотите восстановить этот файл резервной копии?",
|
"Confirm to restore this backup file?": "Вы уверены, что хотите восстановить этот файл резервной копии?",
|
||||||
"Restore Success, App will restart in 1s": "Восстановление успешно выполнено, приложение перезапустится через 1 секунду",
|
"Restore Success, App will restart in 1s": "Восстановление успешно выполнено, приложение перезапустится через 1 секунду",
|
||||||
"Failed to fetch backup files": "Не удалось получить файлы резервных копий"
|
"Failed to fetch backup files": "Не удалось получить файлы резервных копий",
|
||||||
|
"Profile": "Профиль",
|
||||||
|
"Help": "Помощь",
|
||||||
|
"About": "О программе",
|
||||||
|
"Theme": "Тема",
|
||||||
|
"Main Window": "Главное окно",
|
||||||
|
"Group Icon": "Иконка группы",
|
||||||
|
"Menu Icon": "Иконка меню",
|
||||||
|
"PAC File": "PAC файл",
|
||||||
|
"Web UI": "Веб-интерфейс",
|
||||||
|
"Hotkeys": "Горячие клавиши",
|
||||||
|
"Verge Mixed Port": "Смешанный порт Verge",
|
||||||
|
"Verge Socks Port": "Порт Verge Socks",
|
||||||
|
"Verge Redir Port": "Порт перенаправления Verge",
|
||||||
|
"Verge Tproxy Port": "Порт Verge Tproxy",
|
||||||
|
"Verge Port": "Порт Verge",
|
||||||
|
"Verge HTTP Enabled": "HTTP Verge включен",
|
||||||
|
"WebDAV URL": "URL WebDAV",
|
||||||
|
"WebDAV Username": "Имя пользователя WebDAV",
|
||||||
|
"WebDAV Password": "Пароль WebDAV",
|
||||||
|
"Dashboard": "Панель управления",
|
||||||
|
"Restart App": "Перезапустить приложение",
|
||||||
|
"Restart Clash Core": "Перезапустить ядро Clash",
|
||||||
|
"TUN Mode": "Режим TUN",
|
||||||
|
"Copy Env": "Копировать переменные окружения",
|
||||||
|
"Conf Dir": "Директория конфигурации",
|
||||||
|
"Core Dir": "Директория ядра",
|
||||||
|
"Logs Dir": "Директория логов",
|
||||||
|
"Open Dir": "Открыть директорию",
|
||||||
|
"More": "Ещё",
|
||||||
|
"Rule Mode": "Режим правил",
|
||||||
|
"Global Mode": "Глобальный режим",
|
||||||
|
"Direct Mode": "Прямой режим",
|
||||||
|
"Enable Tray Speed": "Включить скорость в лотке"
|
||||||
}
|
}
|
||||||
|
|||||||
434
src/locales/tt.json
Normal file
434
src/locales/tt.json
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
{
|
||||||
|
"millis": "Миллисекундлар",
|
||||||
|
"seconds": "Секундлар",
|
||||||
|
"mins": "Минутлар",
|
||||||
|
"Back": "Кире",
|
||||||
|
"Close": "Ябу",
|
||||||
|
"Cancel": "Баш тарту",
|
||||||
|
"Confirm": "Растау",
|
||||||
|
"Maximize": "Зурайту",
|
||||||
|
"Minimize": "Кечерәйтү",
|
||||||
|
"Format document": "Документны форматлау",
|
||||||
|
"Empty": "Буш",
|
||||||
|
"New": "Яңа",
|
||||||
|
"Edit": "Үзгәртү",
|
||||||
|
"Save": "Саклау",
|
||||||
|
"Delete": "Бетерү",
|
||||||
|
"Enable": "Кушу",
|
||||||
|
"Disable": "Сүндерү",
|
||||||
|
"Label-Proxies": "Прокси",
|
||||||
|
"Label-Profiles": "Профильләр",
|
||||||
|
"Label-Connections": "Тоташулар",
|
||||||
|
"Label-Rules": "Кагыйдәләр",
|
||||||
|
"Label-Logs": "Логлар",
|
||||||
|
"Label-Test": "Тест",
|
||||||
|
"Label-Settings": "Көйләүләр",
|
||||||
|
"Proxies": "Прокси",
|
||||||
|
"Proxy Groups": "Прокси төркемнәре",
|
||||||
|
"Proxy Provider": "Прокси провайдеры",
|
||||||
|
"Update All": "Барысын да яңарту",
|
||||||
|
"Update At": "Яңартылган вакыт",
|
||||||
|
"rule": "кагыйдә",
|
||||||
|
"global": "глобаль",
|
||||||
|
"direct": "туры",
|
||||||
|
"script": "скриптлы",
|
||||||
|
"Location": "Урын",
|
||||||
|
"Delay check": "Задержканы тикшерү",
|
||||||
|
"Sort by default": "Башлангыч итеп сортлау",
|
||||||
|
"Sort by delay": "Задержка буенча сортлау",
|
||||||
|
"Sort by name": "Исем буенча сортлау",
|
||||||
|
"Delay check URL": "Задержканы тикшерү URL-ы",
|
||||||
|
"Delay check to cancel fixed": "Беркетелгәнне гамәлдән чыгару өчен задержканы тикшерү",
|
||||||
|
"Proxy basic": "Прокси турында кыскача мәгълүмат",
|
||||||
|
"Proxy detail": "Прокси турында тулы мәгълүмат",
|
||||||
|
"Profiles": "Профильләр",
|
||||||
|
"Update All Profiles": "Барлык профильләрне яңарту",
|
||||||
|
"View Runtime Config": "Кулланылган конфигурацияне карау",
|
||||||
|
"Reactivate Profiles": "Профильләрне янәдән активлаштыру",
|
||||||
|
"Paste": "Кую",
|
||||||
|
"Profile URL": "Профиль URL-ы",
|
||||||
|
"Import": "Импорт",
|
||||||
|
"From": "Каян",
|
||||||
|
"Update Time": "Яңарту вакыты",
|
||||||
|
"Used / Total": "Кулланылган / Барлыгы",
|
||||||
|
"Expire Time": "Тамамлану вакыты",
|
||||||
|
"Create Profile": "Профиль булдыру",
|
||||||
|
"Edit Profile": "Профильне үзгәртү",
|
||||||
|
"Edit Proxies": "Проксины үзгәртү",
|
||||||
|
"Use newlines for multiple uri": "Берничә URI өчен яңа юл символын кулланыгыз (Base64 кодлавы ярдәм ителә)",
|
||||||
|
"Edit Rules": "Кагыйдәләрне үзгәртү",
|
||||||
|
"Rule Type": "Кагыйдә төре",
|
||||||
|
"Rule Content": "Кагыйдә эчтәлеге",
|
||||||
|
"Proxy Policy": "Прокси сәясәте",
|
||||||
|
"No Resolve": "Резолвсыз",
|
||||||
|
"Prepend Rule": "Кагыйдәне өскә өстәү",
|
||||||
|
"Append Rule": "Кагыйдәне аска өстәү",
|
||||||
|
"Prepend Group": "Төркемне өскә өстәү",
|
||||||
|
"Append Group": "Төркемне аска өстәү",
|
||||||
|
"Prepend Proxy": "Проксины өскә өстәү",
|
||||||
|
"Append Proxy": "Проксины аска өстәү",
|
||||||
|
"Rule Condition Required": "Кагыйдә шарты кирәк",
|
||||||
|
"Invalid Rule": "Яраксыз кагыйдә",
|
||||||
|
"Advanced": "Өстәмә",
|
||||||
|
"Visualization": "Визуализация",
|
||||||
|
"DOMAIN": "Домен исеменең тулы туры килүе",
|
||||||
|
"DOMAIN-SUFFIX": "Домен суффиксына туры килү",
|
||||||
|
"DOMAIN-KEYWORD": "Доменда төп сүзгә туры килү",
|
||||||
|
"DOMAIN-REGEX": "Доменны регекс аша туры китерү",
|
||||||
|
"GEOSITE": "Geosite исемлегендәге доменга туры килү",
|
||||||
|
"GEOIP": "IP-адресның ил коды буенча туры килү",
|
||||||
|
"SRC-GEOIP": "Чыганак IP-адресның ил коды буенча туры килү",
|
||||||
|
"IP-ASN": "IP-адрес ASN'ы буенча туры килү",
|
||||||
|
"SRC-IP-ASN": "Чыганак IP-адрес ASN'ы буенча туры килү",
|
||||||
|
"IP-CIDR": "IP-адреслар диапазонына туры килү",
|
||||||
|
"IP-CIDR6": "IPv6 адреслар диапазонына туры килү",
|
||||||
|
"SRC-IP-CIDR": "Чыганак IP-адреслар диапазонына туры килү",
|
||||||
|
"IP-SUFFIX": "IP-адрес суффиксына туры килү",
|
||||||
|
"SRC-IP-SUFFIX": "Чыганак IP-адрес суффиксына туры килү",
|
||||||
|
"SRC-PORT": "Чыганак портлар диапазонына туры килү",
|
||||||
|
"DST-PORT": "Максат портлар диапазонына туры килү",
|
||||||
|
"IN-PORT": "Керүче портка туры килү",
|
||||||
|
"DSCP": "DSCP тамгалавы (tproxy UDP өчен)",
|
||||||
|
"PROCESS-NAME": "Процесс исеменә туры килү (Android пакет исеме)",
|
||||||
|
"PROCESS-PATH": "Процесс юлына туры килү",
|
||||||
|
"PROCESS-NAME-REGEX": "Процесс исемен регекс белән туры китерү (Android пакет исеме)",
|
||||||
|
"PROCESS-PATH-REGEX": "Процесс юлын регекс белән туры китерү",
|
||||||
|
"NETWORK": "Транспорт протоколына (tcp/udp) туры килү",
|
||||||
|
"UID": "Linux USER ID'га туры килү",
|
||||||
|
"IN-TYPE": "Керүче тоташу төренә туры килү",
|
||||||
|
"IN-USER": "Керүче тоташу кулланучысына туры килү",
|
||||||
|
"IN-NAME": "Керүче тоташу исеменә туры килү",
|
||||||
|
"SUB-RULE": "Кушымча кагыйдә",
|
||||||
|
"RULE-SET": "Кагыйдәләр тупланмасына туры килү",
|
||||||
|
"AND": "Логик ҺӘМ",
|
||||||
|
"OR": "Логик ЯКИ",
|
||||||
|
"NOT": "Логик ТҮГЕЛ",
|
||||||
|
"MATCH": "Барлык сорауларга туры килә",
|
||||||
|
"DIRECT": "Туры чыгу",
|
||||||
|
"REJECT": "Сорауларны тоткарлау",
|
||||||
|
"REJECT-DROP": "Сорауларны кире кагу",
|
||||||
|
"PASS": "Туры килсә дә, бу кагыйдәне урап узу",
|
||||||
|
"Edit Groups": "Прокси төркемнәрен үзгәртү",
|
||||||
|
"Group Type": "Төркем төре",
|
||||||
|
"select": "Проксины кулдан сайлау",
|
||||||
|
"url-test": "URL-тест задержкасына карап прокси сайлау",
|
||||||
|
"fallback": "Хата булган очракта башка проксига күчү",
|
||||||
|
"load-balance": "Трафикны баланслау нигезендә прокси тарату",
|
||||||
|
"relay": "Билгеле прокси чылбыры аша тапшыру",
|
||||||
|
"Group Name": "Төркем исеме",
|
||||||
|
"Use Proxies": "Прокси куллану",
|
||||||
|
"Use Provider": "Провайдер куллану",
|
||||||
|
"Health Check Url": "Сәламәтлекне тикшерү URL-ы",
|
||||||
|
"Expected Status": "Көтелгән статус коды",
|
||||||
|
"Interval": "Интервал",
|
||||||
|
"Lazy": "Сак режим (lazy)",
|
||||||
|
"Timeout": "Таймаут",
|
||||||
|
"Max Failed Times": "Иң күп хаталы тикшерү саны",
|
||||||
|
"Interface Name": "Интерфейс исеме",
|
||||||
|
"Routing Mark": "Маршрут билгесе",
|
||||||
|
"Include All": "Барлык прокси һәм провайдерларны кертү",
|
||||||
|
"Include All Providers": "Барлык провайдерларны кертү",
|
||||||
|
"Include All Proxies": "Барлык проксины кертү",
|
||||||
|
"Exclude Filter": "Фильтр аша чыгару",
|
||||||
|
"Exclude Type": "Чыгару төре",
|
||||||
|
"Disable UDP": "UDP'ны сүндерү",
|
||||||
|
"Hidden": "Яшерен",
|
||||||
|
"Group Name Required": "Төркем исеме кирәк",
|
||||||
|
"Group Name Already Exists": "Әлеге төркем исеме бар инде",
|
||||||
|
"Extend Config": "Merge-ны үзгәртергә",
|
||||||
|
"Extend Script": "Script-ны үзгәртергә",
|
||||||
|
"Global Merge": "Гомумкеңәйтелгән көйләүләр",
|
||||||
|
"Global Script": "Гомумкеңәйтелгән скрипт",
|
||||||
|
"Type": "Төр",
|
||||||
|
"Name": "Исем",
|
||||||
|
"Descriptions": "Тасвирламалар",
|
||||||
|
"Subscription URL": "Подписка URL-ы",
|
||||||
|
"Update Interval": "Яңарту интервалы",
|
||||||
|
"Choose File": "Файл сайлау",
|
||||||
|
"Use System Proxy": "Системалы проксины кулланып яңарту",
|
||||||
|
"Use Clash Proxy": "Clash прокси кулланып яңарту",
|
||||||
|
"Accept Invalid Certs (Danger)": "Дөрес булмаган сертификатларны кабул итү (Куркыныч)",
|
||||||
|
"Refresh": "Яңарту",
|
||||||
|
"Home": "Баш бит",
|
||||||
|
"Select": "Сайлау",
|
||||||
|
"Edit Info": "Мәгълүматны үзгәртү",
|
||||||
|
"Edit File": "Файлны үзгәртү",
|
||||||
|
"Open File": "Файлны ачу",
|
||||||
|
"Update": "Яңарту",
|
||||||
|
"Update(Proxy)": "Яңарту (прокси аша)",
|
||||||
|
"Confirm deletion": "Бетерүне раслагыз",
|
||||||
|
"This operation is not reversible": "Бу гамәлне кире кайтарып булмый",
|
||||||
|
"Script Console": "Скрипт консоле",
|
||||||
|
"To Top": "Өскә",
|
||||||
|
"To End": "Аска",
|
||||||
|
"Connections": "Тоташулар",
|
||||||
|
"Table View": "Таблица күзаллау",
|
||||||
|
"List View": "Исемлек күзаллау",
|
||||||
|
"Close All": "Барысын да ябу",
|
||||||
|
"Default": "Башлангыч",
|
||||||
|
"Download Speed": "Йөкләү тизлеге",
|
||||||
|
"Upload Speed": "Йөкләү (чыгару) тизлеге",
|
||||||
|
"Host": "Хост",
|
||||||
|
"Downloaded": "Йөкләнгән",
|
||||||
|
"Uploaded": "Чыгарылган",
|
||||||
|
"DL Speed": "Йөкләү тизл.",
|
||||||
|
"UL Speed": "Чыгару тизл.",
|
||||||
|
"Chains": "Чылбырлар",
|
||||||
|
"Rule": "Кагыйдә",
|
||||||
|
"Process": "Процесс",
|
||||||
|
"Time": "Тоташу вакыты",
|
||||||
|
"Source": "Чыганак адресы",
|
||||||
|
"Destination IP": "Максат IP-адресы",
|
||||||
|
"Close Connection": "Тоташуны ябу",
|
||||||
|
"Rules": "Кагыйдәләр",
|
||||||
|
"Rule Provider": "Кагыйдә провайдеры",
|
||||||
|
"Logs": "Логлар",
|
||||||
|
"Pause": "Туктау",
|
||||||
|
"Clear": "Чистарту",
|
||||||
|
"Test": "Тест",
|
||||||
|
"Test All": "Барчасын тестлау",
|
||||||
|
"Create Test": "Тест булдыру",
|
||||||
|
"Edit Test": "Тестны үзгәртү",
|
||||||
|
"Icon": "Иконка",
|
||||||
|
"Test URL": "Тест URL-ы",
|
||||||
|
"Settings": "Көйләүләр",
|
||||||
|
"System Setting": "Система көйләүләре",
|
||||||
|
"Tun Mode": "Tun режимы (виртуаль челтәр адаптеры)",
|
||||||
|
"Reset to Default": "Башлангычка кайтару",
|
||||||
|
"Tun Mode Info": "Tun режимы бөтен системаның трафигын тотып ала. Аны кабызган очракта системалы проксины аерым кабызу таләп ителми.",
|
||||||
|
"Stack": "Стек",
|
||||||
|
"System and Mixed Can Only be Used in Service Mode": "Система яки кушылган режимнар бары тик сервис режимында гына активлаштырыла ала",
|
||||||
|
"Device": "Җайланма исеме",
|
||||||
|
"Auto Route": "Авто-маршрутлау",
|
||||||
|
"Strict Route": "Катгый маршрутлау",
|
||||||
|
"Auto Detect Interface": "Интерфейсны автоматик ачыклау",
|
||||||
|
"DNS Hijack": "DNS'ны үзгәртеп тоту (hijack)",
|
||||||
|
"MTU": "MTU (макс. тапшыру берәмлеге)",
|
||||||
|
"Service Mode": "Сервис режимы",
|
||||||
|
"Service Mode Info": "Tun режимын кабызганчы сервис режимын урнаштыру сорала. Сервис буларак эшләтелгән Clash ядросына виртуаль челтәр адаптеры (TUN) куллану рөхсәт ителә.",
|
||||||
|
"Current State": "Агымдагы торыш",
|
||||||
|
"pending": "Көтә",
|
||||||
|
"installed": "Урнаштырылган",
|
||||||
|
"uninstall": "Урнаштырылмаган",
|
||||||
|
"active": "Актив",
|
||||||
|
"unknown": "Билгесез",
|
||||||
|
"Information: Please make sure that the Clash Verge Service is installed and enabled": "Игътибар: Clash Verge сервисы урнаштырылган һәм активлаштырылган булырга тиеш",
|
||||||
|
"Install": "Урнаштыру",
|
||||||
|
"Uninstall": "Салдыру",
|
||||||
|
"Disable Service Mode": "Сервис режимын сүндерү",
|
||||||
|
"System Proxy": "Системалы прокси",
|
||||||
|
"System Proxy Info": "Системалы прокси көйләүләрен үзгәртү рөхсәтен бирегез. Әгәр рөхсәт алу мөмкин түгел икән, прокси көйләүләрен кулдан үзгәртегез",
|
||||||
|
"System Proxy Setting": "Системалы прокси көйләүләре",
|
||||||
|
"Current System Proxy": "Агымдагы системалы прокси",
|
||||||
|
"Enable status": "Активлаштыру статусы",
|
||||||
|
"Enabled": "Кушылган",
|
||||||
|
"Disabled": "Сүнгән",
|
||||||
|
"Server Addr": "Сервер адресы",
|
||||||
|
"Not available": "Мөмкин түгел",
|
||||||
|
"Proxy Guard": "Прокси саклаучы",
|
||||||
|
"Proxy Guard Info": "Системалы прокси көйләүләрен чит программа үзгәртмәсен өчен шушы функцияне кабызыгыз",
|
||||||
|
"Guard Duration": "Саклау вакыты",
|
||||||
|
"Always use Default Bypass": "Һәрвакыт төп Bypass-ны куллану",
|
||||||
|
"Proxy Bypass": "Проксины әйләнеп узу:",
|
||||||
|
"Bypass": "Әйләнеп узу:",
|
||||||
|
"Use PAC Mode": "PAC режимын куллану",
|
||||||
|
"PAC Script Content": "PAC скрипты эчтәлеге",
|
||||||
|
"PAC URL": "PAC адресы",
|
||||||
|
"Auto Launch": "Автостарт",
|
||||||
|
"Silent Start": "Фон режимында башлау",
|
||||||
|
"Silent Start Info": "Программаны фоновый режимда, тәрәзәсез эшләтеп җибәрү",
|
||||||
|
"TG Channel": "Telegram каналы",
|
||||||
|
"Manual": "Документация",
|
||||||
|
"Github Repo": "GitHub репозиториясе",
|
||||||
|
"Clash Setting": "Clash көйләүләре",
|
||||||
|
"Allow Lan": "Локаль челтәргә рөхсәт",
|
||||||
|
"Network Interface": "Челтәр интерфейсы",
|
||||||
|
"Ip Address": "IP адресы",
|
||||||
|
"Mac Address": "MAC адресы",
|
||||||
|
"IPv6": "IPv6",
|
||||||
|
"Unified Delay": "Бердәм задержка",
|
||||||
|
"Unified Delay Info": "Бердәм задержка актив булганда, төрле типтагы узеллар өчен икеләтә тест башкарыла, TCP установканы раслау аермаларын тигезләү максатында",
|
||||||
|
"Log Level": "Лог дәрәҗәсе",
|
||||||
|
"Log Level Info": "Бу фәкать сервис режимында эшләгән вакытта системалы журнал файлларына кагыла",
|
||||||
|
"Port Config": "Порт көйләүләре",
|
||||||
|
"Random Port": "Очраклы порт",
|
||||||
|
"Mixed Port": "Катнаш прокси порты",
|
||||||
|
"Socks Port": "Socks прокси порты",
|
||||||
|
"Http Port": "HTTP(s) прокси порты",
|
||||||
|
"Redir Port": "Redir — үтә күренмәле прокси порты",
|
||||||
|
"Tproxy Port": "Tproxy — үтә күренмәле прокси порты",
|
||||||
|
"External": "Тышкы",
|
||||||
|
"External Controller": "Тышкы контроллер адресы",
|
||||||
|
"Core Secret": "Серсүз",
|
||||||
|
"Recommended": "Тавсия ителә",
|
||||||
|
"Open URL": "URL ачарга",
|
||||||
|
"Replace host, port, secret with %host, %port, %secret": "Хост, порт, серсүзне %host, %port, %secret белән алмаштырыгыз",
|
||||||
|
"Support %host, %port, %secret": "%host, %port, %secret макросларын хуплау",
|
||||||
|
"Clash Core": "Clash ядросы",
|
||||||
|
"Upgrade": "Яңарту",
|
||||||
|
"Restart": "Перезапуск",
|
||||||
|
"Release Version": "Рәсми версия",
|
||||||
|
"Alpha Version": "Альфа-версия",
|
||||||
|
"Please Enable Service Mode": "Башта сервис режимын кабызырга кирәк",
|
||||||
|
"Please enter your root password": "root паролен языгыз",
|
||||||
|
"Grant": "Рөхсәт бирү",
|
||||||
|
"Open UWP tool": "UWP инструментын ачу",
|
||||||
|
"Open UWP tool Info": "Windows 8'дән башлап UWP кушымталары (Microsoft Store кебек) локаль хосттагы челтәр хезмәтләренә турыдан-туры тоташа алмый. Бу инструмент әлеге чикләүне әйләнеп узарга ярдәм итә",
|
||||||
|
"Update GeoData": "GeoData яңарту",
|
||||||
|
"Verge Setting": "Verge көйләүләре",
|
||||||
|
"Language": "Тел",
|
||||||
|
"Theme Mode": "Теманың режимы",
|
||||||
|
"theme.light": "Якты",
|
||||||
|
"theme.dark": "Караңгы",
|
||||||
|
"theme.system": "Система",
|
||||||
|
"Tray Click Event": "Трейдагы басу вакыйгасы",
|
||||||
|
"Show Main Window": "Төп тәрәзәне күрсәтү",
|
||||||
|
"Copy Env Type": "Env төрен күчереп алу",
|
||||||
|
"Copy Success": "Күчерелде",
|
||||||
|
"Start Page": "Баш бит",
|
||||||
|
"Startup Script": "Башлану скрипты",
|
||||||
|
"Browse": "Карау",
|
||||||
|
"Theme Setting": "Тема көйләүләре",
|
||||||
|
"Primary Color": "Төп төс",
|
||||||
|
"Secondary Color": "Икенче төс",
|
||||||
|
"Primary Text Color": "Төп текст төсе",
|
||||||
|
"Secondary Text Color": "Икенче текст төсе",
|
||||||
|
"Info Color": "Мәгълүмат төсе",
|
||||||
|
"Warning Color": "Кисәтү төсе",
|
||||||
|
"Error Color": "Хата төсе",
|
||||||
|
"Success Color": "Уңыш төсе",
|
||||||
|
"Font Family": "Шрифтлар гаиләсе",
|
||||||
|
"CSS Injection": "CSS кертү",
|
||||||
|
"Layout Setting": "Расположение көйләүләре",
|
||||||
|
"Traffic Graph": "Трафик графигы",
|
||||||
|
"Memory Usage": "Хәтер куллану",
|
||||||
|
"Memory Cleanup": "Хәтерне чистарту өчен басыгыз",
|
||||||
|
"Proxy Group Icon": "Прокси төркеме иконкасы",
|
||||||
|
"Nav Icon": "Навигация иконкасы",
|
||||||
|
"Monochrome": "Монохром",
|
||||||
|
"Colorful": "Төсле",
|
||||||
|
"Tray Icon": "Трей иконкасы",
|
||||||
|
"Common Tray Icon": "Гомуми трей иконкасы",
|
||||||
|
"System Proxy Tray Icon": "Системалы прокси иконкасы",
|
||||||
|
"Tun Tray Icon": "Tun (виртуаль адаптер) иконкасы",
|
||||||
|
"Miscellaneous": "Өстәмә көйләүләр",
|
||||||
|
"App Log Level": "Кушымта журналы дәрәҗәсе",
|
||||||
|
"Auto Close Connections": "Тоташуларны автоматик ябу",
|
||||||
|
"Auto Close Connections Info": "Прокси төркеме яисә режимын үзгәрткәндә актив тоташуларны өзү",
|
||||||
|
"Auto Check Update": "Яңартуларны автоматик тикшерү",
|
||||||
|
"Enable Builtin Enhanced": "Эчке камилләштерүне кабызу",
|
||||||
|
"Enable Builtin Enhanced Info": "Конфигурация файлы белән туры килә торган өстәмә оптимизация",
|
||||||
|
"Proxy Layout Columns": "Прокси күрсәтү баганалары саны",
|
||||||
|
"Auto Columns": "Авто баганалар",
|
||||||
|
"Auto Log Clean": "Логларны автоматик чистарту",
|
||||||
|
"Never Clean": "Беркайчан чистартмаска",
|
||||||
|
"Retain _n Days": "{{n}} көн саклау",
|
||||||
|
"Default Latency Test": "Тоткарлануны тикшерү сылтамасы (defaults)",
|
||||||
|
"Default Latency Test Info": "Бу фәкать клиентның HTTP сораулары тесты өчен кулланыла, конфигурация файлына йогынты ясамый",
|
||||||
|
"Default Latency Timeout": "Тоткарлануның стандарт таймауты",
|
||||||
|
"Hotkey Setting": "Клавиатура төймәләре (hotkey) көйләүләре",
|
||||||
|
"open_or_close_dashboard": "Панельне ачу/ябу",
|
||||||
|
"clash_mode_rule": "Кагыйдәләр режимы",
|
||||||
|
"clash_mode_global": "Глобаль режим",
|
||||||
|
"clash_mode_direct": "Туры режим",
|
||||||
|
"toggle_system_proxy": "Системалы проксины кабызу/сүндерү",
|
||||||
|
"toggle_tun_mode": "Tun режимын кабызу/сүндерү",
|
||||||
|
"Backup Setting": "Резерв копия көйләүләре",
|
||||||
|
"Backup Setting Info": "WebDAV аша конфигурация файлын саклауны хуплый",
|
||||||
|
"Runtime Config": "Агымдагы конфигурация",
|
||||||
|
"Open Conf Dir": "Кушымта папкасын ачу",
|
||||||
|
"Open Conf Dir Info": "Әгәр программада хаталар чыкса, бу папкадагы файлларны саклап калыгыз да, аннары барысын да бетереп, программаны яңадан башлагыз",
|
||||||
|
"Open Core Dir": "Ядро сакланган папканы ачу",
|
||||||
|
"Open Logs Dir": "Логлар папкасын ачу",
|
||||||
|
"Check for Updates": "Яңартуларны тикшерү",
|
||||||
|
"Go to Release Page": "Релизлар битенә күчү",
|
||||||
|
"Portable Updater Error": "Портатив версиядә кушымта эчендә яңарту хупланмый, кулдан төшереп алыгыз",
|
||||||
|
"Break Change Update Error": "Бу зур яңарту, ул кушымта эчендә яңартылмый. Борып алып ташлап, яңадан урнаштыру сорала.",
|
||||||
|
"Open Dev Tools": "Разработчик коралларын ачу",
|
||||||
|
"Exit": "Чыгу",
|
||||||
|
"Verge Version": "Verge версиясе",
|
||||||
|
"ReadOnly": "Уку режимы гына",
|
||||||
|
"ReadOnlyMessage": "Уку режимында үзгәртү мөмкин түгел",
|
||||||
|
"Filter": "Фильтр",
|
||||||
|
"Filter conditions": "Фильтр шартлары",
|
||||||
|
"Match Case": "Регистрны исәпкә алу",
|
||||||
|
"Match Whole Word": "Сүзнең тулы туры килүе",
|
||||||
|
"Use Regular Expression": "Регуляр выражениеләр куллану",
|
||||||
|
"Profile Imported Successfully": "Профиль уңышлы импортланды",
|
||||||
|
"Profile Switched": "Профиль алмаштырылды",
|
||||||
|
"Profile Reactivated": "Профиль яңадан активлаштырылды",
|
||||||
|
"Only YAML Files Supported": "Фәкать YAML-файллар гына хуплана",
|
||||||
|
"Settings Applied": "Көйләүләр кулланылды",
|
||||||
|
"Service Installed Successfully": "Сервис уңышлы урнаштырылды",
|
||||||
|
"Service Uninstalled Successfully": "Сервис уңышлы салдырылды",
|
||||||
|
"Proxy Daemon Duration Cannot be Less than 1 Second": "Прокси-демон эш вакыты 1 секундтан ким була алмый",
|
||||||
|
"Invalid Bypass Format": "Дөрес булмаган Bypass форматы",
|
||||||
|
"Clash Port Modified": "Clash порты үзгәртелде",
|
||||||
|
"Port Conflict": "Порт конфликтлары",
|
||||||
|
"Restart Application to Apply Modifications": "Үзгәрешләрне куллану өчен кушымтаны яңадан ачарга кирәк",
|
||||||
|
"External Controller Address Modified": "Тышкы контроллер адресы үзгәртелде",
|
||||||
|
"Permissions Granted Successfully for _clash Core": "{{core}} ядросы өчен рөхсәтләр бирелде",
|
||||||
|
"Core Version Updated": "Ядро версиясе яңартылды",
|
||||||
|
"Clash Core Restarted": "Clash ядросы яңадан башланды",
|
||||||
|
"Switched to _clash Core": "{{core}} ядросына күчү башкарылды",
|
||||||
|
"GeoData Updated": "GeoData яңартылды",
|
||||||
|
"Currently on the Latest Version": "Сездә иң соңгы версия урнаштырылган",
|
||||||
|
"Import subscription successful": "Подписка уңышлы импортланды",
|
||||||
|
"WebDAV Server URL": "WebDAV сервер URL-ы (http(s)://)",
|
||||||
|
"Username": "Кулланучы исеме",
|
||||||
|
"Password": "Пароль",
|
||||||
|
"Backup": "Резерв копия",
|
||||||
|
"Filename": "Файл исеме",
|
||||||
|
"Actions": "Гамәлләр",
|
||||||
|
"Restore": "Кайтару",
|
||||||
|
"No Backups": "Резерв копияләр юк",
|
||||||
|
"WebDAV URL Required": "WebDAV адресы буш булырга тиеш түгел",
|
||||||
|
"Invalid WebDAV URL": "WebDAV адресы дөрес түгел",
|
||||||
|
"Username Required": "Кулланучы исеме буш булмаска тиеш",
|
||||||
|
"Password Required": "Пароль буш булмаска тиеш",
|
||||||
|
"Failed to Fetch Backups": "Резерв копия файлларын алуда хата",
|
||||||
|
"WebDAV Config Saved": "WebDAV көйләүләре сакланды",
|
||||||
|
"WebDAV Config Save Failed": "WebDAV көйләүләрен саклап булмады: {{error}}",
|
||||||
|
"Backup Created": "Резерв копия уңышлы ясалды",
|
||||||
|
"Backup Failed": "Резерв копия хата белән төгәлләнде: {{error}}",
|
||||||
|
"Delete Backup": "Резерв копияне бетерү",
|
||||||
|
"Restore Backup": "Резерв копияне кайтару",
|
||||||
|
"Backup Time": "Резерв копия вакыты",
|
||||||
|
"Confirm to delete this backup file?": "Бу резерв копия файлын бетерергә телисезме?",
|
||||||
|
"Confirm to restore this backup file?": "Бу резерв копия файлын кире кайтарырга телисезме?",
|
||||||
|
"Restore Success, App will restart in 1s": "Уңышлы кайтарылды, кушымта 1 секундтан яңадан башланачак",
|
||||||
|
"Failed to fetch backup files": "Резерв копия файлларын алуда хата",
|
||||||
|
"Profile": "Профиль",
|
||||||
|
"Help": "Ярдәм",
|
||||||
|
"About": "Турында",
|
||||||
|
"Theme": "Тема",
|
||||||
|
"Main Window": "Төп тәрәзә",
|
||||||
|
"Group Icon": "Төркем иконкасы",
|
||||||
|
"Menu Icon": "Меню иконкасы",
|
||||||
|
"PAC File": "PAC файлы",
|
||||||
|
"Web UI": "Веб-интерфейс",
|
||||||
|
"Hotkeys": "Кызу төймәләр",
|
||||||
|
"Verge Mixed Port": "Verge катнаш порты",
|
||||||
|
"Verge Socks Port": "Verge Socks порты",
|
||||||
|
"Verge Redir Port": "Verge кире юнәлтү порты",
|
||||||
|
"Verge Tproxy Port": "Verge Tproxy порты",
|
||||||
|
"Verge Port": "Verge порты",
|
||||||
|
"Verge HTTP Enabled": "Verge HTTP кушылган",
|
||||||
|
"WebDAV URL": "WebDAV URL",
|
||||||
|
"WebDAV Username": "WebDAV кулланучы исеме",
|
||||||
|
"WebDAV Password": "WebDAV серсүзе",
|
||||||
|
"Dashboard": "Панель",
|
||||||
|
"Restart App": "Кушымтаны яңадан ачу",
|
||||||
|
"Restart Clash Core": "Clash ядрони яңадан башлап ачу",
|
||||||
|
"TUN Mode": "TUN режимы",
|
||||||
|
"Copy Env": "Env күчереп алу",
|
||||||
|
"Conf Dir": "Кушымта папкасы",
|
||||||
|
"Core Dir": "Ядро папкасы",
|
||||||
|
"Logs Dir": "Логлар папкасы",
|
||||||
|
"Open Dir": "Папканы ачу",
|
||||||
|
"More": "Башҡа",
|
||||||
|
"Rule Mode": "Кагыйдә режимы",
|
||||||
|
"Global Mode": "Глобаль режим",
|
||||||
|
"Direct Mode": "Туры режим",
|
||||||
|
"Enable Tray Speed": "Трей скоростьне үстерү"
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"millis": "毫秒",
|
"millis": "毫秒",
|
||||||
"mins": "分钟",
|
|
||||||
"seconds": "秒",
|
"seconds": "秒",
|
||||||
|
"mins": "分钟",
|
||||||
"Back": "返回",
|
"Back": "返回",
|
||||||
"Close": "关闭",
|
"Close": "关闭",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
@@ -55,12 +55,12 @@
|
|||||||
"Create Profile": "新建配置",
|
"Create Profile": "新建配置",
|
||||||
"Edit Profile": "编辑配置",
|
"Edit Profile": "编辑配置",
|
||||||
"Edit Proxies": "编辑节点",
|
"Edit Proxies": "编辑节点",
|
||||||
"Use newlines for multiple uri": "多条URI请使用换行分隔(支持Base64编码)",
|
"Use newlines for multiple uri": "多条 URI 请使用换行分隔(支持 Base64 编码)",
|
||||||
"Edit Rules": "编辑规则",
|
"Edit Rules": "编辑规则",
|
||||||
"Rule Type": "规则类型",
|
"Rule Type": "规则类型",
|
||||||
"Rule Content": "规则内容",
|
"Rule Content": "规则内容",
|
||||||
"Proxy Policy": "代理策略",
|
"Proxy Policy": "代理策略",
|
||||||
"No Resolve": "跳过DNS解析",
|
"No Resolve": "跳过 DNS 解析",
|
||||||
"Prepend Rule": "添加前置规则",
|
"Prepend Rule": "添加前置规则",
|
||||||
"Append Rule": "添加后置规则",
|
"Append Rule": "添加后置规则",
|
||||||
"Prepend Group": "添加前置代理组",
|
"Prepend Group": "添加前置代理组",
|
||||||
@@ -75,26 +75,26 @@
|
|||||||
"DOMAIN-SUFFIX": "匹配域名后缀",
|
"DOMAIN-SUFFIX": "匹配域名后缀",
|
||||||
"DOMAIN-KEYWORD": "匹配域名关键字",
|
"DOMAIN-KEYWORD": "匹配域名关键字",
|
||||||
"DOMAIN-REGEX": "匹配域名正则表达式",
|
"DOMAIN-REGEX": "匹配域名正则表达式",
|
||||||
"GEOSITE": "匹配Geosite内的域名",
|
"GEOSITE": "匹配 Geosite 内的域名",
|
||||||
"GEOIP": "匹配IP所属国家代码",
|
"GEOIP": "匹配 IP 所属国家代码",
|
||||||
"SRC-GEOIP": "匹配来源IP所属国家代码",
|
"SRC-GEOIP": "匹配来源 IP 所属国家代码",
|
||||||
"IP-ASN": "匹配IP所属ASN",
|
"IP-ASN": "匹配 IP 所属 ASN",
|
||||||
"SRC-IP-ASN": "匹配来源IP所属ASN",
|
"SRC-IP-ASN": "匹配来源 IP 所属 ASN",
|
||||||
"IP-CIDR": "匹配IP地址范围",
|
"IP-CIDR": "匹配 IP 地址范围",
|
||||||
"IP-CIDR6": "匹配IP地址范围",
|
"IP-CIDR6": "匹配 IP 地址范围",
|
||||||
"SRC-IP-CIDR": "匹配来源IP地址范围",
|
"SRC-IP-CIDR": "匹配来源 IP 地址范围",
|
||||||
"IP-SUFFIX": "匹配IP后缀范围",
|
"IP-SUFFIX": "匹配 IP 后缀范围",
|
||||||
"SRC-IP-SUFFIX": "匹配来源IP后缀范围",
|
"SRC-IP-SUFFIX": "匹配来源 IP 后缀范围",
|
||||||
"SRC-PORT": "匹配请求来源端口范围",
|
"SRC-PORT": "匹配请求来源端口范围",
|
||||||
"DST-PORT": "匹配请求目标端口范围",
|
"DST-PORT": "匹配请求目标端口范围",
|
||||||
"IN-PORT": "匹配入站端口",
|
"IN-PORT": "匹配入站端口",
|
||||||
"DSCP": "DSCP标记(仅限tproxy udp入站)",
|
"DSCP": "DSCP标记(仅限 TPROXY UDP 入站)",
|
||||||
"PROCESS-NAME": "匹配进程名称(Android包名)",
|
"PROCESS-NAME": "匹配进程名称(Android 包名)",
|
||||||
"PROCESS-PATH": "匹配完整进程路径",
|
"PROCESS-PATH": "匹配完整进程路径",
|
||||||
"PROCESS-NAME-REGEX": "正则匹配完整进程名称(Android包名)",
|
"PROCESS-NAME-REGEX": "正则匹配完整进程名称(Android 包名)",
|
||||||
"PROCESS-PATH-REGEX": "正则匹配完整进程路径",
|
"PROCESS-PATH-REGEX": "正则匹配完整进程路径",
|
||||||
"NETWORK": "匹配传输协议(tcp/udp)",
|
"NETWORK": "匹配传输协议 (TCP/UDP)",
|
||||||
"UID": "匹配Linux USER ID",
|
"UID": "匹配 Linux USER ID",
|
||||||
"IN-TYPE": "匹配入站类型",
|
"IN-TYPE": "匹配入站类型",
|
||||||
"IN-USER": "匹配入站用户名",
|
"IN-USER": "匹配入站用户名",
|
||||||
"IN-NAME": "匹配入站名称",
|
"IN-NAME": "匹配入站名称",
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
"Include All Proxies": "引入所有出站代理",
|
"Include All Proxies": "引入所有出站代理",
|
||||||
"Exclude Filter": "排除节点",
|
"Exclude Filter": "排除节点",
|
||||||
"Exclude Type": "排除节点类型",
|
"Exclude Type": "排除节点类型",
|
||||||
"Disable UDP": "禁用UDP",
|
"Disable UDP": "禁用 UDP",
|
||||||
"Hidden": "隐藏代理组",
|
"Hidden": "隐藏代理组",
|
||||||
"Group Name Required": "代理组名称不能为空",
|
"Group Name Required": "代理组名称不能为空",
|
||||||
"Group Name Already Exists": "代理组名称已存在",
|
"Group Name Already Exists": "代理组名称已存在",
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
"Choose File": "选择文件",
|
"Choose File": "选择文件",
|
||||||
"Use System Proxy": "使用系统代理更新",
|
"Use System Proxy": "使用系统代理更新",
|
||||||
"Use Clash Proxy": "使用内核代理更新",
|
"Use Clash Proxy": "使用内核代理更新",
|
||||||
"Accept Invalid Certs (Danger)": "允许无效证书 (危险)",
|
"Accept Invalid Certs (Danger)": "允许无效证书(危险)",
|
||||||
"Refresh": "刷新",
|
"Refresh": "刷新",
|
||||||
"Home": "首页",
|
"Home": "首页",
|
||||||
"Select": "使用",
|
"Select": "使用",
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
"Edit File": "编辑文件",
|
"Edit File": "编辑文件",
|
||||||
"Open File": "打开文件",
|
"Open File": "打开文件",
|
||||||
"Update": "更新",
|
"Update": "更新",
|
||||||
"Update(Proxy)": "更新(代理)",
|
"Update(Proxy)": "更新(代理)",
|
||||||
"Confirm deletion": "确认删除",
|
"Confirm deletion": "确认删除",
|
||||||
"This operation is not reversible": "此操作不可逆",
|
"This operation is not reversible": "此操作不可逆",
|
||||||
"Script Console": "脚本控制台输出",
|
"Script Console": "脚本控制台输出",
|
||||||
@@ -193,19 +193,19 @@
|
|||||||
"Test URL": "测试地址",
|
"Test URL": "测试地址",
|
||||||
"Settings": "设置",
|
"Settings": "设置",
|
||||||
"System Setting": "系统设置",
|
"System Setting": "系统设置",
|
||||||
"Tun Mode": "Tun(虚拟网卡)模式",
|
"Tun Mode": "TUN(虚拟网卡)模式",
|
||||||
"Reset to Default": "重置为默认值",
|
"Reset to Default": "重置为默认值",
|
||||||
"Tun Mode Info": "Tun(虚拟网卡)模式接管系统所有流量,启用时无须打开系统代理",
|
"Tun Mode Info": "TUN(虚拟网卡)模式接管系统所有流量,启用时无须打开系统代理",
|
||||||
"Stack": "Tun 模式堆栈",
|
"Stack": "TUN 模式堆栈",
|
||||||
"System and Mixed Can Only be Used in Service Mode": "System 和 Mixed 只能在服务模式下使用",
|
"System and Mixed Can Only be Used in Service Mode": "System 和 Mixed 只能在服务模式下使用",
|
||||||
"Device": "Tun 网卡名称",
|
"Device": "TUN 网卡名称",
|
||||||
"Auto Route": "自动设置全局路由",
|
"Auto Route": "自动设置全局路由",
|
||||||
"Strict Route": "严格路由",
|
"Strict Route": "严格路由",
|
||||||
"Auto Detect Interface": "自动选择流量出口接口",
|
"Auto Detect Interface": "自动选择流量出口接口",
|
||||||
"DNS Hijack": "DNS 劫持",
|
"DNS Hijack": "DNS 劫持",
|
||||||
"MTU": "最大传输单元",
|
"MTU": "最大传输单元",
|
||||||
"Service Mode": "服务模式",
|
"Service Mode": "服务模式",
|
||||||
"Service Mode Info": "启用TUN模式前请安装服务模式,该服务启动的内核进程可获得安装虚拟网卡(TUN模式)的权限",
|
"Service Mode Info": "启用 TUN 模式前请安装服务模式,该服务启动的内核进程可获得安装虚拟网卡(TUN 模式)的权限",
|
||||||
"Current State": "当前状态",
|
"Current State": "当前状态",
|
||||||
"pending": "等待中",
|
"pending": "等待中",
|
||||||
"installed": "已安装",
|
"installed": "已安装",
|
||||||
@@ -229,11 +229,12 @@
|
|||||||
"Proxy Guard Info": "开启以防止其他软件修改操作系统的代理设置",
|
"Proxy Guard Info": "开启以防止其他软件修改操作系统的代理设置",
|
||||||
"Guard Duration": "代理守卫间隔",
|
"Guard Duration": "代理守卫间隔",
|
||||||
"Always use Default Bypass": "始终使用默认绕过",
|
"Always use Default Bypass": "始终使用默认绕过",
|
||||||
|
"Use Bypass Check": "启用代理绕过检查",
|
||||||
"Proxy Bypass": "代理绕过设置:",
|
"Proxy Bypass": "代理绕过设置:",
|
||||||
"Bypass": "当前绕过:",
|
"Bypass": "当前绕过:",
|
||||||
"Use PAC Mode": "使用PAC模式",
|
"Use PAC Mode": "使用 PAC 模式",
|
||||||
"PAC Script Content": "PAC脚本内容",
|
"PAC Script Content": "PAC 脚本内容",
|
||||||
"PAC URL": "PAC地址:",
|
"PAC URL": "PAC 地址:",
|
||||||
"Auto Launch": "开机自启",
|
"Auto Launch": "开机自启",
|
||||||
"Silent Start": "静默启动",
|
"Silent Start": "静默启动",
|
||||||
"Silent Start Info": "程序启动时以后台模式运行,不显示程序面板",
|
"Silent Start Info": "程序启动时以后台模式运行,不显示程序面板",
|
||||||
@@ -249,14 +250,14 @@
|
|||||||
"Unified Delay": "统一延迟",
|
"Unified Delay": "统一延迟",
|
||||||
"Unified Delay Info": "开启统一延迟时,会进行两次延迟测试,以消除连接握手等带来的不同类型节点的延迟差异",
|
"Unified Delay Info": "开启统一延迟时,会进行两次延迟测试,以消除连接握手等带来的不同类型节点的延迟差异",
|
||||||
"Log Level": "日志等级",
|
"Log Level": "日志等级",
|
||||||
"Log Level Info": "仅对日志目录Service文件夹下的内核日志文件生效",
|
"Log Level Info": "仅对日志目录 Service 文件夹下的内核日志文件生效",
|
||||||
"Port Config": "端口设置",
|
"Port Config": "端口设置",
|
||||||
"Random Port": "随机端口",
|
"Random Port": "随机端口",
|
||||||
"Mixed Port": "混合代理端口",
|
"Mixed Port": "混合代理端口",
|
||||||
"Socks Port": "Socks代理端口",
|
"Socks Port": "SOCKS 代理端口",
|
||||||
"Http Port": "Http(s)代理端口",
|
"Http Port": "HTTP(S) 代理端口",
|
||||||
"Redir Port": "Redir透明代理端口",
|
"Redir Port": "Redir 透明代理端口",
|
||||||
"Tproxy Port": "Tproxy透明代理端口",
|
"TPROXY Port": "TPROXY 透明代理端口",
|
||||||
"External": "外部控制",
|
"External": "外部控制",
|
||||||
"External Controller": "外部控制器监听地址",
|
"External Controller": "外部控制器监听地址",
|
||||||
"Core Secret": "API 访问密钥",
|
"Core Secret": "API 访问密钥",
|
||||||
@@ -273,7 +274,7 @@
|
|||||||
"Please enter your root password": "请输入您的 root 密码",
|
"Please enter your root password": "请输入您的 root 密码",
|
||||||
"Grant": "授权",
|
"Grant": "授权",
|
||||||
"Open UWP tool": "UWP 工具",
|
"Open UWP tool": "UWP 工具",
|
||||||
"Open UWP tool Info": "Windows 8开始限制 UWP 应用(如微软商店)直接访问本地主机的网络服务,使用此工具可绕过该限制",
|
"Open UWP tool Info": "Windows 8 开始限制 UWP 应用(如微软商店)直接访问本地主机的网络服务,使用此工具可绕过该限制",
|
||||||
"Update GeoData": "更新 GeoData",
|
"Update GeoData": "更新 GeoData",
|
||||||
"Verge Setting": "Verge 设置",
|
"Verge Setting": "Verge 设置",
|
||||||
"Language": "语言设置",
|
"Language": "语言设置",
|
||||||
@@ -310,9 +311,9 @@
|
|||||||
"Tray Icon": "托盘图标",
|
"Tray Icon": "托盘图标",
|
||||||
"Common Tray Icon": "常规托盘图标",
|
"Common Tray Icon": "常规托盘图标",
|
||||||
"System Proxy Tray Icon": "系统代理托盘图标",
|
"System Proxy Tray Icon": "系统代理托盘图标",
|
||||||
"Tun Tray Icon": "Tun 模式托盘图标",
|
"Tun Tray Icon": "TUN 模式托盘图标",
|
||||||
"Miscellaneous": "杂项设置",
|
"Miscellaneous": "杂项设置",
|
||||||
"App Log Level": "App日志等级",
|
"App Log Level": "应用日志等级",
|
||||||
"Auto Close Connections": "自动关闭连接",
|
"Auto Close Connections": "自动关闭连接",
|
||||||
"Auto Close Connections Info": "当代理组选中节点或代理模式变动时,关闭已建立的连接",
|
"Auto Close Connections Info": "当代理组选中节点或代理模式变动时,关闭已建立的连接",
|
||||||
"Auto Check Update": "自动检查更新",
|
"Auto Check Update": "自动检查更新",
|
||||||
@@ -322,7 +323,7 @@
|
|||||||
"Auto Columns": "自动列数",
|
"Auto Columns": "自动列数",
|
||||||
"Auto Log Clean": "自动清理日志",
|
"Auto Log Clean": "自动清理日志",
|
||||||
"Never Clean": "不清理",
|
"Never Clean": "不清理",
|
||||||
"Retain _n Days": "保留{{n}}天",
|
"Retain _n Days": "保留 {{n}} 天",
|
||||||
"Default Latency Test": "默认测试链接",
|
"Default Latency Test": "默认测试链接",
|
||||||
"Default Latency Test Info": "仅用于 HTTP 客户端请求测试,不会对配置文件产生影响",
|
"Default Latency Test Info": "仅用于 HTTP 客户端请求测试,不会对配置文件产生影响",
|
||||||
"Default Latency Timeout": "测试超时时间",
|
"Default Latency Timeout": "测试超时时间",
|
||||||
@@ -332,11 +333,12 @@
|
|||||||
"clash_mode_global": "全局模式",
|
"clash_mode_global": "全局模式",
|
||||||
"clash_mode_direct": "直连模式",
|
"clash_mode_direct": "直连模式",
|
||||||
"toggle_system_proxy": "打开/关闭系统代理",
|
"toggle_system_proxy": "打开/关闭系统代理",
|
||||||
"toggle_tun_mode": "打开/关闭 Tun 模式",
|
"toggle_tun_mode": "打开/关闭 TUN 模式",
|
||||||
"Backup Setting": "备份设置",
|
"Backup Setting": "备份设置",
|
||||||
"Backup Setting Info": "支持WebDAV备份配置文件",
|
"Backup Setting Info": "支持 WebDAV 备份配置文件",
|
||||||
"Runtime Config": "当前配置",
|
"Runtime Config": "当前配置",
|
||||||
"Open Conf Dir": "配置目录",
|
"Open Conf Dir": "配置目录",
|
||||||
|
"Open Conf Dir Info": "如果软件运行异常,!备份!并删除此文件夹下的所有文件,重启软件",
|
||||||
"Open Core Dir": "内核目录",
|
"Open Core Dir": "内核目录",
|
||||||
"Open Logs Dir": "日志目录",
|
"Open Logs Dir": "日志目录",
|
||||||
"Check for Updates": "检查更新",
|
"Check for Updates": "检查更新",
|
||||||
@@ -354,18 +356,17 @@
|
|||||||
"Match Whole Word": "全字匹配",
|
"Match Whole Word": "全字匹配",
|
||||||
"Use Regular Expression": "使用正则表达式",
|
"Use Regular Expression": "使用正则表达式",
|
||||||
"Profile Imported Successfully": "导入订阅成功",
|
"Profile Imported Successfully": "导入订阅成功",
|
||||||
"Clash Config Updated": "Clash 配置已更新",
|
|
||||||
"Profile Switched": "订阅已切换",
|
"Profile Switched": "订阅已切换",
|
||||||
"Profile Reactivated": "订阅已激活",
|
"Profile Reactivated": "订阅已激活",
|
||||||
"Only YAML Files Supported": "仅支持 YAML 文件",
|
"Only YAML Files Supported": "仅支持 YAML 文件",
|
||||||
"Settings Applied": "设置已应用",
|
"Settings Applied": "设置已应用",
|
||||||
"Service Installed Successfully": "已成功安装服务",
|
"Service Installed Successfully": "已成功安装服务",
|
||||||
"Service Uninstalled Successfully": "已成功卸载服务",
|
"Service Uninstalled Successfully": "已成功卸载服务",
|
||||||
"Proxy Daemon Duration Cannot be Less than 1 Second": "代理守护间隔时间不得低于1秒",
|
"Proxy Daemon Duration Cannot be Less than 1 Second": "代理守护间隔时间不得低于 1 秒",
|
||||||
"Invalid Bypass Format": "无效的代理绕过格式",
|
"Invalid Bypass Format": "无效的代理绕过格式",
|
||||||
"Clash Port Modified": "Clash 端口已修改",
|
"Clash Port Modified": "Clash 端口已修改",
|
||||||
"Port Conflict": "端口冲突",
|
"Port Conflict": "端口冲突",
|
||||||
"Restart Application to Apply Modifications": "重启Verge以应用修改",
|
"Restart Application to Apply Modifications": "重启 Verge 以应用修改",
|
||||||
"External Controller Address Modified": "外部控制器监听地址已修改",
|
"External Controller Address Modified": "外部控制器监听地址已修改",
|
||||||
"Permissions Granted Successfully for _clash Core": "{{core}} 内核授权成功",
|
"Permissions Granted Successfully for _clash Core": "{{core}} 内核授权成功",
|
||||||
"Core Version Updated": "内核版本已更新",
|
"Core Version Updated": "内核版本已更新",
|
||||||
@@ -374,7 +375,7 @@
|
|||||||
"GeoData Updated": "已更新 GeoData",
|
"GeoData Updated": "已更新 GeoData",
|
||||||
"Currently on the Latest Version": "当前已是最新版本",
|
"Currently on the Latest Version": "当前已是最新版本",
|
||||||
"Import Subscription Successful": "导入订阅成功",
|
"Import Subscription Successful": "导入订阅成功",
|
||||||
"WebDAV Server URL": "WebDAV服务器地址 http(s)://",
|
"WebDAV Server URL": "WebDAV 服务器地址 http(s)://",
|
||||||
"Username": "用户名",
|
"Username": "用户名",
|
||||||
"Password": "密码",
|
"Password": "密码",
|
||||||
"Backup": "备份",
|
"Backup": "备份",
|
||||||
@@ -395,7 +396,40 @@
|
|||||||
"Restore Backup": "恢复备份",
|
"Restore Backup": "恢复备份",
|
||||||
"Backup Time": "备份时间",
|
"Backup Time": "备份时间",
|
||||||
"Confirm to delete this backup file?": "确认删除此备份文件吗?",
|
"Confirm to delete this backup file?": "确认删除此备份文件吗?",
|
||||||
"Confirm to restore this backup file?": "确认恢复此 份文件吗?",
|
"Confirm to restore this backup file?": "确认恢复此份文件吗?",
|
||||||
"Restore Success, App will restart in 1s": "恢复成功,应用将在1秒后重启",
|
"Restore Success, App will restart in 1s": "恢复成功,应用将在 1 秒后重启",
|
||||||
"Failed to fetch backup files": "获取备份文件失败"
|
"Failed to fetch backup files": "获取备份文件失败",
|
||||||
|
"Profile": "配置",
|
||||||
|
"Help": "帮助",
|
||||||
|
"About": "关于",
|
||||||
|
"Theme": "主题",
|
||||||
|
"Main Window": "主窗口",
|
||||||
|
"Group Icon": "分组图标",
|
||||||
|
"Menu Icon": "菜单图标",
|
||||||
|
"PAC File": "PAC 文件",
|
||||||
|
"Web UI": "网页界面",
|
||||||
|
"Hotkeys": "快捷键",
|
||||||
|
"Verge Mixed Port": "Verge 混合端口",
|
||||||
|
"Verge Socks Port": "Verge SOCKS 端口",
|
||||||
|
"Verge Redir Port": "Verge 重定向端口",
|
||||||
|
"Verge Tproxy Port": "Verge 透明代理端口",
|
||||||
|
"Verge Port": "Verge 端口",
|
||||||
|
"Verge HTTP Enabled": "Verge HTTP 已启用",
|
||||||
|
"WebDAV URL": "WebDAV 地址",
|
||||||
|
"WebDAV Username": "WebDAV 用户名",
|
||||||
|
"WebDAV Password": "WebDAV 密码",
|
||||||
|
"Dashboard": "仪表板",
|
||||||
|
"Restart App": "重启应用",
|
||||||
|
"Restart Clash Core": "重启 Clash 核心",
|
||||||
|
"TUN Mode": "TUN 模式",
|
||||||
|
"Copy Env": "复制环境变量",
|
||||||
|
"Conf Dir": "配置目录",
|
||||||
|
"Core Dir": "核心目录",
|
||||||
|
"Logs Dir": "日志目录",
|
||||||
|
"Open Dir": "打开目录",
|
||||||
|
"More": "更多",
|
||||||
|
"Rule Mode": "规则模式",
|
||||||
|
"Global Mode": "全局模式",
|
||||||
|
"Direct Mode": "直连模式",
|
||||||
|
"Enable Tray Speed": "启用托盘速率"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,9 +77,6 @@ const Layout = () => {
|
|||||||
navigate("/profile");
|
navigate("/profile");
|
||||||
Notice.error(msg);
|
Notice.error(msg);
|
||||||
break;
|
break;
|
||||||
case "set_config::ok":
|
|
||||||
Notice.success(t("Clash Config Updated"));
|
|
||||||
break;
|
|
||||||
case "set_config::error":
|
case "set_config::error":
|
||||||
Notice.error(msg);
|
Notice.error(msg);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { BaseStyledSelect } from "@/components/base/base-styled-select";
|
|||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
import { createSockette } from "@/utils/websocket";
|
import { createSockette } from "@/utils/websocket";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
|
import { useVisibility } from "@/hooks/use-visibility";
|
||||||
|
|
||||||
const initConn: IConnections = {
|
const initConn: IConnections = {
|
||||||
uploadTotal: 0,
|
uploadTotal: 0,
|
||||||
@@ -32,7 +33,7 @@ type OrderFunc = (list: IConnectionsItem[]) => IConnectionsItem[];
|
|||||||
const ConnectionsPage = () => {
|
const ConnectionsPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { clashInfo } = useClashInfo();
|
const { clashInfo } = useClashInfo();
|
||||||
|
const pageVisible = useVisibility();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isDark = theme.palette.mode === "dark";
|
const isDark = theme.palette.mode === "dark";
|
||||||
const [match, setMatch] = useState(() => (_: string) => true);
|
const [match, setMatch] = useState(() => (_: string) => true);
|
||||||
@@ -58,58 +59,60 @@ const ConnectionsPage = () => {
|
|||||||
IConnections,
|
IConnections,
|
||||||
any,
|
any,
|
||||||
"getClashConnections" | null
|
"getClashConnections" | null
|
||||||
>(clashInfo ? "getClashConnections" : null, (_key, { next }) => {
|
>(
|
||||||
const { server = "", secret = "" } = clashInfo!;
|
clashInfo && pageVisible ? "getClashConnections" : null,
|
||||||
|
(_key, { next }) => {
|
||||||
|
const { server = "", secret = "" } = clashInfo!;
|
||||||
|
const s = createSockette(
|
||||||
|
`ws://${server}/connections?token=${encodeURIComponent(secret)}`,
|
||||||
|
{
|
||||||
|
onmessage(event) {
|
||||||
|
// meta v1.15.0 出现 data.connections 为 null 的情况
|
||||||
|
const data = JSON.parse(event.data) as IConnections;
|
||||||
|
// 尽量与前一次 connections 的展示顺序保持一致
|
||||||
|
next(null, (old = initConn) => {
|
||||||
|
const oldConn = old.connections;
|
||||||
|
const maxLen = data.connections?.length;
|
||||||
|
|
||||||
const s = createSockette(
|
const connections: IConnectionsItem[] = [];
|
||||||
`ws://${server}/connections?token=${encodeURIComponent(secret)}`,
|
|
||||||
{
|
|
||||||
onmessage(event) {
|
|
||||||
// meta v1.15.0 出现 data.connections 为 null 的情况
|
|
||||||
const data = JSON.parse(event.data) as IConnections;
|
|
||||||
// 尽量与前一次 connections 的展示顺序保持一致
|
|
||||||
next(null, (old = initConn) => {
|
|
||||||
const oldConn = old.connections;
|
|
||||||
const maxLen = data.connections?.length;
|
|
||||||
|
|
||||||
const connections: IConnectionsItem[] = [];
|
const rest = (data.connections || []).filter((each) => {
|
||||||
|
const index = oldConn.findIndex((o) => o.id === each.id);
|
||||||
|
|
||||||
const rest = (data.connections || []).filter((each) => {
|
if (index >= 0 && index < maxLen) {
|
||||||
const index = oldConn.findIndex((o) => o.id === each.id);
|
const old = oldConn[index];
|
||||||
|
each.curUpload = each.upload - old.upload;
|
||||||
|
each.curDownload = each.download - old.download;
|
||||||
|
|
||||||
if (index >= 0 && index < maxLen) {
|
connections[index] = each;
|
||||||
const old = oldConn[index];
|
return false;
|
||||||
each.curUpload = each.upload - old.upload;
|
}
|
||||||
each.curDownload = each.download - old.download;
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
connections[index] = each;
|
for (let i = 0; i < maxLen; ++i) {
|
||||||
return false;
|
if (!connections[i] && rest.length > 0) {
|
||||||
|
connections[i] = rest.shift()!;
|
||||||
|
connections[i].curUpload = 0;
|
||||||
|
connections[i].curDownload = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return { ...data, connections };
|
||||||
});
|
});
|
||||||
|
},
|
||||||
for (let i = 0; i < maxLen; ++i) {
|
onerror(event) {
|
||||||
if (!connections[i] && rest.length > 0) {
|
next(event);
|
||||||
connections[i] = rest.shift()!;
|
},
|
||||||
connections[i].curUpload = 0;
|
|
||||||
connections[i].curDownload = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...data, connections };
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onerror(event) {
|
3,
|
||||||
next(event);
|
);
|
||||||
},
|
|
||||||
},
|
|
||||||
3,
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
s.close();
|
s.close();
|
||||||
};
|
};
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [filterConn, download, upload] = useMemo(() => {
|
const [filterConn, download, upload] = useMemo(() => {
|
||||||
const orderFunc = orderOpts[curOrderOpt];
|
const orderFunc = orderOpts[curOrderOpt];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import useSWR, { mutate } from "swr";
|
import useSWR from "swr";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { Box, Button, Grid, IconButton, Stack, Divider } from "@mui/material";
|
import { Box, Button, IconButton, Stack, Divider, Grid2 } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
closestCenter,
|
closestCenter,
|
||||||
@@ -25,9 +25,9 @@ import {
|
|||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
getProfiles,
|
|
||||||
importProfile,
|
importProfile,
|
||||||
enhanceProfiles,
|
enhanceProfiles,
|
||||||
|
restartCore,
|
||||||
getRuntimeLogs,
|
getRuntimeLogs,
|
||||||
deleteProfile,
|
deleteProfile,
|
||||||
updateProfile,
|
updateProfile,
|
||||||
@@ -45,12 +45,14 @@ import { ProfileMore } from "@/components/profile/profile-more";
|
|||||||
import { ProfileItem } from "@/components/profile/profile-item";
|
import { ProfileItem } from "@/components/profile/profile-item";
|
||||||
import { useProfiles } from "@/hooks/use-profiles";
|
import { useProfiles } from "@/hooks/use-profiles";
|
||||||
import { ConfigViewer } from "@/components/setting/mods/config-viewer";
|
import { ConfigViewer } from "@/components/setting/mods/config-viewer";
|
||||||
import { throttle } from "lodash-es";
|
import { add, throttle } from "lodash-es";
|
||||||
import { BaseStyledTextField } from "@/components/base/base-styled-text-field";
|
import { BaseStyledTextField } from "@/components/base/base-styled-text-field";
|
||||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||||
import { readText } from "@tauri-apps/plugin-clipboard-manager";
|
import { readText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { useListen } from "@/hooks/use-listen";
|
import { useListen } from "@/hooks/use-listen";
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { TauriEvent } from "@tauri-apps/api/event";
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -69,30 +71,41 @@ const ProfilePage = () => {
|
|||||||
const { current } = location.state || {};
|
const { current } = location.state || {};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = addListener("tauri://file-drop", async (event) => {
|
const handleFileDrop = async () => {
|
||||||
const fileList = event.payload as string[];
|
const unlisten = await addListener(
|
||||||
for (let file of fileList) {
|
TauriEvent.DRAG_DROP,
|
||||||
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
async (event: any) => {
|
||||||
Notice.error(t("Only YAML Files Supported"));
|
const paths = event.payload.paths;
|
||||||
continue;
|
|
||||||
}
|
for (let file of paths) {
|
||||||
const item = {
|
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
||||||
type: "local",
|
Notice.error(t("Only YAML Files Supported"));
|
||||||
name: file.split(/\/|\\/).pop() ?? "New Profile",
|
continue;
|
||||||
desc: "",
|
}
|
||||||
url: "",
|
const item = {
|
||||||
option: {
|
type: "local",
|
||||||
with_proxy: false,
|
name: file.split(/\/|\\/).pop() ?? "New Profile",
|
||||||
self_proxy: false,
|
desc: "",
|
||||||
},
|
url: "",
|
||||||
} as IProfileItem;
|
option: {
|
||||||
let data = await readTextFile(file);
|
with_proxy: false,
|
||||||
await createProfile(item, data);
|
self_proxy: false,
|
||||||
await mutateProfiles();
|
},
|
||||||
}
|
} as IProfileItem;
|
||||||
});
|
let data = await readTextFile(file);
|
||||||
|
await createProfile(item, data);
|
||||||
|
await mutateProfiles();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return unlisten;
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsubscribe = handleFileDrop();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unlisten.then((fn) => fn());
|
unsubscribe.then((cleanup) => cleanup());
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -117,9 +130,7 @@ const ProfilePage = () => {
|
|||||||
|
|
||||||
const type1 = ["local", "remote"];
|
const type1 = ["local", "remote"];
|
||||||
|
|
||||||
const profileItems = items.filter((i) => i && type1.includes(i.type!));
|
return items.filter((i) => i && type1.includes(i.type!));
|
||||||
|
|
||||||
return profileItems;
|
|
||||||
}, [profiles]);
|
}, [profiles]);
|
||||||
|
|
||||||
const currentActivatings = () => {
|
const currentActivatings = () => {
|
||||||
@@ -372,14 +383,14 @@ const ProfilePage = () => {
|
|||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
>
|
>
|
||||||
<Box sx={{ mb: 1.5 }}>
|
<Box sx={{ mb: 1.5 }}>
|
||||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
<Grid2 container spacing={{ xs: 1, lg: 1 }}>
|
||||||
<SortableContext
|
<SortableContext
|
||||||
items={profileItems.map((x) => {
|
items={profileItems.map((x) => {
|
||||||
return x.uid;
|
return x.uid;
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{profileItems.map((item) => (
|
{profileItems.map((item) => (
|
||||||
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
<Grid2 size={{ xs: 12, sm: 6, md: 4, lg: 3 }} key={item.file}>
|
||||||
<ProfileItem
|
<ProfileItem
|
||||||
id={item.uid}
|
id={item.uid}
|
||||||
selected={profiles.current === item.uid}
|
selected={profiles.current === item.uid}
|
||||||
@@ -390,14 +401,16 @@ const ProfilePage = () => {
|
|||||||
onSave={async (prev, curr) => {
|
onSave={async (prev, curr) => {
|
||||||
if (prev !== curr && profiles.current === item.uid) {
|
if (prev !== curr && profiles.current === item.uid) {
|
||||||
await onEnhance(false);
|
await onEnhance(false);
|
||||||
|
await restartCore();
|
||||||
|
Notice.success(t("Clash Core Restarted"), 1000);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onDelete={() => onDelete(item.uid)}
|
onDelete={() => onDelete(item.uid)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid2>
|
||||||
))}
|
))}
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
</Grid>
|
</Grid2>
|
||||||
</Box>
|
</Box>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
<Divider
|
<Divider
|
||||||
@@ -406,8 +419,8 @@ const ProfilePage = () => {
|
|||||||
sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
|
sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
|
||||||
></Divider>
|
></Divider>
|
||||||
<Box sx={{ mt: 1.5 }}>
|
<Box sx={{ mt: 1.5 }}>
|
||||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
<Grid2 container spacing={{ xs: 1, lg: 1 }}>
|
||||||
<Grid item xs={12} sm={6} md={6} lg={6}>
|
<Grid2 size={{ xs: 12, sm: 6, md: 6, lg: 6 }}>
|
||||||
<ProfileMore
|
<ProfileMore
|
||||||
id="Merge"
|
id="Merge"
|
||||||
onSave={async (prev, curr) => {
|
onSave={async (prev, curr) => {
|
||||||
@@ -416,8 +429,8 @@ const ProfilePage = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid2>
|
||||||
<Grid item xs={12} sm={6} md={6} lg={6}>
|
<Grid2 size={{ xs: 12, sm: 6, md: 6, lg: 6 }}>
|
||||||
<ProfileMore
|
<ProfileMore
|
||||||
id="Script"
|
id="Script"
|
||||||
logInfo={chainLogs["Script"]}
|
logInfo={chainLogs["Script"]}
|
||||||
@@ -427,8 +440,8 @@ const ProfilePage = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid2>
|
||||||
</Grid>
|
</Grid2>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { Box, Button } from "@mui/material";
|
import { Box, Button } from "@mui/material";
|
||||||
import Grid2 from "@mui/material/Grid2";
|
import Grid2 from "@mui/material/Grid2";
|
||||||
@@ -22,6 +22,7 @@ import { TestViewer, TestViewerRef } from "@/components/test/test-viewer";
|
|||||||
import { TestItem } from "@/components/test/test-item";
|
import { TestItem } from "@/components/test/test-item";
|
||||||
import { emit } from "@tauri-apps/api/event";
|
import { emit } from "@tauri-apps/api/event";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
import { ScrollTopButton } from "@/components/layout/scroll-top-button";
|
||||||
|
|
||||||
// test icons
|
// test icons
|
||||||
import apple from "@/assets/image/test/apple.svg?raw";
|
import apple from "@/assets/image/test/apple.svg?raw";
|
||||||
@@ -121,6 +122,19 @@ const TestPage = () => {
|
|||||||
}, [verge]);
|
}, [verge]);
|
||||||
|
|
||||||
const viewerRef = useRef<TestViewerRef>(null);
|
const viewerRef = useRef<TestViewerRef>(null);
|
||||||
|
const [showScrollTop, setShowScrollTop] = useState(false);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const scrollToTop = () => {
|
||||||
|
containerRef.current?.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScroll = (e: any) => {
|
||||||
|
setShowScrollTop(e.target.scrollTop > 100);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage
|
<BasePage
|
||||||
@@ -146,10 +160,15 @@ const TestPage = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
ref={containerRef}
|
||||||
|
onScroll={handleScroll}
|
||||||
sx={{
|
sx={{
|
||||||
pt: 1.25,
|
pt: 1.25,
|
||||||
mb: 0.5,
|
mb: 0.5,
|
||||||
px: "10px",
|
px: "10px",
|
||||||
|
height: "calc(100vh - 100px)",
|
||||||
|
overflow: "auto",
|
||||||
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DndContext
|
<DndContext
|
||||||
@@ -182,6 +201,17 @@ const TestPage = () => {
|
|||||||
</Grid2>
|
</Grid2>
|
||||||
</Box>
|
</Box>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
|
|
||||||
|
<ScrollTopButton
|
||||||
|
onClick={scrollToTop}
|
||||||
|
show={showScrollTop}
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "20px",
|
||||||
|
left: "20px",
|
||||||
|
zIndex: 1000,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<TestViewer ref={viewerRef} onChange={onTestListItemChange} />
|
<TestViewer ref={viewerRef} onChange={onTestListItemChange} />
|
||||||
</BasePage>
|
</BasePage>
|
||||||
|
|||||||
@@ -4,18 +4,23 @@ import en from "@/locales/en.json";
|
|||||||
import ru from "@/locales/ru.json";
|
import ru from "@/locales/ru.json";
|
||||||
import zh from "@/locales/zh.json";
|
import zh from "@/locales/zh.json";
|
||||||
import fa from "@/locales/fa.json";
|
import fa from "@/locales/fa.json";
|
||||||
|
import tt from "@/locales/tt.json";
|
||||||
|
import id from "@/locales/id.json";
|
||||||
|
import ar from "@/locales/ar.json";
|
||||||
|
|
||||||
const resources = {
|
export const languages = { en, ru, zh, fa, tt, id, ar };
|
||||||
en: { translation: en },
|
|
||||||
ru: { translation: ru },
|
const resources = Object.fromEntries(
|
||||||
zh: { translation: zh },
|
Object.entries(languages).map(([key, value]) => [
|
||||||
fa: { translation: fa },
|
key,
|
||||||
};
|
{ translation: value },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(initReactI18next).init({
|
||||||
resources,
|
resources,
|
||||||
lng: "en",
|
lng: "zh",
|
||||||
fallbackLng: "en",
|
fallbackLng: "zh",
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false,
|
escapeValue: false,
|
||||||
},
|
},
|
||||||
|
|||||||
2
src/services/types.d.ts
vendored
2
src/services/types.d.ts
vendored
@@ -706,6 +706,7 @@ interface IVergeConfig {
|
|||||||
common_tray_icon?: boolean;
|
common_tray_icon?: boolean;
|
||||||
sysproxy_tray_icon?: boolean;
|
sysproxy_tray_icon?: boolean;
|
||||||
tun_tray_icon?: boolean;
|
tun_tray_icon?: boolean;
|
||||||
|
enable_tray_speed?: boolean;
|
||||||
enable_tun_mode?: boolean;
|
enable_tun_mode?: boolean;
|
||||||
enable_auto_launch?: boolean;
|
enable_auto_launch?: boolean;
|
||||||
enable_silent_start?: boolean;
|
enable_silent_start?: boolean;
|
||||||
@@ -723,6 +724,7 @@ interface IVergeConfig {
|
|||||||
verge_socks_enabled?: boolean;
|
verge_socks_enabled?: boolean;
|
||||||
verge_http_enabled?: boolean;
|
verge_http_enabled?: boolean;
|
||||||
enable_proxy_guard?: boolean;
|
enable_proxy_guard?: boolean;
|
||||||
|
enable_bypass_check?: boolean;
|
||||||
use_default_bypass?: boolean;
|
use_default_bypass?: boolean;
|
||||||
proxy_guard_duration?: number;
|
proxy_guard_duration?: number;
|
||||||
system_proxy_bypass?: string;
|
system_proxy_bypass?: string;
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ const KEY_MAP: Record<string, string> = {
|
|||||||
",": "Comma",
|
",": "Comma",
|
||||||
".": "Period",
|
".": "Period",
|
||||||
"/": "Slash",
|
"/": "Slash",
|
||||||
|
// 数字键映射
|
||||||
|
"1": "Digit1",
|
||||||
|
"2": "Digit2",
|
||||||
|
"3": "Digit3",
|
||||||
|
"4": "Digit4",
|
||||||
|
"5": "Digit5",
|
||||||
|
"6": "Digit6",
|
||||||
|
"7": "Digit7",
|
||||||
|
"8": "Digit8",
|
||||||
|
"9": "Digit9",
|
||||||
|
"0": "Digit0",
|
||||||
// Option + 特殊字符映射
|
// Option + 特殊字符映射
|
||||||
"–": "Minus", // Option + -
|
"–": "Minus", // Option + -
|
||||||
"≠": "Equal", // Option + =
|
"≠": "Equal", // Option + =
|
||||||
@@ -57,7 +68,52 @@ const mapKeyCombination = (key: string): string => {
|
|||||||
};
|
};
|
||||||
export const parseHotkey = (key: string) => {
|
export const parseHotkey = (key: string) => {
|
||||||
let temp = key.toUpperCase();
|
let temp = key.toUpperCase();
|
||||||
console.log(temp);
|
|
||||||
|
// 处理特殊符号到键位的映射
|
||||||
|
switch (temp) {
|
||||||
|
// 数字键符号
|
||||||
|
case "!":
|
||||||
|
return "DIGIT1"; // shift + 1
|
||||||
|
case "@":
|
||||||
|
return "DIGIT2"; // shift + 2
|
||||||
|
case "#":
|
||||||
|
return "DIGIT3"; // shift + 3
|
||||||
|
case "$":
|
||||||
|
return "DIGIT4"; // shift + 4
|
||||||
|
case "%":
|
||||||
|
return "DIGIT5"; // shift + 5
|
||||||
|
case "^":
|
||||||
|
return "DIGIT6"; // shift + 6
|
||||||
|
case "&":
|
||||||
|
return "DIGIT7"; // shift + 7
|
||||||
|
case "*":
|
||||||
|
return "DIGIT8"; // shift + 8
|
||||||
|
case "(":
|
||||||
|
return "DIGIT9"; // shift + 9
|
||||||
|
case ")":
|
||||||
|
return "DIGIT0"; // shift + 0
|
||||||
|
// 其他特殊符号
|
||||||
|
case "?":
|
||||||
|
return "SLASH"; // shift + /
|
||||||
|
case ":":
|
||||||
|
return "SEMICOLON"; // shift + ;
|
||||||
|
case "+":
|
||||||
|
return "EQUAL"; // shift + =
|
||||||
|
case "_":
|
||||||
|
return "MINUS"; // shift + -
|
||||||
|
case '"':
|
||||||
|
return "QUOTE"; // shift + '
|
||||||
|
case "<":
|
||||||
|
return "COMMA"; // shift + ,
|
||||||
|
case ">":
|
||||||
|
return "PERIOD"; // shift + .
|
||||||
|
case "{":
|
||||||
|
return "BRACKETLEFT"; // shift + [
|
||||||
|
case "}":
|
||||||
|
return "BRACKETRIGHT"; // shift + ]
|
||||||
|
case "|":
|
||||||
|
return "BACKSLASH"; // shift + \
|
||||||
|
}
|
||||||
|
|
||||||
if (temp.startsWith("ARROW")) {
|
if (temp.startsWith("ARROW")) {
|
||||||
temp = temp.slice(5);
|
temp = temp.slice(5);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default function parseUri(uri: string): IProxyConfig {
|
|||||||
|
|
||||||
function getIfNotBlank(
|
function getIfNotBlank(
|
||||||
value: string | undefined,
|
value: string | undefined,
|
||||||
dft?: string
|
dft?: string,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
return value && value.trim() !== "" ? value : dft;
|
return value && value.trim() !== "" ? value : dft;
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,7 @@ function URI_SS(line: string): IProxyShadowsocksConfig {
|
|||||||
if (v2rayPlugin) {
|
if (v2rayPlugin) {
|
||||||
proxy.plugin = "v2ray-plugin";
|
proxy.plugin = "v2ray-plugin";
|
||||||
proxy["plugin-opts"] = JSON.parse(
|
proxy["plugin-opts"] = JSON.parse(
|
||||||
decodeBase64OrOriginal(v2rayPlugin)
|
decodeBase64OrOriginal(v2rayPlugin),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +193,7 @@ function URI_SS(line: string): IProxyShadowsocksConfig {
|
|||||||
const portIdx = serverAndPort?.lastIndexOf(":") ?? 0;
|
const portIdx = serverAndPort?.lastIndexOf(":") ?? 0;
|
||||||
proxy.server = serverAndPort?.substring(0, portIdx) ?? "";
|
proxy.server = serverAndPort?.substring(0, portIdx) ?? "";
|
||||||
proxy.port = parseInt(
|
proxy.port = parseInt(
|
||||||
`${serverAndPort?.substring(portIdx + 1)}`.match(/\d+/)?.[0] ?? ""
|
`${serverAndPort?.substring(portIdx + 1)}`.match(/\d+/)?.[0] ?? "",
|
||||||
);
|
);
|
||||||
const userInfo = userInfoStr.match(/(^.*?):(.*$)/);
|
const userInfo = userInfoStr.match(/(^.*?):(.*$)/);
|
||||||
proxy.cipher = getCipher(userInfo?.[1]);
|
proxy.cipher = getCipher(userInfo?.[1]);
|
||||||
@@ -252,7 +252,7 @@ function URI_SSR(line: string): IProxyshadowsocksRConfig {
|
|||||||
const serverAndPort = line.substring(0, splitIdx);
|
const serverAndPort = line.substring(0, splitIdx);
|
||||||
const server = serverAndPort.substring(0, serverAndPort.lastIndexOf(":"));
|
const server = serverAndPort.substring(0, serverAndPort.lastIndexOf(":"));
|
||||||
const port = parseInt(
|
const port = parseInt(
|
||||||
serverAndPort.substring(serverAndPort.lastIndexOf(":") + 1)
|
serverAndPort.substring(serverAndPort.lastIndexOf(":") + 1),
|
||||||
);
|
);
|
||||||
|
|
||||||
let params = line
|
let params = line
|
||||||
@@ -284,12 +284,12 @@ function URI_SSR(line: string): IProxyshadowsocksRConfig {
|
|||||||
...proxy,
|
...proxy,
|
||||||
name: other_params.remarks
|
name: other_params.remarks
|
||||||
? decodeBase64OrOriginal(other_params.remarks).trim()
|
? decodeBase64OrOriginal(other_params.remarks).trim()
|
||||||
: proxy.server ?? "",
|
: (proxy.server ?? ""),
|
||||||
"protocol-param": getIfNotBlank(
|
"protocol-param": getIfNotBlank(
|
||||||
decodeBase64OrOriginal(other_params.protoparam || "").replace(/\s/g, "")
|
decodeBase64OrOriginal(other_params.protoparam || "").replace(/\s/g, ""),
|
||||||
),
|
),
|
||||||
"obfs-param": getIfNotBlank(
|
"obfs-param": getIfNotBlank(
|
||||||
decodeBase64OrOriginal(other_params.obfsparam || "").replace(/\s/g, "")
|
decodeBase64OrOriginal(other_params.obfsparam || "").replace(/\s/g, ""),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
return proxy;
|
return proxy;
|
||||||
@@ -330,7 +330,7 @@ function URI_VMESS(line: string): IProxyVmessConfig {
|
|||||||
proxy["ws-opts"] = {
|
proxy["ws-opts"] = {
|
||||||
path:
|
path:
|
||||||
(getIfNotBlank(params["obfs-path"]) || '"/"').match(
|
(getIfNotBlank(params["obfs-path"]) || '"/"').match(
|
||||||
/^"(.*)"$/
|
/^"(.*)"$/,
|
||||||
)?.[1] || "/",
|
)?.[1] || "/",
|
||||||
headers: {
|
headers: {
|
||||||
Host:
|
Host:
|
||||||
@@ -492,6 +492,9 @@ function URI_VMESS(line: string): IProxyVmessConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VLess URL Decode.
|
||||||
|
*/
|
||||||
function URI_VLESS(line: string): IProxyVlessConfig {
|
function URI_VLESS(line: string): IProxyVlessConfig {
|
||||||
line = line.split("vless://")[1];
|
line = line.split("vless://")[1];
|
||||||
let isShadowrocket;
|
let isShadowrocket;
|
||||||
@@ -571,9 +574,11 @@ function URI_VLESS(line: string): IProxyVlessConfig {
|
|||||||
|
|
||||||
if (params.headerType === "http") {
|
if (params.headerType === "http") {
|
||||||
proxy.network = "http";
|
proxy.network = "http";
|
||||||
} else {
|
} else if (params.type === "ws") {
|
||||||
proxy.network = "ws";
|
proxy.network = "ws";
|
||||||
httpupgrade = true;
|
httpupgrade = true;
|
||||||
|
} else {
|
||||||
|
proxy.network = "tcp";
|
||||||
}
|
}
|
||||||
if (!proxy.network && isShadowrocket && params.obfs) {
|
if (!proxy.network && isShadowrocket && params.obfs) {
|
||||||
switch (params.type) {
|
switch (params.type) {
|
||||||
@@ -619,7 +624,7 @@ function URI_VLESS(line: string): IProxyVlessConfig {
|
|||||||
opts["v2ray-http-upgrade-fast-open"] = true;
|
opts["v2ray-http-upgrade-fast-open"] = true;
|
||||||
}
|
}
|
||||||
if (Object.keys(opts).length > 0) {
|
if (Object.keys(opts).length > 0) {
|
||||||
proxy[`${proxy.network}-opts`] = opts;
|
proxy[`ws-opts`] = opts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,7 +636,6 @@ function URI_VLESS(line: string): IProxyVlessConfig {
|
|||||||
proxy.servername = Array.isArray(httpHost) ? httpHost[0] : httpHost;
|
proxy.servername = Array.isArray(httpHost) ? httpHost[0] : httpHost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user