perf(tray): improve menu handling localization support

refactor(tray): replace string literals with MenuIds for menu event handling
This commit is contained in:
Tunglies
2025-10-27 22:07:23 +08:00
Unverified
parent 2ee8d164fd
commit 87168b6ce0
3 changed files with 107 additions and 95 deletions

View File

@@ -0,0 +1,47 @@
use crate::utils::i18n::t;
use smartstring::alias::String;
macro_rules! define_menu {
($($field:ident => $const_name:ident, $id:expr, $text:expr),+ $(,)?) => {
#[derive(Debug)]
pub struct MenuTexts {
$(pub $field: String,)+
}
pub struct MenuIds;
impl MenuTexts {
pub async fn new() -> Self {
let ($($field,)+) = futures::join!($(t($text),)+);
Self { $($field,)+ }
}
}
impl MenuIds {
$(pub const $const_name: &'static str = $id;)+
}
};
}
define_menu! {
dashboard => DASHBOARD, "tray_dashboard", "Dashboard",
rule_mode => RULE_MODE, "tray_rule_mode", "Rule Mode",
global_mode => GLOBAL_MODE, "tray_global_mode", "Global Mode",
direct_mode => DIRECT_MODE, "tray_direct_mode", "Direct Mode",
profiles => PROFILES, "tray_profiles", "Profiles",
proxies => PROXIES, "tray_proxies", "Proxies",
system_proxy => SYSTEM_PROXY, "tray_system_proxy", "System Proxy",
tun_mode => TUN_MODE, "tray_tun_mode", "TUN Mode",
close_all_connections => CLOSE_ALL_CONNECTIONS, "tray_close_all_connections", "Close All Connections",
lightweight_mode => LIGHTWEIGHT_MODE, "tray_lightweight_mode", "LightWeight Mode",
copy_env => COPY_ENV, "tray_copy_env", "Copy Env",
conf_dir => CONF_DIR, "tray_conf_dir", "Conf Dir",
core_dir => CORE_DIR, "tray_core_dir", "Core Dir",
logs_dir => LOGS_DIR, "tray_logs_dir", "Logs Dir",
open_dir => OPEN_DIR, "tray_open_dir", "Open Dir",
restart_clash => RESTART_CLASH, "tray_restart_clash", "Restart Clash Core",
restart_app => RESTART_APP, "tray_restart_app", "Restart App",
verge_version => VERGE_VERSION, "tray_verge_version", "Verge Version",
more => MORE, "tray_more", "More",
exit => EXIT, "tray_exit", "Exit",
}

View File

@@ -33,59 +33,13 @@ use tauri::{
menu::{CheckMenuItem, IsMenuItem, MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
tray::{MouseButton, MouseButtonState, TrayIconEvent},
};
mod menu_def;
use menu_def::{MenuIds, MenuTexts};
// TODO: 是否需要将可变菜单抽离存储起来,后续直接更新对应菜单实例,无需重新创建菜单(待考虑)
type ProxyMenuItem = (Option<Submenu<Wry>>, Vec<Box<dyn IsMenuItem<Wry>>>);
struct MenuTexts {
dashboard: String,
rule_mode: String,
global_mode: String,
direct_mode: String,
profiles: String,
proxies: String,
system_proxy: String,
tun_mode: String,
close_all_connections: String,
lightweight_mode: String,
copy_env: String,
conf_dir: String,
core_dir: String,
logs_dir: String,
open_dir: String,
restart_clash: String,
restart_app: String,
verge_version: String,
more: String,
exit: String,
}
async fn fetch_menu_texts() -> MenuTexts {
MenuTexts {
dashboard: t("Dashboard").await,
rule_mode: t("Rule Mode").await,
global_mode: t("Global Mode").await,
direct_mode: t("Direct Mode").await,
profiles: t("Profiles").await,
proxies: t("Proxies").await,
system_proxy: t("System Proxy").await,
tun_mode: t("TUN Mode").await,
close_all_connections: t("Close All Connections").await,
lightweight_mode: t("LightWeight Mode").await,
copy_env: t("Copy Env").await,
conf_dir: t("Conf Dir").await,
core_dir: t("Core Dir").await,
logs_dir: t("Logs Dir").await,
open_dir: t("Open Dir").await,
restart_clash: t("Restart Clash Core").await,
restart_app: t("Restart App").await,
verge_version: t("Verge Version").await,
more: t("More").await,
exit: t("Exit").await,
}
}
#[derive(Clone)]
struct TrayState {}
@@ -864,7 +818,7 @@ fn create_proxy_menu_item(
(
Some(Submenu::with_id_and_items(
app_handle,
"proxies",
MenuIds::PROXIES,
proxies_text,
true,
&proxy_submenu_refs,
@@ -947,8 +901,7 @@ async fn create_tray_menu(
create_profile_menu_item(app_handle, profile_uid_and_name).await?;
// Pre-fetch all localized strings
let texts = &fetch_menu_texts().await;
let texts = &MenuTexts::new().await;
// Convert to references only when needed
let profile_menu_items_refs: Vec<&dyn IsMenuItem<Wry>> = profile_menu_items
.iter()
@@ -957,7 +910,7 @@ async fn create_tray_menu(
let open_window = &MenuItem::with_id(
app_handle,
"open_window",
MenuIds::DASHBOARD,
&texts.dashboard,
true,
hotkeys.get("open_or_close_dashboard").map(|s| s.as_str()),
@@ -965,7 +918,7 @@ async fn create_tray_menu(
let rule_mode = &CheckMenuItem::with_id(
app_handle,
"rule_mode",
MenuIds::RULE_MODE,
&texts.rule_mode,
true,
current_proxy_mode == "rule",
@@ -974,7 +927,7 @@ async fn create_tray_menu(
let global_mode = &CheckMenuItem::with_id(
app_handle,
"global_mode",
MenuIds::GLOBAL_MODE,
&texts.global_mode,
true,
current_proxy_mode == "global",
@@ -983,7 +936,7 @@ async fn create_tray_menu(
let direct_mode = &CheckMenuItem::with_id(
app_handle,
"direct_mode",
MenuIds::DIRECT_MODE,
&texts.direct_mode,
true,
current_proxy_mode == "direct",
@@ -992,7 +945,7 @@ async fn create_tray_menu(
let profiles = &Submenu::with_id_and_items(
app_handle,
"profiles",
MenuIds::PROFILES,
&texts.profiles,
true,
&profile_menu_items_refs,
@@ -1015,7 +968,7 @@ async fn create_tray_menu(
let system_proxy = &CheckMenuItem::with_id(
app_handle,
"system_proxy",
MenuIds::SYSTEM_PROXY,
&texts.system_proxy,
true,
system_proxy_enabled,
@@ -1024,7 +977,7 @@ async fn create_tray_menu(
let tun_mode = &CheckMenuItem::with_id(
app_handle,
"tun_mode",
MenuIds::TUN_MODE,
&texts.tun_mode,
true,
tun_mode_enabled,
@@ -1033,26 +986,32 @@ async fn create_tray_menu(
let close_all_connections = &MenuItem::with_id(
app_handle,
"close_all_connections",
MenuIds::CLOSE_ALL_CONNECTIONS,
&texts.close_all_connections,
true,
None::<&str>,
)?;
let lighteweight_mode = &CheckMenuItem::with_id(
let lightweight_mode = &CheckMenuItem::with_id(
app_handle,
"entry_lightweight_mode",
MenuIds::LIGHTWEIGHT_MODE,
&texts.lightweight_mode,
true,
is_lightweight_mode,
hotkeys.get("entry_lightweight_mode").map(|s| s.as_str()),
)?;
let copy_env = &MenuItem::with_id(app_handle, "copy_env", &texts.copy_env, true, None::<&str>)?;
let copy_env = &MenuItem::with_id(
app_handle,
MenuIds::COPY_ENV,
&texts.copy_env,
true,
None::<&str>,
)?;
let open_app_dir = &MenuItem::with_id(
app_handle,
"open_app_dir",
MenuIds::CONF_DIR,
&texts.conf_dir,
true,
None::<&str>,
@@ -1060,7 +1019,7 @@ async fn create_tray_menu(
let open_core_dir = &MenuItem::with_id(
app_handle,
"open_core_dir",
MenuIds::CORE_DIR,
&texts.core_dir,
true,
None::<&str>,
@@ -1068,7 +1027,7 @@ async fn create_tray_menu(
let open_logs_dir = &MenuItem::with_id(
app_handle,
"open_logs_dir",
MenuIds::LOGS_DIR,
&texts.logs_dir,
true,
None::<&str>,
@@ -1076,7 +1035,7 @@ async fn create_tray_menu(
let open_dir = &Submenu::with_id_and_items(
app_handle,
"open_dir",
MenuIds::OPEN_DIR,
&texts.open_dir,
true,
&[open_app_dir, open_core_dir, open_logs_dir],
@@ -1084,7 +1043,7 @@ async fn create_tray_menu(
let restart_clash = &MenuItem::with_id(
app_handle,
"restart_clash",
MenuIds::RESTART_CLASH,
&texts.restart_clash,
true,
None::<&str>,
@@ -1092,7 +1051,7 @@ async fn create_tray_menu(
let restart_app = &MenuItem::with_id(
app_handle,
"restart_app",
MenuIds::RESTART_APP,
&texts.restart_app,
true,
None::<&str>,
@@ -1100,7 +1059,7 @@ async fn create_tray_menu(
let app_version = &MenuItem::with_id(
app_handle,
"app_version",
MenuIds::VERGE_VERSION,
format!("{} {version}", &texts.verge_version),
true,
None::<&str>,
@@ -1108,7 +1067,7 @@ async fn create_tray_menu(
let more = &Submenu::with_id_and_items(
app_handle,
"more",
MenuIds::MORE,
&texts.more,
true,
&[
@@ -1121,7 +1080,7 @@ async fn create_tray_menu(
let quit = &MenuItem::with_id(
app_handle,
"quit",
MenuIds::EXIT,
&texts.exit,
true,
Some("CmdOrControl+Q"),
@@ -1154,7 +1113,7 @@ async fn create_tray_menu(
system_proxy as &dyn IsMenuItem<Wry>,
tun_mode as &dyn IsMenuItem<Wry>,
separator,
lighteweight_mode as &dyn IsMenuItem<Wry>,
lightweight_mode as &dyn IsMenuItem<Wry>,
copy_env as &dyn IsMenuItem<Wry>,
open_dir as &dyn IsMenuItem<Wry>,
more as &dyn IsMenuItem<Wry>,
@@ -1171,12 +1130,13 @@ async fn create_tray_menu(
fn on_menu_event(_: &AppHandle, event: MenuEvent) {
AsyncHandler::spawn(|| async move {
match event.id.as_ref() {
mode @ ("rule_mode" | "global_mode" | "direct_mode") => {
let mode = &mode[0..mode.len() - 5]; // Removing the "_mode" suffix
mode @ (MenuIds::RULE_MODE | MenuIds::GLOBAL_MODE | MenuIds::DIRECT_MODE) => {
// Removing the the "tray_" preffix and "_mode" suffix
let mode = &mode[5..mode.len() - 5];
logging!(info, Type::ProxyMode, "Switch Proxy Mode To: {}", mode);
feat::change_clash_mode(mode.into()).await;
}
"open_window" => {
MenuIds::DASHBOARD => {
log::info!(target: "app", "托盘菜单点击: 打开窗口");
if !should_handle_tray_click() {
@@ -1186,40 +1146,41 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
WindowManager::show_main_window().await;
};
}
"system_proxy" => {
MenuIds::SYSTEM_PROXY => {
feat::toggle_system_proxy().await;
}
"tun_mode" => {
MenuIds::TUN_MODE => {
feat::toggle_tun_mode(None).await;
}
"close_all_connections" => {
MenuIds::CLOSE_ALL_CONNECTIONS => {
if let Err(err) = handle::Handle::mihomo().await.close_all_connections().await {
log::error!(target: "app", "Failed to close all connections from tray: {err}");
}
}
"copy_env" => feat::copy_clash_env().await,
"open_app_dir" => {
MenuIds::COPY_ENV => feat::copy_clash_env().await,
MenuIds::CONF_DIR => {
println!("Open directory submenu clicked");
let _ = cmd::open_app_dir().await;
}
"open_core_dir" => {
MenuIds::CORE_DIR => {
let _ = cmd::open_core_dir().await;
}
"open_logs_dir" => {
MenuIds::LOGS_DIR => {
let _ = cmd::open_logs_dir().await;
}
"restart_clash" => feat::restart_clash_core().await,
"restart_app" => feat::restart_app().await,
"entry_lightweight_mode" => {
MenuIds::RESTART_CLASH => feat::restart_clash_core().await,
MenuIds::RESTART_APP => feat::restart_app().await,
MenuIds::LIGHTWEIGHT_MODE => {
if !should_handle_tray_click() {
return;
}
if !is_in_lightweight_mode() {
lightweight::entry_lightweight_mode().await; // Await async function
lightweight::entry_lightweight_mode().await;
} else {
lightweight::exit_lightweight_mode().await; // Await async function
lightweight::exit_lightweight_mode().await;
}
}
"quit" => {
MenuIds::EXIT => {
feat::quit().await;
}
id if id.starts_with("profiles_") => {

View File

@@ -34,6 +34,16 @@ pub fn get_supported_languages() -> Vec<String> {
languages
}
pub async fn current_language() -> String {
Config::verge()
.await
.latest_ref()
.language
.as_deref()
.map(String::from)
.unwrap_or_else(get_system_language)
}
static TRANSLATIONS: Lazy<RwLock<(String, Value)>> = Lazy::new(|| {
let lang = get_system_language();
let json = load_lang_file(&lang).unwrap_or_else(|| Value::Object(Default::default()));
@@ -57,13 +67,7 @@ fn get_system_language() -> String {
}
pub async fn t(key: &str) -> String {
let current_lang = Config::verge()
.await
.latest_ref()
.language
.as_deref()
.map(String::from)
.unwrap_or_else(get_system_language);
let current_lang = current_language().await;
{
if let Ok(cache) = TRANSLATIONS.read()