chore: update i18n

This commit is contained in:
wonfen
2026-07-24 11:53:50 +08:00
parent 82c486923b
commit bce83d5c6f
123 changed files with 2178 additions and 277 deletions
+21 -8
View File
@@ -1,4 +1,4 @@
use super::CmdResult;
use super::{CmdResult, WithErrorCode as _, coded_error};
use crate::feat;
use crate::utils::{dirs, yaml_emitter};
use crate::{
@@ -32,7 +32,9 @@ pub async fn get_clash_info() -> CmdResult<ClashInfo> {
/// 修改Clash配置
#[tauri::command]
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
feat::patch_clash(&payload).await.stringify_err()
feat::patch_clash(&payload)
.await
.with_error_code("CLASH_CONFIG_UPDATE_FAILED")
}
/// 修改Clash模式
@@ -41,7 +43,9 @@ pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
/// 并提示用户(此前命令始终返回 `Ok(())`,吞掉了后端错误)。
#[tauri::command]
pub async fn patch_clash_mode(payload: String) -> CmdResult {
feat::change_clash_mode(payload).await
feat::change_clash_mode(payload)
.await
.with_error_code("CLASH_MODE_UPDATE_FAILED")
}
/// 获取当前 Clash 模式(容错读取)
@@ -74,7 +78,7 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>>
let error_msg: String = format!("Core changed but failed to restart: {err}").into();
handle::Handle::notice_message("config_core::change_error", error_msg.clone());
logging!(error, Type::Core, "{error_msg}");
Ok(Some(error_msg))
Ok(Some(coded_error("CORE_CHANGE_FAILED", error_msg)))
}
}
}
@@ -82,7 +86,7 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>>
let error_msg: String = err;
logging!(error, Type::Core, "failed to change core: {error_msg}");
handle::Handle::notice_message("config_core::change_error", error_msg.clone());
Ok(Some(error_msg))
Ok(Some(coded_error("CORE_CHANGE_FAILED", error_msg)))
}
}
}
@@ -90,7 +94,10 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>>
/// 启动核心
#[tauri::command]
pub async fn start_core() -> CmdResult {
let result = CoreManager::global().start_core().await.stringify_err();
let result = CoreManager::global()
.start_core()
.await
.with_error_code("CORE_START_FAILED");
if result.is_ok() {
handle::Handle::refresh_clash();
}
@@ -101,7 +108,10 @@ pub async fn start_core() -> CmdResult {
#[tauri::command]
pub async fn stop_core() -> CmdResult {
logging_error!(Type::Core, Config::profiles().await.data_arc().save_file().await);
let result = CoreManager::global().stop_core().await.stringify_err();
let result = CoreManager::global()
.stop_core()
.await
.with_error_code("CORE_STOP_FAILED");
if result.is_ok() {
handle::Handle::refresh_clash();
}
@@ -112,7 +122,10 @@ pub async fn stop_core() -> CmdResult {
#[tauri::command]
pub async fn restart_core() -> CmdResult {
logging_error!(Type::Core, Config::profiles().await.data_arc().save_file().await);
let result = CoreManager::global().restart_core().await.stringify_err();
let result = CoreManager::global()
.restart_core()
.await
.with_error_code("CORE_RESTART_FAILED");
if result.is_ok() {
handle::Handle::refresh_clash();
}
+29
View File
@@ -3,6 +3,12 @@ use smartstring::alias::String;
pub type CmdResult<T = ()> = Result<T, String>;
const USER_ERROR_PREFIX: &str = "CVR_ERROR:";
pub fn coded_error(code: &str, detail: impl std::fmt::Display) -> String {
format!("{USER_ERROR_PREFIX}{code}\n{detail}").into()
}
// Command modules
pub mod app;
pub mod backup;
@@ -46,6 +52,10 @@ pub trait StringifyErr<T> {
F: Fn(&str);
}
pub trait WithErrorCode<T> {
fn with_error_code(self, code: &str) -> CmdResult<T>;
}
impl<T, E: std::fmt::Display> StringifyErr<T> for Result<T, E> {
fn stringify_err(self) -> CmdResult<T> {
self.map_err(|e| e.to_string().into())
@@ -62,3 +72,22 @@ impl<T, E: std::fmt::Display> StringifyErr<T> for Result<T, E> {
})
}
}
impl<T, E: std::fmt::Display> WithErrorCode<T> for Result<T, E> {
fn with_error_code(self, code: &str) -> CmdResult<T> {
self.map_err(|error| coded_error(code, error))
}
}
#[cfg(test)]
mod tests {
use super::coded_error;
#[test]
fn coded_error_preserves_stable_code_and_diagnostic_detail() {
assert_eq!(
coded_error("CORE_RESTART_FAILED", "connection refused"),
"CVR_ERROR:CORE_RESTART_FAILED\nconnection refused"
);
}
}
+52 -29
View File
@@ -1,5 +1,4 @@
use super::CmdResult;
use super::StringifyErr as _;
use super::{CmdResult, StringifyErr as _, WithErrorCode as _, coded_error};
use crate::cmd::validate::{ValidationNoticeTarget, handle_validation_notice};
use crate::config::profiles;
use crate::utils::window_manager::WindowManager;
@@ -60,7 +59,7 @@ pub async fn enhance_profiles() -> CmdResult<ValidationOutcome> {
}
Err(e) => {
logging!(error, Type::Cmd, "{}", e);
Err(e.to_string().into())
Err(coded_error("PROFILE_ENHANCE_FAILED", e))
}
}
}
@@ -78,18 +77,18 @@ pub async fn import_profile(url: std::string::String, option: Option<PrfOption>)
}
Err(e) => {
logging!(error, Type::Cmd, "[导入订阅] 下载失败: {}", e);
return Err(profile_import_error(&e).into());
return Err(coded_error("PROFILE_IMPORT_FAILED", profile_import_error(&e)));
}
};
if let Err(e) = profiles_append_item_safe(item).await {
logging!(error, Type::Cmd, "[导入订阅] 保存配置失败: {}", e);
return Err(format!("导入订阅失败: {}", e).into());
return Err(coded_error("PROFILE_IMPORT_FAILED", e));
}
if let Err(e) = profiles_save_file_safe().await {
logging!(error, Type::Cmd, "[导入订阅] 保存配置文件失败: {}", e);
return Err(format!("导入订阅失败: {}", e).into());
return Err(coded_error("PROFILE_IMPORT_FAILED", e));
}
logging!(info, Type::Cmd, "[导入订阅] 配置文件保存成功");
logging_error!(Type::Timer, Timer::global().refresh().await);
@@ -113,7 +112,7 @@ pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
}
Err(err) => {
logging!(error, Type::Cmd, "重新排序配置文件失败: {}", err);
Err(format!("重新排序配置文件失败: {}", err).into())
Err(coded_error("PROFILE_REORDER_FAILED", err))
}
}
}
@@ -124,7 +123,9 @@ pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
match profiles_append_item_with_filedata_safe(&item, file_data).await {
Ok(_) => {
profiles_save_file_safe().await.stringify_err()?;
profiles_save_file_safe()
.await
.with_error_code("PROFILE_CREATE_FAILED")?;
logging_error!(Type::Timer, Timer::global().refresh().await);
// 发送配置变更通知
if let Some(uid) = &item.uid {
@@ -133,10 +134,7 @@ pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResu
}
Ok(())
}
Err(err) => match err.to_string().as_str() {
"the file already exists" => Err("the file already exists".into()),
_ => Err(format!("add profile error: {err}").into()),
},
Err(err) => Err(coded_error("PROFILE_CREATE_FAILED", err)),
}
}
@@ -147,7 +145,7 @@ pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResu
Ok(_) => Ok(()),
Err(e) => {
logging!(error, Type::Cmd, "{}", e);
Err(e.to_string().into())
Err(coded_error("PROFILE_UPDATE_FAILED", e))
}
}
}
@@ -156,8 +154,12 @@ pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResu
#[tauri::command]
pub async fn delete_profile(index: String) -> CmdResult {
// 使用Send-safe helper函数
let should_update = profiles_delete_item_safe(&index).await.stringify_err()?;
profiles_save_file_safe().await.stringify_err()?;
let should_update = profiles_delete_item_safe(&index)
.await
.with_error_code("PROFILE_DELETE_FAILED")?;
profiles_save_file_safe()
.await
.with_error_code("PROFILE_DELETE_FAILED")?;
if let Err(e) = Tray::global().update_tooltip().await {
logging!(warn, Type::Cmd, "Warning: 异步更新托盘提示失败: {e}");
}
@@ -176,15 +178,18 @@ pub async fn delete_profile(index: String) -> CmdResult {
Ok(outcome) => {
logging!(warn, Type::Cmd, "删除订阅后更新配置失败: {}", outcome);
handle_validation_notice(&outcome, ValidationNoticeTarget::Runtime, "运行时配置");
return Err(outcome.to_string().into());
return Err(coded_error("PROFILE_DELETE_FAILED", outcome));
}
Err(e) => {
logging!(error, Type::Cmd, "{}", e);
return Err(e.to_string().into());
return Err(coded_error("PROFILE_DELETE_FAILED", e));
}
}
}
Timer::global().refresh().await.stringify_err()?;
Timer::global()
.refresh()
.await
.with_error_code("PROFILE_DELETE_FAILED")?;
Ok(())
}
@@ -322,7 +327,9 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<ValidationO
Config::profiles().await.edit_draft(|d| d.patch_config(&profiles));
perform_config_update(target_profile, previous_profile.as_ref()).await
perform_config_update(target_profile, previous_profile.as_ref())
.await
.map_err(|error| coded_error("PROFILE_SWITCH_FAILED", error))
}
/// 根据profile name修改profiles
@@ -354,7 +361,9 @@ pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
false
};
profiles_patch_item_safe(&index, &profile).await.stringify_err()?;
profiles_patch_item_safe(&index, &profile)
.await
.with_error_code("PROFILE_UPDATE_FAILED")?;
// 如果更新间隔或允许自动更新变更,异步刷新定时器
if should_refresh_timer {
@@ -379,17 +388,22 @@ pub async fn view_profile(index: String) -> CmdResult {
let profiles_ref = profiles.latest_arc();
let file = profiles_ref
.get_item(&index)
.stringify_err()?
.with_error_code("PROFILE_OPEN_FAILED")?
.file
.as_ref()
.ok_or("the file field is null")?;
.ok_or_else(|| coded_error("PROFILE_OPEN_FAILED", "the file field is null"))?;
let path = dirs::app_profiles_dir().stringify_err()?.join(file.as_str());
let path = dirs::app_profiles_dir()
.with_error_code("PROFILE_OPEN_FAILED")?
.join(file.as_str());
if !path.exists() {
return CmdResult::Err(format!("file not found \"{}\"", path.display()).into());
return CmdResult::Err(coded_error(
"PROFILE_OPEN_FAILED",
format!("file not found \"{}\"", path.display()),
));
}
help::open_file(path).stringify_err()
help::open_file(path).with_error_code("PROFILE_OPEN_FAILED")
}
/// 读取配置文件内容
@@ -399,23 +413,32 @@ pub async fn read_profile_file(index: String) -> CmdResult<String> {
let profiles = Config::profiles().await;
let profiles_ref = profiles.latest_arc();
PrfItem {
file: profiles_ref.get_item(&index).stringify_err()?.file.to_owned(),
file: profiles_ref
.get_item(&index)
.with_error_code("PROFILE_READ_FAILED")?
.file
.to_owned(),
..Default::default()
}
};
if let Some(file) = item.file.as_ref() {
let path = dirs::app_profiles_dir().stringify_err()?.join(file.as_str());
let path = dirs::app_profiles_dir()
.with_error_code("PROFILE_READ_FAILED")?
.join(file.as_str());
match tokio::fs::try_exists(&path).await {
Ok(true) => {}
Ok(false) => return Ok(String::new()),
Err(err) => {
return Err(format!("failed to check profile file \"{}\": {err}", path.display()).into());
return Err(coded_error(
"PROFILE_READ_FAILED",
format!("failed to check profile file \"{}\": {err}", path.display()),
));
}
}
}
let data = item.read_file().await.stringify_err()?;
let data = item.read_file().await.with_error_code("PROFILE_READ_FAILED")?;
Ok(data)
}
+9 -12
View File
@@ -1,30 +1,27 @@
use super::{CmdResult, StringifyErr as _};
use super::{CmdResult, WithErrorCode as _};
use crate::core::{
CoreManager,
service::{self, SERVICE_MANAGER, ServiceStatus},
};
async fn execute_service_operation_sync(status: ServiceStatus, op_type: &str) -> CmdResult {
async fn execute_service_operation_sync(status: ServiceStatus, error_code: &str) -> CmdResult {
let manager = CoreManager::global();
let _lifecycle = manager.lifecycle_lock.lock().await;
if matches!(
&status,
ServiceStatus::ReinstallRequired | ServiceStatus::ForceReinstallRequired
) {
manager
.controlled_stop_core_inner()
.await
.map_err(|error| format!("{op_type} Service failed: {error:#}"))?;
manager.controlled_stop_core_inner().await.with_error_code(error_code)?;
}
SERVICE_MANAGER
.handle_service_status(status)
.await
.map_err(|e| format!("{op_type} Service failed: {e}").into())
.with_error_code(error_code)
}
#[tauri::command]
pub async fn install_service() -> CmdResult {
execute_service_operation_sync(ServiceStatus::InstallRequired, "Install").await
execute_service_operation_sync(ServiceStatus::InstallRequired, "SERVICE_INSTALL_FAILED").await
}
#[tauri::command]
@@ -32,17 +29,17 @@ pub async fn uninstall_service() -> CmdResult {
CoreManager::global()
.uninstall_service_and_start_sidecar()
.await
.map_err(|error| format!("Uninstall Service failed: {error:#}").into())
.with_error_code("SERVICE_UNINSTALL_FAILED")
}
#[tauri::command]
pub async fn reinstall_service() -> CmdResult {
execute_service_operation_sync(ServiceStatus::ReinstallRequired, "Reinstall").await
execute_service_operation_sync(ServiceStatus::ReinstallRequired, "SERVICE_REINSTALL_FAILED").await
}
#[tauri::command]
pub async fn repair_service() -> CmdResult {
execute_service_operation_sync(ServiceStatus::ForceReinstallRequired, "Repair").await
execute_service_operation_sync(ServiceStatus::ForceReinstallRequired, "SERVICE_REPAIR_FAILED").await
}
#[tauri::command]
@@ -60,5 +57,5 @@ pub async fn continue_with_sidecar() -> CmdResult {
crate::core::CoreManager::global()
.continue_with_sidecar()
.await
.stringify_err()
.with_error_code("SERVICE_SIDECAR_FAILED")
}