chore(deps): update rust crate rust-i18n to v4 (#6784)

* chore(deps): update rust crate rust-i18n to v4

* fix: migrate rust-i18n to v4 with Cow-first zero-copy approach

- Adapt to v4 breaking changes: available_locales!() returns Vec<Cow<'static, str>>
- Cache locales in LazyLock<Vec<Cow<'static, str>>> to avoid repeated Vec alloc + sort
- Propagate Cow<'static, str> through resolve/current/system_language APIs
- Fix t! macro args branch: into_owned() + Cow::Owned for type correctness
- Eliminate double resolve in sync_locale (skip redundant set_locale indirection)
- Replace .to_string() with .into_owned() / Cow passthrough in updater.rs

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
This commit is contained in:
renovate[bot] 2026-04-12 19:11:16 +08:00 committed by GitHub
parent 9e32fba13e
commit a4c537541e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 35 additions and 37 deletions

15
Cargo.lock generated
View File

@ -6261,12 +6261,11 @@ dependencies = [
[[package]] [[package]]
name = "rust-i18n" name = "rust-i18n"
version = "3.1.5" version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fda2551fdfaf6cc5ee283adc15e157047b92ae6535cf80f6d4962d05717dc332" checksum = "21031bf5e6f2c0ae745d831791c403608e99a8bd3776c7e5e5535acd70c3b7ba"
dependencies = [ dependencies = [
"globwalk", "globwalk",
"once_cell",
"regex", "regex",
"rust-i18n-macro", "rust-i18n-macro",
"rust-i18n-support", "rust-i18n-support",
@ -6275,12 +6274,11 @@ dependencies = [
[[package]] [[package]]
name = "rust-i18n-macro" name = "rust-i18n-macro"
version = "3.1.5" version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22baf7d7f56656d23ebe24f6bb57a5d40d2bce2a5f1c503e692b5b2fa450f965" checksum = "51fe5295763b358606f7ca26a564e20f4469775a57ec1f09431249a33849ff52"
dependencies = [ dependencies = [
"glob", "glob",
"once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rust-i18n-support", "rust-i18n-support",
@ -6292,9 +6290,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-i18n-support" name = "rust-i18n-support"
version = "3.1.5" version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940ed4f52bba4c0152056d771e563b7133ad9607d4384af016a134b58d758f19" checksum = "69bcc115c8eea2803aa3d85362e339776f4988a0349f2f475af572e497443f6f"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"base62", "base62",
@ -6302,7 +6300,6 @@ dependencies = [
"itertools 0.11.0", "itertools 0.11.0",
"lazy_static", "lazy_static",
"normpath", "normpath",
"once_cell",
"proc-macro2", "proc-macro2",
"regex", "regex",
"serde", "serde",

View File

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
rust-i18n = "3.1.5" rust-i18n = "4.0.0"
sys-locale = "0.3.2" sys-locale = "0.3.2"
[lints] [lints]

View File

@ -1,8 +1,12 @@
use rust_i18n::i18n; use rust_i18n::i18n;
use std::borrow::Cow;
use std::sync::LazyLock;
const DEFAULT_LANGUAGE: &str = "zh"; const DEFAULT_LANGUAGE: &str = "zh";
i18n!("locales", fallback = "zh"); i18n!("locales", fallback = "zh");
static SUPPORTED_LOCALES: LazyLock<Vec<Cow<'static, str>>> = LazyLock::new(|| rust_i18n::available_locales!());
#[inline] #[inline]
fn locale_alias(locale: &str) -> Option<&'static str> { fn locale_alias(locale: &str) -> Option<&'static str> {
match locale { match locale {
@ -14,54 +18,51 @@ fn locale_alias(locale: &str) -> Option<&'static str> {
} }
#[inline] #[inline]
fn resolve_supported_language(language: &str) -> Option<&'static str> { fn resolve_supported_language(language: &str) -> Option<Cow<'static, str>> {
if language.is_empty() { if language.is_empty() {
return None; return None;
} }
let normalized = language.to_lowercase().replace('_', "-"); let normalized = language.to_lowercase().replace('_', "-");
let segments: Vec<&str> = normalized.split('-').collect(); let segments: Vec<&str> = normalized.split('-').collect();
let supported = rust_i18n::available_locales!();
for i in (1..=segments.len()).rev() { for i in (1..=segments.len()).rev() {
let prefix = segments[..i].join("-"); let prefix = segments[..i].join("-");
if let Some(alias) = locale_alias(&prefix) if let Some(alias) = locale_alias(&prefix)
&& let Some(&found) = supported.iter().find(|&&l| l.eq_ignore_ascii_case(alias)) && let Some(found) = SUPPORTED_LOCALES.iter().find(|l| l.eq_ignore_ascii_case(alias))
{ {
return Some(found); return Some(found.clone());
} }
if let Some(&found) = supported.iter().find(|&&l| l.eq_ignore_ascii_case(&prefix)) { if let Some(found) = SUPPORTED_LOCALES.iter().find(|l| l.eq_ignore_ascii_case(&prefix)) {
return Some(found); return Some(found.clone());
} }
} }
None None
} }
#[inline] #[inline]
fn current_language(language: Option<&str>) -> &str { fn current_language(language: Option<&str>) -> Cow<'static, str> {
language language
.as_ref()
.filter(|lang| !lang.is_empty()) .filter(|lang| !lang.is_empty())
.and_then(|lang| resolve_supported_language(lang)) .and_then(resolve_supported_language)
.unwrap_or_else(system_language) .unwrap_or_else(system_language)
} }
#[inline] #[inline]
pub fn system_language() -> &'static str { pub fn system_language() -> Cow<'static, str> {
sys_locale::get_locale() sys_locale::get_locale()
.as_deref() .as_deref()
.and_then(resolve_supported_language) .and_then(resolve_supported_language)
.unwrap_or(DEFAULT_LANGUAGE) .unwrap_or(Cow::Borrowed(DEFAULT_LANGUAGE))
} }
#[inline] #[inline]
pub fn sync_locale(language: Option<&str>) { pub fn sync_locale(language: Option<&str>) {
let language = current_language(language); rust_i18n::set_locale(&current_language(language));
set_locale(language);
} }
#[inline] #[inline]
pub fn set_locale(language: &str) { pub fn set_locale(language: &str) {
let lang = resolve_supported_language(language).unwrap_or(DEFAULT_LANGUAGE); let lang = resolve_supported_language(language).unwrap_or(Cow::Borrowed(DEFAULT_LANGUAGE));
rust_i18n::set_locale(lang); rust_i18n::set_locale(&lang);
} }
#[inline] #[inline]
@ -76,11 +77,11 @@ macro_rules! t {
}; };
($key:expr, $($arg_name:ident = $arg_value:expr),*) => { ($key:expr, $($arg_name:ident = $arg_value:expr),*) => {
{ {
let mut _text = $crate::translate(&$key); let mut _text = $crate::translate(&$key).into_owned();
$( $(
_text = _text.replace(&format!("{{{}}}", stringify!($arg_name)), &$arg_value); _text = _text.replace(&format!("{{{}}}", stringify!($arg_name)), &$arg_value);
)* )*
_text ::std::borrow::Cow::<'static, str>::Owned(_text)
} }
}; };
} }
@ -91,13 +92,13 @@ mod test {
#[test] #[test]
fn test_resolve_supported_language() { fn test_resolve_supported_language() {
assert_eq!(resolve_supported_language("en"), Some("en")); assert_eq!(resolve_supported_language("en").as_deref(), Some("en"));
assert_eq!(resolve_supported_language("en-US"), Some("en")); assert_eq!(resolve_supported_language("en-US").as_deref(), Some("en"));
assert_eq!(resolve_supported_language("zh"), Some("zh")); assert_eq!(resolve_supported_language("zh").as_deref(), Some("zh"));
assert_eq!(resolve_supported_language("zh-CN"), Some("zh")); assert_eq!(resolve_supported_language("zh-CN").as_deref(), Some("zh"));
assert_eq!(resolve_supported_language("zh-Hant"), Some("zhtw")); assert_eq!(resolve_supported_language("zh-Hant").as_deref(), Some("zhtw"));
assert_eq!(resolve_supported_language("jp"), Some("jp")); assert_eq!(resolve_supported_language("jp").as_deref(), Some("jp"));
assert_eq!(resolve_supported_language("ja-JP"), Some("jp")); assert_eq!(resolve_supported_language("ja-JP").as_deref(), Some("jp"));
assert_eq!(resolve_supported_language("fr"), None); assert_eq!(resolve_supported_language("fr"), None);
} }
} }

View File

@ -294,10 +294,10 @@ impl SilentUpdater {
async fn ask_user_to_install(app_handle: &tauri::AppHandle, version: &str) -> bool { async fn ask_user_to_install(app_handle: &tauri::AppHandle, version: &str) -> bool {
use tauri_plugin_dialog::{DialogExt as _, MessageDialogButtons, MessageDialogKind}; use tauri_plugin_dialog::{DialogExt as _, MessageDialogButtons, MessageDialogKind};
let title = clash_verge_i18n::t!("notifications.updateReady.title").to_string(); let title = clash_verge_i18n::t!("notifications.updateReady.title");
let body = clash_verge_i18n::t!("notifications.updateReady.body").replace("{version}", version); let body = clash_verge_i18n::t!("notifications.updateReady.body").replace("{version}", version);
let install_now = clash_verge_i18n::t!("notifications.updateReady.installNow").to_string(); let install_now = clash_verge_i18n::t!("notifications.updateReady.installNow").into_owned();
let later = clash_verge_i18n::t!("notifications.updateReady.later").to_string(); let later = clash_verge_i18n::t!("notifications.updateReady.later").into_owned();
let (tx, rx) = tokio::sync::oneshot::channel(); let (tx, rx) = tokio::sync::oneshot::channel();