refactor(i18n): optimize translation handling with Arc<str> for better memory efficiency

refactor(tray): change menu text storage to use Arc<str> for improved performance
refactor(service): utilize SmartString for error messages to enhance memory management
This commit is contained in:
Tunglies
2025-10-28 00:26:20 +08:00
parent a9eb512f20
commit f39436f1d0
4 changed files with 36 additions and 23 deletions

View File

@@ -3,6 +3,7 @@ use crate::{
core::service::{self, SERVICE_MANAGER, ServiceStatus}, core::service::{self, SERVICE_MANAGER, ServiceStatus},
utils::i18n::t, utils::i18n::t,
}; };
use smartstring::SmartString;
async fn execute_service_operation_sync(status: ServiceStatus, op_type: &str) -> CmdResult { async fn execute_service_operation_sync(status: ServiceStatus, op_type: &str) -> CmdResult {
if let Err(e) = SERVICE_MANAGER if let Err(e) = SERVICE_MANAGER
@@ -12,7 +13,7 @@ async fn execute_service_operation_sync(status: ServiceStatus, op_type: &str) ->
.await .await
{ {
let emsg = format!("{} Service failed: {}", op_type, e); let emsg = format!("{} Service failed: {}", op_type, e);
return Err(t(emsg.as_str()).await); return Err(SmartString::from(&*t(emsg.as_str()).await));
} }
Ok(()) Ok(())
} }

View File

@@ -1,11 +1,11 @@
use crate::utils::i18n::t; use crate::utils::i18n::t;
use smartstring::alias::String; use std::sync::Arc;
macro_rules! define_menu { macro_rules! define_menu {
($($field:ident => $const_name:ident, $id:expr, $text:expr),+ $(,)?) => { ($($field:ident => $const_name:ident, $id:expr, $text:expr),+ $(,)?) => {
#[derive(Debug)] #[derive(Debug)]
pub struct MenuTexts { pub struct MenuTexts {
$(pub $field: String,)+ $(pub $field: Arc<str>,)+
} }
pub struct MenuIds; pub struct MenuIds;

View File

@@ -23,6 +23,7 @@ use futures::future::join_all;
use parking_lot::Mutex; use parking_lot::Mutex;
use smartstring::alias::String; use smartstring::alias::String;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc;
use std::{ use std::{
fs, fs,
sync::atomic::{AtomicBool, Ordering}, sync::atomic::{AtomicBool, Ordering},
@@ -798,7 +799,7 @@ fn create_proxy_menu_item(
app_handle: &AppHandle, app_handle: &AppHandle,
show_proxy_groups_inline: bool, show_proxy_groups_inline: bool,
proxy_submenus: Vec<Submenu<Wry>>, proxy_submenus: Vec<Submenu<Wry>>,
proxies_text: &String, proxies_text: &Arc<str>,
) -> Result<ProxyMenuItem> { ) -> Result<ProxyMenuItem> {
// 创建代理主菜单 // 创建代理主菜单
let (proxies_submenu, inline_proxy_items) = if show_proxy_groups_inline { let (proxies_submenu, inline_proxy_items) = if show_proxy_groups_inline {

View File

@@ -1,12 +1,18 @@
use crate::{config::Config, utils::dirs}; use crate::{config::Config, utils::dirs};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde_json::Value;
use smartstring::alias::String; use smartstring::alias::String;
use std::{fs, path::PathBuf, sync::RwLock}; use std::{
collections::HashMap,
fs,
path::PathBuf,
sync::{Arc, RwLock},
};
use sys_locale; use sys_locale;
const DEFAULT_LANGUAGE: &str = "zh"; const DEFAULT_LANGUAGE: &str = "zh";
type TranslationMap = (String, HashMap<String, Arc<str>>);
fn get_locales_dir() -> Option<PathBuf> { fn get_locales_dir() -> Option<PathBuf> {
dirs::app_resources_dir() dirs::app_resources_dir()
.map(|resource_path| resource_path.join("locales")) .map(|resource_path| resource_path.join("locales"))
@@ -44,18 +50,23 @@ pub async fn current_language() -> String {
.unwrap_or_else(get_system_language) .unwrap_or_else(get_system_language)
} }
static TRANSLATIONS: Lazy<RwLock<(String, Box<Value>)>> = Lazy::new(|| { static TRANSLATIONS: Lazy<RwLock<TranslationMap>> = Lazy::new(|| {
let lang = get_system_language(); let lang = get_system_language();
let json = load_lang_file(&lang).unwrap_or_else(|| Value::Object(Default::default())); let map = load_lang_file(&lang).unwrap_or_default();
RwLock::new((lang, Box::new(json))) RwLock::new((lang, map))
}); });
fn load_lang_file(lang: &str) -> Option<Value> { fn load_lang_file(lang: &str) -> Option<HashMap<String, Arc<str>>> {
let locales_dir = get_locales_dir()?; let locales_dir = get_locales_dir()?;
let file_path = locales_dir.join(format!("{lang}.json")); let file_path = locales_dir.join(format!("{lang}.json"));
fs::read_to_string(file_path) fs::read_to_string(file_path)
.ok() .ok()
.and_then(|content| serde_json::from_str(&content).ok()) .and_then(|content| serde_json::from_str::<HashMap<String, String>>(&content).ok())
.map(|map| {
map.into_iter()
.map(|(k, v)| (k, Arc::from(v.as_str())))
.collect()
})
} }
fn get_system_language() -> String { fn get_system_language() -> String {
@@ -66,38 +77,38 @@ fn get_system_language() -> String {
.unwrap_or_else(|| DEFAULT_LANGUAGE.into()) .unwrap_or_else(|| DEFAULT_LANGUAGE.into())
} }
pub async fn t(key: &str) -> String { pub async fn t(key: &str) -> Arc<str> {
let current_lang = current_language().await; let current_lang = current_language().await;
{ {
if let Ok(cache) = TRANSLATIONS.read() if let Ok(cache) = TRANSLATIONS.read()
&& cache.0 == current_lang && cache.0 == current_lang
&& let Some(text) = cache.1.get(key).and_then(|val| val.as_str()) && let Some(text) = cache.1.get(key)
{ {
return text.into(); return Arc::clone(text);
} }
} }
if let Some(new_json) = load_lang_file(&current_lang) if let Some(new_map) = load_lang_file(&current_lang)
&& let Ok(mut cache) = TRANSLATIONS.write() && let Ok(mut cache) = TRANSLATIONS.write()
{ {
*cache = (current_lang.clone(), Box::new(new_json)); *cache = (current_lang.clone(), new_map);
if let Some(text) = cache.1.get(key).and_then(|val| val.as_str()) { if let Some(text) = cache.1.get(key) {
return text.into(); return Arc::clone(text);
} }
} }
if current_lang != DEFAULT_LANGUAGE if current_lang != DEFAULT_LANGUAGE
&& let Some(default_json) = load_lang_file(DEFAULT_LANGUAGE) && let Some(default_map) = load_lang_file(DEFAULT_LANGUAGE)
&& let Ok(mut cache) = TRANSLATIONS.write() && let Ok(mut cache) = TRANSLATIONS.write()
{ {
*cache = (DEFAULT_LANGUAGE.into(), Box::new(default_json)); *cache = (DEFAULT_LANGUAGE.into(), default_map);
if let Some(text) = cache.1.get(key).and_then(|val| val.as_str()) { if let Some(text) = cache.1.get(key) {
return text.into(); return Arc::clone(text);
} }
} }
key.into() Arc::from(key)
} }