mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-02 21:12:13 +08:00
crate(i18n): add clash-verge-i18n crate and integrate localization support (#5961)
* crate(i18n): add clash-verge-i18n crate and integrate localization support * refactor(service): remove redundant reinstall_service functions for Windows, Linux, and macOS * chore(i18n): align i18n key * feat(i18n): unify scan roots and add backend Rust/YAML support to cleanup script * chore(i18n): add scripts to package.json * fix(tray): initialize i18n locale before setup * refactor(i18n): move locale initialization into Config::init_config * fix(i18n): refresh systray tooltip on language change and correct docs reference * fix(tray): remove unnecessary locale synchronization to improve performance --------- Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
This commit is contained in:
@@ -1,96 +0,0 @@
|
||||
use crate::config::Config;
|
||||
use sys_locale;
|
||||
|
||||
const DEFAULT_LANGUAGE: &str = "zh";
|
||||
|
||||
fn supported_languages_internal() -> Vec<&'static str> {
|
||||
rust_i18n::available_locales!()
|
||||
}
|
||||
|
||||
const fn fallback_language() -> &'static str {
|
||||
DEFAULT_LANGUAGE
|
||||
}
|
||||
|
||||
fn locale_alias(locale: &str) -> Option<&'static str> {
|
||||
match locale {
|
||||
"ja" | "ja-jp" | "jp" => Some("jp"),
|
||||
"zh" | "zh-cn" | "zh-hans" | "zh-sg" | "zh-my" | "zh-chs" => Some("zh"),
|
||||
"zh-tw" | "zh-hk" | "zh-hant" | "zh-mo" | "zh-cht" => Some("zhtw"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_supported_language(language: &str) -> Option<String> {
|
||||
if language.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let normalized = language.to_lowercase().replace('_', "-");
|
||||
|
||||
let mut candidates: Vec<String> = Vec::new();
|
||||
let mut push_candidate = |candidate: String| {
|
||||
if !candidate.is_empty()
|
||||
&& !candidates
|
||||
.iter()
|
||||
.any(|existing| existing.eq_ignore_ascii_case(&candidate))
|
||||
{
|
||||
candidates.push(candidate);
|
||||
}
|
||||
};
|
||||
|
||||
let segments: Vec<&str> = normalized.split('-').collect();
|
||||
|
||||
for i in (1..=segments.len()).rev() {
|
||||
let prefix = segments[..i].join("-");
|
||||
if let Some(alias) = locale_alias(&prefix) {
|
||||
push_candidate(alias.to_string());
|
||||
}
|
||||
push_candidate(prefix);
|
||||
}
|
||||
|
||||
let supported = supported_languages_internal();
|
||||
|
||||
candidates
|
||||
.into_iter()
|
||||
.find(|candidate| supported.iter().any(|&lang| lang.eq_ignore_ascii_case(candidate)))
|
||||
}
|
||||
|
||||
fn system_language() -> String {
|
||||
sys_locale::get_locale()
|
||||
.as_deref()
|
||||
.and_then(resolve_supported_language)
|
||||
.unwrap_or_else(|| fallback_language().to_string())
|
||||
}
|
||||
|
||||
pub fn get_supported_languages() -> Vec<String> {
|
||||
supported_languages_internal()
|
||||
.into_iter()
|
||||
.map(|lang| lang.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn set_locale(language: &str) {
|
||||
let lang = resolve_supported_language(language).unwrap_or_else(|| fallback_language().to_string());
|
||||
rust_i18n::set_locale(&lang);
|
||||
}
|
||||
|
||||
pub async fn current_language() -> String {
|
||||
Config::verge()
|
||||
.await
|
||||
.latest_arc()
|
||||
.language
|
||||
.as_ref()
|
||||
.filter(|lang| !lang.is_empty())
|
||||
.and_then(|lang| resolve_supported_language(lang))
|
||||
.unwrap_or_else(system_language)
|
||||
}
|
||||
|
||||
pub async fn sync_locale() -> String {
|
||||
let language = current_language().await;
|
||||
set_locale(&language);
|
||||
language
|
||||
}
|
||||
|
||||
pub const fn default_language() -> &'static str {
|
||||
fallback_language()
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
pub mod dirs;
|
||||
pub mod help;
|
||||
pub mod i18n;
|
||||
pub mod init;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{core::handle, utils::i18n};
|
||||
use crate::core::handle;
|
||||
use clash_verge_i18n;
|
||||
use tauri_plugin_notification::NotificationExt as _;
|
||||
|
||||
pub enum NotificationEvent<'a> {
|
||||
@@ -21,48 +22,49 @@ fn notify(title: &str, body: &str) {
|
||||
}
|
||||
|
||||
pub async fn notify_event<'a>(event: NotificationEvent<'a>) {
|
||||
i18n::sync_locale().await;
|
||||
// It cause to sync set-local everytime notify, so move it outside
|
||||
// i18n::sync_locale().await;
|
||||
|
||||
match event {
|
||||
NotificationEvent::DashboardToggled => {
|
||||
let title = rust_i18n::t!("notifications.dashboardToggled.title").to_string();
|
||||
let body = rust_i18n::t!("notifications.dashboardToggled.body").to_string();
|
||||
let title = clash_verge_i18n::t!("notifications.dashboardToggled.title");
|
||||
let body = clash_verge_i18n::t!("notifications.dashboardToggled.body");
|
||||
notify(&title, &body);
|
||||
}
|
||||
NotificationEvent::ClashModeChanged { mode } => {
|
||||
let title = rust_i18n::t!("notifications.clashModeChanged.title").to_string();
|
||||
let body = rust_i18n::t!("notifications.clashModeChanged.body").replace("{mode}", mode);
|
||||
let title = clash_verge_i18n::t!("notifications.clashModeChanged.title");
|
||||
let body = clash_verge_i18n::t!("notifications.clashModeChanged.body").replace("{mode}", mode);
|
||||
notify(&title, &body);
|
||||
}
|
||||
NotificationEvent::SystemProxyToggled => {
|
||||
let title = rust_i18n::t!("notifications.systemProxyToggled.title").to_string();
|
||||
let body = rust_i18n::t!("notifications.systemProxyToggled.body").to_string();
|
||||
let title = clash_verge_i18n::t!("notifications.systemProxyToggled.title");
|
||||
let body = clash_verge_i18n::t!("notifications.systemProxyToggled.body");
|
||||
notify(&title, &body);
|
||||
}
|
||||
NotificationEvent::TunModeToggled => {
|
||||
let title = rust_i18n::t!("notifications.tunModeToggled.title").to_string();
|
||||
let body = rust_i18n::t!("notifications.tunModeToggled.body").to_string();
|
||||
let title = clash_verge_i18n::t!("notifications.tunModeToggled.title");
|
||||
let body = clash_verge_i18n::t!("notifications.tunModeToggled.body");
|
||||
notify(&title, &body);
|
||||
}
|
||||
NotificationEvent::LightweightModeEntered => {
|
||||
let title = rust_i18n::t!("notifications.lightweightModeEntered.title").to_string();
|
||||
let body = rust_i18n::t!("notifications.lightweightModeEntered.body").to_string();
|
||||
let title = clash_verge_i18n::t!("notifications.lightweightModeEntered.title");
|
||||
let body = clash_verge_i18n::t!("notifications.lightweightModeEntered.body");
|
||||
notify(&title, &body);
|
||||
}
|
||||
NotificationEvent::ProfilesReactivated => {
|
||||
let title = rust_i18n::t!("notifications.profilesReactivated.title").to_string();
|
||||
let body = rust_i18n::t!("notifications.profilesReactivated.body").to_string();
|
||||
let title = clash_verge_i18n::t!("notifications.profilesReactivated.title");
|
||||
let body = clash_verge_i18n::t!("notifications.profilesReactivated.body");
|
||||
notify(&title, &body);
|
||||
}
|
||||
NotificationEvent::AppQuit => {
|
||||
let title = rust_i18n::t!("notifications.appQuit.title").to_string();
|
||||
let body = rust_i18n::t!("notifications.appQuit.body").to_string();
|
||||
let title = clash_verge_i18n::t!("notifications.appQuit.title");
|
||||
let body = clash_verge_i18n::t!("notifications.appQuit.body");
|
||||
notify(&title, &body);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
NotificationEvent::AppHidden => {
|
||||
let title = rust_i18n::t!("notifications.appHidden.title").to_string();
|
||||
let body = rust_i18n::t!("notifications.appHidden.body").to_string();
|
||||
let title = clash_verge_i18n::t!("notifications.appHidden.title");
|
||||
let body = clash_verge_i18n::t!("notifications.appHidden.body");
|
||||
notify(&title, &body);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user