diff --git a/src-tauri/src/agents/antigravity.rs b/src-tauri/src/agents/antigravity.rs index 3576866..673ad39 100644 --- a/src-tauri/src/agents/antigravity.rs +++ b/src-tauri/src/agents/antigravity.rs @@ -33,7 +33,7 @@ fn object_at<'a>(root: &'a mut Value, keys: &[&str]) -> Result<&'a mut Value, St let mut value = root; for key in keys { if !value.is_object() { - return Err("Antigravity 配置字段必须是对象".into()); + return Err("Antigravity configuration field must be an object".into()); } value = value .as_object_mut() @@ -42,7 +42,7 @@ fn object_at<'a>(root: &'a mut Value, keys: &[&str]) -> Result<&'a mut Value, St .or_insert_with(|| json!({})); } if !value.is_object() { - return Err("Antigravity 配置字段必须是对象".into()); + return Err("Antigravity configuration field must be an object".into()); } Ok(value) } @@ -57,7 +57,7 @@ pub(crate) fn build_antigravity_config( ) -> Result { let mut root = parse(path, existing)?; if !root.is_object() { - return Err("Antigravity 配置根节点必须是对象".into()); + return Err("Antigravity configuration root must be an object".into()); } if path .file_name() @@ -178,7 +178,7 @@ pub(crate) fn antigravity_has_marker( _client: AgentClient, paths: &[PathBuf], ) -> Result { - let path = paths.get(1).ok_or("Antigravity CLI 配置路径缺失")?; + let path = paths.get(1).ok_or("Antigravity CLI configuration path is missing")?; let root = parse(path, read_optional_text(path)?.as_deref())?; Ok(root.get("provider").and_then(Value::as_str) == Some(MANAGED_AGENT_PROVIDER_ID)) } @@ -191,7 +191,7 @@ pub(crate) fn inspect_antigravity_config( ) -> Result<(bool, Option), String> { let settings = parse(&paths[0], read_optional_text(&paths[0])?.as_deref())?; let origin = managed_core_loopback_origin(port); - let path = paths.get(1).ok_or("Antigravity CLI 配置路径缺失")?; + let path = paths.get(1).ok_or("Antigravity CLI configuration path is missing")?; let connection = parse(path, read_optional_text(path)?.as_deref())?; let model = connection .get("model") @@ -247,32 +247,32 @@ pub(crate) fn antigravity_helper_requested(args: &[std::ffi::OsString]) -> bool fn antigravity_helper_command(args: &[std::ffi::OsString]) -> Result { if !args.get(1).is_some_and(|a| a == "--cpa-antigravity-cli") { - return Err("未知的 Antigravity 启动参数".into()); + return Err("Unknown Antigravity launch argument".into()); } let home = args .get(2) .map(PathBuf::from) - .ok_or("Antigravity CLI 启动参数缺少用户目录")?; + .ok_or("Antigravity CLI launch arguments are missing the user directory")?; let paths = antigravity_config_paths(AgentClient::AntigravityCli, &home); let connection = parse(&paths[1], read_optional_text(&paths[1])?.as_deref())?; if connection.get("provider").and_then(Value::as_str) != Some(MANAGED_AGENT_PROVIDER_ID) { - return Err("Antigravity CLI 的 CPA 配置已关闭,请重新应用配置".into()); + return Err("CPA configuration is disabled in Antigravity CLI. Apply the configuration again".into()); } let key = connection .get("apiKey") .and_then(Value::as_str) .filter(|s| !s.is_empty()) - .ok_or("Antigravity CLI 密钥缺失")?; + .ok_or("Antigravity CLI key is missing")?; let url = connection .get("baseUrl") .and_then(Value::as_str) .filter(|s| !s.trim().is_empty()) - .ok_or("Antigravity CLI 地址缺失")?; + .ok_or("Antigravity CLI address is missing")?; let model = connection .get("model") .and_then(Value::as_str) .filter(|s| !s.trim().is_empty()) - .ok_or("Antigravity CLI 模型缺失")?; + .ok_or("Antigravity CLI model is missing")?; let settings = parse(&paths[0], read_optional_text(&paths[0])?.as_deref())?; if settings.get("modelProvider").and_then(Value::as_str) != Some("gemini") || field( @@ -287,9 +287,9 @@ fn antigravity_helper_command(args: &[std::ffi::OsString]) -> Result Result job, @@ -337,6 +337,6 @@ pub(crate) fn run_antigravity_helper(args: &[std::ffi::OsString]) -> Result Result let temp = std::env::temp_dir(); let relative = paths .first() - .ok_or("没有配置路径")? + .ok_or("No configuration path")? .strip_prefix(&temp) - .map_err(|_| "测试配置必须位于临时目录")?; + .map_err(|_| "Test configuration must be in the temporary directory")?; let directory = temp.join( relative .components() .next() - .ok_or("缺少测试目录")? + .ok_or("Test directory is missing")? .as_os_str(), ); assert!(paths.iter().all(|p| p.starts_with(&directory))); @@ -120,7 +120,7 @@ fn valid_version_id(id: &str) -> bool { fn version_path(client: &str, paths: &[PathBuf], id: &str) -> Result { if !valid_version_id(id) { - return Err("备份版本编号无效".into()); + return Err("Invalid backup version number".into()); } Ok(backup_directory(client, paths)?.join(format!("{id}.json"))) } @@ -156,14 +156,14 @@ fn make_version(client: &str, paths: &[PathBuf], images: &Images) -> BackupVersi fn write_version(paths: &[PathBuf], version: &BackupVersion) -> Result<(), String> { let path = version_path(&version.client, paths, &version.id)?; validate_config_path(&path)?; - let parent = path.parent().ok_or("备份目录无效")?; - fs::create_dir_all(parent).map_err(|_| "创建手动备份目录失败")?; + let parent = path.parent().ok_or("Invalid backup directory")?; + fs::create_dir_all(parent).map_err(|_| "Failed to create manual backup directory")?; validate_config_path(&path)?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; fs::set_permissions(parent, fs::Permissions::from_mode(0o700)) - .map_err(|_| "设置备份目录权限失败")?; + .map_err(|_| "Failed to set backup directory permissions")?; } let mut options = fs::OpenOptions::new(); options.write(true).create_new(true); @@ -172,15 +172,15 @@ fn write_version(paths: &[PathBuf], version: &BackupVersion) -> Result<(), Strin use std::os::unix::fs::OpenOptionsExt; options.mode(0o600); } - let payload = serde_json::to_vec(version).map_err(|_| "生成备份失败")?; + let payload = serde_json::to_vec(version).map_err(|_| "Failed to generate backup")?; let package = BackupPackage { payload: version.clone(), checksum: sha256_bytes(&payload), }; - let bytes = serde_json::to_vec(&package).map_err(|_| "生成备份失败")?; + let bytes = serde_json::to_vec(&package).map_err(|_| "Failed to generate backup")?; let mut file = options .open(&path) - .map_err(|_| "创建手动备份失败,请检查备份目录权限")?; + .map_err(|_| "Failed to create manual backup. Check backup directory permissions")?; if file .write_all(&bytes) .and_then(|_| file.sync_all()) @@ -188,23 +188,23 @@ fn write_version(paths: &[PathBuf], version: &BackupVersion) -> Result<(), Strin { drop(file); let _ = fs::remove_file(&path); - return Err("保存手动备份失败".into()); + return Err("Failed to save manual backup".into()); } drop(file); if fs::read(&path).ok().as_deref() != Some(bytes.as_slice()) { let _ = fs::remove_file(&path); - return Err("手动备份写后校验失败".into()); + return Err("Manual backup post-write verification failed".into()); } Ok(()) } fn read_version(client: &str, paths: &[PathBuf], id: &str) -> Result { let path = version_path(client, paths, id)?; - let bytes = read_agent_bytes(&path)?.ok_or("备份文件缺失")?; + let bytes = read_agent_bytes(&path)?.ok_or("Backup file is missing")?; let package: BackupPackage = - serde_json::from_slice(&bytes).map_err(|_| "备份包损坏,无法恢复,可删除此版本")?; + serde_json::from_slice(&bytes).map_err(|_| "Backup package is corrupted and cannot be restored. You can delete this version")?; let version = package.payload; - if package.checksum != sha256_bytes(&serde_json::to_vec(&version).map_err(|_| "备份校验失败")?) + if package.checksum != sha256_bytes(&serde_json::to_vec(&version).map_err(|_| "Backup verification failed")?) || version.version != 1 || version.client != client || version.id != id @@ -213,7 +213,7 @@ fn read_version(client: &str, paths: &[PathBuf], id: &str) -> Result Images { fn validate_restorable(version: &BackupVersion) -> Result<(), String> { let images = backup_images(version); validate_client_config_images(&version.client, &images) - .map_err(|_| "备份含有无法解析的配置,已保留原文但不可恢复".to_string())?; + .map_err(|_| "Backup contains an unparseable configuration; the original text was preserved but cannot be restored".to_string())?; if version.client == "claude-desktop" && desktop_profile_needs_mapping(&images)? && version.mappings.is_none() { - return Err("备份缺少可靠的 Claude Desktop 模型映射,请重新选择模型后创建备份".into()); + return Err("Backup lacks a reliable Claude Desktop model mapping. Select the models again and create a new backup".into()); } Ok(()) } @@ -288,7 +288,7 @@ pub(crate) fn create_backup(client: &str, home: &Path) -> Result Result return Err("读取手动备份目录失败".into()), + Err(_) => return Err("Failed to read manual backup directory".into()), }; let mut versions = Vec::new(); for entry in entries { - let path = entry.map_err(|_| "读取手动备份目录失败")?.path(); + let path = entry.map_err(|_| "Failed to read manual backup directory")?.path(); if path.extension().and_then(|e| e.to_str()) != Some("json") { continue; } @@ -328,8 +328,8 @@ pub(crate) fn list_backups(client: &str, home: &Path) -> Result Result<(), String> { let paths = config_paths(client, home)?; let path = version_path(client, &paths, id)?; - validate_config_path(&path.parent().ok_or("备份目录无效")?.join(".path-check"))?; - fs::remove_file(&path).map_err(|_| "删除手动备份失败,请刷新列表后重试".to_string()) + validate_config_path(&path.parent().ok_or("Invalid backup directory")?.join(".path-check"))?; + fs::remove_file(&path).map_err(|_| "Failed to delete manual backup. Refresh the list and try again".to_string()) } fn preview( @@ -337,13 +337,13 @@ fn preview( paths: &[PathBuf], id: &str, ) -> Result<(BackupPreview, Images, Images), String> { - let package = read_agent_bytes(&version_path(client, paths, id)?)?.ok_or("备份文件缺失")?; + let package = read_agent_bytes(&version_path(client, paths, id)?)?.ok_or("Backup file is missing")?; let version = read_version(client, paths, id)?; validate_restorable(&version)?; let current = config_images(paths)?; let after = backup_images(&version); if read_agent_bytes(&version_path(client, paths, id)?)?.as_deref() != Some(&package) { - return Err("备份在预览期间发生变化,请重新预览".into()); + return Err("Backup changed during preview. Preview it again".into()); } let revision = sha256_bytes( format!( @@ -426,13 +426,13 @@ pub(crate) fn desktop_mapping_bytes( mappings .map(|mappings| { let profile_models_hash = - profile_models_hash(images).ok_or("Claude Desktop 模型配置无效")?; + profile_models_hash(images).ok_or("Invalid Claude Desktop model configuration")?; serde_json::to_vec(&DesktopMappingState { version: 1, profile_models_hash, mappings: mappings.clone(), }) - .map_err(|_| "生成当前模型映射失败".into()) + .map_err(|_| "Failed to generate current model mapping".into()) }) .transpose() } @@ -469,10 +469,10 @@ pub(crate) fn create_agent_config_backup( app: tauri::AppHandle, client: String, ) -> Result { - let home = app.path().home_dir().map_err(|_| "无法获取用户目录")?; + let home = app.path().home_dir().map_err(|_| "Failed to get user directory")?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "配置文件锁已损坏")?; + .map_err(|_| "Configuration file lock is poisoned")?; create_backup(&client, &home) } @@ -481,10 +481,10 @@ pub(crate) fn list_agent_config_backups( app: tauri::AppHandle, client: String, ) -> Result { - let home = app.path().home_dir().map_err(|_| "无法获取用户目录")?; + let home = app.path().home_dir().map_err(|_| "Failed to get user directory")?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "配置文件锁已损坏")?; + .map_err(|_| "Configuration file lock is poisoned")?; list_backups(&client, &home) } @@ -494,10 +494,10 @@ pub(crate) fn delete_agent_config_backup( client: String, id: String, ) -> Result<(), String> { - let home = app.path().home_dir().map_err(|_| "无法获取用户目录")?; + let home = app.path().home_dir().map_err(|_| "Failed to get user directory")?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "配置文件锁已损坏")?; + .map_err(|_| "Configuration file lock is poisoned")?; delete_backup(&client, &home, &id) } diff --git a/src-tauri/src/agents/backups/restore.rs b/src-tauri/src/agents/backups/restore.rs index a2729df..389d9e0 100644 --- a/src-tauri/src/agents/backups/restore.rs +++ b/src-tauri/src/agents/backups/restore.rs @@ -18,7 +18,7 @@ pub(crate) struct RestorePlan { fn local_restore_plan(client: &str, home: &Path, id: &str) -> Result { let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "配置文件锁已损坏")?; + .map_err(|_| "Configuration file lock is poisoned")?; let paths = config_paths(client, home)?; let (preview, before, after) = preview(client, &paths, id)?; let version = read_version(client, &paths, id)?; @@ -55,7 +55,7 @@ fn desktop_restore_configuration( .filter_map(|m| m.get("name").and_then(Value::as_str)) .collect::>(); let mut mappings = plan.version.mappings.as_ref().ok_or( - "此备份版本缺少 Claude Desktop 模型映射,无法安全恢复内核路由,请重新配置模型", + "This backup version lacks Claude Desktop model mappings and cannot safely restore kernel routing. Reconfigure the models", )?.clone(); if mappings.desktop_models.is_none() { let roles = [ @@ -71,7 +71,7 @@ fn desktop_restore_configuration( current.eq_ignore_ascii_case(id) || legacy.eq_ignore_ascii_case(id) })) else { - return Err("此备份中的模型 ID 缺少对应来源,无法安全恢复内核路由".into()); + return Err("A model ID in this backup has no corresponding source, so kernel routing cannot be safely restored".into()); }; if let Some(existing) = entries.iter_mut() .find(|entry| entry.model_id().eq_ignore_ascii_case(id)) @@ -91,7 +91,7 @@ fn desktop_restore_configuration( } mappings.desktop_models = Some(entries); } - let entries = mappings.desktop_models.as_ref().ok_or("缺少备份模型映射")?; + let entries = mappings.desktop_models.as_ref().ok_or("Backup model mapping is missing")?; validate_claude_desktop_entries(entries)?; let models = entries.iter() .map(|entry| AgentModelOption { @@ -206,19 +206,19 @@ pub(crate) async fn execute_restore_plan( revision: &str, ) -> Result { if plan.preview.revision != revision { - return Err("预览后配置发生变化,请重新选择备份版本".into()); + return Err("Configuration changed after preview. Select the backup version again".into()); } let commit = || { let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "配置文件锁已损坏")?; + .map_err(|_| "Configuration file lock is poisoned")?; let latest = preview(&plan.version.client, &plan.paths, &plan.version.id)?.0; let version = read_version(&plan.version.client, &plan.paths, &plan.version.id)?; if latest.revision != plan.local_revision || serde_json::to_vec(&version).map_err(|e| e.to_string())? != serde_json::to_vec(&plan.version).map_err(|e| e.to_string())? { - return Err("恢复期间配置或备份发生变化,请重新选择备份版本".into()); + return Err("Configuration or backup changed during restoration. Select the backup version again".into()); } commit_config_with_mappings( &plan.version.client, diff --git a/src-tauri/src/agents/backups/tests.rs b/src-tauri/src/agents/backups/tests.rs index dcd826b..589d530 100644 --- a/src-tauri/src/agents/backups/tests.rs +++ b/src-tauri/src/agents/backups/tests.rs @@ -781,7 +781,7 @@ fn clear_integration_rolls_back_config_and_state_together() { }, ) .unwrap_err(); - assert!(error.contains("已回滚")); + assert!(error.contains("rolled back")); assert_eq!(config_images(&paths).unwrap(), before); assert_eq!(fs::read_to_string(state).unwrap(), "legacy state"); } @@ -819,7 +819,7 @@ fn partial_writes_and_failed_verification_roll_back_without_persistent_snapshots }, ) .unwrap_err(); - assert!(error.contains("已回滚")); + assert!(error.contains("rolled back")); assert!(!error.contains("secret-token")); assert_eq!(before, config_images(&paths).unwrap()); } @@ -834,14 +834,14 @@ fn partial_writes_and_failed_verification_roll_back_without_persistent_snapshots &mut |_, _| Err("secret-token".into()), ) .unwrap_err(); - assert!(error.contains("回滚失败")); + assert!(error.contains("rollback both failed")); assert!(!error.contains("secret-token")); assert!(list_backups("codex", &home.0).unwrap().versions.is_empty()); save(&paths[0], "external = true"); assert!( commit_config("codex", &paths, &before, &after, "update", None) .unwrap_err() - .contains("其他程序") + .contains("another program") ); assert_eq!(fs::read_to_string(&paths[0]).unwrap(), "external = true"); } diff --git a/src-tauri/src/agents/commands.rs b/src-tauri/src/agents/commands.rs index b9a2ee9..72a66ff 100644 --- a/src-tauri/src/agents/commands.rs +++ b/src-tauri/src/agents/commands.rs @@ -18,7 +18,7 @@ pub(crate) fn inspect_agent_config_statuses( let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let api_key = effective_agent_api_key(config); let targets = [ AgentStatusDetectionTarget::Client(AgentClient::ClaudeCode), @@ -47,7 +47,7 @@ pub(crate) fn inspect_agent_config_statuses( loop { let next = queue .lock() - .map_err(|_| "智能体检测任务队列锁已损坏".to_string())? + .map_err(|_| "Agent detection task queue lock is poisoned".to_string())? .next(); let Some((index, target)) = next else { return Ok(()); @@ -62,22 +62,22 @@ pub(crate) fn inspect_agent_config_statuses( }; results .lock() - .map_err(|_| "智能体检测结果锁已损坏".to_string())?[index] = Some(status); + .map_err(|_| "Agent detection result lock is poisoned".to_string())?[index] = Some(status); } })); } for worker in workers { worker .join() - .map_err(|_| "智能体检测工作线程异常退出".to_string())??; + .map_err(|_| "Agent detection worker thread exited unexpectedly".to_string())??; } Ok::<(), String>(()) })?; results .into_inner() - .map_err(|_| "智能体检测结果锁已损坏".to_string())? + .map_err(|_| "Agent detection result lock is poisoned".to_string())? .into_iter() - .map(|status| status.ok_or_else(|| "智能体检测结果不完整".to_string())) + .map(|status| status.ok_or_else(|| "Agent detection result is incomplete".to_string())) .collect() } @@ -89,7 +89,7 @@ pub(crate) fn refresh_agent_config_status_cache( let _refresh_guard = cache .refresh_lock .lock() - .map_err(|_| "智能体配置状态刷新锁已损坏".to_string())?; + .map_err(|_| "Agent configuration status refresh lock is poisoned".to_string())?; let config = gui_config_state.snapshot()?; let port = config.port; let api_key = effective_agent_api_key(&config); @@ -109,7 +109,7 @@ pub(crate) async fn get_agent_config_statuses( let _refresh_guard = cache .refresh_lock .lock() - .map_err(|_| "智能体配置状态刷新锁已损坏".to_string())?; + .map_err(|_| "Agent configuration status refresh lock is poisoned".to_string())?; let config = gui_config_state.snapshot()?; let port = config.port; if let Some(statuses) = cache.get(port, effective_agent_api_key(&config))? { @@ -119,7 +119,7 @@ pub(crate) async fn get_agent_config_statuses( refresh_agent_config_status_cache(&app, gui_config_state.inner(), cache.inner()) }) .await - .map_err(|error| format!("智能体检测后台任务失败: {error}"))? + .map_err(|error| format!("Agent detection background task failed: {error}"))? } #[tauri::command] @@ -132,7 +132,7 @@ pub(crate) async fn refresh_agent_config_statuses( refresh_agent_config_status_cache(&app, gui_config_state.inner(), cache.inner()) }) .await - .map_err(|error| format!("智能体检测后台任务失败: {error}"))? + .map_err(|error| format!("Agent detection background task failed: {error}"))? } #[tauri::command] @@ -171,7 +171,7 @@ pub(crate) async fn check_pi_provider_update( let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let installed_version = read_pi_provider_version(&home)?; let Some(installed_version_value) = installed_version.as_deref() else { return Ok(PiProviderUpdateStatus { @@ -200,14 +200,14 @@ pub(crate) async fn install_pi_provider( let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let config = gui_config_state.snapshot()?; let executable = find_pi_executable(&home); if executable.is_none() && !inspect_pi_provider_status(&home, config.port, effective_agent_api_key(&config)) .config_exists { - return Err("未检测到 Pi CLI 或 Pi 配置文件".to_string()); + return Err("Pi CLI and Pi configuration file were not detected".to_string()); } let model = resolve_pi_default_model(&config, &model).await?; let port = config.port; @@ -221,7 +221,7 @@ pub(crate) async fn install_pi_provider( } }) .await - .map_err(|error| format!("安装 Pi CLIProxyAPI provider 任务失败: {error}"))??; + .map_err(|error| format!("Pi CLIProxyAPI provider installation task failed: {error}"))??; cache.clear()?; Ok(result) } @@ -236,10 +236,10 @@ pub(crate) async fn update_pi_provider( let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let config = gui_config_state.snapshot()?; let executable = find_pi_executable(&home) - .ok_or_else(|| "未检测到 Pi CLI,请先安装 Pi 并确保 pi 命令在 PATH 中".to_string())?; + .ok_or_else(|| "Pi CLI was not detected. Install Pi and ensure the pi command is in PATH".to_string())?; let model = resolve_pi_default_model(&config, &model).await?; let port = config.port; let api_key = effective_agent_api_key(&config).to_string(); @@ -248,7 +248,7 @@ pub(crate) async fn update_pi_provider( update_pi_provider_inner(&home, &executable, port, &api_key, &model, &proxy_url) }) .await - .map_err(|error| format!("更新 Pi 插件任务失败: {error}"))??; + .map_err(|error| format!("Pi plugin update task failed: {error}"))??; cache.clear()?; Ok(result) } @@ -263,7 +263,7 @@ pub(crate) async fn repair_pi_provider( let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let config = gui_config_state.snapshot()?; let model = resolve_pi_default_model(&config, &model).await?; let port = config.port; @@ -272,7 +272,7 @@ pub(crate) async fn repair_pi_provider( repair_pi_provider_inner(&home, port, &api_key, &model) }) .await - .map_err(|error| format!("修复 Pi 配置任务失败: {error}"))??; + .map_err(|error| format!("Pi configuration repair task failed: {error}"))??; cache.clear()?; Ok(result) } @@ -285,14 +285,14 @@ pub(crate) async fn uninstall_pi_provider( let home = app .path() .home_dir() - .map_err(|error| format!("鏃犳硶鑾峰彇鐢ㄦ埛鐩綍: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let executable = - find_pi_executable(&home).ok_or_else(|| "鏈娴嬪埌 Pi CLI锛岃鍏堝畨瑁?Pi".to_string())?; + find_pi_executable(&home).ok_or_else(|| "Pi CLI was not detected. Install Pi first".to_string())?; let result = tauri::async_runtime::spawn_blocking(move || { uninstall_pi_provider_inner(&home, &executable) }) .await - .map_err(|error| format!("鍗歌浇 Pi 鎻掍欢浠诲姟澶辫触: {error}"))??; + .map_err(|error| format!("Pi plugin uninstall task failed: {error}"))??; cache.clear()?; Ok(result) } @@ -302,7 +302,7 @@ pub(crate) fn check_codex_oauth_login(app: tauri::AppHandle) -> Result<(), Strin let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; validate_codex_oauth_login(&home) } @@ -325,7 +325,7 @@ pub(crate) async fn update_codex_model_catalog_inner( .read_timeout(Duration::from_secs(15)) .timeout(Duration::from_secs(25)), &proxy_url, - "创建 Codex 模型目录更新客户端失败", + "Failed to create Codex model catalog update client", )?; let response = client .get(CODEX_MODEL_CATALOG_URL) @@ -333,11 +333,11 @@ pub(crate) async fn update_codex_model_catalog_inner( .header(reqwest::header::USER_AGENT, APP_USER_AGENT) .send() .await - .map_err(|error| format!("从 GitHub 读取 Codex 模型目录失败: {error}"))?; + .map_err(|error| format!("Failed to read Codex model catalog from GitHub: {error}"))?; let status = response.status(); if !status.is_success() { return Err(format!( - "从 GitHub 读取 Codex 模型目录失败: HTTP {}", + "Failed to read Codex model catalog from GitHub: HTTP {}", status.as_u16() )); } @@ -346,7 +346,7 @@ pub(crate) async fn update_codex_model_catalog_inner( .is_some_and(|size| size > MAX_CODEX_MODEL_CATALOG_BYTES as u64) { return Err(format!( - "GitHub Codex 模型目录超过 {} MiB 限制", + "GitHub Codex model catalog exceeds the {} MiB limit", MAX_CODEX_MODEL_CATALOG_BYTES / 1024 / 1024 )); } @@ -354,19 +354,19 @@ pub(crate) async fn update_codex_model_catalog_inner( let mut bytes = Vec::new(); let mut stream = response.bytes_stream(); while let Some(chunk) = stream.next().await { - let chunk = chunk.map_err(|error| format!("读取 GitHub Codex 模型目录失败: {error}"))?; + let chunk = chunk.map_err(|error| format!("Failed to read GitHub Codex model catalog: {error}"))?; if bytes.len().saturating_add(chunk.len()) > MAX_CODEX_MODEL_CATALOG_BYTES { return Err(format!( - "GitHub Codex 模型目录超过 {} MiB 限制", + "GitHub Codex model catalog exceeds the {} MiB limit", MAX_CODEX_MODEL_CATALOG_BYTES / 1024 / 1024 )); } bytes.extend_from_slice(&chunk); } let catalog_json = String::from_utf8(bytes) - .map_err(|_| "GitHub Codex 模型目录不是有效的 UTF-8 文件".to_string())?; + .map_err(|_| "GitHub Codex model catalog is not a valid UTF-8 file".to_string())?; let revision = codex_catalog::validate_catalog_json(&catalog_json) - .map_err(|error| format!("GitHub Codex 模型目录校验失败: {error}"))?; + .map_err(|error| format!("GitHub Codex model catalog verification failed: {error}"))?; if revision < codex_catalog::current_catalog_revision()? || codex_catalog::current_catalog_json()? == catalog_json @@ -388,7 +388,7 @@ pub(crate) fn codex_model_catalog_override_path(app: &tauri::AppHandle) -> Resul let app_data = app .path() .app_data_dir() - .map_err(|error| format!("无法获取应用数据目录: {error}"))?; + .map_err(|error| format!("Failed to get application data directory: {error}"))?; Ok(app_data .join(CODEX_MODEL_CATALOG_OVERRIDE_DIR) .join(CODEX_MODEL_CATALOG_SOURCE_FILE)) @@ -400,9 +400,9 @@ pub(crate) fn load_codex_model_catalog_override(app: &tauri::AppHandle) -> Resul return Ok(()); } let catalog_json = fs::read_to_string(&path) - .map_err(|error| format!("读取本地 Codex 模型目录更新文件失败: {error}"))?; + .map_err(|error| format!("Failed to read local Codex model catalog update file: {error}"))?; codex_catalog::activate_catalog_json(&catalog_json) - .map_err(|error| format!("本地 Codex 模型目录更新文件无效: {error}"))?; + .map_err(|error| format!("Invalid local Codex model catalog update file: {error}"))?; Ok(()) } @@ -509,7 +509,7 @@ pub(crate) async fn get_model_alias_edit_source( alias: String, ) -> Result { let config = gui_config_state.snapshot()?; - let alias = existing_thinking_alias_model_id(&alias, "别名模型")?; + let alias = existing_thinking_alias_model_id(&alias, "Alias model")?; let content = fetch_management_config_yaml(&config).await?; let definitions = fetch_oauth_model_definitions(&config).await; model_alias_edit_context(&content, &alias, &definitions) @@ -528,17 +528,17 @@ pub(crate) async fn create_thinking_alias( let config = gui_config_state.snapshot()?; let source_id = source_id.trim().to_string(); if source_id.is_empty() { - return Err("请先选择原模型".to_string()); + return Err("Select the source model first".to_string()); } let original_alias = original_alias .as_deref() - .map(|original| existing_thinking_alias_model_id(original, "别名模型")) + .map(|original| existing_thinking_alias_model_id(original, "Alias model")) .transpose()?; let alias = match original_alias.as_deref() { Some(original) if original.eq_ignore_ascii_case(alias.trim()) => { - existing_thinking_alias_model_id(&alias, "别名模型")? + existing_thinking_alias_model_id(&alias, "Alias model")? } - _ => validate_thinking_alias_model_id(&alias, "别名模型")?, + _ => validate_thinking_alias_model_id(&alias, "Alias model")?, }; let effort = if effort.trim().is_empty() { String::new() @@ -569,11 +569,11 @@ pub(crate) async fn create_thinking_alias( .into_iter() .find(|source| source.source.id == source_id) .ok_or_else(|| { - "原模型已不在内核当前可用模型中,或其配置来源已经变化,请刷新后重新选择".to_string() + "The source model is no longer available in the kernel, or its configuration source has changed. Refresh and select it again".to_string() })? }; if fast && !alias_source_supports_fast(&source) { - return Err("Fast 仅支持 OpenAI 兼容 API、Codex API 或 Codex OAuth 模型源".to_string()); + return Err("Fast supports only OpenAI-compatible API, Codex API, or Codex OAuth model sources".to_string()); } if !effort.is_empty() && !source @@ -583,12 +583,12 @@ pub(crate) async fn create_thinking_alias( .any(|level| level.eq_ignore_ascii_case(&effort)) { return Err(format!( - "思考强度 {effort} 不在模型 {} 当前支持的等级中", + "Reasoning effort {effort} is not among the levels currently supported by model {}", source.source.model )); } if source.source.model.eq_ignore_ascii_case(&alias) { - return Err("别名模型不能和原模型相同".to_string()); + return Err("Alias model cannot be the same as the source model".to_string()); } if available_models.iter().any(|model| { @@ -597,7 +597,7 @@ pub(crate) async fn create_thinking_alias( .as_deref() .is_some_and(|original| original.eq_ignore_ascii_case(&alias)) }) { - return Err(format!("{alias} 已经是实际模型 ID,不能再作为别名")); + return Err(format!("{alias} is already an actual model ID and cannot also be used as an alias")); } let updated = match original_alias.as_deref() { Some(original) => edit_model_alias_in_yaml(&content, original, &source, &alias, &effort, fast)?, @@ -614,7 +614,7 @@ pub(crate) async fn delete_thinking_alias( oauth_channel: Option, ) -> Result, String> { let config = gui_config_state.snapshot()?; - let alias = existing_thinking_alias_model_id(&alias, "别名模型")?; + let alias = existing_thinking_alias_model_id(&alias, "Alias model")?; let content = fetch_management_config_yaml(&config).await?; let updated = remove_thinking_alias_from_yaml_for_channel(&content, &alias, oauth_channel.as_deref())?; @@ -660,9 +660,9 @@ pub(crate) async fn create_speed_alias( let config = gui_config_state.snapshot()?; let source_id = source_id.trim().to_string(); if source_id.is_empty() { - return Err("请先选择原模型".to_string()); + return Err("Select the source model first".to_string()); } - let alias = validate_thinking_alias_model_id(&alias, "别名模型")?; + let alias = validate_thinking_alias_model_id(&alias, "Alias model")?; let content = fetch_management_config_yaml(&config).await?; let available_models = fetch_agent_models(config.port, effective_agent_api_key(&config)).await?; @@ -678,24 +678,24 @@ pub(crate) async fn create_speed_alias( .find(|source| source.source.id == source_id) .cloned() .ok_or_else(|| { - "原模型已不在内核当前可用模型中,或其配置来源已经变化,请刷新后重试".to_string() + "The source model is no longer available in the kernel, or its configuration source has changed. Refresh and try again".to_string() })?; if source.source.model.eq_ignore_ascii_case(&alias) { - return Err("别名模型不能和原模型相同".to_string()); + return Err("Alias model cannot be the same as the source model".to_string()); } if available_models .iter() .any(|model| model.name.eq_ignore_ascii_case(&alias)) { - return Err(format!("{alias} 已经是实际模型 ID,不能再作为别名")); + return Err(format!("{alias} is already an actual model ID and cannot also be used as an alias")); } let document = serde_norway::from_str::(&content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; if configured_model_alias_exists(root, &alias) { - return Err(format!("别名模型 {alias} 已存在")); + return Err(format!("Alias model {alias} already exists")); } let updated = add_speed_alias_to_yaml(&content, &source, &alias)?; @@ -710,7 +710,7 @@ pub(crate) async fn delete_speed_alias( oauth_channel: Option, ) -> Result, String> { let config = gui_config_state.snapshot()?; - let alias = existing_thinking_alias_model_id(&alias, "别名模型")?; + let alias = existing_thinking_alias_model_id(&alias, "Alias model")?; let content = fetch_management_config_yaml(&config).await?; let updated = remove_speed_alias_from_yaml_for_channel(&content, &alias, oauth_channel.as_deref())?; @@ -723,7 +723,7 @@ pub(crate) async fn fetch_agent_models( api_key: &str, ) -> Result, String> { if port == 0 { - return Err("内核端口无效".to_string()); + return Err("Invalid kernel port".to_string()); } let tls_enabled = managed_core_tls_enabled(); let client = reqwest::Client::builder() @@ -732,7 +732,7 @@ pub(crate) async fn fetch_agent_models( .timeout(Duration::from_secs(15)) .danger_accept_invalid_certs(tls_enabled) .build() - .map_err(|error| format!("创建模型列表客户端失败: {error}"))?; + .map_err(|error| format!("Failed to create model list client: {error}"))?; let base_url = managed_core_loopback_origin(port); let endpoints = [ format!("{base_url}/v1/models"), @@ -747,16 +747,16 @@ pub(crate) async fn fetch_agent_models( .header(reqwest::header::USER_AGENT, USER_AGENT) .send() .await - .map_err(|error| format!("请求本机模型列表失败: {error}"))?; + .map_err(|error| format!("Failed to request local model list: {error}"))?; let status = response.status(); let body = response .text() .await - .map_err(|error| format!("读取本机模型列表失败: {error}"))?; + .map_err(|error| format!("Failed to read local model list: {error}"))?; if status.is_success() { let payload = serde_json::from_str::(&body).map_err(|error| { format!( - "解析本机模型列表失败: {error}; body={}", + "Failed to parse local model list: {error}; body={}", truncate_for_error(&body) ) })?; @@ -769,7 +769,7 @@ pub(crate) async fn fetch_agent_models( } } - Err("本机内核不支持模型列表接口".to_string()) + Err("Local kernel does not support the model list endpoint".to_string()) } pub(crate) async fn fetch_codex_runtime_models( @@ -777,7 +777,7 @@ pub(crate) async fn fetch_codex_runtime_models( api_key: &str, ) -> Result, String> { if port == 0 { - return Err("内核端口无效".to_string()); + return Err("Invalid kernel port".to_string()); } let tls_enabled = managed_core_tls_enabled(); let client = reqwest::Client::builder() @@ -786,7 +786,7 @@ pub(crate) async fn fetch_codex_runtime_models( .timeout(Duration::from_secs(15)) .danger_accept_invalid_certs(tls_enabled) .build() - .map_err(|error| format!("创建 Codex 模型列表客户端失败: {error}"))?; + .map_err(|error| format!("Failed to create Codex model list client: {error}"))?; let base_url = managed_core_loopback_origin(port); let endpoints = [ format!("{base_url}/v1/models"), @@ -802,16 +802,16 @@ pub(crate) async fn fetch_codex_runtime_models( .header(reqwest::header::USER_AGENT, USER_AGENT) .send() .await - .map_err(|error| format!("请求本地 Codex 模型列表失败: {error}"))?; + .map_err(|error| format!("Failed to request local Codex model list: {error}"))?; let status = response.status(); let body = response .text() .await - .map_err(|error| format!("读取本地 Codex 模型列表失败: {error}"))?; + .map_err(|error| format!("Failed to read local Codex model list: {error}"))?; if status.is_success() { let payload = serde_json::from_str::(&body).map_err(|error| { format!( - "解析本地 Codex 模型列表失败: {error}; body={}", + "Failed to parse local Codex model list: {error}; body={}", truncate_for_error(&body) ) })?; @@ -824,7 +824,7 @@ pub(crate) async fn fetch_codex_runtime_models( } } - Err("本地内核不支持 Codex 模型列表接口".to_string()) + Err("Local kernel does not support the Codex model list endpoint".to_string()) } pub(crate) async fn fetch_codex_catalog_runtime_models( @@ -890,7 +890,7 @@ fn sync_prepared_codex_model_catalog( let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let changed = sync_codex_model_catalog_if_configured( &home, config.port, @@ -958,7 +958,7 @@ pub(crate) fn start_codex_model_catalog_sync(app: tauri::AppHandle) { continue; } if let Err(error) = refresh_applied_codex_model_catalog(&app, &config).await { - eprintln!("后台同步 Codex 模型目录失败,保留现有配置: {error}"); + eprintln!("Failed to synchronize Codex model catalog in the background; keeping the existing configuration: {error}"); } } }); @@ -1001,7 +1001,7 @@ pub(crate) fn resolve_claude_desktop_model_mappings( if client != AgentClient::ClaudeDesktop { return Ok(None); } - let mut requested = requested.ok_or("请重新配置 Claude Desktop 的模型与别名")?; + let mut requested = requested.ok_or("Reconfigure the Claude Desktop model and alias")?; let resolve = |model: &str| resolve_available_agent_model(models, &validate_agent_model(model)?); if let Some(entries) = requested.desktop_models.as_mut() { @@ -1044,10 +1044,10 @@ pub(crate) fn resolve_claude_code_model_mappings( } let requested = requested.unwrap_or_else(|| ClaudeDesktopModelMappings::all(selected_model)); if !(100_000..=1_000_000).contains(&requested.max_context_tokens) { - return Err("Claude Code 最大窗口必须介于 100000 和 1000000 之间".to_string()); + return Err("Claude Code maximum context window must be between 100000 and 1000000".to_string()); } if !(1..=100).contains(&requested.auto_compact_pct) { - return Err("Claude Code 触发压缩百分比必须介于 1 和 100 之间".to_string()); + return Err("Claude Code compaction trigger percentage must be between 1 and 100".to_string()); } let max_context_tokens = if requested.opus_1m || requested.sonnet_1m || requested.haiku_1m { CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW @@ -1078,7 +1078,7 @@ pub(crate) fn prepare_codex_agent_models( models: catalog.models, codex_catalog: Some(catalog.json), }), - Err(error) if error.contains("CPA 当前没有可写入 Codex 的模型") => { + Err(error) if error.contains("CPA currently has no models that can be written to Codex") => { Ok(PreparedAgentModels { models: Vec::new(), codex_catalog: Some("{\n \"models\": []\n}\n".to_string()), @@ -1102,7 +1102,7 @@ pub(crate) async fn apply_agent_config( let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let config = gui_config_state.snapshot()?; let api_key = effective_agent_api_key(&config); if client == AgentClient::Codex && oauth_configuration { @@ -1128,7 +1128,7 @@ pub(crate) async fn apply_agent_config( commit_agent_with_core(&config, claude_desktop_model_mappings.as_ref(), &prepared.models, || { let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "智能体配置文件锁已损坏".to_string())?; + .map_err(|_| "Agent configuration file lock is poisoned".to_string())?; apply_agent_configuration_with_oauth( client, &home, @@ -1151,10 +1151,10 @@ pub(crate) fn clear_codex_config(app: tauri::AppHandle) -> Result, S let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "智能体配置文件锁已损坏".to_string())?; + .map_err(|_| "Agent configuration file lock is poisoned".to_string())?; clear_codex_config_files(&home) } @@ -1174,7 +1174,7 @@ pub(crate) async fn set_agent_config_enabled( let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; if enabled { let config = gui_config_state.snapshot()?; let port = config.port; @@ -1199,7 +1199,7 @@ pub(crate) async fn set_agent_config_enabled( commit_agent_with_core(&config, claude_desktop_model_mappings.as_ref(), &prepared.models, || { let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "智能体配置文件锁已损坏".to_string())?; + .map_err(|_| "Agent configuration file lock is poisoned".to_string())?; let oauth_configuration = if client == AgentClient::Codex { current_codex_oauth_configuration(&home)? } else { @@ -1224,7 +1224,7 @@ pub(crate) async fn set_agent_config_enabled( let config = gui_config_state.snapshot()?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "智能体配置文件锁已损坏".to_string())?; + .map_err(|_| "Agent configuration file lock is poisoned".to_string())?; let _ = force_restore; let result = clear_agent_managed_configuration(client, &home, config.port)?; app.state::().clear()?; @@ -1275,10 +1275,10 @@ pub(crate) fn validate_agent_can_enable( pub(crate) fn validate_agent_model(value: &str) -> Result { let model = value.trim(); if model.is_empty() { - return Err("请先选择模型".to_string()); + return Err("Select a model first".to_string()); } if model.len() > 240 || model.chars().any(char::is_control) { - return Err("模型名称格式无效".to_string()); + return Err("Invalid model name format".to_string()); } Ok(model.to_string()) } @@ -1288,13 +1288,13 @@ pub(crate) fn resolve_available_agent_model( model: &str, ) -> Result { if models.is_empty() { - return Err("当前内核没有可选模型,无法应用配置修改".to_string()); + return Err("The current kernel has no selectable models, so configuration changes cannot be applied".to_string()); } models .iter() .find(|available| available.name.eq_ignore_ascii_case(model)) .map(|available| available.name.clone()) - .ok_or_else(|| format!("模型 {model} 不在当前可用模型列表中,请刷新后重新选择")) + .ok_or_else(|| format!("Model {model} is not in the current list of available models. Refresh and select it again")) } pub(crate) fn parse_agent_model_options( @@ -1311,7 +1311,7 @@ pub(crate) fn parse_agent_model_options( .or_else(|| payload.get("data").and_then(serde_json::Value::as_array)) .or_else(|| payload.get("models").and_then(serde_json::Value::as_array)) .or(object_models.as_ref()) - .ok_or_else(|| "本机模型列表响应缺少 data 数组或 models 目录".to_string())?; + .ok_or_else(|| "Local model list response is missing a data array or models catalog".to_string())?; let mut models = Vec::new(); for item in source { let name = if let Some(name) = item.as_str() { @@ -1407,10 +1407,10 @@ pub(crate) fn mark_configured_agent_model_aliases( content: &str, ) -> Result<(), String> { let document = serde_norway::from_str::(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; for section in MODEL_ALIAS_CONFIG_SECTIONS { let Some(providers) = yaml_mapping_value(root, section) else { @@ -1418,7 +1418,7 @@ pub(crate) fn mark_configured_agent_model_aliases( }; let providers = providers .as_sequence() - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; for provider in providers { let Some(provider) = provider.as_mapping() else { continue; @@ -1431,7 +1431,7 @@ pub(crate) fn mark_configured_agent_model_aliases( }; let configured_models = configured_models .as_sequence() - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; for configured in configured_models { if configured_model_identity(configured).is_some_and(|(_, client_model, _)| { configured_provider_model_is_enabled(provider, &client_model) @@ -1445,7 +1445,7 @@ pub(crate) fn mark_configured_agent_model_aliases( if let Some(oauth_aliases) = yaml_mapping_value(root, "oauth-model-alias") { let oauth_aliases = oauth_aliases .as_mapping() - .ok_or_else(|| "oauth-model-alias 必须是 YAML 映射".to_string())?; + .ok_or_else(|| "oauth-model-alias must be a YAML mapping".to_string())?; for entries in oauth_aliases.values() { let Some(entries) = entries.as_sequence() else { continue; @@ -1485,7 +1485,7 @@ pub(crate) fn parse_codex_model_definitions( .as_array() .or_else(|| payload.get("models").and_then(serde_json::Value::as_array)) .or_else(|| payload.get("data").and_then(serde_json::Value::as_array)) - .ok_or_else(|| "Codex 模型定义响应缺少 models 或 data 数组".to_string())?; + .ok_or_else(|| "Codex model definition response is missing a models or data array".to_string())?; let mut definitions = Vec::new(); for item in source { let id = ["id", "ID", "name"] @@ -1589,15 +1589,15 @@ pub(crate) fn format_agent_models_error(status: u16, body: &str) -> String { .map(str::trim) .filter(|message| !message.is_empty()); if let Some(message) = message { - return format!("获取本机模型列表失败 ({status}): {message}"); + return format!("Failed to get local model list ({status}): {message}"); } } let body = body.trim(); if body.is_empty() { - format!("获取本机模型列表失败 ({status})") + format!("Failed to get local model list ({status})") } else { format!( - "获取本机模型列表失败 ({status}): {}", + "Failed to get local model list ({status}): {}", truncate_for_error(body) ) } diff --git a/src-tauri/src/agents/configuration.rs b/src-tauri/src/agents/configuration.rs index ee3fd27..6b4dcb8 100644 --- a/src-tauri/src/agents/configuration.rs +++ b/src-tauri/src/agents/configuration.rs @@ -63,7 +63,7 @@ pub(crate) fn build_agent_updates_with_oauth( } AgentClient::ClaudeDesktop => { if paths.len() != 4 { - return Err("Claude Desktop 当前平台配置路径不可用".to_string()); + return Err("Claude Desktop configuration path is unavailable on the current platform".to_string()); } let normal_before = read_optional_text(&paths[0])?; let threep_before = read_optional_text(&paths[1])?; @@ -113,7 +113,7 @@ pub(crate) fn build_agent_updates_with_oauth( path: paths[0].clone(), after, }]; - let catalog = codex_catalog.ok_or_else(|| "无法生成 Codex 模型目录".to_string())?; + let catalog = codex_catalog.ok_or_else(|| "Unable to generate Codex model catalog".to_string())?; validate_codex_catalog(catalog, model)?; updates.push(AgentFileUpdate { path: codex_model_catalog_path(home), @@ -164,7 +164,7 @@ pub(crate) fn build_agent_updates_with_oauth( } AgentClient::DeepSeekHarness => { if paths.len() != 2 { - return Err("DeepSeek Harness 配置路径数量无效".to_string()); + return Err("Invalid number of DeepSeek Harness configuration paths".to_string()); } let settings_before = read_optional_text(&paths[0])?; let credentials_before = read_optional_text(&paths[1])?; @@ -255,7 +255,7 @@ pub(crate) fn build_agent_updates_with_oauth( pub(crate) fn read_optional_text(path: &Path) -> Result, String> { read_agent_bytes(path)?.map(|bytes| String::from_utf8(bytes) - .map_err(|_| "配置不是 UTF-8 文本,请使用手动备份恢复或基础配置模板修复".to_string())).transpose() + .map_err(|_| "Configuration is not UTF-8 text. Restore it from a manual backup or repair it with the base configuration template".to_string())).transpose() } pub(crate) fn read_agent_yaml_mapping_or_empty( @@ -266,7 +266,7 @@ pub(crate) fn read_agent_yaml_mapping_or_empty( return Ok(serde_norway::Mapping::new()); } let content = fs::read_to_string(path) - .map_err(|error| format!("读取 {label} 失败 {}: {error}", path_to_string(path)))?; + .map_err(|error| format!("Failed to read {label} {}: {error}", path_to_string(path)))?; parse_agent_yaml_mapping(Some(&content), label) } @@ -278,10 +278,10 @@ pub(crate) fn parse_agent_yaml_mapping( return Ok(serde_norway::Mapping::new()); }; serde_norway::from_str::(content) - .map_err(|error| format!("{label} YAML 格式无效: {error}"))? + .map_err(|error| format!("Invalid {label} YAML format: {error}"))? .as_mapping() .cloned() - .ok_or_else(|| format!("{label} 根节点必须是 YAML 映射")) + .ok_or_else(|| format!("{label} root must be a YAML mapping")) } pub(crate) fn render_agent_yaml_mapping_update( @@ -306,9 +306,9 @@ where .map(|_| existing.unwrap_or_default()) .unwrap_or("{}\n"); let mut document = yaml_serde_edit::YamlValue::parse(source) - .map_err(|error| format!("{label} YAML 格式无效: {error}"))?; + .map_err(|error| format!("Invalid {label} YAML format: {error}"))?; render_updated_core_yaml(&mut document, updated) - .map_err(|error| format!("更新 {label} 失败: {error}")) + .map_err(|error| format!("Failed to update {label}: {error}")) } pub(crate) fn build_deepseek_harness_models( @@ -327,14 +327,14 @@ pub(crate) fn build_deepseek_harness_models( entry.insert( yaml_key("contextWindow"), serde_norway::to_value(context_window) - .map_err(|error| format!("序列化 Harness 模型上下文失败: {error}"))?, + .map_err(|error| format!("Failed to serialize Harness model context: {error}"))?, ); } if let Some(input) = model.input_modalities { entry.insert( yaml_key("input"), serde_norway::to_value(input) - .map_err(|_| "序列化 Harness 模型输入类型失败")?, + .map_err(|_| "Failed to serialize Harness model input types")?, ); } Ok(serde_norway::Value::Mapping(entry)) @@ -369,17 +369,17 @@ pub(crate) fn render_deepseek_harness_settings( .entry(yaml_key("llm-pi-ai")) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| "DeepSeek Harness llm-pi-ai 必须是映射".to_string())?; + .ok_or_else(|| "DeepSeek Harness llm-pi-ai must be a mapping".to_string())?; let providers = llm .entry(yaml_key("providers")) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| "DeepSeek Harness llm-pi-ai.providers 必须是映射".to_string())?; + .ok_or_else(|| "DeepSeek Harness llm-pi-ai.providers must be a mapping".to_string())?; let provider = providers .entry(yaml_key(DEEPSEEK_HARNESS_PROVIDER_ID)) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| "DeepSeek Harness EasyCLIProxyAPI provider 必须是映射".to_string())?; + .ok_or_else(|| "DeepSeek Harness EasyCLIProxyAPI provider must be a mapping".to_string())?; for (key, value) in [ ("displayName", "EasyCLIProxyAPI"), ("apiKeyEnv", DEEPSEEK_HARNESS_CREDENTIAL), @@ -397,7 +397,7 @@ pub(crate) fn render_deepseek_harness_settings( .entry(yaml_key("agent-default-model")) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| "DeepSeek Harness agent-default-model 必须是映射".to_string())?; + .ok_or_else(|| "DeepSeek Harness agent-default-model must be a mapping".to_string())?; let same_model = selection.get(yaml_key("provider")).and_then(serde_norway::Value::as_str) == Some(DEEPSEEK_HARNESS_PROVIDER_ID) && selection.get(yaml_key("model")).and_then(serde_norway::Value::as_str) == Some(model); selection.insert( @@ -437,21 +437,21 @@ fn validate_deepseek_harness_versioned_credentials( label: &str, ) -> Result<(), String> { let version = - yaml_mapping_value(root, "version").ok_or_else(|| format!("{label} 缺少 version 字段"))?; + yaml_mapping_value(root, "version").ok_or_else(|| format!("{label} is missing the version field"))?; if !deepseek_harness_credentials_version_is_supported(version) { return Err(format!( - "{label} version 必须为 {DEEPSEEK_HARNESS_CREDENTIALS_VERSION}" + "{label} version must be {DEEPSEEK_HARNESS_CREDENTIALS_VERSION}" )); } for key in root.keys() { let Some(key) = key.as_str() else { - return Err(format!("{label} 顶层字段名必须是字符串")); + return Err(format!("Top-level field names in {label} must be strings")); }; let _ = key; } for section in ["refs", "records"] { if yaml_mapping_value(root, section).is_some_and(|value| !value.is_mapping()) { - return Err(format!("{label} {section} 必须是映射")); + return Err(format!("{label} {section} must be a mapping")); } } Ok(()) @@ -474,7 +474,7 @@ fn deepseek_harness_credentials_refs_mut<'a>( root.entry(yaml_key("refs")) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| format!("{label} refs 必须是映射")) + .ok_or_else(|| format!("{label} refs must be a mapping")) } fn remove_deepseek_harness_managed_credential( @@ -531,7 +531,7 @@ pub(crate) fn write_deepseek_harness_file( fs::set_permissions(path, fs::Permissions::from_mode(0o600)).map_err(|error| { format!( - "设置 DeepSeek Harness 凭据权限失败 {}: {error}", + "Failed to set DeepSeek Harness credential permissions {}: {error}", path_to_string(path) ) })?; @@ -573,12 +573,12 @@ pub(crate) fn build_claude_agent_config( ) -> Result { let mut root = match existing.map(str::trim).filter(|value| !value.is_empty()) { Some(value) => serde_json::from_str::(value) - .map_err(|error| format!("Claude Code settings.json 格式无效: {error}"))?, + .map_err(|error| format!("Invalid Claude Code settings.json format: {error}"))?, None => serde_json::json!({}), }; let root = root .as_object_mut() - .ok_or_else(|| "Claude Code settings.json 根节点必须是对象".to_string())?; + .ok_or_else(|| "Claude Code settings.json root must be an object".to_string())?; let mappings = mappings .cloned() .unwrap_or_else(|| ClaudeDesktopModelMappings::all(model)); @@ -649,7 +649,7 @@ pub(crate) fn build_claude_agent_config( serde_json::Value::String(model_settings.sonnet), ); let mut rendered = serde_json::to_string_pretty(&serde_json::Value::Object(root.clone())) - .map_err(|error| format!("生成 Claude Code 配置失败: {error}"))?; + .map_err(|error| format!("Failed to generate Claude Code configuration: {error}"))?; rendered.push('\n'); Ok(rendered) } @@ -660,13 +660,13 @@ pub(crate) fn parse_agent_json_object( ) -> Result, String> { let value = match existing.map(str::trim).filter(|value| !value.is_empty()) { Some(value) => serde_json::from_str::(value) - .map_err(|error| format!("{label} 格式无效: {error}"))?, + .map_err(|error| format!("Invalid {label} format: {error}"))?, None => serde_json::json!({}), }; value .as_object() .cloned() - .ok_or_else(|| format!("{label} 根节点必须是对象")) + .ok_or_else(|| format!("{label} root must be an object")) } pub(crate) fn parse_agent_json5_object( @@ -675,13 +675,13 @@ pub(crate) fn parse_agent_json5_object( ) -> Result, String> { let value = match existing.map(str::trim).filter(|value| !value.is_empty()) { Some(value) => json5::from_str::(value) - .map_err(|error| format!("{label} 格式无效: {error}"))?, + .map_err(|error| format!("Invalid {label} format: {error}"))?, None => serde_json::json!({}), }; value .as_object() .cloned() - .ok_or_else(|| format!("{label} 根节点必须是对象")) + .ok_or_else(|| format!("{label} root must be an object")) } pub(crate) fn ensure_json_object_entry<'a>( @@ -713,7 +713,7 @@ pub(crate) fn render_agent_json( label: &str, ) -> Result { let mut rendered = serde_json::to_string_pretty(&serde_json::Value::Object(root)) - .map_err(|error| format!("生成 {label} 失败: {error}"))?; + .map_err(|error| format!("Failed to generate {label}: {error}"))?; rendered.push('\n'); Ok(rendered) } @@ -753,9 +753,9 @@ pub(crate) fn ordered_agent_models( pub(crate) fn build_claude_desktop_deployment_config( existing: Option<&str>, ) -> Result { - let mut root = parse_agent_json_object(existing, "Claude Desktop 配置")?; + let mut root = parse_agent_json_object(existing, "Claude Desktop configuration")?; root.insert("deploymentMode".to_string(), serde_json::json!("3p")); - render_agent_json(root, "Claude Desktop 配置") + render_agent_json(root, "Claude Desktop configuration") } pub(crate) fn build_claude_desktop_profile( @@ -766,7 +766,7 @@ pub(crate) fn build_claude_desktop_profile( models: &[AgentModelOption], mappings: Option<&ClaudeDesktopModelMappings>, ) -> Result { - let mut root = parse_agent_json_object(existing, "Claude Desktop 网关配置")?; + let mut root = parse_agent_json_object(existing, "Claude Desktop gateway configuration")?; root.insert( "disableDeploymentModeChooser".to_string(), serde_json::json!(true), @@ -855,7 +855,7 @@ pub(crate) fn build_claude_desktop_profile( "inferenceModels".to_string(), serde_json::Value::Array(deduplicated_models), ); - render_agent_json(root, "Claude Desktop 网关配置") + render_agent_json(root, "Claude Desktop gateway configuration") } pub(crate) fn claude_desktop_inference_model( @@ -911,7 +911,7 @@ pub(crate) fn claude_desktop_family_tier(route_model: &str) -> Option<&'static s } pub(crate) fn build_claude_desktop_meta(existing: Option<&str>) -> Result { - let mut root = parse_agent_json_object(existing, "Claude Desktop 配置索引")?; + let mut root = parse_agent_json_object(existing, "Claude Desktop configuration index")?; repair_claude_desktop_meta_names(&mut root); let entries = ensure_json_array_entry(&mut root, "entries"); let mut managed_entry = None; @@ -939,7 +939,7 @@ pub(crate) fn build_claude_desktop_meta(existing: Option<&str>) -> Result) { @@ -976,7 +976,7 @@ pub(crate) fn clear_agent_managed_configuration( let entry = after .iter_mut() .find(|(candidate, _)| candidate == &path) - .ok_or("配置清理路径不匹配")?; + .ok_or("Configuration cleanup path mismatch")?; entry.1 = bytes; } after @@ -1018,7 +1018,7 @@ where return Ok(None); } let content = fs::read_to_string(path) - .map_err(|error| format!("读取 {label} 失败 {}: {error}", path_to_string(path)))?; + .map_err(|error| format!("Failed to read {label} {}: {error}", path_to_string(path)))?; let mut root = parse_agent_json_object(Some(&content), label)?; if !update(&mut root) { return Ok(None); @@ -1033,16 +1033,16 @@ where pub(crate) fn prepare_claude_desktop_managed_removal(paths: &[PathBuf]) -> Result { if paths.len() != 4 { - return Err("Claude Desktop 当前平台配置路径不可用".to_string()); + return Err("Claude Desktop configuration path is unavailable on the current platform".to_string()); } - let meta = read_agent_json_or_empty(&paths[3], "Claude Desktop 配置索引")?; + let meta = read_agent_json_or_empty(&paths[3], "Claude Desktop configuration index")?; let applied_id = meta.get("appliedId").and_then(serde_json::Value::as_str); let managed_active = applied_id == Some(CLAUDE_DESKTOP_PROFILE_ID) || (applied_id.is_none() && agent_has_managed_marker(AgentClient::ClaudeDesktop, paths)?); let mut changed = Vec::new(); for (path, label) in [ - (&paths[0], "Claude Desktop 主配置"), - (&paths[1], "Claude Desktop 3P 配置"), + (&paths[0], "Claude Desktop main configuration"), + (&paths[1], "Claude Desktop 3P configuration"), ] { if let Some(update) = prepare_agent_json_removal(path, label, |root| { if managed_active @@ -1062,7 +1062,7 @@ pub(crate) fn prepare_claude_desktop_managed_removal(paths: &[PathBuf]) -> Resul } if let Some(update) = - prepare_agent_json_removal(&paths[2], "Claude Desktop 网关配置", |root| { + prepare_agent_json_removal(&paths[2], "Claude Desktop gateway configuration", |root| { let mut updated = false; for key in [ "disableDeploymentModeChooser", @@ -1081,7 +1081,7 @@ pub(crate) fn prepare_claude_desktop_managed_removal(paths: &[PathBuf]) -> Resul } if let Some(update) = - prepare_agent_json_removal(&paths[3], "Claude Desktop 配置索引", |root| { + prepare_agent_json_removal(&paths[3], "Claude Desktop configuration index", |root| { let mut updated = false; if root.get("appliedId").and_then(serde_json::Value::as_str) == Some(CLAUDE_DESKTOP_PROFILE_ID) @@ -1119,11 +1119,11 @@ pub(crate) fn prepare_claude_code_managed_removal( expected_base_url: &str, ) -> Result { let Some(path) = paths.first() else { - return Err("Claude Code 当前平台配置路径不可用".to_string()); + return Err("Claude Code configuration path is unavailable on the current platform".to_string()); }; let expected_base_url = reqwest::Url::parse(expected_base_url) - .map_err(|_| "CPA 接入地址无效".to_string())?; - let updated = prepare_agent_json_removal(path, "Claude Code 配置", |root| { + .map_err(|_| "Invalid CPA access URL".to_string())?; + let updated = prepare_agent_json_removal(path, "Claude Code configuration", |root| { let managed_model = root .get("env") .and_then(serde_json::Value::as_object) @@ -1193,16 +1193,16 @@ pub(crate) fn prepare_codex_managed_removal(paths: &[PathBuf]) -> Result() - .map_err(|error| format!("解析 Codex 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse Codex configuration: {error}"))?; let managed_selected = document.get("model_provider").and_then(Item::as_str) == Some(MANAGED_AGENT_PROVIDER_ID); let managed_catalog = @@ -1223,7 +1223,7 @@ pub(crate) fn prepare_codex_managed_removal(paths: &[PathBuf]) -> Result(&rendered) - .map_err(|error| format!("验证恢复后的 Codex 配置失败: {error}"))?; + .map_err(|error| format!("Failed to validate restored Codex configuration: {error}"))?; let mut changed = vec![( path.clone(), (!rendered.trim().is_empty()).then(|| rendered.into_bytes()), @@ -1236,10 +1236,10 @@ pub(crate) fn prepare_codex_managed_removal(paths: &[PathBuf]) -> Result Result { let Some(path) = paths.first() else { - return Err("OpenCode 当前平台配置路径不可用".to_string()); + return Err("OpenCode configuration path is unavailable on the current platform".to_string()); }; let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/"); - let updated = prepare_agent_json5_removal(path, "OpenCode 配置", |root| { + let updated = prepare_agent_json5_removal(path, "OpenCode configuration", |root| { let mut changed = false; if root .get("model") @@ -1268,12 +1268,12 @@ pub(crate) fn prepare_opencode_managed_removal(paths: &[PathBuf]) -> Result Result { if paths.is_empty() { - return Err("ZCode 当前平台配置路径不可用".to_string()); + return Err("ZCode configuration path is unavailable on the current platform".to_string()); } let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/"); let mut changed_paths = Vec::new(); for path in paths { - let updated = prepare_agent_json_removal(path, "ZCode 配置", |root| { + let updated = prepare_agent_json_removal(path, "ZCode configuration", |root| { let mut changed = false; let remove_model = if root .get("model") @@ -1355,16 +1355,16 @@ pub(crate) fn prepare_managed_toml_client_removal( use toml_edit::{Document, Item}; let Some(path) = paths.first() else { - return Err(format!("{label} 当前平台配置路径不可用")); + return Err(format!("{label} configuration path is unavailable on the current platform")); }; if !path.is_file() { return Ok(Vec::new()); } let content = fs::read_to_string(path) - .map_err(|error| format!("读取 {label} 配置失败 {}: {error}", path_to_string(path)))?; + .map_err(|error| format!("Failed to read {label} configuration {}: {error}", path_to_string(path)))?; let mut document = content .parse::() - .map_err(|error| format!("解析 {label} 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse {label} configuration: {error}"))?; let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/"); let mut changed = false; @@ -1437,7 +1437,7 @@ pub(crate) fn prepare_managed_toml_client_removal( } let rendered = document.to_string(); toml::from_str::(&rendered) - .map_err(|error| format!("验证恢复后的 {label} 配置失败: {error}"))?; + .map_err(|error| format!("Failed to validate restored {label} configuration: {error}"))?; Ok(vec![( path.clone(), (!rendered.trim().is_empty()).then(|| rendered.into_bytes()), @@ -1456,13 +1456,13 @@ where return Ok(None); } let content = fs::read_to_string(path) - .map_err(|error| format!("读取 {label} 失败 {}: {error}", path_to_string(path)))?; + .map_err(|error| format!("Failed to read {label} {}: {error}", path_to_string(path)))?; let value = json5::from_str::(&content) - .map_err(|error| format!("解析 {label} 失败: {error}"))?; + .map_err(|error| format!("Failed to parse {label}: {error}"))?; let mut root = value .as_object() .cloned() - .ok_or_else(|| format!("{label} 根节点必须是对象"))?; + .ok_or_else(|| format!("{label} root must be an object"))?; if !update(&mut root) { return Ok(None); } @@ -1485,10 +1485,10 @@ where pub(crate) fn prepare_openclaw_managed_removal(paths: &[PathBuf]) -> Result { let Some(path) = paths.first() else { - return Err("OpenClaw 当前平台配置路径不可用".to_string()); + return Err("OpenClaw configuration path is unavailable on the current platform".to_string()); }; let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/"); - let updated = prepare_agent_json5_removal(path, "OpenClaw 配置", |root| { + let updated = prepare_agent_json5_removal(path, "OpenClaw configuration", |root| { let mut changed = false; if let Some(models) = root .get_mut("models") @@ -1554,19 +1554,19 @@ pub(crate) fn prepare_openclaw_managed_removal(paths: &[PathBuf]) -> Result Result { let Some(path) = paths.first() else { - return Err("Hermes 当前平台配置路径不可用".to_string()); + return Err("Hermes configuration path is unavailable on the current platform".to_string()); }; if !path.is_file() { return Ok(Vec::new()); } let content = fs::read_to_string(path) - .map_err(|error| format!("读取 Hermes 配置失败 {}: {error}", path_to_string(path)))?; + .map_err(|error| format!("Failed to read Hermes configuration {}: {error}", path_to_string(path)))?; let mut document = yaml_serde_edit::YamlValue::parse(&content) - .map_err(|error| format!("解析 Hermes 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse Hermes configuration: {error}"))?; let mut updated = document.get().clone(); let root = updated .as_mapping_mut() - .ok_or_else(|| "Hermes 配置根节点必须是映射".to_string())?; + .ok_or_else(|| "Hermes configuration root must be a mapping".to_string())?; let mut changed = false; let providers_empty = if let Some(providers) = root .get_mut(yaml_key("custom_providers")) @@ -1681,12 +1681,12 @@ pub(crate) fn prepare_deepseek_harness_managed_removal( paths: &[PathBuf], ) -> Result { if paths.len() != 2 { - return Err("DeepSeek Harness 配置路径数量无效".to_string()); + return Err("Invalid number of DeepSeek Harness configuration paths".to_string()); } let mut changed_paths = Vec::new(); if paths[0].is_file() { let current = fs::read_to_string(&paths[0]) - .map_err(|error| format!("读取 DeepSeek Harness settings 失败: {error}"))?; + .map_err(|error| format!("Failed to read DeepSeek Harness settings: {error}"))?; let mut changed = false; let rendered = render_agent_yaml_mapping_update( Some(¤t), @@ -1706,7 +1706,7 @@ pub(crate) fn prepare_deepseek_harness_managed_removal( } if paths[1].is_file() { let current = fs::read_to_string(&paths[1]) - .map_err(|error| format!("读取 DeepSeek Harness credentials 失败: {error}"))?; + .map_err(|error| format!("Failed to read DeepSeek Harness credentials: {error}"))?; let mut changed = false; let rendered = render_agent_yaml_mapping_update( Some(¤t), @@ -1777,8 +1777,8 @@ pub(crate) fn build_restored_claude_code_config( current: &str, original: Option<&str>, ) -> Result, String> { - let mut root = parse_agent_json_object(Some(current), "当前 Claude Code 配置")?; - let original_root = parse_restored_json_object(original, "原始 Claude Code 配置")?; + let mut root = parse_agent_json_object(Some(current), "Current Claude Code configuration")?; + let original_root = parse_restored_json_object(original, "Original Claude Code configuration")?; restore_json_key(&mut root, original_root.as_ref(), "model"); let original_env = original_root .as_ref() @@ -1824,7 +1824,7 @@ pub(crate) fn build_restored_claude_code_config( } else { restore_json_key(&mut root, original_root.as_ref(), "env"); } - render_restored_json(root, original.is_some(), "恢复后的 Claude Code 配置") + render_restored_json(root, original.is_some(), "Restored Claude Code configuration") } pub(crate) fn build_restored_claude_desktop_config( @@ -1832,8 +1832,8 @@ pub(crate) fn build_restored_claude_desktop_config( current: &str, original: Option<&str>, ) -> Result, String> { - let mut root = parse_agent_json_object(Some(current), "当前 Claude Desktop 配置")?; - let original_root = parse_restored_json_object(original, "原始 Claude Desktop 配置")?; + let mut root = parse_agent_json_object(Some(current), "Current Claude Desktop configuration")?; + let original_root = parse_restored_json_object(original, "Original Claude Desktop configuration")?; match index { 0 | 1 => restore_json_key(&mut root, original_root.as_ref(), "deploymentMode"), 2 => { @@ -1879,17 +1879,17 @@ pub(crate) fn build_restored_claude_desktop_config( root.insert("entries".to_string(), serde_json::Value::Array(entries)); } } - _ => return Err("Claude Desktop 配置文件索引无效".to_string()), + _ => return Err("Invalid Claude Desktop configuration file index".to_string()), } - render_restored_json(root, original.is_some(), "恢复后的 Claude Desktop 配置") + render_restored_json(root, original.is_some(), "Restored Claude Desktop configuration") } pub(crate) fn build_restored_opencode_config( current: &str, original: Option<&str>, ) -> Result, String> { - let mut root = parse_agent_json5_object(Some(current), "当前 OpenCode 配置")?; - let original_root = parse_restored_json5_object(original, "原始 OpenCode 配置")?; + let mut root = parse_agent_json5_object(Some(current), "Current OpenCode configuration")?; + let original_root = parse_restored_json5_object(original, "Original OpenCode configuration")?; for key in ["$schema", "model"] { restore_json_key(&mut root, original_root.as_ref(), key); } @@ -1954,7 +1954,7 @@ pub(crate) fn build_restored_opencode_config( } else { restore_json_key(&mut root, original_root.as_ref(), "provider"); } - let restored = render_restored_json(root, original.is_some(), "恢复后的 OpenCode 配置")?; + let restored = render_restored_json(root, original.is_some(), "Restored OpenCode configuration")?; Ok(restored.map(|rendered| { let comments = extract_json5_comments(current); if comments.is_empty() { @@ -1969,8 +1969,8 @@ pub(crate) fn build_restored_zcode_config( current: &str, original: Option<&str>, ) -> Result, String> { - let mut root = parse_agent_json_object(Some(current), "当前 ZCode 配置")?; - let original_root = parse_restored_json_object(original, "原始 ZCode 配置")?; + let mut root = parse_agent_json_object(Some(current), "Current ZCode configuration")?; + let original_root = parse_restored_json_object(original, "Original ZCode configuration")?; restore_json_key(&mut root, original_root.as_ref(), "model"); let original_provider = original_root .as_ref() @@ -2042,7 +2042,7 @@ pub(crate) fn build_restored_zcode_config( } else { restore_json_key(&mut root, original_root.as_ref(), "provider"); } - render_restored_json(root, original.is_some(), "恢复后的 ZCode 配置") + render_restored_json(root, original.is_some(), "Restored ZCode configuration") } pub(crate) fn build_restored_kimi_code_config( @@ -2086,9 +2086,9 @@ pub(crate) fn build_restored_managed_toml_client_config( ) -> Result, String> { use toml_edit::Item; - let mut current_document = parse_codex_document(Some(current), &format!("当前 {label} 配置"))?; + let mut current_document = parse_codex_document(Some(current), &format!("Current {label} configuration"))?; let original_document = original - .map(|content| parse_codex_document(Some(content), &format!("原始 {label} 配置"))) + .map(|content| parse_codex_document(Some(content), &format!("Original {label} configuration"))) .transpose()?; if let Some(original_document) = original_document.as_ref() { merge_missing_codex_table_items( @@ -2199,7 +2199,7 @@ pub(crate) fn build_restored_managed_toml_client_config( } let rendered = current_document.to_string(); toml::from_str::(&rendered) - .map_err(|error| format!("验证恢复后的 {label} 配置失败: {error}"))?; + .map_err(|error| format!("Failed to validate restored {label} configuration: {error}"))?; if original.is_none() && rendered.trim().is_empty() { Ok(None) } else { @@ -2212,18 +2212,18 @@ pub(crate) fn build_restored_openclaw_config( original: Option<&str>, ) -> Result, String> { let current_value = json5::from_str::(current) - .map_err(|error| format!("当前 OpenClaw 配置格式无效: {error}"))?; + .map_err(|error| format!("Invalid current OpenClaw configuration format: {error}"))?; let mut root = current_value .as_object() .cloned() - .ok_or_else(|| "当前 OpenClaw 配置根节点必须是对象".to_string())?; + .ok_or_else(|| "Current OpenClaw configuration root must be an object".to_string())?; let original_root = original .map(|content| { json5::from_str::(content) - .map_err(|error| format!("原始 OpenClaw 配置格式无效: {error}"))? + .map_err(|error| format!("Invalid original OpenClaw configuration format: {error}"))? .as_object() .cloned() - .ok_or_else(|| "原始 OpenClaw 配置根节点必须是对象".to_string()) + .ok_or_else(|| "Original OpenClaw configuration root must be an object".to_string()) }) .transpose()?; let original_models = original_root @@ -2364,7 +2364,7 @@ pub(crate) fn build_restored_openclaw_config( if root.is_empty() && original.is_none() { return Ok(None); } - let rendered = render_agent_json(root, "恢复后的 OpenClaw 配置")?; + let rendered = render_agent_json(root, "Restored OpenClaw configuration")?; let comments = extract_json5_comments(current); Ok(Some(if comments.is_empty() { rendered @@ -2390,15 +2390,15 @@ pub(crate) fn build_restored_hermes_config( original: Option<&str>, ) -> Result, String> { let mut document = yaml_serde_edit::YamlValue::parse(current) - .map_err(|error| format!("当前 Hermes 配置格式无效: {error}"))?; + .map_err(|error| format!("Invalid current Hermes configuration format: {error}"))?; let mut updated = document.get().clone(); let root = updated .as_mapping_mut() - .ok_or_else(|| "当前 Hermes 配置根节点必须是映射".to_string())?; + .ok_or_else(|| "Current Hermes configuration root must be a mapping".to_string())?; let original_value = original .map(|content| { serde_norway::from_str::(content) - .map_err(|error| format!("原始 Hermes 配置格式无效: {error}")) + .map_err(|error| format!("Invalid original Hermes configuration format: {error}")) }) .transpose()?; let original_root = original_value @@ -2494,12 +2494,12 @@ pub(crate) fn restore_deepseek_harness_provider( .entry(yaml_key("llm-pi-ai")) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| "当前 DeepSeek Harness llm-pi-ai 必须是映射".to_string())?; + .ok_or_else(|| "Current DeepSeek Harness llm-pi-ai must be a mapping".to_string())?; let providers = llm .entry(yaml_key("providers")) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| "当前 DeepSeek Harness llm-pi-ai.providers 必须是映射".to_string())?; + .ok_or_else(|| "Current DeepSeek Harness llm-pi-ai.providers must be a mapping".to_string())?; providers.insert(yaml_key(DEEPSEEK_HARNESS_PROVIDER_ID), original_provider); } else { let mut remove_llm = false; @@ -2531,17 +2531,17 @@ pub(crate) fn build_restored_deepseek_harness_settings( current: &str, original: Option<&str>, ) -> Result, String> { - let original_root = parse_agent_yaml_mapping(original, "原始 DeepSeek Harness settings")?; + let original_root = parse_agent_yaml_mapping(original, "Original DeepSeek Harness settings")?; let rendered = render_agent_yaml_mapping_update( Some(current), - "当前 DeepSeek Harness settings", + "Current DeepSeek Harness settings", |root| { restore_deepseek_harness_provider(root, Some(&original_root))?; restore_yaml_key(root, Some(&original_root), "agent-default-model"); Ok(()) }, )?; - let root = parse_agent_yaml_mapping(Some(&rendered), "恢复后的 DeepSeek Harness settings")?; + let root = parse_agent_yaml_mapping(Some(&rendered), "Restored DeepSeek Harness settings")?; Ok((!root.is_empty() || original.is_some()).then_some(rendered)) } @@ -2549,15 +2549,15 @@ pub(crate) fn build_restored_deepseek_harness_credentials( current: &str, original: Option<&str>, ) -> Result, String> { - let original_root = parse_agent_yaml_mapping(original, "原始 DeepSeek Harness credentials")?; + let original_root = parse_agent_yaml_mapping(original, "Original DeepSeek Harness credentials")?; let original_credential = deepseek_harness_original_credential(&original_root).cloned(); let original_had_refs = yaml_mapping_value(&original_root, "refs").is_some(); let rendered = render_agent_yaml_mapping_update( Some(current), - "当前 DeepSeek Harness credentials", + "Current DeepSeek Harness credentials", |root| { let refs = - deepseek_harness_credentials_refs_mut(root, "当前 DeepSeek Harness credentials")?; + deepseek_harness_credentials_refs_mut(root, "Current DeepSeek Harness credentials")?; if let Some(value) = original_credential { refs.insert(yaml_key(DEEPSEEK_HARNESS_CREDENTIAL), value); } else { @@ -2570,7 +2570,7 @@ pub(crate) fn build_restored_deepseek_harness_credentials( Ok(()) }, )?; - let root = parse_agent_yaml_mapping(Some(&rendered), "恢复后的 DeepSeek Harness credentials")?; + let root = parse_agent_yaml_mapping(Some(&rendered), "Restored DeepSeek Harness credentials")?; if deepseek_harness_credentials_document_is_empty(&root) { if let Some(original) = original.filter(|_| original_root.is_empty()) { return Ok(Some(original.to_string())); @@ -2638,12 +2638,12 @@ pub(crate) fn build_agent_session_restored_bytes_with_preference( return Ok(original.map(ToOwned::to_owned)); } let current = std::str::from_utf8(current) - .map_err(|_| format!("当前智能体配置不是 UTF-8 文本: {}", path_to_string(path)))?; + .map_err(|_| format!("Current agent configuration is not UTF-8 text: {}", path_to_string(path)))?; let original_bytes = original; let original = original_bytes .map(|bytes| { std::str::from_utf8(bytes) - .map_err(|_| format!("原智能体配置不是 UTF-8 文本: {}", path_to_string(path))) + .map_err(|_| format!("Original agent configuration is not UTF-8 text: {}", path_to_string(path))) }) .transpose()?; let restored = match client { @@ -2652,7 +2652,7 @@ pub(crate) fn build_agent_session_restored_bytes_with_preference( let index = paths .iter() .position(|candidate| candidate == path) - .ok_or_else(|| "Claude Desktop 恢复路径不匹配".to_string())?; + .ok_or_else(|| "Claude Desktop restoration path mismatch".to_string())?; build_restored_claude_desktop_config(index, current, original)? } AgentClient::Codex => build_restored_codex_agent_config(Some(current), original)?, @@ -2663,11 +2663,11 @@ pub(crate) fn build_agent_session_restored_bytes_with_preference( let index = paths .iter() .position(|candidate| candidate == path) - .ok_or_else(|| "DeepSeek Harness 恢复路径不匹配".to_string())?; + .ok_or_else(|| "DeepSeek Harness restoration path mismatch".to_string())?; match index { 0 => build_restored_deepseek_harness_settings(current, original)?, 1 => build_restored_deepseek_harness_credentials(current, original)?, - _ => return Err("DeepSeek Harness 恢复路径索引无效".to_string()), + _ => return Err("Invalid DeepSeek Harness restoration path index".to_string()), } } AgentClient::ZCode => build_restored_zcode_config(current, original)?, @@ -2710,7 +2710,7 @@ pub(crate) fn build_codex_agent_config_with_oauth( let mut document = match existing.filter(|value| !value.trim().is_empty()) { Some(value) => value .parse::() - .map_err(|error| format!("Codex config.toml 格式无效: {error}"))?, + .map_err(|error| format!("Invalid Codex config.toml format: {error}"))?, None => Document::new(), }; set_codex_table_item( @@ -2733,7 +2733,7 @@ pub(crate) fn build_codex_agent_config_with_oauth( } let providers = document["model_providers"] .as_table_mut() - .ok_or_else(|| "Codex model_providers 必须是 TOML 表".to_string())?; + .ok_or_else(|| "Codex model_providers must be a TOML table".to_string())?; if !providers .get(MANAGED_AGENT_PROVIDER_ID) .is_some_and(toml_edit::Item::is_table) @@ -2744,7 +2744,7 @@ pub(crate) fn build_codex_agent_config_with_oauth( let provider = providers .get_mut(MANAGED_AGENT_PROVIDER_ID) .and_then(Item::as_table_mut) - .ok_or_else(|| "Codex cpa-gui provider 必须是 TOML 表".to_string())?; + .ok_or_else(|| "Codex cpa-gui provider must be a TOML table".to_string())?; set_codex_table_item(provider, "name", value("EasyCLIProxyAPI")); set_codex_table_item(provider, "base_url", value(base_url)); set_codex_table_item(provider, "wire_api", value("responses")); @@ -2756,7 +2756,7 @@ pub(crate) fn build_codex_agent_config_with_oauth( } let rendered = document.to_string(); toml::from_str::(&rendered) - .map_err(|error| format!("验证 Codex 配置失败: {error}"))?; + .map_err(|error| format!("Failed to validate Codex configuration: {error}"))?; Ok(rendered) } @@ -2765,7 +2765,7 @@ pub(crate) fn build_codex_api_auth(api_key: &str) -> Result { "auth_mode": "apikey", "OPENAI_API_KEY": api_key, })) - .map_err(|error| format!("生成 Codex auth.json 失败: {error}"))?; + .map_err(|error| format!("Failed to generate Codex auth.json: {error}"))?; rendered.push('\n'); Ok(rendered) } @@ -2779,7 +2779,7 @@ pub(crate) fn build_codex_auth_update( let after = if oauth_configuration { if validate_codex_oauth_login_at(&path).is_ok() { read_optional_text(&path)? - .ok_or_else(|| "Codex OAuth 登录凭据在应用配置前已被删除,请重新登录".to_string())? + .ok_or_else(|| "Codex OAuth login credentials were deleted before the application configuration was applied. Sign in again".to_string())? } else { available_codex_oauth_auth(home)? } @@ -2848,7 +2848,7 @@ pub(crate) fn parse_codex_document( match content.filter(|value| !value.trim().is_empty()) { Some(value) => value .parse::() - .map_err(|error| format!("{label} 格式无效: {error}")), + .map_err(|error| format!("Invalid {label} format: {error}")), None => Ok(Document::new()), } } @@ -2874,14 +2874,14 @@ pub(crate) fn ensure_codex_provider_table( let providers = root .get_mut("model_providers") .and_then(Item::as_table_mut) - .ok_or_else(|| "Codex model_providers 必须是 TOML 表".to_string())?; + .ok_or_else(|| "Codex model_providers must be a TOML table".to_string())?; if !providers.contains_key(MANAGED_AGENT_PROVIDER_ID) { providers.insert(MANAGED_AGENT_PROVIDER_ID, Item::Table(Table::new())); } providers .get_mut(MANAGED_AGENT_PROVIDER_ID) .and_then(Item::as_table_mut) - .ok_or_else(|| "Codex cpa-gui provider 必须是 TOML 表".to_string()) + .ok_or_else(|| "Codex cpa-gui provider must be a TOML table".to_string()) } pub(crate) fn merge_missing_codex_table_items( @@ -2916,9 +2916,9 @@ pub(crate) fn build_restored_codex_agent_config_with_policy( ) -> Result, String> { use toml_edit::Item; - let mut current_document = parse_codex_document(current, "当前 Codex config.toml")?; + let mut current_document = parse_codex_document(current, "Current Codex config.toml")?; let original_document = original - .map(|content| parse_codex_document(Some(content), "原始 Codex config.toml")) + .map(|content| parse_codex_document(Some(content), "Original Codex config.toml")) .transpose()?; if let Some(original_document) = original_document.as_ref() { merge_missing_codex_table_items( @@ -2995,7 +2995,7 @@ pub(crate) fn build_restored_codex_agent_config_with_policy( let rendered = current_document.to_string(); toml::from_str::(&rendered) - .map_err(|error| format!("验证恢复后的 Codex 配置失败: {error}"))?; + .map_err(|error| format!("Failed to validate restored Codex configuration: {error}"))?; if original.is_none() && rendered.trim().is_empty() { Ok(None) } else { @@ -3005,19 +3005,19 @@ pub(crate) fn build_restored_codex_agent_config_with_policy( pub(crate) fn validate_codex_catalog(catalog: &str, model: &str) -> Result<(), String> { let root: serde_json::Value = serde_json::from_str(catalog) - .map_err(|error| format!("Codex 模型目录格式无效: {error}"))?; + .map_err(|error| format!("Invalid Codex model catalog format: {error}"))?; let models = root .get("models") .and_then(serde_json::Value::as_array) .filter(|models| !models.is_empty()) - .ok_or_else(|| "Codex 模型目录必须包含非空 models 数组".to_string())?; + .ok_or_else(|| "Codex model catalog must contain a non-empty models array".to_string())?; if !models.iter().any(|entry| { entry .get("slug") .and_then(serde_json::Value::as_str) .is_some_and(|slug| slug.eq_ignore_ascii_case(model)) }) { - return Err(format!("默认模型 {model} 不在生成的 Codex 模型目录中")); + return Err(format!("Default model {model} is not in the generated Codex model catalog")); } Ok(()) } @@ -3054,7 +3054,7 @@ pub(crate) fn build_opencode_agent_config( "model".to_string(), serde_json::json!(format!("{MANAGED_AGENT_PROVIDER_ID}/{model}")), ); - let rendered = render_agent_json(root, "OpenCode 配置")?; + let rendered = render_agent_json(root, "OpenCode configuration")?; let comments = existing.map(extract_json5_comments).unwrap_or_default(); if comments.is_empty() { Ok(rendered) @@ -3130,7 +3130,7 @@ fn build_zcode_config( } else { root.insert("model".to_string(), serde_json::json!(model)); } - render_agent_json(root, "ZCode 配置") + render_agent_json(root, "ZCode configuration") } pub(crate) fn ensure_toml_child_table<'a>( @@ -3205,7 +3205,7 @@ pub(crate) fn build_kimi_code_agent_config( } let rendered = document.to_string(); toml::from_str::(&rendered) - .map_err(|error| format!("验证 Kimi Code 配置失败: {error}"))?; + .map_err(|error| format!("Failed to validate Kimi Code configuration: {error}"))?; Ok(rendered) } @@ -3251,7 +3251,7 @@ pub(crate) fn build_grok_build_agent_config( } let rendered = document.to_string(); toml::from_str::(&rendered) - .map_err(|error| format!("验证 Grok Build 配置失败: {error}"))?; + .map_err(|error| format!("Failed to validate Grok Build configuration: {error}"))?; Ok(rendered) } @@ -3264,12 +3264,12 @@ pub(crate) fn build_openclaw_agent_config( ) -> Result { let mut root = match existing.map(str::trim).filter(|value| !value.is_empty()) { Some(value) => json5::from_str::(value) - .map_err(|error| format!("OpenClaw openclaw.json 格式无效: {error}"))?, + .map_err(|error| format!("Invalid OpenClaw openclaw.json format: {error}"))?, None => serde_json::json!({}), }; let root = root .as_object_mut() - .ok_or_else(|| "OpenClaw openclaw.json 根节点必须是对象".to_string())?; + .ok_or_else(|| "OpenClaw openclaw.json root must be an object".to_string())?; let ordered_models = ordered_agent_models(available_models, model); let models = ensure_json_object_entry(root, "models"); models @@ -3313,7 +3313,7 @@ pub(crate) fn build_openclaw_agent_config( .unwrap_or_else(|| serde_json::json!({})); model_catalog.insert(name, value); } - let rendered = render_agent_json(root.clone(), "OpenClaw 配置")?; + let rendered = render_agent_json(root.clone(), "OpenClaw configuration")?; let comments = existing.map(extract_json5_comments).unwrap_or_default(); if comments.is_empty() { Ok(rendered) @@ -3414,13 +3414,13 @@ pub(crate) fn build_hermes_agent_config( ) -> Result { let original = match existing.map(str::trim).filter(|value| !value.is_empty()) { Some(value) => serde_norway::from_str::(value) - .map_err(|error| format!("Hermes config.yaml 格式无效: {error}"))?, + .map_err(|error| format!("Invalid Hermes config.yaml format: {error}"))?, None => serde_norway::Value::Mapping(serde_norway::Mapping::new()), }; let mut root = original.clone(); let mapping = root .as_mapping_mut() - .ok_or_else(|| "Hermes config.yaml 根节点必须是映射".to_string())?; + .ok_or_else(|| "Hermes config.yaml root must be a mapping".to_string())?; let providers = ensure_yaml_sequence_entry(mapping, "custom_providers"); let mut managed_provider = None; let mut retained_providers = Vec::with_capacity(providers.len()); @@ -3447,10 +3447,10 @@ pub(crate) fn build_hermes_agent_config( "model": model, "models": provider_models })) - .map_err(|error| format!("生成 Hermes provider 失败: {error}"))?; + .map_err(|error| format!("Failed to generate Hermes provider: {error}"))?; let canonical_provider = canonical_provider .as_mapping() - .ok_or_else(|| "生成 Hermes provider 失败: 根节点不是映射".to_string())?; + .ok_or_else(|| "Failed to generate Hermes provider: root is not a mapping".to_string())?; let mut managed_provider = managed_provider.unwrap_or_default(); for (key, value) in canonical_provider { managed_provider.insert(key.clone(), value.clone()); @@ -3468,11 +3468,11 @@ pub(crate) fn build_hermes_agent_config( ); let rendered = if let Some(existing) = existing.filter(|value| !value.trim().is_empty()) { render_yaml_value_changes(existing, &original, &root) - .or_else(|_| serde_norway::to_string(&root).map_err(|_| "生成 Hermes 配置失败".to_string()))? + .or_else(|_| serde_norway::to_string(&root).map_err(|_| "Failed to generate Hermes configuration".to_string()))? } else { - serde_norway::to_string(&root).map_err(|error| format!("生成 Hermes 配置失败: {error}"))? + serde_norway::to_string(&root).map_err(|error| format!("Failed to generate Hermes configuration: {error}"))? }; serde_norway::from_str::(&rendered) - .map_err(|error| format!("验证 Hermes 配置失败: {error}"))?; + .map_err(|error| format!("Failed to validate Hermes configuration: {error}"))?; Ok(rendered) } diff --git a/src-tauri/src/agents/deepseek_harness.rs b/src-tauri/src/agents/deepseek_harness.rs index 5b0614d..ea2d1d2 100644 --- a/src-tauri/src/agents/deepseek_harness.rs +++ b/src-tauri/src/agents/deepseek_harness.rs @@ -58,7 +58,7 @@ fn read_harness_state(bytes: Option<&[u8]>) -> Result) -> Result { existing, "DeepSeek Harness settings", )?) - .map_err(|_| "DSH settings 必须是有效的配置映射".into()) + .map_err(|_| "DSH settings must be a valid configuration mapping".into()) } fn selected_fields(value: &Value, group: &str) -> Profile { @@ -284,13 +284,13 @@ pub(crate) fn validate_harness_profile( let field = definitions .iter() .find(|field| field["name"] == key.as_str()) - .ok_or_else(|| format!("{path}.{key}: 不支持的配置字段"))?; + .ok_or_else(|| format!("{path}.{key}: unsupported configuration field"))?; validate_harness_field(value, field, api, &format!("{path}.{key}"))?; } for field in definitions { if field["required"] == true && !profile.contains_key(field["name"].as_str().unwrap()) { return Err(format!( - "{path}.{}: 必须填写", + "{path}.{}: required", field["name"].as_str().unwrap() )); } @@ -305,7 +305,7 @@ pub(crate) fn validate_harness_profile( .and_then(Value::as_f64) .unwrap_or(10000.0) { - return Err(format!("{path}: initialDelayMs 不能大于 maxDelayMs")); + return Err(format!("{path}: initialDelayMs cannot be greater than maxDelayMs")); } Ok(()) } @@ -316,14 +316,14 @@ fn validate_harness_field( api: &str, path: &str, ) -> Result<(), String> { - let invalid = || format!("{path}: 配置值不符合 DSH 文档要求"); + let invalid = || format!("{path}: configuration value does not meet DSH documentation requirements"); if !api.is_empty() && field .get("apis") .and_then(Value::as_array) .is_some_and(|apis| !apis.contains(&json!(api))) { - return Err(format!("{path}: 不适用于 {api},请恢复自动或选择对应协议")); + return Err(format!("{path}: not applicable to {api}; restore automatic selection or choose the matching protocol")); } let valid = match field["kind"].as_str().unwrap() { "group" => { @@ -407,7 +407,7 @@ fn render_harness_profiles( state: &mut DeepSeekHarnessCatalogState, ) -> Result { if models.is_empty() { - return Err("DSH 模型列表为空,已保留现有配置,请刷新后重试".into()); + return Err("DSH model list is empty. Existing configuration was preserved; refresh and try again".into()); } let root = harness_root(existing)?; let old = harness_provider(&root); @@ -452,7 +452,7 @@ fn render_harness_profiles( .and_then(|v| v.get_mut("providers")) .and_then(|v| v.get_mut(DEEPSEEK_HARNESS_PROVIDER_ID)) .and_then(serde_norway::Value::as_mapping_mut) - .ok_or("DSH provider 配置缺失")?; + .ok_or("DSH provider configuration is missing")?; managed.insert( yaml_key("baseURL"), serde_norway::Value::String( @@ -470,12 +470,12 @@ fn render_harness_profiles( for (key, value) in &provider { managed.insert( yaml_key(key), - serde_norway::to_value(value).map_err(|_| "序列化 DSH provider 失败")?, + serde_norway::to_value(value).map_err(|_| "Failed to serialize DSH provider")?, ); } managed.insert( yaml_key("models"), - serde_norway::to_value(&published).map_err(|_| "序列化 DSH 模型失败")?, + serde_norway::to_value(&published).map_err(|_| "Failed to serialize DSH model")?, ); Ok(()) })?; @@ -486,7 +486,7 @@ fn render_harness_profiles( pub(crate) fn deepseek_harness_template_catalog_state(images: &Images) -> Result, String> { let root = harness_root(text( - images.first().ok_or("缺少 DSH settings")?.1.as_deref(), + images.first().ok_or("DSH settings are missing")?.1.as_deref(), )?)?; let mut state = DeepSeekHarnessCatalogState::default(); if let Some(provider) = harness_provider(&root) { @@ -501,7 +501,7 @@ pub(crate) fn deepseek_harness_template_catalog_state(images: &Images) -> Result } } } - serde_json::to_vec(&state).map_err(|_| "生成 DSH 模型配置状态失败".into()) + serde_json::to_vec(&state).map_err(|_| "Failed to generate DSH model configuration state".into()) } pub(crate) fn build_deepseek_harness_catalog_settings( @@ -559,7 +559,7 @@ pub(crate) fn prepare_deepseek_harness_configuration( )?; after.push(( paths[2].clone(), - Some(serde_json::to_vec(&state).map_err(|_| "生成 DSH 模型配置状态失败")?), + Some(serde_json::to_vec(&state).map_err(|_| "Failed to generate DSH model configuration state")?), )); Ok((paths, before, after)) } @@ -607,7 +607,7 @@ fn harness_editor_from_images( let base_url = format!("{}/v1", managed_core_loopback_origin(port)); let revision = sha256_bytes( &serde_json::to_vec(&(image_revision(before), &models, &base_url)) - .map_err(|_| "生成 DSH 配置版本失败")?, + .map_err(|_| "Failed to generate DSH configuration version")?, ); Ok(( HarnessEditorSnapshot { @@ -648,15 +648,15 @@ pub(crate) fn save_harness_editor( let before = config_images(&paths)?; let (snapshot, mut state) = harness_editor_from_images(&before, models, port)?; if request.revision != snapshot.revision { - return Err("DSH_MODEL_CATALOG_CHANGED: 模型列表或配置已变化,请重新加载".into()); + return Err("DSH_MODEL_CATALOG_CHANGED: Model list or configuration has changed. Reload it".into()); } if request.models.len() != models.len() { - return Err("DSH 模型列表不完整,请重新加载".into()); + return Err("DSH model list is incomplete. Reload it".into()); } let mut seen = std::collections::HashSet::new(); for model in request.models { if !seen.insert(model.id.clone()) || !models.iter().any(|m| m.name == model.id) { - return Err("DSH 模型列表包含重复或不可用的 ID".into()); + return Err("DSH model list contains duplicate or unavailable IDs".into()); } state.models.insert(model.id, model.configuration); } @@ -685,7 +685,7 @@ pub(crate) fn save_harness_editor( .as_ref() .is_some_and(|id| !models.iter().any(|m| &m.name == id)) { - return Err("当前默认模型已不可用,请先更新默认模型".into()); + return Err("The current default model is no longer available. Update the default model first".into()); } let settings = render_harness_profiles( text(before[0].1.as_deref())?, @@ -707,7 +707,7 @@ pub(crate) fn save_harness_editor( after[0] = prepared[0].clone(); } state.models.retain(|_, profile| !profile.is_empty()); - after[2].1 = Some(serde_json::to_vec(&state).map_err(|_| "生成 DSH 模型配置状态失败")?); + after[2].1 = Some(serde_json::to_vec(&state).map_err(|_| "Failed to generate DSH model configuration state")?); commit_config( "deepseek-harness", &paths, @@ -726,10 +726,10 @@ pub(crate) async fn get_deepseek_harness_model_catalog_editor( ) -> Result { let config = gui_config_state.snapshot()?; let models = fetch_deepseek_harness_models(&config).await?; - let home = app.path().home_dir().map_err(|_| "无法获取用户目录")?; + let home = app.path().home_dir().map_err(|_| "Failed to get user directory")?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "智能体配置文件锁已损坏")?; + .map_err(|_| "Agent configuration file lock is poisoned")?; harness_editor_snapshot(&home, &models, config.port) } @@ -741,9 +741,9 @@ pub(crate) async fn save_deepseek_harness_model_catalog_editor( ) -> Result { let config = gui_config_state.snapshot()?; let models = fetch_deepseek_harness_models(&config).await?; - let home = app.path().home_dir().map_err(|_| "无法获取用户目录")?; + let home = app.path().home_dir().map_err(|_| "Failed to get user directory")?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "智能体配置文件锁已损坏")?; + .map_err(|_| "Agent configuration file lock is poisoned")?; save_harness_editor(&home, &models, config.port, request) } diff --git a/src-tauri/src/agents/discovery.rs b/src-tauri/src/agents/discovery.rs index a60ef14..298014e 100644 --- a/src-tauri/src/agents/discovery.rs +++ b/src-tauri/src/agents/discovery.rs @@ -159,13 +159,13 @@ pub(crate) fn read_pi_provider_version(home: &Path) -> Result, St } let content = fs::read_to_string(&path).map_err(|error| { format!( - "读取 Pi provider package.json 失败 {}: {error}", + "Failed to read Pi provider package.json {}: {error}", path_to_string(&path) ) })?; let package = serde_json::from_str::(&content).map_err(|error| { format!( - "解析 Pi provider package.json 失败 {}: {error}", + "Failed to parse Pi provider package.json {}: {error}", path_to_string(&path) ) })?; @@ -186,7 +186,7 @@ pub(crate) fn parse_pi_provider_latest_version( .map(str::trim) .filter(|value| !value.is_empty()) .map(str::to_string) - .ok_or_else(|| "npm registry 返回的 Pi provider 版本无效".to_string()) + .ok_or_else(|| "Invalid Pi provider version returned by npm registry".to_string()) } pub(crate) async fn fetch_pi_provider_latest_version(proxy_url: &str) -> Result { @@ -195,7 +195,7 @@ pub(crate) async fn fetch_pi_provider_latest_version(proxy_url: &str) -> Result< .connect_timeout(Duration::from_secs(5)) .timeout(Duration::from_secs(12)), proxy_url, - "创建 Pi provider 更新检测客户端失败", + "Failed to create Pi provider update check client", )?; let response = client .get(PI_CLIPROXYAPI_NPM_LATEST_URL) @@ -203,25 +203,25 @@ pub(crate) async fn fetch_pi_provider_latest_version(proxy_url: &str) -> Result< .header(reqwest::header::USER_AGENT, APP_USER_AGENT) .send() .await - .map_err(|error| format!("查询 Pi provider 最新版本失败: {error}"))?; + .map_err(|error| format!("Failed to query latest Pi provider version: {error}"))?; let status = response.status(); if !status.is_success() { return Err(format!( - "查询 Pi provider 最新版本失败: HTTP {}", + "Failed to query latest Pi provider version: HTTP {}", status.as_u16() )); } let payload = response .json::() .await - .map_err(|error| format!("解析 Pi provider 最新版本失败: {error}"))?; + .map_err(|error| format!("Failed to parse latest Pi provider version: {error}"))?; parse_pi_provider_latest_version(&payload) } pub(crate) fn pi_provider_update_available(installed: &str, latest: &str) -> Result { let parse = |value: &str| { semver::Version::parse(value.trim().trim_start_matches('v')) - .map_err(|error| format!("无法解析 Pi provider 版本号 {value}: {error}")) + .map_err(|error| format!("Failed to parse Pi provider version {value}: {error}")) }; Ok(parse(latest)? > parse(installed)?) } @@ -238,7 +238,7 @@ pub(crate) fn read_pi_settings(home: &Path) -> Result, } let content = fs::read_to_string(&path).map_err(|error| { format!( - "读取 Pi settings.json 失败 {}: {error}", + "Failed to read Pi settings.json {}: {error}", path_to_string(&path) ) })?; @@ -246,7 +246,7 @@ pub(crate) fn read_pi_settings(home: &Path) -> Result, .map(Some) .map_err(|error| { format!( - "解析 Pi settings.json 失败 {}: {error}", + "Failed to parse Pi settings.json {}: {error}", path_to_string(&path) ) }) @@ -280,16 +280,16 @@ pub(crate) fn build_pi_provider_config( ) -> Result { let mut root = match existing.map(str::trim).filter(|value| !value.is_empty()) { Some(value) => serde_json::from_str::(value) - .map_err(|error| format!("解析 Pi CLIProxyAPI 配置失败: {error}"))?, + .map_err(|error| format!("Failed to parse Pi CLIProxyAPI configuration: {error}"))?, None => serde_json::json!({}), }; let object = root .as_object_mut() - .ok_or_else(|| "Pi CLIProxyAPI 配置根节点必须是 JSON 对象".to_string())?; + .ok_or_else(|| "Pi CLIProxyAPI configuration root must be a JSON object".to_string())?; object.insert("baseUrl".to_string(), serde_json::json!(base_url)); object.insert("apiKey".to_string(), serde_json::json!(api_key)); let mut rendered = serde_json::to_string_pretty(&root) - .map_err(|error| format!("生成 Pi CLIProxyAPI 配置失败: {error}"))?; + .map_err(|error| format!("Failed to generate Pi CLIProxyAPI configuration: {error}"))?; rendered.push('\n'); Ok(rendered) } @@ -300,18 +300,18 @@ pub(crate) fn build_pi_provider_settings( ) -> Result { let default_model = default_model.trim(); if default_model.is_empty() { - return Err("Pi 默认模型不能为空".to_string()); + return Err("Pi default model cannot be empty".to_string()); } let mut root = serde_json::from_str::(existing) - .map_err(|error| format!("解析 Pi settings.json 失败: {error}"))?; + .map_err(|error| format!("Failed to parse Pi settings.json: {error}"))?; let object = root .as_object_mut() - .ok_or_else(|| "Pi settings.json 根节点必须是 JSON 对象".to_string())?; + .ok_or_else(|| "Pi settings.json root must be a JSON object".to_string())?; let packages = object .entry("packages") .or_insert_with(|| serde_json::json!([])) .as_array_mut() - .ok_or_else(|| "Pi settings.json packages 必须是数组".to_string())?; + .ok_or_else(|| "Pi settings.json packages must be an array".to_string())?; if !packages.iter().any(|package| { package.as_str().is_some_and(pi_package_source_matches) || package @@ -327,7 +327,7 @@ pub(crate) fn build_pi_provider_settings( ); object.insert("defaultModel".to_string(), serde_json::json!(default_model)); let mut rendered = serde_json::to_string_pretty(&root) - .map_err(|error| format!("生成 Pi settings.json 失败: {error}"))?; + .map_err(|error| format!("Failed to generate Pi settings.json: {error}"))?; rendered.push('\n'); Ok(rendered) } @@ -365,13 +365,13 @@ pub(crate) fn inspect_pi_provider_status( let mut credentials_match = false; if config_path.is_file() { match fs::read_to_string(&config_path) - .map_err(|error| format!("读取 Pi CLIProxyAPI 配置失败: {error}")) + .map_err(|error| format!("Failed to read Pi CLIProxyAPI configuration: {error}")) .and_then(|content| { let root = serde_json::from_str::(&content) - .map_err(|error| format!("解析 Pi CLIProxyAPI 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse Pi CLIProxyAPI configuration: {error}"))?; let object = root .as_object() - .ok_or_else(|| "Pi CLIProxyAPI 配置根节点必须是 JSON 对象".to_string())?; + .ok_or_else(|| "Pi CLIProxyAPI configuration root must be a JSON object".to_string())?; let expected_base_url = managed_core_loopback_origin(port); credentials_match = object .get("baseUrl") @@ -393,19 +393,19 @@ pub(crate) fn inspect_pi_provider_status( && config_valid; let mut warnings = Vec::new(); if executable.is_some() && !plugin_installed && config_valid { - warnings.push("Pi CLIProxyAPI provider 插件尚未安装".to_string()); + warnings.push("Pi CLIProxyAPI provider plugin is not installed".to_string()); } else if plugin_installed && (!credentials_match || !default_provider_matches || current_model.is_none()) && config_valid && config_exists { warnings.push( - "Pi 插件配置不完整,请使用“应用配置”重新写入凭据、默认 provider 和默认模型".to_string(), + "Pi plugin configuration is incomplete. Use “Apply Configuration” to rewrite the credentials, default provider, and default model".to_string(), ); } if executable.is_none() && config_exists { warnings.push( - "只检测到 Pi 配置文件,仍可编辑配置并接入 CPA;启动功能不可用".to_string(), + "Only a Pi configuration file was detected. You can still edit the configuration and connect it to CPA, but launch functionality is unavailable".to_string(), ); } let modification_state = if configured { @@ -472,10 +472,10 @@ pub(crate) fn install_pi_provider_inner( proxy_url: &str, ) -> Result { if port == 0 { - return Err("内核端口无效".to_string()); + return Err("Invalid kernel port".to_string()); } if api_key.trim().is_empty() { - return Err("EasyCLIProxyAPI 没有可用的 API key".to_string()); + return Err("EasyCLIProxyAPI has no available API key".to_string()); } let settings_path = pi_provider_settings_path(home); let mut changed_files = Vec::new(); @@ -499,17 +499,17 @@ pub(crate) fn configure_pi_provider_without_cli_inner( default_model: &str, ) -> Result { if port == 0 { - return Err("内核端口无效".to_string()); + return Err("Invalid kernel port".to_string()); } if api_key.trim().is_empty() { - return Err("EasyCLIProxyAPI 没有可用的 API key".to_string()); + return Err("EasyCLIProxyAPI has no available API key".to_string()); } let settings_path = pi_provider_settings_path(home); config_package_operation(home, "plugin-config", || { let settings = if settings_path.is_file() { fs::read_to_string(&settings_path).map_err(|error| { format!( - "读取 Pi settings.json 失败 {}: {error}", + "Failed to read Pi settings.json {}: {error}", path_to_string(&settings_path) ) })? @@ -538,7 +538,7 @@ pub(crate) fn repair_pi_provider_inner( let settings_path = pi_provider_settings_path(home); let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "配置文件锁已损坏")?; + .map_err(|_| "Configuration file lock is poisoned")?; let before = config_images(&config_paths(PI_AGENT_ID, home)?)?; validate_config_images(&before)?; if !pi_provider_package_installed(home)? { @@ -547,7 +547,7 @@ pub(crate) fn repair_pi_provider_inner( let existing = if config_path.is_file() { Some(fs::read_to_string(&config_path).map_err(|error| { format!( - "读取 Pi CLIProxyAPI 配置失败 {}: {error}", + "Failed to read Pi CLIProxyAPI configuration {}: {error}", path_to_string(&config_path) ) })?) @@ -558,7 +558,7 @@ pub(crate) fn repair_pi_provider_inner( let rendered = build_pi_provider_config(existing.as_deref(), &base_url, api_key)?; let settings = fs::read_to_string(&settings_path).map_err(|error| { format!( - "读取 Pi settings.json 失败 {}: {error}", + "Failed to read Pi settings.json {}: {error}", path_to_string(&settings_path) ) })?; @@ -592,7 +592,7 @@ pub(crate) fn update_pi_provider_inner( proxy_url: &str, ) -> Result { if !pi_provider_package_installed(home)? { - return Err("Pi CLIProxyAPI provider 插件尚未安装".to_string()); + return Err("Pi CLIProxyAPI provider plugin is not installed".to_string()); } config_package_operation(home, "plugin-update", || { update_pi_package(executable, home, proxy_url) @@ -640,7 +640,7 @@ pub(crate) fn agent_command_path( directories.insert(0, executable_directory.to_path_buf()); } - env::join_paths(directories).map_err(|error| format!("构造智能体命令 PATH 失败: {error}")) + env::join_paths(directories).map_err(|error| format!("Failed to construct agent command PATH: {error}")) } pub(crate) fn configure_agent_command_environment( @@ -672,7 +672,7 @@ pub(crate) fn install_pi_package( configure_agent_command_environment(&mut command, home, executable)?; let output = command .output() - .map_err(|error| format!("执行 Pi 插件安装失败: {error}"))?; + .map_err(|error| format!("Failed to run Pi plugin installation: {error}"))?; if output.status.success() { return Ok(()); } @@ -680,7 +680,7 @@ pub(crate) fn install_pi_package( let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); let detail = if !stderr.is_empty() { stderr } else { stdout }; Err(format!( - "Pi CLIProxyAPI provider 插件安装失败{}", + "Pi CLIProxyAPI provider plugin installation failed{}", if detail.is_empty() { String::new() } else { @@ -710,7 +710,7 @@ pub(crate) fn update_pi_package( configure_agent_command_environment(&mut command, home, executable)?; let output = command .output() - .map_err(|error| format!("执行 Pi 插件更新失败: {error}"))?; + .map_err(|error| format!("Failed to run Pi plugin update: {error}"))?; if output.status.success() { return Ok(()); } @@ -718,7 +718,7 @@ pub(crate) fn update_pi_package( let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); let detail = if !stderr.is_empty() { stderr } else { stdout }; Err(format!( - "Pi CLIProxyAPI provider 插件更新失败{}", + "Pi CLIProxyAPI provider plugin update failed{}", if detail.is_empty() { String::new() } else { @@ -742,7 +742,7 @@ pub(crate) fn remove_pi_package(executable: &Path, home: &Path) -> Result<(), St configure_agent_command_environment(&mut command, home, executable)?; let output = command .output() - .map_err(|error| format!("鎵ц Pi 鎻掍欢鍗歌浇澶辫触: {error}"))?; + .map_err(|error| format!("Failed to run Pi plugin uninstall: {error}"))?; if output.status.success() { return Ok(()); } @@ -750,7 +750,7 @@ pub(crate) fn remove_pi_package(executable: &Path, home: &Path) -> Result<(), St let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); let detail = if !stderr.is_empty() { stderr } else { stdout }; Err(format!( - "Pi CLIProxyAPI provider 鎻掍欢鍗歌浇澶辫触{}", + "Pi CLIProxyAPI provider plugin uninstall failed{}", if detail.is_empty() { String::new() } else { @@ -769,9 +769,9 @@ pub(crate) fn current_codex_oauth_configuration(home: &Path) -> Result { if paths.len() != 2 { - return Err("ZCode 配置路径数量无效".to_string()); + return Err("Invalid number of ZCode configuration paths".to_string()); } let (app_configured, app_model) = inspect_zcode_agent_config(&paths[0], port, api_key)?; let (cli_configured, cli_model) = inspect_zcode_agent_config(&paths[1], port, api_key)?; @@ -1415,7 +1415,7 @@ pub(crate) fn agent_has_managed_marker( if !paths[0].is_file() { return Ok(false); } - let root = read_agent_json_or_empty(&paths[0], "Claude Code 配置")?; + let root = read_agent_json_or_empty(&paths[0], "Claude Code configuration")?; let env = root.get("env"); Ok(env .and_then(|value| value.get("ANTHROPIC_BASE_URL")) @@ -1426,8 +1426,8 @@ pub(crate) fn agent_has_managed_marker( if paths.len() != 4 { return Ok(false); } - let meta = read_agent_json_or_empty(&paths[3], "Claude Desktop 配置索引")?; - let profile = read_agent_json_or_empty(&paths[2], "Claude Desktop 网关配置")?; + let meta = read_agent_json_or_empty(&paths[3], "Claude Desktop configuration index")?; + let profile = read_agent_json_or_empty(&paths[2], "Claude Desktop gateway configuration")?; Ok(meta.get("appliedId").and_then(serde_json::Value::as_str) == Some(CLAUDE_DESKTOP_PROFILE_ID) || meta @@ -1450,9 +1450,9 @@ pub(crate) fn agent_has_managed_marker( } let root: toml::Value = toml::from_str( &fs::read_to_string(&paths[0]) - .map_err(|error| format!("读取 Codex 配置失败: {error}"))?, + .map_err(|error| format!("Failed to read Codex configuration: {error}"))?, ) - .map_err(|error| format!("解析 Codex 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse Codex configuration: {error}"))?; Ok(root.get("model_provider").and_then(toml::Value::as_str) == Some(MANAGED_AGENT_PROVIDER_ID)) } @@ -1460,7 +1460,7 @@ pub(crate) fn agent_has_managed_marker( if !paths[0].is_file() { return Ok(false); } - let root = read_agent_json5_or_empty(&paths[0], "OpenCode 配置")?; + let root = read_agent_json5_or_empty(&paths[0], "OpenCode configuration")?; let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/"); let provider_exists = root .get("provider") @@ -1478,9 +1478,9 @@ pub(crate) fn agent_has_managed_marker( } let root: serde_json::Value = json5::from_str( &fs::read_to_string(&paths[0]) - .map_err(|error| format!("读取 OpenClaw 配置失败: {error}"))?, + .map_err(|error| format!("Failed to read OpenClaw configuration: {error}"))?, ) - .map_err(|error| format!("解析 OpenClaw 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse OpenClaw configuration: {error}"))?; let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/"); let provider_exists = root .get("models") @@ -1502,9 +1502,9 @@ pub(crate) fn agent_has_managed_marker( } let root: serde_yaml::Value = serde_yaml::from_str( &fs::read_to_string(&paths[0]) - .map_err(|error| format!("读取 Hermes 配置失败: {error}"))?, + .map_err(|error| format!("Failed to read Hermes configuration: {error}"))?, ) - .map_err(|error| format!("解析 Hermes 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse Hermes configuration: {error}"))?; let provider_exists = root .get("custom_providers") .and_then(serde_yaml::Value::as_sequence) @@ -1530,7 +1530,7 @@ pub(crate) fn agent_has_managed_marker( if !path.is_file() { continue; } - let root = read_agent_json_or_empty(path, "ZCode 配置")?; + let root = read_agent_json_or_empty(path, "ZCode configuration")?; let provider_exists = root .get("provider") .and_then(|value| value.get(MANAGED_AGENT_PROVIDER_ID)) @@ -1551,7 +1551,7 @@ pub(crate) fn agent_has_managed_marker( } AgentClient::KimiCode => inspect_managed_toml_model_marker( &paths[0], - "Kimi Code 配置", + "Kimi Code configuration", Some("providers"), "models", None, @@ -1559,7 +1559,7 @@ pub(crate) fn agent_has_managed_marker( ), AgentClient::GrokBuild => inspect_managed_toml_model_marker( &paths[0], - "Grok Build 配置", + "Grok Build configuration", None, "model", Some("models"), @@ -1609,9 +1609,9 @@ pub(crate) fn inspect_managed_toml_model_marker( return Ok(false); } let root: toml::Value = toml::from_str( - &fs::read_to_string(path).map_err(|error| format!("读取 {label} 失败: {error}"))?, + &fs::read_to_string(path).map_err(|error| format!("Failed to read {label}: {error}"))?, ) - .map_err(|error| format!("解析 {label} 失败: {error}"))?; + .map_err(|error| format!("Failed to parse {label}: {error}"))?; let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/"); let selected = (if let Some(section) = default_section { root.get(section) @@ -2677,11 +2677,11 @@ pub(crate) fn command_output_with_timeout( let mut stdout = child .stdout .take() - .ok_or_else(|| io::Error::other("无法读取智能体探测标准输出"))?; + .ok_or_else(|| io::Error::other("Unable to read agent detection standard output"))?; let mut stderr = child .stderr .take() - .ok_or_else(|| io::Error::other("无法读取智能体探测错误输出"))?; + .ok_or_else(|| io::Error::other("Unable to read agent detection error output"))?; thread::scope(|scope| { let stdout_reader = scope.spawn(move || { let mut output = Vec::new(); @@ -2704,10 +2704,10 @@ pub(crate) fn command_output_with_timeout( }; let stdout = stdout_reader .join() - .map_err(|_| io::Error::other("智能体探测输出线程异常退出"))??; + .map_err(|_| io::Error::other("Agent detection output thread exited unexpectedly"))??; let stderr = stderr_reader .join() - .map_err(|_| io::Error::other("智能体探测错误线程异常退出"))??; + .map_err(|_| io::Error::other("Agent detection error thread exited unexpectedly"))??; Ok(status.map(|status| std::process::Output { status, stdout, @@ -2773,9 +2773,9 @@ pub(crate) fn inspect_claude_agent_config( return Ok((false, None)); } let root: serde_json::Value = serde_json::from_str( - &fs::read_to_string(path).map_err(|error| format!("读取 Claude Code 配置失败: {error}"))?, + &fs::read_to_string(path).map_err(|error| format!("Failed to read Claude Code configuration: {error}"))?, ) - .map_err(|error| format!("解析 Claude Code 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse Claude Code configuration: {error}"))?; let env = root.get("env").and_then(serde_json::Value::as_object); let expected_base = managed_core_loopback_origin(port); let configured = env @@ -3097,10 +3097,10 @@ pub(crate) fn inspect_claude_desktop_agent_config( if paths.len() != 4 || !paths.iter().any(|path| path.is_file()) { return Ok((false, None)); } - let normal = read_agent_json_or_empty(&paths[0], "Claude Desktop 主配置")?; - let threep = read_agent_json_or_empty(&paths[1], "Claude Desktop 3P 配置")?; - let profile = read_agent_json_or_empty(&paths[2], "Claude Desktop 网关配置")?; - let meta = read_agent_json_or_empty(&paths[3], "Claude Desktop 配置索引")?; + let normal = read_agent_json_or_empty(&paths[0], "Claude Desktop main configuration")?; + let threep = read_agent_json_or_empty(&paths[1], "Claude Desktop 3P configuration")?; + let profile = read_agent_json_or_empty(&paths[2], "Claude Desktop gateway configuration")?; + let meta = read_agent_json_or_empty(&paths[3], "Claude Desktop configuration index")?; let expected_base = managed_core_loopback_origin(port); let configured = normal .get("deploymentMode") @@ -3148,14 +3148,14 @@ pub(crate) fn read_agent_json_or_empty( return Ok(serde_json::json!({})); } let content = - fs::read_to_string(path).map_err(|error| format!("读取 {label} 失败: {error}"))?; + fs::read_to_string(path).map_err(|error| format!("Failed to read {label}: {error}"))?; if content.trim().is_empty() { return Ok(serde_json::json!({})); } let value: serde_json::Value = - serde_json::from_str(&content).map_err(|error| format!("解析 {label} 失败: {error}"))?; + serde_json::from_str(&content).map_err(|error| format!("Failed to parse {label}: {error}"))?; if !value.is_object() { - return Err(format!("{label} 根节点必须是对象")); + return Err(format!("{label} root must be an object")); } Ok(value) } @@ -3168,14 +3168,14 @@ pub(crate) fn read_agent_json5_or_empty( return Ok(serde_json::json!({})); } let content = - fs::read_to_string(path).map_err(|error| format!("读取 {label} 失败: {error}"))?; + fs::read_to_string(path).map_err(|error| format!("Failed to read {label}: {error}"))?; if content.trim().is_empty() { return Ok(serde_json::json!({})); } let value: serde_json::Value = - json5::from_str(&content).map_err(|error| format!("解析 {label} 失败: {error}"))?; + json5::from_str(&content).map_err(|error| format!("Failed to parse {label}: {error}"))?; if !value.is_object() { - return Err(format!("{label} 根节点必须是对象")); + return Err(format!("{label} root must be an object")); } Ok(value) } @@ -3189,9 +3189,9 @@ pub(crate) fn inspect_codex_agent_config( return Ok((false, None, false)); } let root: toml::Value = toml::from_str( - &fs::read_to_string(path).map_err(|error| format!("读取 Codex 配置失败: {error}"))?, + &fs::read_to_string(path).map_err(|error| format!("Failed to read Codex configuration: {error}"))?, ) - .map_err(|error| format!("解析 Codex 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse Codex configuration: {error}"))?; let expected_base = format!("{}/v1", managed_core_loopback_origin(port)); let provider = root .get("model_providers") @@ -3246,7 +3246,7 @@ pub(crate) fn validate_codex_catalog_file(config_path: &Path, model: &str) -> Re .join(CODEX_MODEL_CATALOG_FILE); let catalog = fs::read_to_string(&catalog_path).map_err(|error| { format!( - "读取 Codex 模型目录失败 {}: {error}", + "Failed to read Codex model catalog {}: {error}", path_to_string(&catalog_path) ) })?; @@ -3261,7 +3261,7 @@ pub(crate) fn inspect_opencode_agent_config( if !path.is_file() { return Ok((false, None)); } - let root = read_agent_json5_or_empty(path, "OpenCode 配置")?; + let root = read_agent_json5_or_empty(path, "OpenCode configuration")?; let provider = root .get("provider") .and_then(|providers| providers.get(MANAGED_AGENT_PROVIDER_ID)); @@ -3296,9 +3296,9 @@ pub(crate) fn inspect_zcode_agent_config( return Ok((false, None)); } let root: serde_json::Value = serde_json::from_str( - &fs::read_to_string(path).map_err(|error| format!("读取 ZCode 配置失败: {error}"))?, + &fs::read_to_string(path).map_err(|error| format!("Failed to read ZCode configuration: {error}"))?, ) - .map_err(|error| format!("解析 ZCode 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse ZCode configuration: {error}"))?; let provider = root .get("provider") .and_then(|providers| providers.get(MANAGED_AGENT_PROVIDER_ID)); @@ -3361,9 +3361,9 @@ pub(crate) fn inspect_kimi_code_agent_config( return Ok((false, None)); } let root: toml::Value = toml::from_str( - &fs::read_to_string(path).map_err(|error| format!("读取 Kimi Code 配置失败: {error}"))?, + &fs::read_to_string(path).map_err(|error| format!("Failed to read Kimi Code configuration: {error}"))?, ) - .map_err(|error| format!("解析 Kimi Code 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse Kimi Code configuration: {error}"))?; let expected_base = format!("{}/v1", managed_core_loopback_origin(port)); let provider = root .get("providers") @@ -3418,9 +3418,9 @@ pub(crate) fn inspect_grok_build_agent_config( return Ok((false, None)); } let root: toml::Value = toml::from_str( - &fs::read_to_string(path).map_err(|error| format!("读取 Grok Build 配置失败: {error}"))?, + &fs::read_to_string(path).map_err(|error| format!("Failed to read Grok Build configuration: {error}"))?, ) - .map_err(|error| format!("解析 Grok Build 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse Grok Build configuration: {error}"))?; let expected_base = format!("{}/v1", managed_core_loopback_origin(port)); let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/"); let selected = root @@ -3468,9 +3468,9 @@ pub(crate) fn inspect_openclaw_agent_config( return Ok((false, None)); } let content = - fs::read_to_string(path).map_err(|error| format!("读取 OpenClaw 配置失败: {error}"))?; + fs::read_to_string(path).map_err(|error| format!("Failed to read OpenClaw configuration: {error}"))?; let root: serde_json::Value = json5::from_str(&content) - .map_err(|error| format!("解析 OpenClaw JSON5 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse OpenClaw JSON5 configuration: {error}"))?; let provider = root .get("models") .and_then(|models| models.get("providers")) @@ -3507,9 +3507,9 @@ pub(crate) fn inspect_hermes_agent_config( return Ok((false, None)); } let root: serde_yaml::Value = serde_yaml::from_str( - &fs::read_to_string(path).map_err(|error| format!("读取 Hermes 配置失败: {error}"))?, + &fs::read_to_string(path).map_err(|error| format!("Failed to read Hermes configuration: {error}"))?, ) - .map_err(|error| format!("解析 Hermes YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse Hermes YAML configuration: {error}"))?; let provider = root .get("custom_providers") .and_then(serde_yaml::Value::as_sequence) @@ -3550,7 +3550,7 @@ pub(crate) fn inspect_deepseek_harness_config( api_key: &str, ) -> Result<(bool, Option), String> { if paths.len() != 2 { - return Err("DeepSeek Harness 配置路径数量无效".to_string()); + return Err("Invalid number of DeepSeek Harness configuration paths".to_string()); } let settings = read_agent_yaml_mapping_or_empty(&paths[0], "DeepSeek Harness settings")?; let credentials = read_agent_yaml_mapping_or_empty(&paths[1], "DeepSeek Harness credentials")?; @@ -3644,7 +3644,7 @@ fn deepseek_harness_credentials_layout_supported(credentials: &serde_norway::Map pub(crate) fn deepseek_harness_has_managed_marker(paths: &[PathBuf]) -> Result { if paths.len() != 2 { - return Err("DeepSeek Harness 配置路径数量无效".to_string()); + return Err("Invalid number of DeepSeek Harness configuration paths".to_string()); } let settings = read_agent_yaml_mapping_or_empty(&paths[0], "DeepSeek Harness settings")?; let credentials = read_agent_yaml_mapping_or_empty(&paths[1], "DeepSeek Harness credentials")?; diff --git a/src-tauri/src/agents/launch.rs b/src-tauri/src/agents/launch.rs index 831febc..7704ff5 100644 --- a/src-tauri/src/agents/launch.rs +++ b/src-tauri/src/agents/launch.rs @@ -131,7 +131,7 @@ pub(crate) fn launch_agent( let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let config = gui_config_state.snapshot()?; let terminal = normalize_agent_terminal(&config.default_terminal); let requested_target = target @@ -140,20 +140,20 @@ pub(crate) fn launch_agent( .filter(|value| !value.is_empty()); if deepseek_harness_options.is_some() && !client.trim().eq_ignore_ascii_case("deepseek-harness") { - return Err("DeepSeek Harness 启动选项不能用于其他客户端".to_string()); + return Err("DeepSeek Harness launch options cannot be used with other clients".to_string()); } if client.trim().eq_ignore_ascii_case(PI_AGENT_ID) { if requested_target.is_some_and(|value| value != "cli") { - return Err("Pi 只支持 CLI 启动方式".to_string()); + return Err("Pi supports only CLI launch mode".to_string()); } let status = inspect_pi_provider_status(&home, config.port, effective_agent_api_key(&config)); if !status.installed { - return Err("未检测到 Pi CLI,请先安装 Pi 并重新检测".to_string()); + return Err("Pi CLI was not detected. Install Pi and detect it again".to_string()); } let executable = - find_pi_executable(&home).ok_or_else(|| "未找到 Pi CLI 可执行文件".to_string())?; + find_pi_executable(&home).ok_or_else(|| "Pi CLI executable not found".to_string())?; let launch_directory = resolve_launch_directory(working_directory.as_deref(), &home)?; return launch_cli_agent( &executable, @@ -167,11 +167,11 @@ pub(crate) fn launch_agent( let client = AgentClient::parse(&client)?; if !client.supported_platform() { - return Err(format!("当前平台不支持启动 {}", client.name())); + return Err(format!("The current platform does not support launching {}", client.name())); } let status = inspect_agent_config(client, &home, config.port, effective_agent_api_key(&config)); if !status.installed { - return Err(format!("未检测到 {},请先安装并重新检测", client.name())); + return Err(format!("{} was not detected. Install it and detect it again", client.name())); } let default_target = status .launch_targets @@ -184,30 +184,30 @@ pub(crate) fn launch_agent( (AgentClient::AntigravityCli, "cli") if antigravity_has_marker(client, &agent_config_paths(client, &home))? => { if !status.configured { - return Err("Antigravity CLI 配置与 CPA 不一致,请重新应用配置".into()); + return Err("Antigravity CLI configuration does not match CPA. Apply the configuration again".into()); } - let executable = env::current_exe().map_err(|_| "无法定位 CPA 启动适配程序")?; + let executable = env::current_exe().map_err(|_| "Unable to locate CPA launch adapter")?; let directory = resolve_launch_directory(working_directory.as_deref(), &home)?; launch_cli_agent(&executable, client.name(), &directory, &antigravity_cli_helper_arguments(&home), &[], &terminal) } (AgentClient::WorkBuddy, "app") => { let executable = find_workbuddy_desktop_executable(&home) - .ok_or_else(|| "未找到 WorkBuddy 应用程序".to_string())?; + .ok_or_else(|| "WorkBuddy application not found".to_string())?; launch_desktop_agent(&executable, client.name()) } (AgentClient::ZCode, "app") => { let executable = find_zcode_desktop_executable(&home) - .ok_or_else(|| "未找到 ZCode 应用程序".to_string())?; + .ok_or_else(|| "ZCode application not found".to_string())?; launch_desktop_agent(&executable, client.name()) } (AgentClient::Codex, "app") => launch_codex_desktop(&home), (AgentClient::OpenCode, "app") => launch_opencode_desktop(&home), (AgentClient::ClaudeDesktop | AgentClient::ZCode | AgentClient::WorkBuddy, "cli") => { - Err(format!("{} 不支持 CLI 启动方式", client.name())) + Err(format!("{} does not support CLI launch mode", client.name())) } (_, "cli") => { let executable = find_agent_executable(client, &home) - .ok_or_else(|| format!("未找到 {} 的可执行文件", client.name()))?; + .ok_or_else(|| format!("Executable for {} not found", client.name()))?; let launch_directory = resolve_launch_directory(working_directory.as_deref(), &home)?; if client == AgentClient::DeepSeekHarness { let mode = deepseek_harness_launch_mode(deepseek_harness_options.as_ref())?; @@ -237,8 +237,8 @@ pub(crate) fn launch_agent( &terminal, ) } - (_, "app") => Err(format!("{} 不支持桌面 App 启动方式", client.name())), - _ => Err("不支持的智能体启动方式".to_string()), + (_, "app") => Err(format!("{} does not support desktop App launch mode", client.name())), + _ => Err("Unsupported agent launch mode".to_string()), } } @@ -255,7 +255,7 @@ fn deepseek_harness_launch_mode( ) { Ok(mode) } else { - Err(format!("不支持的 DeepSeek Harness 启动模式: {mode}")) + Err(format!("Unsupported DeepSeek Harness launch mode: {mode}")) } } @@ -265,7 +265,7 @@ fn agent_cli_launch_arguments( ) -> Result, String> { if client != AgentClient::DeepSeekHarness { if deepseek_harness_options.is_some() { - return Err("DeepSeek Harness 启动选项不能用于其他客户端".to_string()); + return Err("DeepSeek Harness launch options cannot be used with other clients".to_string()); } return Ok(Vec::new()); } @@ -284,7 +284,7 @@ fn build_deepseek_harness_launch_arguments( .and_then(|options| options.profile.as_deref()) .unwrap_or_default(), )?, - _ => return Err(format!("不支持的 DeepSeek Harness 启动模式: {mode}")), + _ => return Err(format!("Unsupported DeepSeek Harness launch mode: {mode}")), }; let mut arguments = if profile == "web" { vec!["web".to_string()] @@ -295,7 +295,7 @@ fn build_deepseek_harness_launch_arguments( if let Some(options) = options { for patch in &options.patches { arguments.push("--patch".to_string()); - arguments.push(validate_deepseek_harness_argument(patch, "patch 路径")?); + arguments.push(validate_deepseek_harness_argument(patch, "Patch path")?); } } @@ -330,7 +330,7 @@ fn build_deepseek_harness_launch_arguments( let task = options .and_then(|options| options.task.as_deref()) .unwrap_or_default(); - arguments.push(validate_deepseek_harness_argument(task, "Headless 任务")?); + arguments.push(validate_deepseek_harness_argument(task, "Headless task")?); } "acp" | "sdk" | "sdk-minimal" | "custom" => {} _ => unreachable!(), @@ -343,7 +343,7 @@ impl DeepSeekHarnessProcessState { let mut process = self .process .lock() - .map_err(|_| "DeepSeek Harness 进程状态锁已损坏".to_string())?; + .map_err(|_| "DeepSeek Harness process status lock is poisoned".to_string())?; let Some(managed) = process.as_mut() else { return Ok(DeepSeekHarnessProcessStatus::default()); }; @@ -357,7 +357,7 @@ impl DeepSeekHarnessProcessState { *process = None; Ok(DeepSeekHarnessProcessStatus::default()) } - Err(error) => Err(format!("检查 DeepSeek Harness 进程状态失败: {error}")), + Err(error) => Err(format!("Failed to check DeepSeek Harness process status: {error}")), } } } @@ -388,17 +388,17 @@ fn launch_managed_deepseek_harness( let mut process = process_state .process .lock() - .map_err(|_| "DeepSeek Harness 进程状态锁已损坏".to_string())?; + .map_err(|_| "DeepSeek Harness process status lock is poisoned".to_string())?; if let Some(managed) = process.as_mut() { match managed.child.try_wait() { Ok(None) => { return Err(format!( - "DeepSeek Harness 已在运行(PID {}),请先关闭当前进程", + "DeepSeek Harness is already running (PID {}). Stop the current process first", managed.child.id() )); } Ok(Some(_)) => *process = None, - Err(error) => return Err(format!("检查 DeepSeek Harness 进程状态失败: {error}")), + Err(error) => return Err(format!("Failed to check DeepSeek Harness process status: {error}")), } } @@ -428,7 +428,7 @@ pub(crate) async fn restart_deepseek_harness_process( restart_managed_deepseek_harness(app.state::().inner()) }) .await - .map_err(|error| format!("重启 DeepSeek Harness Web 任务失败: {error}"))? + .map_err(|error| format!("DeepSeek Harness Web restart task failed: {error}"))? } fn restart_managed_deepseek_harness( @@ -449,25 +449,25 @@ fn restart_managed_deepseek_harness_with( let mut process = process_state .process .lock() - .map_err(|_| "DeepSeek Harness 进程状态锁已损坏".to_string())?; + .map_err(|_| "DeepSeek Harness process status lock is poisoned".to_string())?; let managed = process .as_mut() - .ok_or_else(|| "没有正在运行的 DeepSeek Harness Web 服务".to_string())?; + .ok_or_else(|| "No DeepSeek Harness Web service is running".to_string())?; if managed .child .try_wait() - .map_err(|error| format!("检查 DeepSeek Harness 进程状态失败: {error}"))? + .map_err(|error| format!("Failed to check DeepSeek Harness process status: {error}"))? .is_some() { *process = None; - return Err("DeepSeek Harness Web 已退出,请重新启动".to_string()); + return Err("DeepSeek Harness Web has exited. Start it again".to_string()); } if managed.mode != "web" { - return Err("只有 Web 模式支持重启".to_string()); + return Err("Only Web mode supports restart".to_string()); } let launch = managed.launch.clone(); if !launch.executable.is_file() || !launch.working_directory.is_dir() { - return Err("DeepSeek Harness 启动程序或工作目录已不存在".to_string()); + return Err("DeepSeek Harness launcher or working directory no longer exists".to_string()); } stop(&mut managed.child)?; *process = None; @@ -507,10 +507,10 @@ fn ensure_deepseek_harness_web_endpoint_available( let listener = TcpListener::bind((host, port)).map_err(|error| { if error.kind() == io::ErrorKind::AddrInUse { format!( - "DeepSeek Harness Web 地址 {host}:{port} 已被占用,请关闭已有服务或选择其他端口" + "DeepSeek Harness Web address {host}:{port} is in use. Stop the existing service or choose another port" ) } else { - format!("无法使用 DeepSeek Harness Web 地址 {host}:{port}: {error}") + format!("Cannot use DeepSeek Harness Web address {host}:{port}: {error}") } })?; drop(listener); @@ -541,7 +541,7 @@ fn spawn_managed_deepseek_harness( ); return command .spawn() - .map_err(|error| format!("启动 DeepSeek Harness 失败: {error}")); + .map_err(|error| format!("Failed to start DeepSeek Harness: {error}")); } #[cfg(unix)] @@ -555,13 +555,13 @@ fn spawn_managed_deepseek_harness( .process_group(0); return command .spawn() - .map_err(|error| format!("启动 DeepSeek Harness 失败: {error}")); + .map_err(|error| format!("Failed to start DeepSeek Harness: {error}")); } #[cfg(not(any(windows, unix)))] { let _ = (executable, working_directory, arguments); - Err("当前平台不支持受控启动 DeepSeek Harness".to_string()) + Err("The current platform does not support managed DeepSeek Harness startup".to_string()) } } @@ -571,14 +571,14 @@ pub(crate) fn stop_managed_deepseek_harness( let mut process = process_state .process .lock() - .map_err(|_| "DeepSeek Harness 进程状态锁已损坏".to_string())?; + .map_err(|_| "DeepSeek Harness process status lock is poisoned".to_string())?; let Some(managed) = process.as_mut() else { return Ok(()); }; if managed .child .try_wait() - .map_err(|error| format!("检查 DeepSeek Harness 进程状态失败: {error}"))? + .map_err(|error| format!("Failed to check DeepSeek Harness process status: {error}"))? .is_some() { *process = None; @@ -602,10 +602,10 @@ fn terminate_deepseek_harness_process_tree(child: &mut Child) -> Result<(), Stri configure_background_command(&mut command); let status = command .status() - .map_err(|error| format!("关闭 DeepSeek Harness 进程树失败: {error}"))?; + .map_err(|error| format!("Failed to stop DeepSeek Harness process tree: {error}"))?; if !status.success() { return Err(format!( - "关闭 DeepSeek Harness 进程树失败: PID {process_id}" + "Failed to stop DeepSeek Harness process tree: PID {process_id}" )); } let _ = child.wait(); @@ -618,10 +618,10 @@ fn terminate_deepseek_harness_process_tree(child: &mut Child) -> Result<(), Stri let term_status = Command::new("kill") .args(["-TERM", &process_group]) .status() - .map_err(|error| format!("关闭 DeepSeek Harness 进程组失败: {error}"))?; + .map_err(|error| format!("Failed to stop DeepSeek Harness process group: {error}"))?; if !term_status.success() { return Err(format!( - "关闭 DeepSeek Harness 进程组失败: PID {}", + "Failed to stop DeepSeek Harness process group: PID {}", child.id() )); } @@ -630,23 +630,23 @@ fn terminate_deepseek_harness_process_tree(child: &mut Child) -> Result<(), Stri Ok(Some(_)) => return Ok(()), Ok(None) => thread::sleep(Duration::from_millis(100)), Err(error) => { - return Err(format!("检查 DeepSeek Harness 进程状态失败: {error}")); + return Err(format!("Failed to check DeepSeek Harness process status: {error}")); } } } let kill_status = Command::new("kill") .args(["-KILL", &process_group]) .status() - .map_err(|error| format!("强制关闭 DeepSeek Harness 进程组失败: {error}"))?; + .map_err(|error| format!("Failed to force-stop DeepSeek Harness process group: {error}"))?; if !kill_status.success() { return Err(format!( - "强制关闭 DeepSeek Harness 进程组失败: PID {}", + "Failed to force-stop DeepSeek Harness process group: PID {}", child.id() )); } child .wait() - .map_err(|error| format!("等待 DeepSeek Harness 进程退出失败: {error}"))?; + .map_err(|error| format!("Failed while waiting for DeepSeek Harness process to exit: {error}"))?; return Ok(()); } @@ -654,10 +654,10 @@ fn terminate_deepseek_harness_process_tree(child: &mut Child) -> Result<(), Stri { child .kill() - .map_err(|error| format!("关闭 DeepSeek Harness 进程失败: {error}"))?; + .map_err(|error| format!("Failed to stop DeepSeek Harness process: {error}"))?; child .wait() - .map_err(|error| format!("等待 DeepSeek Harness 进程退出失败: {error}"))?; + .map_err(|error| format!("Failed while waiting for DeepSeek Harness process to exit: {error}"))?; Ok(()) } } @@ -665,7 +665,7 @@ fn terminate_deepseek_harness_process_tree(child: &mut Child) -> Result<(), Stri fn validate_deepseek_harness_profile(profile: &str) -> Result<&str, String> { let profile = profile.trim(); if profile.is_empty() { - return Err("请输入 DeepSeek Harness profile 名称".to_string()); + return Err("Enter a DeepSeek Harness profile name".to_string()); } if matches!(profile, "." | "..") || !profile.chars().all(|character| { @@ -673,7 +673,7 @@ fn validate_deepseek_harness_profile(profile: &str) -> Result<&str, String> { }) { return Err( - "DeepSeek Harness profile 名称只能包含字母、数字、点、连字符和下划线".to_string(), + "DeepSeek Harness profile name may contain only letters, numbers, periods, hyphens, and underscores".to_string(), ); } Ok(profile) @@ -682,10 +682,10 @@ fn validate_deepseek_harness_profile(profile: &str) -> Result<&str, String> { fn validate_deepseek_harness_argument(value: &str, label: &str) -> Result { let value = value.trim(); if value.is_empty() { - return Err(format!("{label}不能为空")); + return Err(format!("{label} cannot be empty")); } if value.chars().any(char::is_control) { - return Err(format!("{label}不能包含控制字符")); + return Err(format!("{label} cannot contain control characters")); } Ok(value.to_string()) } @@ -711,19 +711,19 @@ pub(crate) async fn restart_agent_app(app: tauri::AppHandle, client: String) -> | AgentClient::ZCode | AgentClient::WorkBuddy ) { - return Err(format!("{} 不支持桌面应用重启", client.name())); + return Err(format!("{} does not support restarting the desktop application", client.name())); } let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; tauri::async_runtime::spawn_blocking(move || { let target = find_desktop_restart_target(client, &home)?; match client { AgentClient::Codex => stop_codex_desktop(&target)?, AgentClient::OpenCode => { let DesktopAppTarget::Application(path) = &target else { - return Err("OpenCode 桌面安装类型无效".to_string()); + return Err("Invalid OpenCode desktop installation type".to_string()); }; stop_opencode_desktop(path)?; } @@ -738,7 +738,7 @@ pub(crate) async fn restart_agent_app(app: tauri::AppHandle, client: String) -> } }) .await - .map_err(|error| format!("重启桌面应用任务失败: {error}"))? + .map_err(|error| format!("Desktop application restart task failed: {error}"))? } fn find_desktop_restart_target( @@ -771,7 +771,7 @@ fn find_desktop_restart_target( } _ => None, }; - target.ok_or_else(|| format!("未检测到 {} 桌面应用,请重新检测", client.name())) + target.ok_or_else(|| format!("{} desktop application was not detected. Detect it again", client.name())) } #[cfg(target_os = "windows")] @@ -794,7 +794,7 @@ fn stop_other_desktop(target: &DesktopAppTarget, label: &str) -> Result<(), Stri #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] fn stop_other_desktop(_target: &DesktopAppTarget, label: &str) -> Result<(), String> { - Err(format!("当前平台不支持重启 {label}")) + Err(format!("The current platform does not support restarting {label}")) } fn resolve_launch_directory(value: Option<&str>, fallback: &Path) -> Result { @@ -802,21 +802,21 @@ fn resolve_launch_directory(value: Option<&str>, fallback: &Path) -> Result Result<(), String> { let target = find_codex_app_installation(home) - .ok_or_else(|| "未检测到 Codex 桌面应用,请重新检测或改用 Codex CLI".to_string())?; + .ok_or_else(|| "Codex desktop application was not detected. Detect it again or use Codex CLI".to_string())?; launch_codex_target(&target) } @@ -886,9 +886,9 @@ fn macos_processes_for_application(application: &Path) -> Result, Strin let output = Command::new("ps") .args(["-axww", "-o", "pid=", "-o", "comm="]) .output() - .map_err(|error| format!("读取桌面应用进程失败: {error}"))?; + .map_err(|error| format!("Failed to read desktop application processes: {error}"))?; if !output.status.success() { - return Err("读取桌面应用进程失败".to_string()); + return Err("Failed to read desktop application processes".to_string()); } Ok(desktop_process_ids_from_ps( &String::from_utf8_lossy(&output.stdout), @@ -921,7 +921,7 @@ fn stop_macos_desktop_application(application: &Path, label: &str) -> Result<(), if unsafe { libc::kill(pid, libc::SIGKILL) } != 0 { let error = io::Error::last_os_error(); if error.raw_os_error() != Some(libc::ESRCH) { - return Err(format!("关闭 {label} 进程 {pid} 失败: {error}")); + return Err(format!("Failed to stop {label} process {pid}: {error}")); } } } @@ -931,7 +931,7 @@ fn stop_macos_desktop_application(application: &Path, label: &str) -> Result<(), } thread::sleep(Duration::from_millis(100)); } - Err(format!("{label} 未能完全关闭")) + Err(format!("{label} did not close completely")) } #[cfg(target_os = "linux")] @@ -977,7 +977,7 @@ fn stop_linux_desktop_application(application: &Path, label: &str) -> Result<(), Err(error) } else { Err(format!( - "{label} 未能完全关闭;剩余进程 ID: {}", + "{label} did not close completely; remaining process IDs: {}", remaining .iter() .map(i32::to_string) @@ -1080,18 +1080,18 @@ fn signal_linux_process(process_id: i32, signal: i32) -> Result<(), String> { if error.raw_os_error() == Some(libc::ESRCH) { Ok(()) } else { - Err(format!("关闭桌面应用进程 {process_id} 失败: {error}")) + Err(format!("Failed to stop desktop application process {process_id}: {error}")) } } #[cfg(not(any(target_os = "windows", target_os = "macos")))] fn stop_codex_desktop(_target: &DesktopAppTarget) -> Result<(), String> { - Err("当前平台不支持重启 Codex App".to_string()) + Err("The current platform does not support restarting Codex App".to_string()) } #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] fn stop_opencode_desktop(_application: &Path) -> Result<(), String> { - Err("当前平台不支持重启 OpenCode Desktop".to_string()) + Err("The current platform does not support restarting OpenCode Desktop".to_string()) } fn launch_claude_desktop(home: &Path) -> Result<(), String> { @@ -1103,12 +1103,12 @@ fn launch_claude_desktop(home: &Path) -> Result<(), String> { launch_windows_claude_store_app() } #[cfg(not(target_os = "windows"))] - Err("未检测到 Claude Desktop 应用,请先安装或重新检测".to_string()) + Err("Claude Desktop application was not detected. Install or detect it again".to_string()) } fn launch_opencode_desktop(home: &Path) -> Result<(), String> { let application = find_opencode_desktop_application(home) - .ok_or_else(|| "未检测到 OpenCode Desktop 应用,请先安装或重新检测".to_string())?; + .ok_or_else(|| "OpenCode Desktop application was not detected. Install or detect it again".to_string())?; launch_desktop_agent(&application, "OpenCode Desktop") } @@ -1130,7 +1130,7 @@ fn launch_desktop_agent(executable: &Path, label: &str) -> Result<(), String> { #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] { let _ = (executable, label); - return Err("当前平台不支持桌面智能体".to_string()); + return Err("The current platform does not support desktop agents".to_string()); } #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] @@ -1143,7 +1143,7 @@ fn launch_desktop_agent(executable: &Path, label: &str) -> Result<(), String> { command .spawn() .map(|_| ()) - .map_err(|error| format!("启动 {label} 失败: {error}")) + .map_err(|error| format!("Failed to start {label}: {error}")) } } @@ -1159,13 +1159,13 @@ fn launch_windows_store_app(app_id: &str, label: &str) -> Result<(), String> { command .spawn() .map(|_| ()) - .map_err(|error| format!("启动 {label} 失败: {error}")) + .map_err(|error| format!("Failed to start {label}: {error}")) } #[cfg(target_os = "windows")] fn launch_windows_claude_store_app() -> Result<(), String> { let app_id = find_windows_claude_app_id() - .ok_or_else(|| "未找到可启动的 Claude Desktop 应用".to_string())?; + .ok_or_else(|| "No launchable Claude Desktop application found".to_string())?; launch_windows_store_app(&app_id, "Claude Desktop") } @@ -1218,18 +1218,18 @@ fn launch_cli_agent( command_line.replace('\\', "\\\\").replace('"', "\\\"") ) } else { - return Err(format!("启动 {label} 失败:不支持所选终端")); + return Err(format!("Failed to start {label}: selected terminal is unsupported")); }; let output = Command::new("osascript") .arg("-e") .arg(script) .output() - .map_err(|error| format!("启动 {label} 终端失败: {error}"))?; + .map_err(|error| format!("Failed to start {label} terminal: {error}"))?; if output.status.success() { Ok(()) } else { let detail = String::from_utf8_lossy(&output.stderr).trim().to_string(); - Err(format!("启动 {label} 终端失败: {detail}")) + Err(format!("Failed to start {label} terminal: {detail}")) } } @@ -1270,11 +1270,11 @@ fn launch_cli_agent( } } if terminal != "auto" && last_error.is_none() { - return Err(format!("启动 {label} 失败:未找到所选终端")); + return Err(format!("Failed to start {label}: selected terminal not found")); } Err(match last_error { - Some(error) => format!("启动 {label} 失败: {error}"), - None => format!("启动 {label} 失败:未找到可用的终端程序"), + Some(error) => format!("Failed to start {label}: {error}"), + None => format!("Failed to start {label}: no available terminal program found"), }) } @@ -1365,7 +1365,7 @@ fn launch_cli_agent( } "windows-terminal" => { let terminal_executable = program_on_path("wt.exe") - .ok_or_else(|| format!("启动 {label} 失败:未找到 Windows Terminal"))?; + .ok_or_else(|| format!("Failed to start {label}: Windows Terminal not found"))?; let directory = path_to_string(working_directory); let mut command = Command::new(terminal_executable); command.args(["-d", &directory, "--"]); @@ -1403,7 +1403,7 @@ fn launch_cli_agent( command.creation_flags(CREATE_NEW_CONSOLE); command } - _ => return Err(format!("启动 {label} 失败:不支持所选终端")), + _ => return Err(format!("Failed to start {label}: selected terminal is unsupported")), }; for key in environment_to_remove { command.env_remove(key); @@ -1411,7 +1411,7 @@ fn launch_cli_agent( command .spawn() .map(|_| ()) - .map_err(|error| format!("启动 {label} 失败: {error}")) + .map_err(|error| format!("Failed to start {label}: {error}")) } #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] @@ -1423,7 +1423,7 @@ fn launch_cli_agent( _environment_to_remove: &[&str], _terminal: &str, ) -> Result<(), String> { - Err(format!("当前平台不支持启动 {label}")) + Err(format!("The current platform does not support launching {label}")) } #[cfg(test)] @@ -1464,7 +1464,7 @@ mod tests { .unwrap() .as_nanos(), )) - .join("用户's [workspace] $value"); + .join("User's [workspace] $value"); fs::create_dir_all(&directory).unwrap(); let executable = directory.join("GUI helper.exe"); let compiled = directory.parent().unwrap().join("helper.exe"); @@ -1811,7 +1811,7 @@ class GuiHelper { fn relative_launch_directory_is_rejected() { let error = resolve_launch_directory(Some("relative/project"), Path::new("/fallback")) .expect_err("relative path should be rejected"); - assert!(error.contains("绝对路径")); + assert!(error.contains("absolute path")); } #[test] @@ -1966,7 +1966,7 @@ class GuiHelper { let error = ensure_deepseek_harness_web_endpoint_available(Some(&options)).unwrap_err(); - assert!(error.contains("已被占用"), "{error}"); + assert!(error.contains("is in use"), "{error}"); } #[test] diff --git a/src-tauri/src/agents/native_oauth.rs b/src-tauri/src/agents/native_oauth.rs index e255174..da8d9ce 100644 --- a/src-tauri/src/agents/native_oauth.rs +++ b/src-tauri/src/agents/native_oauth.rs @@ -8,17 +8,17 @@ pub(crate) fn write_codex_private_file(path: &Path, content: &[u8]) -> Result<() #[cfg(unix)] { use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; - fs::create_dir_all(path.parent().ok_or("Codex 凭据路径无效")?) - .map_err(|_| "创建 Codex 凭据目录失败")?; + fs::create_dir_all(path.parent().ok_or("Invalid Codex credentials path")?) + .map_err(|_| "Failed to create Codex credentials directory")?; let file = fs::OpenOptions::new() .create(true) .write(true) .truncate(false) .mode(0o600) .open(path) - .map_err(|_| "无法打开 Codex 凭据文件")?; + .map_err(|_| "Unable to open Codex credentials file")?; file.set_permissions(fs::Permissions::from_mode(0o600)) - .map_err(|_| "设置 Codex 凭据文件权限失败")?; + .map_err(|_| "Failed to set Codex credentials file permissions")?; } write_bytes_directly(path, content) } @@ -57,9 +57,9 @@ fn parse_state(bytes: Option<&[u8]>) -> Result { }); }; let state: NativeOAuthState = serde_json::from_slice(bytes) - .map_err(|_| "无法读取 Codex 接入方式切换记录,请检查配置文件".to_string())?; + .map_err(|_| "Unable to read Codex access mode switch record. Check the configuration file".to_string())?; if state.version != 1 { - return Err("不支持的 Codex 接入方式切换记录版本".into()); + return Err("Unsupported Codex access mode switch record version".into()); } Ok(state) } @@ -90,7 +90,7 @@ pub(crate) fn codex_native_oauth_enabled(home: &Path) -> Result { pub(crate) fn ensure_codex_cpa_mode(home: &Path) -> Result<(), String> { if codex_native_oauth_enabled(home)? { - return Err("请点击“更新配置”或“一键接入”恢复 CPA 配置".into()); + return Err("Click “Update Configuration” or “One-Click Setup” to restore the CPA configuration".into()); } Ok(()) } @@ -278,7 +278,7 @@ pub(crate) fn close_codex_configuration(home: &Path) -> Result Result Result { - let home = app.path().home_dir().map_err(|_| "无法获取用户目录")?; + let home = app.path().home_dir().map_err(|_| "Failed to get user directory")?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "智能体配置文件锁已损坏")?; + .map_err(|_| "Agent configuration file lock is poisoned")?; let result = close_codex_configuration(&home)?; app.state::().clear()?; Ok(result) @@ -362,7 +362,7 @@ fn prepare_native_oauth_switch(home: &Path, enabled: bool) -> Result<(Images, Im .get("model") .and_then(toml_edit::Item::as_str) .map(str::to_string); - let saved = parse_codex_document(Some(&state.cpa_settings), "Codex CPA 接入配置")?; + let saved = parse_codex_document(Some(&state.cpa_settings), "Codex CPA access configuration")?; for key in ROUTING_KEYS { restore_codex_table_item(document.as_table_mut(), Some(saved.as_table()), key); } @@ -377,7 +377,7 @@ fn prepare_native_oauth_switch(home: &Path, enabled: bool) -> Result<(Images, Im } document["model_providers"] .as_table_like_mut() - .ok_or("Codex model_providers 格式无效")? + .ok_or("Invalid Codex model_providers format")? .insert("openai", openai.clone()); } if oauth_auth(state.cpa_auth.as_deref())?.is_some() { @@ -391,7 +391,7 @@ fn prepare_native_oauth_switch(home: &Path, enabled: bool) -> Result<(Images, Im let rendered = document.to_string(); parse_codex_document(Some(&rendered), "Codex config.toml")?; let state_bytes = - serde_json::to_vec_pretty(&state).map_err(|_| "保存 Codex 接入方式切换记录失败")?; + serde_json::to_vec_pretty(&state).map_err(|_| "Failed to save Codex access mode switch record")?; let after = vec![ (paths[0].clone(), Some(state_bytes)), (paths[1].clone(), next_auth.map(String::into_bytes)), @@ -444,7 +444,7 @@ pub(crate) fn apply_codex_cpa_configuration( state.native_auth = None; } native_after[0].1 = - Some(serde_json::to_vec_pretty(&state).map_err(|_| "保存 Codex 切换记录失败")?); + Some(serde_json::to_vec_pretty(&state).map_err(|_| "Failed to save Codex switch record")?); let config_paths = config_paths("codex", home)?; let current = config_images(&config_paths)?; for image in ¤t { @@ -452,12 +452,12 @@ pub(crate) fn apply_codex_cpa_configuration( .iter() .any(|(path, bytes)| path == &image.0 && bytes != &image.1) { - return Err("配置已被其他程序修改,请刷新后重试".into()); + return Err("Configuration was modified by another program. Refresh and try again".into()); } } let mut restored_config = native_after[2].clone(); let mut restored_document = - parse_codex_document(text(restored_config.1.as_deref())?, "Codex CPA 配置")?; + parse_codex_document(text(restored_config.1.as_deref())?, "Codex CPA configuration")?; if restored_document.contains_key("forced_login_method") { restored_document["forced_login_method"] = toml_edit::value(if options.oauth_configuration { @@ -471,9 +471,9 @@ pub(crate) fn apply_codex_cpa_configuration( baseline .iter_mut() .find(|(path, _)| path == &restored_config.0) - .ok_or("Codex 配置路径不匹配")? + .ok_or("Codex configuration path mismatch")? .1 = restored_config.1.clone(); - let catalog = options.codex_catalog.ok_or("无法生成 Codex 模型目录")?; + let catalog = options.codex_catalog.ok_or("Unable to generate Codex model catalog")?; validate_codex_catalog(catalog, model)?; let updates = vec![ AgentFileUpdate { @@ -518,10 +518,10 @@ pub(crate) fn apply_codex_cpa_configuration( pub(crate) fn restore_codex_official_config( app: tauri::AppHandle, ) -> Result { - let home = app.path().home_dir().map_err(|_| "无法获取用户目录")?; + let home = app.path().home_dir().map_err(|_| "Failed to get user directory")?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "智能体配置文件锁已损坏")?; + .map_err(|_| "Agent configuration file lock is poisoned")?; let result = switch_codex_native_oauth(&home, true)?; app.state::().clear()?; Ok(result) diff --git a/src-tauri/src/agents/state.rs b/src-tauri/src/agents/state.rs index e4f3562..b54c19d 100644 --- a/src-tauri/src/agents/state.rs +++ b/src-tauri/src/agents/state.rs @@ -5,18 +5,18 @@ pub(crate) fn agent_backup_path(path: &Path) -> Result { let file_name = path .file_name() .and_then(|value| value.to_str()) - .ok_or_else(|| format!("智能体配置文件名无效: {}", path_to_string(path)))?; + .ok_or_else(|| format!("Invalid agent configuration filename: {}", path_to_string(path)))?; Ok(path.with_file_name(format!("{file_name}.cpa-gui.backup"))) } pub(crate) fn agent_state_path(paths: &[PathBuf]) -> Result { let primary = paths .first() - .ok_or_else(|| "当前平台没有可用的智能体配置路径".to_string())?; + .ok_or_else(|| "No agent configuration path is available on the current platform".to_string())?; let file_name = primary .file_name() .and_then(|value| value.to_str()) - .ok_or_else(|| format!("智能体配置文件名无效: {}", path_to_string(primary)))?; + .ok_or_else(|| format!("Invalid agent configuration filename: {}", path_to_string(primary)))?; Ok(primary.with_file_name(format!("{file_name}.cpa-gui.state.json"))) } @@ -37,12 +37,12 @@ pub(crate) fn write_agent_applied_state( if state.client == AgentClient::Codex.id() { CODEX_APPLIED_STATES .lock() - .map_err(|_| "Codex 应用状态内存锁已损坏".to_string())? + .map_err(|_| "Codex application state memory lock is poisoned".to_string())? .insert(path.to_path_buf(), state.clone()); if path.is_file() { fs::remove_file(path).map_err(|error| { format!( - "清理旧版 Codex 应用状态失败 {}: {error}", + "Failed to clean up legacy Codex application state {}: {error}", path_to_string(path) ) })?; @@ -51,7 +51,7 @@ pub(crate) fn write_agent_applied_state( } let mut content = serde_json::to_string_pretty(state) - .map_err(|error| format!("生成智能体应用状态失败: {error}"))?; + .map_err(|error| format!("Failed to generate agent application state: {error}"))?; content.push('\n'); write_bytes_directly(path, content.as_bytes()) } @@ -60,7 +60,7 @@ pub(crate) fn write_agent_applied_state( pub(crate) fn clear_codex_applied_state(path: &Path) -> Result<(), String> { CODEX_APPLIED_STATES .lock() - .map_err(|_| "Codex 应用状态内存锁已损坏".to_string())? + .map_err(|_| "Codex application state memory lock is poisoned".to_string())? .remove(path); Ok(()) } @@ -91,11 +91,11 @@ pub(crate) fn latest_dated_agent_backup_path(path: &Path) -> Result Result<(), String> { if state.client != client.id() { - return Err("智能体应用状态与客户端不匹配".to_string()); + return Err("Agent application state does not match the client".to_string()); } if state.backup_files.is_empty() { return Ok(()); @@ -197,7 +197,7 @@ pub(crate) fn validate_agent_applied_state( .first() .is_some_and(|path| state_paths == [path.clone()])); if !valid_paths { - return Err("智能体应用状态文件数量或路径不匹配".to_string()); + return Err("Agent application state file count or path mismatch".to_string()); } for file in &state.backup_files { let legacy_backup = agent_backup_path(&file.path)?; @@ -205,7 +205,7 @@ pub(crate) fn validate_agent_applied_state( .path .file_name() .and_then(|value| value.to_str()) - .ok_or_else(|| format!("智能体配置文件名无效: {}", path_to_string(&file.path)))?; + .ok_or_else(|| format!("Invalid agent configuration filename: {}", path_to_string(&file.path)))?; let dated_backup = file.backup_path.parent() == file.path.parent() && file .backup_path @@ -213,7 +213,7 @@ pub(crate) fn validate_agent_applied_state( .and_then(|value| value.to_str()) .is_some_and(|name| is_dated_agent_backup_name(name, original_name)); if file.backup_path != legacy_backup && !dated_backup { - return Err("智能体应用状态包含非预期备份路径".to_string()); + return Err("Agent application state contains an unexpected backup path".to_string()); } } Ok(()) @@ -229,7 +229,7 @@ pub(crate) fn load_agent_applied_state( if client == AgentClient::Codex { if let Some(state) = CODEX_APPLIED_STATES .lock() - .map_err(|_| "Codex 应用状态内存锁已损坏".to_string())? + .map_err(|_| "Codex application state memory lock is poisoned".to_string())? .get(&state_path) .cloned() { @@ -248,17 +248,17 @@ pub(crate) fn load_agent_applied_state( return Ok(recovered); } let content = fs::read_to_string(&state_path) - .map_err(|error| format!("读取智能体应用状态失败: {error}"))?; + .map_err(|error| format!("Failed to read agent application state: {error}"))?; let value = serde_json::from_str::(&content) - .map_err(|error| format!("解析智能体应用状态失败: {error}"))?; + .map_err(|error| format!("Failed to parse agent application state: {error}"))?; let version = value .get("version") .and_then(serde_json::Value::as_u64) - .ok_or_else(|| "智能体应用状态缺少版本号".to_string())?; - let version = u8::try_from(version).map_err(|_| "不支持的智能体应用状态版本".to_string())?; + .ok_or_else(|| "Agent application state is missing a version number".to_string())?; + let version = u8::try_from(version).map_err(|_| "Unsupported agent application state version".to_string())?; if version == AGENT_APPLIED_STATE_VERSION || version == 3 { let state = serde_json::from_value::(value) - .map_err(|error| format!("解析智能体应用状态失败: {error}"))?; + .map_err(|error| format!("Failed to parse agent application state: {error}"))?; validate_agent_applied_state(client, &paths, &state)?; if client == AgentClient::Codex { write_agent_applied_state(&state_path, &state)?; @@ -268,11 +268,11 @@ pub(crate) fn load_agent_applied_state( if version != LEGACY_AGENT_MODIFICATION_STATE_VERSION && version != AGENT_MODIFICATION_STATE_VERSION { - return Err("不支持的智能体应用状态版本".to_string()); + return Err("Unsupported agent application state version".to_string()); } let record = serde_json::from_value::(value) - .map_err(|error| format!("解析旧版智能体状态失败: {error}"))?; + .map_err(|error| format!("Failed to parse legacy agent state: {error}"))?; validate_agent_record(client, &paths, &record)?; let backup_files = record .files @@ -304,7 +304,7 @@ pub(crate) fn read_agent_bytes(path: &Path) -> Result>, String> { match fs::read(path) { Ok(bytes) => Ok(Some(bytes)), Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None), - Err(_) => Err(format!("读取智能体配置失败: {},请检查文件权限或占用情况", path_to_string(path))), + Err(_) => Err(format!("Failed to read agent configuration: {}. Check file permissions or whether the file is in use", path_to_string(path))), } } @@ -318,16 +318,16 @@ pub(crate) fn read_agent_original_bytes( let original_sha256 = file .original_sha256 .as_deref() - .ok_or_else(|| format!("原配置备份缺少校验值: {}", path_to_string(&file.path)))?; + .ok_or_else(|| format!("Original configuration backup is missing a checksum: {}", path_to_string(&file.path)))?; let bytes = fs::read(&file.backup_path).map_err(|error| { format!( - "读取原配置备份失败 {}: {error}", + "Failed to read original configuration backup {}: {error}", path_to_string(&file.backup_path) ) })?; if sha256_bytes(&bytes) != original_sha256 { return Err(format!( - "原配置备份校验失败: {}", + "Original configuration backup verification failed: {}", path_to_string(&file.backup_path) )); } @@ -342,7 +342,7 @@ pub(crate) fn read_agent_original_text( .map(|bytes| { String::from_utf8(bytes).map_err(|_| { format!( - "原 Codex 配置不是 UTF-8 文本: {}", + "Original Codex configuration is not UTF-8 text: {}", path_to_string(&file.path) ) }) @@ -357,11 +357,11 @@ pub(crate) fn write_agent_state( ) -> Result<(), String> { if let Some(parent) = path.parent() { fs::create_dir_all(parent).map_err(|error| { - format!("创建智能体状态目录失败 {}: {error}", path_to_string(parent)) + format!("Failed to create agent state directory {}: {error}", path_to_string(parent)) })?; } let mut content = serde_json::to_string_pretty(record) - .map_err(|error| format!("生成智能体备份状态失败: {error}"))?; + .map_err(|error| format!("Failed to generate agent backup state: {error}"))?; content.push('\n'); write_yaml_if_changed(path, &content).map(|_| ()) } @@ -375,7 +375,7 @@ pub(crate) fn validate_agent_record( let supported_version = record.version == AGENT_MODIFICATION_STATE_VERSION || record.version == LEGACY_AGENT_MODIFICATION_STATE_VERSION; if !supported_version || record.client != client.id() { - return Err("智能体备份状态版本或客户端不匹配".to_string()); + return Err("Agent backup state version or client mismatch".to_string()); } if ![ AGENT_PHASE_APPLYING, @@ -385,7 +385,7 @@ pub(crate) fn validate_agent_record( ] .contains(&record.phase.as_str()) { - return Err("智能体备份状态阶段无效".to_string()); + return Err("Invalid agent backup state stage".to_string()); } let expected_paths = expected_agent_record_paths(client, paths); let record_paths = record @@ -400,11 +400,11 @@ pub(crate) fn validate_agent_record( .first() .is_some_and(|path| record_paths == [path.clone()])); if !valid_paths { - return Err("智能体备份状态文件数量或路径不匹配".to_string()); + return Err("Agent backup state file count or path mismatch".to_string()); } for file in &record.files { if file.backup_path != agent_backup_path(&file.path)? { - return Err("智能体备份状态包含非预期路径".to_string()); + return Err("Agent backup state contains an unexpected path".to_string()); } } Ok(()) @@ -421,9 +421,9 @@ pub(crate) fn load_agent_record( } let mut record: AgentModificationRecord = serde_json::from_str( &fs::read_to_string(&state_path) - .map_err(|error| format!("读取智能体备份状态失败: {error}"))?, + .map_err(|error| format!("Failed to read agent backup state: {error}"))?, ) - .map_err(|error| format!("解析智能体备份状态失败: {error}"))?; + .map_err(|error| format!("Failed to parse agent backup state: {error}"))?; validate_agent_record(client, paths, &record)?; if record.version == LEGACY_AGENT_MODIFICATION_STATE_VERSION { record.version = AGENT_MODIFICATION_STATE_VERSION; @@ -548,17 +548,17 @@ pub(crate) fn inspect_agent_modification( }; let mut warnings = Vec::new(); if !backup_available { - warnings.push("原配置备份不完整,恢复前请勿删除剩余备份文件".to_string()); + warnings.push("Original configuration backup is incomplete. Do not delete the remaining backup files before restoration".to_string()); } if state == AGENT_MODIFICATION_STATE_CONFLICT { - warnings.push("配置已被其他程序修改,清除修改时需要确认是否恢复".to_string()); + warnings.push("Configuration was modified by another program. Confirmation is required before restoring it when clearing changes".to_string()); } else if state == "recovery" { - warnings.push("上次配置操作未完整结束,可使用“清除修改”恢复原配置".to_string()); + warnings.push("The last configuration operation did not complete. Use “Clear Changes” to restore the original configuration".to_string()); } else if client == AgentClient::Codex && codex_record_needs_resync(&record, configured, current_model).unwrap_or(true) { warnings.push( - "Codex 受管配置已发生变化,将在下次更新或启动时自动重新同步".to_string(), + "Managed Codex configuration has changed and will be resynchronized automatically on the next update or launch".to_string(), ); } AgentModificationInspection { @@ -595,7 +595,7 @@ pub(crate) fn inspect_agent_modification( state: "active".to_string(), backup_available: true, warnings: vec![ - "检测到旧版 CPA 配置和备份,可使用“清除修改”恢复原配置".to_string() + "Legacy CPA configuration and backup detected. Use “Clear Changes” to restore the original configuration".to_string() ], } } @@ -604,7 +604,7 @@ pub(crate) fn inspect_agent_modification( enabled: false, state: "inactive".to_string(), backup_available: false, - warnings: vec!["检测到 CPA 配置,但缺少可安全恢复的原始备份".to_string()], + warnings: vec!["CPA configuration detected, but no original backup is available for safe restoration".to_string()], } } Err(error) => { @@ -727,7 +727,7 @@ pub(crate) fn fresh_agent_contents_with_oauth( build_deepseek_harness_settings(None, &openai_base, model, models)?, build_deepseek_harness_credentials(None, api_key)?, ]), - AgentClient::AntigravityCli => Err("Antigravity 模板需要安装和用户目录信息".into()), + AgentClient::AntigravityCli => Err("Antigravity template requires installation and user directory information".into()), AgentClient::WorkBuddy => Ok(vec![build_workbuddy_agent_config( None, &openai_base, api_key, model, models, )?]), @@ -817,14 +817,14 @@ pub(crate) fn build_legacy_agent_record( let (existed_before, original_sha256) = if backup_path.is_file() { let backup = fs::read(&backup_path).map_err(|error| { format!( - "读取旧版智能体备份失败 {}: {error}", + "Failed to read legacy agent backup {}: {error}", path_to_string(&backup_path) ) })?; (true, Some(sha256_bytes(&backup))) } else { let actual = String::from_utf8(current.clone()) - .map_err(|_| format!("智能体配置不是 UTF-8 文本: {}", path_to_string(path)))?; + .map_err(|_| format!("Agent configuration is not UTF-8 text: {}", path_to_string(path)))?; if !agent_contents_equal(client, &actual, &generated[index]) { return Ok(None); } @@ -855,16 +855,16 @@ pub(crate) fn prepare_agent_record( updates: &[AgentFileUpdate], ) -> Result { if paths.len() != updates.len() { - return Err("智能体配置更新文件数量不匹配".to_string()); + return Err("Agent configuration update file count mismatch".to_string()); } let mut prepared = Vec::new(); for (path, update) in paths.iter().zip(updates) { if path != &update.path { - return Err("智能体配置更新路径不匹配".to_string()); + return Err("Agent configuration update path mismatch".to_string()); } if let Some(parent) = path.parent() { fs::create_dir_all(parent).map_err(|error| { - format!("创建智能体配置目录失败 {}: {error}", path_to_string(parent)) + format!("Failed to create agent configuration directory {}: {error}", path_to_string(parent)) })?; } let backup_path = agent_backup_path(path)?; @@ -872,13 +872,13 @@ pub(crate) fn prepare_agent_record( let previous_backup = if backup_path.exists() { if !backup_path.is_file() { return Err(format!( - "智能体备份路径不是文件: {}", + "Agent backup path is not a file: {}", path_to_string(&backup_path) )); } Some(fs::read(&backup_path).map_err(|error| { format!( - "读取原有智能体备份失败 {}: {error}", + "Failed to read existing agent backup {}: {error}", path_to_string(&backup_path) ) })?) @@ -907,19 +907,19 @@ pub(crate) fn prepare_agent_record( write_bytes_atomically(&backup_path, current).and_then(|_| { let copied = fs::read(&backup_path).map_err(|error| { format!( - "校验智能体备份失败 {}: {error}", + "Failed to verify agent backup {}: {error}", path_to_string(&backup_path) ) })?; if sha256_bytes(&copied) != sha256_bytes(current) { - return Err(format!("智能体备份校验失败: {}", path_to_string(&path))); + return Err(format!("Agent backup verification failed: {}", path_to_string(&path))); } Ok(()) }) } else if backup_path.exists() { fs::remove_file(&backup_path).map_err(|error| { format!( - "清理旧智能体备份失败 {}: {error}", + "Failed to clean up old agent backup {}: {error}", path_to_string(&backup_path) ) }) @@ -930,7 +930,7 @@ pub(crate) fn prepare_agent_record( let rollback = restore_snapshots(&backup_snapshots); return Err(match rollback { Ok(()) => error, - Err(rollback_error) => format!("{error};恢复原有备份失败: {rollback_error}"), + Err(rollback_error) => format!("{error}; failed to restore existing backup: {rollback_error}"), }); } @@ -959,11 +959,11 @@ pub(crate) fn extend_agent_record_for_updates( updates: &[AgentFileUpdate], ) -> Result { if record.files.len() > updates.len() { - return Err("智能体配置更新文件数量不匹配".to_string()); + return Err("Agent configuration update file count mismatch".to_string()); } for (file, update) in record.files.iter().zip(updates) { if file.path != update.path { - return Err("智能体配置更新路径不匹配".to_string()); + return Err("Agent configuration update path mismatch".to_string()); } } if record.files.len() == updates.len() { @@ -974,20 +974,20 @@ pub(crate) fn extend_agent_record_for_updates( for update in updates.iter().skip(record.files.len()) { if let Some(parent) = update.path.parent() { fs::create_dir_all(parent).map_err(|error| { - format!("创建智能体配置目录失败 {}: {error}", path_to_string(parent)) + format!("Failed to create agent configuration directory {}: {error}", path_to_string(parent)) })?; } let backup_path = agent_backup_path(&update.path)?; let previous_backup = if backup_path.exists() { if !backup_path.is_file() { return Err(format!( - "智能体备份路径不是文件: {}", + "Agent backup path is not a file: {}", path_to_string(&backup_path) )); } Some(fs::read(&backup_path).map_err(|error| { format!( - "读取原有智能体备份失败 {}: {error}", + "Failed to read existing agent backup {}: {error}", path_to_string(&backup_path) ) })?) @@ -1008,13 +1008,13 @@ pub(crate) fn extend_agent_record_for_updates( write_bytes_atomically(&backup_path, current).and_then(|_| { let copied = fs::read(&backup_path).map_err(|error| { format!( - "校验智能体备份失败 {}: {error}", + "Failed to verify agent backup {}: {error}", path_to_string(&backup_path) ) })?; if sha256_bytes(&copied) != sha256_bytes(current) { return Err(format!( - "智能体备份校验失败: {}", + "Agent backup verification failed: {}", path_to_string(&update.path) )); } @@ -1023,7 +1023,7 @@ pub(crate) fn extend_agent_record_for_updates( } else if backup_path.exists() { fs::remove_file(&backup_path).map_err(|error| { format!( - "清理旧智能体备份失败 {}: {error}", + "Failed to clean up old agent backup {}: {error}", path_to_string(&backup_path) ) }) @@ -1034,7 +1034,7 @@ pub(crate) fn extend_agent_record_for_updates( let rollback = restore_snapshots(&backup_snapshots); return Err(match rollback { Ok(()) => error, - Err(rollback_error) => format!("{error};恢复原有备份失败: {rollback_error}"), + Err(rollback_error) => format!("{error}; failed to restore existing backup: {rollback_error}"), }); } next.files.push(AgentModificationFile { @@ -1076,7 +1076,7 @@ pub(crate) fn restore_snapshots_with_direct_path( let result = match bytes { Some(bytes) => write_agent_bytes(path, bytes, direct_write_path), None if path.exists() => fs::remove_file(path) - .map_err(|error| format!("删除配置失败 {}: {error}", path_to_string(path))), + .map_err(|error| format!("Failed to delete configuration {}: {error}", path_to_string(path))), None => Ok(()), }; if let Err(error) = result { @@ -1111,7 +1111,7 @@ pub(crate) fn apply_agent_file_replacements( None if current.is_some() => { changed.push(path_to_string(path)); fs::remove_file(path) - .map_err(|error| format!("删除配置失败 {}: {error}", path_to_string(path))) + .map_err(|error| format!("Failed to delete configuration {}: {error}", path_to_string(path))) } None => Ok(()), }; @@ -1119,7 +1119,7 @@ pub(crate) fn apply_agent_file_replacements( let rollback = restore_snapshots_with_direct_path(&snapshots, direct_write_path); return Err(match rollback { Ok(()) => error, - Err(rollback_error) => format!("{error};回滚失败: {rollback_error}"), + Err(rollback_error) => format!("{error}; rollback failed: {rollback_error}"), }); } } @@ -1133,12 +1133,12 @@ pub(crate) fn restore_codex_agent_record_files( let config_file = record .files .first() - .ok_or_else(|| "Codex 配置状态缺少 config.toml".to_string())?; + .ok_or_else(|| "Codex configuration state is missing config.toml".to_string())?; let current_config = read_agent_bytes(&config_file.path)? .map(|bytes| { String::from_utf8(bytes).map_err(|_| { format!( - "当前 Codex 配置不是 UTF-8 文本: {}", + "Current Codex configuration is not UTF-8 text: {}", path_to_string(&config_file.path) ) }) @@ -1182,7 +1182,7 @@ pub(crate) fn apply_agent_updates( let rollback = restore_snapshots_with_direct_path(&snapshots, direct_write_path); return Err(match rollback { Ok(()) => error, - Err(rollback_error) => format!("{error};回滚失败: {rollback_error}"), + Err(rollback_error) => format!("{error}; rollback failed: {rollback_error}"), }); } changed.push(path_to_string(&update.path)); @@ -1197,7 +1197,7 @@ pub(crate) fn restore_agent_snapshots_direct(snapshots: &[FileSnapshot]) -> Resu let result = match content { Some(content) => write_bytes_directly(path, content), None if path.exists() => fs::remove_file(path) - .map_err(|error| format!("删除新建配置失败 {}: {error}", path_to_string(path))), + .map_err(|error| format!("Failed to delete newly created configuration {}: {error}", path_to_string(path))), None => Ok(()), }; if let Err(error) = result { @@ -1216,7 +1216,7 @@ pub(crate) fn dated_agent_backup_path(path: &Path) -> Result { let file_name = path .file_name() .and_then(|value| value.to_str()) - .ok_or_else(|| format!("智能体配置文件名无效: {}", path_to_string(path)))?; + .ok_or_else(|| format!("Invalid agent configuration filename: {}", path_to_string(path)))?; let date = chrono::Local::now().format("%y-%m-%d"); Ok(path.with_file_name(format!("{file_name}.{date}.bak"))) } @@ -1254,7 +1254,7 @@ pub(crate) fn restore_agent_applied_state_configuration( if file.existed_before { fs::read(&file.backup_path).map(Some).map_err(|error| { format!( - "读取智能体备份失败 {}: {error}", + "Failed to read agent backup {}: {error}", path_to_string(&file.backup_path) ) }) @@ -1290,7 +1290,7 @@ pub(crate) fn restore_agent_applied_state_configuration( } else if file.path.exists() { fs::remove_file(&file.path).map_err(|error| { format!( - "删除临时智能体配置失败 {}: {error}", + "Failed to delete temporary agent configuration {}: {error}", path_to_string(&file.path) ) })?; @@ -1301,14 +1301,14 @@ pub(crate) fn restore_agent_applied_state_configuration( if let Err(error) = restore_result { return match restore_agent_snapshots_direct(&snapshots) { Ok(()) => Err(error), - Err(rollback_error) => Err(format!("{error};回滚恢复操作失败: {rollback_error}")), + Err(rollback_error) => Err(format!("{error}; failed to roll back restoration: {rollback_error}")), }; } for file in &state.backup_files { if file.existed_before && file.backup_path.is_file() { fs::remove_file(&file.backup_path).map_err(|error| { format!( - "清理智能体备份失败 {}: {error}", + "Failed to clean up agent backup {}: {error}", path_to_string(&file.backup_path) ) })?; @@ -1321,7 +1321,7 @@ pub(crate) fn restore_agent_applied_state_configuration( if state_path.is_file() { fs::remove_file(state_path).map_err(|error| { format!( - "清理智能体应用状态失败 {}: {error}", + "Failed to clean up agent application state {}: {error}", path_to_string(state_path) ) })?; @@ -1354,7 +1354,7 @@ pub(crate) fn sync_codex_model_catalog_if_configured( let paths = agent_config_paths(client, home); let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "智能体配置文件锁已损坏".to_string())?; + .map_err(|_| "Agent configuration file lock is poisoned".to_string())?; if codex_native_oauth_enabled(home)? { return Ok(false); } let before = config_images(&config_paths(client.id(), home)?)?; validate_config_images(&before)?; @@ -1376,13 +1376,13 @@ pub(crate) fn sync_codex_model_catalog_if_configured( validate_codex_catalog(catalog, model)?; } else { let root: serde_json::Value = serde_json::from_str(catalog) - .map_err(|error| format!("Codex 模型目录格式无效: {error}"))?; + .map_err(|error| format!("Invalid Codex model catalog format: {error}"))?; if !root .get("models") .and_then(serde_json::Value::as_array) .is_some_and(Vec::is_empty) { - return Err("空模型列表必须对应空的 Codex 模型目录".to_string()); + return Err("An empty model list must correspond to an empty Codex model catalog".to_string()); } } @@ -1396,10 +1396,10 @@ pub(crate) fn sync_codex_model_catalog_if_configured( } if current_model.as_deref() != next_model { let content = fs::read_to_string(&paths[0]) - .map_err(|error| format!("读取 Codex 配置失败: {error}"))?; + .map_err(|error| format!("Failed to read Codex configuration: {error}"))?; let mut document = content .parse::() - .map_err(|error| format!("Codex config.toml 格式无效: {error}"))?; + .map_err(|error| format!("Invalid Codex config.toml format: {error}"))?; if let Some(model) = next_model { set_codex_table_item(document.as_table_mut(), "model", toml_edit::value(model)); } else { @@ -1466,7 +1466,7 @@ pub(crate) fn apply_agent_configuration_with_oauth( if client == AgentClient::WorkBuddy { error } else { - "配置构建失败,请检查配置结构或使用手动备份恢复、基础配置模板修复".to_string() + "Failed to build configuration. Check its structure, restore from a manual backup, or repair it with the base configuration template".to_string() } })?; config_updates(client.id(), home, &before, &updates, "update", Some(model.to_string()), mappings) @@ -1559,7 +1559,7 @@ pub(crate) fn build_agent_template_updates( }, )?; if paths.len() != contents.len() { - return Err("基础配置模板文件数量不匹配".to_string()); + return Err("Base configuration template file count mismatch".to_string()); } let mut updates = paths .into_iter() @@ -1567,7 +1567,7 @@ pub(crate) fn build_agent_template_updates( .map(|(path, after)| AgentFileUpdate { path, after }) .collect::>(); if client == AgentClient::Codex { - let catalog = codex_catalog.ok_or_else(|| "无法生成 Codex 模型目录".to_string())?; + let catalog = codex_catalog.ok_or_else(|| "Unable to generate Codex model catalog".to_string())?; validate_codex_catalog(catalog, model)?; updates.push(AgentFileUpdate { path: codex_model_catalog_path(home), @@ -1600,13 +1600,13 @@ pub(crate) fn restore_agent_record_files( let result = if file.existed_before { let backup = fs::read(&file.backup_path).map_err(|error| { format!( - "读取原配置备份失败 {}: {error}", + "Failed to read original configuration backup {}: {error}", path_to_string(&file.backup_path) ) })?; if Some(sha256_bytes(&backup)) != file.original_sha256 { return Err(format!( - "原配置备份校验失败: {}", + "Original configuration backup verification failed: {}", path_to_string(&file.backup_path) )); } @@ -1619,7 +1619,7 @@ pub(crate) fn restore_agent_record_files( } else if file.path.exists() { changed.push(path_to_string(&file.path)); fs::remove_file(&file.path).map_err(|error| { - format!("删除智能体配置失败 {}: {error}", path_to_string(&file.path)) + format!("Failed to delete agent configuration {}: {error}", path_to_string(&file.path)) }) } else { Ok(()) @@ -1628,7 +1628,7 @@ pub(crate) fn restore_agent_record_files( let rollback = restore_snapshots_with_direct_path(&snapshots, direct_write_path); return Err(match rollback { Ok(()) => error, - Err(rollback_error) => format!("{error};回滚失败: {rollback_error}"), + Err(rollback_error) => format!("{error}; rollback failed: {rollback_error}"), }); } } @@ -1644,7 +1644,7 @@ pub(crate) fn discard_prepared_agent_backups( if file.backup_path.exists() { if let Err(error) = fs::remove_file(&file.backup_path) { errors.push(format!( - "删除未启用的智能体备份失败 {}: {error}", + "Failed to delete disabled agent backup {}: {error}", path_to_string(&file.backup_path) )); } @@ -1665,7 +1665,7 @@ pub(crate) fn cleanup_agent_record( if state_path.exists() { fs::remove_file(state_path).map_err(|error| { format!( - "删除智能体备份状态失败 {}: {error}", + "Failed to delete agent backup state {}: {error}", path_to_string(state_path) ) })?; @@ -1674,7 +1674,7 @@ pub(crate) fn cleanup_agent_record( if file.backup_path.exists() { if let Err(error) = fs::remove_file(&file.backup_path) { eprintln!( - "清理智能体备份失败 {}: {error}", + "Failed to clean up agent backup {}: {error}", path_to_string(&file.backup_path) ); } @@ -1736,7 +1736,7 @@ pub(crate) fn enable_agent_modification( } } return Err( - "检测到 CPA 配置,但缺少可安全恢复的原始备份;请先手动恢复客户端配置".to_string(), + "CPA configuration detected, but no original backup is available for safe restoration. Restore the client configuration manually first".to_string(), ); } @@ -1781,7 +1781,7 @@ pub(crate) fn enable_agent_modification( Err(restore_error) => { record.phase = AGENT_PHASE_RECOVERY.to_string(); let _ = write_agent_state(&state_path, &record); - Err(format!("{error};恢复原配置失败: {restore_error}")) + Err(format!("{error}; failed to restore original configuration: {restore_error}")) } }, } @@ -1810,9 +1810,9 @@ pub(crate) fn disable_agent_modification( Vec::new(), )); } - let model = model.ok_or_else(|| "无法识别当前 CPA 模型".to_string())?; + let model = model.ok_or_else(|| "Unable to identify the current CPA model".to_string())?; let record = build_legacy_agent_record(client, home, port, &model)? - .ok_or_else(|| "检测到 CPA 配置,但缺少可安全恢复的原始备份".to_string())?; + .ok_or_else(|| "CPA configuration detected, but no original backup is available for safe restoration".to_string())?; write_agent_state(&state_path, &record)?; record } @@ -1829,7 +1829,7 @@ pub(crate) fn disable_agent_modification( Err(error) => { record.phase = AGENT_PHASE_RECOVERY.to_string(); let _ = write_agent_state(&state_path, &record); - Err(format!("恢复原配置失败: {error}")) + Err(format!("Failed to restore original configuration: {error}")) } }; } @@ -1866,7 +1866,7 @@ pub(crate) fn disable_agent_modification( Err(error) => { record.phase = AGENT_PHASE_RECOVERY.to_string(); let _ = write_agent_state(&state_path, &record); - Err(format!("恢复原配置失败: {error}")) + Err(format!("Failed to restore original configuration: {error}")) } } } @@ -1888,23 +1888,23 @@ pub(crate) fn update_agent_modification( let (_, current_model, _) = inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?; if !agent_has_managed_marker(client, &paths)? { - return Err("请先应用配置修改".to_string()); + return Err("Apply configuration changes first".to_string()); } - let current_model = current_model.ok_or_else(|| "无法识别当前 CPA 模型".to_string())?; + let current_model = current_model.ok_or_else(|| "Unable to identify the current CPA model".to_string())?; let record = build_legacy_agent_record(client, home, port, ¤t_model)? - .ok_or_else(|| "缺少原配置备份,无法安全更新".to_string())?; + .ok_or_else(|| "Original configuration backup is missing; cannot update safely".to_string())?; write_agent_state(&state_path, &record)?; record } }; if record.phase != AGENT_PHASE_ACTIVE { - return Err("上次配置操作尚未完整结束,请先使用“清除修改”恢复原配置".to_string()); + return Err("The last configuration operation has not completed. Use “Clear Changes” to restore the original configuration first".to_string()); } if client != AgentClient::Codex { let conflicts = record_conflict_files(&record)?; if !conflicts.is_empty() { return Err(format!( - "配置已被其他程序修改,无法更新: {}", + "Configuration was modified by another program and cannot be updated: {}", conflicts.join("、") )); } @@ -1924,7 +1924,7 @@ pub(crate) fn update_agent_modification( next.model = model.to_string(); for (file, update) in next.files.iter_mut().zip(&updates) { if file.path != update.path { - return Err("智能体配置更新路径不匹配".to_string()); + return Err("Agent configuration update path mismatch".to_string()); } file.managed_sha256 = sha256_bytes(update.after.as_bytes()); } @@ -1932,7 +1932,7 @@ pub(crate) fn update_agent_modification( let rollback = restore_snapshots(&backup_snapshots); return Err(match rollback { Ok(()) => error, - Err(rollback_error) => format!("{error};恢复模型目录备份失败: {rollback_error}"), + Err(rollback_error) => format!("{error}; failed to restore model catalog backup: {rollback_error}"), }); } match apply_agent_updates(client, &updates) { @@ -1952,10 +1952,10 @@ pub(crate) fn update_agent_modification( let backup_rollback = restore_snapshots(&backup_snapshots).err(); let mut errors = vec![error]; if let Some(rollback_error) = state_rollback { - errors.push(format!("恢复原状态失败: {rollback_error}")); + errors.push(format!("Failed to restore original state: {rollback_error}")); } if let Some(rollback_error) = backup_rollback { - errors.push(format!("恢复模型目录备份失败: {rollback_error}")); + errors.push(format!("Failed to restore model catalog backup: {rollback_error}")); } Err(errors.join(";")) } diff --git a/src-tauri/src/agents/templates.rs b/src-tauri/src/agents/templates.rs index 8c54ec3..99cbcc8 100644 --- a/src-tauri/src/agents/templates.rs +++ b/src-tauri/src/agents/templates.rs @@ -35,7 +35,7 @@ pub(crate) fn build_codex_template_auth(home: &Path) -> Result Result, String> { if port == 0 || api_key.trim().is_empty() { - return Err("CPA 地址或密钥无效".into()); + return Err("Invalid CPA address or key".into()); } let settings = serde_json::json!({"packages": [PI_CLIPROXYAPI_PACKAGE]}).to_string(); Ok(vec![ @@ -86,29 +86,36 @@ pub(crate) async fn prepare_desktop_core_update( } pub(crate) fn agent_core_error(error: String) -> String { - if error.contains("自动恢复失败") || error.contains("回滚失败") { - "内核别名或智能体配置写入失败且回滚失败,请检查当前配置".into() - } else if error.contains("已恢复原配置") { - "内核别名或智能体配置写入失败,已恢复原配置,请检查连接和模型映射后重试".into() - } else if error.contains("配置已变化") { - "内核配置已变化,请重新预览后重试".into() - } else if error.contains("已被其他模型使用") { - "模型别名已被其他模型占用,请切换其他别名后重试".into() - } else if error.contains("无法确定模型") && error.contains("CPA 配置来源") { - "无法找到原模型的有效接入来源,请确认接入已启用且未屏蔽原模型或别名,刷新模型列表后重试".into() - } else if error.starts_with("更新后的内核配置与预期值不一致") - || error.starts_with("验证更新后的内核配置失败") + let normalized = error.to_ascii_lowercase(); + if normalized.contains("automatic restoration failed") + || normalized.contains("rollback failed") + || normalized.contains("rollback both failed") { - "内核配置格式兼容性校验失败,已取消别名同步,原配置未写入".into() - } else if error.starts_with("解析内核 YAML 配置失败") { - "内核 YAML 配置格式无效,请检查配置格式后重试".into() - } else if error.starts_with("管理 API 错误 (401)") - || error.starts_with("管理 API 错误 (403)") - || error.starts_with("管理接口不可用") + "Kernel alias or agent configuration write failed, and rollback also failed. Check the current configuration".into() + } else if normalized.contains("original configuration was restored") { + "Kernel alias or agent configuration write failed. Original configuration was restored. Check the connection and model mapping, then try again".into() + } else if normalized.contains("configuration changed") { + "Kernel configuration changed. Preview it again and retry".into() + } else if normalized.contains("already used by another model") { + "Model alias is already used by another model. Choose a different alias and try again".into() + } else if normalized.contains("unable to determine") + && normalized.contains("cpa configuration source") + && normalized.contains("model") { - "内核管理接口认证失败,请检查管理密钥后重试".into() + "Unable to find a valid access source for the original model. Make sure access is enabled and the original model or alias is not blocked, then refresh the model list and try again".into() + } else if normalized.starts_with("updated kernel configuration does not match the expected value") + || normalized.starts_with("failed to validate updated kernel configuration") + { + "Kernel configuration format compatibility check failed. Alias synchronization was canceled, and the original configuration was not written".into() + } else if normalized.starts_with("failed to parse kernel yaml configuration") { + "Invalid kernel YAML configuration format. Check the format and try again".into() + } else if normalized.starts_with("management api error (401)") + || normalized.starts_with("management api error (403)") + || normalized.starts_with("management interface unavailable") + { + "Kernel management interface authentication failed. Check the management key and try again".into() } else { - "内核模型别名同步失败,请检查内核连接和模型映射".into() + "Kernel model alias synchronization failed. Check the kernel connection and model mapping".into() } } @@ -144,7 +151,7 @@ async fn prepare_template_plan( let model = resolve_pi_default_model(config, model).await?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "配置文件锁已损坏")?; + .map_err(|_| "Configuration file lock is poisoned")?; let before = config_images(&paths)?; let updates = build_pi_template_updates(home, config.port, api_key, &model)?; let after = prepare_config_updates(client, &paths, &before, &updates, true)?; @@ -169,7 +176,7 @@ async fn prepare_template_plan( )?; let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "配置文件锁已损坏")?; + .map_err(|_| "Configuration file lock is poisoned")?; let before = config_images(&paths)?; let updates = build_agent_template_updates(AgentDefaultConfiguration { client: parsed, @@ -210,7 +217,7 @@ async fn prepare_template_plan( &mappings, core_revision, )) - .map_err(|_| "生成模板预览失败")?, + .map_err(|_| "Failed to generate template preview")?, ); let preview = TemplatePreview { revision, @@ -235,14 +242,14 @@ async fn execute_template_plan( revision: &str, ) -> Result { if revision != plan.preview.revision { - return Err("预览后配置或模板发生变化,请重新预览基础配置模板".into()); + return Err("Configuration or template changed after preview. Preview the base configuration template again".into()); } let commit = || { let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "配置文件锁已损坏")?; + .map_err(|_| "Configuration file lock is poisoned")?; if mapping_revision(&plan.client, &plan.paths)? != plan.mapping_revision { - return Err("模型映射已变化,请重新预览".into()); + return Err("Model mapping changed. Preview it again".into()); } commit_config_with_mappings( &plan.client, @@ -273,7 +280,7 @@ pub(crate) async fn preview_agent_config_template( claude_code_model_mappings: Option, claude_desktop_model_mappings: Option, ) -> Result { - let home = app.path().home_dir().map_err(|_| "无法获取用户目录")?; + let home = app.path().home_dir().map_err(|_| "Failed to get user directory")?; let config = app.state::().snapshot()?; Ok(prepare_template_plan( &config, @@ -298,7 +305,7 @@ pub(crate) async fn apply_agent_config_template( claude_desktop_model_mappings: Option, revision: String, ) -> Result { - let home = app.path().home_dir().map_err(|_| "无法获取用户目录")?; + let home = app.path().home_dir().map_err(|_| "Failed to get user directory")?; let config = app.state::().snapshot()?; let plan = prepare_template_plan( &config, diff --git a/src-tauri/src/agents/templates/tests.rs b/src-tauri/src/agents/templates/tests.rs index 56d9c0d..1b36543 100644 --- a/src-tauri/src/agents/templates/tests.rs +++ b/src-tauri/src/agents/templates/tests.rs @@ -4,14 +4,14 @@ use super::*; fn core_failure_messages_expose_outcomes_without_secret_source_text() { for detail in [ "YAML source: key: secret-token", - "已恢复原配置 secret-token", - "自动恢复失败 secret-token", - "配置已变化 secret-token", + "Original configuration restored secret-token", + "Automatic restoration failed secret-token", + "Configuration changed secret-token", ] { let rendered = agent_core_error(detail.into()); assert!(!rendered.contains("secret-token")); - if detail.contains("自动恢复失败") { - assert!(rendered.contains("回滚失败")); + if detail.contains("Automatic restoration failed") { + assert!(rendered.contains("rollback also failed")); } } } @@ -19,13 +19,28 @@ fn core_failure_messages_expose_outcomes_without_secret_source_text() { #[test] fn core_failure_messages_distinguish_alias_and_configuration_failures() { for (detail, expected) in [ - ("别名 claude-sonnet-5 已被其他模型使用,请更换别名 secret-token", "请切换其他别名"), - ("无法确定模型 private-model 的 CPA 配置来源,无法创建 Claude Desktop 别名 secret-token", "有效接入来源"), - ("更新后的内核配置与预期值不一致(路径: secret-token),已拒绝写入", "格式兼容性校验失败"), - ("验证更新后的内核配置失败: secret-token", "原配置未写入"), - ("解析内核 YAML 配置失败: secret-token", "YAML 配置格式无效"), - ("管理 API 错误 (401): secret-token", "认证失败"), - ("管理 API 错误 (403): secret-token", "认证失败"), + ( + "Already used by another model: claude-sonnet-5 secret-token", + "Choose a different alias", + ), + ( + "Unable to determine model private-model CPA configuration source; cannot create Claude Desktop alias secret-token", + "valid access source", + ), + ( + "Updated kernel configuration does not match the expected value (path: secret-token); write rejected", + "format compatibility check failed", + ), + ( + "Failed to validate updated kernel configuration: secret-token", + "original configuration was not written", + ), + ( + "Failed to parse kernel YAML configuration: secret-token", + "Invalid kernel YAML configuration format", + ), + ("Management API error (401): secret-token", "authentication failed"), + ("Management API error (403): secret-token", "authentication failed"), ] { let rendered = agent_core_error(detail.into()); assert!(rendered.contains(expected), "{rendered}"); diff --git a/src-tauri/src/agents/transactions.rs b/src-tauri/src/agents/transactions.rs index 06b4af6..8c841e1 100644 --- a/src-tauri/src/agents/transactions.rs +++ b/src-tauri/src/agents/transactions.rs @@ -31,7 +31,7 @@ fn integration_managed_paths<'a>(client: &str, paths: &'a [PathBuf]) -> &'a [Pat fn integration_state_path(client: &str, paths: &[PathBuf]) -> Result { let paths = integration_managed_paths(client, paths); let identity = sha256_bytes( - &serde_json::to_vec(paths).map_err(|_| "生成接入配置标识失败".to_string())?, + &serde_json::to_vec(paths).map_err(|_| "Failed to generate access configuration marker".to_string())?, ); Ok(agent_data_directory(paths)? .join("agents") @@ -49,7 +49,7 @@ fn parse_integration_state( return Ok(None); }; let state: AgentIntegrationState = serde_json::from_slice(bytes) - .map_err(|_| "接入配置合并状态损坏,请先手动备份配置".to_string())?; + .map_err(|_| "Access configuration merge state is corrupted. Back up the configuration manually first".to_string())?; let paths = integration_managed_paths(client, paths); let valid_images = |images: &Images| { images.len() == paths.len() @@ -64,13 +64,13 @@ fn parse_integration_state( || !valid_images(&state.original) || !valid_images(&state.applied) { - return Err("接入配置合并状态与当前客户端不匹配,请先手动备份配置".into()); + return Err("Access configuration merge state does not match the current client. Back up the configuration manually first".into()); } Ok(Some(state)) } fn integration_state_bytes(state: &AgentIntegrationState) -> Result, String> { - serde_json::to_vec(state).map_err(|_| "生成接入配置合并状态失败".to_string()) + serde_json::to_vec(state).map_err(|_| "Failed to generate access configuration merge state".to_string()) } fn merge_user_value( @@ -126,18 +126,18 @@ fn merge_user_image( return Ok(None); }; let current_text = std::str::from_utf8(current) - .map_err(|_| format!("配置不是 UTF-8 文本: {}", path_to_string(path)))?; + .map_err(|_| format!("Configuration is not UTF-8 text: {}", path_to_string(path)))?; let applied_value = parse( path, applied - .map(|bytes| std::str::from_utf8(bytes).map_err(|_| "接入状态不是 UTF-8 文本")) + .map(|bytes| std::str::from_utf8(bytes).map_err(|_| "Access state is not UTF-8 text")) .transpose()?, )?; let current_value = parse(path, Some(current_text))?; let baseline_value = parse( path, baseline - .map(|bytes| std::str::from_utf8(bytes).map_err(|_| "原始状态不是 UTF-8 文本")) + .map(|bytes| std::str::from_utf8(bytes).map_err(|_| "Original state is not UTF-8 text")) .transpose()?, )?; let mut merged = merge_user_value( @@ -240,7 +240,7 @@ pub(crate) fn config_paths(client: &str, home: &Path) -> Result, St } let client = AgentClient::parse(client)?; if !client.supported_platform() { - return Err("当前平台不支持此智能体配置".into()); + return Err("The current platform does not support this agent configuration".into()); } Ok(expected_agent_record_paths( client, @@ -254,7 +254,7 @@ pub(crate) fn validate_config_path(path: &Path) -> Result<(), String> { .components() .any(|c| matches!(c, std::path::Component::ParentDir)) { - return Err("配置路径必须是绝对路径且不能越界".into()); + return Err("Configuration path must be absolute and remain within bounds".into()); } for ancestor in path.ancestors() { match fs::symlink_metadata(ancestor) { @@ -267,16 +267,16 @@ pub(crate) fn validate_config_path(path: &Path) -> Result<(), String> { }; if linked { return Err(format!( - "配置路径不能包含符号链接: {}", + "Configuration path cannot contain symbolic links: {}", path_to_string(ancestor) )); } if ancestor == path && !meta.is_file() { - return Err("配置路径不是普通文件".into()); + return Err("Configuration path is not a regular file".into()); } } Err(e) if e.kind() == io::ErrorKind::NotFound => {} - Err(_) => return Err("无法检查配置路径".into()), + Err(_) => return Err("Unable to check configuration path".into()), } } Ok(()) @@ -321,10 +321,10 @@ pub(crate) fn write_config_images(client: &str, images: &Images) -> Result<(), S write_agent_configuration_file(AgentClient::parse(client)?, path, bytes)?; } } else { - fs::remove_file(path).map_err(|_| "删除配置文件失败".to_string())?; + fs::remove_file(path).map_err(|_| "Failed to delete configuration file".to_string())?; } if read_agent_bytes(path)? != *bytes { - return Err("配置写后校验失败".into()); + return Err("Configuration post-write verification failed".into()); } } Ok(()) @@ -401,10 +401,10 @@ fn commit_config_transaction( .zip(paths) .any(|((p, _), expected)| p != expected) { - return Err("配置更新路径不匹配".into()); + return Err("Configuration update path mismatch".into()); } if config_images(paths)? != *before { - return Err("配置已被其他程序修改,请刷新后重试".into()); + return Err("Configuration was modified by another program. Refresh and try again".into()); } let mut previous = before.clone(); let mut target = after.clone(); @@ -500,14 +500,14 @@ fn commit_config_transaction( } let all_paths = previous.iter().map(|(p, _)| p.clone()).collect::>(); if config_images(&all_paths)? != previous { - return Err("配置已被其他程序修改,请刷新后重试".into()); + return Err("Configuration was modified by another program. Refresh and try again".into()); } let mut attempted = Vec::new(); let result = (if checked { (|| { for (index, ((path, old), (_, next))) in previous.iter().zip(&target).enumerate() { if read_agent_bytes(path)? != *old { - return Err("配置已被其他程序修改".into()); + return Err("Configuration was modified by another program".into()); } if old == next { continue; @@ -525,7 +525,7 @@ fn commit_config_transaction( if config_images(&all_paths)? == target { Ok(()) } else { - Err("配置写后校验失败".into()) + Err("Configuration post-write verification failed".into()) } }); if result.is_err() { @@ -540,7 +540,7 @@ fn commit_config_transaction( failed |= writer(client, &vec![previous[index].clone()]).is_err(); } if failed { - Err("回滚失败".into()) + Err("Rollback failed".into()) } else { Ok(()) } @@ -551,13 +551,13 @@ fn commit_config_transaction( if config_images(&all_paths)? == previous { Ok(()) } else { - Err("回滚校验失败".into()) + Err("Rollback verification failed".into()) } }); return Err(if rollback.is_ok() { - "配置写入失败,已回滚本次修改" + "Configuration write failed; this change was rolled back" } else { - "配置写入失败且回滚失败,请检查配置并使用手动备份恢复或基础配置模板修复" + "Configuration write and rollback both failed. Check the configuration and restore it from a manual backup or repair it with the base configuration template" } .into()); } @@ -581,7 +581,7 @@ pub(crate) fn validate_client_config_images(client: &str, images: &Images) -> Re } for (path, bytes) in images { if !parse(path, text(bytes.as_deref())?)?.is_object() { - return Err("配置根节点必须是对象".into()); + return Err("Configuration root must be an object".into()); } } if matches!(client, "opencode" | "openclaw") { @@ -597,7 +597,7 @@ pub(crate) fn validate_client_config_images(client: &str, images: &Images) -> Re if let Some(content) = text(bytes.as_deref())? { serde_json::from_str::(content.strip_prefix('\u{feff}').unwrap_or(content)) .map_err(|_| { - "配置 JSON 格式错误,请使用手动备份恢复或基础配置模板修复".to_string() + "Invalid configuration JSON. Restore it from a manual backup or repair it with the base configuration template".to_string() })?; } } @@ -619,7 +619,7 @@ pub(crate) fn prepare_config_updates( let entry = after .iter_mut() .find(|(path, _)| path == &update.path) - .ok_or("配置更新路径不匹配")?; + .ok_or("Configuration update path mismatch")?; let mut rendered = update.after.clone(); let mut generated = parse(&update.path, Some(&rendered))?; if !template { @@ -653,7 +653,7 @@ pub(crate) fn prepare_config_updates( .iter() .any(|p| updates.iter().filter(|u| &u.path == p).count() != 1)) { - return Err("基础配置模板必须覆盖整组文件".into()); + return Err("Base configuration template must cover the entire file group".into()); } validate_client_config_images(client, &after)?; Ok(after) @@ -809,7 +809,7 @@ pub(crate) fn config_package_operation( ) -> Result<(), String> { let _guard = AGENT_CONFIG_FILE_LOCK .lock() - .map_err(|_| "配置文件锁已损坏")?; + .map_err(|_| "Configuration file lock is poisoned")?; let paths = config_paths(PI_AGENT_ID, home)?; let before = config_images(&paths)?; validate_config_images(&before)?; @@ -818,8 +818,8 @@ pub(crate) fn config_package_operation( .is_err() { return match write_config_images(PI_AGENT_ID, &before) { - Ok(()) => Err("插件操作失败,已回滚本次配置修改".into()), - Err(_) => Err("插件操作失败且配置回滚失败,请检查配置".into()), + Ok(()) => Err("Plugin operation failed; this configuration change was rolled back".into()), + Err(_) => Err("Plugin operation and configuration rollback both failed. Check the configuration".into()), }; } Ok(()) @@ -894,7 +894,7 @@ fn validate_unmanaged_preserved( }; if project(before)? != project(after)? { return Err(format!( - "更新意外改变了自定义配置,已拒绝写入: {}", + "The update unexpectedly changed custom configuration and was not written: {}", path_to_string(path) )); } @@ -903,7 +903,7 @@ fn validate_unmanaged_preserved( pub(crate) fn text(bytes: Option<&[u8]>) -> Result, String> { bytes - .map(|bytes| std::str::from_utf8(bytes).map_err(|_| "配置不是 UTF-8 文本".to_string())) + .map(|bytes| std::str::from_utf8(bytes).map_err(|_| "Configuration is not UTF-8 text".to_string())) .transpose() } @@ -921,13 +921,13 @@ pub(crate) fn parse(path: &Path, content: Option<&str>) -> Result }; let value = result.map_err(|_| { format!( - "{} 配置格式错误,请使用手动备份恢复或基础配置模板修复", + "Invalid {} configuration format. Restore it from a manual backup or repair it with the base configuration template", path_to_string(path) ) })?; // WorkBuddy also saves models.json as a top-level array. if !value.is_object() && !(path.file_name().is_some_and(|n| n == "models.json") && value.is_array()) { - return Err(format!("{} 配置根节点必须是对象", path_to_string(path))); + return Err(format!("{} configuration root must be an object", path_to_string(path))); } Ok(value) } diff --git a/src-tauri/src/agents/windows_probe.rs b/src-tauri/src/agents/windows_probe.rs index bfa35c2..71306f6 100644 --- a/src-tauri/src/agents/windows_probe.rs +++ b/src-tauri/src/agents/windows_probe.rs @@ -823,7 +823,7 @@ pub(crate) fn stop_windows_matching_processes( .collect::>() .join(", "); return Err(format!( - "{label} 未能完全关闭; remaining process IDs: {ids}" + "{label} did not close completely; remaining process IDs: {ids}" )); } for process_id in remaining { diff --git a/src-tauri/src/agents/workbuddy.rs b/src-tauri/src/agents/workbuddy.rs index ebe733a..574cc67 100644 --- a/src-tauri/src/agents/workbuddy.rs +++ b/src-tauri/src/agents/workbuddy.rs @@ -112,7 +112,7 @@ pub(crate) fn read_workbuddy_app_version(executable: &Path) -> Option { pub(crate) fn parse_workbuddy_config(content: Option<&str>) -> Result { let value = match content { Some(content) => serde_json::from_str(content.strip_prefix('\u{feff}').unwrap_or(content)) - .map_err(|_| "WorkBuddy models.json 格式无效,请使用手动备份恢复或基础配置模板修复")?, + .map_err(|_| "Invalid WorkBuddy models.json format. Restore it from a manual backup or repair it with the base configuration template")?, None => json!({}), }; validate_workbuddy_config(&value)?; @@ -127,14 +127,14 @@ pub(crate) fn validate_workbuddy_config(value: &Value) -> Result<(), String> { v.as_array() .is_none_or(|a| a.iter().any(|v| !v.is_string())) }) { - return Err("WorkBuddy availableModels 必须是字符串数组".into()); + return Err("WorkBuddy availableModels must be an array of strings".into()); } match root.get("models") { None => None, - Some(v) => Some(v.as_array().ok_or("WorkBuddy models 必须是数组")?), + Some(v) => Some(v.as_array().ok_or("WorkBuddy models must be an array")?), } } else { - return Err("WorkBuddy models.json 根节点必须是对象或数组".into()); + return Err("WorkBuddy models.json root must be an object or array".into()); }; let mut ids = std::collections::HashSet::new(); for entry in entries.into_iter().flatten() { @@ -142,9 +142,9 @@ pub(crate) fn validate_workbuddy_config(value: &Value) -> Result<(), String> { .get("id") .and_then(Value::as_str) .filter(|id| !id.trim().is_empty()) - .ok_or("WorkBuddy 模型缺少有效 id")?; + .ok_or("WorkBuddy model is missing a valid id")?; if !ids.insert(workbuddy_request_model(id)) { - return Err("WorkBuddy 存在重复模型 id,请先修复配置".into()); + return Err("WorkBuddy contains duplicate model IDs. Fix the configuration first".into()); } } Ok(()) @@ -195,7 +195,7 @@ pub(crate) fn build_workbuddy_agent_config( .is_some_and(|id| workbuddy_request_model(id) == model) }) { return Err( - "WorkBuddy 已有同名自定义模型,请先在 WorkBuddy 中修改该模型 ID,或选择其他 CPA 模型" + "WorkBuddy already has a custom model with the same name. Change that model ID in WorkBuddy or select another CPA model" .into(), ); } @@ -217,7 +217,7 @@ pub(crate) fn build_workbuddy_agent_config( .iter() .filter(|m| workbuddy_managed_model(m)) { - let id = previous["id"].as_str().ok_or("WorkBuddy 模型缺少有效 id")?; + let id = previous["id"].as_str().ok_or("WorkBuddy model is missing a valid id")?; let previous_model = workbuddy_request_model(id); if previous_model == model { continue; @@ -245,7 +245,7 @@ pub(crate) fn build_workbuddy_agent_config( visible.push(json!(model)); } } - serde_json::to_string_pretty(&root).map_err(|_| "生成 WorkBuddy 配置失败".into()) + serde_json::to_string_pretty(&root).map_err(|_| "Failed to generate WorkBuddy configuration".into()) } fn update_workbuddy_model( @@ -415,7 +415,7 @@ pub(crate) fn validate_workbuddy_unmanaged_preserved( value }; if project(before) != project(after) { - return Err("更新意外改变了 WorkBuddy 自定义配置,已拒绝写入".into()); + return Err("The update unexpectedly changed WorkBuddy custom configuration and was not written".into()); } Ok(()) } @@ -449,11 +449,11 @@ pub(crate) fn build_restored_workbuddy_config( } serde_json::to_string_pretty(&root) .map(Some) - .map_err(|_| "恢复 WorkBuddy 配置失败".into()) + .map_err(|_| "Failed to restore WorkBuddy configuration".into()) } pub(crate) fn prepare_workbuddy_managed_removal(paths: &[PathBuf]) -> Result { - let path = paths.first().ok_or("WorkBuddy 配置路径不可用")?; + let path = paths.first().ok_or("WorkBuddy configuration path is unavailable")?; let Some(current) = read_optional_text(path)? else { return Ok(Vec::new()); }; @@ -467,7 +467,7 @@ pub(crate) fn prepare_workbuddy_managed_removal(paths: &[PathBuf]) -> Result Result { let window = app .get_webview_window("main") - .ok_or_else(|| "主窗口不存在".to_string())?; + .ok_or_else(|| "Main window does not exist".to_string())?; window .hide() - .map_err(|error| format!("隐藏主窗口失败: {error}"))?; + .map_err(|error| format!("Failed to hide main window: {error}"))?; } } @@ -593,7 +593,7 @@ pub(crate) fn resolve_windows_close_request( pub(crate) fn app_autostart_enabled(app: &tauri::AppHandle) -> Result { app.autolaunch() .is_enabled() - .map_err(|error| format!("读取系统开机自启状态失败: {error}")) + .map_err(|error| format!("Failed to read system startup status: {error}")) } pub(crate) fn set_app_autostart_enabled( @@ -604,11 +604,11 @@ pub(crate) fn set_app_autostart_enabled( if enabled { manager .enable() - .map_err(|error| format!("启用开机自启失败: {error}")) + .map_err(|error| format!("Failed to enable launch at startup: {error}")) } else { manager .disable() - .map_err(|error| format!("关闭开机自启失败: {error}")) + .map_err(|error| format!("Failed to disable launch at startup: {error}")) } } @@ -670,7 +670,7 @@ pub(crate) fn save_software_settings( .flatten(); return Err(match rollback_error { Some(rollback_error) => { - format!("{error}; 回滚开机自启设置也失败: {rollback_error}") + format!("{error}; failed to roll back startup settings: {rollback_error}") } None => error, }); diff --git a/src-tauri/src/app_update.rs b/src-tauri/src/app_update.rs index c254242..611cc70 100644 --- a/src-tauri/src/app_update.rs +++ b/src-tauri/src/app_update.rs @@ -52,11 +52,11 @@ pub(crate) fn set_download_source( VersionDownloadCandidate::builtin( VersionDownloadSource::from_str(&source) .filter(|source| *source != VersionDownloadSource::Custom) - .ok_or_else(|| "不支持的下载源".to_string())?, + .ok_or_else(|| "Unsupported download source".to_string())?, ) }; if candidate.source == VersionDownloadSource::Gitcode && !gitcode_version_source_available() { - return Err("当前构建未配置完整的 GitCode 软件与内核镜像源".to_string()); + return Err("This build does not include complete GitCode application and kernel mirror sources".to_string()); } let config = gui_config_state.set_download_candidate(candidate)?; Ok(version_source_settings(&config)) @@ -71,7 +71,7 @@ pub(crate) fn add_custom_download_mirror( let config = gui_config_state.update(|config| { if !config.custom_download_mirrors.contains(&url) { if config.custom_download_mirrors.len() >= 12 { - return Err("最多可添加 12 个自定义镜像".to_string()); + return Err("You can add up to 12 custom mirrors".to_string()); } config.custom_download_mirrors.push(url.clone()); } @@ -113,7 +113,7 @@ pub(crate) fn set_prefer_gitcode_downloads( VersionDownloadSource::Github }; if enabled && !gitcode_version_source_available() { - return Err("当前构建未配置完整的 GitCode 软件与内核镜像源".to_string()); + return Err("This build does not include complete GitCode application and kernel mirror sources".to_string()); } let config = gui_config_state.set_download_source(source)?; Ok(version_source_settings(&config)) @@ -138,7 +138,7 @@ pub(crate) fn persist_automatic_download_source_switch( VERSION_DOWNLOAD_SOURCE_CHANGED_EVENT, version_source_settings(&config), ) - .map_err(|error| format!("通知下载源自动切换失败: {error}")) + .map_err(|error| format!("Failed to notify automatic download source switch: {error}")) } #[tauri::command] @@ -182,7 +182,7 @@ async fn resolve_app_update( .read_timeout(Duration::from_secs(15)) .timeout(Duration::from_secs(20)), &proxy_url, - "创建版本检查客户端失败", + "Failed to create version check client", )?; let requested_source = config.selected_download_candidate(); let (manifest, resolved_source) = fetch_portable_update_manifest( @@ -210,9 +210,9 @@ async fn resolve_app_update( let unsupported_reason = if auto_update_supported { None } else if portable_support != Some(true) { - Some("当前程序不是支持自动升级的便携版,请手动下载首个支持版本".to_string()) + Some("This application is not a portable version that supports automatic upgrades. Manually download the first supported version".to_string()) } else { - Some("更新清单不包含当前平台或架构".to_string()) + Some("The update manifest does not include the current platform or architecture".to_string()) }; let pending = if update_available && auto_update_supported { @@ -273,7 +273,7 @@ pub(crate) async fn fetch_portable_update_manifest( Err(error) => failures.push(format!("{}: {error}", candidate.display_name())), } } - Err(format!("所有软件版本检测源均失败: {}", failures.join("; "))) + Err(format!("All application version check sources failed: {}", failures.join("; "))) } pub(crate) async fn fetch_portable_update_manifest_from_gitcode( @@ -441,16 +441,16 @@ pub(crate) fn validate_portable_update_manifest( ) -> Result<(), String> { if manifest.schema_version != 1 { return Err(format!( - "不支持的软件更新清单版本: {}", + "Unsupported application update manifest version: {}", manifest.schema_version )); } semver::Version::parse(manifest.version.trim().trim_start_matches('v')) - .map_err(|error| format!("软件更新版本无效: {error}"))?; + .map_err(|error| format!("Invalid application update version: {error}"))?; chrono::DateTime::parse_from_rfc3339(manifest.published_at.trim()) - .map_err(|error| format!("软件更新发布时间无效: {error}"))?; + .map_err(|error| format!("Invalid application update release date: {error}"))?; let release_url = reqwest::Url::parse(&manifest.release_url) - .map_err(|_| "软件更新发布地址无效".to_string())?; + .map_err(|_| "Invalid application update release URL".to_string())?; if release_url.scheme() != "https" || release_url.host_str() != Some("github.com") || release_url.port().is_some() @@ -462,7 +462,7 @@ pub(crate) fn validate_portable_update_manifest( .path() .starts_with("/router-for-me/EasyCLIProxyAPI/releases/tag/v") { - return Err("软件更新发布地址不受信任".to_string()); + return Err("Untrusted application update release URL".to_string()); } let (platform, display_platform, suffix) = portable_update_asset_platform()?; validate_portable_update_asset_catalog( @@ -492,7 +492,7 @@ pub(crate) fn portable_update_asset_platform( Some("windows") => Ok(("windows", "Windows", "zip")), Some("linux") => Ok(("linux", "Linux", "tar.gz")), Some("darwin") => Ok(("darwin", "Darwin", "dmg")), - _ => Err("当前平台不支持应用内自动升级".to_string()), + _ => Err("The current platform does not support in-app automatic upgrades".to_string()), } } @@ -505,7 +505,7 @@ fn validate_portable_update_asset_catalog( allow_windows_legacy: bool, ) -> Result<(), String> { if assets.len() != 2 { - return Err(format!("软件更新清单必须包含两个 {display_platform} 架构")); + return Err(format!("The application update manifest must include both {display_platform} architectures")); } let version = manifest.version.trim().trim_start_matches('v'); let tag = format!("v{version}"); @@ -513,7 +513,7 @@ fn validate_portable_update_asset_catalog( let key = format!("{platform}-{arch}"); let asset = assets .get(&key) - .ok_or_else(|| format!("软件更新清单缺少 {key}"))?; + .ok_or_else(|| format!("Application update manifest is missing {key}"))?; validate_portable_update_asset(asset)?; let full_package_name = format!("EasyCLIProxyAPI-v{version}-{display_platform}-{arch}.{suffix}"); @@ -524,7 +524,7 @@ fn validate_portable_update_asset_catalog( let is_expected = asset.url == full_package_url || (allow_windows_legacy && asset.url == legacy_package_url); if !is_expected { - return Err(format!("软件更新资产名称与 {key} 不匹配")); + return Err(format!("Application update asset name does not match {key}")); } let mut expected_filenames = vec![full_package_name.as_str()]; if allow_windows_legacy { @@ -536,7 +536,7 @@ fn validate_portable_update_asset_catalog( } pub(crate) fn validate_portable_update_asset(asset: &PortableUpdateAsset) -> Result<(), String> { - let url = reqwest::Url::parse(&asset.url).map_err(|_| "软件更新下载地址无效".to_string())?; + let url = reqwest::Url::parse(&asset.url).map_err(|_| "Invalid application update download URL".to_string())?; if url.scheme() != "https" || url.host_str() != Some("github.com") || url.port().is_some() @@ -546,14 +546,14 @@ pub(crate) fn validate_portable_update_asset(asset: &PortableUpdateAsset) -> Res || url.fragment().is_some() || !asset.url.starts_with(APP_RELEASE_DOWNLOAD_PREFIX) { - return Err("软件更新下载地址不受信任".to_string()); + return Err("Untrusted application update download URL".to_string()); } if asset.size_bytes == 0 || asset.size_bytes > 512 * 1024 * 1024 { - return Err("软件更新包大小无效".to_string()); + return Err("Invalid application update package size".to_string()); } let digest = asset.sha256.trim().to_ascii_lowercase(); if digest.len() != 64 || !digest.bytes().all(|byte| byte.is_ascii_hexdigit()) { - return Err("软件更新 SHA-256 无效".to_string()); + return Err("Invalid application update SHA-256".to_string()); } Ok(()) } @@ -637,9 +637,9 @@ pub(crate) fn validate_local_portable_app_manifest(expected_arch: &str) -> Resul return Ok(false); } let contents = - fs::read_to_string(&path).map_err(|error| format!("读取便携版标识失败: {error}"))?; + fs::read_to_string(&path).map_err(|error| format!("Failed to read portable version marker: {error}"))?; let manifest = serde_json::from_str::(&contents) - .map_err(|error| format!("解析便携版标识失败: {error}"))?; + .map_err(|error| format!("Failed to parse portable version marker: {error}"))?; Ok(manifest.schema_version == 1 && manifest.application == "EasyCLIProxyAPI" && Some(manifest.platform.as_str()) == portable_update_platform_key() @@ -657,7 +657,7 @@ pub(crate) fn get_app_update_task(state: tauri::State<'_, AppUpdateState>) -> Ap pub(crate) fn cancel_app_update(state: tauri::State<'_, AppUpdateState>) -> Result<(), String> { let task = state.snapshot(); if !task.running || !task.cancellable { - return Err("当前应用更新阶段无法取消".to_string()); + return Err("The current application update stage cannot be canceled".to_string()); } state.cancel(); Ok(()) @@ -669,7 +669,7 @@ pub(crate) async fn start_app_update( state: tauri::State<'_, AppUpdateState>, ) -> Result<(), String> { if portable_update_platform_key().is_none() { - return Err("当前平台不支持应用内自动升级".to_string()); + return Err("The current platform does not support in-app automatic upgrades".to_string()); } let token = CancellationToken::new(); state.start(token.clone())?; @@ -686,9 +686,9 @@ pub(crate) async fn start_app_update( pending.ok_or_else(|| { if info.update_available { info.unsupported_reason - .unwrap_or_else(|| "当前版本不支持自动更新".to_string()) + .unwrap_or_else(|| "The current version does not support automatic updates".to_string()) } else { - "当前没有可安装的软件更新,请重新检查更新".to_string() + "No application update is currently available to install. Check for updates again".to_string() } }) }) @@ -711,7 +711,7 @@ pub(crate) async fn start_app_update( let task = state.finish( if cancelled { "cancelled" } else { "failed" }, Some(if cancelled { - "应用更新下载已取消".to_string() + "Application update download canceled".to_string() } else { error }), @@ -732,7 +732,7 @@ where { let pending = tokio::select! { biased; - _ = token.cancelled() => return Err("应用更新下载已取消".to_string()), + _ = token.cancelled() => return Err("Application update download canceled".to_string()), result = refresh => result?, }; state.start_download(&pending)?; @@ -749,7 +749,7 @@ pub(crate) async fn download_and_stage_portable_app_update( #[cfg(not(any(windows, target_os = "linux", target_os = "macos")))] { let _ = (app, pending, token, proxy_url, download_source); - Err("当前平台不支持应用内自动升级".to_string()) + Err("The current platform does not support in-app automatic upgrades".to_string()) } #[cfg(windows)] @@ -765,7 +765,7 @@ pub(crate) async fn download_and_stage_portable_app_update( .as_nanos() )); fs::create_dir_all(&work_dir) - .map_err(|error| format!("创建应用更新临时目录失败: {error}"))?; + .map_err(|error| format!("Failed to create temporary application update directory: {error}"))?; let archive_path = work_dir.join("update.zip"); let result = async { download_portable_update_archive( @@ -778,22 +778,22 @@ pub(crate) async fn download_and_stage_portable_app_update( ) .await?; if token.is_cancelled() { - return Err("应用更新下载已取消".to_string()); + return Err("Application update download canceled".to_string()); } update_app_task(app, |task| { task.cancellable = false; task.phase = "verifying".to_string(); - task.message = Some("正在校验应用更新包".to_string()); + task.message = Some("Verifying application update package".to_string()); }); let actual_sha256 = sha256_file(&archive_path)?; if actual_sha256 != pending.asset.sha256.trim().to_ascii_lowercase() { - return Err("应用更新包 SHA-256 校验失败".to_string()); + return Err("Application update package SHA-256 verification failed".to_string()); } update_app_task(app, |task| { task.phase = "staging".to_string(); - task.message = Some("正在准备应用更新".to_string()); + task.message = Some("Preparing application update".to_string()); }); let staging_dir = work_dir.join("staging"); let package = extract_portable_update_archive(&archive_path, &staging_dir)?; @@ -801,14 +801,14 @@ pub(crate) async fn download_and_stage_portable_app_update( || package.manifest.platform != "windows" || package.manifest.arch != pending.arch { - return Err("应用更新包版本或架构不匹配".to_string()); + return Err("Application update package version or architecture mismatch".to_string()); } let current_exe = - env::current_exe().map_err(|error| format!("读取当前程序路径失败: {error}"))?; + env::current_exe().map_err(|error| format!("Failed to read current executable path: {error}"))?; let app_dir = current_exe .parent() - .ok_or_else(|| "当前程序路径没有父目录".to_string())?; + .ok_or_else(|| "Current executable path has no parent directory".to_string())?; preflight_portable_update_directory(app_dir)?; let core_archive_name = match package.core_archive_name { Some(name) => name, @@ -816,7 +816,7 @@ pub(crate) async fn download_and_stage_portable_app_update( }; let helper_path = work_dir.join("EasyCLIProxyAPI-updater.exe"); fs::copy(¤t_exe, &helper_path) - .map_err(|error| format!("准备应用更新助手失败: {error}"))?; + .map_err(|error| format!("Failed to prepare application update helper: {error}"))?; let descriptor = PortableUpdateDescriptor { parent_pid: std::process::id(), @@ -851,7 +851,7 @@ pub(crate) async fn download_and_stage_portable_app_update( validate_portable_update_asset(&pending.asset)?; let work_dir = portable_update_work_dir(&pending.version); fs::create_dir_all(&work_dir) - .map_err(|error| format!("创建应用更新临时目录失败: {error}"))?; + .map_err(|error| format!("Failed to create temporary application update directory: {error}"))?; let archive_path = work_dir.join("update.tar.gz"); let result = async { download_portable_update_archive( @@ -867,7 +867,7 @@ pub(crate) async fn download_and_stage_portable_app_update( update_app_task(app, |task| { task.cancellable = false; task.phase = "staging".to_string(); - task.message = Some("正在准备应用更新".to_string()); + task.message = Some("Preparing application update".to_string()); }); let staging_dir = work_dir.join("staging"); @@ -876,20 +876,20 @@ pub(crate) async fn download_and_stage_portable_app_update( || package.manifest.platform != "linux" || package.manifest.arch != pending.arch { - return Err("应用更新包版本或架构不匹配".to_string()); + return Err("Application update package version or architecture mismatch".to_string()); } let core_archive_name = package .core_archive_name - .ok_or_else(|| "Linux 应用更新包缺少内置核心".to_string())?; + .ok_or_else(|| "Linux application update package is missing the bundled kernel".to_string())?; let current_exe = - env::current_exe().map_err(|error| format!("读取当前程序路径失败: {error}"))?; + env::current_exe().map_err(|error| format!("Failed to read current executable path: {error}"))?; let app_dir = current_exe .parent() - .ok_or_else(|| "当前程序路径没有父目录".to_string())?; + .ok_or_else(|| "Current executable path has no parent directory".to_string())?; preflight_portable_update_directory(app_dir)?; let helper_path = work_dir.join("EasyCLIProxyAPI-updater"); fs::copy(¤t_exe, &helper_path) - .map_err(|error| format!("准备应用更新助手失败: {error}"))?; + .map_err(|error| format!("Failed to prepare application update helper: {error}"))?; let descriptor = PortableUpdateDescriptor { parent_pid: std::process::id(), @@ -923,7 +923,7 @@ pub(crate) async fn download_and_stage_portable_app_update( validate_portable_update_asset(&pending.asset)?; let work_dir = portable_update_work_dir(&pending.version); fs::create_dir_all(&work_dir) - .map_err(|error| format!("创建应用更新临时目录失败: {error}"))?; + .map_err(|error| format!("Failed to create temporary application update directory: {error}"))?; let archive_path = work_dir.join("update.dmg"); let result = async { download_portable_update_archive( @@ -939,23 +939,23 @@ pub(crate) async fn download_and_stage_portable_app_update( update_app_task(app, |task| { task.cancellable = false; task.phase = "staging".to_string(); - task.message = Some("正在准备应用更新".to_string()); + task.message = Some("Preparing application update".to_string()); }); let staged_app = work_dir.join("staging").join("EasyCLIProxyAPI.app"); stage_macos_application_from_dmg(&archive_path, &work_dir, &staged_app)?; validate_macos_staged_application(&staged_app, pending)?; let current_exe = - env::current_exe().map_err(|error| format!("读取当前程序路径失败: {error}"))?; + env::current_exe().map_err(|error| format!("Failed to read current executable path: {error}"))?; let current_app = macos_application_bundle_from_executable(¤t_exe)?; preflight_macos_update_directory(¤t_app)?; let executable_relative_path = current_exe .strip_prefix(¤t_app) - .map_err(|_| "macOS 应用程序路径无效".to_string())? + .map_err(|_| "Invalid macOS application path".to_string())? .to_path_buf(); let backup_app = current_app .parent() - .ok_or_else(|| "macOS 应用程序路径没有父目录".to_string())? + .ok_or_else(|| "macOS application path has no parent directory".to_string())? .join(".EasyCLIProxyAPI.app.update-backup"); let descriptor = MacosUpdateDescriptor { parent_pid: std::process::id(), @@ -998,11 +998,11 @@ fn ensure_portable_update_download( pending: &PendingAppUpdate, ) -> Result<(), String> { if token.is_cancelled() { - return Err("应用更新下载已取消".to_string()); + return Err("Application update download canceled".to_string()); } let actual_sha256 = sha256_file(archive_path)?; if actual_sha256 != pending.asset.sha256.trim().to_ascii_lowercase() { - return Err("应用更新包 SHA-256 校验失败".to_string()); + return Err("Application update package SHA-256 verification failed".to_string()); } Ok(()) } @@ -1023,9 +1023,9 @@ async fn launch_portable_update_helper( fs::write( &descriptor_path, serde_json::to_vec_pretty(descriptor) - .map_err(|error| format!("序列化应用更新描述失败: {error}"))?, + .map_err(|error| format!("Failed to serialize application update descriptor: {error}"))?, ) - .map_err(|error| format!("写入应用更新描述失败: {error}"))?; + .map_err(|error| format!("Failed to write application update descriptor: {error}"))?; let helper_ack_path = portable_update_helper_ack_path(work_dir); let _ = fs::remove_file(&helper_ack_path); let mut command = Command::new(helper_path); @@ -1038,7 +1038,7 @@ async fn launch_portable_update_helper( configure_background_command(&mut command); let mut child = command .spawn() - .map_err(|error| format!("启动应用更新助手失败: {error}"))?; + .map_err(|error| format!("Failed to start application update helper: {error}"))?; wait_for_portable_update_helper_start( &mut child, &helper_ack_path, @@ -1048,7 +1048,7 @@ async fn launch_portable_update_helper( update_app_task(app, |task| { task.cancellable = false; task.phase = "restarting".to_string(); - task.message = Some("更新已准备完成,应用即将重启".to_string()); + task.message = Some("Update is ready. The application will restart shortly".to_string()); }); app.exit(0); Ok(()) @@ -1065,7 +1065,7 @@ pub(crate) fn acknowledge_portable_update_helper_start(work_dir: &Path) -> Resul portable_update_helper_ack_path(work_dir), std::process::id().to_string(), ) - .map_err(|error| format!("写入应用更新助手启动确认失败: {error}")) + .map_err(|error| format!("Failed to write application update helper startup confirmation: {error}")) } #[cfg(any(windows, target_os = "linux", target_os = "macos"))] @@ -1081,15 +1081,15 @@ async fn wait_for_portable_update_helper_start( } if let Some(status) = child .try_wait() - .map_err(|error| format!("检查应用更新助手状态失败: {error}"))? + .map_err(|error| format!("Failed to check application update helper status: {error}"))? { - return Err(format!("应用更新助手未能启动,退出状态: {status}")); + return Err(format!("Application update helper failed to start; exit status: {status}")); } if Instant::now() >= deadline { let _ = child.kill(); let _ = child.wait(); return Err(format!( - "应用更新助手未能在 {} 秒内完成启动确认", + "Application update helper did not confirm startup within {} seconds", timeout.as_secs() )); } @@ -1119,7 +1119,7 @@ pub(crate) async fn download_portable_update_archive( .read_timeout(Duration::from_secs(30)) .timeout(Duration::from_secs(15 * 60)), proxy_url, - "创建应用更新下载客户端失败", + "Failed to create application update download client", )?; let urls = portable_update_download_urls(&pending.asset, download_source); let mut failures = Vec::new(); @@ -1129,7 +1129,7 @@ pub(crate) async fn download_portable_update_archive( task.percent = Some(0.0); if index > 0 { task.message = Some(format!( - "下载失败,正在切换到 {}", + "Download failed; switching to {}", update_download_source_name(url) )); } @@ -1142,7 +1142,7 @@ pub(crate) async fn download_portable_update_archive( Err(error) => failures.push(error), } } - Err(format!("所有应用更新下载源均失败: {}", failures.join("; "))) + Err(format!("All application update download sources failed: {}", failures.join("; "))) } pub(crate) fn portable_update_download_urls( @@ -1201,24 +1201,24 @@ pub(crate) async fn download_portable_update_archive_url( .header(reqwest::header::USER_AGENT, APP_USER_AGENT) .send() .await - .map_err(|error| format!("下载应用更新失败: {error}"))? + .map_err(|error| format!("Failed to download application update: {error}"))? .error_for_status() - .map_err(|error| format!("下载应用更新失败: {error}"))?; + .map_err(|error| format!("Failed to download application update: {error}"))?; let mut stream = response.bytes_stream(); let mut file = - File::create(destination).map_err(|error| format!("创建应用更新临时文件失败: {error}"))?; + File::create(destination).map_err(|error| format!("Failed to create temporary application update file: {error}"))?; let mut downloaded = 0_u64; let mut progress = crate::progress::ProgressThrottle::default(); while let Some(chunk) = stream.next().await { if token.is_cancelled() { - return Err("应用更新下载已取消".to_string()); + return Err("Application update download canceled".to_string()); } - let chunk = chunk.map_err(|error| format!("读取应用更新下载数据失败: {error}"))?; + let chunk = chunk.map_err(|error| format!("Failed to read application update download data: {error}"))?; file.write_all(&chunk) - .map_err(|error| format!("写入应用更新临时文件失败: {error}"))?; + .map_err(|error| format!("Failed to write temporary application update file: {error}"))?; downloaded = downloaded.saturating_add(chunk.len() as u64); if downloaded > pending.asset.size_bytes { - return Err("应用更新包超过清单声明大小".to_string()); + return Err("Application update package exceeds the size declared in the manifest".to_string()); } if progress.ready(Instant::now(), downloaded == pending.asset.size_bytes) { update_app_task(app, |task| { @@ -1234,10 +1234,10 @@ pub(crate) async fn download_portable_update_archive_url( } } file.flush() - .map_err(|error| format!("保存应用更新临时文件失败: {error}"))?; + .map_err(|error| format!("Failed to save temporary application update file: {error}"))?; if downloaded != pending.asset.size_bytes { return Err(format!( - "应用更新包大小不匹配: 预期 {},实际 {}", + "Application update package size mismatch: expected {}, got {}", pending.asset.size_bytes, downloaded )); } @@ -1276,10 +1276,10 @@ pub(crate) fn extract_portable_update_archive( staging_dir: &Path, ) -> Result { fs::create_dir_all(staging_dir) - .map_err(|error| format!("创建应用更新暂存目录失败: {error}"))?; - let file = File::open(archive_path).map_err(|error| format!("打开应用更新包失败: {error}"))?; + .map_err(|error| format!("Failed to create application update staging directory: {error}"))?; + let file = File::open(archive_path).map_err(|error| format!("Failed to open application update package: {error}"))?; let mut archive = - ZipArchive::new(file).map_err(|error| format!("读取应用更新 ZIP 失败: {error}"))?; + ZipArchive::new(file).map_err(|error| format!("Failed to read application update ZIP: {error}"))?; let archive_names = archive .file_names() .map(|name| name.replace('\\', "/")) @@ -1298,16 +1298,16 @@ pub(crate) fn extract_portable_update_archive( for index in 0..archive.len() { let mut entry = archive .by_index(index) - .map_err(|error| format!("读取应用更新 ZIP 条目失败: {error}"))?; + .map_err(|error| format!("Failed to read application update ZIP entry: {error}"))?; let name = entry.name().replace('\\', "/"); if entry.enclosed_name().is_none() { - return Err("应用更新包包含不安全路径".to_string()); + return Err("Application update package contains an unsafe path".to_string()); } if entry .unix_mode() .is_some_and(|mode| mode & 0o170000 == 0o120000) { - return Err("应用更新包不能包含符号链接".to_string()); + return Err("Application update package cannot contain symbolic links".to_string()); } if entry.is_dir() { continue; @@ -1323,13 +1323,13 @@ pub(crate) fn extract_portable_update_archive( let root = components .next() .filter(|value| !value.is_empty()) - .ok_or_else(|| "应用更新包缺少顶层目录".to_string())?; + .ok_or_else(|| "Application update package is missing a top-level directory".to_string())?; relative = components.collect::>(); if relative.is_empty() || relative.iter().any(|value| value.is_empty()) { - return Err("应用更新包目录结构无效".to_string()); + return Err("Invalid application update package directory structure".to_string()); } if package_root.as_deref().is_some_and(|value| value != root) { - return Err("应用更新包必须只包含一个顶层目录".to_string()); + return Err("Application update package must contain exactly one top-level directory".to_string()); } package_root.get_or_insert_with(|| root.to_string()); relative_name = relative.join("/"); @@ -1337,21 +1337,21 @@ pub(crate) fn extract_portable_update_archive( let destination = match relative_name.as_str() { PORTABLE_APP_BINARY if !seen_binary => { if entry.size() == 0 || entry.size() > 256 * 1024 * 1024 { - return Err("应用更新程序大小异常".to_string()); + return Err("Invalid application updater size".to_string()); } seen_binary = true; staging_dir.join(PORTABLE_APP_BINARY) } PORTABLE_APP_MANIFEST_FILE if !seen_manifest => { if entry.size() == 0 || entry.size() > 64 * 1024 { - return Err("应用更新标识大小异常".to_string()); + return Err("Invalid application update marker size".to_string()); } seen_manifest = true; staging_dir.join(PORTABLE_APP_MANIFEST_FILE) } CORE_VERSION_FILE if !seen_core_version => { if entry.size() == 0 || entry.size() > 1024 { - return Err("应用更新包内核版本文件大小异常".to_string()); + return Err("Invalid bundled kernel version file size in application update package".to_string()); } seen_core_version = true; staging_dir.join(CORE_VERSION_FILE) @@ -1367,23 +1367,23 @@ pub(crate) fn extract_portable_update_archive( || entry.size() == 0 || entry.size() > 512 * 1024 * 1024 { - return Err("应用更新包内置内核压缩包无效".to_string()); + return Err("Invalid bundled kernel archive in application update package".to_string()); } core_archive_name = Some(filename.to_string()); let core_staging_dir = staging_dir.join("cpa-core"); fs::create_dir_all(&core_staging_dir) - .map_err(|error| format!("创建内置内核暂存目录失败: {error}"))?; + .map_err(|error| format!("Failed to create bundled kernel staging directory: {error}"))?; core_staging_dir.join(filename) } - _ => return Err(format!("应用更新包包含未知文件: {name}")), + _ => return Err(format!("Application update package contains an unknown file: {name}")), }; let mut output = File::create(&destination) - .map_err(|error| format!("创建应用更新暂存文件失败: {error}"))?; + .map_err(|error| format!("Failed to create application update staging file: {error}"))?; io::copy(&mut entry, &mut output) - .map_err(|error| format!("解压应用更新文件失败: {error}"))?; + .map_err(|error| format!("Failed to extract application update file: {error}"))?; output .flush() - .map_err(|error| format!("保存应用更新暂存文件失败: {error}"))?; + .map_err(|error| format!("Failed to save application update staging file: {error}"))?; } let required_files_present = if legacy_layout { regular_file_count == 2 && seen_binary && seen_manifest @@ -1391,17 +1391,17 @@ pub(crate) fn extract_portable_update_archive( regular_file_count == 4 && seen_binary && seen_manifest && seen_core_version }; if !required_files_present { - return Err("应用更新包缺少必要文件".to_string()); + return Err("Application update package is missing required files".to_string()); } let manifest = fs::read_to_string(staging_dir.join(PORTABLE_APP_MANIFEST_FILE)) - .map_err(|error| format!("读取应用更新标识失败: {error}"))?; + .map_err(|error| format!("Failed to read application update marker: {error}"))?; let manifest = serde_json::from_str::(&manifest) - .map_err(|error| format!("解析应用更新标识失败: {error}"))?; + .map_err(|error| format!("Failed to parse application update marker: {error}"))?; if manifest.schema_version != 1 || manifest.application != "EasyCLIProxyAPI" || !manifest.auto_update { - return Err("应用更新标识无效".to_string()); + return Err("Invalid application update marker".to_string()); } if !legacy_layout { let expected_root = format!( @@ -1410,22 +1410,22 @@ pub(crate) fn extract_portable_update_archive( manifest.arch ); if package_root.as_deref() != Some(expected_root.as_str()) { - return Err("应用更新包顶层目录与版本或架构不匹配".to_string()); + return Err("Application update package top-level directory does not match the version or architecture".to_string()); } let core_version = fs::read_to_string(staging_dir.join(CORE_VERSION_FILE)) - .map_err(|error| format!("读取内置内核版本失败: {error}"))?; + .map_err(|error| format!("Failed to read bundled kernel version: {error}"))?; let core_version = core_version.trim().trim_start_matches('v'); if core_version.is_empty() || !core_version.split('.').all(|segment| { !segment.is_empty() && segment.bytes().all(|byte| byte.is_ascii_digit()) }) { - return Err("应用更新包内置内核版本无效".to_string()); + return Err("Invalid bundled kernel version in application update package".to_string()); } let expected_core_archive = format!("CLIProxyAPI_{core_version}_windows_{}.zip", manifest.arch); if core_archive_name.as_deref() != Some(expected_core_archive.as_str()) { - return Err("应用更新包内置内核版本或架构不匹配".to_string()); + return Err("Bundled kernel version or architecture mismatch in application update package".to_string()); } } Ok(PortablePackagePayload { @@ -1440,13 +1440,13 @@ pub(crate) fn extract_portable_update_tar_gz( staging_dir: &Path, ) -> Result { fs::create_dir_all(staging_dir) - .map_err(|error| format!("创建应用更新暂存目录失败: {error}"))?; - let file = File::open(archive_path).map_err(|error| format!("打开应用更新包失败: {error}"))?; + .map_err(|error| format!("Failed to create application update staging directory: {error}"))?; + let file = File::open(archive_path).map_err(|error| format!("Failed to open application update package: {error}"))?; let decoder = GzDecoder::new(file); let mut archive = Archive::new(decoder); let entries = archive .entries() - .map_err(|error| format!("读取应用更新 TAR.GZ 失败: {error}"))?; + .map_err(|error| format!("Failed to read application update TAR.GZ: {error}"))?; let mut package_root = None::; let mut seen_binary = false; let mut seen_manifest = false; @@ -1455,33 +1455,33 @@ pub(crate) fn extract_portable_update_tar_gz( let mut regular_file_count = 0_u32; for entry in entries { - let mut entry = entry.map_err(|error| format!("读取应用更新条目失败: {error}"))?; + let mut entry = entry.map_err(|error| format!("Failed to read application update entry: {error}"))?; let entry_type = entry.header().entry_type(); if entry_type.is_dir() { continue; } if !entry_type.is_file() { - return Err("应用更新包不能包含链接或特殊文件".to_string()); + return Err("Application update package cannot contain links or special files".to_string()); } let entry_path = entry .path() - .map_err(|error| format!("读取应用更新条目路径失败: {error}"))?; + .map_err(|error| format!("Failed to read application update entry path: {error}"))?; let mut components = Vec::new(); for component in entry_path.components() { match component { Component::Normal(value) => components.push(value.to_string_lossy().into_owned()), Component::CurDir => {} Component::ParentDir | Component::RootDir | Component::Prefix(_) => { - return Err("应用更新包包含不安全路径".to_string()); + return Err("Application update package contains an unsafe path".to_string()); } } } if components.len() < 2 || components.iter().any(String::is_empty) { - return Err("应用更新包目录结构无效".to_string()); + return Err("Invalid application update package directory structure".to_string()); } let root = &components[0]; if package_root.as_deref().is_some_and(|value| value != root) { - return Err("应用更新包必须只包含一个顶层目录".to_string()); + return Err("Application update package must contain exactly one top-level directory".to_string()); } package_root.get_or_insert_with(|| root.clone()); let relative = &components[1..]; @@ -1491,21 +1491,21 @@ pub(crate) fn extract_portable_update_tar_gz( let destination = match relative_name.as_str() { PORTABLE_APP_BINARY if !seen_binary => { if size == 0 || size > 256 * 1024 * 1024 { - return Err("应用更新程序大小异常".to_string()); + return Err("Invalid application updater size".to_string()); } seen_binary = true; staging_dir.join(PORTABLE_APP_BINARY) } PORTABLE_APP_MANIFEST_FILE if !seen_manifest => { if size == 0 || size > 64 * 1024 { - return Err("应用更新标识大小异常".to_string()); + return Err("Invalid application update marker size".to_string()); } seen_manifest = true; staging_dir.join(PORTABLE_APP_MANIFEST_FILE) } CORE_VERSION_FILE if !seen_core_version => { if size == 0 || size > 1024 { - return Err("应用更新包内核版本文件大小异常".to_string()); + return Err("Invalid bundled kernel version file size in application update package".to_string()); } seen_core_version = true; staging_dir.join(CORE_VERSION_FILE) @@ -1521,23 +1521,23 @@ pub(crate) fn extract_portable_update_tar_gz( || size == 0 || size > 512 * 1024 * 1024 { - return Err("应用更新包内置内核压缩包无效".to_string()); + return Err("Invalid bundled kernel archive in application update package".to_string()); } core_archive_name = Some(filename.clone()); let core_staging_dir = staging_dir.join("cpa-core"); fs::create_dir_all(&core_staging_dir) - .map_err(|error| format!("创建内置内核暂存目录失败: {error}"))?; + .map_err(|error| format!("Failed to create bundled kernel staging directory: {error}"))?; core_staging_dir.join(filename) } - _ => return Err(format!("应用更新包包含未知文件: {relative_name}")), + _ => return Err(format!("Application update package contains an unknown file: {relative_name}")), }; let mut output = File::create(&destination) - .map_err(|error| format!("创建应用更新暂存文件失败: {error}"))?; + .map_err(|error| format!("Failed to create application update staging file: {error}"))?; io::copy(&mut entry, &mut output) - .map_err(|error| format!("解压应用更新文件失败: {error}"))?; + .map_err(|error| format!("Failed to extract application update file: {error}"))?; output .flush() - .map_err(|error| format!("保存应用更新暂存文件失败: {error}"))?; + .map_err(|error| format!("Failed to save application update staging file: {error}"))?; } if regular_file_count != 4 || !seen_binary @@ -1545,24 +1545,24 @@ pub(crate) fn extract_portable_update_tar_gz( || !seen_core_version || core_archive_name.is_none() { - return Err("应用更新包缺少必要文件".to_string()); + return Err("Application update package is missing required files".to_string()); } use std::os::unix::fs::PermissionsExt; fs::set_permissions( staging_dir.join(PORTABLE_APP_BINARY), fs::Permissions::from_mode(0o755), ) - .map_err(|error| format!("设置应用更新程序权限失败: {error}"))?; + .map_err(|error| format!("Failed to set application updater permissions: {error}"))?; let manifest = fs::read_to_string(staging_dir.join(PORTABLE_APP_MANIFEST_FILE)) - .map_err(|error| format!("读取应用更新标识失败: {error}"))?; + .map_err(|error| format!("Failed to read application update marker: {error}"))?; let manifest = serde_json::from_str::(&manifest) - .map_err(|error| format!("解析应用更新标识失败: {error}"))?; + .map_err(|error| format!("Failed to parse application update marker: {error}"))?; if manifest.schema_version != 1 || manifest.application != "EasyCLIProxyAPI" || manifest.platform != "linux" || !manifest.auto_update { - return Err("应用更新标识无效".to_string()); + return Err("Invalid application update marker".to_string()); } let expected_root = format!( "EasyCLIProxyAPI-v{}-Linux-{}", @@ -1570,22 +1570,22 @@ pub(crate) fn extract_portable_update_tar_gz( manifest.arch ); if package_root.as_deref() != Some(expected_root.as_str()) { - return Err("应用更新包顶层目录与版本或架构不匹配".to_string()); + return Err("Application update package top-level directory does not match the version or architecture".to_string()); } let core_version = fs::read_to_string(staging_dir.join(CORE_VERSION_FILE)) - .map_err(|error| format!("读取内置内核版本失败: {error}"))?; + .map_err(|error| format!("Failed to read bundled kernel version: {error}"))?; let core_version = core_version.trim().trim_start_matches('v'); if core_version.is_empty() || !core_version .split('.') .all(|segment| !segment.is_empty() && segment.bytes().all(|byte| byte.is_ascii_digit())) { - return Err("应用更新包内置内核版本无效".to_string()); + return Err("Invalid bundled kernel version in application update package".to_string()); } let expected_core_archive = format!("CLIProxyAPI_{core_version}_linux_{}.tar.gz", manifest.arch); if core_archive_name.as_deref() != Some(expected_core_archive.as_str()) { - return Err("应用更新包内置内核版本或架构不匹配".to_string()); + return Err("Bundled kernel version or architecture mismatch in application update package".to_string()); } Ok(PortablePackagePayload { manifest, @@ -1601,55 +1601,55 @@ pub(crate) fn stage_current_portable_core_payload( ) -> Result { let current_core_version = app_dir.join(CORE_VERSION_FILE); let core_version = fs::read_to_string(¤t_core_version) - .map_err(|error| format!("读取当前内置内核版本失败: {error}"))?; + .map_err(|error| format!("Failed to read current bundled kernel version: {error}"))?; let core_version = core_version.trim().trim_start_matches('v'); if core_version.is_empty() || !core_version .split('.') .all(|segment| !segment.is_empty() && segment.bytes().all(|byte| byte.is_ascii_digit())) { - return Err("当前内置内核版本无效,无法兼容旧版更新包".to_string()); + return Err("Current bundled kernel version is invalid and cannot support legacy update packages".to_string()); } let core_archive_name = format!("CLIProxyAPI_{core_version}_windows_{arch}.zip"); let current_core_archive = app_dir.join("cpa-core").join(&core_archive_name); if !current_core_archive.is_file() { return Err(format!( - "当前便携版缺少内置内核压缩包 {core_archive_name},无法兼容旧版更新包" + "Current portable version is missing bundled kernel archive {core_archive_name} and cannot support legacy update packages" )); } let staged_core_dir = staging_dir.join("cpa-core"); fs::create_dir_all(&staged_core_dir) - .map_err(|error| format!("创建旧版更新兼容暂存目录失败: {error}"))?; + .map_err(|error| format!("Failed to create legacy update compatibility staging directory: {error}"))?; fs::copy(¤t_core_version, staging_dir.join(CORE_VERSION_FILE)) - .map_err(|error| format!("暂存当前内核版本文件失败: {error}"))?; + .map_err(|error| format!("Failed to stage current kernel version file: {error}"))?; fs::copy( current_core_archive, staged_core_dir.join(&core_archive_name), ) - .map_err(|error| format!("暂存当前内置内核压缩包失败: {error}"))?; + .map_err(|error| format!("Failed to stage current bundled kernel archive: {error}"))?; Ok(core_archive_name) } #[cfg(any(windows, target_os = "linux"))] pub(crate) fn preflight_portable_update_directory(app_dir: &Path) -> Result<(), String> { if !app_dir.join(CORE_VERSION_FILE).is_file() || !app_dir.join("cpa-core").is_dir() { - return Err("当前便携版目录不完整,请下载最新版完整包覆盖升级".to_string()); + return Err("The current portable directory is incomplete. Download the latest full package to upgrade".to_string()); } let probe = app_dir.join(format!( ".easycliproxy-update-write-test-{}", std::process::id() )); fs::write(&probe, b"update-write-test") - .map_err(|error| format!("应用目录不可写,无法自动升级: {error}"))?; - fs::remove_file(&probe).map_err(|error| format!("清理应用更新写入测试失败: {error}"))?; + .map_err(|error| format!("Application directory is not writable; automatic upgrade is unavailable: {error}"))?; + fs::remove_file(&probe).map_err(|error| format!("Failed to clean up application update write test: {error}"))?; let core_probe = app_dir.join("cpa-core").join(format!( ".easycliproxy-update-write-test-{}", std::process::id() )); fs::write(&core_probe, b"update-write-test") - .map_err(|error| format!("内置内核目录不可写,无法自动升级: {error}"))?; + .map_err(|error| format!("Bundled kernel directory is not writable; automatic upgrade is unavailable: {error}"))?; fs::remove_file(&core_probe) - .map_err(|error| format!("清理内置内核更新写入测试失败: {error}"))?; + .map_err(|error| format!("Failed to clean up bundled kernel update write test: {error}"))?; Ok(()) } @@ -1670,17 +1670,17 @@ pub(crate) fn wait_for_windows_process_exit( if error.raw_os_error() == Some(87) { return Ok(()); } - return Err(format!("打开旧版应用进程失败: {error}")); + return Err(format!("Failed to open legacy application process: {error}")); } let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32; let result = unsafe { WaitForSingleObject(handle, timeout_ms) }; unsafe { CloseHandle(handle) }; if result == WAIT_TIMEOUT { - return Err("等待旧版应用退出超时".to_string()); + return Err("Timed out waiting for legacy application to exit".to_string()); } if result == WAIT_FAILED { return Err(format!( - "等待旧版应用退出失败: {}", + "Failed while waiting for legacy application to exit: {}", io::Error::last_os_error() )); } @@ -1695,7 +1695,7 @@ pub(crate) fn wait_for_portable_parent_exit(pid: u32, timeout: Duration) -> Resu #[cfg(any(target_os = "linux", target_os = "macos"))] pub(crate) fn wait_for_portable_parent_exit(pid: u32, timeout: Duration) -> Result<(), String> { if pid == 0 || pid > i32::MAX as u32 { - return Err("应用更新父进程编号无效".to_string()); + return Err("Invalid application update parent process ID".to_string()); } let deadline = Instant::now() + timeout; loop { @@ -1706,11 +1706,11 @@ pub(crate) fn wait_for_portable_parent_exit(pid: u32, timeout: Duration) -> Resu return Ok(()); } if error.raw_os_error() != Some(libc::EPERM) { - return Err(format!("检查旧版应用进程失败: {error}")); + return Err(format!("Failed to check legacy application process: {error}")); } } if Instant::now() >= deadline { - return Err("等待旧版应用退出超时".to_string()); + return Err("Timed out waiting for legacy application to exit".to_string()); } thread::sleep(Duration::from_millis(100)); } @@ -1724,12 +1724,12 @@ pub(crate) fn validate_portable_update_descriptor( if descriptor.parent_pid == 0 || semver::Version::parse(descriptor.target_version.trim().trim_start_matches('v')).is_err() { - return Err("应用更新描述无效".to_string()); + return Err("Invalid application update descriptor".to_string()); } let app_dir = descriptor .current_exe .parent() - .ok_or_else(|| "应用更新目标路径无效".to_string())?; + .ok_or_else(|| "Invalid application update target path".to_string())?; if !descriptor.current_exe.is_absolute() || descriptor.current_exe != app_dir.join(PORTABLE_APP_BINARY) || descriptor.current_manifest != app_dir.join(PORTABLE_APP_MANIFEST_FILE) @@ -1738,12 +1738,12 @@ pub(crate) fn validate_portable_update_descriptor( || descriptor.current_core_version != app_dir.join(CORE_VERSION_FILE) || descriptor.backup_core_version != app_dir.join(".core-version.txt.update-backup") { - return Err("应用更新目标路径无效".to_string()); + return Err("Invalid application update target path".to_string()); } let core_archive_name = descriptor .staged_core_archive .file_name() - .ok_or_else(|| "应用更新内置内核文件名无效".to_string())?; + .ok_or_else(|| "Invalid application update bundled kernel filename".to_string())?; if descriptor.target_core_archive != app_dir.join("cpa-core").join(core_archive_name) || !core_archive_name .to_string_lossy() @@ -1753,22 +1753,22 @@ pub(crate) fn validate_portable_update_descriptor( .ends_with(portable_update_core_archive_suffix()) || descriptor.install_core_archive == descriptor.target_core_archive.is_file() { - return Err("应用更新内置内核目标路径无效".to_string()); + return Err("Invalid application update bundled kernel target path".to_string()); } let canonical_work_dir = fs::canonicalize(&descriptor.work_dir) - .map_err(|error| format!("读取应用更新临时目录失败: {error}"))?; + .map_err(|error| format!("Failed to read application update temporary directory: {error}"))?; let canonical_temp_dir = fs::canonicalize(env::temp_dir()) - .map_err(|error| format!("读取系统临时目录失败: {error}"))?; + .map_err(|error| format!("Failed to read system temporary directory: {error}"))?; if !canonical_work_dir.starts_with(&canonical_temp_dir) || !canonical_work_dir .file_name() .and_then(|value| value.to_str()) .is_some_and(|value| value.starts_with("EasyCLIProxyAPI-update-")) { - return Err("应用更新工作目录无效".to_string()); + return Err("Invalid application update working directory".to_string()); } let canonical_descriptor = fs::canonicalize(descriptor_path) - .map_err(|error| format!("读取应用更新描述路径失败: {error}"))?; + .map_err(|error| format!("Failed to read application update descriptor path: {error}"))?; if canonical_descriptor.parent() != Some(canonical_work_dir.as_path()) || canonical_descriptor .file_name() @@ -1776,7 +1776,7 @@ pub(crate) fn validate_portable_update_descriptor( != Some("update-descriptor.json") || descriptor.ack_path != descriptor.work_dir.join("update-started.ack") { - return Err("应用更新描述路径越界".to_string()); + return Err("Application update descriptor path is out of bounds".to_string()); } for staged in [ &descriptor.staged_exe, @@ -1785,9 +1785,9 @@ pub(crate) fn validate_portable_update_descriptor( &descriptor.staged_core_archive, ] { let canonical = fs::canonicalize(staged) - .map_err(|error| format!("读取应用更新暂存文件失败: {error}"))?; + .map_err(|error| format!("Failed to read application update staging file: {error}"))?; if !canonical.starts_with(&canonical_work_dir) { - return Err("应用更新暂存路径越界".to_string()); + return Err("Application update staging path is out of bounds".to_string()); } } if descriptor.staged_exe @@ -1809,7 +1809,7 @@ pub(crate) fn validate_portable_update_descriptor( .join("cpa-core") .join(core_archive_name) { - return Err("应用更新暂存路径无效".to_string()); + return Err("Invalid application update staging path".to_string()); } Ok(()) } @@ -1849,32 +1849,32 @@ pub(crate) fn restore_portable_update_backup( || !descriptor.backup_manifest.is_file() || !descriptor.backup_core_version.is_file() { - return Err("应用更新备份不完整,无法回滚".to_string()); + return Err("Application update backup is incomplete; cannot roll back".to_string()); } if descriptor.current_exe.exists() { fs::remove_file(&descriptor.current_exe) - .map_err(|error| format!("移除新版应用失败: {error}"))?; + .map_err(|error| format!("Failed to remove new application version: {error}"))?; } fs::rename(&descriptor.backup_exe, &descriptor.current_exe) - .map_err(|error| format!("恢复旧版应用失败: {error}"))?; + .map_err(|error| format!("Failed to restore old application version: {error}"))?; if descriptor.current_manifest.exists() { fs::remove_file(&descriptor.current_manifest) - .map_err(|error| format!("移除新版便携版标识失败: {error}"))?; + .map_err(|error| format!("Failed to remove new portable version marker: {error}"))?; } fs::rename(&descriptor.backup_manifest, &descriptor.current_manifest) - .map_err(|error| format!("恢复旧版便携版标识失败: {error}"))?; + .map_err(|error| format!("Failed to restore old portable version marker: {error}"))?; if descriptor.current_core_version.exists() { fs::remove_file(&descriptor.current_core_version) - .map_err(|error| format!("移除新版内核版本文件失败: {error}"))?; + .map_err(|error| format!("Failed to remove new kernel version file: {error}"))?; } fs::rename( &descriptor.backup_core_version, &descriptor.current_core_version, ) - .map_err(|error| format!("恢复旧版内核版本文件失败: {error}"))?; + .map_err(|error| format!("Failed to restore old kernel version file: {error}"))?; if descriptor.install_core_archive && descriptor.target_core_archive.exists() { fs::remove_file(&descriptor.target_core_archive) - .map_err(|error| format!("移除新版内置内核压缩包失败: {error}"))?; + .map_err(|error| format!("Failed to remove new bundled kernel archive: {error}"))?; } Ok(()) } @@ -1886,7 +1886,7 @@ pub(crate) fn replace_portable_update_files( let app_dir = descriptor .current_exe .parent() - .ok_or_else(|| "应用更新目标路径无效".to_string())?; + .ok_or_else(|| "Invalid application update target path".to_string())?; let replacement_exe = app_dir.join(portable_update_replacement_executable_name()); let replacement_manifest = app_dir.join(".portable-app.json.update-new"); let replacement_core_version = app_dir.join(".core-version.txt.update-new"); @@ -1896,22 +1896,22 @@ pub(crate) fn replace_portable_update_files( let _ = fs::remove_file(&replacement_core_version); let _ = fs::remove_file(&replacement_core_archive); fs::copy(&descriptor.staged_exe, &replacement_exe) - .map_err(|error| format!("准备新版应用程序失败: {error}"))?; + .map_err(|error| format!("Failed to prepare new application executable: {error}"))?; if let Err(error) = fs::copy(&descriptor.staged_manifest, &replacement_manifest) { let _ = fs::remove_file(&replacement_exe); - return Err(format!("准备新版便携版标识失败: {error}")); + return Err(format!("Failed to prepare new portable version marker: {error}")); } if let Err(error) = fs::copy(&descriptor.staged_core_version, &replacement_core_version) { let _ = fs::remove_file(&replacement_exe); let _ = fs::remove_file(&replacement_manifest); - return Err(format!("准备新版内核版本文件失败: {error}")); + return Err(format!("Failed to prepare new kernel version file: {error}")); } if descriptor.install_core_archive { if let Err(error) = fs::copy(&descriptor.staged_core_archive, &replacement_core_archive) { let _ = fs::remove_file(&replacement_exe); let _ = fs::remove_file(&replacement_manifest); let _ = fs::remove_file(&replacement_core_version); - return Err(format!("准备新版内置内核压缩包失败: {error}")); + return Err(format!("Failed to prepare new bundled kernel archive: {error}")); } } @@ -1923,7 +1923,7 @@ pub(crate) fn replace_portable_update_files( let _ = fs::remove_file(&replacement_manifest); let _ = fs::remove_file(&replacement_core_version); let _ = fs::remove_file(&replacement_core_archive); - return Err(format!("备份旧版应用失败: {error}")); + return Err(format!("Failed to back up old application version: {error}")); } if let Err(error) = fs::rename(&descriptor.current_manifest, &descriptor.backup_manifest) { let _ = fs::rename(&descriptor.backup_exe, &descriptor.current_exe); @@ -1931,7 +1931,7 @@ pub(crate) fn replace_portable_update_files( let _ = fs::remove_file(&replacement_manifest); let _ = fs::remove_file(&replacement_core_version); let _ = fs::remove_file(&replacement_core_archive); - return Err(format!("备份便携版标识失败: {error}")); + return Err(format!("Failed to back up portable version marker: {error}")); } if let Err(error) = fs::rename( &descriptor.current_core_version, @@ -1943,19 +1943,19 @@ pub(crate) fn replace_portable_update_files( let _ = fs::remove_file(&replacement_manifest); let _ = fs::remove_file(&replacement_core_version); let _ = fs::remove_file(&replacement_core_archive); - return Err(format!("备份旧版内核版本文件失败: {error}")); + return Err(format!("Failed to back up old kernel version file: {error}")); } let replace_result = (|| -> Result<(), String> { fs::rename(&replacement_exe, &descriptor.current_exe) - .map_err(|error| format!("替换应用程序失败: {error}"))?; + .map_err(|error| format!("Failed to replace application executable: {error}"))?; fs::rename(&replacement_manifest, &descriptor.current_manifest) - .map_err(|error| format!("替换便携版标识失败: {error}"))?; + .map_err(|error| format!("Failed to replace portable version marker: {error}"))?; fs::rename(&replacement_core_version, &descriptor.current_core_version) - .map_err(|error| format!("替换内核版本文件失败: {error}"))?; + .map_err(|error| format!("Failed to replace kernel version file: {error}"))?; if descriptor.install_core_archive { fs::rename(&replacement_core_archive, &descriptor.target_core_archive) - .map_err(|error| format!("安装新版内置内核压缩包失败: {error}"))?; + .map_err(|error| format!("Failed to install new bundled kernel archive: {error}"))?; } Ok(()) })(); @@ -1997,7 +1997,7 @@ pub(crate) fn cleanup_superseded_bundled_core_archives(descriptor: &PortableUpda { if let Err(error) = fs::remove_file(&path) { eprintln!( - "清理旧版内置内核压缩包失败 {}: {error}", + "Failed to clean up old bundled kernel archive {}: {error}", path_to_string(&path) ); } @@ -2024,9 +2024,9 @@ pub(crate) fn cleanup_portable_update_payload( #[cfg(any(windows, target_os = "linux"))] pub(crate) fn run_portable_update_helper(descriptor_path: &Path) -> Result<(), String> { let descriptor = serde_json::from_slice::( - &fs::read(descriptor_path).map_err(|error| format!("读取应用更新描述失败: {error}"))?, + &fs::read(descriptor_path).map_err(|error| format!("Failed to read application update descriptor: {error}"))?, ) - .map_err(|error| format!("解析应用更新描述失败: {error}"))?; + .map_err(|error| format!("Failed to parse application update descriptor: {error}"))?; validate_portable_update_descriptor(descriptor_path, &descriptor)?; acknowledge_portable_update_helper_start(&descriptor.work_dir)?; if let Err(error) = @@ -2061,7 +2061,7 @@ pub(crate) fn run_portable_update_helper(descriptor_path: &Path) -> Result<(), S configure_background_command(&mut rollback); let _ = rollback.spawn(); cleanup_portable_update_payload(descriptor_path, &descriptor); - return Err(format!("启动新版应用失败: {error}")); + return Err(format!("Failed to start new application version: {error}")); } }; @@ -2074,7 +2074,7 @@ pub(crate) fn run_portable_update_helper(descriptor_path: &Path) -> Result<(), S } if child .try_wait() - .map_err(|error| format!("检查新版应用状态失败: {error}"))? + .map_err(|error| format!("Failed to check new application version status: {error}"))? .is_some() { break; @@ -2099,7 +2099,7 @@ pub(crate) fn run_portable_update_helper(descriptor_path: &Path) -> Result<(), S let _ = rollback.spawn(); cleanup_portable_update_payload(descriptor_path, &descriptor); Err(format!( - "新版应用 {} 未能在 60 秒内完成启动确认,已回滚", + "New application version {} did not confirm startup within 60 seconds; rolled back", descriptor.target_version )) } @@ -2108,12 +2108,12 @@ pub(crate) fn run_portable_update_helper(descriptor_path: &Path) -> Result<(), S fn run_macos_update_command(command: &mut Command, action: &str) -> Result<(), String> { let output = command .output() - .map_err(|error| format!("{action}失败: {error}"))?; + .map_err(|error| format!("Failed to {action}: {error}"))?; if output.status.success() { return Ok(()); } let stderr = String::from_utf8_lossy(&output.stderr); - Err(format!("{action}失败: {}", stderr.trim())) + Err(format!("Failed to {action}: {}", stderr.trim())) } #[cfg(target_os = "macos")] @@ -2123,43 +2123,43 @@ pub(crate) fn stage_macos_application_from_dmg( staged_app: &Path, ) -> Result<(), String> { let mount_dir = work_dir.join("mount"); - fs::create_dir_all(&mount_dir).map_err(|error| format!("创建 DMG 挂载目录失败: {error}"))?; + fs::create_dir_all(&mount_dir).map_err(|error| format!("Failed to create DMG mount directory: {error}"))?; let mut attach = Command::new("hdiutil"); attach .arg("attach") .arg(dmg_path) .args(["-nobrowse", "-readonly", "-mountpoint"]) .arg(&mount_dir); - run_macos_update_command(&mut attach, "挂载应用更新 DMG")?; + run_macos_update_command(&mut attach, "Mount application update DMG")?; let source_app = mount_dir.join("EasyCLIProxyAPI.app"); let stage_result = (|| -> Result<(), String> { if !source_app.is_dir() { - return Err("应用更新 DMG 缺少 EasyCLIProxyAPI.app".to_string()); + return Err("Application update DMG is missing EasyCLIProxyAPI.app".to_string()); } let staging_parent = staged_app .parent() - .ok_or_else(|| "应用更新暂存路径无效".to_string())?; + .ok_or_else(|| "Invalid application update staging path".to_string())?; fs::create_dir_all(staging_parent) - .map_err(|error| format!("创建应用更新暂存目录失败: {error}"))?; + .map_err(|error| format!("Failed to create application update staging directory: {error}"))?; let mut ditto = Command::new("ditto"); ditto.arg(&source_app).arg(staged_app); - run_macos_update_command(&mut ditto, "暂存新版 macOS 应用")?; + run_macos_update_command(&mut ditto, "Stage new macOS application")?; let mut codesign = Command::new("codesign"); codesign .args(["--verify", "--deep", "--strict"]) .arg(staged_app); - run_macos_update_command(&mut codesign, "校验新版 macOS 应用签名")?; + run_macos_update_command(&mut codesign, "Verify new macOS application signature")?; let mut gatekeeper = Command::new("spctl"); gatekeeper .args(["--assess", "--type", "execute", "--verbose=2"]) .arg(staged_app); - run_macos_update_command(&mut gatekeeper, "校验新版 macOS 应用系统信任状态") + run_macos_update_command(&mut gatekeeper, "Verify new macOS application system trust status") })(); let mut detach = Command::new("hdiutil"); detach.arg("detach").arg(&mount_dir); - let detach_result = run_macos_update_command(&mut detach, "卸载应用更新 DMG"); + let detach_result = run_macos_update_command(&mut detach, "Unmount application update DMG"); match (stage_result, detach_result) { (Err(error), _) => Err(error), (Ok(()), Err(error)) => Err(error), @@ -2177,9 +2177,9 @@ pub(crate) fn validate_macos_staged_application( .join("Resources") .join(PORTABLE_APP_MANIFEST_FILE); let contents = fs::read_to_string(&manifest_path) - .map_err(|error| format!("读取新版 macOS 自动更新标识失败: {error}"))?; + .map_err(|error| format!("Failed to read new macOS automatic update marker: {error}"))?; let manifest = serde_json::from_str::(&contents) - .map_err(|error| format!("解析新版 macOS 自动更新标识失败: {error}"))?; + .map_err(|error| format!("Failed to parse new macOS automatic update marker: {error}"))?; if manifest.schema_version != 1 || manifest.application != "EasyCLIProxyAPI" || manifest.platform != "darwin" @@ -2187,7 +2187,7 @@ pub(crate) fn validate_macos_staged_application( || !manifest.auto_update || normalize_version(&manifest.version) != normalize_version(&pending.version) { - return Err("新版 macOS 应用标识与更新目标不匹配".to_string()); + return Err("New macOS application marker does not match the update target".to_string()); } Ok(()) } @@ -2199,33 +2199,33 @@ pub(crate) fn macos_application_bundle_from_executable( let macos_dir = executable .parent() .filter(|path| path.file_name().and_then(|name| name.to_str()) == Some("MacOS")) - .ok_or_else(|| "当前程序不在标准 macOS 应用包中".to_string())?; + .ok_or_else(|| "Current executable is not inside a standard macOS application bundle".to_string())?; let contents_dir = macos_dir .parent() .filter(|path| path.file_name().and_then(|name| name.to_str()) == Some("Contents")) - .ok_or_else(|| "当前程序不在标准 macOS 应用包中".to_string())?; + .ok_or_else(|| "Current executable is not inside a standard macOS application bundle".to_string())?; let app = contents_dir .parent() .filter(|path| path.extension().and_then(|value| value.to_str()) == Some("app")) - .ok_or_else(|| "当前程序不在标准 macOS 应用包中".to_string())?; + .ok_or_else(|| "Current executable is not inside a standard macOS application bundle".to_string())?; Ok(app.to_path_buf()) } #[cfg(target_os = "macos")] pub(crate) fn preflight_macos_update_directory(current_app: &Path) -> Result<(), String> { if !current_app.is_dir() { - return Err("当前 macOS 应用包不存在".to_string()); + return Err("Current macOS application bundle does not exist".to_string()); } let app_parent = current_app .parent() - .ok_or_else(|| "macOS 应用包没有父目录".to_string())?; + .ok_or_else(|| "macOS application bundle has no parent directory".to_string())?; let probe = app_parent.join(format!( ".easycliproxy-update-write-test-{}", std::process::id() )); fs::write(&probe, b"update-write-test") - .map_err(|error| format!("macOS 应用目录不可写,无法自动升级: {error}"))?; - fs::remove_file(&probe).map_err(|error| format!("清理应用更新写入测试失败: {error}")) + .map_err(|error| format!("macOS application directory is not writable; automatic upgrade is unavailable: {error}"))?; + fs::remove_file(&probe).map_err(|error| format!("Failed to clean up application update write test: {error}")) } #[cfg(target_os = "macos")] @@ -2242,12 +2242,12 @@ pub(crate) fn validate_macos_update_descriptor( .and_then(|value| value.to_str()) != Some("app") { - return Err("macOS 应用更新描述无效".to_string()); + return Err("Invalid macOS application update descriptor".to_string()); } let app_parent = descriptor .current_app .parent() - .ok_or_else(|| "macOS 应用更新目标路径无效".to_string())?; + .ok_or_else(|| "Invalid macOS application update target path".to_string())?; if descriptor.backup_app != app_parent.join(".EasyCLIProxyAPI.app.update-backup") || descriptor.executable_relative_path.is_absolute() || descriptor @@ -2258,24 +2258,24 @@ pub(crate) fn validate_macos_update_descriptor( .executable_relative_path .starts_with(Path::new("Contents").join("MacOS")) { - return Err("macOS 应用更新目标路径无效".to_string()); + return Err("Invalid macOS application update target path".to_string()); } let canonical_work_dir = fs::canonicalize(&descriptor.work_dir) - .map_err(|error| format!("读取应用更新临时目录失败: {error}"))?; + .map_err(|error| format!("Failed to read application update temporary directory: {error}"))?; let canonical_temp_dir = fs::canonicalize(env::temp_dir()) - .map_err(|error| format!("读取系统临时目录失败: {error}"))?; + .map_err(|error| format!("Failed to read system temporary directory: {error}"))?; if !canonical_work_dir.starts_with(&canonical_temp_dir) || !canonical_work_dir .file_name() .and_then(|value| value.to_str()) .is_some_and(|value| value.starts_with("EasyCLIProxyAPI-update-")) { - return Err("应用更新工作目录无效".to_string()); + return Err("Invalid application update working directory".to_string()); } let canonical_descriptor = fs::canonicalize(descriptor_path) - .map_err(|error| format!("读取应用更新描述路径失败: {error}"))?; + .map_err(|error| format!("Failed to read application update descriptor path: {error}"))?; let canonical_staged_app = fs::canonicalize(&descriptor.staged_app) - .map_err(|error| format!("读取新版 macOS 应用路径失败: {error}"))?; + .map_err(|error| format!("Failed to read new macOS application path: {error}"))?; if canonical_descriptor.parent() != Some(canonical_work_dir.as_path()) || canonical_descriptor .file_name() @@ -2289,7 +2289,7 @@ pub(crate) fn validate_macos_update_descriptor( || !canonical_staged_app.starts_with(&canonical_work_dir) || descriptor.ack_path != descriptor.work_dir.join("update-started.ack") { - return Err("macOS 应用更新暂存路径越界".to_string()); + return Err("macOS application update staging path is out of bounds".to_string()); } if !descriptor .current_app @@ -2300,7 +2300,7 @@ pub(crate) fn validate_macos_update_descriptor( .join(&descriptor.executable_relative_path) .is_file() { - return Err("macOS 应用更新可执行文件缺失".to_string()); + return Err("macOS application update executable is missing".to_string()); } Ok(()) } @@ -2310,14 +2310,14 @@ pub(crate) fn restore_macos_update_backup( descriptor: &MacosUpdateDescriptor, ) -> Result<(), String> { if !descriptor.backup_app.is_dir() { - return Err("macOS 应用更新备份不完整,无法回滚".to_string()); + return Err("macOS application update backup is incomplete; cannot roll back".to_string()); } if descriptor.current_app.exists() { fs::remove_dir_all(&descriptor.current_app) - .map_err(|error| format!("移除新版 macOS 应用失败: {error}"))?; + .map_err(|error| format!("Failed to remove new macOS application: {error}"))?; } fs::rename(&descriptor.backup_app, &descriptor.current_app) - .map_err(|error| format!("恢复旧版 macOS 应用失败: {error}")) + .map_err(|error| format!("Failed to restore old macOS application: {error}")) } #[cfg(target_os = "macos")] @@ -2325,25 +2325,25 @@ pub(crate) fn replace_macos_application(descriptor: &MacosUpdateDescriptor) -> R let app_parent = descriptor .current_app .parent() - .ok_or_else(|| "macOS 应用更新目标路径无效".to_string())?; + .ok_or_else(|| "Invalid macOS application update target path".to_string())?; let replacement_app = app_parent.join(".EasyCLIProxyAPI-update-new.app"); let legacy_replacement_app = app_parent.join(".EasyCLIProxyAPI.app.update-new"); if legacy_replacement_app.exists() { fs::remove_dir_all(&legacy_replacement_app) - .map_err(|error| format!("清理旧格式的 macOS 更新暂存失败: {error}"))?; + .map_err(|error| format!("Failed to clean up legacy macOS update staging data: {error}"))?; } if replacement_app.exists() { fs::remove_dir_all(&replacement_app) - .map_err(|error| format!("清理旧的 macOS 更新暂存失败: {error}"))?; + .map_err(|error| format!("Failed to clean up old macOS update staging data: {error}"))?; } let mut ditto = Command::new("ditto"); ditto.arg(&descriptor.staged_app).arg(&replacement_app); - run_macos_update_command(&mut ditto, "准备新版 macOS 应用")?; + run_macos_update_command(&mut ditto, "Prepare new macOS application")?; let mut codesign = Command::new("codesign"); codesign .args(["--verify", "--deep", "--strict"]) .arg(&replacement_app); - if let Err(error) = run_macos_update_command(&mut codesign, "校验待替换 macOS 应用签名") + if let Err(error) = run_macos_update_command(&mut codesign, "Verify replacement macOS application signature") { let _ = fs::remove_dir_all(&replacement_app); return Err(error); @@ -2353,21 +2353,21 @@ pub(crate) fn replace_macos_application(descriptor: &MacosUpdateDescriptor) -> R .args(["--assess", "--type", "execute", "--verbose=2"]) .arg(&replacement_app); if let Err(error) = - run_macos_update_command(&mut gatekeeper, "校验待替换 macOS 应用系统信任状态") + run_macos_update_command(&mut gatekeeper, "Verify replacement macOS application system trust status") { let _ = fs::remove_dir_all(&replacement_app); return Err(error); } if descriptor.backup_app.exists() { fs::remove_dir_all(&descriptor.backup_app) - .map_err(|error| format!("清理旧的 macOS 应用备份失败: {error}"))?; + .map_err(|error| format!("Failed to clean up old macOS application backup: {error}"))?; } fs::rename(&descriptor.current_app, &descriptor.backup_app) - .map_err(|error| format!("备份旧版 macOS 应用失败: {error}"))?; + .map_err(|error| format!("Failed to back up old macOS application: {error}"))?; if let Err(error) = fs::rename(&replacement_app, &descriptor.current_app) { let _ = fs::rename(&descriptor.backup_app, &descriptor.current_app); let _ = fs::remove_dir_all(&replacement_app); - return Err(format!("替换 macOS 应用失败: {error}")); + return Err(format!("Failed to replace macOS application: {error}")); } Ok(()) } @@ -2385,9 +2385,9 @@ fn cleanup_macos_update_payload(descriptor_path: &Path, descriptor: &MacosUpdate #[cfg(target_os = "macos")] pub(crate) fn run_portable_update_helper(descriptor_path: &Path) -> Result<(), String> { let descriptor = serde_json::from_slice::( - &fs::read(descriptor_path).map_err(|error| format!("读取应用更新描述失败: {error}"))?, + &fs::read(descriptor_path).map_err(|error| format!("Failed to read application update descriptor: {error}"))?, ) - .map_err(|error| format!("解析应用更新描述失败: {error}"))?; + .map_err(|error| format!("Failed to parse application update descriptor: {error}"))?; validate_macos_update_descriptor(descriptor_path, &descriptor)?; acknowledge_portable_update_helper_start(&descriptor.work_dir)?; if let Err(error) = @@ -2425,7 +2425,7 @@ pub(crate) fn run_portable_update_helper(descriptor_path: &Path) -> Result<(), S .join(&descriptor.executable_relative_path); let _ = Command::new(rollback_exe).spawn(); cleanup_macos_update_payload(descriptor_path, &descriptor); - return Err(format!("启动新版 macOS 应用失败: {error}")); + return Err(format!("Failed to start new macOS application: {error}")); } }; let deadline = Instant::now() + Duration::from_secs(60); @@ -2437,7 +2437,7 @@ pub(crate) fn run_portable_update_helper(descriptor_path: &Path) -> Result<(), S } if child .try_wait() - .map_err(|error| format!("检查新版 macOS 应用状态失败: {error}"))? + .map_err(|error| format!("Failed to check new macOS application status: {error}"))? .is_some() { break; @@ -2453,7 +2453,7 @@ pub(crate) fn run_portable_update_helper(descriptor_path: &Path) -> Result<(), S let _ = Command::new(rollback_exe).spawn(); cleanup_macos_update_payload(descriptor_path, &descriptor); Err(format!( - "新版应用 {} 未能在 60 秒内完成启动确认,已回滚", + "New application version {} did not confirm startup within 60 seconds; rolled back", descriptor.target_version )) } diff --git a/src-tauri/src/claude_catalog.rs b/src-tauri/src/claude_catalog.rs index d879e06..09e2666 100644 --- a/src-tauri/src/claude_catalog.rs +++ b/src-tauri/src/claude_catalog.rs @@ -52,22 +52,22 @@ fn catalog_state() -> Result<&'static ClaudeCatalog, String> { fn parse_catalog(content: &str) -> Result { let root: Value = serde_json::from_str(content) - .map_err(|error| format!("解析内置 Claude model-catalog.json 失败: {error}"))?; + .map_err(|error| format!("Failed to parse bundled Claude model-catalog.json: {error}"))?; let root = root .as_object() - .ok_or_else(|| "内置 Claude model-catalog.json 根节点必须是对象".to_string())?; + .ok_or_else(|| "Bundled Claude model-catalog.json root must be an object".to_string())?; let fallback_context_window = root .get("fallback_model") .and_then(Value::as_object) .and_then(|model| model.get("context_window")) .and_then(positive_u64) .ok_or_else(|| { - "内置 Claude model-catalog.json 缺少有效的 fallback_model.context_window".to_string() + "Bundled Claude model-catalog.json is missing a valid fallback_model.context_window".to_string() })?; let models = root .get("models") .and_then(Value::as_array) - .ok_or_else(|| "内置 Claude model-catalog.json 必须包含 models 数组".to_string())?; + .ok_or_else(|| "Bundled Claude model-catalog.json must contain a models array".to_string())?; let mut context_windows = HashMap::with_capacity(models.len()); let mut display_names = HashMap::with_capacity(models.len()); let mut claude_code_effort_levels = HashMap::with_capacity(models.len()); @@ -75,7 +75,7 @@ fn parse_catalog(content: &str) -> Result { for (index, model) in models.iter().enumerate() { let model = model.as_object().ok_or_else(|| { format!( - "内置 Claude model-catalog.json 第 {} 个模型必须是对象", + "Model {} in bundled Claude model-catalog.json must be an object", index + 1 ) })?; @@ -86,14 +86,14 @@ fn parse_catalog(content: &str) -> Result { .filter(|value| !value.is_empty()) .ok_or_else(|| { format!( - "内置 Claude model-catalog.json 第 {} 个模型的 slug 不能为空", + "The slug of model {} in bundled Claude model-catalog.json cannot be empty", index + 1 ) })?; let key = normalize_id(slug); if !model_ids.insert(key.clone()) { return Err(format!( - "内置 Claude model-catalog.json 模型 slug 大小写重复: {slug}" + "Bundled Claude model-catalog.json contains duplicate model slugs differing only by case: {slug}" )); } if let Some(context_window) = model.get("context_window").and_then(positive_u64) { @@ -194,7 +194,7 @@ mod tests { }"#, ) .unwrap_err(); - assert!(error.contains("slug 大小写重复")); + assert!(error.contains("duplicate model slugs differing only by case")); } #[test] diff --git a/src-tauri/src/codex_catalog.rs b/src-tauri/src/codex_catalog.rs index ad8277c..3c8d078 100644 --- a/src-tauri/src/codex_catalog.rs +++ b/src-tauri/src/codex_catalog.rs @@ -104,7 +104,7 @@ pub(crate) fn activate_catalog_json(catalog_json: &str) -> Result let parsed = parse_sources(catalog_json)?; let mut state = catalog_state()? .write() - .map_err(|_| "Codex 模型目录内存锁已损坏".to_string())?; + .map_err(|_| "Codex model catalog memory lock is poisoned".to_string())?; Ok(state.activate(catalog_json, parsed)) } @@ -115,14 +115,14 @@ pub(crate) fn validate_catalog_json(catalog_json: &str) -> Result { pub(crate) fn current_catalog_revision() -> Result { let state = catalog_state()? .read() - .map_err(|_| "Codex 模型目录内存锁已损坏".to_string())?; + .map_err(|_| "Codex model catalog memory lock is poisoned".to_string())?; Ok(state.sources.revision) } pub(crate) fn current_catalog_json() -> Result { let state = catalog_state()? .read() - .map_err(|_| "Codex 模型目录内存锁已损坏".to_string())?; + .map_err(|_| "Codex model catalog memory lock is poisoned".to_string())?; Ok(state.json.clone()) } @@ -132,7 +132,7 @@ pub(crate) fn parse_runtime_models(payload: &Value) -> Result Result Result { let state = catalog_state()? .read() - .map_err(|_| "Codex 模型目录内存锁已损坏".to_string())?; + .map_err(|_| "Codex model catalog memory lock is poisoned".to_string())?; prepare_catalog_with_customizations(runtime_models, &state.sources, &state.customizations) } @@ -265,11 +265,11 @@ fn parse_sources(catalog_json: &str) -> Result { fn parse_fallback_model(fallback_json: &str) -> Result, String> { let fallback: Value = serde_json::from_str(fallback_json) - .map_err(|error| format!("解析内置 fallback-model.json 失败: {error}"))?; + .map_err(|error| format!("Failed to parse bundled fallback-model.json: {error}"))?; let fallback = fallback .as_object() .cloned() - .ok_or_else(|| "内置 fallback-model.json 根节点必须是对象".to_string())?; + .ok_or_else(|| "Bundled fallback-model.json root must be an object".to_string())?; validate_model(&fallback, "fallback-model.json", false)?; Ok(fallback) } @@ -279,15 +279,15 @@ fn parse_catalog_sources( fallback: Map, ) -> Result { let root: Value = serde_json::from_str(catalog_json) - .map_err(|error| format!("解析内置 model-catalog.json 失败: {error}"))?; + .map_err(|error| format!("Failed to parse bundled model-catalog.json: {error}"))?; let root = root .as_object() - .ok_or_else(|| "内置 model-catalog.json 根节点必须是对象".to_string())?; + .ok_or_else(|| "Bundled model-catalog.json root must be an object".to_string())?; let revision = match root.get("catalog_revision") { Some(value) => value .as_u64() - .ok_or_else(|| "Codex 模型目录 catalog_revision 必须为非负整数".to_string())?, + .ok_or_else(|| "Codex model catalog catalog_revision must be a non-negative integer".to_string())?, None => 0, }; @@ -295,20 +295,20 @@ fn parse_catalog_sources( .get("models") .and_then(Value::as_array) .filter(|models| !models.is_empty()) - .ok_or_else(|| "内置 model-catalog.json 必须包含非空 models 数组".to_string())?; + .ok_or_else(|| "Bundled model-catalog.json must contain a non-empty models array".to_string())?; let mut templates = HashMap::with_capacity(values.len()); let mut max_template_priority = 0; for (order, value) in values.iter().enumerate() { let model = value .as_object() .cloned() - .ok_or_else(|| format!("内置 model-catalog.json 第 {} 个模型必须是对象", order + 1))?; - validate_model(&model, &format!("第 {} 个正式模板", order + 1), true)?; + .ok_or_else(|| format!("Model {} in bundled model-catalog.json must be an object", order + 1))?; + validate_model(&model, &format!("Official template {}", order + 1), true)?; let slug = string_value(&model, "slug"); let key = normalize_id(&slug); if templates.contains_key(&key) { return Err(format!( - "内置 model-catalog.json 模型 slug 大小写重复: {slug}" + "Bundled model-catalog.json contains duplicate model slugs differing only by case: {slug}" )); } max_template_priority = max_template_priority.max(priority_value(&model)); @@ -332,13 +332,13 @@ fn parse_catalog_sources( #[cfg(test)] fn parse_combined_sources_for_test(catalog_json: &str) -> Result { let mut root: Value = serde_json::from_str(catalog_json) - .map_err(|error| format!("解析测试 model-catalog.json 失败: {error}"))?; + .map_err(|error| format!("Failed to parse test model-catalog.json: {error}"))?; let fallback = root .as_object_mut() .and_then(|root| root.remove("fallback_model")) .and_then(|fallback| fallback.as_object().cloned()) - .ok_or_else(|| "测试 model-catalog.json 缺少 fallback_model 对象".to_string())?; - validate_model(&fallback, "测试 fallback_model", false)?; + .ok_or_else(|| "Test model-catalog.json is missing a fallback_model object".to_string())?; + validate_model(&fallback, "Test fallback_model", false)?; parse_catalog_sources(&root.to_string(), fallback) } @@ -348,23 +348,23 @@ fn validate_model( require_slug: bool, ) -> Result<(), String> { if require_slug && string_value(model, "slug").is_empty() { - return Err(format!("内置 model-catalog.json {label} 的 slug 不能为空")); + return Err(format!("The slug of {label} in bundled model-catalog.json cannot be empty")); } if string_value(model, "base_instructions").is_empty() { return Err(format!( - "内置 model-catalog.json {label} 的 base_instructions 不能为空" + "The base_instructions of {label} in bundled model-catalog.json cannot be empty" )); } validate_required_codex_fields(model, label)?; let context_window = positive_u64_value(model.get("context_window")) - .ok_or_else(|| format!("内置 model-catalog.json {label} 的 context_window 必须为正数"))?; + .ok_or_else(|| format!("The context_window of {label} in bundled model-catalog.json must be positive"))?; let max_context_window = positive_u64_value(model.get("max_context_window")).ok_or_else(|| { - format!("内置 model-catalog.json {label} 的 max_context_window 必须为正数") + format!("The max_context_window of {label} in bundled model-catalog.json must be positive") })?; if max_context_window < context_window { return Err(format!( - "内置 model-catalog.json {label} 的 max_context_window 不能小于 context_window" + "The max_context_window of {label} in bundled model-catalog.json cannot be less than context_window" )); } Ok(()) @@ -374,13 +374,13 @@ fn validate_required_codex_fields(model: &Map, label: &str) -> Re for field in ["display_name", "shell_type", "visibility"] { if string_value(model, field).is_empty() { return Err(format!( - "Codex 模型 {label} 的必填字段 {field} 必须是非空字符串" + "Required field {field} of Codex model {label} must be a non-empty string" )); } } for field in ["supported_reasoning_levels", "experimental_supported_tools"] { if !model.get(field).is_some_and(Value::is_array) { - return Err(format!("Codex 模型 {label} 的必填字段 {field} 必须是数组")); + return Err(format!("Required field {field} of Codex model {label} must be an array")); } } for field in [ @@ -390,7 +390,7 @@ fn validate_required_codex_fields(model: &Map, label: &str) -> Re ] { if !model.get(field).is_some_and(Value::is_boolean) { return Err(format!( - "Codex 模型 {label} 的必填字段 {field} 必须是布尔值" + "Required field {field} of Codex model {label} must be a boolean" )); } } @@ -398,26 +398,26 @@ fn validate_required_codex_fields(model: &Map, label: &str) -> Re .get("priority") .is_some_and(|value| value.as_i64().is_some() || value.as_u64().is_some()) { - return Err(format!("Codex 模型 {label} 的必填字段 priority 必须是整数")); + return Err(format!("Required field priority of Codex model {label} must be an integer")); } if !model .get("default_reasoning_summary") .is_some_and(Value::is_string) { return Err(format!( - "Codex 模型 {label} 的必填字段 default_reasoning_summary 必须是字符串" + "Required field default_reasoning_summary of Codex model {label} must be a string" )); } let truncation = model .get("truncation_policy") .and_then(Value::as_object) - .ok_or_else(|| format!("Codex 模型 {label} 缺少 truncation_policy 对象"))?; + .ok_or_else(|| format!("Codex model {label} is missing a truncation_policy object"))?; if string_value(truncation, "mode").is_empty() || !truncation .get("limit") .is_some_and(|value| value.as_i64().is_some() || value.as_u64().is_some()) { - return Err(format!("Codex 模型 {label} 的 truncation_policy 无效")); + return Err(format!("Invalid truncation_policy for Codex model {label}")); } Ok(()) } @@ -436,7 +436,7 @@ fn prepare_catalog_with_customizations( customizations: &customizations::ModelCustomizations, ) -> Result { if runtime_models.is_empty() { - return Err("CPA 当前没有可写入 Codex 的模型".to_string()); + return Err("CPA currently has no models that can be written to Codex".to_string()); } let mut entries = Vec::with_capacity(runtime_models.len()); @@ -471,7 +471,7 @@ fn prepare_catalog_with_customizations( } } if entries.is_empty() { - return Err("CPA 当前没有有效的 Codex 模型 ID".to_string()); + return Err("CPA currently has no valid Codex model ID".to_string()); } for entry in &mut entries { @@ -532,7 +532,7 @@ fn prepare_catalog_with_customizations( .map(|entry| Value::Object(entry.value)) .collect::>(); let mut json = serde_json::to_string_pretty(&serde_json::json!({ "models": values })) - .map_err(|error| format!("生成 Codex 模型目录失败: {error}"))?; + .map_err(|error| format!("Failed to generate Codex model catalog: {error}"))?; json.push('\n'); Ok(PreparedCodexCatalog { models, json }) } diff --git a/src-tauri/src/codex_catalog/customizations.rs b/src-tauri/src/codex_catalog/customizations.rs index c02620a..1f90fc1 100644 --- a/src-tauri/src/codex_catalog/customizations.rs +++ b/src-tauri/src/codex_catalog/customizations.rs @@ -80,7 +80,7 @@ fn editable_configuration(model: &Map) -> Map { fn validate_configuration(model: &Map) -> Result<(), String> { for field in model.keys() { if !EDITABLE_FIELDS.contains(&field.as_str()) { - return Err(format!("不允许修改模型字段 {field}")); + return Err(format!("Model field {field} cannot be modified")); } } for field in [ @@ -96,7 +96,7 @@ fn validate_configuration(model: &Map) -> Result<(), String> { .as_u64() .is_some_and(|value| (1..=9_007_199_254_740_991).contains(&value)) { - return Err(format!("{field} 必须是有效的正整数")); + return Err(format!("{field} must be a valid positive integer")); } } } @@ -105,14 +105,14 @@ fn validate_configuration(model: &Map) -> Result<(), String> { model.get("max_context_window").and_then(Value::as_u64), ) { if maximum < context { - return Err("最大上下文窗口不能小于上下文窗口".to_string()); + return Err("Maximum context window cannot be smaller than the context window".to_string()); } if model .get("auto_compact_token_limit") .and_then(Value::as_u64) .is_some_and(|limit| limit > context) { - return Err("自动压缩阈值不能大于上下文窗口".to_string()); + return Err("Automatic compaction threshold cannot exceed the context window".to_string()); } } if let Some(value) = model.get("effective_context_window_percent") { @@ -120,7 +120,7 @@ fn validate_configuration(model: &Map) -> Result<(), String> { .as_u64() .is_some_and(|value| (1..=100).contains(&value)) { - return Err("有效上下文比例必须为 1 到 100 的整数".to_string()); + return Err("Effective context percentage must be an integer from 1 to 100".to_string()); } } for field in ["display_name", "description"] { @@ -131,61 +131,61 @@ fn validate_configuration(model: &Map) -> Result<(), String> { if !value.as_str().is_some_and(|text| { text.len() <= 4_000 && (field == "description" || !text.trim().is_empty()) }) { - return Err(format!("{field} 必须是有效的文本")); + return Err(format!("{field} must be valid text")); } } } if let Some(value) = model.get("visibility") { if !matches!(value.as_str(), Some("list" | "hide" | "none")) { - return Err("模型显示状态无效".to_string()); + return Err("Invalid model display status".to_string()); } } if let Some(value) = model.get("supports_parallel_tool_calls") { if !value.is_boolean() { - return Err("并行工具调用必须为布尔值".to_string()); + return Err("Parallel tool calls must be a boolean".to_string()); } } if let Some(value) = model.get("input_modalities") { - let modalities = value.as_array().ok_or("输入类型必须为数组")?; + let modalities = value.as_array().ok_or("Input types must be an array")?; let mut seen = HashSet::new(); if modalities.is_empty() || modalities.iter().any(|value| { !matches!(value.as_str(), Some("text" | "image")) || !seen.insert(value.as_str()) }) { - return Err("输入类型仅允许不重复的 text 和 image,且至少选择一项".to_string()); + return Err("Input types may contain only unique text and image values, with at least one selected".to_string()); } } let default = model.get("default_reasoning_level"); if let Some(value) = default { if !value.is_null() && !value.as_str().is_some_and(is_allowed_reasoning_level) { - return Err("默认思考等级无效".to_string()); + return Err("Invalid default reasoning level".to_string()); } } if let Some(value) = model.get("supported_reasoning_levels") { - let levels = value.as_array().ok_or("思考等级必须为数组")?; + let levels = value.as_array().ok_or("Reasoning levels must be an array")?; let mut seen = HashSet::new(); for level in levels { let effort = level .get("effort") .and_then(Value::as_str) - .ok_or("思考等级缺少 effort")?; + .ok_or("Reasoning level is missing effort")?; if !is_allowed_reasoning_level(effort) || !seen.insert(effort) { - return Err(format!("思考等级无效或重复: {effort}")); + return Err(format!("Invalid or duplicate reasoning level: {effort}")); } if !level .get("description") .and_then(Value::as_str) .is_some_and(|text| text.len() <= 4_000) { - return Err("思考等级说明必须是有效的文本".to_string()); + return Err("Reasoning level description must be valid text".to_string()); } } if default .and_then(Value::as_str) .is_some_and(|effort| !seen.contains(effort)) { - return Err("默认思考等级必须包含在可选等级中".to_string()); + return Err("Default reasoning level must be included in the available levels".to_string()); } } Ok(()) @@ -199,7 +199,7 @@ pub(super) fn apply_customizations( if let Some(customization) = customizations.get(&normalize_id(&slug)) { model.extend(customization.clone()); validate_configuration(&editable_configuration(model)) - .map_err(|error| format!("模型 {slug} 的自定义配置无效: {error}"))?; + .map_err(|error| format!("Invalid custom configuration for model {slug}: {error}"))?; enable_fast_mode(model); } Ok(()) @@ -219,9 +219,9 @@ pub(super) fn snapshot_for_state( let mut models = Vec::new(); for value in root["models"] .as_array() - .ok_or("模型目录缺少 models 数组")? + .ok_or("Model catalog is missing a models array")? { - let mut model = value.as_object().cloned().ok_or("模型目录条目必须为对象")?; + let mut model = value.as_object().cloned().ok_or("Model catalog entry must be an object")?; let slug = string_value(&model, "slug"); let key = normalize_id(&slug); let defaults = editable_configuration(&model); @@ -252,7 +252,7 @@ pub(crate) fn editor_snapshot( ) -> Result { let state = catalog_state()? .read() - .map_err(|_| "Codex 模型目录内存锁已损坏")?; + .map_err(|_| "Codex model catalog memory lock is poisoned")?; snapshot_for_state(runtime_models, &state) } @@ -264,7 +264,7 @@ fn customizations_from_request( return Err("CODEX_MODEL_CATALOG_CHANGED".to_string()); } if request.models.len() != snapshot.models.len() { - return Err("模型列表已变化,请重新加载后编辑".to_string()); + return Err("Model list has changed. Reload it before editing".to_string()); } let mut seen = HashSet::new(); let mut customizations = BTreeMap::new(); @@ -274,15 +274,15 @@ fn customizations_from_request( .models .iter() .find(|model| normalize_id(&model.slug) == key) - .ok_or_else(|| format!("模型 {} 已不在当前列表中", requested.slug))?; + .ok_or_else(|| format!("Model {} is no longer in the current list", requested.slug))?; if !seen.insert(key.clone()) { - return Err(format!("模型 {} 重复", requested.slug)); + return Err(format!("Duplicate model {}", requested.slug)); } if requested.configuration.len() != EDITABLE_FIELDS.len() { - return Err(format!("模型 {} 的配置字段不完整", requested.slug)); + return Err(format!("Model {} has incomplete configuration fields", requested.slug)); } validate_configuration(&requested.configuration) - .map_err(|error| format!("模型 {}: {error}", requested.slug))?; + .map_err(|error| format!("Model {}: {error}", requested.slug))?; let mut changes: Map = requested .configuration .iter() @@ -313,18 +313,18 @@ fn customizations_from_request( fn decode_customizations(content: &[u8]) -> Result { if content.len() > crate::MAX_CODEX_MODEL_CATALOG_BYTES { - return Err("Codex 自定义模型配置超过大小限制".to_string()); + return Err("Codex custom model configuration exceeds the size limit".to_string()); } let saved: SavedCustomizations = serde_json::from_slice(content).map_err(|error| error.to_string())?; if saved.version != 1 { - return Err("不支持的 Codex 自定义模型配置版本".to_string()); + return Err("Unsupported Codex custom model configuration version".to_string()); } let mut normalized = BTreeMap::new(); for (slug, model) in saved.models { let key = normalize_id(&slug); if key.is_empty() || normalized.contains_key(&key) { - return Err("自定义模型 ID 为空或重复".to_string()); + return Err("Custom model ID is empty or duplicated".to_string()); } validate_configuration(&model)?; normalized.insert(key, model); @@ -336,12 +336,12 @@ pub(crate) fn load_customizations(path: &Path) -> Result<(), String> { let content = match std::fs::read(path) { Ok(content) => content, Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()), - Err(error) => return Err(format!("读取 Codex 自定义模型配置失败: {error}")), + Err(error) => return Err(format!("Failed to read Codex custom model configuration: {error}")), }; let customizations = decode_customizations(&content)?; let mut state = catalog_state()? .write() - .map_err(|_| "Codex 模型目录内存锁已损坏")?; + .map_err(|_| "Codex model catalog memory lock is poisoned")?; state.customizations = customizations; Ok(()) } @@ -360,7 +360,7 @@ fn save_for_state( }; let content = serde_json::to_vec_pretty(&saved).map_err(|error| error.to_string())?; if content.len() > crate::MAX_CODEX_MODEL_CATALOG_BYTES { - return Err("Codex 自定义模型配置超过大小限制".to_string()); + return Err("Codex custom model configuration exceeds the size limit".to_string()); } crate::write_bytes_atomically(path, &content)?; state.customizations = saved.models; @@ -374,7 +374,7 @@ pub(crate) fn save_customizations( ) -> Result { let mut state = catalog_state()? .write() - .map_err(|_| "Codex 模型目录内存锁已损坏")?; + .map_err(|_| "Codex model catalog memory lock is poisoned")?; save_for_state(path, runtime_models, request, &mut state) } diff --git a/src-tauri/src/codex_catalog/runtime_context.rs b/src-tauri/src/codex_catalog/runtime_context.rs index c595f5c..3fedd26 100644 --- a/src-tauri/src/codex_catalog/runtime_context.rs +++ b/src-tauri/src/codex_catalog/runtime_context.rs @@ -5,10 +5,10 @@ pub(crate) fn apply_configured_context_limits( content: &str, ) -> Result<(), String> { let document: serde_norway::Value = serde_norway::from_str(content) - .map_err(|error| format!("解析内核模型上下文配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel model context configuration: {error}"))?; let root = document .as_mapping() - .ok_or("内核配置顶层必须是 YAML 映射")?; + .ok_or("Kernel configuration root must be a YAML mapping")?; let mut limits: HashMap = HashMap::new(); for section in crate::MODEL_ALIAS_CONFIG_SECTIONS { let Some(providers) = diff --git a/src-tauri/src/codex_sessions.rs b/src-tauri/src/codex_sessions.rs index e2d53d2..37538b9 100644 --- a/src-tauri/src/codex_sessions.rs +++ b/src-tauri/src/codex_sessions.rs @@ -197,7 +197,7 @@ pub(crate) async fn list_codex_sessions( let user_home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let codex_home = resolve_codex_home(&user_home); let request = request.unwrap_or(ListCodexSessionsRequest { offset: 0, @@ -207,7 +207,7 @@ pub(crate) async fn list_codex_sessions( list_codex_sessions_from_home(&codex_home, request.offset, request.limit) }) .await - .map_err(|error| format!("读取 Codex 会话任务失败: {error}"))? + .map_err(|error| format!("Failed to read Codex session task: {error}"))? } #[tauri::command] @@ -218,13 +218,13 @@ pub(crate) async fn delete_codex_sessions( let user_home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let codex_home = resolve_codex_home(&user_home); tauri::async_runtime::spawn_blocking(move || { delete_codex_sessions_from_home(&codex_home, request.session_ids) }) .await - .map_err(|error| format!("删除 Codex 会话任务失败: {error}"))? + .map_err(|error| format!("Failed to delete Codex session task: {error}"))? } #[tauri::command] @@ -234,7 +234,7 @@ pub(crate) async fn repair_codex_session_metadata( let user_home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let codex_home = resolve_codex_home(&user_home); let progress_app = app.clone(); tauri::async_runtime::spawn_blocking(move || { @@ -244,7 +244,7 @@ pub(crate) async fn repair_codex_session_metadata( }) }) .await - .map_err(|error| format!("恢复 Codex 历史会话任务失败: {error}"))? + .map_err(|error| format!("Failed to restore Codex historical session task: {error}"))? } #[tauri::command] @@ -254,13 +254,13 @@ pub(crate) async fn preview_codex_session_index_cleanup( let user_home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let codex_home = resolve_codex_home(&user_home); tauri::async_runtime::spawn_blocking(move || { preview_session_index_cleanup_from_home(&codex_home) }) .await - .map_err(|error| format!("预览 Codex 会话索引任务失败: {error}"))? + .map_err(|error| format!("Failed to preview Codex session index task: {error}"))? } #[tauri::command] @@ -271,7 +271,7 @@ pub(crate) async fn apply_codex_session_index_cleanup( let user_home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let codex_home = resolve_codex_home(&user_home); tauri::async_runtime::spawn_blocking(move || { apply_session_index_cleanup_from_home( @@ -282,7 +282,7 @@ pub(crate) async fn apply_codex_session_index_cleanup( ) }) .await - .map_err(|error| format!("清理 Codex 会话索引任务失败: {error}"))? + .map_err(|error| format!("Failed to clean up Codex session index task: {error}"))? } fn resolve_codex_home(user_home: &Path) -> PathBuf { @@ -303,10 +303,10 @@ fn open_read_only(path: &Path) -> Result { path, OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX, ) - .map_err(|error| format!("打开数据库失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to open database {}: {error}", path.display()))?; connection .busy_timeout(SQLITE_BUSY_TIMEOUT) - .map_err(|error| format!("设置数据库等待时间失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to set database timeout {}: {error}", path.display()))?; Ok(connection) } @@ -315,10 +315,10 @@ fn open_read_write(path: &Path) -> Result { path, OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_NO_MUTEX, ) - .map_err(|error| format!("打开数据库失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to open database {}: {error}", path.display()))?; connection .busy_timeout(SQLITE_BUSY_TIMEOUT) - .map_err(|error| format!("设置数据库等待时间失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to set database timeout {}: {error}", path.display()))?; Ok(connection) } @@ -384,7 +384,7 @@ fn discover_database_paths(codex_home: &Path, reference_only: bool) -> (Vec has_supported_table = true, Ok(false) => {} Err(error) => { - warnings.push(format!("检查数据库结构失败 {}: {error}", path.display())) + warnings.push(format!("Failed to inspect database schema {}: {error}", path.display())) } } } @@ -403,7 +403,7 @@ fn table_exists(connection: &Connection, table: &str) -> Result { ) { Ok(()) => Ok(true), Err(rusqlite::Error::QueryReturnedNoRows) => Ok(false), - Err(error) => Err(format!("读取 SQLite 表清单失败: {error}")), + Err(error) => Err(format!("Failed to read SQLite table list: {error}")), } } @@ -416,12 +416,12 @@ fn table_columns(connection: &Connection, table: &str) -> Result "PRAGMA table_info(\"{}\")", table.replace('"', "\"\"") )) - .map_err(|error| format!("读取 {table} 表结构失败: {error}"))?; + .map_err(|error| format!("Failed to read {table} table schema: {error}"))?; let rows = statement .query_map([], |row| row.get::<_, String>(1)) - .map_err(|error| format!("读取 {table} 表结构失败: {error}"))?; + .map_err(|error| format!("Failed to read {table} table schema: {error}"))?; rows.collect::>>() - .map_err(|error| format!("读取 {table} 表结构失败: {error}")) + .map_err(|error| format!("Failed to read {table} table schema: {error}")) } fn optional_column<'a>(columns: &HashSet, column: &'a str, fallback: &'a str) -> &'a str { @@ -556,7 +556,7 @@ fn list_sessions_from_rollouts(codex_home: &Path) -> (Vec, Ok(None) => {} Err(error) if is_locked_error_message(&error) => { warnings.push(format!( - "会话文件正在使用,已跳过 {}: {error}", + "Session file is in use; skipped {}: {error}", path.display() )); } @@ -574,7 +574,7 @@ fn read_session_index_metadata( return Ok(HashMap::new()); } let content = fs::read_to_string(&path) - .map_err(|error| format!("读取 session_index.jsonl 失败: {error}"))?; + .map_err(|error| format!("Failed to read session_index.jsonl: {error}"))?; let mut metadata = HashMap::new(); for line in content.lines() { let Ok(record) = serde_json::from_str::(line) else { @@ -617,7 +617,7 @@ fn summarize_rollout_session( metadata: &HashMap, ) -> Result, String> { let file = fs::File::open(path) - .map_err(|error| format!("读取 rollout 失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to read rollout {}: {error}", path.display()))?; let mut id = None; let mut title = String::new(); let mut cwd = String::new(); @@ -625,7 +625,7 @@ fn summarize_rollout_session( let mut updated_at_ms = None; for line in BufReader::new(file).lines() { let line = - line.map_err(|error| format!("读取 rollout 失败 {}: {error}", path.display()))?; + line.map_err(|error| format!("Failed to read rollout {}: {error}", path.display()))?; let Ok(record) = serde_json::from_str::(&line) else { continue; }; @@ -740,13 +740,13 @@ fn list_session_ids_from_database(path: &Path) -> Result, String ); let mut statement = connection .prepare(&sql) - .map_err(|error| format!("统计会话总数失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to count sessions {}: {error}", path.display()))?; let rows = statement .query_map([], |row| row.get::<_, String>(0)) - .map_err(|error| format!("统计会话总数失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to count sessions {}: {error}", path.display()))?; for id in rows .collect::>>() - .map_err(|error| format!("统计会话总数失败 {}: {error}", path.display()))? + .map_err(|error| format!("Failed to count sessions {}: {error}", path.display()))? { let id = session_identity_key(&id); if !id.is_empty() { @@ -790,7 +790,7 @@ fn list_thread_rows( ); let mut statement = connection .prepare(&sql) - .map_err(|error| format!("读取会话数据库失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to read session database {}: {error}", path.display()))?; let rows = statement .query_map([i64::try_from(limit).unwrap_or(i64::MAX)], |row| { Ok(CodexSessionSummary { @@ -803,9 +803,9 @@ fn list_thread_rows( database_path: path.to_string_lossy().to_string(), }) }) - .map_err(|error| format!("读取会话数据库失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to read session database {}: {error}", path.display()))?; rows.collect::>>() - .map_err(|error| format!("读取会话数据库失败 {}: {error}", path.display())) + .map_err(|error| format!("Failed to read session database {}: {error}", path.display())) } fn list_automation_rows( @@ -832,7 +832,7 @@ fn list_automation_rows( ); let mut statement = connection .prepare(&sql) - .map_err(|error| format!("读取自动化会话数据库失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to read automation session database {}: {error}", path.display()))?; let rows = statement .query_map([i64::try_from(limit).unwrap_or(i64::MAX)], |row| { let status = row.get::<_, Option>(3)?.unwrap_or_default(); @@ -846,9 +846,9 @@ fn list_automation_rows( database_path: path.to_string_lossy().to_string(), }) }) - .map_err(|error| format!("读取自动化会话数据库失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to read automation session database {}: {error}", path.display()))?; rows.collect::>>() - .map_err(|error| format!("读取自动化会话数据库失败 {}: {error}", path.display())) + .map_err(|error| format!("Failed to read automation session database {}: {error}", path.display())) } fn normalize_thread_id(value: &str) -> String { @@ -875,7 +875,7 @@ fn delete_codex_sessions_from_home( ) -> Result { let _guard = CODEX_SESSION_WRITE_LOCK .lock() - .map_err(|_| "Codex 会话写入锁已损坏".to_string())?; + .map_err(|_| "Codex session write lock is poisoned".to_string())?; let mut unique = Vec::new(); let mut seen = HashSet::new(); for value in session_ids { @@ -885,15 +885,15 @@ fn delete_codex_sessions_from_home( } } if unique.is_empty() { - return Err("请至少选择一个有效的会话 ID".to_string()); + return Err("Select at least one valid session ID".to_string()); } if unique.len() > MAX_PAGE_SIZE { - return Err(format!("一次最多删除 {MAX_PAGE_SIZE} 个会话")); + return Err(format!("You can delete up to {MAX_PAGE_SIZE} sessions at a time")); } let (database_paths, database_warnings) = discover_database_paths(codex_home, false); if !database_warnings.is_empty() { return Err(format!( - "无法安全检查全部 Codex 会话数据库,删除已中止:{}", + "Unable to safely inspect all Codex session databases; deletion aborted: {}", database_warnings.join(";") )); } @@ -929,7 +929,7 @@ fn delete_one_session( return Ok(CodexSessionDeleteResult { session_id: session_id.to_string(), status: "notFound".to_string(), - message: "未在本地数据库中找到该会话".to_string(), + message: "Session not found in the local database".to_string(), backup_path: None, }); } @@ -945,7 +945,7 @@ fn delete_one_session( session_id: session_id.to_string(), status: status.to_string(), message: format!( - "已提交 {committed_databases} 个数据库事务,其余数据库删除失败:{error}" + "Committed {committed_databases} database transactions; deletion failed for the remaining databases: {error}" ), backup_path: Some(backup_dir.to_string_lossy().to_string()), }); @@ -971,19 +971,19 @@ fn delete_one_session( } } Ok(None) => rollout_errors.push(format!( - "已保留不在 Codex 会话目录中的 rollout:{}", + "Preserved rollout outside the Codex session directory: {}", path.display() )), Err(error) => rollout_errors.push(error), } } let (status, message) = if rollout_errors.is_empty() { - ("deleted", "已删除本地数据库记录和 rollout 文件".to_string()) + ("deleted", "Deleted local database records and rollout files".to_string()) } else { ( "partial", format!( - "数据库记录已删除,但部分 rollout 未删除:{}", + "Database records were deleted, but some rollouts were not: {}", rollout_errors.join(";") ), ) @@ -1140,7 +1140,7 @@ fn select_json_rows( ) -> Result, String> { let mut statement = connection .prepare(sql) - .map_err(|error| format!("准备会话备份查询失败: {error}"))?; + .map_err(|error| format!("Failed to prepare session backup query: {error}"))?; let columns = statement .column_names() .iter() @@ -1154,9 +1154,9 @@ fn select_json_rows( } Ok(Value::Object(value)) }) - .map_err(|error| format!("读取会话备份数据失败: {error}"))?; + .map_err(|error| format!("Failed to read session backup data: {error}"))?; rows.collect::>>() - .map_err(|error| format!("读取会话备份数据失败: {error}")) + .map_err(|error| format!("Failed to read session backup data: {error}")) } fn sql_value_to_json(value: ValueRef<'_>) -> Value { @@ -1198,7 +1198,7 @@ fn create_operation_directory( .join(category) .join(name); fs::create_dir_all(&directory) - .map_err(|error| format!("创建会话备份目录失败 {}: {error}", directory.display()))?; + .map_err(|error| format!("Failed to create session backup directory {}: {error}", directory.display()))?; Ok(directory) } @@ -1246,7 +1246,7 @@ fn create_delete_backup( } fs::create_dir_all(&rollout_directory).map_err(|error| { format!( - "创建 rollout 备份目录失败 {}: {error}", + "Failed to create rollout backup directory {}: {error}", rollout_directory.display() ) })?; @@ -1259,7 +1259,7 @@ fn create_delete_backup( ); let target = rollout_directory.join(&file_name); fs::copy(&validated, &target) - .map_err(|error| format!("备份 rollout 失败 {}: {error}", validated.display()))?; + .map_err(|error| format!("Failed to back up rollout {}: {error}", validated.display()))?; rollout_files.push(json!({ "originalPath": validated.to_string_lossy(), "backupPath": format!("rollouts/{file_name}") @@ -1295,7 +1295,7 @@ fn validated_rollout_path(codex_home: &Path, path: &Path) -> Result Re let mut connection = open_read_write(&plan.path)?; let transaction = connection .transaction() - .map_err(|error| format!("开始数据库事务失败 {}: {error}", plan.path.display()))?; + .map_err(|error| format!("Failed to begin database transaction {}: {error}", plan.path.display()))?; if plan.has_thread_row { delete_where_if_supported( &transaction, @@ -1352,7 +1352,7 @@ fn apply_database_delete_plan(plan: &DatabaseDeletePlan, session_id: &str) -> Re "UPDATE agent_job_items SET assigned_thread_id = NULL WHERE assigned_thread_id = ?1", [session_id], ) - .map_err(|error| format!("更新 agent_job_items 失败: {error}"))?; + .map_err(|error| format!("Failed to update agent_job_items: {error}"))?; } delete_where_if_supported(&transaction, "threads", &["id"], "id = ?1", &[&session_id])?; } @@ -1388,7 +1388,7 @@ fn apply_database_delete_plan(plan: &DatabaseDeletePlan, session_id: &str) -> Re )?; transaction .commit() - .map_err(|error| format!("提交数据库删除失败 {}: {error}", plan.path.display())) + .map_err(|error| format!("Failed to commit database deletion {}: {error}", plan.path.display())) } fn delete_where_if_supported( @@ -1415,12 +1415,12 @@ fn delete_where_if_supported( params, ) .map(|_| ()) - .map_err(|error| format!("删除 {table} 关联记录失败: {error}")) + .map_err(|error| format!("Failed to delete related records from {table}: {error}")) } fn write_json_atomically(path: &Path, value: &Value) -> Result<(), String> { let bytes = - serde_json::to_vec_pretty(value).map_err(|error| format!("序列化会话备份失败: {error}"))?; + serde_json::to_vec_pretty(value).map_err(|error| format!("Failed to serialize session backup: {error}"))?; crate::write_bytes_atomically(path, &bytes) } @@ -1430,10 +1430,10 @@ fn resolve_current_codex_provider(codex_home: &Path) -> Result { return Ok("openai".to_string()); } let text = fs::read_to_string(&path) - .map_err(|error| format!("读取 Codex 配置失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to read Codex configuration {}: {error}", path.display()))?; let document = text .parse::() - .map_err(|error| format!("Codex 配置无法解析: {error}"))?; + .map_err(|error| format!("Failed to parse Codex configuration: {error}"))?; Ok(document .get("model_provider") .and_then(toml::Value::as_str) @@ -1453,11 +1453,11 @@ where { let target_provider = target_provider.trim(); if target_provider.is_empty() { - return Err("当前 Codex provider 不能为空".to_string()); + return Err("Current Codex provider cannot be empty".to_string()); } let _guard = CODEX_SESSION_WRITE_LOCK .lock() - .map_err(|_| "Codex 会话写入锁已损坏".to_string())?; + .map_err(|_| "Codex session write lock is poisoned".to_string())?; progress(CodexSessionRepairProgress { phase: "scanning".to_string(), percent: 10, @@ -1541,7 +1541,7 @@ where let _ = crate::write_bytes_atomically(&previous.path, &previous.original); restore_modified_time(&previous.path, previous.original_mtime); } - return Err(format!("写入历史会话失败;已尝试回滚:{error}")); + return Err(format!("Failed to write historical session; rollback attempted: {error}")); } } progress(CodexSessionRepairProgress { @@ -1569,7 +1569,7 @@ where restore_modified_time(&previous.path, previous.original_mtime); } return Err(format!( - "更新会话数据库失败;rollout 已尝试回滚,备份仍保留:{error}" + "Failed to update session database; rollout rollback attempted and backup retained: {error}" )); } }; @@ -1608,12 +1608,12 @@ fn collect_rollout_files_recursive(root: &Path, files: &mut Vec) -> Res return Ok(()); } let entries = fs::read_dir(root) - .map_err(|error| format!("扫描会话目录失败 {}: {error}", root.display()))?; + .map_err(|error| format!("Failed to scan session directory {}: {error}", root.display()))?; for entry in entries { - let entry = entry.map_err(|error| format!("读取会话目录项失败: {error}"))?; + let entry = entry.map_err(|error| format!("Failed to read session directory entry: {error}"))?; let file_type = entry .file_type() - .map_err(|error| format!("读取会话目录项类型失败: {error}"))?; + .map_err(|error| format!("Failed to read session directory entry type: {error}"))?; if file_type.is_symlink() { continue; } @@ -1632,9 +1632,9 @@ fn build_rollout_repair( target_provider: &str, ) -> Result, String> { let original = - fs::read(path).map_err(|error| format!("读取 rollout 失败 {}: {error}", path.display()))?; + fs::read(path).map_err(|error| format!("Failed to read rollout {}: {error}", path.display()))?; let text = String::from_utf8(original.clone()) - .map_err(|error| format!("rollout 不是 UTF-8 {}: {error}", path.display()))?; + .map_err(|error| format!("Rollout is not UTF-8 {}: {error}", path.display()))?; let mut next = String::with_capacity(text.len()); let mut changed = false; let mut session_meta_count = 0usize; @@ -1674,7 +1674,7 @@ fn build_rollout_repair( { payload.insert("model_provider".to_string(), json!(target_provider)); output = serde_json::to_string(&record) - .map_err(|error| format!("序列化 session_meta 失败: {error}"))?; + .map_err(|error| format!("Failed to serialize session_meta: {error}"))?; changed = true; } } @@ -1737,9 +1737,9 @@ fn load_projectless_thread_ids(codex_home: &Path) -> Result, Str return Ok(HashSet::new()); } let value = serde_json::from_str::( - &fs::read_to_string(&path).map_err(|error| format!("读取 Codex 全局状态失败: {error}"))?, + &fs::read_to_string(&path).map_err(|error| format!("Failed to read Codex global state: {error}"))?, ) - .map_err(|error| format!("解析 Codex 全局状态失败: {error}"))?; + .map_err(|error| format!("Failed to parse Codex global state: {error}"))?; Ok(value .get("projectless-thread-ids") .and_then(Value::as_array) @@ -1789,7 +1789,7 @@ fn count_sqlite_repairs( [target_provider], |row| row.get::<_, i64>(0), ) - .map_err(|error| format!("统计 Provider 迁移行失败: {error}"))? + .map_err(|error| format!("Failed to count provider migration rows: {error}"))? as usize; } if columns.contains("has_user_event") { @@ -1800,7 +1800,7 @@ fn count_sqlite_repairs( [id], |row| row.get::<_, i64>(0), ) - .map_err(|error| format!("统计会话可见性更新行失败: {error}"))? + .map_err(|error| format!("Failed to count session visibility update rows: {error}"))? as usize; } } @@ -1812,7 +1812,7 @@ fn count_sqlite_repairs( (id, cwd), |row| row.get::<_, i64>(0), ) - .map_err(|error| format!("统计项目路径更新行失败: {error}"))? + .map_err(|error| format!("Failed to count project path update rows: {error}"))? as usize; } } @@ -1821,7 +1821,7 @@ fn count_sqlite_repairs( match count_result { Ok(count) => total += count, Err(error) if is_locked_error_message(&error) => warnings.push(format!( - "数据库正在使用,无法预统计同步行 {}: {error}", + "Database is in use; cannot pre-count synchronization rows {}: {error}", path.display() )), Err(error) => return Err(error), @@ -1848,7 +1848,7 @@ fn apply_sqlite_repairs( } let transaction = connection .transaction() - .map_err(|error| format!("开始历史会话恢复事务失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to begin historical session restoration transaction {}: {error}", path.display()))?; let mut updated = 0usize; if columns.contains("model_provider") { updated += transaction @@ -1856,7 +1856,7 @@ fn apply_sqlite_repairs( "UPDATE threads SET model_provider = ?1 WHERE COALESCE(model_provider, '') <> ?1", [target_provider], ) - .map_err(|error| format!("更新会话 provider 失败: {error}"))?; + .map_err(|error| format!("Failed to update session provider: {error}"))?; } if columns.contains("has_user_event") { for id in &user_event_ids { @@ -1865,7 +1865,7 @@ fn apply_sqlite_repairs( "UPDATE threads SET has_user_event = 1 WHERE id = ?1 AND COALESCE(has_user_event, 0) <> 1", [id], ) - .map_err(|error| format!("更新会话用户事件标记失败: {error}"))?; + .map_err(|error| format!("Failed to update session user-event flag: {error}"))?; } } if columns.contains("cwd") { @@ -1875,18 +1875,18 @@ fn apply_sqlite_repairs( "UPDATE threads SET cwd = ?1 WHERE id = ?2 AND COALESCE(cwd, '') <> ?1", (cwd, id), ) - .map_err(|error| format!("更新会话项目路径失败: {error}"))?; + .map_err(|error| format!("Failed to update session project path: {error}"))?; } } transaction .commit() - .map_err(|error| format!("提交历史会话恢复事务失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to commit historical session restoration transaction {}: {error}", path.display()))?; Ok(updated) })(); match update_result { Ok(updated) => total += updated, Err(error) if is_locked_error_message(&error) => warnings.push(format!( - "数据库正在使用,已跳过本次同步 {}: {error}", + "Database is in use; skipped this synchronization {}: {error}", path.display() )), Err(error) => return Err(error), @@ -1911,7 +1911,7 @@ fn create_repair_backup( let source = codex_home.join(name); if source.is_file() { fs::copy(&source, directory.join(name)) - .map_err(|error| format!("备份 {name} 失败: {error}"))?; + .map_err(|error| format!("Failed to back up {name}: {error}"))?; } } let sqlite_home = resolve_sqlite_home(codex_home); @@ -1928,10 +1928,10 @@ fn create_repair_backup( let target = directory.join("databases").join(&relative); if let Some(parent) = target.parent() { fs::create_dir_all(parent) - .map_err(|error| format!("创建数据库备份目录失败: {error}"))?; + .map_err(|error| format!("Failed to create database backup directory: {error}"))?; } fs::copy(&source, &target) - .map_err(|error| format!("备份数据库失败 {}: {error}", source.display()))?; + .map_err(|error| format!("Failed to back up database {}: {error}", source.display()))?; database_files.push(relative.to_string_lossy().replace('\\', "/")); } } @@ -1948,10 +1948,10 @@ fn create_repair_backup( let target = directory.join("rollouts").join(&file_name); if let Some(parent) = target.parent() { fs::create_dir_all(parent) - .map_err(|error| format!("创建 rollout 备份目录失败: {error}"))?; + .map_err(|error| format!("Failed to create rollout backup directory: {error}"))?; } fs::write(&target, &repair.original) - .map_err(|error| format!("备份 rollout 失败 {}: {error}", repair.path.display()))?; + .map_err(|error| format!("Failed to back up rollout {}: {error}", repair.path.display()))?; rollout_files.push(json!({ "originalPath": repair.path.to_string_lossy(), "backupPath": format!("rollouts/{file_name}") @@ -2002,7 +2002,7 @@ fn encrypted_content_warning(repairs: &[RolloutRepair], target_provider: &str) - return None; } Some(format!( - "部分历史会话包含来自 {} 的 encrypted_content;元数据已同步到 {},但继续或压缩这些会话可能失败。", + "Some historical sessions contain encrypted_content from {}; metadata was synchronized to {}, but continuing or compacting these sessions may fail.", providers.join("、"), target_provider )) @@ -2017,7 +2017,7 @@ fn prune_repair_backups(codex_home: &Path) -> Result<(), String> { return Ok(()); } let mut directories = fs::read_dir(&root) - .map_err(|error| format!("读取历史会话恢复备份失败: {error}"))? + .map_err(|error| format!("Failed to read historical session restoration backup: {error}"))? .filter_map(Result::ok) .filter(|entry| entry.path().is_dir()) .collect::>(); @@ -2027,7 +2027,7 @@ fn prune_repair_backups(codex_home: &Path) -> Result<(), String> { let path = entry.path(); if path.starts_with(&root) { fs::remove_dir_all(&path).map_err(|error| { - format!("清理旧的历史会话恢复备份失败 {}: {error}", path.display()) + format!("Failed to clean up old historical session restoration backup {}: {error}", path.display()) })?; } } @@ -2060,7 +2060,7 @@ fn collect_live_thread_ids(codex_home: &Path) -> Result, String> let text = match fs::read_to_string(&path) { Ok(text) => text, Err(error) if is_locked_io_error(&error) => continue, - Err(error) => return Err(format!("读取 rollout 失败 {}: {error}", path.display())), + Err(error) => return Err(format!("Failed to read rollout {}: {error}", path.display())), }; for line in text.lines() { let Ok(record) = serde_json::from_str::(line) else { @@ -2084,7 +2084,7 @@ fn collect_live_thread_ids(codex_home: &Path) -> Result, String> let (database_paths, database_warnings) = discover_database_paths(codex_home, true); if !database_warnings.is_empty() { return Err(format!( - "无法完整核对全部 Codex 数据库,无效会话记录检查已中止:{}", + "Unable to fully verify all Codex databases; invalid session record check aborted: {}", database_warnings.join(";") )); } @@ -2101,13 +2101,13 @@ fn collect_live_thread_ids(codex_home: &Path) -> Result, String> ); let mut statement = connection .prepare(&sql) - .map_err(|error| format!("读取会话引用失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to read session reference {}: {error}", path.display()))?; let rows = statement .query_map([], |row| row.get::<_, String>(0)) - .map_err(|error| format!("读取会话引用失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to read session reference {}: {error}", path.display()))?; for id in rows .collect::>>() - .map_err(|error| format!("读取会话引用失败 {}: {error}", path.display()))? + .map_err(|error| format!("Failed to read session reference {}: {error}", path.display()))? { ids.insert(normalize_thread_id(&id)); ids.insert(id); @@ -2142,9 +2142,9 @@ fn build_session_index_plan( return Ok(None); } let original = - fs::read(path).map_err(|error| format!("读取 session_index.jsonl 失败: {error}"))?; + fs::read(path).map_err(|error| format!("Failed to read session_index.jsonl: {error}"))?; let original_text = String::from_utf8(original.clone()) - .map_err(|error| format!("session_index.jsonl 不是 UTF-8: {error}"))?; + .map_err(|error| format!("session_index.jsonl is not UTF-8: {error}"))?; let mut seen_candidates = HashSet::new(); let candidates = original_text .lines() @@ -2195,16 +2195,16 @@ fn apply_session_index_cleanup_from_home( ) -> Result { let _guard = CODEX_SESSION_WRITE_LOCK .lock() - .map_err(|_| "Codex 会话写入锁已损坏".to_string())?; + .map_err(|_| "Codex session write lock is poisoned".to_string())?; if require_stopped_app { ensure_codex_apps_stopped()?; } let live_ids = collect_live_thread_ids(codex_home)?; let path = codex_home.join("session_index.jsonl"); let plan = build_session_index_plan(&path, &live_ids)? - .ok_or_else(|| "session_index.jsonl 不存在,无法清理".to_string())?; + .ok_or_else(|| "session_index.jsonl does not exist and cannot be cleaned".to_string())?; if plan.snapshot_sha256 != expected_snapshot { - return Err("session_index.jsonl 已在预览后发生变化,请重新预览".to_string()); + return Err("session_index.jsonl changed after preview. Preview it again".to_string()); } let candidate_ids = plan .candidates @@ -2221,7 +2221,7 @@ fn apply_session_index_cleanup_from_home( .iter() .any(|id| !candidate_ids.contains(id.as_str())) { - return Err("确认列表已经过期或包含非候选会话,请重新预览".to_string()); + return Err("The confirmation list has expired or contains non-candidate sessions. Preview again".to_string()); } if selected.is_empty() { return Ok(SessionIndexCleanupResult { @@ -2239,7 +2239,7 @@ fn apply_session_index_cleanup_from_home( let backup_dir = create_operation_directory(codex_home, "session-index-cleanup", "index-cleanup")?; fs::write(backup_dir.join("session_index.jsonl"), &plan.original) - .map_err(|error| format!("备份 session_index.jsonl 失败: {error}"))?; + .map_err(|error| format!("Failed to back up session_index.jsonl: {error}"))?; write_json_atomically( &backup_dir.join("metadata.json"), &json!({ @@ -2252,10 +2252,10 @@ fn apply_session_index_cleanup_from_home( }), )?; let current = fs::read(&plan.path) - .map_err(|error| format!("写入前重新读取 session_index.jsonl 失败: {error}"))?; + .map_err(|error| format!("Failed to reread session_index.jsonl before writing: {error}"))?; if current != plan.original { return Err(format!( - "session_index.jsonl 在写入前再次发生变化;未覆盖新内容,备份位于 {}", + "session_index.jsonl changed again before writing; new content was not overwritten, and the backup is at {}", backup_dir.display() )); } @@ -2265,10 +2265,10 @@ fn apply_session_index_cleanup_from_home( let expected_after_sha256 = sha256_hex(next.as_bytes()); crate::write_bytes_atomically(&plan.path, next.as_bytes())?; let written = fs::read(&plan.path) - .map_err(|error| format!("写入后校验 session_index.jsonl 失败: {error}"))?; + .map_err(|error| format!("Failed to verify session_index.jsonl after writing: {error}"))?; if sha256_hex(&written) != expected_after_sha256 { return Err(format!( - "session_index.jsonl 写入后 SHA-256 校验失败;原文件备份位于 {}", + "session_index.jsonl SHA-256 verification failed after writing; the original file backup is at {}", backup_dir.display() )); } @@ -2313,7 +2313,7 @@ fn ensure_codex_apps_stopped() -> Result<(), String> { return Ok(()); } Err(format!( - "Codex App / ChatGPT 仍在运行(进程:{}),请完全退出后重新预览并清理", + "Codex App / ChatGPT is still running (process: {}). Exit it completely, then preview and clean again", running .iter() .map(u32::to_string) @@ -2338,10 +2338,10 @@ fn windows_app_process_ids(names: &[&str]) -> Result, String> { .args(["/FI", &filter, "/FO", "CSV", "/NH"]) .creation_flags(CREATE_NO_WINDOW) .output() - .map_err(|error| format!("无法检查 {name} 进程状态: {error}"))?; + .map_err(|error| format!("Failed to check {name} process status: {error}"))?; if !output.status.success() { return Err(format!( - "检查 {name} 进程状态失败: {}", + "Error checking {name} process status: {}", String::from_utf8_lossy(&output.stderr).trim() )); } @@ -2379,7 +2379,7 @@ fn unix_app_process_ids(names: &[&str]) -> Result, String> { let output = Command::new("pgrep") .args(["-x", name]) .output() - .map_err(|error| format!("无法检查 {name} 进程状态: {error}"))?; + .map_err(|error| format!("Failed to check {name} process status: {error}"))?; match output.status.code() { Some(0) => ids.extend( String::from_utf8_lossy(&output.stdout) @@ -2389,7 +2389,7 @@ fn unix_app_process_ids(names: &[&str]) -> Result, String> { Some(1) => {} _ => { return Err(format!( - "检查 {name} 进程状态失败: {}", + "Error checking {name} process status: {}", String::from_utf8_lossy(&output.stderr).trim() )); } @@ -3020,7 +3020,7 @@ mod tests { .unwrap(); let error = preview_session_index_cleanup_from_home(&root).unwrap_err(); - assert!(error.contains("无效会话记录检查已中止")); + assert!(error.contains("invalid session record check aborted")); assert!(root.join("session_index.jsonl").is_file()); fs::remove_dir_all(root).unwrap(); } diff --git a/src-tauri/src/configuration_watcher.rs b/src-tauri/src/configuration_watcher.rs index 43074cf..fbf50dd 100644 --- a/src-tauri/src/configuration_watcher.rs +++ b/src-tauri/src/configuration_watcher.rs @@ -51,7 +51,7 @@ fn patch_core_from_gui_config_if_valid(config: &GuiConfigFile) -> Result<(), Str return Ok(()); } let content = fs::read_to_string(&path) - .map_err(|error| format!("读取内核配置失败 {}: {error}", path_to_string(&path)))?; + .map_err(|error| format!("Failed to read kernel configuration {}: {error}", path_to_string(&path)))?; let updated = apply_gui_managed_settings(&content, config)?; write_yaml_if_changed(&path, &updated).map(|_| ()) } @@ -60,7 +60,7 @@ fn tracked_configuration_paths(app: &tauri::AppHandle) -> Result, S let home = app .path() .home_dir() - .map_err(|error| format!("无法获取用户目录: {error}"))?; + .map_err(|error| format!("Failed to get user directory: {error}"))?; let mut paths = vec![ gui_config_path()?, core_install_dir()?.join(CORE_CONFIG_FILE), @@ -142,12 +142,12 @@ fn handle_configuration_file_changes( let parsed = (|| -> Result { let content = fs::read_to_string(tracked_path).map_err(|error| { format!( - "读取 GUI 配置失败 {}: {error}", + "Failed to read GUI configuration {}: {error}", path_to_string(tracked_path) ) })?; let mut config = toml::from_str::(&content) - .map_err(|error| format!("解析 GUI 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse GUI configuration: {error}"))?; config.allow_lan = !is_loopback_host(&config.host); config.proxy_url = super::network_proxy::resolve(&config); validate_gui_config(&config)?; @@ -178,10 +178,10 @@ fn handle_configuration_file_changes( if is_core_path(tracked_path) { let parsed = (|| -> Result { let content = fs::read_to_string(tracked_path).map_err(|error| { - format!("读取内核配置失败 {}: {error}", path_to_string(tracked_path)) + format!("Failed to read kernel configuration {}: {error}", path_to_string(tracked_path)) })?; let document = serde_norway::from_str::(&content) - .map_err(|error| format!("解析内核配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel configuration: {error}"))?; core_config_settings_from_value(&document) })(); let settings = match parsed { @@ -262,7 +262,7 @@ fn ensure_configuration_watch_directories( } watcher .watch(&directory, RecursiveMode::NonRecursive) - .map_err(|error| format!("监控配置目录失败 {}: {error}", path_to_string(&directory)))?; + .map_err(|error| format!("Failed to monitor configuration directory {}: {error}", path_to_string(&directory)))?; watched_directories.push(directory.clone()); newly_watched.push(directory); } @@ -355,7 +355,7 @@ pub(crate) fn start_configuration_file_watcher(app: tauri::AppHandle) -> Result< let _ = sender.try_send(()); } }) - .map_err(|error| format!("创建配置文件监控器失败: {error}"))?; + .map_err(|error| format!("Failed to create configuration file watcher: {error}"))?; let mut watched_directories = Vec::new(); ensure_configuration_watch_directories(&mut watcher, &tracked_paths, &mut watched_directories)?; @@ -379,7 +379,7 @@ pub(crate) fn start_configuration_file_watcher(app: tauri::AppHandle) -> Result< Err(_) => return, }; if let Some(error) = changes.error.take() { - eprintln!("配置文件监控错误: {error}"); + eprintln!("Configuration file watcher error: {error}"); } match ensure_configuration_watch_directories( &mut watcher, @@ -398,7 +398,7 @@ pub(crate) fn start_configuration_file_watcher(app: tauri::AppHandle) -> Result< } } } - Err(error) => eprintln!("配置目录监控更新失败: {error}"), + Err(error) => eprintln!("Failed to update configuration directory watcher: {error}"), } handle_configuration_file_changes(&app, changes.paths, &tracked_paths); } diff --git a/src-tauri/src/core_config/alias_edit.rs b/src-tauri/src/core_config/alias_edit.rs index 4236598..98b7bb7 100644 --- a/src-tauri/src/core_config/alias_edit.rs +++ b/src-tauri/src/core_config/alias_edit.rs @@ -20,18 +20,18 @@ pub(crate) fn validate_model_alias_revision( revision: Option<&str>, ) -> Result<(), String> { if revision != Some(model_alias_config_revision(content)?.as_str()) { - return Err("配置已变化,请关闭编辑器并刷新后重试".to_string()); + return Err("Configuration changed. Close the editor, refresh, and try again".to_string()); } Ok(()) } pub(crate) fn model_alias_config_revision(content: &str) -> Result { let document = serde_norway::from_str::(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let mut value = serde_json::to_value(document).map_err(|error| error.to_string())?; let root = value .as_object_mut() - .ok_or("内核配置顶层必须是 YAML 映射")?; + .ok_or("Kernel configuration root must be a YAML mapping")?; if root .get("oauth-model-alias") .and_then(serde_json::Value::as_object) @@ -57,7 +57,7 @@ pub(crate) fn model_alias_edit_context( .map_err(|error| error.to_string())?; let root = document .as_mapping() - .ok_or("内核配置顶层必须是 YAML 映射")?; + .ok_or("Kernel configuration root must be a YAML mapping")?; Ok(ModelAliasEditContext { effort: find_thinking_alias_effort(root, alias, &source.protocol), fast: find_speed_alias_service_tier(root, alias, &source.protocol).as_deref() @@ -81,7 +81,7 @@ fn editable_model_alias( ("codex-api-key", "Codex API", "codex-api", "codex"), ( "openai-compatibility", - "OpenAI 兼容", + "OpenAI-compatible", "openai-compatible", "openai", ), @@ -93,7 +93,7 @@ fn editable_model_alias( }; let providers = providers .as_sequence() - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; for (provider_index, value) in providers.iter().enumerate() { let Some(value) = value.as_mapping() else { continue; @@ -103,7 +103,7 @@ fn editable_model_alias( }; let models = models .as_sequence() - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; for (model_index, model) in models.iter().enumerate() { let Some((upstream, client, display_name)) = configured_model_identity(model) else { @@ -142,11 +142,11 @@ fn editable_model_alias( if let Some(channels) = yaml_mapping_value(root, "oauth-model-alias") { let channels = channels .as_mapping() - .ok_or("oauth-model-alias 必须是 YAML 映射")?; + .ok_or("oauth-model-alias must be a YAML mapping")?; for (channel_name, models) in channels { let models = models .as_sequence() - .ok_or("oauth-model-alias 的通道配置必须是数组")?; + .ok_or("oauth-model-alias channel configuration must be an array")?; for (model_index, model) in models.iter().enumerate() { let Some((upstream, client, display_name)) = configured_model_identity(model) else { @@ -162,7 +162,7 @@ fn editable_model_alias( let channel = channel_name .as_str() .and_then(oauth_alias_channel) - .ok_or("暂不支持编辑此 OAuth 通道的模型别名")?; + .ok_or("Editing model aliases for this OAuth channel is not supported yet")?; matches.push(EditableModelAlias { source: ResolvedThinkingAliasSource { source: ThinkingAliasSource { @@ -186,7 +186,7 @@ fn editable_model_alias( } } if matching_names != 1 || matches.len() != 1 { - return Err("别名不存在或存在多个同名映射,请刷新并检查配置后重试".to_string()); + return Err("Alias does not exist or has multiple mappings with the same name. Refresh, check the configuration, and try again".to_string()); } Ok(matches.remove(0)) } @@ -197,10 +197,10 @@ pub(crate) fn resolve_model_alias_edit_source( definitions: &[OAuthModelDefinitions], ) -> Result { let document = serde_norway::from_str::(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let root = document .as_mapping() - .ok_or("内核配置顶层必须是 YAML 映射")?; + .ok_or("Kernel configuration root must be a YAML mapping")?; let mut source = editable_model_alias(root, alias)?.source; if let ThinkingAliasSourceLocation::Oauth { channel, .. } = source.location { if let Some(model) = definitions @@ -229,17 +229,17 @@ pub(crate) fn edit_model_alias_in_yaml( fast: bool, ) -> Result { if fast && !alias_source_supports_fast(source) { - return Err("Fast 仅支持 OpenAI 兼容 API、Codex API 或 Codex OAuth 模型源".to_string()); + return Err("Fast supports only OpenAI-compatible API, Codex API, or Codex OAuth model sources".to_string()); } let mut document = yaml_serde_edit::YamlValue::parse(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let mut updated = document.get().clone(); let root = updated .as_mapping_mut() - .ok_or("内核配置顶层必须是 YAML 映射")?; + .ok_or("Kernel configuration root must be a YAML mapping")?; let original = editable_model_alias(root, original_alias)?; if !alias.eq_ignore_ascii_case(original_alias) && configured_model_alias_exists(root, alias) { - return Err(format!("别名模型 {alias} 已存在")); + return Err(format!("Alias model {alias} already exists")); } let mut replacement = match &source.location { ThinkingAliasSourceLocation::ConfigModel { @@ -254,8 +254,8 @@ pub(crate) fn edit_model_alias_in_yaml( .and_then(|provider| yaml_mapping_value(provider, "models")) .and_then(serde_norway::Value::as_sequence) .and_then(|models| models.get(*model_index)) - .ok_or("原模型配置已经变化,请刷新后重试")?; - let (upstream, _, _) = configured_model_identity(model).ok_or("原模型配置格式无效")?; + .ok_or("Source model configuration changed. Refresh and try again")?; + let (upstream, _, _) = configured_model_identity(model).ok_or("Invalid source model configuration format")?; let same_model = matches!(&original.source.location, ThinkingAliasSourceLocation::ConfigModel { section: old_section, provider_index: old_provider, .. } if old_section == section && old_provider == provider_index) @@ -359,21 +359,21 @@ pub(crate) fn edit_model_alias_in_yaml( .and_then(serde_norway::Value::as_mapping_mut) .and_then(|provider| yaml_mapping_value_mut(provider, "models")) .and_then(serde_norway::Value::as_sequence_mut) - .ok_or("模型提供商已经变化,请刷新后重试")?, + .ok_or("Model provider changed. Refresh and try again")?, ThinkingAliasSourceLocation::Oauth { channel, .. } => root .entry(yaml_key("oauth-model-alias")) .or_insert_with(|| serde_norway::Value::Mapping(Default::default())) .as_mapping_mut() - .ok_or("oauth-model-alias 必须是 YAML 映射")? + .ok_or("oauth-model-alias must be a YAML mapping")? .entry(yaml_key(channel)) .or_insert_with(|| serde_norway::Value::Sequence(Vec::new())) .as_sequence_mut() - .ok_or("oauth-model-alias 的通道配置必须是数组")?, + .ok_or("oauth-model-alias channel configuration must be an array")?, }; if same_group { let model = models .get_mut(original.model_index) - .ok_or("原别名已经变化,请刷新后重试")?; + .ok_or("Original alias changed. Refresh and try again")?; *model = serde_norway::Value::Mapping(replacement); } else { models.push(serde_norway::Value::Mapping(replacement)); @@ -436,7 +436,7 @@ fn edit_alias_payload( let payload = root .entry(yaml_key("payload")) .or_insert_with(|| serde_norway::Value::Mapping(Default::default())); - let payload = payload.as_mapping_mut().ok_or("payload 必须是 YAML 映射")?; + let payload = payload.as_mapping_mut().ok_or("payload must be a YAML mapping")?; for section in [ "default", "default-raw", @@ -449,7 +449,7 @@ fn edit_alias_payload( }; let rules = rules .as_sequence_mut() - .ok_or_else(|| format!("payload.{section} 必须是数组"))?; + .ok_or_else(|| format!("payload.{section} must be an array"))?; let mut result = Vec::new(); for rule in rules.iter() { let Some(mapping) = rule.as_mapping() else { @@ -460,7 +460,7 @@ fn edit_alias_payload( result.push(rule.clone()); continue; }; - let models = models.as_sequence().ok_or("payload.models 必须是数组")?; + let models = models.as_sequence().ok_or("payload.models must be an array")?; let (mut target, others): (Vec<_>, Vec<_>) = models.iter().cloned().partition(|model| { thinking_payload_model_matches(model, original_alias, original_protocol) @@ -472,7 +472,7 @@ fn edit_alias_payload( for model in &mut target { let model = model .as_mapping_mut() - .ok_or("payload.models 条目必须是映射")?; + .ok_or("payload.models entries must be mappings")?; model.insert( yaml_key("name"), serde_norway::Value::String(alias.to_string()), @@ -494,7 +494,7 @@ fn edit_alias_payload( if let Some(params) = yaml_mapping_value_mut(&mut edited, "params") { let params = params .as_mapping_mut() - .ok_or("payload.override.params 必须是映射")?; + .ok_or("payload.override.params must be a mapping")?; let has_effort = ALIAS_EFFORT_KEYS .iter() .any(|key| params.contains_key(yaml_key(key))); @@ -507,7 +507,7 @@ fn edit_alias_payload( edit.effort.to_string() } else { thinking_effort_from_params(params, original_protocol, raw).ok_or( - "此规则的思考配置无法转换,请先在配置中调整后再切换模型协议", + "This rule's reasoning configuration cannot be converted. Adjust it in the configuration before switching model protocols", )? }; for key in ALIAS_EFFORT_KEYS { diff --git a/src-tauri/src/core_config/alias_save.rs b/src-tauri/src/core_config/alias_save.rs index e2390e6..864f571 100644 --- a/src-tauri/src/core_config/alias_save.rs +++ b/src-tauri/src/core_config/alias_save.rs @@ -8,7 +8,7 @@ fn alias_config_is_unchanged(current: &str, latest: &str) -> Result Result<(), String> { let parse = |content: &str| { serde_norway::from_str::(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}")) + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}")) }; let current = parse(current)?; let updated = parse(updated)?; @@ -27,7 +27,7 @@ fn validate_alias_api_access_preserved(current: &str, updated: &str) -> Result<( } let providers = value .as_sequence_mut() - .ok_or_else(|| format!("{section} 必须是数组,已拒绝保存别名"))?; + .ok_or_else(|| format!("{section} must be an array; alias was not saved"))?; if MODEL_ALIAS_CONFIG_SECTIONS.contains(§ion) { for provider in providers { if let Some(provider) = provider.as_mapping_mut() { @@ -39,7 +39,7 @@ fn validate_alias_api_access_preserved(current: &str, updated: &str) -> Result<( }; if without_models(¤t)? != without_models(&updated)? { return Err(format!( - "别名更新意外改变了 API 接入配置({section}),已拒绝写入" + "The alias update unexpectedly changed API access configuration ({section}); write rejected" )); } } @@ -70,7 +70,7 @@ pub(crate) async fn commit_management_alias_config_changes( let changes = management_alias_config_changes(current, updated)?; let latest = fetch_management_config_yaml(config).await?; if !alias_config_is_unchanged(current, &latest)? { - return Err("配置已变化,请关闭编辑器并刷新后重试".to_string()); + return Err("Configuration changed. Close the editor, refresh, and try again".to_string()); } let result = async { if let Some(aliases) = changes.oauth_model_aliases.as_ref() { @@ -85,8 +85,8 @@ pub(crate) async fn commit_management_alias_config_changes( match result { Ok(value) => Ok(value), Err(error) => Err(match restore_management_alias_config(config, current, updated).await { - Ok(()) => format!("保存失败,已恢复原配置,可重试:{error}"), - Err(restore_error) => format!("保存失败:{error};自动恢复失败,配置可能已部分写入,请关闭编辑器并刷新检查:{restore_error}"), + Ok(()) => format!("Save failed. Original configuration was restored; you can retry: {error}"), + Err(restore_error) => format!("Save failed: {error}; automatic restoration failed, and the configuration may be partially written. Close the editor, refresh, and inspect it: {restore_error}"), }), } } @@ -103,7 +103,7 @@ async fn restore_management_alias_config( || (from_current.oauth_model_aliases.is_some() && from_updated.oauth_model_aliases.is_some()) { - return Err("检测到其他配置修改,未覆盖这些修改".to_string()); + return Err("Other configuration changes were detected and were not overwritten".to_string()); } let mut errors = Vec::new(); if from_current.update_config_yaml { @@ -120,7 +120,7 @@ async fn restore_management_alias_config( return Err(errors.join(";")); } if !alias_config_is_unchanged(current, &fetch_management_config_yaml(config).await?)? { - return Err("恢复后配置与原配置不一致".to_string()); + return Err("Restored configuration does not match the original configuration".to_string()); } Ok(()) } diff --git a/src-tauri/src/core_config/aliases.rs b/src-tauri/src/core_config/aliases.rs index a3edbb6..b0c14d2 100644 --- a/src-tauri/src/core_config/aliases.rs +++ b/src-tauri/src/core_config/aliases.rs @@ -151,13 +151,13 @@ pub(crate) async fn fetch_active_oauth_alias_channels( .header(reqwest::header::ACCEPT, "application/json") .send() .await - .map_err(|error| format_management_request_error("读取 OAuth 凭据来源失败", &error))?; + .map_err(|error| format_management_request_error("Failed to read OAuth credential sources", &error))?; let payload = read_management_value(response).await?; let files = payload .get("files") .and_then(serde_json::Value::as_array) .or_else(|| payload.as_array()) - .ok_or_else(|| "OAuth 凭据来源响应缺少 files 数组".to_string())?; + .ok_or_else(|| "OAuth credential source response is missing a files array".to_string())?; Ok(files .iter() .filter(|file| { @@ -188,10 +188,10 @@ pub(crate) async fn fetch_active_oauth_alias_channels( pub(crate) fn existing_thinking_alias_model_id(value: &str, label: &str) -> Result { let value = value.trim(); if value.is_empty() { - return Err(format!("{label}不能为空")); + return Err(format!("{label} cannot be empty")); } if value.len() > 240 || value.chars().any(char::is_control) { - return Err(format!("{label}格式无效")); + return Err(format!("Invalid {label} format")); } Ok(value.to_string()) } @@ -202,7 +202,7 @@ pub(crate) fn validate_thinking_alias_model_id(value: &str, label: &str) -> Resu .chars() .any(|character| character.is_whitespace() || character.is_control()) { - return Err(format!("{label}格式无效,不能包含空白字符")); + return Err(format!("Invalid {label} format; whitespace is not allowed")); } Ok(value) } @@ -210,17 +210,17 @@ pub(crate) fn validate_thinking_alias_model_id(value: &str, label: &str) -> Resu pub(crate) fn validate_thinking_alias_effort(value: &str) -> Result { let effort = value.trim().to_ascii_lowercase(); if effort.is_empty() { - return Err("思考强度不能为空".to_string()); + return Err("Reasoning effort cannot be empty".to_string()); } if effort.chars().all(|character| character.is_ascii_digit()) { - return Err("固定思考别名不支持纯数字预算,请输入思考等级名称".to_string()); + return Err("Fixed reasoning aliases do not support numeric-only budgets. Enter a reasoning level name".to_string()); } if effort.len() > 64 || !effort.chars().all(|character| { character.is_ascii_alphanumeric() || matches!(character, '-' | '_' | '.') }) { - return Err("思考强度格式无效,仅支持字母、数字、短横线、下划线和点".to_string()); + return Err("Invalid reasoning effort format. Only letters, numbers, hyphens, underscores, and periods are allowed".to_string()); } Ok(effort) } @@ -236,7 +236,7 @@ pub(crate) async fn fetch_management_config_yaml(config: &GuiConfigFile) -> Resu ) .send() .await - .map_err(|error| format_management_request_error("读取内核 YAML 配置失败", &error))?; + .map_err(|error| format_management_request_error("Failed to read kernel YAML configuration", &error))?; read_management_text(response).await } @@ -252,7 +252,7 @@ pub(crate) async fn put_management_config_yaml( .body(content.to_string()) .send() .await - .map_err(|error| format_management_request_error("保存内核 YAML 配置失败", &error))?; + .map_err(|error| format_management_request_error("Failed to save kernel YAML configuration", &error))?; read_management_value(response).await.map(|_| ()) } @@ -267,7 +267,7 @@ pub(crate) async fn put_management_oauth_model_aliases( .json(aliases) .send() .await - .map_err(|error| format_management_request_error("保存 OAuth 模型别名失败", &error))?; + .map_err(|error| format_management_request_error("Failed to save OAuth model alias", &error))?; read_management_value(response).await.map(|_| ()) } @@ -283,10 +283,10 @@ pub(crate) fn management_alias_config_changes( ) -> Result { let split = |content: &str| { let mut document = serde_norway::from_str::(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let root = document .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let oauth_aliases = root .remove(yaml_key("oauth-model-alias")) .unwrap_or_else(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())); @@ -299,7 +299,7 @@ pub(crate) fn management_alias_config_changes( } else { Some( serde_json::to_value(updated_oauth) - .map_err(|error| format!("序列化 OAuth 模型别名失败: {error}"))?, + .map_err(|error| format!("Failed to serialize OAuth model alias: {error}"))?, ) }; Ok(ManagementAliasConfigChanges { @@ -323,7 +323,7 @@ pub(crate) fn ensure_claude_desktop_model_aliases_with_codex_oauth_in_yaml( codex_oauth_models: &[CodexModelDefinition], ) -> Result { let codex_channel = oauth_alias_channel("codex") - .ok_or_else(|| "缺少 Codex OAuth 别名 channel 定义".to_string())?; + .ok_or_else(|| "Codex OAuth alias channel definition is missing".to_string())?; let definitions = [OAuthModelDefinitions { channel: codex_channel, models: codex_oauth_models.to_vec(), @@ -363,11 +363,11 @@ pub(crate) fn ensure_claude_desktop_model_aliases_with_oauth_definitions_and_rou routes: [&str; 3], ) -> Result { let mut document = yaml_serde_edit::YamlValue::parse(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let mut updated = document.get().clone(); let root = updated .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let sources = root.clone(); if let Some(entries) = &mappings.desktop_models { @@ -393,7 +393,7 @@ pub(crate) fn ensure_claude_desktop_model_aliases_with_oauth_definitions_and_rou || (occupied && !managed_aliases.iter() .any(|alias| alias.eq_ignore_ascii_case(&entry.alias))) { - return Err(format!("别名 {} 已被其他模型使用,请更换别名", entry.alias)); + return Err(format!("Alias {} is already used by another model. Choose a different alias", entry.alias)); } } for alias in managed_aliases { @@ -467,21 +467,21 @@ pub(crate) fn validate_claude_desktop_entries( entries: &[ClaudeDesktopModelMapping], ) -> Result<(), String> { if entries.is_empty() { - return Err("请至少添加一个 Claude Desktop 模型".into()); + return Err("Add at least one Claude Desktop model".into()); } let mut ids = HashSet::new(); for entry in entries { validate_agent_model(entry.source_or_alias())?; if entry.source_or_alias().chars().any(char::is_whitespace) { - return Err("原模型 ID 不能包含空白字符".into()); + return Err("Source model ID cannot contain whitespace".into()); } let alias = entry.alias.trim(); if !alias.is_empty() && !alias.eq_ignore_ascii_case(entry.source_or_alias()) && !valid_claude_desktop_alias(alias) { - return Err(format!("Claude Desktop 别名 {alias} 必须以 claude- 开头,只能包含小写字母、数字、连字符和小数点,且不能包含 gpt、grok、gemini、deepseek 等其他模型系列名称")); + return Err(format!("Claude Desktop alias {alias} must start with claude-, may contain only lowercase letters, numbers, hyphens, and periods, and cannot contain other model family names such as gpt, grok, gemini, or deepseek")); } let id = entry.model_id(); if !ids.insert(id.to_ascii_lowercase()) { - return Err(format!("Claude Desktop 模型 ID 重复: {id}")); + return Err(format!("Duplicate Claude Desktop model ID: {id}")); } } Ok(()) @@ -556,11 +556,11 @@ pub(crate) fn managed_claude_desktop_aliases(root: &serde_norway::Mapping) -> Ve #[cfg(test)] pub(crate) fn remove_managed_claude_model_aliases_in_yaml(content: &str) -> Result { let mut document = yaml_serde_edit::YamlValue::parse(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let mut updated = document.get().clone(); let root = updated .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let mut changed = false; for alias in [ CLAUDE_DESKTOP_OPUS_MODEL_ID, @@ -589,7 +589,7 @@ pub(crate) fn remove_managed_claude_model_alias( }; let providers = providers .as_sequence_mut() - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; for provider in providers { let Some(provider) = provider.as_mapping_mut() else { continue; @@ -599,7 +599,7 @@ pub(crate) fn remove_managed_claude_model_alias( }; let models = models .as_sequence_mut() - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; let before = models.len(); models.retain(|model| !is_managed_claude_model_alias(model, alias)); changed |= models.len() != before; @@ -620,7 +620,7 @@ pub(crate) fn remove_existing_claude_model_alias( }; let providers = providers .as_sequence_mut() - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; for provider in providers { let Some(provider) = provider.as_mapping_mut() else { continue; @@ -630,7 +630,7 @@ pub(crate) fn remove_existing_claude_model_alias( }; let models = models .as_sequence_mut() - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; let before = models.len(); models.retain(|model| !occupies_claude_client_alias(model, alias)); changed |= models.len() != before; @@ -650,14 +650,14 @@ pub(crate) fn remove_oauth_claude_model_alias( }; let oauth_aliases = oauth_aliases .as_mapping_mut() - .ok_or_else(|| "oauth-model-alias 必须是 YAML 映射".to_string())?; + .ok_or_else(|| "oauth-model-alias must be a YAML mapping".to_string())?; let mut changed = false; let mut empty_channels = Vec::new(); for (channel, entries) in oauth_aliases.iter_mut() { let channel_name = channel.as_str().unwrap_or("unknown"); let entries = entries .as_sequence_mut() - .ok_or_else(|| format!("oauth-model-alias.{channel_name} 必须是数组"))?; + .ok_or_else(|| format!("oauth-model-alias.{channel_name} must be an array"))?; let before = entries.len(); entries.retain(|entry| { if managed_only { @@ -850,7 +850,7 @@ pub(crate) fn ensure_claude_desktop_model_alias( return Ok(()); } Err(format!( - "无法确定模型 {source_model} 的 CPA 配置来源,无法创建 Claude Desktop 别名 {alias}" + "Unable to determine the CPA configuration source for model {source_model}; cannot create Claude Desktop alias {alias}" )) } @@ -978,7 +978,7 @@ fn resolve_claude_desktop_alias_source( }; let providers = providers .as_sequence() - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; for (provider_index, provider) in providers.iter().enumerate() { let Some(provider) = provider.as_mapping() else { continue; @@ -991,7 +991,7 @@ fn resolve_claude_desktop_alias_source( }; let models = models .as_sequence() - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; if let Some(model) = models.iter().find_map(|model| { let (_, client_model, _) = configured_model_identity(model)?; if !configured_provider_model_is_enabled(provider, &client_model) { @@ -1043,7 +1043,7 @@ fn append_claude_desktop_model_alias( .and_then(serde_norway::Value::as_mapping_mut) .and_then(|provider| yaml_mapping_value_mut(provider, "models")) .and_then(serde_norway::Value::as_sequence_mut) - .ok_or("模型来源配置已变化,请刷新后重试")?; + .ok_or("Model source configuration changed. Refresh and try again")?; (models, model) } ClaudeDesktopAliasSource::OAuth { channel, mut model } => { @@ -1051,12 +1051,12 @@ fn append_claude_desktop_model_alias( .entry(yaml_key("oauth-model-alias")) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or("oauth-model-alias 必须是 YAML 映射")?; + .ok_or("oauth-model-alias must be a YAML mapping")?; let models = aliases .entry(yaml_key(&channel)) .or_insert_with(|| serde_norway::Value::Sequence(Vec::new())) .as_sequence_mut() - .ok_or_else(|| format!("oauth-model-alias.{channel} 必须是数组"))?; + .ok_or_else(|| format!("oauth-model-alias.{channel} must be an array"))?; model.insert(yaml_key("fork"), serde_norway::Value::Bool(true)); if oauth_alias_channel(&channel).is_some_and(|channel| channel.force_mapping) { model.insert(yaml_key("force-mapping"), serde_norway::Value::Bool(true)); @@ -1069,7 +1069,7 @@ fn append_claude_desktop_model_alias( serde_norway::Value::String(alias.to_string()), ); let display_name = managed_claude_alias_display_name(alias) - .ok_or_else(|| format!("不支持的 Claude 托管别名: {alias}"))?; + .ok_or_else(|| format!("Unsupported Claude managed alias: {alias}"))?; model.insert( yaml_key("display-name"), serde_norway::Value::String(display_name.to_string()), @@ -1089,14 +1089,14 @@ pub(crate) fn append_managed_oauth_model_alias( .entry(yaml_key("oauth-model-alias")) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| "oauth-model-alias 必须是 YAML 映射".to_string())?; + .ok_or_else(|| "oauth-model-alias must be a YAML mapping".to_string())?; let channel_aliases = oauth_aliases .entry(yaml_key(channel)) .or_insert_with(|| serde_norway::Value::Sequence(Vec::new())) .as_sequence_mut() - .ok_or_else(|| format!("oauth-model-alias.{channel} 必须是数组"))?; + .ok_or_else(|| format!("oauth-model-alias.{channel} must be an array"))?; let display_name = managed_claude_alias_display_name(alias) - .ok_or_else(|| format!("不支持的 Claude 托管别名: {alias}"))?; + .ok_or_else(|| format!("Unsupported Claude managed alias: {alias}"))?; let mut alias_mapping = serde_norway::Mapping::new(); alias_mapping.insert( yaml_key("name"), @@ -1133,7 +1133,7 @@ pub(crate) async fn fetch_oauth_channel_model_definitions( .send() .await .map_err(|error| { - format_management_request_error(&format!("读取 {channel} OAuth 模型定义失败"), &error) + format_management_request_error(&format!("Failed to read {channel} OAuth model definition"), &error) })?; let payload = read_management_value(response).await?; parse_codex_model_definitions(&payload) @@ -1229,10 +1229,10 @@ pub(crate) fn resolved_oauth_alias_sources( capability: AliasSourceCapability, ) -> Result, String> { let document = serde_norway::from_str::(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let mut sources = Vec::new(); collect_config_thinking_alias_sources( root, @@ -1246,7 +1246,7 @@ pub(crate) fn resolved_oauth_alias_sources( collect_config_thinking_alias_sources( root, "openai-compatibility", - "OpenAI 兼容", + "OpenAI-compatible", "openai-compatible", "openai", available_models, @@ -1356,7 +1356,7 @@ pub(crate) fn collect_config_thinking_alias_sources( }; let providers = providers .as_sequence() - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; for (provider_index, provider) in providers.iter().enumerate() { let Some(provider) = provider.as_mapping() else { continue; @@ -1371,7 +1371,7 @@ pub(crate) fn collect_config_thinking_alias_sources( thinking_alias_provider_name(provider, fallback_provider, provider_index); let provider_revision = sha256_bytes( serde_norway::to_string(provider) - .map_err(|error| format!("读取模型源配置失败: {error}"))? + .map_err(|error| format!("Failed to read model source configuration: {error}"))? .as_bytes(), ); let Some(models) = yaml_mapping_value(provider, "models") else { @@ -1379,7 +1379,7 @@ pub(crate) fn collect_config_thinking_alias_sources( }; let models = models .as_sequence() - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; for (model_index, model) in models.iter().enumerate() { let Some((upstream_model, client_model, display_name)) = configured_model_identity(model) @@ -1495,7 +1495,7 @@ pub(crate) fn configured_model_identity( pub(crate) fn thinking_aliases_from_yaml(content: &str) -> Result, String> { let document = serde_norway::from_str::(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; thinking_aliases_from_value(&document) } @@ -1504,17 +1504,17 @@ pub(crate) fn thinking_aliases_from_value( ) -> Result, String> { let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let mut entries = Vec::new(); if let Some(oauth_aliases) = yaml_mapping_value(root, "oauth-model-alias") { let oauth_aliases = oauth_aliases .as_mapping() - .ok_or_else(|| "oauth-model-alias 必须是 YAML 映射".to_string())?; + .ok_or_else(|| "oauth-model-alias must be a YAML mapping".to_string())?; for (channel, channel_aliases) in oauth_aliases { let channel = channel.as_str().unwrap_or("unknown"); let channel_aliases = channel_aliases .as_sequence() - .ok_or_else(|| format!("oauth-model-alias.{channel} 必须是数组"))?; + .ok_or_else(|| format!("oauth-model-alias.{channel} must be an array"))?; let (provider, kind, protocol) = oauth_alias_channel_details(channel); for entry in channel_aliases { let Some(mapping) = entry.as_mapping() else { @@ -1556,7 +1556,7 @@ pub(crate) fn thinking_aliases_from_value( collect_config_thinking_alias_entries( root, "openai-compatibility", - "OpenAI 兼容", + "OpenAI-compatible", "openai-compatible", "openai", &mut entries, @@ -1592,7 +1592,7 @@ pub(crate) fn thinking_aliases_from_value( pub(crate) fn speed_aliases_from_yaml(content: &str) -> Result, String> { let document = serde_norway::from_str::(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; speed_aliases_from_value(&document) } @@ -1601,17 +1601,17 @@ pub(crate) fn speed_aliases_from_value( ) -> Result, String> { let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let mut entries = Vec::new(); if let Some(oauth_aliases) = yaml_mapping_value(root, "oauth-model-alias") { let oauth_aliases = oauth_aliases .as_mapping() - .ok_or_else(|| "oauth-model-alias 必须是 YAML 映射".to_string())?; + .ok_or_else(|| "oauth-model-alias must be a YAML mapping".to_string())?; for (channel, channel_aliases) in oauth_aliases { let channel = channel.as_str().unwrap_or("unknown"); let channel_aliases = channel_aliases .as_sequence() - .ok_or_else(|| format!("oauth-model-alias.{channel} 必须是数组"))?; + .ok_or_else(|| format!("oauth-model-alias.{channel} must be an array"))?; let (provider, kind, protocol) = oauth_alias_channel_details(channel); for entry in channel_aliases { let Some(mapping) = entry.as_mapping() else { @@ -1657,7 +1657,7 @@ pub(crate) fn speed_aliases_from_value( collect_config_speed_alias_entries( root, "openai-compatibility", - "OpenAI 兼容", + "OpenAI-compatible", "openai-compatible", "openai", &mut entries, @@ -1688,7 +1688,7 @@ pub(crate) fn collect_config_thinking_alias_entries( }; let providers = providers .as_sequence() - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; for (provider_index, provider) in providers.iter().enumerate() { let Some(provider) = provider.as_mapping() else { continue; @@ -1700,7 +1700,7 @@ pub(crate) fn collect_config_thinking_alias_entries( }; let models = models .as_sequence() - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; for model in models { let Some((source_model, alias, _)) = configured_model_identity(model) else { continue; @@ -1738,7 +1738,7 @@ pub(crate) fn collect_config_speed_alias_entries( }; let providers = providers .as_sequence() - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; for (provider_index, provider) in providers.iter().enumerate() { let Some(provider) = provider.as_mapping() else { continue; @@ -1750,7 +1750,7 @@ pub(crate) fn collect_config_speed_alias_entries( }; let models = models .as_sequence() - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; for model in models { let Some((source_model, alias, _)) = configured_model_identity(model) else { continue; @@ -1960,7 +1960,7 @@ pub(crate) fn insert_thinking_effort_params( effort, ), protocol => { - return Err(format!("暂不支持为 {protocol} 来源强制覆写思考强度")); + return Err(format!("Forcing a reasoning effort override is not yet supported for {protocol} sources")); } }, } @@ -1975,17 +1975,17 @@ pub(crate) fn add_model_alias_to_yaml( fast: bool, ) -> Result { if fast && !alias_source_supports_fast(source) { - return Err("Fast 仅支持 OpenAI 兼容 API、Codex API 或 Codex OAuth 模型源".to_string()); + return Err("Fast supports only OpenAI-compatible API, Codex API, or Codex OAuth model sources".to_string()); } let mut document = yaml_serde_edit::YamlValue::parse(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let mut updated = document.get().clone(); let root = updated .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; if configured_model_alias_exists(root, alias) { - return Err(format!("别名模型 {alias} 已存在")); + return Err(format!("Alias model {alias} already exists")); } match &source.location { @@ -2040,12 +2040,12 @@ pub(crate) fn append_alias_payload_override( .entry(yaml_key("payload")) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| "payload 必须是 YAML 映射".to_string())?; + .ok_or_else(|| "payload must be a YAML mapping".to_string())?; let override_rules = payload .entry(yaml_key("override")) .or_insert_with(|| serde_norway::Value::Sequence(Vec::new())) .as_sequence_mut() - .ok_or_else(|| "payload.override 必须是数组".to_string())?; + .ok_or_else(|| "payload.override must be an array".to_string())?; let mut model_mapping = serde_norway::Mapping::new(); model_mapping.insert( @@ -2075,17 +2075,17 @@ pub(crate) fn add_speed_alias_to_yaml( alias: &str, ) -> Result { if !alias_source_supports_fast(source) { - return Err("Fast 仅支持 OpenAI 兼容 API、Codex API 或 Codex OAuth 模型源".to_string()); + return Err("Fast supports only OpenAI-compatible API, Codex API, or Codex OAuth model sources".to_string()); } let mut document = yaml_serde_edit::YamlValue::parse(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let mut updated = document.get().clone(); let root = updated .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; if configured_model_alias_exists(root, alias) { - return Err(format!("别名模型 {alias} 已存在")); + return Err(format!("Alias model {alias} already exists")); } match &source.location { @@ -2133,12 +2133,12 @@ pub(crate) fn append_oauth_model_alias( .entry(yaml_key("oauth-model-alias")) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| "oauth-model-alias 必须是 YAML 映射".to_string())?; + .ok_or_else(|| "oauth-model-alias must be a YAML mapping".to_string())?; let channel_aliases = oauth_aliases .entry(yaml_key(channel)) .or_insert_with(|| serde_norway::Value::Sequence(Vec::new())) .as_sequence_mut() - .ok_or_else(|| format!("oauth-model-alias.{channel} 必须是数组"))?; + .ok_or_else(|| format!("oauth-model-alias.{channel} must be an array"))?; let mut alias_mapping = serde_norway::Mapping::new(); alias_mapping.insert( yaml_key("name"), @@ -2167,22 +2167,22 @@ pub(crate) fn append_config_thinking_alias( ) -> Result<(), String> { let providers = yaml_mapping_value_mut(root, section) .and_then(serde_norway::Value::as_sequence_mut) - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; let provider = providers .get_mut(provider_index) .and_then(serde_norway::Value::as_mapping_mut) - .ok_or_else(|| "模型提供商已经变化,请刷新后重试".to_string())?; + .ok_or_else(|| "Model provider changed. Refresh and try again".to_string())?; let models = yaml_mapping_value_mut(provider, "models") .and_then(serde_norway::Value::as_sequence_mut) - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; let source = models .get(model_index) .cloned() - .ok_or_else(|| "原模型已经变化,请刷新后重试".to_string())?; + .ok_or_else(|| "Source model changed. Refresh and try again".to_string())?; let (_, current_model, _) = - configured_model_identity(&source).ok_or_else(|| "原模型配置格式无效".to_string())?; + configured_model_identity(&source).ok_or_else(|| "Invalid source model configuration format".to_string())?; if !current_model.eq_ignore_ascii_case(expected_model) { - return Err("原模型已经变化,请刷新后重试".to_string()); + return Err("Source model changed. Refresh and try again".to_string()); } let mut alias_model = source.as_mapping().cloned().unwrap_or_else(|| { let mut mapping = serde_norway::Mapping::new(); @@ -2223,22 +2223,22 @@ pub(crate) fn append_config_speed_alias( ) -> Result<(), String> { let providers = yaml_mapping_value_mut(root, section) .and_then(serde_norway::Value::as_sequence_mut) - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; let provider = providers .get_mut(provider_index) .and_then(serde_norway::Value::as_mapping_mut) - .ok_or_else(|| "模型提供商已经变化,请刷新后重试".to_string())?; + .ok_or_else(|| "Model provider changed. Refresh and try again".to_string())?; let models = yaml_mapping_value_mut(provider, "models") .and_then(serde_norway::Value::as_sequence_mut) - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; let source = models .get(model_index) .cloned() - .ok_or_else(|| "原模型已经变化,请刷新后重试".to_string())?; + .ok_or_else(|| "Source model changed. Refresh and try again".to_string())?; let (_, current_model, _) = - configured_model_identity(&source).ok_or_else(|| "原模型配置格式无效".to_string())?; + configured_model_identity(&source).ok_or_else(|| "Invalid source model configuration format".to_string())?; if !current_model.eq_ignore_ascii_case(expected_model) { - return Err("原模型已经变化,请刷新后重试".to_string()); + return Err("Source model changed. Refresh and try again".to_string()); } let mut alias_model = source.as_mapping().cloned().unwrap_or_else(|| { let mut mapping = serde_norway::Mapping::new(); @@ -2281,11 +2281,11 @@ pub(crate) fn remove_thinking_alias_from_yaml_for_channel( oauth_channel: Option<&str>, ) -> Result { let mut document = yaml_serde_edit::YamlValue::parse(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let mut updated = document.get().clone(); let root = updated .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let mut removed = remove_oauth_model_alias(root, alias, oauth_channel)?; if oauth_channel.is_none() { removed |= remove_config_model_alias(root, "codex-api-key", alias)?; @@ -2294,7 +2294,7 @@ pub(crate) fn remove_thinking_alias_from_yaml_for_channel( removed |= remove_config_model_alias(root, "gemini-api-key", alias)?; } if !removed { - return Err(format!("别名模型 {alias} 不存在,请刷新后重试")); + return Err(format!("Alias model {alias} does not exist. Refresh and try again")); } let scope = AliasPayloadScope::after_removal(root, alias, oauth_channel); remove_alias_payload_options(root, alias, &scope)?; @@ -2312,18 +2312,18 @@ pub(crate) fn remove_speed_alias_from_yaml_for_channel( oauth_channel: Option<&str>, ) -> Result { let mut document = yaml_serde_edit::YamlValue::parse(content) - .map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel YAML configuration: {error}"))?; let mut updated = document.get().clone(); let root = updated .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let mut removed = remove_oauth_model_alias(root, alias, oauth_channel)?; if oauth_channel.is_none() { removed |= remove_config_speed_alias(root, "codex-api-key", "codex", alias)?; removed |= remove_config_speed_alias(root, "openai-compatibility", "openai", alias)?; } if !removed { - return Err(format!("别名模型 {alias} 不存在,请刷新后重试")); + return Err(format!("Alias model {alias} does not exist. Refresh and try again")); } let scope = AliasPayloadScope::after_removal(root, alias, oauth_channel); remove_alias_payload_options(root, alias, &scope)?; @@ -2340,7 +2340,7 @@ pub(crate) fn remove_oauth_model_alias( }; let oauth_aliases = oauth_aliases .as_mapping_mut() - .ok_or_else(|| "oauth-model-alias 必须是 YAML 映射".to_string())?; + .ok_or_else(|| "oauth-model-alias must be a YAML mapping".to_string())?; let mut removed = false; let mut empty_channels = Vec::new(); for (channel, entries) in oauth_aliases.iter_mut() { @@ -2350,7 +2350,7 @@ pub(crate) fn remove_oauth_model_alias( } let entries = entries .as_sequence_mut() - .ok_or_else(|| format!("oauth-model-alias.{channel_name} 必须是数组"))?; + .ok_or_else(|| format!("oauth-model-alias.{channel_name} must be an array"))?; entries.retain(|entry| { let matches = entry .as_mapping() @@ -2414,7 +2414,7 @@ pub(crate) fn remove_config_model_alias( }; let providers = providers .as_sequence_mut() - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; let mut removed = false; for provider in providers { let Some(provider) = provider.as_mapping_mut() else { @@ -2425,7 +2425,7 @@ pub(crate) fn remove_config_model_alias( }; let models = models .as_sequence_mut() - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; models.retain(|model| { let matches = configured_model_identity(model) .map(|(source, model_alias, _)| { @@ -2453,7 +2453,7 @@ pub(crate) fn remove_config_speed_alias( }; let providers = providers .as_sequence_mut() - .ok_or_else(|| format!("{section} 必须是数组"))?; + .ok_or_else(|| format!("{section} must be an array"))?; let mut removed = false; for provider in providers { let Some(provider) = provider.as_mapping_mut() else { @@ -2464,7 +2464,7 @@ pub(crate) fn remove_config_speed_alias( }; let models = models .as_sequence_mut() - .ok_or_else(|| format!("{section}.models 必须是数组"))?; + .ok_or_else(|| format!("{section}.models must be an array"))?; models.retain(|model| { let matches = configured_model_identity(model) .map(|(source, model_alias, _)| { @@ -2572,14 +2572,14 @@ fn remove_alias_payload_options( let Some(payload) = yaml_mapping_value_mut(root, "payload") else { return Ok(()); }; - let payload = payload.as_mapping_mut().ok_or("payload 必须是 YAML 映射")?; + let payload = payload.as_mapping_mut().ok_or("payload must be a YAML mapping")?; for section in ["override", "override-raw"] { let Some(rules) = yaml_mapping_value_mut(payload, section) else { continue; }; let rules = rules .as_sequence_mut() - .ok_or_else(|| format!("payload.{section} 必须是数组"))?; + .ok_or_else(|| format!("payload.{section} must be an array"))?; let mut next = Vec::with_capacity(rules.len()); for rule in rules.iter() { let Some(mapping) = rule.as_mapping() else { @@ -2606,7 +2606,7 @@ fn remove_alias_payload_options( }; let models = models .as_sequence() - .ok_or_else(|| format!("payload.{section}.models 必须是数组"))?; + .ok_or_else(|| format!("payload.{section}.models must be an array"))?; let (target, others): (Vec<_>, Vec<_>) = models .iter() .cloned() @@ -2649,12 +2649,12 @@ pub(crate) fn render_updated_core_yaml( let rendered = expand_top_level_flow_style_collections(&document.get_string(), document.get())?; let rendered = indent_indentationless_yaml_sequences(&rendered); let validated = serde_norway::from_str::(&rendered) - .map_err(|error| format!("验证更新后的内核配置失败: {error}"))?; + .map_err(|error| format!("Failed to validate updated kernel configuration: {error}"))?; if &validated != document.get() { let path = first_yaml_mismatch_path(document.get(), &validated, &mut Vec::new()) .unwrap_or_else(|| "".to_string()); return Err(format!( - "更新后的内核配置与预期值不一致(路径: {path}),已拒绝写入" + "Updated kernel configuration does not match the expected value (path: {path}); write rejected" )); } Ok(rendered) @@ -2666,7 +2666,7 @@ pub(crate) fn expand_top_level_flow_style_collections( ) -> Result { let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let mut rendered = content.to_string(); for (key, value) in root { let Some(key) = key.as_str() else { @@ -2683,7 +2683,7 @@ pub(crate) fn expand_top_level_flow_style_collections( let mut wrapper = serde_norway::Mapping::new(); wrapper.insert(yaml_key(key), value.clone()); let block = serde_norway::to_string(&serde_norway::Value::Mapping(wrapper)) - .map_err(|error| format!("格式化内核 YAML 配置失败: {error}"))?; + .map_err(|error| format!("Failed to format kernel YAML configuration: {error}"))?; rendered = replace_top_level_yaml_block(&rendered, key, &block); } Ok(rendered) @@ -2783,13 +2783,13 @@ pub(crate) fn truncate_for_error(value: &str) -> String { pub(crate) fn open_external_url_inner(app: &tauri::AppHandle, url: &str) -> Result<(), String> { let url = url.trim(); if url.is_empty() { - return Err("链接为空".to_string()); + return Err("Link is empty".to_string()); } if !(url.starts_with("http://") || url.starts_with("https://")) { - return Err("只允许打开 http/https 链接".to_string()); + return Err("Only http/https links can be opened".to_string()); } app.opener() .open_url(url, None::<&str>) - .map_err(|err| format!("打开浏览器失败: {err}")) + .map_err(|err| format!("Failed to open browser: {err}")) } diff --git a/src-tauri/src/core_config/commands.rs b/src-tauri/src/core_config/commands.rs index 9e14a3d..ee64170 100644 --- a/src-tauri/src/core_config/commands.rs +++ b/src-tauri/src/core_config/commands.rs @@ -63,13 +63,13 @@ pub(crate) fn save_gui_settings( settings: GuiNetworkSettings, ) -> Result { if settings.port == 0 { - return Err("端口必须在 1 到 65535 之间".to_string()); + return Err("Port must be between 1 and 65535".to_string()); } let _refresh_guard = cache .refresh_lock .lock() - .map_err(|_| "智能体配置状态刷新锁已损坏".to_string())?; + .map_err(|_| "Agent configuration status refresh lock is poisoned".to_string())?; let previous = gui_config_state.snapshot()?; let mut next = previous.clone(); next.port = settings.port; @@ -87,7 +87,7 @@ pub(crate) fn save_gui_settings( let rollback_error = patch_core_network_settings(&previous).err(); return Err(match rollback_error { Some(rollback_error) => { - format!("{error};回滚内核网络配置也失败: {rollback_error}") + format!("{error}; failed to roll back kernel network configuration: {rollback_error}") } None => error, }); @@ -96,7 +96,7 @@ pub(crate) fn save_gui_settings( if config.port != previous.port { if let Err(error) = cache.clear() { - eprintln!("清空智能体配置状态缓存失败: {error}"); + eprintln!("Failed to clear agent configuration status cache: {error}"); } } @@ -110,15 +110,15 @@ pub(crate) fn save_network_routing_settings( settings: GuiNetworkRoutingSettings, ) -> Result { if settings.port == 0 { - return Err("端口必须在 1 到 65535 之间".to_string()); + return Err("Port must be between 1 and 65535".to_string()); } let routing_session_affinity_ttl = - normalize_optional_config_string(settings.routing_session_affinity_ttl, "会话粘性 TTL")?; + normalize_optional_config_string(settings.routing_session_affinity_ttl, "Session affinity TTL")?; let _refresh_guard = cache .refresh_lock .lock() - .map_err(|_| "智能体配置状态刷新锁已损坏".to_string())?; + .map_err(|_| "Agent configuration status refresh lock is poisoned".to_string())?; let previous = gui_config_state.snapshot()?; let mut next = previous.clone(); next.port = settings.port; @@ -149,7 +149,7 @@ pub(crate) fn save_network_routing_settings( if config.port != previous.port { if let Err(error) = cache.clear() { - eprintln!("清空智能体配置状态缓存失败: {error}"); + eprintln!("Failed to clear agent configuration status cache: {error}"); } } @@ -273,7 +273,7 @@ pub(crate) fn save_core_logging_settings( settings: CoreLoggingSettingsInput, ) -> Result { if !(1..=3600).contains(&settings.redis_usage_queue_retention_seconds) { - return Err("Redis 用量队列保留时间必须在 1 到 3600 秒之间".to_string()); + return Err("Redis usage queue retention must be between 1 and 3600 seconds".to_string()); } let previous = current_core_config_settings(gui_config_state.inner())?; @@ -313,7 +313,7 @@ pub(crate) fn add_core_api_key( .iter() .any(|existing| existing == &api_key) { - return Err("该鉴权密钥已经存在".to_string()); + return Err("This authentication key already exists".to_string()); } settings.api_keys.push(api_key); patch_core_api_keys(&settings.api_keys)?; @@ -333,13 +333,13 @@ pub(crate) fn replace_core_api_key_value( let index = api_keys .iter() .position(|existing| existing == original_api_key) - .ok_or_else(|| "要编辑的鉴权密钥不存在,请刷新后重试".to_string())?; + .ok_or_else(|| "The authentication key to edit does not exist. Refresh and try again".to_string())?; if replacement_api_key != original_api_key && api_keys .iter() .any(|existing| existing == &replacement_api_key) { - return Err("该鉴权密钥已经存在".to_string()); + return Err("This authentication key already exists".to_string()); } api_keys[index] = replacement_api_key; Ok(()) @@ -352,7 +352,7 @@ pub(crate) fn remove_core_api_key_value( let index = api_keys .iter() .position(|existing| existing == api_key) - .ok_or_else(|| "要删除的鉴权密钥不存在,请刷新后重试".to_string())?; + .ok_or_else(|| "The authentication key to delete does not exist. Refresh and try again".to_string())?; api_keys.remove(index); Ok(()) } @@ -368,7 +368,7 @@ pub(crate) fn update_core_api_key( let api_key = api_key.trim().to_string(); let remark = remark.trim().to_string(); if original_api_key.is_empty() { - return Err("要编辑的鉴权密钥不能为空".to_string()); + return Err("The authentication key to edit cannot be empty".to_string()); } validate_core_api_key(&api_key)?; validate_api_key_remark(&remark)?; @@ -393,7 +393,7 @@ pub(crate) fn delete_core_api_key( ) -> Result { let api_key = api_key.trim(); if api_key.is_empty() { - return Err("要删除的鉴权密钥不能为空".to_string()); + return Err("The authentication key to delete cannot be empty".to_string()); } let mut settings = current_core_config_settings(gui_config_state.inner())?; remove_core_api_key_value(&mut settings.api_keys, api_key)?; @@ -515,7 +515,7 @@ pub(crate) fn set_core_session_affinity_ttl( gui_config_state: tauri::State<'_, GuiConfigState>, ttl: String, ) -> Result { - let ttl = normalize_optional_config_string(ttl, "会话粘性 TTL")?; + let ttl = normalize_optional_config_string(ttl, "Session affinity TTL")?; let mut settings = current_core_config_settings(gui_config_state.inner())?; let previous_ttl = settings.routing_session_affinity_ttl.clone(); settings.routing_session_affinity_ttl = ttl; diff --git a/src-tauri/src/core_config/settings.rs b/src-tauri/src/core_config/settings.rs index 5d5808b..75f4a09 100644 --- a/src-tauri/src/core_config/settings.rs +++ b/src-tauri/src/core_config/settings.rs @@ -2,23 +2,23 @@ use super::*; pub(crate) fn validate_core_api_key(api_key: &str) -> Result<(), String> { if api_key.is_empty() { - return Err("鉴权密钥不能为空".to_string()); + return Err("Authentication key cannot be empty".to_string()); } if !api_key.bytes().all(|byte| (0x21..=0x7e).contains(&byte)) { - return Err("鉴权密钥只能包含 ASCII 可见字符,且不能包含空格".to_string()); + return Err("Authentication key may contain only visible ASCII characters and cannot contain spaces".to_string()); } if is_example_core_api_key(api_key) { - return Err("不能使用内核模板里的示例鉴权密钥".to_string()); + return Err("The example authentication key from the kernel template cannot be used".to_string()); } Ok(()) } pub(crate) fn validate_api_key_remark(remark: &str) -> Result<(), String> { if remark.chars().count() > 80 { - return Err("密钥备注不能超过 80 个字符".to_string()); + return Err("Key note cannot exceed 80 characters".to_string()); } if remark.chars().any(char::is_control) { - return Err("密钥备注不能包含换行或控制字符".to_string()); + return Err("Key note cannot contain line breaks or control characters".to_string()); } Ok(()) } @@ -30,7 +30,7 @@ pub(crate) fn validate_api_access_provider_section(section: &str) -> Result<(), ) { Ok(()) } else { - Err("API 接入类型无效".to_string()) + Err("Invalid API access type".to_string()) } } @@ -84,10 +84,10 @@ impl GuiConfigFile { pub(crate) fn validate_management_secret_key(secret_key: &str) -> Result<(), String> { if secret_key.chars().count() > 512 { - return Err("管理密钥不能超过 512 个字符".to_string()); + return Err("Management key cannot exceed 512 characters".to_string()); } if secret_key.chars().any(char::is_control) { - return Err("管理密钥不能包含控制字符".to_string()); + return Err("Management key cannot contain control characters".to_string()); } Ok(()) } @@ -95,13 +95,13 @@ pub(crate) fn validate_management_secret_key(secret_key: &str) -> Result<(), Str pub(crate) fn validate_strong_management_secret_key(secret_key: &str) -> Result<(), String> { validate_management_secret_key(secret_key)?; if secret_key.trim().is_empty() { - return Err("WebUI 密钥不能为空".to_string()); + return Err("WebUI key cannot be empty".to_string()); } if secret_key.trim() == LEGACY_DEFAULT_MANAGEMENT_SECRET_KEY { - return Err("不能继续使用旧版默认 WebUI 密钥 123456".to_string()); + return Err("The legacy default WebUI key 123456 cannot be used".to_string()); } if is_hashed_management_secret_key(secret_key) { - return Err("GUI 配置必须保存可用于管理接口认证的明文 WebUI 密钥".to_string()); + return Err("GUI configuration must save a plaintext WebUI key that can authenticate with the management interface".to_string()); } Ok(()) } @@ -110,7 +110,7 @@ pub(crate) fn generate_management_secret_key() -> Result { use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; let mut random = [0_u8; 32]; - getrandom::fill(&mut random).map_err(|error| format!("生成 WebUI 安全密钥失败: {error}"))?; + getrandom::fill(&mut random).map_err(|error| format!("Failed to generate WebUI security key: {error}"))?; Ok(format!("wui-Aa9_{}", URL_SAFE_NO_PAD.encode(random))) } @@ -158,7 +158,7 @@ pub(crate) fn validate_routing_strategy(strategy: &str) -> Result<(), String> { if matches!(strategy, "round-robin" | "fill-first") { return Ok(()); } - Err("路由策略只支持 round-robin 或 fill-first".to_string()) + Err("Routing strategy supports only round-robin or fill-first".to_string()) } pub(crate) fn normalize_optional_config_string( @@ -167,7 +167,7 @@ pub(crate) fn normalize_optional_config_string( ) -> Result { let value = value.trim().to_string(); if value.chars().any(char::is_control) { - return Err(format!("{field_name} 不能包含控制字符")); + return Err(format!("{field_name} cannot contain control characters")); } Ok(value) } @@ -251,7 +251,7 @@ pub(crate) fn config_update_error_with_rollback( rollback_error: Option, ) -> String { match rollback_error { - Some(rollback_error) => format!("{error};回滚内核配置也失败: {rollback_error}"), + Some(rollback_error) => format!("{error}; failed to roll back kernel configuration: {rollback_error}"), None => error, } } @@ -275,9 +275,9 @@ pub(crate) fn merge_core_config_fields( let current_value = current .map(|current| { let current = serde_norway::from_str::(current) - .map_err(|err| format!("解析现有内核配置失败,为避免配置丢失已停止写入: {err}"))?; + .map_err(|err| format!("Failed to parse existing kernel configuration; writing stopped to prevent configuration loss: {err}"))?; if !current.is_mapping() { - return Err("现有内核配置根节点必须是 YAML 映射,已停止写入".to_string()); + return Err("Existing kernel configuration root must be a YAML mapping; writing stopped".to_string()); } Ok(current) }) @@ -290,9 +290,9 @@ pub(crate) fn merge_core_config_value( current: Option, ) -> Result { let template_value = serde_norway::from_str::(template) - .map_err(|err| format!("解析内核配置模板失败: {err}"))?; + .map_err(|err| format!("Failed to parse kernel configuration template: {err}"))?; if !template_value.is_mapping() { - return Err("内核配置模板根节点必须是 YAML 映射".to_string()); + return Err("Kernel configuration template root must be a YAML mapping".to_string()); } let mut merged = template_value.clone(); @@ -302,9 +302,9 @@ pub(crate) fn merge_core_config_value( let rendered = render_yaml_value_changes(template, &template_value, &merged)?; let rendered_value = serde_norway::from_str::(&rendered) - .map_err(|err| format!("验证迁移后的内核配置失败: {err}"))?; + .map_err(|err| format!("Failed to validate migrated kernel configuration: {err}"))?; if !rendered_value.is_mapping() { - return Err("迁移后的内核配置根节点必须是 YAML 映射".to_string()); + return Err("Migrated kernel configuration root must be a YAML mapping".to_string()); } Ok(rendered) } @@ -353,26 +353,26 @@ pub(crate) fn patch_core_network_routing_yaml( document, "request-retry", serde_norway::to_value(config.request_retry) - .map_err(|err| format!("序列化请求重试次数失败: {err}"))?, + .map_err(|err| format!("Failed to serialize request retry count: {err}"))?, )?; set_core_yaml_top_level_value( document, "max-retry-credentials", serde_norway::to_value(config.max_retry_credentials) - .map_err(|err| format!("序列化最大重试凭据数失败: {err}"))?, + .map_err(|err| format!("Failed to serialize maximum retry credentials: {err}"))?, )?; set_core_yaml_top_level_value( document, "max-retry-interval", serde_norway::to_value(config.max_retry_interval) - .map_err(|err| format!("序列化最大重试等待时间失败: {err}"))?, + .map_err(|err| format!("Failed to serialize maximum retry interval: {err}"))?, )?; set_core_yaml_nested_value( document, "streaming", "bootstrap-retries", serde_norway::to_value(config.streaming_bootstrap_retries) - .map_err(|err| format!("序列化流式启动重试次数失败: {err}"))?, + .map_err(|err| format!("Failed to serialize streaming bootstrap retry count: {err}"))?, )?; Ok(*document != original) }) @@ -477,7 +477,7 @@ pub(crate) fn apply_network_settings( ) -> Result<(), String> { let mapping = document .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let host = config.host.trim(); mapping.insert( @@ -486,7 +486,7 @@ pub(crate) fn apply_network_settings( ); mapping.insert( serde_norway::Value::String("port".to_string()), - serde_norway::to_value(config.port).map_err(|err| format!("序列化内核端口失败: {err}"))?, + serde_norway::to_value(config.port).map_err(|err| format!("Failed to serialize kernel port: {err}"))?, ); Ok(()) } @@ -507,7 +507,7 @@ pub(crate) fn apply_gui_managed_settings( document, "port", serde_norway::to_value(config.port) - .map_err(|err| format!("序列化内核端口失败: {err}"))?, + .map_err(|err| format!("Failed to serialize kernel port: {err}"))?, )?; changed |= set_core_yaml_top_level_value( document, @@ -533,13 +533,13 @@ pub(crate) fn apply_gui_managed_settings( document, "logs-max-total-size-mb", serde_norway::to_value(config.logs_max_total_size_mb) - .map_err(|err| format!("序列化日志容量限制失败: {err}"))?, + .map_err(|err| format!("Failed to serialize log size limit: {err}"))?, )?; changed |= set_core_yaml_top_level_value( document, "error-logs-max-files", serde_norway::to_value(config.error_logs_max_files) - .map_err(|err| format!("序列化错误日志保留数失败: {err}"))?, + .map_err(|err| format!("Failed to serialize error log retention count: {err}"))?, )?; changed |= set_core_yaml_top_level_value( document, @@ -550,7 +550,7 @@ pub(crate) fn apply_gui_managed_settings( document, "redis-usage-queue-retention-seconds", serde_norway::to_value(config.redis_usage_queue_retention_seconds) - .map_err(|err| format!("序列化 Redis 用量队列保留时间失败: {err}"))?, + .map_err(|err| format!("Failed to serialize Redis usage queue retention: {err}"))?, )?; changed |= set_core_yaml_top_level_value( document, @@ -601,26 +601,26 @@ pub(crate) fn apply_gui_managed_settings( document, "request-retry", serde_norway::to_value(config.request_retry) - .map_err(|err| format!("序列化请求重试次数失败: {err}"))?, + .map_err(|err| format!("Failed to serialize request retry count: {err}"))?, )?; changed |= set_core_yaml_top_level_value( document, "max-retry-credentials", serde_norway::to_value(config.max_retry_credentials) - .map_err(|err| format!("序列化最大重试凭据数失败: {err}"))?, + .map_err(|err| format!("Failed to serialize maximum retry credentials: {err}"))?, )?; changed |= set_core_yaml_top_level_value( document, "max-retry-interval", serde_norway::to_value(config.max_retry_interval) - .map_err(|err| format!("序列化最大重试等待时间失败: {err}"))?, + .map_err(|err| format!("Failed to serialize maximum retry interval: {err}"))?, )?; changed |= set_core_yaml_nested_value( document, "streaming", "bootstrap-retries", serde_norway::to_value(config.streaming_bootstrap_retries) - .map_err(|err| format!("序列化流式启动重试次数失败: {err}"))?, + .map_err(|err| format!("Failed to serialize streaming bootstrap retry count: {err}"))?, )?; Ok(changed) })? @@ -628,14 +628,14 @@ pub(crate) fn apply_gui_managed_settings( let updated = patch_core_api_keys_yaml(&updated, &gui_api_key_values(&config.api_keys))?; serde_norway::from_str::(&updated) - .map_err(|err| format!("验证启动内核配置失败: {err}"))?; + .map_err(|err| format!("Failed to validate kernel startup configuration: {err}"))?; Ok(updated) } pub(crate) fn write_bytes_directly(path: &Path, content: &[u8]) -> Result<(), String> { let directory = path.parent().unwrap_or_else(|| Path::new(".")); fs::create_dir_all(directory) - .map_err(|error| format!("创建配置目录失败 {}: {error}", path_to_string(directory)))?; + .map_err(|error| format!("Failed to create configuration directory {}: {error}", path_to_string(directory)))?; let write_result = (|| -> io::Result<()> { let mut file = fs::OpenOptions::new() @@ -649,7 +649,7 @@ pub(crate) fn write_bytes_directly(path: &Path, content: &[u8]) -> Result<(), St file.sync_all() })(); - write_result.map_err(|error| format!("直接写入配置失败 {}: {error}", path_to_string(path)))?; + write_result.map_err(|error| format!("Failed to write configuration directly {}: {error}", path_to_string(path)))?; remember_software_write(path, content); Ok(()) } @@ -660,7 +660,7 @@ pub(crate) fn write_bytes_atomically(path: &Path, content: &[u8]) -> Result<(), static SEQUENCE: AtomicU64 = AtomicU64::new(0); let directory = path.parent().unwrap_or_else(|| Path::new(".")); fs::create_dir_all(directory) - .map_err(|error| format!("创建配置目录失败 {}: {error}", path_to_string(directory)))?; + .map_err(|error| format!("Failed to create configuration directory {}: {error}", path_to_string(directory)))?; let file_name = path .file_name() .and_then(|name| name.to_str()) @@ -682,7 +682,7 @@ pub(crate) fn write_bytes_atomically(path: &Path, content: &[u8]) -> Result<(), if let Err(error) = write_result { let _ = fs::remove_file(&temporary_path); return Err(format!( - "原子写入配置失败 {}: {error}", + "Failed to write configuration atomically {}: {error}", path_to_string(path) )); } @@ -882,16 +882,16 @@ fn copy_auth_file_atomically(source: &Path, destination: &Path) -> Result<(), St Ok(metadata) => { if metadata.file_type().is_symlink() || !metadata.is_file() { return Err(format!( - "OAuth 迁移目标不是普通文件: {}", + "OAuth migration target is not a regular file: {}", path_to_string(destination) )); } let source_bytes = fs::read(source).map_err(|error| { - format!("读取旧 OAuth 文件失败 {}: {error}", path_to_string(source)) + format!("Failed to read old OAuth file {}: {error}", path_to_string(source)) })?; let destination_bytes = fs::read(destination).map_err(|error| { format!( - "读取现有 OAuth 文件失败 {}: {error}", + "Failed to read existing OAuth file {}: {error}", path_to_string(destination) ) })?; @@ -899,14 +899,14 @@ fn copy_auth_file_atomically(source: &Path, destination: &Path) -> Result<(), St return Ok(()); } return Err(format!( - "OAuth 迁移目标已存在不同内容,未覆盖: {}", + "OAuth migration target already contains different content and was not overwritten: {}", path_to_string(destination) )); } Err(error) if error.kind() == io::ErrorKind::NotFound => {} Err(error) => { return Err(format!( - "检查 OAuth 迁移目标失败 {}: {error}", + "Failed to check OAuth migration target {}: {error}", path_to_string(destination) )); } @@ -918,7 +918,7 @@ fn copy_auth_file_atomically(source: &Path, destination: &Path) -> Result<(), St let directory = destination.parent().unwrap_or_else(|| Path::new(".")); fs::create_dir_all(directory).map_err(|error| { format!( - "创建 OAuth 迁移目录失败 {}: {error}", + "Failed to create OAuth migration directory {}: {error}", path_to_string(directory) ) })?; @@ -942,7 +942,7 @@ fn copy_auth_file_atomically(source: &Path, destination: &Path) -> Result<(), St if let Err(error) = copy_result { let _ = fs::remove_file(&temporary_path); return Err(format!( - "复制 OAuth 文件失败 {} -> {}: {error}", + "Failed to copy OAuth file {} -> {}: {error}", path_to_string(source), path_to_string(destination) )); @@ -957,21 +957,21 @@ fn copy_auth_directory(source: &Path, destination: &Path) -> Result<(), String> Err(error) if error.kind() == io::ErrorKind::NotFound => { return fs::create_dir_all(destination).map_err(|create_error| { format!( - "创建 OAuth 目录失败 {}: {create_error}", + "Failed to create OAuth directory {}: {create_error}", path_to_string(destination) ) }); } Err(error) => { return Err(format!( - "检查旧 OAuth 目录失败 {}: {error}", + "Failed to check old OAuth directory {}: {error}", path_to_string(source) )); } }; if source_metadata.file_type().is_symlink() || !source_metadata.is_dir() { return Err(format!( - "旧 OAuth 路径不是普通目录: {}", + "Old OAuth path is not a regular directory: {}", path_to_string(source) )); } @@ -979,7 +979,7 @@ fn copy_auth_directory(source: &Path, destination: &Path) -> Result<(), String> match fs::symlink_metadata(destination) { Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => { return Err(format!( - "OAuth 迁移目标不是普通目录: {}", + "OAuth migration target is not a regular directory: {}", path_to_string(destination) )); } @@ -987,37 +987,37 @@ fn copy_auth_directory(source: &Path, destination: &Path) -> Result<(), String> Err(error) if error.kind() == io::ErrorKind::NotFound => { fs::create_dir_all(destination).map_err(|create_error| { format!( - "创建 OAuth 迁移目录失败 {}: {create_error}", + "Failed to create OAuth migration directory {}: {create_error}", path_to_string(destination) ) })?; } Err(error) => { return Err(format!( - "检查 OAuth 迁移目录失败 {}: {error}", + "Failed to check OAuth migration directory {}: {error}", path_to_string(destination) )); } } for entry in fs::read_dir(source) - .map_err(|error| format!("读取旧 OAuth 目录失败 {}: {error}", path_to_string(source)))? + .map_err(|error| format!("Failed to read old OAuth directory {}: {error}", path_to_string(source)))? { let entry = entry.map_err(|error| { format!( - "读取旧 OAuth 目录项失败 {}: {error}", + "Failed to read old OAuth directory entry {}: {error}", path_to_string(source) ) })?; let file_type = entry.file_type().map_err(|error| { format!( - "检查旧 OAuth 目录项失败 {}: {error}", + "Failed to check old OAuth directory entry {}: {error}", path_to_string(&entry.path()) ) })?; if file_type.is_symlink() { return Err(format!( - "OAuth 迁移不接受符号链接: {}", + "OAuth migration does not accept symbolic links: {}", path_to_string(&entry.path()) )); } @@ -1028,7 +1028,7 @@ fn copy_auth_directory(source: &Path, destination: &Path) -> Result<(), String> copy_auth_file_atomically(&entry.path(), &target)?; } else { return Err(format!( - "OAuth 迁移不支持该文件类型: {}", + "OAuth migration does not support this file type: {}", path_to_string(&entry.path()) )); } @@ -1049,7 +1049,7 @@ pub(crate) fn migrate_auth_dir_from_macos_app_bundle( let destination = normalize_path_lexically(persistent_auth_dir); if auth_dir_is_inside_macos_app_bundle(&destination) { return Err(format!( - "OAuth 持久化目录不能位于 macOS 应用包内: {}", + "OAuth persistence directory cannot be inside a macOS application bundle: {}", path_to_string(&destination) )); } @@ -1069,7 +1069,7 @@ fn migrate_packaged_macos_auth_dir(config: &mut GuiConfigFile) -> Result Result { let (mut config, presence, mut changed, gui_parse_failed) = if gui_config_exists { let content = fs::read_to_string(&config_path) - .map_err(|err| format!("读取 GUI 配置失败 {}: {err}", path_to_string(&config_path)))?; + .map_err(|err| format!("Failed to read GUI configuration {}: {err}", path_to_string(&config_path)))?; match ( toml::from_str::(&content), toml::from_str::(&content), @@ -1111,14 +1111,14 @@ pub(crate) fn load_or_create_gui_config() -> Result { } else if legacy_config_exists { let content = fs::read_to_string(&legacy_config_path).map_err(|err| { format!( - "读取旧 GUI 配置失败 {}: {err}", + "Failed to read old GUI configuration {}: {err}", path_to_string(&legacy_config_path) ) })?; let config = serde_yaml::from_str::(&content) - .map_err(|err| format!("解析旧 GUI 配置失败: {err}"))?; + .map_err(|err| format!("Failed to parse old GUI configuration: {err}"))?; let presence = serde_yaml::from_str::(&content) - .map_err(|err| format!("解析旧 GUI 配置字段失败: {err}"))?; + .map_err(|err| format!("Failed to parse old GUI configuration field: {err}"))?; (config, presence, true, false) } else { ( @@ -1326,14 +1326,14 @@ pub(crate) fn load_or_create_gui_config() -> Result { } if management_secret_rotated { if let Err(error) = patch_core_management_secret_key(&config.management_secret_key) { - eprintln!("更新旧版 CPA WebUI 密钥失败,将在下次启动内核时重试: {error}"); + eprintln!("Failed to update legacy CPA WebUI key; will retry the next time the kernel starts: {error}"); } } if !config.auth_dir.trim().is_empty() { let install_dir = core_install_dir()?; let auth_dir = auth_dir_path_for_core(&config.auth_dir, &install_dir); fs::create_dir_all(&auth_dir) - .map_err(|error| format!("创建凭证目录失败 {}: {error}", path_to_string(&auth_dir)))?; + .map_err(|error| format!("Failed to create credentials directory {}: {error}", path_to_string(&auth_dir)))?; } Ok(config) } @@ -1524,7 +1524,7 @@ pub(crate) fn restore_main_window_size(app: &tauri::AppHandle) -> Result<(), Str }; let window = app .get_webview_window("main") - .ok_or_else(|| "主窗口不存在,无法恢复窗口尺寸".to_string())?; + .ok_or_else(|| "Main window does not exist; cannot restore window size".to_string())?; let restored_size = fit_window_size_to_current_monitor(&window, saved_size); window @@ -1532,7 +1532,7 @@ pub(crate) fn restore_main_window_size(app: &tauri::AppHandle) -> Result<(), Str f64::from(restored_size.width), f64::from(restored_size.height), )) - .map_err(|error| format!("恢复主窗口尺寸失败: {error}"))?; + .map_err(|error| format!("Failed to restore main window size: {error}"))?; window_size_state.replace(restored_size) } @@ -1543,15 +1543,15 @@ pub(crate) fn persist_main_window_size(app: &tauri::AppHandle) -> Result<(), Str None => { let window = app .get_webview_window("main") - .ok_or_else(|| "主窗口不存在,无法保存窗口尺寸".to_string())?; + .ok_or_else(|| "Main window does not exist; cannot save window size".to_string())?; let physical_size = window .inner_size() - .map_err(|error| format!("读取主窗口尺寸失败: {error}"))?; + .map_err(|error| format!("Failed to read main window size: {error}"))?; let scale_factor = window .scale_factor() - .map_err(|error| format!("读取主窗口缩放比例失败: {error}"))?; + .map_err(|error| format!("Failed to read main window scale factor: {error}"))?; logical_window_size_from_physical(&physical_size, scale_factor) - .ok_or_else(|| "主窗口尺寸无效,已跳过保存".to_string())? + .ok_or_else(|| "Invalid main window size; save skipped".to_string())? } }; @@ -1854,7 +1854,7 @@ pub(crate) fn write_gui_config_to_path( let content = document.to_string(); toml::from_str::(&content) - .map_err(|error| format!("验证 GUI 配置失败: {error}"))?; + .map_err(|error| format!("Failed to validate GUI configuration: {error}"))?; if existing.as_deref() == Some(content.as_str()) { return Ok(()); } @@ -1863,19 +1863,19 @@ pub(crate) fn write_gui_config_to_path( pub(crate) fn validate_gui_config(config: &GuiConfigFile) -> Result<(), String> { if config.port == 0 { - return Err("GUI 配置端口必须在 1 到 65535 之间".to_string()); + return Err("GUI configuration port must be between 1 and 65535".to_string()); } if config.host.trim().is_empty() || config.host.chars().any(char::is_control) { - return Err("GUI 配置 host 无效".to_string()); + return Err("Invalid GUI configuration host".to_string()); } if config.auth_dir.trim().is_empty() || config.auth_dir.chars().any(char::is_control) { - return Err("凭证目录不能为空或包含控制字符".to_string()); + return Err("Credentials directory cannot be empty or contain control characters".to_string()); } #[cfg(target_os = "macos")] { let auth_dir = auth_dir_path_for_core(&config.auth_dir, &core_install_dir()?); if auth_dir_is_inside_macos_app_bundle(&auth_dir) { - return Err("OAuth 凭证目录不能位于 macOS 应用包内".to_string()); + return Err("OAuth credentials directory cannot be inside a macOS application bundle".to_string()); } } for entry in &config.api_keys { @@ -1890,7 +1890,7 @@ pub(crate) fn validate_gui_config(config: &GuiConfigFile) -> Result<(), String> .chars() .all(|character| character.is_ascii_hexdigit()) { - return Err("API 接入备注的密钥指纹无效".to_string()); + return Err("Invalid key fingerprint in API access note".to_string()); } if !entry.record_hash.is_empty() && (entry.record_hash.len() != 64 @@ -1899,7 +1899,7 @@ pub(crate) fn validate_gui_config(config: &GuiConfigFile) -> Result<(), String> .chars() .all(|character| character.is_ascii_hexdigit())) { - return Err("API 接入备注的条目指纹无效".to_string()); + return Err("Invalid entry fingerprint in API access note".to_string()); } validate_api_key_remark(&entry.remark)?; } @@ -1908,11 +1908,11 @@ pub(crate) fn validate_gui_config(config: &GuiConfigFile) -> Result<(), String> if config.proxy_override { network_proxy::normalize_optional_proxy_url(&config.proxy_url)?; } else if config.proxy_url.chars().any(char::is_control) { - return Err("代理 URL 不能包含控制字符".to_string()); + return Err("Proxy URL cannot contain control characters".to_string()); } for url in &config.custom_download_mirrors { if normalize_custom_download_mirror_url(url).as_deref() != Ok(url.as_str()) { - return Err(format!("自定义下载镜像地址无效: {url}")); + return Err(format!("Invalid custom download mirror URL: {url}")); } } if config.download_source == VersionDownloadSource::Custom @@ -1920,17 +1920,17 @@ pub(crate) fn validate_gui_config(config: &GuiConfigFile) -> Result<(), String> .custom_download_mirrors .contains(&config.active_custom_download_mirror) { - return Err("当前选择的自定义下载镜像不存在".to_string()); + return Err("The selected custom download mirror does not exist".to_string()); } if config .routing_session_affinity_ttl .chars() .any(char::is_control) { - return Err("会话粘性 TTL 不能包含控制字符".to_string()); + return Err("Session affinity TTL cannot contain control characters".to_string()); } if !(1..=3600).contains(&config.redis_usage_queue_retention_seconds) { - return Err("Redis 用量队列保留时间必须在 1 到 3600 秒之间".to_string()); + return Err("Redis usage queue retention must be between 1 and 3600 seconds".to_string()); } Ok(()) } diff --git a/src-tauri/src/core_config/yaml.rs b/src-tauri/src/core_config/yaml.rs index a82ef27..eac4739 100644 --- a/src-tauri/src/core_config/yaml.rs +++ b/src-tauri/src/core_config/yaml.rs @@ -9,21 +9,21 @@ pub(crate) fn merge_core_config_for_start( let example_config_path = install_dir.join(CORE_EXAMPLE_CONFIG_FILE); if !example_config_path.is_file() { return Err(format!( - "未找到内核配置模板: {}", + "Kernel configuration template not found: {}", path_to_string(&example_config_path) )); } let template = fs::read_to_string(&example_config_path).map_err(|err| { format!( - "读取内核配置模板失败 {}: {err}", + "Failed to read kernel configuration template {}: {err}", path_to_string(&example_config_path) ) })?; let current = if config_path.is_file() { Some(fs::read_to_string(&config_path).map_err(|err| { format!( - "读取现有内核配置失败 {}: {err}", + "Failed to read existing kernel configuration {}: {err}", path_to_string(&config_path) ) })?) @@ -45,7 +45,7 @@ pub(crate) fn patch_core_network_settings(config: &GuiConfigFile) -> Result<(), } let content = fs::read_to_string(&config_path) - .map_err(|err| format!("读取内核配置失败 {}: {err}", path_to_string(&config_path)))?; + .map_err(|err| format!("Failed to read kernel configuration {}: {err}", path_to_string(&config_path)))?; let Some(updated) = patch_core_network_yaml(&content, config)? else { return Ok(()); }; @@ -60,7 +60,7 @@ pub(crate) fn patch_core_network_routing_settings(config: &GuiConfigFile) -> Res } let content = fs::read_to_string(&config_path) - .map_err(|err| format!("读取内核配置失败 {}: {err}", path_to_string(&config_path)))?; + .map_err(|err| format!("Failed to read kernel configuration {}: {err}", path_to_string(&config_path)))?; let Some(updated) = patch_core_network_routing_yaml(&content, config)? else { return Ok(()); }; @@ -102,7 +102,7 @@ pub(crate) fn patch_core_session_routing_settings(config: &GuiConfigFile) -> Res pub(crate) fn lock_core_config_file() -> Result, String> { CORE_CONFIG_FILE_LOCK .lock() - .map_err(|_| "内核配置文件锁已损坏".to_string()) + .map_err(|_| "Kernel configuration file lock is poisoned".to_string()) } pub(crate) fn read_installed_core_config_settings() -> Result { @@ -245,7 +245,7 @@ pub(crate) fn core_sensitive_words_settings_from_value( ) -> Result { let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let read_words = |provider: &str| -> Result, String> { let Some(section) = yaml_mapping_value(root, provider) else { return Ok(Vec::new()); @@ -255,7 +255,7 @@ pub(crate) fn core_sensitive_words_settings_from_value( } let section = section .as_mapping() - .ok_or_else(|| format!("{provider} 必须是 YAML 映射"))?; + .ok_or_else(|| format!("{provider} must be a YAML mapping"))?; let Some(value) = yaml_mapping_value(section, "sensitive-words") else { return Ok(Vec::new()); }; @@ -264,13 +264,13 @@ pub(crate) fn core_sensitive_words_settings_from_value( } value .as_sequence() - .ok_or_else(|| format!("{provider}.sensitive-words 必须是字符串列表"))? + .ok_or_else(|| format!("{provider}.sensitive-words must be a list of strings"))? .iter() .map(|entry| { entry .as_str() .map(str::to_string) - .ok_or_else(|| format!("{provider}.sensitive-words 必须是字符串列表")) + .ok_or_else(|| format!("{provider}.sensitive-words must be a list of strings")) }) .collect() }; @@ -291,10 +291,10 @@ pub(crate) fn patch_core_sensitive_words_yaml( settings: &CoreSensitiveWordsSettings, ) -> Result, String> { let original = serde_norway::from_str::(content) - .map_err(|error| format!("解析内核配置失败: {error}"))?; + .map_err(|error| format!("Failed to parse kernel configuration: {error}"))?; let root = original .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let mut prepared = content.to_string(); for (provider, words) in [ ("antigravity", &settings.antigravity_sensitive_words), @@ -319,17 +319,17 @@ pub(crate) fn patch_core_sensitive_words_yaml( } else { section .as_mapping() - .ok_or_else(|| format!("{provider} 必须是 YAML 映射"))? + .ok_or_else(|| format!("{provider} must be a YAML mapping"))? .clone() }; section.insert( yaml_key("sensitive-words"), - serde_norway::to_value(words).map_err(|error| format!("序列化敏感词失败: {error}"))?, + serde_norway::to_value(words).map_err(|error| format!("Failed to serialize sensitive words: {error}"))?, ); let mut replacement = serde_norway::Mapping::new(); replacement.insert(yaml_key(provider), serde_norway::Value::Mapping(section)); let block = serde_norway::to_string(&replacement) - .map_err(|error| format!("序列化内核配置区段失败: {error}"))?; + .map_err(|error| format!("Failed to serialize kernel configuration section: {error}"))?; prepared = replace_top_level_yaml_block(&prepared, provider, &block); } let updated = patch_core_yaml_document(&prepared, |document| { @@ -340,7 +340,7 @@ pub(crate) fn patch_core_sensitive_words_yaml( ] { let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let section = yaml_mapping_value(root, provider); let current_words = section .and_then(serde_norway::Value::as_mapping) @@ -380,10 +380,10 @@ pub(crate) fn patch_core_sensitive_words_settings( let _config_guard = lock_core_config_file()?; let config_path = core_install_dir()?.join(CORE_CONFIG_FILE); if !config_path.is_file() { - return Err("内核配置尚未生成,请先启动 CPA 内核".to_string()); + return Err("Kernel configuration has not been generated. Start the CPA kernel first".to_string()); } let content = fs::read_to_string(&config_path) - .map_err(|error| format!("读取内核配置失败 {}: {error}", path_to_string(&config_path)))?; + .map_err(|error| format!("Failed to read kernel configuration {}: {error}", path_to_string(&config_path)))?; if let Some(updated) = patch_core_sensitive_words_yaml(&content, settings)? { write_yaml_if_changed(&config_path, &updated)?; } @@ -398,7 +398,7 @@ pub(crate) fn patch_core_api_keys(api_keys: &[String]) -> Result<(), String> { } let content = fs::read_to_string(&config_path) - .map_err(|err| format!("读取内核配置失败 {}: {err}", path_to_string(&config_path)))?; + .map_err(|err| format!("Failed to read kernel configuration {}: {err}", path_to_string(&config_path)))?; let updated = patch_core_api_keys_yaml(&content, api_keys)?; write_yaml_if_changed(&config_path, &updated)?; Ok(()) @@ -458,12 +458,12 @@ pub(crate) fn patch_core_logging_settings(settings: &CoreConfigSettings) -> Resu ( "logs-max-total-size-mb", serde_norway::to_value(settings.logs_max_total_size_mb) - .map_err(|err| format!("序列化日志容量限制失败: {err}"))?, + .map_err(|err| format!("Failed to serialize log size limit: {err}"))?, ), ( "error-logs-max-files", serde_norway::to_value(settings.error_logs_max_files) - .map_err(|err| format!("序列化错误日志保留数失败: {err}"))?, + .map_err(|err| format!("Failed to serialize error log retention count: {err}"))?, ), ( "usage-statistics-enabled", @@ -472,7 +472,7 @@ pub(crate) fn patch_core_logging_settings(settings: &CoreConfigSettings) -> Resu ( "redis-usage-queue-retention-seconds", serde_norway::to_value(settings.redis_usage_queue_retention_seconds) - .map_err(|err| format!("序列化 Redis 用量队列保留时间失败: {err}"))?, + .map_err(|err| format!("Failed to serialize Redis usage queue retention: {err}"))?, ), ] { changed |= set_core_yaml_top_level_value(document, key, value)?; @@ -538,7 +538,7 @@ where } let content = fs::read_to_string(&config_path) - .map_err(|err| format!("读取内核配置失败 {}: {err}", path_to_string(&config_path)))?; + .map_err(|err| format!("Failed to read kernel configuration {}: {err}", path_to_string(&config_path)))?; let Some(updated) = patch_core_yaml_document(&content, update)? else { return Ok(()); }; @@ -556,7 +556,7 @@ where F: FnOnce(&mut serde_norway::Value) -> Result, { let original = serde_norway::from_str::(content) - .map_err(|err| format!("解析内核配置失败: {err}"))?; + .map_err(|err| format!("Failed to parse kernel configuration: {err}"))?; let mut updated = original.clone(); if !update(&mut updated)? { return Ok(None); @@ -640,24 +640,24 @@ pub(crate) fn render_yaml_value_changes( remaining_changes = yaml_edit_changes; let file = editable_content .parse::() - .map_err(|err| format!("解析可编辑内核配置失败: {err}"))?; + .map_err(|err| format!("Failed to parse editable kernel configuration: {err}"))?; let document = file .document() - .ok_or_else(|| "内核配置没有 YAML 文档".to_string())?; + .ok_or_else(|| "Kernel configuration has no YAML document".to_string())?; let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; for change in remaining_changes { set_yaml_edit_mapping_path(&root, &change.path, &change.value)?; } let rendered = file.to_string(); let validated = serde_norway::from_str::(&rendered) - .map_err(|err| format!("验证更新后的内核配置失败: {err}"))?; + .map_err(|err| format!("Failed to validate updated kernel configuration: {err}"))?; if validated != *updated { let path = first_yaml_mismatch_path(updated, &validated, &mut Vec::new()) .unwrap_or_else(|| "".to_string()); return Err(format!( - "更新后的内核配置与预期值不一致(路径: {path}),已拒绝写入" + "Updated kernel configuration does not match the expected value (path: {path}); write rejected" )); } Ok(rendered) @@ -787,13 +787,13 @@ pub(crate) fn collect_yaml_value_changes( ) => { for key in original_mapping.keys() { if !updated_mapping.contains_key(key) { - return Err("当前内核配置更新不支持删除任意 YAML 字段".to_string()); + return Err("The current kernel configuration update does not support deleting arbitrary YAML fields".to_string()); } } for (key, updated_value) in updated_mapping { let key = key .as_str() - .ok_or_else(|| "内核配置映射键必须是字符串".to_string())?; + .ok_or_else(|| "Kernel configuration mapping keys must be strings".to_string())?; path.push(key.to_string()); if let Some(original_value) = original_mapping.get(yaml_key(key)) { collect_yaml_value_changes(original_value, updated_value, path, changes)?; @@ -807,7 +807,7 @@ pub(crate) fn collect_yaml_value_changes( } Ok(()) } - _ if path.is_empty() => Err("内核配置顶层必须保持为 YAML 映射".to_string()), + _ if path.is_empty() => Err("Kernel configuration root must remain a YAML mapping".to_string()), _ => { changes.push(YamlValueChange { path: path.clone(), @@ -880,7 +880,7 @@ pub(crate) fn insert_yaml_block_mapping_values_at_path( let mut insertion = String::new(); for (key, value) in entries { let value = serde_json::to_string(value) - .map_err(|error| format!("序列化内核配置值失败: {error}"))?; + .map_err(|error| format!("Failed to serialize kernel configuration value: {error}"))?; insertion.push_str(&indent); insertion.push_str(&render_yaml_mapping_key(key)); insertion.push_str(": "); @@ -932,18 +932,18 @@ pub(crate) fn replace_yaml_sequence_value( ) -> Result { let file = content .parse::() - .map_err(|err| format!("解析可编辑内核配置失败: {err}"))?; + .map_err(|err| format!("Failed to parse editable kernel configuration: {err}"))?; let document = file .document() - .ok_or_else(|| "内核配置没有 YAML 文档".to_string())?; + .ok_or_else(|| "Kernel configuration has no YAML document".to_string())?; let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let node = yaml_edit_node_at_path(&root, path) - .ok_or_else(|| format!("未找到内核配置序列 {}", path.join(".")))?; + .ok_or_else(|| format!("Kernel configuration sequence {} not found", path.join(".")))?; let sequence = node .as_sequence() - .ok_or_else(|| format!("内核配置字段 {} 必须是 YAML 序列", path.join(".")))?; + .ok_or_else(|| format!("Kernel configuration field {} must be a YAML sequence", path.join(".")))?; let range = sequence.byte_range(); let start = range.start as usize; let end = range.end as usize; @@ -958,7 +958,7 @@ pub(crate) fn replace_yaml_sequence_value( let serialized = if prefix.trim().is_empty() { let serialized = serde_norway::to_string(value) - .map_err(|err| format!("序列化内核配置序列失败: {err}"))?; + .map_err(|err| format!("Failed to serialize kernel configuration sequence: {err}"))?; let serialized = serialized.trim_end_matches(['\r', '\n']); let mut indented = String::with_capacity(serialized.len() + prefix.len() * 2); for (index, line) in serialized.split('\n').enumerate() { @@ -970,7 +970,7 @@ pub(crate) fn replace_yaml_sequence_value( } indented } else { - serde_json::to_string(value).map_err(|err| format!("序列化内核配置序列失败: {err}"))? + serde_json::to_string(value).map_err(|err| format!("Failed to serialize kernel configuration sequence: {err}"))? }; Ok(format!( @@ -1001,7 +1001,7 @@ pub(crate) fn set_yaml_edit_mapping_path( value: &serde_norway::Value, ) -> Result<(), String> { let Some((key, remaining)) = path.split_first() else { - return Err("内核配置更新路径不能为空".to_string()); + return Err("Kernel configuration update path cannot be empty".to_string()); }; if remaining.is_empty() { return set_yaml_edit_mapping_value(mapping, key, value); @@ -1009,7 +1009,7 @@ pub(crate) fn set_yaml_edit_mapping_path( if let Some(child) = mapping.get(key.as_str()) { let child = child .as_mapping() - .ok_or_else(|| format!("内核配置区段 {key} 必须是 YAML 映射"))?; + .ok_or_else(|| format!("Kernel configuration section {key} must be a YAML mapping"))?; return set_yaml_edit_mapping_path(child, remaining, value); } let nested_value = nested_yaml_value_for_path(remaining, value.clone()); @@ -1050,12 +1050,12 @@ pub(crate) fn set_yaml_edit_mapping_value( if let Some(node) = mapping.get(key) { if let Some(sequence) = node.as_sequence() { if sequence.len() != values.len() { - return Err(format!("内核配置序列 {key} 长度变化未被预处理")); + return Err(format!("Length change in kernel configuration sequence {key} was not preprocessed")); } for (index, value) in values.iter().enumerate() { let value = yaml_edit_node_from_value(value)?; if !sequence.set(index, value) { - return Err(format!("更新内核配置序列 {key}[{index}] 失败")); + return Err(format!("Failed to update kernel configuration sequence {key}[{index}]")); } } return Ok(()); @@ -1088,14 +1088,14 @@ pub(crate) fn yaml_edit_node_from_value( ) -> Result { const WRAPPER_KEY: &str = "__cpa_gui_value__"; let value = - serde_json::to_string(value).map_err(|err| format!("序列化内核配置值失败: {err}"))?; + serde_json::to_string(value).map_err(|err| format!("Failed to serialize kernel configuration value: {err}"))?; let serialized = format!("{WRAPPER_KEY}: {value}\n"); let file = serialized .parse::() - .map_err(|err| format!("解析内核配置值失败: {err}"))?; + .map_err(|err| format!("Failed to parse kernel configuration value: {err}"))?; file.document() .and_then(|document| document.get(WRAPPER_KEY)) - .ok_or_else(|| "无法构造内核配置值".to_string()) + .ok_or_else(|| "Unable to construct kernel configuration value".to_string()) } pub(crate) fn set_core_yaml_top_level_value( @@ -1105,7 +1105,7 @@ pub(crate) fn set_core_yaml_top_level_value( ) -> Result { let root = document .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let key = yaml_key(key); if root.get(&key) == Some(&value) { return Ok(false); @@ -1122,12 +1122,12 @@ pub(crate) fn set_core_yaml_nested_value( ) -> Result { let root = document .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let section = root .entry(yaml_key(section)) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())) .as_mapping_mut() - .ok_or_else(|| "内核配置区段必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration section must be a YAML mapping".to_string())?; let key = yaml_key(key); if section.get(&key) == Some(&value) { return Ok(false); @@ -1141,10 +1141,10 @@ pub(crate) fn patch_core_api_keys_yaml( api_keys: &[String], ) -> Result { let parsed = serde_norway::from_str::(content) - .map_err(|err| format!("解析内核配置失败: {err}"))?; + .map_err(|err| format!("Failed to parse kernel configuration: {err}"))?; let root = parsed .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let has_legacy_api_keys = nested_yaml_value( root, &["auth", "providers", "config-api-key", "api-key-entries"], @@ -1154,10 +1154,10 @@ pub(crate) fn patch_core_api_keys_yaml( let content = if has_legacy_api_keys { let file = content .parse::() - .map_err(|err| format!("解析内核配置失败: {err}"))?; + .map_err(|err| format!("Failed to parse kernel configuration: {err}"))?; let document = file .document() - .ok_or_else(|| "内核配置没有 YAML 文档".to_string())?; + .ok_or_else(|| "Kernel configuration has no YAML document".to_string())?; clear_legacy_api_key_paths(&document); file.to_string() } else { @@ -1166,7 +1166,7 @@ pub(crate) fn patch_core_api_keys_yaml( let block = render_core_api_keys_yaml(api_keys)?; let updated = replace_top_level_yaml_block(&content, "api-keys", &block); serde_norway::from_str::(&updated) - .map_err(|err| format!("验证更新后的内核配置失败: {err}"))?; + .map_err(|err| format!("Failed to validate updated kernel configuration: {err}"))?; Ok(updated) } @@ -1183,7 +1183,7 @@ pub(crate) fn render_core_api_keys_yaml(api_keys: &[String]) -> Result Result<(PathBuf, yaml_serde_edit::YamlValue), String> { let config_path = core_install_dir()?.join(CORE_CONFIG_FILE); if !config_path.is_file() { - return Err("内核配置尚未生成,请先启动 CPA 内核".to_string()); + return Err("Kernel configuration has not been generated. Start the CPA kernel first".to_string()); } let content = fs::read_to_string(&config_path) - .map_err(|err| format!("读取内核配置失败 {}: {err}", path_to_string(&config_path)))?; + .map_err(|err| format!("Failed to read kernel configuration {}: {err}", path_to_string(&config_path)))?; let document = yaml_serde_edit::YamlValue::parse(&content) - .map_err(|err| format!("解析内核配置失败: {err}"))?; + .map_err(|err| format!("Failed to parse kernel configuration: {err}"))?; Ok((config_path, document)) } @@ -1321,13 +1321,13 @@ pub(crate) fn core_config_settings_from_value( ) -> Result { let root = document .as_mapping() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let host = yaml_mapping_value(root, "host") .map(|value| { value .as_str() .map(str::to_string) - .ok_or_else(|| "host 必须是字符串".to_string()) + .ok_or_else(|| "host must be a string".to_string()) }) .transpose()? .unwrap_or_else(|| "127.0.0.1".to_string()); @@ -1337,7 +1337,7 @@ pub(crate) fn core_config_settings_from_value( .as_u64() .and_then(|value| u16::try_from(value).ok()) .filter(|value| *value != 0) - .ok_or_else(|| "port 必须是 1 到 65535 之间的整数".to_string()) + .ok_or_else(|| "port must be an integer between 1 and 65535".to_string()) }) .transpose()? .unwrap_or(8317); @@ -1346,7 +1346,7 @@ pub(crate) fn core_config_settings_from_value( value .as_str() .map(str::to_string) - .ok_or_else(|| "auth-dir 必须是字符串".to_string()) + .ok_or_else(|| "auth-dir must be a string".to_string()) }) .transpose()? .unwrap_or_else(|| OAUTH_DIR_NAME.to_string()); @@ -1354,7 +1354,7 @@ pub(crate) fn core_config_settings_from_value( .map(|value| { value .as_bool() - .ok_or_else(|| "debug 必须是布尔值".to_string()) + .ok_or_else(|| "debug must be a boolean".to_string()) }) .transpose()? .unwrap_or(false); @@ -1362,7 +1362,7 @@ pub(crate) fn core_config_settings_from_value( .map(|value| { value .as_bool() - .ok_or_else(|| "commercial-mode 必须是布尔值".to_string()) + .ok_or_else(|| "commercial-mode must be a boolean".to_string()) }) .transpose()? .unwrap_or(false); @@ -1370,7 +1370,7 @@ pub(crate) fn core_config_settings_from_value( .map(|value| { value .as_bool() - .ok_or_else(|| "logging-to-file 必须是布尔值".to_string()) + .ok_or_else(|| "logging-to-file must be a boolean".to_string()) }) .transpose()? .unwrap_or(false); @@ -1379,7 +1379,7 @@ pub(crate) fn core_config_settings_from_value( value .as_u64() .and_then(|value| u32::try_from(value).ok()) - .ok_or_else(|| "logs-max-total-size-mb 必须是非负整数".to_string()) + .ok_or_else(|| "logs-max-total-size-mb must be a non-negative integer".to_string()) }) .transpose()? .unwrap_or(DEFAULT_LOGS_MAX_TOTAL_SIZE_MB); @@ -1388,7 +1388,7 @@ pub(crate) fn core_config_settings_from_value( value .as_u64() .and_then(|value| u32::try_from(value).ok()) - .ok_or_else(|| "error-logs-max-files 必须是非负整数".to_string()) + .ok_or_else(|| "error-logs-max-files must be a non-negative integer".to_string()) }) .transpose()? .unwrap_or(DEFAULT_ERROR_LOGS_MAX_FILES); @@ -1396,7 +1396,7 @@ pub(crate) fn core_config_settings_from_value( .map(|value| { value .as_bool() - .ok_or_else(|| "usage-statistics-enabled 必须是布尔值".to_string()) + .ok_or_else(|| "usage-statistics-enabled must be a boolean".to_string()) }) .transpose()? .unwrap_or(true); @@ -1406,7 +1406,7 @@ pub(crate) fn core_config_settings_from_value( value .as_u64() .and_then(|value| u32::try_from(value).ok()) - .ok_or_else(|| "redis-usage-queue-retention-seconds 必须是非负整数".to_string()) + .ok_or_else(|| "redis-usage-queue-retention-seconds must be a non-negative integer".to_string()) }) .transpose()? .unwrap_or(DEFAULT_REDIS_USAGE_QUEUE_RETENTION_SECONDS); @@ -1419,7 +1419,7 @@ pub(crate) fn core_config_settings_from_value( .map(|value| { value .as_bool() - .ok_or_else(|| "request-log 必须是布尔值".to_string()) + .ok_or_else(|| "request-log must be a boolean".to_string()) }) .transpose()? .unwrap_or(false); @@ -1432,7 +1432,7 @@ pub(crate) fn core_config_settings_from_value( .map(|value| { value .as_bool() - .ok_or_else(|| "plugins.enabled 必须是布尔值".to_string()) + .ok_or_else(|| "plugins.enabled must be a boolean".to_string()) }) .transpose()? .unwrap_or(false); @@ -1441,7 +1441,7 @@ pub(crate) fn core_config_settings_from_value( value .as_str() .map(str::to_string) - .ok_or_else(|| "routing.strategy 必须是字符串".to_string()) + .ok_or_else(|| "routing.strategy must be a string".to_string()) }) .transpose()? .unwrap_or_else(|| "round-robin".to_string()); @@ -1450,7 +1450,7 @@ pub(crate) fn core_config_settings_from_value( value .as_str() .map(str::to_string) - .ok_or_else(|| "proxy-url 必须是字符串".to_string()) + .ok_or_else(|| "proxy-url must be a string".to_string()) }) .transpose()? .unwrap_or_default(); @@ -1459,7 +1459,7 @@ pub(crate) fn core_config_settings_from_value( .map(|value| { value .as_bool() - .ok_or_else(|| "routing.session-affinity 必须是布尔值".to_string()) + .ok_or_else(|| "routing.session-affinity must be a boolean".to_string()) }) .transpose()? .unwrap_or(false); @@ -1470,7 +1470,7 @@ pub(crate) fn core_config_settings_from_value( value .as_str() .map(str::to_string) - .ok_or_else(|| "routing.session-affinity-ttl 必须是字符串".to_string()) + .ok_or_else(|| "routing.session-affinity-ttl must be a string".to_string()) }) .transpose()? .unwrap_or_default(); @@ -1478,7 +1478,7 @@ pub(crate) fn core_config_settings_from_value( .map(|value| { value .as_bool() - .ok_or_else(|| "disable-cooling 必须是布尔值".to_string()) + .ok_or_else(|| "disable-cooling must be a boolean".to_string()) }) .transpose()? .unwrap_or(DEFAULT_DISABLE_COOLING); @@ -1487,7 +1487,7 @@ pub(crate) fn core_config_settings_from_value( value .as_u64() .and_then(|value| u32::try_from(value).ok()) - .ok_or_else(|| "request-retry 必须是非负整数".to_string()) + .ok_or_else(|| "request-retry must be a non-negative integer".to_string()) }) .transpose()? .unwrap_or(DEFAULT_REQUEST_RETRY); @@ -1496,7 +1496,7 @@ pub(crate) fn core_config_settings_from_value( value .as_u64() .and_then(|value| u32::try_from(value).ok()) - .ok_or_else(|| "max-retry-credentials 必须是非负整数".to_string()) + .ok_or_else(|| "max-retry-credentials must be a non-negative integer".to_string()) }) .transpose()? .unwrap_or(DEFAULT_MAX_RETRY_CREDENTIALS); @@ -1505,7 +1505,7 @@ pub(crate) fn core_config_settings_from_value( value .as_u64() .and_then(|value| u32::try_from(value).ok()) - .ok_or_else(|| "max-retry-interval 必须是非负整数".to_string()) + .ok_or_else(|| "max-retry-interval must be a non-negative integer".to_string()) }) .transpose()? .unwrap_or(DEFAULT_MAX_RETRY_INTERVAL); @@ -1514,7 +1514,7 @@ pub(crate) fn core_config_settings_from_value( value .as_u64() .and_then(|value| u32::try_from(value).ok()) - .ok_or_else(|| "streaming.bootstrap-retries 必须是非负整数".to_string()) + .ok_or_else(|| "streaming.bootstrap-retries must be a non-negative integer".to_string()) }) .transpose()? .unwrap_or(DEFAULT_STREAMING_BOOTSTRAP_RETRIES); @@ -1577,7 +1577,7 @@ pub(crate) fn extract_api_key_sequence( let sequence = value .as_sequence() - .ok_or_else(|| format!("{field_name} 必须是数组"))?; + .ok_or_else(|| format!("{field_name} must be an array"))?; sequence .iter() .filter_map(extract_api_key_value) @@ -1602,7 +1602,7 @@ pub(crate) fn extract_api_key_value(value: &serde_norway::Value) -> Option Result<(), String> { let root = document .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; root.insert( yaml_key("api-keys"), serde_norway::Value::Sequence( @@ -1675,18 +1675,18 @@ where T: Into, { if path.len() != 2 { - return Err("内核配置路径无效".to_string()); + return Err("Invalid kernel configuration path".to_string()); } let root = document .as_mapping_mut() - .ok_or_else(|| "内核配置顶层必须是 YAML 映射".to_string())?; + .ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?; let section = root .entry(yaml_key(path[0])) .or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new())); let section = section .as_mapping_mut() - .ok_or_else(|| format!("{} 必须是 YAML 映射", path[0]))?; + .ok_or_else(|| format!("{} must be a YAML mapping", path[0]))?; section.insert(yaml_key(path[1]), value.into()); Ok(()) } diff --git a/src-tauri/src/core_runtime.rs b/src-tauri/src/core_runtime.rs index b038c73..a9ef035 100644 --- a/src-tauri/src/core_runtime.rs +++ b/src-tauri/src/core_runtime.rs @@ -8,7 +8,7 @@ pub(crate) fn lock_core_operation( ) -> Result, String> { let guard = CORE_OPERATION_LOCK .try_lock() - .map_err(|_| "内核正在执行其他操作,请稍后重试".to_string())?; + .map_err(|_| "The kernel is performing another operation. Try again later".to_string())?; process_state.ensure_active()?; Ok(guard) } @@ -71,7 +71,7 @@ static CORE_PROCESS_SPAWNER: LazyLock Result { let mut command = command; command .spawn() - .map_err(|error| format!("启动 CPA 内核失败: {error}"))? + .map_err(|error| format!("Failed to start CPA kernel: {error}"))? }; #[cfg(windows)] @@ -95,7 +95,7 @@ pub(crate) fn spawn_core_child(command: Command) -> Result { return match terminate_child(&mut child) { Ok(()) => Err(error), Err(cleanup_error) => Err(format!( - "{error};清理未托管的内核进程也失败: {cleanup_error}" + "{error}; failed to clean up unmanaged kernel process: {cleanup_error}" )), }; } @@ -113,11 +113,11 @@ pub(crate) fn spawn_core_child_on_lifetime_thread(command: Command) -> Result(); let gui_config_state = app.state::(); let (info, archive_path) = bundled_core_archive()? - .ok_or_else(|| "当前发行包没有匹配此系统架构的内置内核".to_string())?; + .ok_or_else(|| "This distribution has no bundled kernel matching the system architecture".to_string())?; let token = CancellationToken::new(); state.start(token, Some(info.version.clone()))?; let result = install_core_with_runtime_restore( @@ -201,7 +201,7 @@ pub(crate) async fn install_bundled_core( result }) .await - .map_err(|error| format!("离线内核安装后台任务失败: {error}"))? + .map_err(|error| format!("Offline kernel installation background task failed: {error}"))? } pub(crate) fn core_needs_bundled_install(install_dir: &Path, bundled_version: &str) -> bool { @@ -238,7 +238,7 @@ pub(crate) fn mark_bundled_core_version_handled( &install_dir.join(BUNDLED_CORE_HANDLED_VERSION_FILE), bundled_version.as_bytes(), ) - .map_err(|error| format!("记录已处理的内置内核版本失败: {error}")) + .map_err(|error| format!("Failed to record processed bundled kernel version: {error}")) } pub(crate) fn remember_bundled_core_when_up_to_date( @@ -267,7 +267,7 @@ pub(crate) fn auto_install_bundled_core_if_needed(app: &tauri::AppHandle) -> Res let (info, archive_path) = match bundled_core_archive()? { Some(bundled) => bundled, None if find_core_binary(&install_dir).is_some() => return Ok(false), - None => return Err("未检测到 CPA 内核,且当前发行包没有匹配的离线内核".to_string()), + None => return Err("CPA kernel was not detected, and this distribution has no matching offline kernel".to_string()), }; if !core_needs_bundled_install(&install_dir, &info.version) { remember_bundled_core_when_up_to_date(&install_dir, &info.version)?; @@ -276,7 +276,7 @@ pub(crate) fn auto_install_bundled_core_if_needed(app: &tauri::AppHandle) -> Res let window = app .get_webview_window("main") .map(|webview| webview.as_ref().window()) - .ok_or_else(|| "无法获取主窗口,不能自动安装离线内核".to_string())?; + .ok_or_else(|| "Cannot access the main window to automatically install the offline kernel".to_string())?; let state = app.state::(); state.start(CancellationToken::new(), Some(info.version.clone()))?; let result = install_core_with_runtime_restore( @@ -351,7 +351,7 @@ pub(crate) async fn install_core_version( result }) .await - .map_err(|error| format!("内核安装后台任务失败: {error}"))? + .map_err(|error| format!("Kernel installation background task failed: {error}"))? } fn install_core_with_runtime_restore( @@ -415,11 +415,11 @@ pub(crate) fn combine_install_and_restart_results( match (install_result, restart_result) { (Ok(result), Ok(())) => Ok(result), (Ok(_), Err(restart_error)) => { - Err(format!("内核已安装,但自动恢复运行失败: {restart_error}")) + Err(format!("Kernel installed, but failed to automatically resume operation: {restart_error}")) } (Err(install_error), Ok(())) => Err(install_error), (Err(install_error), Err(restart_error)) => Err(format!( - "{install_error};自动恢复原内核运行状态也失败: {restart_error}" + "{install_error}; failed to automatically restore the previous kernel running state: {restart_error}" )), } } @@ -498,7 +498,7 @@ pub(crate) async fn install_core_version_inner( ) -> Result { let platform = current_core_platform()?; let client = http_client(proxy_url, &custom_mirrors)?; - state.progress(window, "检查版本", 0, None, true); + state.progress(window, "Check version", 0, None, true); let requested_source = download_source.clone(); let (release, resolved_source) = fetch_release_cancelable( &client, @@ -523,7 +523,7 @@ pub(crate) async fn install_core_version_inner( let download_dir = base_dir.join("cpa-core.download"); if current_core_status(None, None)?.running { - return Err("CPA 内核正在运行,请先停止后再安装或更新".to_string()); + return Err("CPA kernel is running. Stop it before installing or updating".to_string()); } reset_dir(&staging_dir)?; @@ -532,7 +532,7 @@ pub(crate) async fn install_core_version_inner( let archive_file_name = Path::new(&asset.name) .file_name() .and_then(|file_name| file_name.to_str()) - .ok_or_else(|| format!("非法 asset 文件名: {}", asset.name))?; + .ok_or_else(|| format!("Invalid asset filename: {}", asset.name))?; let archive_path = download_dir.join(archive_file_name); let (downloaded, successful_source) = download_asset( @@ -556,7 +556,7 @@ pub(crate) async fn install_core_version_inner( ensure_not_cancelled(&token, Some(&archive_path))?; state.progress( window, - "解压中", + "Extracting", downloaded.size, Some(downloaded.size), false, @@ -564,15 +564,15 @@ pub(crate) async fn install_core_version_inner( match platform.archive_kind.as_str() { "tar.gz" => extract_tar_gz(&archive_path, &staging_dir)?, "zip" => extract_zip(&archive_path, &staging_dir)?, - other => return Err(format!("不支持的压缩包类型: {other}")), + other => return Err(format!("Unsupported archive type: {other}")), } ensure_not_cancelled(&token, Some(&archive_path))?; let binary_path = find_core_binary(&staging_dir) - .ok_or_else(|| "解压后未找到 CPA 内核二进制文件".to_string())?; + .ok_or_else(|| "CPA kernel binary was not found after extraction".to_string())?; let binary_relative_path = binary_path .strip_prefix(&staging_dir) - .map_err(|err| format!("计算内核二进制相对路径失败: {err}"))? + .map_err(|err| format!("Failed to calculate relative path of kernel binary: {err}"))? .to_path_buf(); migrate_core_config_for_update(&install_dir, &staging_dir)?; preserve_bundled_core_assets(&install_dir, &staging_dir)?; @@ -608,18 +608,18 @@ pub(crate) fn install_bundled_core_inner( let staging_dir = base_dir.join("cpa-core.staging"); if current_core_status(None, None)?.running { - return Err("CPA 内核正在运行,请先停止后再使用内置内核".to_string()); + return Err("CPA kernel is running. Stop it before using the bundled kernel".to_string()); } let archive_size = fs::metadata(archive_path) - .map_err(|error| format!("读取内置内核压缩包失败: {error}"))? + .map_err(|error| format!("Failed to read bundled kernel archive: {error}"))? .len(); - state.progress(window, "校验内置内核", 0, Some(archive_size), false); + state.progress(window, "Verify bundled kernel", 0, Some(archive_size), false); validate_bundled_core_checksum(archive_path)?; reset_dir(&staging_dir)?; state.progress( window, - "解压内置内核", + "Extract bundled kernel", archive_size, Some(archive_size), false, @@ -627,14 +627,14 @@ pub(crate) fn install_bundled_core_inner( match platform.archive_kind.as_str() { "tar.gz" => extract_tar_gz(archive_path, &staging_dir)?, "zip" => extract_zip(archive_path, &staging_dir)?, - other => return Err(format!("不支持的内置压缩包类型: {other}")), + other => return Err(format!("Unsupported bundled archive type: {other}")), } let binary_path = find_core_binary(&staging_dir) - .ok_or_else(|| "内置压缩包中没有 CPA 内核二进制文件".to_string())?; + .ok_or_else(|| "Bundled archive does not contain a CPA kernel binary".to_string())?; let binary_relative_path = binary_path .strip_prefix(&staging_dir) - .map_err(|error| format!("计算内置内核二进制路径失败: {error}"))? + .map_err(|error| format!("Failed to calculate bundled kernel binary path: {error}"))? .to_path_buf(); migrate_core_config_for_update(&install_dir, &staging_dir)?; preserve_bundled_core_assets(&install_dir, &staging_dir)?; @@ -697,7 +697,7 @@ pub(crate) async fn fetch_release( Err(error) => failures.push(format!("{}: {error}", candidate.display_name())), } } - Err(format!("所有内核版本检测源均失败: {}", failures.join(";"))) + Err(format!("All kernel version check sources failed: {}", failures.join(";"))) } pub(crate) async fn fetch_release_from_github( @@ -710,7 +710,7 @@ pub(crate) async fn fetch_release_from_github( Err(atom_error) => fetch_release_from_page(client, source) .await .map_err(|page_error| { - format!("GitHub 发布源请求失败: {atom_error};release 页面请求失败: {page_error}") + format!("GitHub release source request failed: {atom_error}; release page request failed: {page_error}") }), } } @@ -726,12 +726,12 @@ pub(crate) async fn fetch_release_from_gitcode( .header(reqwest::header::USER_AGENT, USER_AGENT) .send() .await - .map_err(|error| format!("查询 GitCode 最新内核发行版失败: {error}"))? + .map_err(|error| format!("Failed to query latest GitCode kernel release: {error}"))? .error_for_status() - .map_err(|error| format!("读取 GitCode 最新内核发行版失败: {error}"))? + .map_err(|error| format!("Failed to read latest GitCode kernel release: {error}"))? .json::() .await - .map_err(|error| format!("解析 GitCode 最新内核发行版失败: {error}"))?; + .map_err(|error| format!("Failed to parse latest GitCode kernel release: {error}"))?; validate_release_tag(&release.tag_name)?; Ok(release_from_gitcode_tag(&release.tag_name, repository)) } @@ -747,19 +747,19 @@ pub(crate) async fn fetch_release_from_page( .header(reqwest::header::USER_AGENT, USER_AGENT) .send() .await - .map_err(|err| format!("GitHub release 页面请求失败: {err}"))?; + .map_err(|err| format!("GitHub release page request failed: {err}"))?; let status = response.status(); let final_url = response.url().clone(); if !status.is_success() { let body = response .text() .await - .map_err(|err| format!("读取 GitHub release 页面失败: {err}"))?; + .map_err(|err| format!("Failed to read GitHub release page: {err}"))?; return Err(format_github_error(status.as_u16(), &body)); } let tag = release_tag_from_url(&final_url) - .ok_or_else(|| "GitHub release 页面没有返回版本标签".to_string())?; + .ok_or_else(|| "GitHub release page did not return a version tag".to_string())?; Ok(release_from_tag_for_repositories( &tag, configured_gitcode_core_repository(), @@ -781,17 +781,17 @@ pub(crate) async fn fetch_release_from_atom( .header(reqwest::header::USER_AGENT, USER_AGENT) .send() .await - .map_err(|err| format!("GitHub Atom feed 请求失败: {err}"))?; + .map_err(|err| format!("GitHub Atom feed request failed: {err}"))?; let status = response.status(); let body = response .text() .await - .map_err(|err| format!("读取 GitHub Atom feed 失败: {err}"))?; + .map_err(|err| format!("Failed to read GitHub Atom feed: {err}"))?; if !status.is_success() { return Err(format_github_error(status.as_u16(), &body)); } let tag = release_tag_from_atom(&body) - .ok_or_else(|| "GitHub Atom feed 没有返回版本标签".to_string())?; + .ok_or_else(|| "GitHub Atom feed did not return a version tag".to_string())?; Ok(release_from_tag_for_repositories( &tag, configured_gitcode_core_repository(), @@ -945,7 +945,7 @@ pub(crate) fn release_tag_from_url(url: &reqwest::Url) -> Option { pub(crate) fn is_app_update_available(current: &str, latest: &str) -> Result { let parse = |value: &str| { semver::Version::parse(value.trim().trim_start_matches('v')) - .map_err(|error| format!("无法解析版本号 {value}: {error}")) + .map_err(|error| format!("Failed to parse version {value}: {error}")) }; Ok(parse(latest)? > parse(current)?) } @@ -1008,14 +1008,14 @@ pub(crate) fn parse_release_assets(html: &str) -> Vec { pub(crate) fn format_github_error(status: u16, body: &str) -> String { if let Ok(value) = serde_json::from_str::(body) { if let Some(message) = value.get("message").and_then(|item| item.as_str()) { - return format!("GitHub 返回错误 ({status}): {}", message.trim()); + return format!("GitHub returned an error ({status}): {}", message.trim()); } } let body = body.trim(); if body.is_empty() { - format!("GitHub 返回错误 ({status})") + format!("GitHub returned an error ({status})") } else { - format!("GitHub 返回错误 ({status}): {}", truncate_for_error(body)) + format!("GitHub returned an error ({status}): {}", truncate_for_error(body)) } } @@ -1028,7 +1028,7 @@ pub(crate) async fn fetch_release_cancelable( ) -> Result<(GithubRelease, VersionDownloadCandidate), String> { tokio::select! { result = fetch_release(client, version, source, custom_mirrors) => result, - _ = token.cancelled() => Err("已取消下载".to_string()), + _ = token.cancelled() => Err("Download canceled".to_string()), } } @@ -1042,7 +1042,7 @@ pub(crate) fn apply_configured_proxy( return Ok(builder); } let proxy = reqwest::Proxy::all(proxy_url) - .map_err(|_| "代理 URL 无效".to_string())? + .map_err(|_| "Invalid proxy URL".to_string())? .no_proxy(reqwest::NoProxy::from_string("localhost,127.0.0.1,::1")); Ok(builder.proxy(proxy)) } @@ -1069,7 +1069,7 @@ pub(crate) fn http_client( .read_timeout(Duration::from_secs(30)) .timeout(Duration::from_secs(600)), proxy_url, - "创建 HTTP 客户端失败", + "Failed to create HTTP client", ) } @@ -1084,10 +1084,10 @@ pub(crate) fn select_release_asset<'a>( .filter(|asset| asset.name == expected_name && !asset.name.contains("_no-plugin")); let asset = matches .next() - .ok_or_else(|| format!("未找到匹配当前平台的 release asset: {expected_name}"))?; + .ok_or_else(|| format!("No release asset matches the current platform: {expected_name}"))?; if matches.next().is_some() { - return Err(format!("找到多个匹配的 release asset: {expected_name}")); + return Err(format!("Multiple release assets match: {expected_name}")); } Ok(asset) @@ -1117,7 +1117,7 @@ pub(crate) async fn download_asset( if index > 0 { state.progress( window, - &format!("下载失败,正在切换到 {}", candidate.display_name()), + &format!("Download failed; switching to {}", candidate.display_name()), 0, asset.size, true, @@ -1148,9 +1148,9 @@ pub(crate) async fn download_asset( } if failures.is_empty() { let _ = fs::remove_file(archive_path); - return Err("内核发行版没有可用的下载地址".to_string()); + return Err("Kernel release has no available download URL".to_string()); } - Err(format!("所有内核下载源均失败: {}", failures.join(";"))) + Err(format!("All kernel download sources failed: {}", failures.join(";"))) } pub(crate) fn core_download_source_name(url: &str) -> String { @@ -1182,7 +1182,7 @@ pub(crate) async fn download_asset_inner( state: &CoreDownloadState, token: &CancellationToken, ) -> Result { - state.progress(window, "准备下载", 0, expected_total, true); + state.progress(window, "Preparing download", 0, expected_total, true); ensure_not_cancelled(token, Some(archive_path))?; let request = client @@ -1190,38 +1190,38 @@ pub(crate) async fn download_asset_inner( .header(reqwest::header::USER_AGENT, USER_AGENT) .send(); let response = tokio::select! { - response = request => response.map_err(|err| format!("下载内核压缩包失败: {err}"))?, - _ = token.cancelled() => return Err("已取消下载".to_string()), + response = request => response.map_err(|err| format!("Failed to download kernel archive: {err}"))?, + _ = token.cancelled() => return Err("Download canceled".to_string()), } .error_for_status() - .map_err(|err| format!("下载地址返回错误状态: {err}"))?; + .map_err(|err| format!("Download URL returned an error status: {err}"))?; let total = expected_total.or_else(|| response.content_length()); let mut stream = response.bytes_stream(); let mut file = - File::create(archive_path).map_err(|err| format!("创建内核压缩包失败: {err}"))?; + File::create(archive_path).map_err(|err| format!("Failed to create kernel archive: {err}"))?; let mut downloaded = 0_u64; let mut hasher = Sha256::new(); let mut progress = crate::progress::ProgressThrottle::default(); while let Some(chunk) = tokio::select! { chunk = stream.next() => chunk, - _ = token.cancelled() => return Err("已取消下载".to_string()), + _ = token.cancelled() => return Err("Download canceled".to_string()), } { ensure_not_cancelled(token, Some(archive_path))?; - let chunk = chunk.map_err(|err| format!("读取下载数据失败: {err}"))?; + let chunk = chunk.map_err(|err| format!("Failed to read download data: {err}"))?; file.write_all(&chunk) - .map_err(|err| format!("保存下载数据失败: {err}"))?; + .map_err(|err| format!("Failed to save download data: {err}"))?; hasher.update(&chunk); downloaded += chunk.len() as u64; if progress.ready(Instant::now(), total == Some(downloaded)) { - state.progress(window, "下载中", downloaded, total, true); + state.progress(window, "Downloading", downloaded, total, true); } } - state.progress(window, "下载中", downloaded, total, true); + state.progress(window, "Downloading", downloaded, total, true); file.flush() - .map_err(|err| format!("刷新内核压缩包失败: {err}"))?; + .map_err(|err| format!("Failed to flush kernel archive: {err}"))?; ensure_not_cancelled(token, Some(archive_path))?; let sha256 = format!("{:x}", hasher.finalize()); @@ -1242,7 +1242,7 @@ pub(crate) fn ensure_not_cancelled( let _ = fs::remove_file(archive_path); } - return Err("已取消下载".to_string()); + return Err("Download canceled".to_string()); } Ok(()) @@ -1256,13 +1256,13 @@ pub(crate) fn current_core_platform() -> Result { "linux" => ("linux", "tar.gz"), "macos" => ("darwin", "tar.gz"), "windows" => ("windows", "zip"), - other => return Err(format!("不支持的操作系统: {other}")), + other => return Err(format!("Unsupported operating system: {other}")), }; let asset_arch = match arch { "x86_64" => "amd64", "aarch64" => "aarch64", - other => return Err(format!("不支持的 CPU 架构: {other}")), + other => return Err(format!("Unsupported CPU architecture: {other}")), }; Ok(CorePlatform { @@ -1300,13 +1300,13 @@ pub(crate) fn current_core_status( let current_version = read_core_metadata(&install_dir).map(|metadata| metadata.version); let message = if starting { - "CPA 内核正在启动".to_string() + "CPA kernel is starting".to_string() } else if !installed { - "未安装 CPA 内核,请先安装最新版".to_string() + "CPA kernel is not installed. Install the latest version first".to_string() } else if running { - "CPA 内核正在运行".to_string() + "CPA kernel is running".to_string() } else { - "CPA 内核已安装,当前未运行".to_string() + "CPA kernel is installed but not running".to_string() }; Ok(CoreStatus { @@ -1377,12 +1377,12 @@ impl CoreStartupFailure { impl std::fmt::Display for CoreStartupFailure { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::ShuttingDown => formatter.write_str("应用正在退出,已取消内核启动"), - Self::Exited(status) => write!(formatter, "CPA 内核启动后立即退出: {status}"), + Self::ShuttingDown => formatter.write_str("Application is exiting; kernel startup canceled"), + Self::Exited(status) => write!(formatter, "CPA kernel exited immediately after startup: {status}"), Self::Spawn(error) => formatter.write_str(error), - Self::StatusCheck(error) => write!(formatter, "检查 CPA 内核启动状态失败: {error}"), + Self::StatusCheck(error) => write!(formatter, "Failed to check CPA kernel startup status: {error}"), Self::TimedOut(port) => { - write!(formatter, "CPA 内核启动超时:10 秒内未监听管理端口 {port}") + write!(formatter, "CPA kernel startup timed out: management port {port} was not listening within 10 seconds") } } } @@ -1402,7 +1402,7 @@ pub(crate) fn core_start_stdio(log_path: &Path) -> io::Result<(Stdio, Stdio)> { .create(true) .truncate(true) .open(log_path)?; - writeln!(header_file, "===== CPA 内核启动 {} =====", unix_now())?; + writeln!(header_file, "===== CPA kernel startup {} =====", unix_now())?; drop(header_file); let stdout_file = File::options().append(true).open(log_path)?; @@ -1414,10 +1414,10 @@ pub(crate) fn core_start_stdio(log_path: &Path) -> io::Result<(Stdio, Stdio)> { fn core_start_error_with_log(error: &str, log_path: &Path, log_error: Option<&str>) -> String { match log_error { Some(log_error) => format!( - "{error};无法写入启动日志 {}: {log_error}", + "{error}; failed to write startup log {}: {log_error}", path_to_string(log_path) ), - None => format!("{error};启动日志: {}", path_to_string(log_path)), + None => format!("{error}; startup log: {}", path_to_string(log_path)), } } @@ -1512,22 +1512,22 @@ pub(crate) fn start_core_process_inner( if !gui_config.auth_dir.trim().is_empty() { let auth_dir = auth_dir_path_for_core(&gui_config.auth_dir, &install_dir); fs::create_dir_all(&auth_dir) - .map_err(|error| format!("创建凭证目录失败 {}: {error}", path_to_string(&auth_dir)))?; + .map_err(|error| format!("Failed to create credentials directory {}: {error}", path_to_string(&auth_dir)))?; } let binary_path = find_core_binary(&install_dir) - .ok_or_else(|| "未安装 CPA 内核,请先安装最新版".to_string())?; + .ok_or_else(|| "CPA kernel is not installed. Install the latest version first".to_string())?; let existing_process_ids = find_core_process_ids(&binary_path); if process_state.managed_pid().is_some() || !existing_process_ids.is_empty() { if !existing_process_ids.is_empty() { process_state.adopt_process_ids(&binary_path, existing_process_ids)?; } - return Err("CPA 内核已经在运行".to_string()); + return Err("CPA kernel is already running".to_string()); } let management_address = core_management_address(&gui_config.host, gui_config.port)?; if TcpStream::connect_timeout(&management_address, Duration::from_millis(250)).is_ok() { return Err(format!( - "端口 {} 已被其他程序占用,请更换端口后重试", + "Port {} is in use by another program. Change the port and try again", gui_config.port )); } @@ -1553,7 +1553,7 @@ pub(crate) fn start_core_process_inner( if let Err(heal_error) = rematerialize_core_binary(&binary_path) { return Err(failure.message_with_detail( &log_path, - Some(&format!("自动修复 CPA 内核文件失败: {heal_error}")), + Some(&format!("Failed to automatically repair CPA kernel files: {heal_error}")), )); } match start_once() { @@ -1561,7 +1561,7 @@ pub(crate) fn start_core_process_inner( Err(failure) if failure.was_killed_by_sigkill() => { return Err(failure.message_with_detail( &log_path, - Some("系统再次终止 CPA 内核,请重新安装内核后重试"), + Some("The system terminated the CPA kernel again. Reinstall the kernel and try again"), )); } Err(failure) => return Err(failure.message(&log_path)), @@ -1705,7 +1705,7 @@ pub(crate) fn stop_core_process_inner(process_state: &CoreProcessState) -> Resul } else if stopped_any { Ok(()) } else { - Err("CPA 内核当前未运行".to_string()) + Err("CPA kernel is not currently running".to_string()) } } @@ -1714,11 +1714,11 @@ pub(crate) fn core_install_dir() -> Result { } pub(crate) fn executable_dir() -> Result { - let exe_path = env::current_exe().map_err(|err| format!("读取当前程序路径失败: {err}"))?; + let exe_path = env::current_exe().map_err(|err| format!("Failed to read current executable path: {err}"))?; exe_path .parent() .map(|path| path.to_path_buf()) - .ok_or_else(|| format!("当前程序路径没有父目录: {}", path_to_string(&exe_path))) + .ok_or_else(|| format!("Current executable path has no parent directory: {}", path_to_string(&exe_path))) } pub(crate) fn macos_app_resources_dir(executable_dir: &Path) -> Option { @@ -1742,7 +1742,7 @@ pub(crate) fn core_base_dir() -> Result { if macos_app_resources_dir(&executable_dir).is_some() { let home_dir = env::var_os("HOME") .map(PathBuf::from) - .ok_or_else(|| "无法确定 macOS 用户目录".to_string())?; + .ok_or_else(|| "Unable to determine macOS user directory".to_string())?; return Ok(home_dir .join("Library") .join("Application Support") @@ -1794,7 +1794,7 @@ pub(crate) fn bundled_core_archive() -> Result Result Result 1 { return Err(format!( - "发现多个匹配当前平台的内置内核,请在 {} 中指定发行版本", + "Multiple bundled kernels match the current platform. Specify a release version in {}", CORE_VERSION_FILE )); } @@ -1853,7 +1853,7 @@ pub(crate) fn bundled_core_archive() -> Result Result<(), String> { let archive_name = archive_path .file_name() - .ok_or_else(|| "内置内核压缩包文件名无效".to_string())?; + .ok_or_else(|| "Invalid bundled kernel archive filename".to_string())?; fs::copy(archive_path, target_dir.join(archive_name)) - .map_err(|error| format!("保留所选内置内核压缩包失败: {error}"))?; + .map_err(|error| format!("Failed to preserve selected bundled kernel archive: {error}"))?; if let Some(source_dir) = archive_path.parent() { let checksums = source_dir.join(CORE_CHECKSUMS_FILE); if checksums.is_file() { fs::copy(&checksums, target_dir.join(CORE_CHECKSUMS_FILE)) - .map_err(|error| format!("保留内置内核校验文件失败: {error}"))?; + .map_err(|error| format!("Failed to preserve bundled kernel checksum file: {error}"))?; } } Ok(()) @@ -1932,7 +1932,7 @@ pub(crate) fn migrate_core_config_for_update( Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()), Err(error) => { return Err(format!( - "读取旧版内核配置失败,为避免配置丢失已取消更新 {}: {error}", + "Failed to read legacy kernel configuration; update canceled to prevent configuration loss {}: {error}", path_to_string(&old_config_path) )); } @@ -1941,13 +1941,13 @@ pub(crate) fn migrate_core_config_for_update( let template_path = target_dir.join(CORE_EXAMPLE_CONFIG_FILE); if !template_path.is_file() { return Err(format!( - "新版内核缺少配置模板,已取消更新: {}", + "New kernel is missing a configuration template; update canceled: {}", path_to_string(&template_path) )); } let template = fs::read_to_string(&template_path).map_err(|error| { format!( - "读取新版内核配置模板失败 {}: {error}", + "Failed to read new kernel configuration template {}: {error}", path_to_string(&template_path) ) })?; @@ -1955,7 +1955,7 @@ pub(crate) fn migrate_core_config_for_update( let config_path = target_dir.join(CORE_CONFIG_FILE); fs::write(&config_path, migrated).map_err(|error| { format!( - "写入迁移后的新版内核配置失败 {}: {error}", + "Failed to write migrated new kernel configuration {}: {error}", path_to_string(&config_path) ) })?; @@ -1964,7 +1964,7 @@ pub(crate) fn migrate_core_config_for_update( pub(crate) fn validate_bundled_core_checksum(archive_path: &Path) -> Result<(), String> { let Some(directory) = archive_path.parent() else { - return Err("内置内核压缩包没有父目录".to_string()); + return Err("Bundled kernel archive has no parent directory".to_string()); }; let checksums_path = directory.join(CORE_CHECKSUMS_FILE); if !checksums_path.is_file() { @@ -1973,9 +1973,9 @@ pub(crate) fn validate_bundled_core_checksum(archive_path: &Path) -> Result<(), let archive_name = archive_path .file_name() .and_then(|value| value.to_str()) - .ok_or_else(|| "内置内核压缩包文件名无效".to_string())?; + .ok_or_else(|| "Invalid bundled kernel archive filename".to_string())?; let checksums = fs::read_to_string(&checksums_path) - .map_err(|error| format!("读取内置内核校验文件失败: {error}"))?; + .map_err(|error| format!("Failed to read bundled kernel checksum file: {error}"))?; let expected = checksums.lines().find_map(|line| { let mut fields = line.split_whitespace(); let digest = fields.next()?; @@ -1987,19 +1987,19 @@ pub(crate) fn validate_bundled_core_checksum(archive_path: &Path) -> Result<(), }; let actual = sha256_file(archive_path)?; if actual != expected { - return Err("内置内核压缩包 SHA-256 校验失败".to_string()); + return Err("Bundled kernel archive SHA-256 verification failed".to_string()); } Ok(()) } pub(crate) fn sha256_file(path: &Path) -> Result { - let mut file = File::open(path).map_err(|error| format!("打开校验文件失败: {error}"))?; + let mut file = File::open(path).map_err(|error| format!("Failed to open checksum file: {error}"))?; let mut hasher = Sha256::new(); let mut buffer = [0_u8; 64 * 1024]; loop { let read = file .read(&mut buffer) - .map_err(|error| format!("读取校验文件失败: {error}"))?; + .map_err(|error| format!("Failed to read checksum file: {error}"))?; if read == 0 { break; } @@ -2020,8 +2020,8 @@ pub(crate) fn write_core_metadata( ) -> Result<(), String> { let metadata_path = install_dir.join(CORE_METADATA_FILE); let content = serde_json::to_string_pretty(metadata) - .map_err(|err| format!("生成内核元数据失败: {err}"))?; - fs::write(metadata_path, content).map_err(|err| format!("写入内核元数据失败: {err}")) + .map_err(|err| format!("Failed to generate kernel metadata: {err}"))?; + fs::write(metadata_path, content).map_err(|err| format!("Failed to write kernel metadata: {err}")) } pub(crate) fn validate_downloaded_asset( @@ -2045,7 +2045,7 @@ pub(crate) fn validate_download_metadata( if let Some(expected_total) = expected_total { if downloaded != expected_total { return Err(format!( - "下载大小校验失败: 实际 {downloaded} 字节,期望 {expected_total} 字节" + "Download size verification failed: got {downloaded} bytes, expected {expected_total} bytes" )); } } @@ -2057,7 +2057,7 @@ pub(crate) fn validate_download_metadata( .to_ascii_lowercase(); if !expected.is_empty() && sha256 != expected { - return Err("下载文件 SHA-256 校验失败".to_string()); + return Err("Downloaded file SHA-256 verification failed".to_string()); } } @@ -2072,7 +2072,7 @@ pub(crate) fn cleanup_core_work_dirs() -> Result<(), String> { let path = base_dir.join(name); if path.exists() { if let Err(err) = fs::remove_dir_all(&path) { - last_error = Some(format!("清理临时目录失败 {}: {err}", path_to_string(&path))); + last_error = Some(format!("Failed to clean up temporary directory {}: {err}", path_to_string(&path))); } } } @@ -2282,11 +2282,11 @@ pub(crate) fn shutdown_managed_core( .is_some_and(|binary_path| is_core_running(&binary_path)); if was_running { if let Err(error) = stop_core_process_inner(process_state) { - eprintln!("退出时关闭 CPA 内核失败: {error}"); + eprintln!("Failed to stop CPA kernel on exit: {error}"); } } if let Err(error) = gui_config_state.set_run_on_startup(was_running) { - eprintln!("保存退出前的内核状态失败: {error}"); + eprintln!("Failed to save kernel state before exit: {error}"); } } @@ -2306,7 +2306,7 @@ pub(crate) fn attach_child_to_windows_job(child: &Child) -> Result Result Result<(), String> { if child .try_wait() - .map_err(|error| format!("检查 CPA 内核进程状态失败: {error}"))? + .map_err(|error| format!("Failed to check CPA kernel process status: {error}"))? .is_some() { return Ok(()); @@ -2357,10 +2357,10 @@ pub(crate) fn terminate_child(child: &mut Child) -> Result<(), String> { { child .kill() - .map_err(|err| format!("关闭 CPA 内核进程失败: {err}"))?; + .map_err(|err| format!("Failed to stop CPA kernel process: {err}"))?; child .wait() - .map_err(|err| format!("等待 CPA 内核进程退出失败: {err}"))?; + .map_err(|err| format!("Failed while waiting for CPA kernel process to exit: {err}"))?; Ok(()) } @@ -2373,16 +2373,16 @@ pub(crate) fn terminate_child(child: &mut Child) -> Result<(), String> { match child.try_wait() { Ok(Some(_)) => return Ok(()), Ok(None) => thread::sleep(Duration::from_millis(100)), - Err(err) => return Err(format!("检查 CPA 内核进程状态失败: {err}")), + Err(err) => return Err(format!("Failed to check CPA kernel process status: {err}")), } } child .kill() - .map_err(|err| format!("强制关闭 CPA 内核进程失败: {err}"))?; + .map_err(|err| format!("Failed to force-stop CPA kernel process: {err}"))?; child .wait() - .map_err(|err| format!("等待 CPA 内核进程退出失败: {err}"))?; + .map_err(|err| format!("Failed while waiting for CPA kernel process to exit: {err}"))?; Ok(()) } @@ -2403,7 +2403,7 @@ pub(crate) fn terminate_process_id(process_id: u32) -> Result<(), String> { if error.raw_os_error() == Some(ERROR_INVALID_PARAMETER as i32) { return Ok(()); } - return Err(format!("打开 CPA 内核进程失败: PID {process_id}: {error}")); + return Err(format!("Failed to open CPA kernel process: PID {process_id}: {error}")); } let handle = unsafe { OwnedHandle::from_raw_handle(handle) }; let raw_handle = handle.as_raw_handle(); @@ -2419,14 +2419,14 @@ pub(crate) fn terminate_process_id(process_id: u32) -> Result<(), String> { configure_background_command(&mut command); let status = command .status() - .map_err(|err| format!("关闭 CPA 内核进程失败: {err}"))?; + .map_err(|err| format!("Failed to stop CPA kernel process: {err}"))?; let wait = unsafe { WaitForSingleObject(raw_handle, if status.success() { 10_000 } else { 0 }) }; match wait { WAIT_OBJECT_0 => Ok(()), - WAIT_TIMEOUT => Err(format!("CPA 内核进程未退出: PID {process_id}")), + WAIT_TIMEOUT => Err(format!("CPA kernel process did not exit: PID {process_id}")), _ => Err(format!( - "等待 CPA 内核进程退出失败: PID {process_id}: {}", + "Failed while waiting for CPA kernel process to exit: PID {process_id}: {}", io::Error::last_os_error() )), } @@ -2452,12 +2452,12 @@ pub(crate) fn send_process_signal(process_id: u32, signal: &str) -> Result<(), S let status = Command::new("kill") .args([format!("-{signal}"), process_id.to_string()]) .status() - .map_err(|err| format!("发送进程信号失败: {err}"))?; + .map_err(|err| format!("Failed to send process signal: {err}"))?; if status.success() { Ok(()) } else { - Err(format!("发送进程信号失败: PID {process_id}")) + Err(format!("Failed to send process signal: PID {process_id}")) } } @@ -2491,56 +2491,56 @@ pub(crate) fn is_process_alive(process_id: u32) -> bool { pub(crate) fn reset_dir(path: &Path) -> Result<(), String> { if path.exists() { fs::remove_dir_all(path) - .map_err(|err| format!("清理目录失败 {}: {err}", path_to_string(path)))?; + .map_err(|err| format!("Failed to clean up directory {}: {err}", path_to_string(path)))?; } - fs::create_dir_all(path).map_err(|err| format!("创建目录失败 {}: {err}", path_to_string(path))) + fs::create_dir_all(path).map_err(|err| format!("Failed to create directory {}: {err}", path_to_string(path))) } pub(crate) fn overlay_install_dir(install_dir: &Path, staging_dir: &Path) -> Result<(), String> { if !staging_dir.is_dir() { return Err(format!( - "内核暂存目录不存在: {}", + "Kernel staging directory does not exist: {}", path_to_string(staging_dir) )); } if !install_dir.exists() { return fs::rename(staging_dir, install_dir) - .map_err(|err| format!("安装新内核目录失败: {err}")); + .map_err(|err| format!("Failed to install new kernel directory: {err}")); } if !install_dir.is_dir() { return Err(format!( - "内核安装路径不是目录: {}", + "Kernel installation path is not a directory: {}", path_to_string(install_dir) )); } overlay_directory(staging_dir, install_dir)?; - fs::remove_dir_all(staging_dir).map_err(|err| format!("清理内核暂存目录失败: {err}")) + fs::remove_dir_all(staging_dir).map_err(|err| format!("Failed to clean up kernel staging directory: {err}")) } fn overlay_directory(source_dir: &Path, target_dir: &Path) -> Result<(), String> { for entry in fs::read_dir(source_dir) - .map_err(|err| format!("读取内核暂存目录失败 {}: {err}", path_to_string(source_dir)))? + .map_err(|err| format!("Failed to read kernel staging directory {}: {err}", path_to_string(source_dir)))? { - let entry = entry.map_err(|err| format!("读取内核暂存条目失败: {err}"))?; + let entry = entry.map_err(|err| format!("Failed to read kernel staging entry: {err}"))?; let source_path = entry.path(); let target_path = target_dir.join(entry.file_name()); let file_type = entry .file_type() - .map_err(|err| format!("读取内核暂存条目类型失败: {err}"))?; + .map_err(|err| format!("Failed to read kernel staging entry type: {err}"))?; if file_type.is_dir() { if target_path.exists() && !target_path.is_dir() { return Err(format!( - "无法用内核目录覆盖同名文件: {}", + "Cannot overwrite a same-named file with a kernel directory: {}", path_to_string(&target_path) )); } fs::create_dir_all(&target_path).map_err(|err| { format!( - "创建内核安装子目录失败 {}: {err}", + "Failed to create kernel installation subdirectory {}: {err}", path_to_string(&target_path) ) })?; @@ -2548,14 +2548,14 @@ fn overlay_directory(source_dir: &Path, target_dir: &Path) -> Result<(), String> } else if file_type.is_file() { if target_path.exists() && !target_path.is_file() { return Err(format!( - "无法用内核文件覆盖同名目录: {}", + "Cannot overwrite a same-named directory with a kernel file: {}", path_to_string(&target_path) )); } copy_core_file_replace(&source_path, &target_path)?; } else { return Err(format!( - "内核暂存目录包含不支持的条目: {}", + "Kernel staging directory contains an unsupported entry: {}", path_to_string(&source_path) )); } @@ -2570,12 +2570,12 @@ pub(crate) fn copy_core_file_replace(source_path: &Path, target_path: &Path) -> static SEQUENCE: AtomicU64 = AtomicU64::new(0); let parent = target_path.parent().ok_or_else(|| { format!( - "覆盖内核文件失败 {}: 无法确定父目录", + "Failed to overwrite kernel file {}: cannot determine parent directory", path_to_string(target_path) ) })?; fs::create_dir_all(parent) - .map_err(|error| format!("创建内核文件目录失败 {}: {error}", path_to_string(parent)))?; + .map_err(|error| format!("Failed to create kernel file directory {}: {error}", path_to_string(parent)))?; let file_name = target_path .file_name() .map(|name| name.to_string_lossy()) @@ -2597,7 +2597,7 @@ pub(crate) fn copy_core_file_replace(source_path: &Path, target_path: &Path) -> if let Err(error) = replace_result { let _ = fs::remove_file(&temporary_path); return Err(format!( - "覆盖内核文件失败 {}: {error}", + "Failed to overwrite kernel file {}: {error}", path_to_string(target_path) )); } @@ -2612,33 +2612,33 @@ pub(crate) fn rematerialize_core_binary(binary_path: &Path) -> Result<(), String pub(crate) fn extract_tar_gz(archive_path: &Path, install_dir: &Path) -> Result<(), String> { let archive_file = - File::open(archive_path).map_err(|err| format!("打开 tar.gz 失败: {err}"))?; + File::open(archive_path).map_err(|err| format!("Failed to open tar.gz: {err}"))?; let decoder = GzDecoder::new(archive_file); let mut archive = Archive::new(decoder); let entries = archive .entries() - .map_err(|err| format!("读取 tar.gz 条目失败: {err}"))?; + .map_err(|err| format!("Failed to read tar.gz entry: {err}"))?; for entry in entries { - let mut entry = entry.map_err(|err| format!("读取 tar.gz 条目失败: {err}"))?; + let mut entry = entry.map_err(|err| format!("Failed to read tar.gz entry: {err}"))?; let entry_path = entry .path() - .map_err(|err| format!("读取 tar.gz 条目路径失败: {err}"))?; + .map_err(|err| format!("Failed to read tar.gz entry path: {err}"))?; let out_path = checked_archive_path(install_dir, entry_path.as_ref())?; let entry_type = entry.header().entry_type(); if entry_type.is_dir() { - fs::create_dir_all(&out_path).map_err(|err| format!("创建目录失败: {err}"))?; + fs::create_dir_all(&out_path).map_err(|err| format!("Failed to create directory: {err}"))?; } else if entry_type.is_file() { if let Some(parent) = out_path.parent() { - fs::create_dir_all(parent).map_err(|err| format!("创建目录失败: {err}"))?; + fs::create_dir_all(parent).map_err(|err| format!("Failed to create directory: {err}"))?; } entry .unpack(&out_path) - .map_err(|err| format!("解压 tar.gz 文件失败: {err}"))?; + .map_err(|err| format!("Failed to extract tar.gz file: {err}"))?; } else { return Err(format!( - "tar.gz 包含不支持的条目类型: {}", + "tar.gz contains an unsupported entry type: {}", path_to_string(&out_path) )); } @@ -2648,40 +2648,40 @@ pub(crate) fn extract_tar_gz(archive_path: &Path, install_dir: &Path) -> Result< } pub(crate) fn extract_zip(archive_path: &Path, install_dir: &Path) -> Result<(), String> { - let archive_file = File::open(archive_path).map_err(|err| format!("打开 zip 失败: {err}"))?; + let archive_file = File::open(archive_path).map_err(|err| format!("Failed to open zip: {err}"))?; let mut archive = - ZipArchive::new(archive_file).map_err(|err| format!("读取 zip 失败: {err}"))?; + ZipArchive::new(archive_file).map_err(|err| format!("Failed to read zip: {err}"))?; for index in 0..archive.len() { let mut file = archive .by_index(index) - .map_err(|err| format!("读取 zip 条目失败: {err}"))?; + .map_err(|err| format!("Failed to read zip entry: {err}"))?; let enclosed_name = file .enclosed_name() - .ok_or_else(|| format!("zip 条目路径不安全: {}", file.name()))?; + .ok_or_else(|| format!("Unsafe zip entry path: {}", file.name()))?; let out_path = checked_archive_path(install_dir, &enclosed_name)?; if is_zip_symlink(&file) { - return Err(format!("zip 包含不支持的符号链接条目: {}", file.name())); + return Err(format!("zip contains an unsupported symbolic link entry: {}", file.name())); } if file.is_dir() { - fs::create_dir_all(&out_path).map_err(|err| format!("创建目录失败: {err}"))?; + fs::create_dir_all(&out_path).map_err(|err| format!("Failed to create directory: {err}"))?; continue; } if let Some(parent) = out_path.parent() { - fs::create_dir_all(parent).map_err(|err| format!("创建目录失败: {err}"))?; + fs::create_dir_all(parent).map_err(|err| format!("Failed to create directory: {err}"))?; } - let mut out_file = File::create(&out_path).map_err(|err| format!("创建文件失败: {err}"))?; - io::copy(&mut file, &mut out_file).map_err(|err| format!("写入文件失败: {err}"))?; + let mut out_file = File::create(&out_path).map_err(|err| format!("Failed to create file: {err}"))?; + io::copy(&mut file, &mut out_file).map_err(|err| format!("Failed to write file: {err}"))?; #[cfg(unix)] if let Some(mode) = file.unix_mode() { use std::os::unix::fs::PermissionsExt; fs::set_permissions(&out_path, fs::Permissions::from_mode(mode)) - .map_err(|err| format!("设置文件权限失败: {err}"))?; + .map_err(|err| format!("Failed to set file permissions: {err}"))?; } } @@ -2696,7 +2696,7 @@ pub(crate) fn checked_archive_path(base_dir: &Path, entry_path: &Path) -> Result ) }) { return Err(format!( - "压缩包条目路径不安全: {}", + "Unsafe archive entry path: {}", path_to_string(entry_path) )); } @@ -2761,7 +2761,7 @@ pub(crate) fn configure_initial_main_window( ) -> Result<(), String> { let window = app_handle .get_webview_window("main") - .ok_or_else(|| "主窗口不存在".to_string())?; + .ok_or_else(|| "Main window does not exist".to_string())?; #[cfg(target_os = "macos")] set_macos_dock_visible(app_handle, !start_hidden); @@ -2769,20 +2769,20 @@ pub(crate) fn configure_initial_main_window( if start_hidden { return window .hide() - .map_err(|error| format!("静默启动时隐藏主窗口失败: {error}")); + .map_err(|error| format!("Failed to hide main window during silent startup: {error}")); } window .show() - .map_err(|error| format!("显示主窗口失败: {error}"))?; + .map_err(|error| format!("Failed to show main window: {error}"))?; if window.is_minimized().unwrap_or(false) { window .unminimize() - .map_err(|error| format!("恢复主窗口失败: {error}"))?; + .map_err(|error| format!("Failed to restore main window: {error}"))?; } window .set_focus() - .map_err(|error| format!("聚焦主窗口失败: {error}")) + .map_err(|error| format!("Failed to focus main window: {error}")) } pub(crate) fn path_to_string(path: &Path) -> String { diff --git a/src-tauri/src/instance_lock.rs b/src-tauri/src/instance_lock.rs index f725a06..695e047 100644 --- a/src-tauri/src/instance_lock.rs +++ b/src-tauri/src/instance_lock.rs @@ -47,13 +47,13 @@ pub(crate) fn acquire_app_instance_guard_for( let handle = unsafe { CreateMutexW(std::ptr::null(), 0, wide_name.as_ptr()) }; if handle.is_null() { return Err(format!( - "创建当前目录的软件实例锁失败: {}", + "Failed to create application instance lock for the current directory: {}", std::io::Error::last_os_error() )); } if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS { unsafe { CloseHandle(handle) }; - return Err("当前 EasyCLIProxyAPI 目录已经有一个软件实例在运行".to_string()); + return Err("An application instance is already running in the current EasyCLIProxyAPI directory".to_string()); } Ok(AppInstanceGuard { @@ -76,15 +76,15 @@ pub(crate) fn acquire_app_instance_guard_for( .write(true) .create(true) .open(&lock_path) - .map_err(|error| format!("打开当前目录的软件实例锁失败: {error}"))?; + .map_err(|error| format!("Failed to open application instance lock for the current directory: {error}"))?; let result = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) }; if result != 0 { let error = std::io::Error::last_os_error(); let raw_error = error.raw_os_error(); if raw_error == Some(libc::EWOULDBLOCK) || raw_error == Some(libc::EAGAIN) { - return Err("当前 EasyCLIProxyAPI 目录已经有一个软件实例在运行".to_string()); + return Err("An application instance is already running in the current EasyCLIProxyAPI directory".to_string()); } - return Err(format!("锁定当前 EasyCLIProxyAPI 目录失败: {error}")); + return Err(format!("Failed to lock the current EasyCLIProxyAPI directory: {error}")); } Ok(AppInstanceGuard { _file: file }) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 36a72f9..32f0dcb 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -13,6 +13,8 @@ mod desktop_theme; mod instance_lock; mod management_api; mod network_proxy; +#[cfg(any(target_os = "macos", target_os = "windows"))] +mod native_i18n; mod oauth_browser; mod progress; mod provider_health; @@ -133,7 +135,7 @@ const OAUTH_DIR_NAME: &str = "oauth"; const DEFAULT_AUTH_DIR: &str = "../oauth"; const DEFAULT_API_KEY: &str = "123456"; const DEFAULT_AGENT_TERMINAL: &str = "auto"; -const DEFAULT_API_KEY_INITIAL_REMARK: &str = "默认密钥"; +const DEFAULT_API_KEY_INITIAL_REMARK: &str = "Default key"; const DEFAULT_REQUEST_RETRY: u32 = 3; const DEFAULT_MAX_RETRY_CREDENTIALS: u32 = 0; const DEFAULT_MAX_RETRY_INTERVAL: u32 = 30; @@ -227,9 +229,7 @@ static CONFIG_WRITE_HASHES: LazyLock &'static str { let normalized = locale.trim().to_ascii_lowercase(); - if normalized.starts_with("en") { - "en" - } else if normalized.starts_with("ja") { + if normalized.starts_with("ja") { "ja" } else if normalized == "zh-tw" || normalized == "zh-hk" @@ -237,54 +237,10 @@ fn normalize_app_locale(locale: &str) -> &'static str { || normalized.starts_with("zh-hant") { "zh-TW" - } else { + } else if normalized.starts_with("zh") { "zh-CN" - } -} - -#[cfg(any(target_os = "macos", target_os = "windows"))] -fn locale_text<'a>(locale: &str, zh_cn: &'a str, en: &'a str) -> &'a str { - match normalize_app_locale(locale) { - "en" => en, - "zh-TW" => match zh_cn { - "打开主界面" => "開啟主介面", - "退出" => "退出", - "内核状态:处理中" => "核心狀態:處理中", - "内核状态:未安装" => "核心狀態:未安裝", - "内核状态:运行中" => "核心狀態:執行中", - "内核状态:已停止" => "核心狀態:已停止", - "处理中..." => "處理中...", - "停止内核" => "停止核心", - "启动内核" => "啟動核心", - "重启内核" => "重新啟動核心", - "EasyCLIProxyAPI · 内核处理中" => "EasyCLIProxyAPI · 核心處理中", - "EasyCLIProxyAPI · 内核未安装" => "EasyCLIProxyAPI · 核心未安裝", - "EasyCLIProxyAPI · 内核运行中" => "EasyCLIProxyAPI · 核心執行中", - "EasyCLIProxyAPI · 内核已停止" => "EasyCLIProxyAPI · 核心已停止", - "EasyCLIProxyAPI · 内核操作失败" => "EasyCLIProxyAPI · 核心操作失敗", - "内核状态:正在检查" => "核心狀態:正在檢查", - _ => zh_cn, - }, - "ja" => match zh_cn { - "打开主界面" => "メイン画面を開く", - "退出" => "終了", - "内核状态:处理中" => "コア状態:処理中", - "内核状态:未安装" => "コア状態:未インストール", - "内核状态:运行中" => "コア状態:実行中", - "内核状态:已停止" => "コア状態:停止済み", - "处理中..." => "処理中...", - "停止内核" => "コアを停止", - "启动内核" => "コアを起動", - "重启内核" => "コアを再起動", - "EasyCLIProxyAPI · 内核处理中" => "EasyCLIProxyAPI · コア処理中", - "EasyCLIProxyAPI · 内核未安装" => "EasyCLIProxyAPI · コア未インストール", - "EasyCLIProxyAPI · 内核运行中" => "EasyCLIProxyAPI · コア実行中", - "EasyCLIProxyAPI · 内核已停止" => "EasyCLIProxyAPI · コア停止済み", - "EasyCLIProxyAPI · 内核操作失败" => "EasyCLIProxyAPI · コア操作失敗", - "内核状态:正在检查" => "コア状態:確認中", - _ => zh_cn, - }, - _ => zh_cn, + } else { + "en" } } @@ -389,7 +345,7 @@ impl AgentConfigStatusCache { .filter(|entry| entry.port == port && entry.api_key_sha256 == api_key_sha256) .map(|entry| entry.statuses.clone()) }) - .map_err(|_| "智能体配置状态缓存锁已损坏".to_string()) + .map_err(|_| "Agent configuration status cache lock is poisoned".to_string()) } fn replace( @@ -401,7 +357,7 @@ impl AgentConfigStatusCache { let mut current = self .entry .lock() - .map_err(|_| "智能体配置状态缓存锁已损坏".to_string())?; + .map_err(|_| "Agent configuration status cache lock is poisoned".to_string())?; *current = Some(AgentConfigStatusCacheEntry { port, api_key_sha256: sha256_bytes(api_key.as_bytes()), @@ -414,7 +370,7 @@ impl AgentConfigStatusCache { let mut current = self .entry .lock() - .map_err(|_| "智能体配置状态缓存锁已损坏".to_string())?; + .map_err(|_| "Agent configuration status cache lock is poisoned".to_string())?; *current = None; Ok(()) } @@ -698,7 +654,7 @@ impl VersionDownloadSource { Self::Gitcode => "GitCode", Self::GhProxy => "gh-proxy.com", Self::GhFast => "ghfast.top", - Self::Custom => "自定义镜像", + Self::Custom => "Custom mirror", } } @@ -794,7 +750,7 @@ fn version_download_source_candidates( fn normalize_custom_download_mirror_url(value: &str) -> Result { let mut url = - reqwest::Url::parse(value.trim()).map_err(|error| format!("镜像地址无效: {error}"))?; + reqwest::Url::parse(value.trim()).map_err(|error| format!("Invalid mirror URL: {error}"))?; if url.scheme() != "https" || url.host_str().is_none() || url.port().is_some() @@ -803,7 +759,7 @@ fn normalize_custom_download_mirror_url(value: &str) -> Result { || url.query().is_some() || url.fragment().is_some() { - return Err("镜像地址必须是无认证、端口、查询参数和片段的 HTTPS 地址".to_string()); + return Err("Mirror URL must be an HTTPS URL without authentication, port, query parameters, or fragment".to_string()); } if !url.path().ends_with('/') { let path = format!("{}/", url.path()); @@ -924,7 +880,7 @@ where impl Default for GuiConfigFile { fn default() -> Self { Self { - locale: "zh-CN".to_string(), + locale: "en".to_string(), port: 8317, allow_lan: false, host: "127.0.0.1".to_string(), @@ -1389,7 +1345,7 @@ impl AgentClient { "antigravity-cli" => Ok(Self::AntigravityCli), "kimi-code" => Ok(Self::KimiCode), "grok-build" => Ok(Self::GrokBuild), - _ => Err(format!("不支持的智能体客户端: {value}")), + _ => Err(format!("Unsupported agent client: {value}")), } } @@ -1629,7 +1585,7 @@ impl Default for CoreInstallTask { Self { running: false, cancellable: false, - phase: "空闲".to_string(), + phase: "Idle".to_string(), downloaded: 0, total: None, percent: None, @@ -1657,10 +1613,10 @@ impl CoreDownloadState { let mut inner = self .inner .lock() - .map_err(|_| "内核安装状态锁已损坏".to_string())?; + .map_err(|_| "Kernel installation status lock is poisoned".to_string())?; if inner.running { - return Err("已有内核安装任务正在运行".to_string()); + return Err("A kernel installation task is already running".to_string()); } inner.running = true; @@ -1669,8 +1625,8 @@ impl CoreDownloadState { running: true, cancellable: true, phase: version - .map(|version| format!("准备安装 {version}")) - .unwrap_or_else(|| "准备安装最新版".to_string()), + .map(|version| format!("Preparing to install {version}")) + .unwrap_or_else(|| "Preparing to install the latest version".to_string()), downloaded: 0, total: None, percent: None, @@ -1738,18 +1694,18 @@ impl CoreDownloadState { match result { Ok(result) => { - inner.task.phase = "安装完成".to_string(); + inner.task.phase = "Installation complete".to_string(); inner.task.downloaded = 1; inner.task.total = Some(1); inner.task.percent = Some(100.0); - inner.task.message = Some(format!("{} 安装完成", result.version)); + inner.task.message = Some(format!("{} installation complete", result.version)); inner.task.result = Some(result); } Err(error) => { - inner.task.phase = if error.contains("取消") { - "已取消".to_string() + inner.task.phase = if error.to_ascii_lowercase().contains("cancel") { + "Canceled".to_string() } else { - "安装失败".to_string() + "Installation failed".to_string() }; inner.task.message = Some(error); inner.task.result = None; @@ -1783,9 +1739,9 @@ impl AppUpdateState { let mut inner = self .inner .lock() - .map_err(|_| "应用更新状态锁已损坏".to_string())?; + .map_err(|_| "Application update status lock is poisoned".to_string())?; if inner.task.running { - return Err("已有应用更新任务正在运行".to_string()); + return Err("An application update task is already running".to_string()); } inner.token = Some(token); inner.task = AppUpdateTask { @@ -1801,16 +1757,16 @@ impl AppUpdateState { let mut inner = self .inner .lock() - .map_err(|_| "应用更新状态锁已损坏".to_string())?; + .map_err(|_| "Application update status lock is poisoned".to_string())?; if !inner.task.running || inner.task.phase != "checking" { - return Err("应用更新未处于检查阶段".to_string()); + return Err("Application update is not in the checking stage".to_string()); } if inner .token .as_ref() .is_none_or(CancellationToken::is_cancelled) { - return Err("应用更新下载已取消".to_string()); + return Err("Application update download canceled".to_string()); } inner.task = AppUpdateTask { running: true, @@ -1875,7 +1831,7 @@ impl CoreProcessState { fn ensure_active(&self) -> Result<(), String> { if self.is_shutting_down() { - Err("应用正在退出,已取消内核操作".to_string()) + Err("Application is exiting; kernel operation canceled".to_string()) } else { Ok(()) } @@ -1921,7 +1877,7 @@ impl CoreProcessState { let mut adopted = self .adopted_processes .lock() - .map_err(|_| "接管的内核进程状态锁已损坏".to_string())?; + .map_err(|_| "Adopted kernel process status lock is poisoned".to_string())?; *adopted = process_ids .into_iter() .filter(|process_id| is_process_alive(*process_id)) @@ -1939,7 +1895,7 @@ impl CoreProcessState { self.adopted_processes .lock() .map(|mut processes| processes.clear()) - .map_err(|_| "接管的内核进程状态锁已损坏".to_string()) + .map_err(|_| "Adopted kernel process status lock is poisoned".to_string()) } fn take_adopted_processes(&self) -> Vec { @@ -1956,7 +1912,7 @@ impl CoreProcessState { let mut managed_child = self .child .lock() - .map_err(|_| "内核进程状态锁已损坏".to_string())?; + .map_err(|_| "Kernel process status lock is poisoned".to_string())?; *managed_child = Some(child); Ok(pid) } @@ -1973,14 +1929,14 @@ impl MainWindowSizeState { self.inner .lock() .map(|size| *size) - .map_err(|_| "主窗口尺寸状态锁已损坏".to_string()) + .map_err(|_| "Main window size status lock is poisoned".to_string()) } fn replace(&self, size: SavedWindowSize) -> Result<(), String> { let mut current = self .inner .lock() - .map_err(|_| "主窗口尺寸状态锁已损坏".to_string())?; + .map_err(|_| "Main window size status lock is poisoned".to_string())?; *current = Some(size); Ok(()) } @@ -1997,14 +1953,14 @@ impl GuiConfigState { self.inner .lock() .map(|config| config.clone()) - .map_err(|_| "GUI 配置状态锁已损坏".to_string()) + .map_err(|_| "GUI configuration status lock is poisoned".to_string()) } fn replace_external(&self, config: GuiConfigFile) -> Result<(), String> { let mut current = self .inner .lock() - .map_err(|_| "GUI 配置状态锁已损坏".to_string())?; + .map_err(|_| "GUI configuration status lock is poisoned".to_string())?; *current = config; Ok(()) } @@ -2016,7 +1972,7 @@ impl GuiConfigState { let mut current = self .inner .lock() - .map_err(|_| "GUI 配置状态锁已损坏".to_string())?; + .map_err(|_| "GUI configuration status lock is poisoned".to_string())?; let mut config = current.clone(); apply_core_settings_to_gui_config(&mut config, settings); apply_external_core_proxy_override(&mut config, settings)?; @@ -2136,7 +2092,7 @@ impl GuiConfigState { self.update(|config| { if let Some(url) = &candidate.custom_url { if !config.custom_download_mirrors.contains(url) { - return Err("自定义镜像不存在".to_string()); + return Err("Custom mirror does not exist".to_string()); } config.active_custom_download_mirror = url.clone(); } @@ -2154,7 +2110,7 @@ impl GuiConfigState { let mut current = self .inner .lock() - .map_err(|_| "GUI 配置状态锁已损坏".to_string())?; + .map_err(|_| "GUI configuration status lock is poisoned".to_string())?; if current.selected_download_candidate() != *expected_source || expected_source == fallback_source { @@ -2263,7 +2219,7 @@ impl GuiConfigState { let mut current = self .inner .lock() - .map_err(|_| "GUI 配置状态锁已损坏".to_string())?; + .map_err(|_| "GUI configuration status lock is poisoned".to_string())?; let mut config = current.clone(); update(&mut config)?; sanitize_gui_config(&mut config)?; @@ -2385,7 +2341,7 @@ fn main() { let result = args .next() .map(PathBuf::from) - .ok_or_else(|| "应用更新助手缺少描述文件".to_string()) + .ok_or_else(|| "Application update helper is missing its descriptor file".to_string()) .and_then(|path| run_portable_update_helper(&path)); if let Err(error) = result { eprintln!("{error}"); @@ -2409,11 +2365,11 @@ fn main() { eprintln!("{error}"); let mut config = GuiConfigFile::default(); if let Err(secret_error) = ensure_strong_management_secret(&mut config) { - eprintln!("初始化 WebUI 安全密钥失败: {secret_error}"); + eprintln!("Failed to initialize WebUI security key: {secret_error}"); return; } if let Err(sanitize_error) = sanitize_gui_config(&mut config) { - eprintln!("初始化固定凭证目录失败: {sanitize_error}"); + eprintln!("Failed to initialize fixed credentials directory: {sanitize_error}"); } config } @@ -2463,7 +2419,7 @@ fn main() { if let Some(observed_size) = observed_size { let window_size_state = window.state::(); if let Err(error) = window_size_state.replace(observed_size) { - eprintln!("记录主窗口尺寸失败: {error}"); + eprintln!("Failed to record main window size: {error}"); } } }); @@ -2475,7 +2431,7 @@ fn main() { api.prevent_close(); set_macos_dock_visible(window.app_handle(), false); if let Err(error) = window.hide() { - eprintln!("隐藏主窗口失败: {error}"); + eprintln!("Failed to hide main window: {error}"); set_macos_dock_visible(window.app_handle(), true); } } @@ -2488,7 +2444,7 @@ fn main() { if let tauri::WindowEvent::CloseRequested { api, .. } = event { api.prevent_close(); if let Err(error) = window.emit(WINDOWS_CLOSE_REQUEST_EVENT, ()) { - eprintln!("显示 Windows 关闭确认失败: {error}"); + eprintln!("Failed to show Windows close confirmation: {error}"); } } } @@ -2497,21 +2453,21 @@ fn main() { let app = app .setup(move |app| { if let Err(error) = network_proxy::refresh(app.state::().inner()) { - eprintln!("读取启动代理设置失败: {error}"); + eprintln!("Failed to read startup proxy settings: {error}"); } if let Err(error) = codex_catalog::validate_embedded_catalog() { - eprintln!("Codex 内置模型目录无效: {error}"); + eprintln!("Invalid bundled Codex model catalog: {error}"); } if let Err(error) = load_codex_model_catalog_override(app.handle()) { - eprintln!("加载 Codex 模型目录更新文件失败,将使用内置目录: {error}"); + eprintln!("Failed to load Codex model catalog update file; using bundled catalog: {error}"); } let catalog_update_app = app.handle().clone(); if let Err(error) = load_codex_model_customizations(app.handle()) { - eprintln!("加载 Codex 自定义模型配置失败: {error}"); + eprintln!("Failed to load Codex custom model configuration: {error}"); } tauri::async_runtime::spawn(async move { if let Err(error) = update_codex_model_catalog_inner(&catalog_update_app).await { - eprintln!("后台更新 Codex 模型目录失败,继续使用当前目录: {error}"); + eprintln!("Failed to update Codex model catalog in the background; continuing with current catalog: {error}"); } }); if let Err(error) = restore_main_window_size(app.handle()) { @@ -2524,13 +2480,13 @@ fn main() { setup_windows_tray(app)?; if let Err(error) = configure_initial_main_window(app.handle(), start_hidden) { - eprintln!("配置启动窗口状态失败: {error}"); + eprintln!("Failed to configure startup window state: {error}"); } if let Err(error) = configuration_watcher::start_configuration_file_watcher(app.handle().clone()) { - eprintln!("启动配置文件监控失败: {error}"); + eprintln!("Failed to start configuration file watcher: {error}"); } network_proxy::start_monitor(app.handle().clone()); @@ -2539,7 +2495,7 @@ fn main() { let usage_app = app.handle().clone(); tauri::async_runtime::spawn_blocking(move || { if let Err(error) = usage::initialize_usage_storage() { - eprintln!("初始化使用记录目录失败: {error}"); + eprintln!("Failed to initialize usage records directory: {error}"); } usage::start_usage_collector(usage_app); }); @@ -2553,7 +2509,7 @@ fn main() { gui_config_state.inner(), cache.inner(), ) { - eprintln!("后台刷新智能体配置状态失败: {error}"); + eprintln!("Failed to refresh agent configuration status in the background: {error}"); } }); @@ -2582,22 +2538,22 @@ fn main() { } match auto_install_bundled_core_if_needed(&core_app) { - Ok(true) => eprintln!("已自动安装或升级至软件内置的 CPA 内核版本"), + Ok(true) => eprintln!("Automatically installed or upgraded to the CPA kernel version bundled with the application"), Ok(false) => {} - Err(error) => eprintln!("自动安装 CPA 离线内核失败: {error}"), + Err(error) => eprintln!("Failed to automatically install CPA offline kernel: {error}"), } let adopted_process_ids = match adopt_existing_core_processes(process_state.inner()) { Ok(process_ids) => process_ids, Err(error) => { - eprintln!("扫描并接管当前目录的 CPA 内核失败: {error}"); + eprintln!("Failed to scan for and adopt the CPA kernel in the current directory: {error}"); Vec::new() } }; if !adopted_process_ids.is_empty() { eprintln!( - "已接管当前目录中运行的 CPA 内核: PID {}", + "Adopted running CPA kernel in the current directory: PID {}", adopted_process_ids .iter() .map(u32::to_string) @@ -2606,7 +2562,7 @@ fn main() { ); } else if should_start_core_on_launch(&config) { if let Err(error) = start_core_process_inner(process_state.inner(), &config) { - eprintln!("自动启动 CPA 内核失败: {error}"); + eprintln!("Failed to automatically start CPA kernel: {error}"); } } @@ -2621,7 +2577,7 @@ fn main() { if let Some(ack_path) = portable_update_ack.as_ref() { fs::write(ack_path, env!("CARGO_PKG_VERSION").as_bytes()) - .map_err(|error| format!("写入应用更新启动确认失败: {error}"))?; + .map_err(|error| format!("Failed to write application update startup confirmation: {error}"))?; } Ok(()) @@ -2772,7 +2728,7 @@ fn main() { return; } if let Err(error) = persist_main_window_size(app_handle) { - eprintln!("保存主窗口尺寸失败: {error}"); + eprintln!("Failed to save main window size: {error}"); } app_handle.state::().cancel(); let app_handle = app_handle.clone(); @@ -2793,7 +2749,7 @@ fn main() { usage::stop_usage_collector(app_handle); let deepseek_process_state = app_handle.state::(); if let Err(error) = stop_managed_deepseek_harness(deepseek_process_state.inner()) { - eprintln!("关闭 DeepSeek Harness 失败: {error}"); + eprintln!("Failed to stop DeepSeek Harness: {error}"); } let gui_config_state = app_handle.state::(); let process_state = app_handle.state::(); diff --git a/src-tauri/src/management_api.rs b/src-tauri/src/management_api.rs index 6a58d2b..af47b42 100644 --- a/src-tauri/src/management_api.rs +++ b/src-tauri/src/management_api.rs @@ -72,11 +72,11 @@ pub(crate) async fn management_request( "PUT" => reqwest::Method::PUT, "PATCH" => reqwest::Method::PATCH, "DELETE" => reqwest::Method::DELETE, - _ => return Err("不支持的管理 API 请求方法".to_string()), + _ => return Err("Unsupported management API request method".to_string()), }; let path = request.path.trim(); if path.is_empty() || path.contains("://") || path.contains("..") { - return Err("无效的管理 API 路径".to_string()); + return Err("Invalid management API path".to_string()); } let client = management_http_client()?; @@ -96,7 +96,7 @@ pub(crate) async fn management_request( let response = builder .send() .await - .map_err(|err| format_management_request_error("请求管理 API 失败", &err))?; + .map_err(|err| format_management_request_error("Management API request failed", &err))?; read_management_value(response).await } @@ -108,7 +108,7 @@ pub(crate) async fn upload_auth_file( ) -> Result { let name = name.trim().to_string(); if name.is_empty() || !name.to_ascii_lowercase().ends_with(".json") { - return Err("凭证文件名必须以 .json 结尾".to_string()); + return Err("Credential filename must end with .json".to_string()); } let config = gui_config_state.snapshot()?; @@ -123,7 +123,7 @@ pub(crate) async fn upload_auth_file( .body(data) .send() .await - .map_err(|err| format_management_request_error("上传凭证文件失败", &err))?; + .map_err(|err| format_management_request_error("Failed to upload credential file", &err))?; read_management_value(response).await } @@ -135,7 +135,7 @@ pub(crate) fn open_auth_files_directory( let install_dir = core_install_dir()?; let auth_dir = auth_dir_path_for_core(&config.auth_dir, &install_dir); fs::create_dir_all(&auth_dir) - .map_err(|error| format!("创建凭证目录失败 {}: {error}", path_to_string(&auth_dir)))?; + .map_err(|error| format!("Failed to create credentials directory {}: {error}", path_to_string(&auth_dir)))?; open_directory_in_file_manager(&auth_dir) } @@ -147,7 +147,7 @@ pub(crate) fn open_core_logs_directory( let install_dir = core_install_dir()?; let logs_dir = core_logs_dir_path(&config.auth_dir, &install_dir); fs::create_dir_all(&logs_dir) - .map_err(|error| format!("创建日志目录失败 {}: {error}", path_to_string(&logs_dir)))?; + .map_err(|error| format!("Failed to create log directory {}: {error}", path_to_string(&logs_dir)))?; open_directory_in_file_manager(&logs_dir) } @@ -169,7 +169,7 @@ fn open_directory_in_file_manager(path: &Path) -> Result<(), String> { command .spawn() .map(|_| ()) - .map_err(|error| format!("打开目录失败 {}: {error}", path_to_string(path))) + .map_err(|error| format!("Failed to open directory {}: {error}", path_to_string(path))) } #[tauri::command] @@ -195,7 +195,7 @@ pub(crate) async fn start_oauth_login( let response = request .send() .await - .map_err(|err| format_management_request_error("请求 OAuth 登录链接失败", &err))?; + .map_err(|err| format_management_request_error("Failed to request OAuth login URL", &err))?; let payload = read_management_json::(response).await?; if let Some(error) = payload .error @@ -208,7 +208,7 @@ pub(crate) async fn start_oauth_login( .url .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()) - .ok_or_else(|| "内核未返回 OAuth 登录链接".to_string())?; + .ok_or_else(|| "Kernel did not return an OAuth login URL".to_string())?; let state = payload .state .map(|value| value.trim().to_string()) @@ -234,7 +234,7 @@ pub(crate) async fn get_oauth_status( ) -> Result { let state = state.trim().to_string(); if state.is_empty() { - return Err("OAuth state 不能为空".to_string()); + return Err("OAuth state cannot be empty".to_string()); } let config = gui_config_state.snapshot()?; let client = management_http_client()?; @@ -244,7 +244,7 @@ pub(crate) async fn get_oauth_status( .query(&[("state", state)]) .send() .await - .map_err(|err| format_management_request_error("查询 OAuth 状态失败", &err))?; + .map_err(|err| format_management_request_error("Failed to query OAuth status", &err))?; let payload = read_management_json::(response).await?; let status = payload .status @@ -269,7 +269,7 @@ pub(crate) async fn submit_oauth_callback( ) -> Result<(), String> { let redirect_url = redirect_url.trim().to_string(); if redirect_url.is_empty() { - return Err("回调链接不能为空".to_string()); + return Err("Callback URL cannot be empty".to_string()); } let config = gui_config_state.snapshot()?; let provider_key = normalize_management_oauth_provider(&provider)?; @@ -284,12 +284,12 @@ pub(crate) async fn submit_oauth_callback( .json(&body) .send() .await - .map_err(|err| format_management_request_error("提交 OAuth 回调失败", &err))?; + .map_err(|err| format_management_request_error("Failed to submit OAuth callback", &err))?; let status = response.status(); let text = response .text() .await - .map_err(|err| format_management_request_error("读取 OAuth 回调响应失败", &err))?; + .map_err(|err| format_management_request_error("Failed to read OAuth callback response", &err))?; if !status.is_success() { return Err(format_management_error(status.as_u16(), &text)); } @@ -304,7 +304,7 @@ pub(crate) fn management_http_client() -> Result { .timeout(Duration::from_secs(30)) .danger_accept_invalid_certs(true) .build() - .map_err(|err| format_management_request_error("创建管理 API 客户端失败", &err)) + .map_err(|err| format_management_request_error("Failed to create management API client", &err)) }); CLIENT.as_ref().cloned().map_err(Clone::clone) } @@ -339,14 +339,14 @@ pub(crate) fn format_management_request_error( pub(crate) fn management_authorization(config: &GuiConfigFile) -> Result { let secret_key = config.management_secret_key.trim(); if secret_key.is_empty() || is_hashed_management_secret_key(secret_key) { - return Err("管理接口不可用:没有可用的明文管理密钥".to_string()); + return Err("Management interface unavailable: no plaintext management key is available".to_string()); } Ok(format!("Bearer {secret_key}")) } pub(crate) fn management_endpoint(config: &GuiConfigFile, path: &str) -> Result { if config.port == 0 { - return Err("内核端口无效".to_string()); + return Err("Invalid kernel port".to_string()); } let path = path.trim_start_matches('/'); let origin = core_origin( @@ -371,7 +371,7 @@ fn normalize_management_oauth_provider(provider: &str) -> Result .bytes() .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-') { - return Err("无效的 OAuth 提供商".to_string()); + return Err("Invalid OAuth provider".to_string()); } Ok(key) } @@ -388,16 +388,16 @@ where let text = response .text() .await - .map_err(|err| format_management_request_error("读取管理 API 响应失败", &err))?; + .map_err(|err| format_management_request_error("Failed to read management API response", &err))?; if !status.is_success() { return Err(format_management_error(status.as_u16(), &text)); } if text.trim().is_empty() { - return Err("管理 API 返回了空响应".to_string()); + return Err("Management API returned an empty response".to_string()); } serde_json::from_str::(&text).map_err(|err| { format!( - "解析管理 API 响应失败: {err}; body={}", + "Failed to parse management API response: {err}; body={}", truncate_for_error(&text) ) }) @@ -410,7 +410,7 @@ pub(crate) async fn read_management_value( let text = response .text() .await - .map_err(|err| format_management_request_error("读取管理 API 响应失败", &err))?; + .map_err(|err| format_management_request_error("Failed to read management API response", &err))?; if !status.is_success() { return Err(format_management_error(status.as_u16(), &text)); } @@ -428,7 +428,7 @@ pub(crate) async fn read_management_text(response: reqwest::Response) -> Result< let text = response .text() .await - .map_err(|err| format_management_request_error("读取管理 API 响应失败", &err))?; + .map_err(|err| format_management_request_error("Failed to read management API response", &err))?; if !status.is_success() { return Err(format_management_error(status.as_u16(), &text)); } @@ -444,15 +444,15 @@ fn format_management_error(status: u16, body: &str) -> String { { let message = message.trim(); if !message.is_empty() { - return format!("管理 API 错误 ({status}): {message}"); + return format!("Management API error ({status}): {message}"); } } } let body = body.trim(); if body.is_empty() { - format!("管理 API 错误 ({status})") + format!("Management API error ({status})") } else { - format!("管理 API 错误 ({status}): {}", truncate_for_error(body)) + format!("Management API error ({status}): {}", truncate_for_error(body)) } } diff --git a/src-tauri/src/native_i18n.rs b/src-tauri/src/native_i18n.rs new file mode 100644 index 0000000..ca2fe7a --- /dev/null +++ b/src-tauri/src/native_i18n.rs @@ -0,0 +1,113 @@ +#[derive(Clone, Copy)] +pub(crate) enum NativeText { + OpenMainWindow, + Quit, + CoreStatusWorking, + CoreStatusNotInstalled, + CoreStatusRunning, + CoreStatusStopped, + CoreStatusChecking, + Working, + StopCore, + StartCore, + RestartCore, + TooltipCoreWorking, + TooltipCoreNotInstalled, + TooltipCoreRunning, + TooltipCoreStopped, + TooltipCoreOperationFailed, + OperationFailed, +} + +pub(crate) fn native_text(locale: &str, text: NativeText) -> &'static str { + match super::normalize_app_locale(locale) { + "zh-CN" => match text { + NativeText::OpenMainWindow => "打开主界面", + NativeText::Quit => "退出", + NativeText::CoreStatusWorking => "内核状态:处理中", + NativeText::CoreStatusNotInstalled => "内核状态:未安装", + NativeText::CoreStatusRunning => "内核状态:运行中", + NativeText::CoreStatusStopped => "内核状态:已停止", + NativeText::CoreStatusChecking => "内核状态:正在检查", + NativeText::Working => "处理中...", + NativeText::StopCore => "停止内核", + NativeText::StartCore => "启动内核", + NativeText::RestartCore => "重启内核", + NativeText::TooltipCoreWorking => "EasyCLIProxyAPI · 内核处理中", + NativeText::TooltipCoreNotInstalled => "EasyCLIProxyAPI · 内核未安装", + NativeText::TooltipCoreRunning => "EasyCLIProxyAPI · 内核运行中", + NativeText::TooltipCoreStopped => "EasyCLIProxyAPI · 内核已停止", + NativeText::TooltipCoreOperationFailed => "EasyCLIProxyAPI · 内核操作失败", + NativeText::OperationFailed => "操作失败", + }, + "zh-TW" => match text { + NativeText::OpenMainWindow => "開啟主介面", + NativeText::Quit => "退出", + NativeText::CoreStatusWorking => "核心狀態:處理中", + NativeText::CoreStatusNotInstalled => "核心狀態:未安裝", + NativeText::CoreStatusRunning => "核心狀態:執行中", + NativeText::CoreStatusStopped => "核心狀態:已停止", + NativeText::CoreStatusChecking => "核心狀態:正在檢查", + NativeText::Working => "處理中...", + NativeText::StopCore => "停止核心", + NativeText::StartCore => "啟動核心", + NativeText::RestartCore => "重新啟動核心", + NativeText::TooltipCoreWorking => "EasyCLIProxyAPI · 核心處理中", + NativeText::TooltipCoreNotInstalled => "EasyCLIProxyAPI · 核心未安裝", + NativeText::TooltipCoreRunning => "EasyCLIProxyAPI · 核心執行中", + NativeText::TooltipCoreStopped => "EasyCLIProxyAPI · 核心已停止", + NativeText::TooltipCoreOperationFailed => "EasyCLIProxyAPI · 核心操作失敗", + NativeText::OperationFailed => "操作失敗", + }, + "ja" => match text { + NativeText::OpenMainWindow => "メイン画面を開く", + NativeText::Quit => "終了", + NativeText::CoreStatusWorking => "コア状態:処理中", + NativeText::CoreStatusNotInstalled => "コア状態:未インストール", + NativeText::CoreStatusRunning => "コア状態:実行中", + NativeText::CoreStatusStopped => "コア状態:停止済み", + NativeText::CoreStatusChecking => "コア状態:確認中", + NativeText::Working => "処理中...", + NativeText::StopCore => "コアを停止", + NativeText::StartCore => "コアを起動", + NativeText::RestartCore => "コアを再起動", + NativeText::TooltipCoreWorking => "EasyCLIProxyAPI · コア処理中", + NativeText::TooltipCoreNotInstalled => "EasyCLIProxyAPI · コア未インストール", + NativeText::TooltipCoreRunning => "EasyCLIProxyAPI · コア実行中", + NativeText::TooltipCoreStopped => "EasyCLIProxyAPI · コア停止済み", + NativeText::TooltipCoreOperationFailed => "EasyCLIProxyAPI · コア操作失敗", + NativeText::OperationFailed => "操作に失敗しました", + }, + _ => match text { + NativeText::OpenMainWindow => "Open Main Window", + NativeText::Quit => "Quit", + NativeText::CoreStatusWorking => "Core status: Working", + NativeText::CoreStatusNotInstalled => "Core status: Not installed", + NativeText::CoreStatusRunning => "Core status: Running", + NativeText::CoreStatusStopped => "Core status: Stopped", + NativeText::CoreStatusChecking => "Core status: Checking", + NativeText::Working => "Working...", + NativeText::StopCore => "Stop Core", + NativeText::StartCore => "Start Core", + NativeText::RestartCore => "Restart Core", + NativeText::TooltipCoreWorking => "EasyCLIProxyAPI · Core working", + NativeText::TooltipCoreNotInstalled => "EasyCLIProxyAPI · Core not installed", + NativeText::TooltipCoreRunning => "EasyCLIProxyAPI · Core running", + NativeText::TooltipCoreStopped => "EasyCLIProxyAPI · Core stopped", + NativeText::TooltipCoreOperationFailed => "EasyCLIProxyAPI · Core operation failed", + NativeText::OperationFailed => "Operation failed", + }, + } +} + +pub(crate) fn native_operation_failed(locale: &str, summary: &str) -> String { + let separator = if super::normalize_app_locale(locale) == "en" { + ": " + } else { + ":" + }; + format!( + "{}{separator}{summary}", + native_text(locale, NativeText::OperationFailed) + ) +} diff --git a/src-tauri/src/network_proxy.rs b/src-tauri/src/network_proxy.rs index 4249184..a6c70c6 100644 --- a/src-tauri/src/network_proxy.rs +++ b/src-tauri/src/network_proxy.rs @@ -3,7 +3,7 @@ use super::*; pub(crate) fn normalize_proxy_url(value: &str) -> Result { let value = value.trim(); let invalid = || { - "代理地址无效,请使用 http://host:port、https://host:port 或 socks5://host:port(不是 PAC 地址)".to_string() + "Invalid proxy address. Use http://host:port, https://host:port, or socks5://host:port (not a PAC address)".to_string() }; if value.is_empty() || value.chars().any(|c| c.is_control() || c.is_whitespace()) { return Err(invalid()); @@ -38,7 +38,7 @@ fn fixed_proxy(value: &str, scheme: &str) -> Result { fn parse_detected_proxy(value: &str) -> Result { let value = value.trim().trim_matches('\'').trim_matches('"'); if value.is_empty() { - return Err("系统代理地址为空".to_string()); + return Err("System proxy address is empty".to_string()); } if value.contains("://") { normalize_proxy_url(value) @@ -51,7 +51,7 @@ fn parse_detected_proxy(value: &str) -> Result { fn parse_env_proxy(name: &str, value: &str) -> Result { let value = value.trim().trim_matches('\'').trim_matches('"'); if value.is_empty() { - return Err("系统代理地址为空".to_string()); + return Err("System proxy address is empty".to_string()); } if !value.contains("://") && name.eq_ignore_ascii_case("socks_proxy") { return normalize_proxy_url(&format!("socks5://{value}")); @@ -90,12 +90,12 @@ fn parse_windows_proxy(value: &str) -> Result { ); } } - Err("系统代理没有可用的 HTTP / HTTPS / SOCKS 地址".to_string()) + Err("System proxy has no available HTTP / HTTPS / SOCKS address".to_string()) } #[cfg(any(target_os = "windows", test))] fn pac_error() -> String { - "检测到 PAC / 自动发现代理,暂不支持转换为内核固定代理".to_string() + "PAC / auto-discovery proxy detected; conversion to a fixed kernel proxy is not supported yet".to_string() } #[cfg(target_os = "windows")] @@ -119,7 +119,7 @@ fn detect_system_proxy() -> Result { unsafe { let mut config: WINHTTP_CURRENT_USER_IE_PROXY_CONFIG = std::mem::zeroed(); if WinHttpGetIEProxyConfigForCurrentUser(&mut config) == 0 { - return Err("无法读取当前用户的 Windows 系统代理".to_string()); + return Err("Unable to read Windows system proxy for the current user".to_string()); } let proxy = read_wide(config.lpszProxy); let pac = read_wide(config.lpszAutoConfigUrl); @@ -181,17 +181,17 @@ fn desktop_setting(program: &str, args: &[&str]) -> Result { .stderr(Stdio::null()); let mut child = command .spawn() - .map_err(|_| format!("无法运行 {program} 读取系统代理"))?; + .map_err(|_| format!("Unable to run {program} to read system proxy"))?; let deadline = std::time::Instant::now() + Duration::from_millis(400); loop { match child.try_wait() { Ok(Some(status)) => { if !status.success() { - return Err(format!("{program} 读取系统代理失败")); + return Err(format!("{program} failed to read system proxy")); } let output = child .wait_with_output() - .map_err(|_| "读取系统代理失败".to_string())?; + .map_err(|_| "Failed to read system proxy".to_string())?; return Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()); } Ok(None) if std::time::Instant::now() < deadline => { @@ -200,7 +200,7 @@ fn desktop_setting(program: &str, args: &[&str]) -> Result { _ => { let _ = child.kill(); let _ = child.wait(); - return Err("读取系统代理超时".to_string()); + return Err("Timed out while reading system proxy".to_string()); } } } @@ -300,10 +300,10 @@ fn parse_networksetup_proxy(output: &str, scheme: &str) -> Result Result { return Ok(value); } } - Err("无法读取 KDE 系统代理".to_string()) + Err("Unable to read KDE system proxy".to_string()) } #[cfg(target_os = "linux")] @@ -511,7 +511,7 @@ fn apply(state: &GuiConfigState) -> Result { let snapshot = state .inner .lock() - .map_err(|_| "代理配置锁已损坏".to_string())? + .map_err(|_| "Proxy configuration lock is poisoned".to_string())? .clone(); let detected = if snapshot.proxy_override { None @@ -521,7 +521,7 @@ fn apply(state: &GuiConfigState) -> Result { let mut current = state .inner .lock() - .map_err(|_| "代理配置锁已损坏".to_string())?; + .map_err(|_| "Proxy configuration lock is poisoned".to_string())?; let mut next = current.clone(); next.proxy_url = if next.proxy_override { next.proxy_url.clone() @@ -567,7 +567,7 @@ pub(crate) fn set_manual(state: &GuiConfigState, proxy_url: String) -> Result<() let mut current = state .inner .lock() - .map_err(|_| "代理配置锁已损坏".to_string())?; + .map_err(|_| "Proxy configuration lock is poisoned".to_string())?; let mut next = current.clone(); next.proxy_override = !proxy_url.is_empty(); next.proxy_url = if next.proxy_override { @@ -609,10 +609,10 @@ pub(crate) async fn verify_core_proxy(config: &GuiConfigFile) -> Result<(), Stri .timeout(Duration::from_secs(3)) .send() .await - .map_err(|_| "无法更新内核代理,请重启内核后重试".to_string())?; + .map_err(|_| "Unable to update kernel proxy. Restart the kernel and try again".to_string())?; if !response.status().is_success() { return Err( - "内核拒绝代理更新,请重启内核或升级内核后重试".to_string() + "Kernel rejected the proxy update. Restart or upgrade the kernel and try again".to_string() ); } updated = true; @@ -624,7 +624,7 @@ pub(crate) async fn verify_core_proxy(config: &GuiConfigFile) -> Result<(), Stri tokio::time::sleep(Duration::from_millis(200)).await; } Err( - "代理配置已保存,但尚未确认内核已应用。请确认内核正在运行,必要时重启内核后重试。" + "Proxy configuration was saved, but the kernel has not confirmed applying it. Make sure the kernel is running and restart it if necessary." .to_string(), ) } diff --git a/src-tauri/src/provider_health.rs b/src-tauri/src/provider_health.rs index d7666e1..52ab85e 100644 --- a/src-tauri/src/provider_health.rs +++ b/src-tauri/src/provider_health.rs @@ -272,7 +272,7 @@ fn persist_provider_health_success( }, }); if let Err(error) = usage::persist_local_usage_event(app, "desktop_health_check", event) { - eprintln!("保存桌面健康检测使用记录失败: {error}"); + eprintln!("Failed to save desktop health check usage record: {error}"); } } @@ -290,19 +290,19 @@ pub(crate) async fn provider_health_probe( request: ProviderHealthProbeRequest, ) -> Result { let url = reqwest::Url::parse(request.url.trim()) - .map_err(|error| format!("健康检测地址无效: {error}"))?; + .map_err(|error| format!("Invalid health check URL: {error}"))?; if !matches!(url.scheme(), "http" | "https") || url.host_str().is_none() { - return Err("健康检测仅支持 HTTP 或 HTTPS 地址".to_string()); + return Err("Health checks support only HTTP or HTTPS URLs".to_string()); } if !matches!( request.protocol.as_str(), "openai-chat" | "openai-responses" | "claude" | "gemini" ) { - return Err("不支持的健康检测协议".to_string()); + return Err("Unsupported health check protocol".to_string()); } let endpoint = format!("POST {}", url.path()); if request.data.len() > 64 * 1024 { - return Err("健康检测请求体过大".to_string()); + return Err("Health check request body is too large".to_string()); } let _permit = PROVIDER_HEALTH_SLOTS @@ -321,14 +321,14 @@ pub(crate) async fn provider_health_probe( .connect_timeout(Duration::from_secs(10)) .timeout(timeout), proxy_url, - "创建健康检测客户端失败", + "Failed to create health check client", )?; let mut headers = reqwest::header::HeaderMap::new(); for (name, value) in &request.header { let name = reqwest::header::HeaderName::from_bytes(name.as_bytes()) - .map_err(|error| format!("健康检测请求头名称无效: {error}"))?; + .map_err(|error| format!("Invalid health check request header name: {error}"))?; let value = reqwest::header::HeaderValue::from_str(value) - .map_err(|error| format!("健康检测请求头值无效: {error}"))?; + .map_err(|error| format!("Invalid health check request header value: {error}"))?; headers.insert(name, value); } if !headers.contains_key(reqwest::header::USER_AGENT) { @@ -345,15 +345,15 @@ pub(crate) async fn provider_health_probe( .body(request.data.clone()) .send() .await - .map_err(|error| format!("健康检测请求失败: {error}"))?; + .map_err(|error| format!("Health check request failed: {error}"))?; let status = response.status(); if !status.is_success() { let detail = response.text().await.unwrap_or_default(); let detail = detail.trim(); return Err(if detail.is_empty() { - format!("上游返回 HTTP {}", status.as_u16()) + format!("Upstream returned HTTP {}", status.as_u16()) } else { - format!("上游返回 HTTP {}: {}", status.as_u16(), detail) + format!("Upstream returned HTTP {}: {}", status.as_u16(), detail) }); } let content_type = response @@ -363,15 +363,15 @@ pub(crate) async fn provider_health_probe( .unwrap_or("") .to_ascii_lowercase(); if !provider_health_content_type_is_streaming(&content_type) { - return Err("上游未返回流式响应,无法测量首字延迟".to_string()); + return Err("Upstream did not return a streaming response; time to first token cannot be measured".to_string()); } let mut stream = response.bytes_stream(); let mut received = Vec::new(); while let Some(chunk) = stream.next().await { - let chunk = chunk.map_err(|error| format!("读取健康检测流失败: {error}"))?; + let chunk = chunk.map_err(|error| format!("Failed to read health check stream: {error}"))?; if received.len().saturating_add(chunk.len()) > MAX_PROVIDER_HEALTH_STREAM_BYTES { - return Err("健康检测在限制范围内未收到模型首字".to_string()); + return Err("Health check did not receive the model's first token within the limit".to_string()); } received.extend_from_slice(&chunk); let elapsed_ms = started_at.elapsed().as_millis().max(1) as u64; @@ -397,5 +397,5 @@ pub(crate) async fn provider_health_probe( }); } } - Err("健康检测未收到模型首字".to_string()) + Err("Health check did not receive the model's first token".to_string()) } diff --git a/src-tauri/src/tests/agent_configuration.rs b/src-tauri/src/tests/agent_configuration.rs index d7bbedd..f0f67f1 100644 --- a/src-tauri/src/tests/agent_configuration.rs +++ b/src-tauri/src/tests/agent_configuration.rs @@ -1632,7 +1632,7 @@ fn deepseek_harness_credentials_reject_legacy_flat_layout() { ) .unwrap_err(); - assert!(error.contains("缺少 version 字段"), "{error}"); + assert!(error.contains("missing the version field"), "{error}"); } #[test] diff --git a/src-tauri/src/tests/agent_state.rs b/src-tauri/src/tests/agent_state.rs index e16e174..c2e2087 100644 --- a/src-tauri/src/tests/agent_state.rs +++ b/src-tauri/src/tests/agent_state.rs @@ -664,7 +664,7 @@ fn applied_state_rejects_backup_paths_outside_the_managed_config_set() { let error = load_agent_applied_state(AgentClient::OpenCode, &home) .err() .unwrap(); - assert!(error.contains("非预期备份路径")); + assert!(error.contains("unexpected backup path")); fs::remove_dir_all(home).unwrap(); } @@ -707,7 +707,7 @@ fn missing_session_backup_is_detected_before_any_file_is_restored() { restore_agent_applied_state_configuration(AgentClient::Codex, &paths, &state_path, &state) .err() .unwrap(); - assert!(error.contains("读取智能体备份失败")); + assert!(error.contains("Failed to read agent backup")); assert_eq!(fs::read_to_string(&config_path).unwrap(), "managed-config"); assert_eq!( fs::read_to_string(&catalog_path).unwrap(), diff --git a/src-tauri/src/tests/agent_transactions.rs b/src-tauri/src/tests/agent_transactions.rs index 770d151..5dada7a 100644 --- a/src-tauri/src/tests/agent_transactions.rs +++ b/src-tauri/src/tests/agent_transactions.rs @@ -82,7 +82,7 @@ custom_option = "keep-original" assert!(inspection .warnings .iter() - .any(|warning| warning.contains("自动重新同步"))); + .any(|warning| warning.contains("resynchronized automatically"))); let updated = update_agent_modification( AgentClient::Codex, diff --git a/src-tauri/src/tests/alias_save.rs b/src-tauri/src/tests/alias_save.rs index 05b7bfa..c0a9fcf 100644 --- a/src-tauri/src/tests/alias_save.rs +++ b/src-tauri/src/tests/alias_save.rs @@ -200,7 +200,7 @@ async fn alias_save_recovers_failures_before_and_after_writes() { let core = MockCore::new(CURRENT, failure); let updated = CURRENT.replace("my-alias", "renamed"); let result = put_management_alias_config_changes(&core.config, CURRENT, &updated).await; - assert!(result.unwrap_err().contains("已恢复原配置")); + assert!(result.unwrap_err().contains("Original configuration was restored")); let (persisted, requests) = core.finish(); assert_eq!(persisted, yaml_json(CURRENT), "{requests:?}"); assert_eq!( @@ -223,8 +223,8 @@ async fn alias_save_reports_failed_rollback_without_claiming_restoration() { ) .await; let error = result.unwrap_err(); - assert!(error.contains("自动恢复失败")); - assert!(error.contains("配置可能已部分写入")); + assert!(error.contains("automatic restoration failed")); + assert!(error.contains("configuration may be partially written")); let (persisted, _) = core.finish(); assert_eq!( persisted["oauth-model-alias"]["codex"][0]["alias"], @@ -242,7 +242,9 @@ async fn alias_save_does_not_rollback_over_external_changes() { &CURRENT.replace("my-alias", "renamed"), ) .await; - assert!(result.unwrap_err().contains("检测到其他配置修改")); + assert!(result + .unwrap_err() + .contains("Other configuration changes were detected")); let (persisted, requests) = core.finish(); assert_eq!(persisted["debug"], true); assert_eq!( @@ -263,7 +265,7 @@ async fn alias_save_rejects_stale_snapshot_before_any_write() { &CURRENT.replace("my-alias", "renamed"), ) .await; - assert!(result.unwrap_err().contains("配置已变化")); + assert!(result.unwrap_err().contains("Configuration changed")); let (_, requests) = core.finish(); assert_eq!(requests, ["GET /v0/management/config.yaml"]); } @@ -317,7 +319,7 @@ async fn alias_save_can_retry_after_restoration_reformats_yaml() { put_management_alias_config_changes(&core.config, CURRENT, &updated) .await .unwrap_err() - .contains("已恢复原配置") + .contains("Original configuration was restored") ); let restored = fetch_management_config_yaml(&core.config).await.unwrap(); validate_model_alias_revision(&restored, Some(&context.revision)).unwrap(); @@ -575,7 +577,7 @@ async fn desktop_backup_restore_rejects_core_changes_since_preview() { execute_restore_plan(&core.config, plan, &preview.preview.revision) .await .unwrap_err() - .contains("预览后") + .contains("changed after preview") ); assert_eq!( current_desktop_mappings(&fixture.home) @@ -600,7 +602,7 @@ async fn desktop_backup_restore_rolls_back_core_when_local_files_change() { assert!(execute_restore_plan(&core.config, plan, &revision) .await .unwrap_err() - .contains("已恢复原配置")); + .contains("Original configuration was restored")); assert_eq!(config_images(&paths).unwrap(), before); assert_eq!( current_desktop_mappings(&fixture.home) diff --git a/src-tauri/src/tests/app_settings.rs b/src-tauri/src/tests/app_settings.rs index d005f8a..cff376f 100644 --- a/src-tauri/src/tests/app_settings.rs +++ b/src-tauri/src/tests/app_settings.rs @@ -93,7 +93,7 @@ fn gui_config_defaults_are_stable() { assert!(content.contains("auth-dir = \"../oauth\"")); assert!(content.contains("[[api-keys]]")); assert!(content.contains("key = \"123456\"")); - assert!(content.contains("remark = \"默认密钥\"")); + assert!(content.contains("remark = \"Default key\"")); assert!(content.contains("management-secret-key = \"\"")); assert!(content.contains("plugins-enabled = false")); assert!(content.contains("routing-strategy = \"round-robin\"")); diff --git a/src-tauri/src/tests/app_update.rs b/src-tauri/src/tests/app_update.rs index dbea39f..36d6dbc 100644 --- a/src-tauri/src/tests/app_update.rs +++ b/src-tauri/src/tests/app_update.rs @@ -496,7 +496,7 @@ async fn failed_or_cancelled_refresh_never_downloads_the_previous_offer() { let (result, _) = tokio::join!(refresh, async { state.cancel(); }); - assert_eq!(result.err().as_deref(), Some("应用更新下载已取消")); + assert_eq!(result.err().as_deref(), Some("Application update download canceled")); assert!(state.snapshot().target_version.is_none()); let pending = PendingAppUpdate { version: "v0.3.3".to_string(), diff --git a/src-tauri/src/tests/core_config.rs b/src-tauri/src/tests/core_config.rs index bbda98c..7e98c90 100644 --- a/src-tauri/src/tests/core_config.rs +++ b/src-tauri/src/tests/core_config.rs @@ -26,7 +26,7 @@ fn api_key_remarks_follow_matching_core_keys() { default_api_key_entry(), GuiApiKeyEntry { key: "custom-key".to_string(), - remark: "开发环境".to_string(), + remark: "Development environment".to_string(), }, ]; let core_keys = vec!["custom-key".to_string(), "new-key".to_string()]; @@ -34,7 +34,7 @@ fn api_key_remarks_follow_matching_core_keys() { let merged = merge_core_api_keys_with_gui_metadata(&existing, &core_keys, None); assert_eq!(gui_api_key_values(&merged), vec!["custom-key", "new-key"]); - assert_eq!(merged[0].remark, "开发环境"); + assert_eq!(merged[0].remark, "Development environment"); assert!(merged[1].remark.is_empty()); } @@ -279,7 +279,7 @@ fn packaged_macos_auth_migration_does_not_overwrite_conflicting_credentials() { let error = migrate_auth_dir_from_macos_app_bundle(&mut config, &install_dir, &destination) .unwrap_err(); - assert!(error.contains("未覆盖"), "{error}"); + assert!(error.contains("not overwritten"), "{error}"); assert_eq!(config.auth_dir, original_auth_dir); assert_eq!( fs::read(destination.join("account.json")).unwrap(), @@ -997,7 +997,7 @@ fn managed_session_settings_use_canonical_yaml_and_preserve_unrelated_content() #[test] fn optional_core_strings_are_trimmed_and_reject_control_characters() { assert_eq!( - normalize_optional_config_string(" socks5://proxy:7890 ".to_string(), "代理 URL") + normalize_optional_config_string(" socks5://proxy:7890 ".to_string(), "Proxy URL") .unwrap(), "socks5://proxy:7890" ); @@ -1005,7 +1005,7 @@ fn optional_core_strings_are_trimmed_and_reject_control_characters() { normalize_optional_config_string(" 1h ".to_string(), "TTL").unwrap(), "1h" ); - assert!(normalize_optional_config_string("bad\nvalue".to_string(), "代理 URL").is_err()); + assert!(normalize_optional_config_string("bad\nvalue".to_string(), "Proxy URL").is_err()); } #[test] @@ -1238,7 +1238,7 @@ fn startup_preserves_all_user_owned_yaml_and_only_applies_gui_managed_values() { default_api_key_entry(), GuiApiKeyEntry { key: "gui-key".to_string(), - remark: "测试密钥".to_string(), + remark: "Test key".to_string(), }, ], api_access_remarks: Vec::new(), diff --git a/src-tauri/src/tests/core_runtime.rs b/src-tauri/src/tests/core_runtime.rs index 4b8095d..06e4425 100644 --- a/src-tauri/src/tests/core_runtime.rs +++ b/src-tauri/src/tests/core_runtime.rs @@ -121,11 +121,11 @@ fn exiting_rejects_new_core_operations_and_cleans_up_an_in_flight_child() { assert!(lock_core_operation(&state) .unwrap_err() - .contains("应用正在退出")); + .contains("Application is exiting")); assert!(state .store_child(child) .unwrap_err() - .contains("应用正在退出")); + .contains("Application is exiting")); assert!(!is_process_alive(child_id)); assert_eq!(state.managed_pid(), None); } @@ -416,7 +416,7 @@ fn successful_core_install_reports_automatic_restart_failure() { let result = combine_install_and_restart_results(Ok("installed"), Err("port busy".into())); assert_eq!( result.unwrap_err(), - "内核已安装,但自动恢复运行失败: port busy" + "Kernel installed, but failed to automatically resume operation: port busy" ); } @@ -435,7 +435,7 @@ fn failed_core_install_reports_restart_failure_too() { ); assert_eq!( result.unwrap_err(), - "checksum mismatch;自动恢复原内核运行状态也失败: port busy" + "checksum mismatch; failed to automatically restore the previous kernel running state: port busy" ); } @@ -534,7 +534,7 @@ fn overlaying_a_core_updates_packaged_files_and_preserves_plugins() { .unwrap() .ino(), original_binary_inode, - "更新后的内核必须使用新 inode" + "The updated kernel must use a new inode" ); assert!(!staging_dir.exists()); fs::remove_dir_all(root).unwrap(); @@ -959,7 +959,7 @@ fn core_start_log_captures_stdout_and_stderr() { assert!(command.status().unwrap().success()); let output = fs::read_to_string(&log_path).unwrap(); - assert!(output.contains("===== CPA 内核启动")); + assert!(output.contains("===== CPA kernel startup")); assert!(output.contains("core stdout marker")); assert!(output.contains("core stderr marker")); assert!(!output.contains("stale startup output")); diff --git a/src-tauri/src/tests/desktop_alias_routing.rs b/src-tauri/src/tests/desktop_alias_routing.rs index 1496fe5..8d316bf 100644 --- a/src-tauri/src/tests/desktop_alias_routing.rs +++ b/src-tauri/src/tests/desktop_alias_routing.rs @@ -156,7 +156,7 @@ fn desktop_custom_alias_edits_remove_old_managed_routes_and_preserve_user_routes assert!(entries.iter().any(|m| m["alias"] == "claude-user-model")); assert!(!entries.iter().any(|m| m["alias"] == "claude-opus-4-6" || m["alias"] == "claude-sonnet-4-6")); assert!(ensure_claude_desktop_model_aliases_in_yaml(input, - &custom_mappings(&[("gpt-two", "claude-user-model")]), &models).unwrap_err().contains("已被其他模型使用")); + &custom_mappings(&[("gpt-two", "claude-user-model")]), &models).unwrap_err().contains("already used by another model")); assert!(ensure_claude_desktop_model_aliases_in_yaml(input, &custom_mappings(&[("gpt-two", "claude-native-model")]), &test_agent_models(&["gpt-two", "claude-native-model"])).is_err()); @@ -195,7 +195,7 @@ fn desktop_manual_source_can_create_an_alias_when_missing_from_the_loaded_model_ assert!(routed["openai-compatibility"][0]["models"].as_array().unwrap().iter() .any(|entry| entry["name"] == model && entry["alias"] == "claude-opus-5")); assert!(ensure_claude_desktop_model_aliases_in_yaml("{}", &mappings, &[]) - .unwrap_err().contains("无法确定模型 manual-model")); + .unwrap_err().contains("CPA configuration source for model manual-model")); } #[test] diff --git a/src-tauri/src/tests/model_aliases.rs b/src-tauri/src/tests/model_aliases.rs index 563f58b..7e64dd2 100644 --- a/src-tauri/src/tests/model_aliases.rs +++ b/src-tauri/src/tests/model_aliases.rs @@ -565,10 +565,10 @@ fn thinking_alias_effort_accepts_provider_defined_levels() { #[test] fn existing_aliases_with_spaces_can_be_loaded_and_deleted() { assert_eq!( - existing_thinking_alias_model_id(" Codex Auto Review ", "别名模型").unwrap(), + existing_thinking_alias_model_id(" Codex Auto Review ", "Alias model").unwrap(), "Codex Auto Review" ); - assert!(validate_thinking_alias_model_id("Codex Auto Review", "别名模型").is_err()); + assert!(validate_thinking_alias_model_id("Codex Auto Review", "Alias model").is_err()); let content = "codex-api-key:\n - name: provider\n base-url: https://www.loomex.cc\n models:\n - name: codex-auto-review\n alias: Codex Auto Review\n - name: keep-me\n"; let entries = thinking_aliases_from_yaml(content).unwrap(); assert_eq!(entries.len(), 1); @@ -615,7 +615,7 @@ fn thinking_alias_rejects_duplicate_client_visible_name() { assert!( add_model_alias_to_yaml(input, &source, "GPT-5.5-HIGH", "high", false) .unwrap_err() - .contains("已存在") + .contains("already exists") ); } diff --git a/src-tauri/src/tests/platform.rs b/src-tauri/src/tests/platform.rs index c13da07..d9e4741 100644 --- a/src-tauri/src/tests/platform.rs +++ b/src-tauri/src/tests/platform.rs @@ -51,14 +51,14 @@ fn windows_tray_presentation_tracks_core_state_and_busy_actions() { } #[test] -fn app_locale_normalization_has_a_stable_chinese_fallback() { +fn app_locale_normalization_has_a_stable_english_fallback() { assert_eq!(normalize_app_locale("en"), "en"); assert_eq!(normalize_app_locale("en-US"), "en"); assert_eq!(normalize_app_locale("ja-JP"), "ja"); assert_eq!(normalize_app_locale("zh-TW"), "zh-TW"); assert_eq!(normalize_app_locale("zh-Hant-HK"), "zh-TW"); - assert_eq!(normalize_app_locale("unsupported"), "zh-CN"); - assert_eq!(GuiConfigFile::default().locale, "zh-CN"); + assert_eq!(normalize_app_locale("unsupported"), "en"); + assert_eq!(GuiConfigFile::default().locale, "en"); } #[cfg(target_os = "windows")] diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index 4990057..f261950 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -1,4 +1,5 @@ use super::*; +use crate::native_i18n::{native_operation_failed, native_text, NativeText}; #[cfg(target_os = "macos")] use objc2_app_kit::NSStatusItemBehavior; @@ -18,7 +19,7 @@ pub(crate) struct MacosTrayClickState { #[cfg(target_os = "macos")] pub(crate) fn set_macos_dock_visible(app_handle: &tauri::AppHandle, visible: bool) { if let Err(error) = app_handle.set_dock_visibility(visible) { - eprintln!("更新 Dock 图标状态失败: {error}"); + eprintln!("Failed to update Dock icon visibility: {error}"); } } @@ -29,17 +30,17 @@ pub(crate) fn show_main_window_on_main_thread(app_handle: &tauri::AppHandle) { }; set_macos_dock_visible(app_handle, true); if let Err(error) = window.show() { - eprintln!("显示主窗口失败: {error}"); + eprintln!("Failed to show the main window: {error}"); set_macos_dock_visible(app_handle, false); return; } if window.is_minimized().unwrap_or(false) { if let Err(error) = window.unminimize() { - eprintln!("恢复主窗口失败: {error}"); + eprintln!("Failed to restore the main window: {error}"); } } if let Err(error) = window.set_focus() { - eprintln!("聚焦主窗口失败: {error}"); + eprintln!("Failed to focus the main window: {error}"); } } @@ -54,7 +55,7 @@ pub(crate) fn show_main_window(app_handle: &tauri::AppHandle) { if let Err(error) = app_handle.clone().run_on_main_thread(move || { show_main_window_on_main_thread(&app_handle); }) { - eprintln!("调度主窗口显示失败: {error}"); + eprintln!("Failed to schedule showing the main window: {error}"); } } @@ -72,7 +73,7 @@ pub(crate) fn show_macos_tray_menu(tray: &TrayIcon) { }); if let Err(error) = result { - eprintln!("显示托盘菜单失败: {error}"); + eprintln!("Failed to show the tray menu: {error}"); } } @@ -82,18 +83,18 @@ pub(crate) fn setup_macos_tray(app: &mut tauri::App) -> tauri::Resul .state::() .snapshot() .map(|config| config.locale) - .unwrap_or_else(|_| "zh-CN".to_string()); + .unwrap_or_else(|_| "en".to_string()); let open_main_window = MenuItem::with_id( app, "open-main-window", - locale_text(&locale, "打开主界面", "Open Main Window"), + native_text(&locale, NativeText::OpenMainWindow), true, None::<&str>, )?; let quit = MenuItem::with_id( app, "quit", - locale_text(&locale, "退出", "Quit"), + native_text(&locale, NativeText::Quit), true, None::<&str>, )?; @@ -128,7 +129,7 @@ pub(crate) fn setup_macos_tray(app: &mut tauri::App) -> tauri::Resul let now = Instant::now(); let Ok(mut state) = click_state.lock() else { - eprintln!("读取托盘点击状态失败"); + eprintln!("Failed to read tray click state"); return; }; state.sequence += 1; @@ -159,7 +160,7 @@ pub(crate) fn setup_macos_tray(app: &mut tauri::App) -> tauri::Resul } Ok(_) => false, Err(_) => { - eprintln!("读取托盘点击状态失败"); + eprintln!("Failed to read tray click state"); false } }; @@ -220,45 +221,29 @@ pub(crate) fn windows_tray_presentation( locale: &str, ) -> WindowsTrayPresentation { let status_text = if busy { - locale_text(locale, "内核状态:处理中", "Core status: Working") + native_text(locale, NativeText::CoreStatusWorking) } else if !status.installed { - locale_text(locale, "内核状态:未安装", "Core status: Not installed") + native_text(locale, NativeText::CoreStatusNotInstalled) } else if status.running { - locale_text(locale, "内核状态:运行中", "Core status: Running") + native_text(locale, NativeText::CoreStatusRunning) } else { - locale_text(locale, "内核状态:已停止", "Core status: Stopped") + native_text(locale, NativeText::CoreStatusStopped) }; let toggle_text = if busy { - locale_text(locale, "处理中...", "Working...") + native_text(locale, NativeText::Working) } else if status.running { - locale_text(locale, "停止内核", "Stop Core") + native_text(locale, NativeText::StopCore) } else { - locale_text(locale, "启动内核", "Start Core") + native_text(locale, NativeText::StartCore) }; let tooltip = if busy { - locale_text( - locale, - "EasyCLIProxyAPI · 内核处理中", - "EasyCLIProxyAPI · Core working", - ) + native_text(locale, NativeText::TooltipCoreWorking) } else if !status.installed { - locale_text( - locale, - "EasyCLIProxyAPI · 内核未安装", - "EasyCLIProxyAPI · Core not installed", - ) + native_text(locale, NativeText::TooltipCoreNotInstalled) } else if status.running { - locale_text( - locale, - "EasyCLIProxyAPI · 内核运行中", - "EasyCLIProxyAPI · Core running", - ) + native_text(locale, NativeText::TooltipCoreRunning) } else { - locale_text( - locale, - "EasyCLIProxyAPI · 内核已停止", - "EasyCLIProxyAPI · Core stopped", - ) + native_text(locale, NativeText::TooltipCoreStopped) }; WindowsTrayPresentation { @@ -287,7 +272,7 @@ impl WindowsTrayState { self.locale .lock() .map(|locale| locale.clone()) - .unwrap_or_else(|_| "zh-CN".to_string()) + .unwrap_or_else(|_| "en".to_string()) } fn set_locale(&self, locale: &str) { @@ -298,17 +283,17 @@ impl WindowsTrayState { let labels = [ ( &self.open_main_window, - locale_text(normalized, "打开主界面", "Open Main Window"), + native_text(normalized, NativeText::OpenMainWindow), ), ( &self.restart_core_item, - locale_text(normalized, "重启内核", "Restart Core"), + native_text(normalized, NativeText::RestartCore), ), - (&self.quit_item, locale_text(normalized, "退出", "Quit")), + (&self.quit_item, native_text(normalized, NativeText::Quit)), ]; for (item, label) in labels { if let Err(error) = item.set_text(label) { - eprintln!("更新 Windows 托盘语言失败: {error}"); + eprintln!("Failed to update Windows tray language: {error}"); } } } @@ -323,24 +308,22 @@ impl WindowsTrayState { } let locale = self.locale(); - if let Err(error) = self.status_item.set_text(locale_text( - &locale, - "内核状态:处理中", - "Core status: Working", - )) { - eprintln!("更新 Windows 托盘内核状态失败: {error}"); + if let Err(error) = self + .status_item + .set_text(native_text(&locale, NativeText::CoreStatusWorking)) { + eprintln!("Failed to update Windows tray core status: {error}"); } if let Err(error) = self.toggle_core_item - .set_text(locale_text(&locale, "处理中...", "Working...")) + .set_text(native_text(&locale, NativeText::Working)) { - eprintln!("更新 Windows 托盘操作文本失败: {error}"); + eprintln!("Failed to update Windows tray action text: {error}"); } if let Err(error) = self.toggle_core_item.set_enabled(false) { - eprintln!("更新 Windows 托盘操作状态失败: {error}"); + eprintln!("Failed to update Windows tray action state: {error}"); } if let Err(error) = self.restart_core_item.set_enabled(false) { - eprintln!("更新 Windows 托盘重启状态失败: {error}"); + eprintln!("Failed to update Windows tray restart state: {error}"); } true } @@ -353,25 +336,25 @@ impl WindowsTrayState { let presentation = windows_tray_presentation(status, self.busy.load(Ordering::Acquire), &self.locale()); if let Err(error) = self.status_item.set_text(presentation.status_text.clone()) { - eprintln!("更新 Windows 托盘内核状态失败: {error}"); + eprintln!("Failed to update Windows tray core status: {error}"); } if let Err(error) = self .toggle_core_item .set_text(presentation.toggle_text.clone()) { - eprintln!("更新 Windows 托盘操作文本失败: {error}"); + eprintln!("Failed to update Windows tray action text: {error}"); } if let Err(error) = self .toggle_core_item .set_enabled(presentation.toggle_enabled) { - eprintln!("更新 Windows 托盘操作状态失败: {error}"); + eprintln!("Failed to update Windows tray action state: {error}"); } if let Err(error) = self .restart_core_item .set_enabled(presentation.restart_enabled) { - eprintln!("更新 Windows 托盘重启状态失败: {error}"); + eprintln!("Failed to update Windows tray restart state: {error}"); } presentation.tooltip } @@ -381,14 +364,9 @@ impl WindowsTrayState { if error.chars().count() > 48 { summary.push('…'); } - let text = match normalize_app_locale(&self.locale()) { - "en" => format!("Operation failed: {summary}"), - "zh-TW" => format!("操作失敗:{summary}"), - "ja" => format!("操作に失敗しました:{summary}"), - _ => format!("操作失败:{summary}"), - }; + let text = native_operation_failed(&self.locale(), &summary); if let Err(update_error) = self.status_item.set_text(text) { - eprintln!("更新 Windows 托盘错误状态失败: {update_error}"); + eprintln!("Failed to update Windows tray error state: {update_error}"); } } } @@ -399,16 +377,16 @@ pub(crate) fn show_windows_main_window(app_handle: &tauri::AppHandle) { return; }; if let Err(error) = window.show() { - eprintln!("显示主窗口失败: {error}"); + eprintln!("Failed to show the main window: {error}"); return; } if window.is_minimized().unwrap_or(false) { if let Err(error) = window.unminimize() { - eprintln!("恢复主窗口失败: {error}"); + eprintln!("Failed to restore the main window: {error}"); } } if let Err(error) = window.set_focus() { - eprintln!("聚焦主窗口失败: {error}"); + eprintln!("Failed to focus the main window: {error}"); } } @@ -417,11 +395,11 @@ pub(crate) fn update_windows_tray_status(app_handle: &tauri::AppHandle, status: let tooltip = app_handle .try_state::() .map(|tray_state| tray_state.update(status)) - .unwrap_or_else(|| windows_tray_presentation(status, false, "zh-CN").tooltip); + .unwrap_or_else(|| windows_tray_presentation(status, false, "en").tooltip); if let Some(tray) = app_handle.tray_by_id(WINDOWS_TRAY_ID) { if let Err(error) = tray.set_tooltip(Some(&tooltip)) { - eprintln!("更新 Windows 托盘提示失败: {error}"); + eprintln!("Failed to update Windows tray tooltip: {error}"); } } } @@ -440,21 +418,20 @@ pub(crate) fn update_windows_tray_locale( #[cfg(target_os = "windows")] pub(crate) fn show_windows_tray_action_error(app_handle: &tauri::AppHandle, error: &str) { - eprintln!("Windows 托盘内核操作失败: {error}"); + eprintln!("Windows tray core operation failed: {error}"); let locale = app_handle .try_state::() .map(|state| state.locale()) - .unwrap_or_else(|| "zh-CN".to_string()); + .unwrap_or_else(|| "en".to_string()); if let Some(tray_state) = app_handle.try_state::() { tray_state.show_error(error); } if let Some(tray) = app_handle.tray_by_id(WINDOWS_TRAY_ID) { - if let Err(update_error) = tray.set_tooltip(Some(locale_text( + if let Err(update_error) = tray.set_tooltip(Some(native_text( &locale, - "EasyCLIProxyAPI · 内核操作失败", - "EasyCLIProxyAPI · Core operation failed", + NativeText::TooltipCoreOperationFailed, ))) { - eprintln!("更新 Windows 托盘错误提示失败: {update_error}"); + eprintln!("Failed to update Windows tray error tooltip: {update_error}"); } } } @@ -471,12 +448,11 @@ pub(crate) fn run_windows_tray_core_action( return; } if let Some(tray) = app_handle.tray_by_id(WINDOWS_TRAY_ID) { - if let Err(error) = tray.set_tooltip(Some(locale_text( + if let Err(error) = tray.set_tooltip(Some(native_text( &tray_state.locale(), - "EasyCLIProxyAPI · 内核处理中", - "EasyCLIProxyAPI · Core working", + NativeText::TooltipCoreWorking, ))) { - eprintln!("更新 Windows 托盘处理中提示失败: {error}"); + eprintln!("Failed to update Windows tray busy tooltip: {error}"); } } @@ -535,39 +511,39 @@ pub(crate) fn setup_windows_tray(app: &mut tauri::App) -> tauri::Res .state::() .snapshot() .map(|config| config.locale) - .unwrap_or_else(|_| "zh-CN".to_string()); + .unwrap_or_else(|_| "en".to_string()); let open_main_window = MenuItem::with_id( app, WINDOWS_TRAY_OPEN_MENU_ID, - locale_text(&locale, "打开主界面", "Open Main Window"), + native_text(&locale, NativeText::OpenMainWindow), true, None::<&str>, )?; let status_item = MenuItem::with_id( app, WINDOWS_TRAY_STATUS_MENU_ID, - locale_text(&locale, "内核状态:正在检查", "Core status: Checking"), + native_text(&locale, NativeText::CoreStatusChecking), false, None::<&str>, )?; let toggle_core_item = MenuItem::with_id( app, WINDOWS_TRAY_TOGGLE_CORE_MENU_ID, - locale_text(&locale, "启动内核", "Start Core"), + native_text(&locale, NativeText::StartCore), false, None::<&str>, )?; let restart_core_item = MenuItem::with_id( app, WINDOWS_TRAY_RESTART_CORE_MENU_ID, - locale_text(&locale, "重启内核", "Restart Core"), + native_text(&locale, NativeText::RestartCore), false, None::<&str>, )?; let quit = MenuItem::with_id( app, WINDOWS_TRAY_QUIT_MENU_ID, - locale_text(&locale, "退出", "Quit"), + native_text(&locale, NativeText::Quit), true, None::<&str>, )?; diff --git a/src-tauri/src/usage.rs b/src-tauri/src/usage.rs index 770bab7..72eb247 100644 --- a/src-tauri/src/usage.rs +++ b/src-tauri/src/usage.rs @@ -92,7 +92,7 @@ where task() }) .await - .map_err(|error| format!("使用记录后台任务失败: {error}"))? + .map_err(|error| format!("Usage records background task failed: {error}"))? } pub(crate) struct UsageCollectorState { @@ -171,7 +171,7 @@ impl UsageCollectorStatus { fn waiting() -> Self { Self { state: "waiting-core".to_string(), - message: "等待内核启动".to_string(), + message: "Waiting for kernel startup".to_string(), last_collected_at: None, total_records: 0, } @@ -544,7 +544,7 @@ impl UsageCollectorState { self.inner .lock() .map(|inner| inner.status.clone()) - .map_err(|_| "使用记录采集状态锁已损坏".to_string()) + .map_err(|_| "Usage collection status lock is poisoned".to_string()) } } @@ -573,20 +573,20 @@ fn migrate_usage_storage_directory(source: &Path, target: &Path) -> Result<(), S } let target_parent = target .parent() - .ok_or_else(|| "使用记录目标目录没有父目录".to_string())?; + .ok_or_else(|| "Usage records target directory has no parent directory".to_string())?; fs::create_dir_all(target_parent) - .map_err(|error| format!("创建使用记录迁移目标目录失败: {error}"))?; + .map_err(|error| format!("Failed to create usage records migration target directory: {error}"))?; if fs::rename(source, target).is_ok() { return Ok(()); } if let Err(error) = copy_usage_storage_directory(source, target) { let _ = fs::remove_dir_all(target); - return Err(format!("迁移旧版使用记录失败: {error}")); + return Err(format!("Failed to migrate legacy usage records: {error}")); } if let Err(error) = fs::remove_dir_all(source) { eprintln!( - "旧版使用记录已复制,但无法清理原目录 {}: {error}", + "Legacy usage records were copied, but the original directory could not be cleaned up {}: {error}", source.display() ); } @@ -595,22 +595,22 @@ fn migrate_usage_storage_directory(source: &Path, target: &Path) -> Result<(), S #[cfg(any(target_os = "macos", test))] fn copy_usage_storage_directory(source: &Path, target: &Path) -> Result<(), String> { - fs::create_dir_all(target).map_err(|error| format!("创建使用记录目录失败: {error}"))?; - let entries = fs::read_dir(source).map_err(|error| format!("读取旧版使用记录失败: {error}"))?; + fs::create_dir_all(target).map_err(|error| format!("Failed to create usage records directory: {error}"))?; + let entries = fs::read_dir(source).map_err(|error| format!("Failed to read legacy usage records: {error}"))?; for entry in entries { - let entry = entry.map_err(|error| format!("读取旧版使用记录项目失败: {error}"))?; + let entry = entry.map_err(|error| format!("Failed to read legacy usage record entry: {error}"))?; let file_type = entry .file_type() - .map_err(|error| format!("读取旧版使用记录项目类型失败: {error}"))?; + .map_err(|error| format!("Failed to read legacy usage record entry type: {error}"))?; let destination = target.join(entry.file_name()); if file_type.is_dir() { copy_usage_storage_directory(&entry.path(), &destination)?; } else if file_type.is_file() { fs::copy(entry.path(), &destination) - .map_err(|error| format!("复制旧版使用记录失败: {error}"))?; + .map_err(|error| format!("Failed to copy legacy usage records: {error}"))?; } else { return Err(format!( - "旧版使用记录包含不支持的文件类型: {}", + "Legacy usage records contain an unsupported file type: {}", entry.path().display() )); } @@ -619,11 +619,11 @@ fn copy_usage_storage_directory(source: &Path, target: &Path) -> Result<(), Stri } fn initialize_usage_storage_at(root: &Path) -> Result<(), String> { - fs::create_dir_all(root).map_err(|error| format!("创建使用记录目录失败: {error}"))?; + fs::create_dir_all(root).map_err(|error| format!("Failed to create usage records directory: {error}"))?; let mut connection = open_usage_database_at(root)?; connection .pragma_update(None, "journal_mode", "WAL") - .map_err(|error| format!("启用 SQLite WAL 失败: {error}"))?; + .map_err(|error| format!("Failed to enable SQLite WAL: {error}"))?; migrate_usage_database(&mut connection, root)?; migrate_legacy_json_storage(&mut connection, root)?; cleanup_usage_inbox(&connection, Local::now())?; @@ -700,7 +700,7 @@ fn repair_usage_cache_records_at(root: &Path) -> Result Result Result Result Result<() let backup_path = create_usage_migration_backup(connection, root)?; if let Err(error) = migrate_legacy_v2_usage_schema(connection) { return Err(format!( - "迁移旧版使用记录数据库失败,备份保留在 {}: {error}", + "Failed to migrate legacy usage records database; backup retained at {}: {error}", backup_path.display() )); } @@ -800,7 +800,7 @@ fn detect_usage_database_layout(connection: &Connection) -> Result Result Result { @@ -836,18 +836,18 @@ fn usage_table_exists(connection: &Connection, table: &str) -> Result(0), ) .map(|exists| exists != 0) - .map_err(|error| format!("检查 SQLite 表 {table} 失败: {error}")) + .map_err(|error| format!("Failed to inspect SQLite table {table}: {error}")) } fn usage_table_columns(connection: &Connection, table: &str) -> Result, String> { let mut statement = connection .prepare("SELECT name FROM pragma_table_info(?1)") - .map_err(|error| format!("准备读取 SQLite 表结构失败 {table}: {error}"))?; + .map_err(|error| format!("Failed to prepare SQLite table schema read for {table}: {error}"))?; let columns = statement .query_map(params![table], |row| row.get::<_, String>(0)) - .map_err(|error| format!("读取 SQLite 表结构失败 {table}: {error}"))? + .map_err(|error| format!("Failed to read SQLite table schema {table}: {error}"))? .collect::, _>>() - .map_err(|error| format!("解析 SQLite 表结构失败 {table}: {error}"))?; + .map_err(|error| format!("Failed to parse SQLite table schema {table}: {error}"))?; Ok(columns) } @@ -1072,14 +1072,14 @@ fn replace_sql_fragment_case_insensitive( fn create_usage_migration_backup(connection: &Connection, root: &Path) -> Result { let backup_dir = root.join(USAGE_BACKUP_DIR_NAME); fs::create_dir_all(&backup_dir) - .map_err(|error| format!("创建使用记录备份目录失败: {error}"))?; + .map_err(|error| format!("Failed to create usage records backup directory: {error}"))?; let backup_path = backup_dir.join(format!("usage-before-keeper-v3-{}.db", unique_file_stamp())); connection .execute( "VACUUM INTO ?1", params![backup_path.to_string_lossy().to_string()], ) - .map_err(|error| format!("备份旧版使用记录数据库失败: {error}"))?; + .map_err(|error| format!("Failed to back up legacy usage records database: {error}"))?; Ok(backup_path) } @@ -1087,7 +1087,7 @@ fn migrate_legacy_v2_usage_schema(connection: &mut Connection) -> Result<(), Str let before = load_usage_migration_snapshot(connection)?; let transaction = connection .transaction() - .map_err(|error| format!("开始使用记录数据库迁移事务失败: {error}"))?; + .map_err(|error| format!("Failed to begin usage records database migration transaction: {error}"))?; transaction .execute_batch( r#" @@ -1123,11 +1123,11 @@ fn migrate_legacy_v2_usage_schema(connection: &mut Connection) -> Result<(), Str collector_source = 'legacy_migration'; "#, ) - .map_err(|error| format!("转换旧版使用记录字段失败: {error}"))?; + .map_err(|error| format!("Failed to convert legacy usage record fields: {error}"))?; let after = load_usage_migration_snapshot(&transaction)?; if before != after { return Err(format!( - "使用记录迁移校验不一致,迁移前 {before:?},迁移后 {after:?}" + "Usage record migration verification mismatch: before {before:?}, after {after:?}" )); } initialize_usage_schema(&transaction)?; @@ -1143,10 +1143,10 @@ fn migrate_legacy_v2_usage_schema(connection: &mut Connection) -> Result<(), Str "INSERT INTO usage_metadata (key, value) VALUES (?1, ?2) ON CONFLICT(key) DO UPDATE SET value = excluded.value", params![USAGE_DATABASE_MIGRATION_KEY, Local::now().to_rfc3339()], ) - .map_err(|error| format!("写入使用记录迁移标记失败: {error}"))?; + .map_err(|error| format!("Failed to write usage record migration marker: {error}"))?; transaction .commit() - .map_err(|error| format!("提交使用记录数据库迁移失败: {error}")) + .map_err(|error| format!("Failed to commit usage records database migration: {error}")) } fn load_usage_migration_snapshot( @@ -1186,7 +1186,7 @@ fn load_usage_migration_snapshot( }) }, ) - .map_err(|error| format!("读取使用记录迁移校验快照失败: {error}")) + .map_err(|error| format!("Failed to read usage record migration verification snapshot: {error}")) } fn initialize_usage_schema(connection: &Connection) -> Result<(), String> { @@ -1301,17 +1301,17 @@ fn initialize_usage_schema(connection: &Connection) -> Result<(), String> { ); "#, ) - .map_err(|error| format!("初始化 SQLite 使用记录结构失败: {error}"))?; + .map_err(|error| format!("Failed to initialize SQLite usage records schema: {error}"))?; ensure_usage_failure_columns(connection)?; connection .execute( "CREATE INDEX IF NOT EXISTS idx_usage_events_canceled_timestamp ON usage_events(canceled, timestamp_ms DESC)", [], ) - .map_err(|error| format!("创建 SQLite 取消记录索引失败: {error}"))?; + .map_err(|error| format!("Failed to create SQLite canceled-record index: {error}"))?; connection .pragma_update(None, "user_version", USAGE_DATABASE_SCHEMA_VERSION) - .map_err(|error| format!("更新 SQLite 使用记录版本失败: {error}")) + .map_err(|error| format!("Failed to update SQLite usage records version: {error}")) } fn ensure_usage_failure_columns(connection: &Connection) -> Result<(), String> { @@ -1329,7 +1329,7 @@ fn ensure_usage_failure_columns(connection: &Connection) -> Result<(), String> { &format!("ALTER TABLE usage_events ADD COLUMN {column} {definition}"), [], ) - .map_err(|error| format!("添加 SQLite 使用记录字段 {column} 失败: {error}"))?; + .map_err(|error| format!("Failed to add SQLite usage record field {column}: {error}"))?; columns.insert(column.to_string()); } backfill_usage_failure_details(connection)?; @@ -1344,7 +1344,7 @@ fn backfill_usage_failure_details(connection: &Connection) -> Result<(), String> |row| row.get::<_, String>(0), ) .optional() - .map_err(|error| format!("读取使用记录失败详情迁移状态失败: {error}"))?; + .map_err(|error| format!("Failed to read usage record failure-details migration status: {error}"))?; if migrated.is_some() { return Ok(()); } @@ -1359,14 +1359,14 @@ fn backfill_usage_failure_details(connection: &Connection) -> Result<(), String> WHERE status = 'processed' AND usage_event_key != '' "#, ) - .map_err(|error| format!("准备回填使用记录失败详情失败: {error}"))?; + .map_err(|error| format!("Failed to prepare usage record failure-details backfill: {error}"))?; let rows = statement .query_map([], |row| { Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)) }) - .map_err(|error| format!("查询使用记录失败详情失败: {error}"))? + .map_err(|error| format!("Failed to query usage record failure details: {error}"))? .collect::, _>>() - .map_err(|error| format!("读取使用记录失败详情失败: {error}"))?; + .map_err(|error| format!("Failed to read usage record failure details: {error}"))?; rows }; for (event_key, raw_message) in rows { @@ -1394,7 +1394,7 @@ fn backfill_usage_failure_details(connection: &Connection) -> Result<(), String> "#, params![canceled, i64::from(failure_status), failure_body, event_key], ) - .map_err(|error| format!("回填使用记录失败详情失败: {error}"))?; + .map_err(|error| format!("Failed to backfill usage record failure details: {error}"))?; } } @@ -1403,7 +1403,7 @@ fn backfill_usage_failure_details(connection: &Connection) -> Result<(), String> "INSERT INTO usage_metadata (key, value) VALUES (?1, ?2)", params![USAGE_FAILURE_MIGRATION_KEY, Local::now().to_rfc3339()], ) - .map_err(|error| format!("记录使用记录失败详情迁移状态失败: {error}"))?; + .map_err(|error| format!("Failed to record usage record failure-details migration status: {error}"))?; Ok(()) } @@ -1412,19 +1412,19 @@ fn open_usage_database() -> Result { } fn open_usage_database_at(root: &Path) -> Result { - fs::create_dir_all(root).map_err(|error| format!("创建使用记录目录失败: {error}"))?; + fs::create_dir_all(root).map_err(|error| format!("Failed to create usage records directory: {error}"))?; let path = root.join(USAGE_DATABASE_FILE); let connection = Connection::open(&path) - .map_err(|error| format!("打开 SQLite 使用记录数据库失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to open SQLite usage records database {}: {error}", path.display()))?; connection .busy_timeout(Duration::from_secs(SQLITE_BUSY_TIMEOUT_SECONDS)) - .map_err(|error| format!("设置 SQLite busy timeout 失败: {error}"))?; + .map_err(|error| format!("Failed to set SQLite busy timeout: {error}"))?; connection .pragma_update(None, "foreign_keys", "ON") - .map_err(|error| format!("启用 SQLite foreign keys 失败: {error}"))?; + .map_err(|error| format!("Failed to enable SQLite foreign keys: {error}"))?; connection .pragma_update(None, "synchronous", "NORMAL") - .map_err(|error| format!("设置 SQLite synchronous 模式失败: {error}"))?; + .map_err(|error| format!("Failed to set SQLite synchronous mode: {error}"))?; Ok(connection) } @@ -1436,12 +1436,12 @@ fn load_usage_database_limit(connection: &Connection) -> Result { |row| row.get::<_, String>(0), ) .optional() - .map_err(|error| format!("读取使用记录数据库大小限制失败: {error}"))?; + .map_err(|error| format!("Failed to read usage records database size limit: {error}"))?; value .map(|value| { value .parse::() - .map_err(|error| format!("使用记录数据库大小限制无效: {error}")) + .map_err(|error| format!("Invalid usage records database size limit: {error}")) }) .transpose() .map(|value| value.unwrap_or(0)) @@ -1453,7 +1453,7 @@ fn save_usage_database_limit( ) -> Result<(), String> { max_database_size_mb .checked_mul(BYTES_PER_MB) - .ok_or_else(|| "使用记录数据库大小限制过大".to_string())?; + .ok_or_else(|| "Usage records database size limit is too large".to_string())?; connection .execute( r#"INSERT INTO usage_metadata (key, value) VALUES (?1, ?2) @@ -1461,35 +1461,35 @@ fn save_usage_database_limit( params![USAGE_DATABASE_MAX_MB_KEY, max_database_size_mb.to_string()], ) .map(|_| ()) - .map_err(|error| format!("保存使用记录数据库大小限制失败: {error}")) + .map_err(|error| format!("Failed to save usage records database size limit: {error}")) } fn usage_database_active_bytes(connection: &Connection) -> Result { let page_size = connection .query_row("PRAGMA page_size", [], |row| row.get::<_, u64>(0)) - .map_err(|error| format!("读取使用记录数据库页大小失败: {error}"))?; + .map_err(|error| format!("Failed to read usage records database page size: {error}"))?; let page_count = connection .query_row("PRAGMA page_count", [], |row| row.get::<_, u64>(0)) - .map_err(|error| format!("读取使用记录数据库页数失败: {error}"))?; + .map_err(|error| format!("Failed to read usage records database page count: {error}"))?; let free_pages = connection .query_row("PRAGMA freelist_count", [], |row| row.get::<_, u64>(0)) - .map_err(|error| format!("读取使用记录数据库空闲页数失败: {error}"))?; + .map_err(|error| format!("Failed to read usage records database free page count: {error}"))?; page_count .saturating_sub(free_pages) .checked_mul(page_size) - .ok_or_else(|| "使用记录数据库大小溢出".to_string()) + .ok_or_else(|| "Usage records database size overflow".to_string()) } fn usage_database_allocated_bytes(connection: &Connection) -> Result { let page_size = connection .query_row("PRAGMA page_size", [], |row| row.get::<_, u64>(0)) - .map_err(|error| format!("读取使用记录数据库页大小失败: {error}"))?; + .map_err(|error| format!("Failed to read usage records database page size: {error}"))?; let page_count = connection .query_row("PRAGMA page_count", [], |row| row.get::<_, u64>(0)) - .map_err(|error| format!("读取使用记录数据库页数失败: {error}"))?; + .map_err(|error| format!("Failed to read usage records database page count: {error}"))?; page_count .checked_mul(page_size) - .ok_or_else(|| "使用记录数据库大小溢出".to_string()) + .ok_or_else(|| "Usage records database size overflow".to_string()) } fn usage_database_file_bytes(database_path: &Path) -> Result { @@ -1503,7 +1503,7 @@ fn usage_database_file_bytes(database_path: &Path) -> Result { Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} Err(error) => { return Err(format!( - "读取使用记录数据库文件大小失败 {}: {error}", + "Failed to read usage records database file size {}: {error}", path.display() )); } @@ -1519,7 +1519,7 @@ fn usage_database_disk_bytes(root: &Path) -> Result { fn usage_database_connection_disk_bytes(connection: &Connection) -> Result { let path = connection .path() - .ok_or_else(|| "无法读取使用记录数据库文件路径".to_string())?; + .ok_or_else(|| "Unable to read usage records database file path".to_string())?; usage_database_file_bytes(Path::new(path)) } @@ -1542,7 +1542,7 @@ fn load_usage_storage_settings( .query_row("SELECT COUNT(*) FROM usage_events", [], |row| { row.get::<_, u64>(0) }) - .map_err(|error| format!("统计使用记录数量失败: {error}"))?; + .map_err(|error| format!("Failed to count usage records: {error}"))?; Ok(UsageStorageSettings { max_database_size_mb: load_usage_database_limit(connection)?, database_size_bytes: usage_database_disk_bytes(root)?, @@ -1558,7 +1558,7 @@ fn enforce_usage_database_limit(connection: &Connection) -> Result } let max_bytes = max_database_size_mb .checked_mul(BYTES_PER_MB) - .ok_or_else(|| "使用记录数据库大小限制过大".to_string())?; + .ok_or_else(|| "Usage records database size limit is too large".to_string())?; prune_usage_database_to_bytes(connection, max_bytes) } @@ -1567,11 +1567,11 @@ fn shrink_usage_database_to_mb( target_database_size_mb: u64, ) -> Result { if target_database_size_mb == 0 { - return Err("数据库瘦身目标必须大于 0 MB".to_string()); + return Err("Database compaction target must be greater than 0 MB".to_string()); } let target_bytes = target_database_size_mb .checked_mul(BYTES_PER_MB) - .ok_or_else(|| "数据库瘦身目标过大".to_string())?; + .ok_or_else(|| "Database compaction target is too large".to_string())?; prune_usage_database_to_bytes(connection, target_bytes) } @@ -1590,7 +1590,7 @@ fn prune_usage_database_to_bytes(connection: &Connection, max_bytes: u64) -> Res "DELETE FROM usage_inbox WHERE status IN ('processed', 'decode_failed', 'discarded')", [], ) - .map_err(|error| format!("清理使用记录临时数据失败: {error}"))?; + .map_err(|error| format!("Failed to clean up temporary usage data: {error}"))?; let mut active_bytes = usage_database_active_bytes(connection)?; let mut deleted_records = 0_u64; @@ -1599,7 +1599,7 @@ fn prune_usage_database_to_bytes(connection: &Connection, max_bytes: u64) -> Res .query_row("SELECT COUNT(*) FROM usage_events", [], |row| { row.get::<_, u64>(0) }) - .map_err(|error| format!("统计待清理使用记录失败: {error}"))?; + .map_err(|error| format!("Failed to count usage records pending cleanup: {error}"))?; if total_records == 0 { break; } @@ -1618,7 +1618,7 @@ fn prune_usage_database_to_bytes(connection: &Connection, max_bytes: u64) -> Res )"#, params![batch], ) - .map_err(|error| format!("删除最旧使用记录失败: {error}"))? + .map_err(|error| format!("Failed to delete oldest usage records: {error}"))? as u64; if deleted == 0 { break; @@ -1630,7 +1630,7 @@ fn prune_usage_database_to_bytes(connection: &Connection, max_bytes: u64) -> Res if allocated_bytes > max_bytes || inbox_deleted > 0 || deleted_records > 0 { connection .execute_batch("VACUUM;") - .map_err(|error| format!("回收使用记录数据库空间失败: {error}"))?; + .map_err(|error| format!("Failed to reclaim usage records database space: {error}"))?; truncate_usage_database_wal(connection); } Ok(deleted_records) @@ -1644,7 +1644,7 @@ fn migrate_legacy_json_storage(connection: &mut Connection, root: &Path) -> Resu |row| row.get::<_, String>(0), ) .optional() - .map_err(|error| format!("读取旧使用记录迁移状态失败: {error}"))? + .map_err(|error| format!("Failed to read legacy usage record migration status: {error}"))? .is_some(); if migrated { return Ok(()); @@ -1652,14 +1652,14 @@ fn migrate_legacy_json_storage(connection: &mut Connection, root: &Path) -> Resu let transaction = connection .transaction() - .map_err(|error| format!("开始旧使用记录迁移事务失败: {error}"))?; + .map_err(|error| format!("Failed to begin legacy usage record migration transaction: {error}"))?; let mut migrated_records = 0_usize; for path in sorted_json_files(&root.join(LEGACY_USAGE_EVENTS_DIR))? { let content = fs::read_to_string(&path) - .map_err(|error| format!("读取旧使用记录失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to read legacy usage record {}: {error}", path.display()))?; let file = serde_json::from_str::(&content) - .map_err(|error| format!("解析旧使用记录失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to parse legacy usage record {}: {error}", path.display()))?; validate_legacy_schema(file.schema_version, &path)?; migrated_records = migrated_records.saturating_add(insert_usage_records_in_transaction( &transaction, @@ -1669,9 +1669,9 @@ fn migrate_legacy_json_storage(connection: &mut Connection, root: &Path) -> Resu for path in sorted_json_files(&root.join(LEGACY_USAGE_INBOX_DIR))? { let content = fs::read_to_string(&path) - .map_err(|error| format!("读取旧使用记录收件箱失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to read legacy usage record inbox {}: {error}", path.display()))?; let file = serde_json::from_str::(&content) - .map_err(|error| format!("解析旧使用记录收件箱失败 {}: {error}", path.display()))?; + .map_err(|error| format!("Failed to parse legacy usage record inbox {}: {error}", path.display()))?; validate_legacy_schema(file.schema_version, &path)?; migrated_records = migrated_records.saturating_add(insert_usage_records_in_transaction( &transaction, @@ -1684,10 +1684,10 @@ fn migrate_legacy_json_storage(connection: &mut Connection, root: &Path) -> Resu "INSERT INTO usage_metadata (key, value) VALUES (?1, ?2)", params![LEGACY_JSON_MIGRATION_KEY, migrated_records.to_string()], ) - .map_err(|error| format!("记录旧使用记录迁移状态失败: {error}"))?; + .map_err(|error| format!("Failed to record legacy usage record migration status: {error}"))?; transaction .commit() - .map_err(|error| format!("提交旧使用记录迁移失败: {error}"))?; + .map_err(|error| format!("Failed to commit legacy usage record migration: {error}"))?; Ok(()) } @@ -1696,7 +1696,7 @@ fn sorted_json_files(directory: &Path) -> Result, String> { return Ok(Vec::new()); } let mut paths = fs::read_dir(directory) - .map_err(|error| format!("读取旧使用记录目录失败 {}: {error}", directory.display()))? + .map_err(|error| format!("Failed to read legacy usage records directory {}: {error}", directory.display()))? .filter_map(Result::ok) .map(|entry| entry.path()) .filter(|path| path.extension().and_then(|value| value.to_str()) == Some("json")) @@ -1710,7 +1710,7 @@ fn validate_legacy_schema(version: u8, path: &Path) -> Result<(), String> { Ok(()) } else { Err(format!( - "不支持的旧使用记录版本 {version}: {}", + "Unsupported legacy usage record version {version}: {}", path.display() )) } @@ -1744,7 +1744,7 @@ impl RedisUsageQueueSource { Ok(batch) } Err(legacy_error) => Err(format!( - "Redis usage 队列拉取失败(usage: {usage_error}; queue: {legacy_error})" + "Failed to fetch Redis usage queue (usage: {usage_error}; queue: {legacy_error})" )), } } @@ -1835,7 +1835,7 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { if let Err(error) = open_usage_database_at(&root) .and_then(|connection| cleanup_usage_inbox(&connection, Local::now())) { - eprintln!("清理使用记录 inbox 失败: {error}"); + eprintln!("Failed to clean up usage records inbox: {error}"); } next_inbox_cleanup_at = tokio::time::Instant::now() + Duration::from_secs(60 * 60); } @@ -1851,7 +1851,7 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { set_collector_status( &app, "collecting", - &format!("已恢复 {saved} 条待处理记录"), + &format!("Restored {saved} pending records"), Some(collected_at.clone()), ); let _ = app.emit(USAGE_UPDATED_EVENT, collected_at); @@ -1881,7 +1881,7 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { subscription_config = None; redis_queue = RedisUsageQueueSource::default(); subscribe_retry_at = tokio::time::Instant::now(); - set_collector_status(&app, "waiting-core", "等待内核就绪", None); + set_collector_status(&app, "waiting-core", "Waiting for kernel to become ready", None); retry_seconds = 1; wait_or_cancel(&token, 1).await; continue; @@ -1892,14 +1892,14 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { Ok(next_subscription) => { subscription = Some(next_subscription); subscription_config = Some((config.port, config.management_secret_key.clone())); - set_collector_status(&app, "collecting", "已连接 CPA usage 实时订阅", None); + set_collector_status(&app, "collecting", "Connected to CPA usage live subscription", None); match backfill_usage_queue(&root, &config, &mut redis_queue).await { Ok(outcome) => { let saved = outcome.inserted; publish_collected_records( &app, outcome, - &format!("实时订阅已连接,补录 {saved} 条队列记录"), + &format!("Live subscription connected; imported {saved} queued records"), ); retry_seconds = 1; } @@ -1907,7 +1907,7 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { set_collector_status( &app, "collecting", - &format!("实时订阅已连接,历史队列补录失败: {error}"), + &format!("Live subscription connected, but historical queue import failed: {error}"), None, ); } @@ -1920,7 +1920,7 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { set_collector_status( &app, "collecting", - &format!("使用 HTTP 兼容模式采集;实时订阅不可用: {error}"), + &format!("Collecting in HTTP compatibility mode; live subscription unavailable: {error}"), None, ); } @@ -1948,7 +1948,7 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { publish_collected_records( &app, outcome, - &format!("实时订阅已保存 {saved} 条新记录"), + &format!("Live subscription saved {saved} new records"), ); retry_seconds = 1; } @@ -1961,7 +1961,7 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { continue; } Err(_) => { - set_collector_status(&app, "collecting", "CPA usage 实时订阅采集中", None); + set_collector_status(&app, "collecting", "Collecting from CPA usage live subscription", None); retry_seconds = 1; continue; } @@ -1973,7 +1973,7 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { set_collector_status( &app, "collecting", - &format!("实时订阅断开,已切换 HTTP 兼容模式: {error}"), + &format!("Live subscription disconnected; switched to HTTP compatibility mode: {error}"), None, ); } @@ -1986,7 +1986,7 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { &app, "collecting", &format!( - "使用记录采集中({})", + "Collecting usage records ({})", collector_source_label(&batch.source) ), None, @@ -2007,7 +2007,7 @@ async fn usage_collector_loop(app: tauri::AppHandle, token: CancellationToken) { publish_collected_records( &app, outcome, - &format!("{source_label}已保存 {saved} 条新记录"), + &format!("{source_label} saved {saved} new records"), ); retry_seconds = 1; } @@ -2054,7 +2054,7 @@ async fn backfill_usage_queue( return backfill_http_usage_queue(root, config, outcome) .await .map_err(|http_error| { - format!("补录队列失败(Redis: {redis_error}; HTTP: {http_error})") + format!("Failed to import queue (Redis: {redis_error}; HTTP: {http_error})") }); } } @@ -2118,7 +2118,7 @@ pub(crate) fn persist_local_usage_event( let config = app.state::().snapshot()?; let root = usage_root_dir()?; let outcome = persist_queue_items_from_source(&root, collector_source, vec![value], &config)?; - publish_collected_records(app, outcome, "已保存桌面健康检测使用记录"); + publish_collected_records(app, outcome, "Saved desktop health check usage record"); Ok(outcome.inserted) } @@ -2137,21 +2137,21 @@ async fn fetch_usage_queue(config: &GuiConfigFile) -> Result, String> .query(&[("count", USAGE_QUEUE_BATCH_SIZE)]) .send() .await - .map_err(|error| format_management_request_error("读取 CPA 使用记录队列失败", &error))?; + .map_err(|error| format_management_request_error("Failed to read CPA usage records queue", &error))?; let status = response.status(); let text = response .text() .await - .map_err(|error| format_management_request_error("读取 CPA 使用记录响应失败", &error))?; + .map_err(|error| format_management_request_error("Failed to read CPA usage records response", &error))?; if !status.is_success() { return Err(format!( - "CPA 使用记录队列返回 HTTP {}: {}", + "CPA usage records queue returned HTTP {}: {}", status.as_u16(), text.trim() )); } serde_json::from_str::>(&text) - .map_err(|error| format!("解析 CPA 使用记录失败: {error}")) + .map_err(|error| format!("Failed to parse CPA usage records: {error}")) } async fn fetch_usage_queue_raw(config: &GuiConfigFile) -> Result, String> { @@ -2160,7 +2160,7 @@ async fn fetch_usage_queue_raw(config: &GuiConfigFile) -> Result, St .into_iter() .map(|item| { serde_json::to_string(&item) - .map_err(|error| format!("序列化 CPA 使用记录失败: {error}")) + .map_err(|error| format!("Failed to serialize CPA usage records: {error}")) }) .collect() } @@ -2178,16 +2178,16 @@ async fn pull_usage_queue_with_fallback( messages, }) .map_err(|http_error| { - format!("读取 CPA 使用记录失败(Redis: {redis_error}; HTTP: {http_error})") + format!("Failed to read CPA usage records (Redis: {redis_error}; HTTP: {http_error})") }), } } fn collector_source_label(source: &str) -> &'static str { if source.starts_with("redis_pull:") { - "Redis 队列" + "Redis queue" } else { - "HTTP 兼容模式" + "HTTP compatibility mode" } } @@ -2236,7 +2236,7 @@ fn enqueue_usage_queue_items( .into_iter() .map(|item| { serde_json::to_string(&item) - .map_err(|error| format!("序列化 CPA 使用记录失败: {error}")) + .map_err(|error| format!("Failed to serialize CPA usage records: {error}")) }) .collect::, _>>()?; enqueue_usage_raw_messages(connection, source, messages) @@ -2256,7 +2256,7 @@ fn enqueue_usage_raw_messages( } let transaction = connection .transaction() - .map_err(|error| format!("开始 SQLite 使用记录 inbox 事务失败: {error}"))?; + .map_err(|error| format!("Failed to begin SQLite usage records inbox transaction: {error}"))?; let mut statement = transaction .prepare( r#" @@ -2266,7 +2266,7 @@ fn enqueue_usage_raw_messages( ) VALUES (?1, ?2, ?3, 'pending', 0, ?4, ?4, ?4) "#, ) - .map_err(|error| format!("准备 SQLite 使用记录 inbox 写入失败: {error}"))?; + .map_err(|error| format!("Failed to prepare SQLite usage records inbox write: {error}"))?; let received_at = Local::now().to_rfc3339(); let mut inserted = 0_usize; for raw_message in messages { @@ -2278,13 +2278,13 @@ fn enqueue_usage_raw_messages( raw_message, received_at, ]) - .map_err(|error| format!("写入 SQLite 使用记录 inbox 失败: {error}"))?, + .map_err(|error| format!("Failed to write SQLite usage records inbox: {error}"))?, ); } drop(statement); transaction .commit() - .map_err(|error| format!("提交 SQLite 使用记录 inbox 失败: {error}"))?; + .map_err(|error| format!("Failed to commit SQLite usage records inbox: {error}"))?; Ok(inserted) } @@ -2348,7 +2348,7 @@ fn process_usage_inbox( let mut records = Vec::with_capacity(rows.len()); for row in rows { let parsed = serde_json::from_str::(&row.raw_message) - .map_err(|error| format!("解析 inbox JSON 失败: {error}")) + .map_err(|error| format!("Failed to parse inbox JSON: {error}")) .and_then(|value| normalize_usage_record(value, config)); match parsed { Ok(mut record) => { @@ -2369,7 +2369,7 @@ fn process_usage_inbox( let persist_result = (|| -> Result { let transaction = connection .transaction() - .map_err(|error| format!("开始 SQLite inbox 处理事务失败: {error}"))?; + .map_err(|error| format!("Failed to begin SQLite inbox processing transaction: {error}"))?; let inserted = insert_usage_records_in_transaction(&transaction, &records)?; let processed_at = Local::now().to_rfc3339(); for (row, record) in valid_rows.iter().zip(records.iter()) { @@ -2384,11 +2384,11 @@ fn process_usage_inbox( "#, params![record.id, processed_at, row.id], ) - .map_err(|error| format!("标记 SQLite 使用记录 inbox 已处理失败: {error}"))?; + .map_err(|error| format!("Failed to mark SQLite usage records inbox as processed: {error}"))?; } transaction .commit() - .map_err(|error| format!("提交 SQLite inbox 处理事务失败: {error}"))?; + .map_err(|error| format!("Failed to commit SQLite inbox processing transaction: {error}"))?; Ok(inserted) })(); match persist_result { @@ -2418,7 +2418,7 @@ fn list_processable_usage_inbox( LIMIT ?2 "#, ) - .map_err(|error| format!("准备读取 SQLite 使用记录 inbox 失败: {error}"))?; + .map_err(|error| format!("Failed to prepare SQLite usage records inbox read: {error}"))?; let rows = statement .query_map( params![ @@ -2434,9 +2434,9 @@ fn list_processable_usage_inbox( }) }, ) - .map_err(|error| format!("查询 SQLite 使用记录 inbox 失败: {error}"))? + .map_err(|error| format!("Failed to query SQLite usage records inbox: {error}"))? .collect::, _>>() - .map_err(|error| format!("读取 SQLite 使用记录 inbox 失败: {error}"))?; + .map_err(|error| format!("Failed to read SQLite usage records inbox: {error}"))?; Ok(rows) } @@ -2458,7 +2458,7 @@ fn mark_usage_inbox_decode_failed( ) .map(|_| ()) .map_err(|update_error| { - format!("标记 SQLite 使用记录 inbox 解码失败时出错: {update_error}") + format!("Error marking SQLite usage records inbox decode failure: {update_error}") }) } @@ -2488,7 +2488,7 @@ fn mark_usage_inbox_process_failed( params![status, next_attempt, error, now, row.id], ) .map_err(|update_error| { - format!("标记 SQLite 使用记录 inbox 处理失败时出错: {update_error}") + format!("Error marking SQLite usage records inbox processing failure: {update_error}") })?; } Ok(()) @@ -2511,13 +2511,13 @@ fn cleanup_usage_inbox(connection: &Connection, now: DateTime) -> Result< "DELETE FROM usage_inbox WHERE status = 'processed' AND processed_at < ?1", params![processed_cutoff], ) - .map_err(|error| format!("清理已处理使用记录 inbox 失败: {error}"))?; + .map_err(|error| format!("Failed to clean up processed usage records inbox: {error}"))?; connection .execute( "DELETE FROM usage_inbox WHERE status IN ('decode_failed', 'discarded') AND updated_at < ?1", params![failed_cutoff], ) - .map_err(|error| format!("清理失败使用记录 inbox 失败: {error}"))?; + .map_err(|error| format!("Failed to clean up failed usage records inbox: {error}"))?; Ok(()) } @@ -2528,11 +2528,11 @@ fn insert_usage_records( ) -> Result { let transaction = connection .transaction() - .map_err(|error| format!("开始 SQLite 使用记录事务失败: {error}"))?; + .map_err(|error| format!("Failed to begin SQLite usage records transaction: {error}"))?; let inserted = insert_usage_records_in_transaction(&transaction, records)?; transaction .commit() - .map_err(|error| format!("提交 SQLite 使用记录失败: {error}"))?; + .map_err(|error| format!("Failed to commit SQLite usage records: {error}"))?; Ok(inserted) } @@ -2564,7 +2564,7 @@ fn insert_usage_records_in_transaction( ) "#, ) - .map_err(|error| format!("准备 SQLite 使用记录写入失败: {error}"))?; + .map_err(|error| format!("Failed to prepare SQLite usage records write: {error}"))?; let created_at = Local::now().to_rfc3339(); let mut inserted = 0_usize; for record in records { @@ -2632,7 +2632,7 @@ fn insert_usage_records_in_transaction( record.failure_body, created_at, ]) - .map_err(|error| format!("写入 SQLite 使用记录失败: {error}"))?, + .map_err(|error| format!("Failed to write SQLite usage records: {error}"))?, ); } Ok(inserted) @@ -2641,12 +2641,12 @@ fn insert_usage_records_in_transaction( fn normalize_usage_record(value: Value, config: &GuiConfigFile) -> Result { let object = value .as_object() - .ok_or_else(|| "CPA 使用记录必须是 JSON 对象".to_string())?; + .ok_or_else(|| "CPA usage record must be a JSON object".to_string())?; let timestamp = string_field(object, "timestamp") .filter(|value| DateTime::parse_from_rfc3339(value).is_ok()) .unwrap_or_else(|| Local::now().to_rfc3339()); let request_id = string_field(object, "request_id") - .ok_or_else(|| "CPA 使用记录必须包含 request_id".to_string())?; + .ok_or_else(|| "CPA usage record must contain request_id".to_string())?; let api_key = string_field(object, "api_key").unwrap_or_default(); let api_key_hash = hash_text(&api_key); let api_key_remark = config @@ -2966,7 +2966,7 @@ fn load_usage_overview( )) }, ) - .map_err(|error| format!("统计 SQLite 使用记录失败: {error}"))?; + .map_err(|error| format!("Failed to count SQLite usage records: {error}"))?; let (estimated_cost, priced_requests) = load_estimated_cost(connection, &filter)?; @@ -2997,7 +2997,7 @@ fn load_usage_overview( ); let mut statement = connection .prepare(&timeline_sql) - .map_err(|error| format!("准备 SQLite 使用趋势查询失败: {error}"))?; + .map_err(|error| format!("Failed to prepare SQLite usage trend query: {error}"))?; let timeline_rows = statement .query_map(params_from_iter(filter.params.iter()), |row| { Ok(( @@ -3010,9 +3010,9 @@ fn load_usage_overview( from_sql_i64(row.get(6)?), )) }) - .map_err(|error| format!("查询 SQLite 使用趋势失败: {error}"))? + .map_err(|error| format!("Failed to query SQLite usage trends: {error}"))? .collect::, _>>() - .map_err(|error| format!("读取 SQLite 使用趋势失败: {error}"))?; + .map_err(|error| format!("Failed to read SQLite usage trends: {error}"))?; let mut grouped = BTreeMap::::new(); for (hour, model, requests, success, failure, canceled, tokens) in timeline_rows { let point = grouped.entry(hour.clone()).or_insert_with(|| UsageTimelinePoint { @@ -3131,7 +3131,7 @@ fn load_usage_cost_groups( ); let mut statement = connection .prepare(&sql) - .map_err(|error| format!("准备使用成本查询失败: {error}"))?; + .map_err(|error| format!("Failed to prepare usage cost query: {error}"))?; let groups = statement .query_map(params_from_iter(filter.params.iter()), |row| { Ok(UsageCostGroup { @@ -3156,9 +3156,9 @@ fn load_usage_cost_groups( total_tokens: from_sql_i64(row.get(16)?), }) }) - .map_err(|error| format!("查询使用成本失败: {error}"))? + .map_err(|error| format!("Failed to query usage costs: {error}"))? .collect::, _>>() - .map_err(|error| format!("读取使用成本失败: {error}"))?; + .map_err(|error| format!("Failed to read usage costs: {error}"))?; Ok(groups) } @@ -3267,15 +3267,15 @@ fn parse_model_price_catalog( updated_at_ms: i64, ) -> Result, String> { let catalog = serde_json::from_str::(content) - .map_err(|error| format!("解析模型价格文件失败: {error}"))?; + .map_err(|error| format!("Failed to parse model pricing file: {error}"))?; if catalog.schema_version != 1 { return Err(format!( - "不支持的模型价格文件版本 {}", + "Unsupported model pricing file version {}", catalog.schema_version )); } if catalog.models.is_empty() { - return Err("模型价格文件不包含任何模型".to_string()); + return Err("Model pricing file contains no models".to_string()); } let _catalog_updated_at = catalog.updated_at; let mut prices = HashMap::with_capacity(catalog.models.len()); @@ -3307,12 +3307,12 @@ fn parse_models_dev_prices( updated_at_ms: i64, ) -> Result, String> { let catalog: Value = serde_json::from_str(content) - .map_err(|error| format!("解析 models.dev 价格失败: {error}"))?; + .map_err(|error| format!("Failed to parse models.dev pricing: {error}"))?; let providers = catalog .get("providers") .and_then(Value::as_object) .or_else(|| catalog.as_object()) - .ok_or("models.dev 价格目录格式无效")?; + .ok_or("Invalid models.dev pricing catalog format")?; let mut prices = Vec::new(); for (provider, entry) in providers { let Some(models) = entry @@ -3352,15 +3352,15 @@ fn parse_models_dev_prices( } } if prices.is_empty() { - return Err("models.dev 没有可用的模型价格".to_string()); + return Err("models.dev has no available model pricing".to_string()); } Ok(prices) } fn parse_litellm_prices(content: &str, updated_at_ms: i64) -> Result, String> { let catalog: Value = serde_json::from_str(content) - .map_err(|error| format!("解析 LiteLLM 价格失败: {error}"))?; - let models = catalog.as_object().ok_or("LiteLLM 价格目录格式无效")?; + .map_err(|error| format!("Failed to parse LiteLLM pricing: {error}"))?; + let models = catalog.as_object().ok_or("Invalid LiteLLM pricing catalog format")?; let mut prices = Vec::new(); for (model_id, entry) in models { let mode = entry.get("mode").and_then(Value::as_str).unwrap_or_default(); @@ -3404,7 +3404,7 @@ fn parse_litellm_prices(content: &str, updated_at_ms: i64) -> Result Result Result, _>>() - .map_err(|error| format!("读取模型价格失败: {error}"))?; + .map_err(|error| format!("Failed to read model pricing: {error}"))?; for price in prices { if price.source != "litellm" { if let Some(existing) = merged @@ -3550,7 +3550,7 @@ fn load_model_prices(connection: &Connection) -> Result Result<(), String> { if price.model.trim().is_empty() { - return Err("模型名称不能为空".to_string()); + return Err("Model name cannot be empty".to_string()); } for value in [ price.prompt, @@ -3559,7 +3559,7 @@ fn validate_model_price(price: &ModelPrice) -> Result<(), String> { price.cache_creation, ] { if !value.is_finite() || value < 0.0 { - return Err(format!("模型 {} 包含无效价格", price.model)); + return Err(format!("Model {} contains invalid pricing", price.model)); } } Ok(()) @@ -3606,7 +3606,7 @@ fn upsert_model_price(connection: &Connection, price: &ModelPrice) -> Result<(), price.updated_at_ms, ], ) - .map_err(|error| format!("保存模型价格失败: {error}"))?; + .map_err(|error| format!("Failed to save model pricing: {error}"))?; Ok(()) } @@ -3704,7 +3704,7 @@ pub(crate) async fn delete_usage_model_price(model: String) -> Result<(), String "DELETE FROM model_prices WHERE model = ?1 COLLATE NOCASE", params![model.trim()], ) - .map_err(|error| format!("删除模型价格失败: {error}"))?; + .map_err(|error| format!("Failed to delete model pricing: {error}"))?; Ok(()) }) .await @@ -3822,28 +3822,28 @@ async fn fetch_model_price_catalog_once( .get(source_url) .send() .await - .map_err(|error| (true, format!("连接 {source_name} 失败: {error}")))?; + .map_err(|error| (true, format!("Failed to connect to {source_name}: {error}")))?; let status = response.status(); if !status.is_success() { let retryable = status.is_server_error() || status == reqwest::StatusCode::TOO_MANY_REQUESTS; - return Err((retryable, format!("读取 {source_name} 价格失败: HTTP {status}"))); + return Err((retryable, format!("Failed to read {source_name} pricing: HTTP {status}"))); } if response.content_length().is_some_and(|length| length > MODEL_PRICE_SYNC_MAX_BYTES as u64) { - return Err((false, format!("{source_name} 价格目录过大"))); + return Err((false, format!("{source_name} pricing catalog is too large"))); } let mut content = Vec::new(); while let Some(chunk) = response .chunk() .await - .map_err(|error| (true, format!("读取 {source_name} 响应失败: {error}")))? + .map_err(|error| (true, format!("Failed to read {source_name} response: {error}")))? { if content.len().saturating_add(chunk.len()) > MODEL_PRICE_SYNC_MAX_BYTES { - return Err((false, format!("{source_name} 价格目录过大"))); + return Err((false, format!("{source_name} pricing catalog is too large"))); } content.extend_from_slice(&chunk); } String::from_utf8(content) - .map_err(|error| (true, format!("{source_name} 响应不是 UTF-8: {error}"))) + .map_err(|error| (true, format!("{source_name} response is not UTF-8: {error}"))) } async fn fetch_model_price_catalog( @@ -3859,14 +3859,14 @@ async fn fetch_model_price_catalog( } Err((retryable, error)) => { return Err(if retryable { - format!("{error}(已尝试 {attempt} 次)") + format!("{error} (attempted {attempt} times)") } else { error }); } } } - unreachable!("至少尝试读取一次价格目录") + unreachable!("Attempt to read the pricing catalog at least once") } #[tauri::command] @@ -3878,7 +3878,7 @@ pub(crate) async fn preview_usage_model_prices( let (source_name, source_url) = match source.as_str() { "models-dev" => ("Models.dev", MODEL_PRICE_SYNC_URL), "litellm" => ("LiteLLM", LITELLM_PRICE_SYNC_URL), - _ => return Err("不支持的价格来源".to_string()), + _ => return Err("Unsupported pricing source".to_string()), }; let config = gui_config_state.snapshot()?; let client_builder = reqwest::Client::builder() @@ -3887,9 +3887,9 @@ pub(crate) async fn preview_usage_model_prices( .timeout(Duration::from_secs(60)); let proxy_url = config.proxy_url.trim(); let client = apply_configured_proxy(client_builder, proxy_url) - .map_err(|error| format!("配置价格同步代理失败: {error}"))? + .map_err(|error| format!("Failed to configure pricing synchronization proxy: {error}"))? .build() - .map_err(|error| format!("创建价格同步客户端失败: {error}"))?; + .map_err(|error| format!("Failed to create pricing synchronization client: {error}"))?; let content = fetch_model_price_catalog(&client, source_name, source_url).await?; let now = Local::now().timestamp_millis(); let remote_prices = if source == "models-dev" { @@ -3919,17 +3919,17 @@ pub(crate) async fn apply_usage_model_prices(prices: Vec) -> Result< if !matches!(price.source.as_str(), "models.dev" | "litellm-sync") || price.source_model_id.trim().is_empty() { - return Err("无效的同步价格来源".to_string()); + return Err("Invalid pricing synchronization source".to_string()); } if !seen.insert(price.model.to_ascii_lowercase()) { - return Err(format!("重复的模型价格: {}", price.model)); + return Err(format!("Duplicate model pricing: {}", price.model)); } } run_usage_task(move || { let mut connection = open_usage_database()?; let transaction = connection .transaction() - .map_err(|error| format!("开始保存模型价格失败: {error}"))?; + .map_err(|error| format!("Failed to begin saving model pricing: {error}"))?; for mut price in prices.iter().cloned() { price.model = price.model.trim().to_string(); price.updated_at_ms = Local::now().timestamp_millis(); @@ -3937,7 +3937,7 @@ pub(crate) async fn apply_usage_model_prices(prices: Vec) -> Result< } transaction .commit() - .map_err(|error| format!("提交模型价格失败: {error}"))?; + .map_err(|error| format!("Failed to commit model pricing: {error}"))?; Ok(ModelPriceSyncResult { imported: prices.len() }) }) .await @@ -3978,9 +3978,9 @@ fn load_usage_analysis( r#" SELECT {USAGE_DISPLAY_MODEL_SQL}, - COALESCE(NULLIF(TRIM(provider), ''), '未知 Provider'), - COALESCE(NULLIF(TRIM(source), ''), '未知来源'), - COALESCE(NULLIF(TRIM(api_key_hash), ''), '未记录密钥'), + COALESCE(NULLIF(TRIM(provider), ''), 'Unknown Provider'), + COALESCE(NULLIF(TRIM(source), ''), 'Unknown source'), + COALESCE(NULLIF(TRIM(api_key_hash), ''), 'Unrecorded key'), MAX(TRIM(api_key_remark)), MAX(TRIM(api_key_display)), COUNT(*), COALESCE(SUM(CASE WHEN failed != 0 AND canceled = 0 THEN 1 ELSE 0 END), 0), @@ -3992,10 +3992,10 @@ fn load_usage_analysis( ); let mut statement = connection .prepare(&sql) - .map_err(|error| format!("准备使用分析查询失败: {error}"))?; + .map_err(|error| format!("Failed to prepare usage analytics query: {error}"))?; let mut rows = statement .query(params_from_iter(filter.params.iter())) - .map_err(|error| format!("查询使用分析失败: {error}"))?; + .map_err(|error| format!("Failed to query usage analytics: {error}"))?; let mut categories: [HashMap; 4] = Default::default(); let mut key_labels = HashMap::::new(); let read_result = (|| -> rusqlite::Result<()> { @@ -4028,7 +4028,7 @@ fn load_usage_analysis( } Ok(()) })(); - read_result.map_err(|error| format!("读取使用分析失败: {error}"))?; + read_result.map_err(|error| format!("Failed to read usage analytics: {error}"))?; let [models, providers, sources, api_keys] = categories.map(sorted_usage_categories); let sources = sources .into_iter() @@ -4071,7 +4071,7 @@ fn load_source_categories( query: &UsageQuery, config: &GuiConfigFile, ) -> Result, String> { - let mut categories = load_simple_categories(connection, query, "source", "未知来源")?; + let mut categories = load_simple_categories(connection, query, "source", "Unknown source")?; for category in &mut categories { category.label = usage_source_display(config, "", &category.key); } @@ -4104,7 +4104,7 @@ fn load_simple_categories( values.extend(filter.params); let mut statement = connection .prepare(&sql) - .map_err(|error| format!("准备 SQLite 使用分析查询失败: {error}"))?; + .map_err(|error| format!("Failed to prepare SQLite usage analytics query: {error}"))?; let categories = statement .query_map(params_from_iter(values.iter()), |row| { let key = row.get::<_, String>(0)?; @@ -4116,9 +4116,9 @@ fn load_simple_categories( tokens: from_sql_i64(row.get(3)?), }) }) - .map_err(|error| format!("查询 SQLite 使用分析失败: {error}"))? + .map_err(|error| format!("Failed to query SQLite usage analytics: {error}"))? .collect::, _>>() - .map_err(|error| format!("读取 SQLite 使用分析失败: {error}"))?; + .map_err(|error| format!("Failed to read SQLite usage analytics: {error}"))?; Ok(categories) } @@ -4131,7 +4131,7 @@ fn load_api_key_categories( let sql = format!( r#" SELECT - COALESCE(NULLIF(TRIM(api_key_hash), ''), '未记录密钥'), + COALESCE(NULLIF(TRIM(api_key_hash), ''), 'Unrecorded key'), MAX(TRIM(api_key_remark)), MAX(TRIM(api_key_display)), COUNT(*), @@ -4145,7 +4145,7 @@ fn load_api_key_categories( ); let mut statement = connection .prepare(&sql) - .map_err(|error| format!("准备 SQLite API Key 使用分析查询失败: {error}"))?; + .map_err(|error| format!("Failed to prepare SQLite API Key usage analytics query: {error}"))?; let categories = statement .query_map(params_from_iter(filter.params.iter()), |row| { let key = row.get::<_, String>(0)?; @@ -4160,9 +4160,9 @@ fn load_api_key_categories( tokens: from_sql_i64(row.get(5)?), }) }) - .map_err(|error| format!("查询 SQLite API Key 使用分析失败: {error}"))? + .map_err(|error| format!("Failed to query SQLite API Key usage analytics: {error}"))? .collect::, _>>() - .map_err(|error| format!("读取 SQLite API Key 使用分析失败: {error}"))?; + .map_err(|error| format!("Failed to read SQLite API Key usage analytics: {error}"))?; Ok(categories) } @@ -4187,7 +4187,7 @@ fn load_usage_events( row.get::<_, i64>(0) }) .map(from_sql_i64) - .map_err(|error| format!("统计 SQLite 使用事件失败: {error}"))? + .map_err(|error| format!("Failed to count SQLite usage events: {error}"))? .min(usize::MAX as u64) as usize; let page_size = query.page_size.unwrap_or(50).clamp(20, 200); let total_pages = total.div_ceil(page_size).max(1); @@ -4217,12 +4217,12 @@ fn load_usage_events( values.push(SqlValue::Integer(offset.min(i64::MAX as usize) as i64)); let mut statement = connection .prepare(&sql) - .map_err(|error| format!("准备 SQLite 使用事件查询失败: {error}"))?; + .map_err(|error| format!("Failed to prepare SQLite usage event query: {error}"))?; let mut items = statement .query_map(params_from_iter(values.iter()), usage_record_from_row) - .map_err(|error| format!("查询 SQLite 使用事件失败: {error}"))? + .map_err(|error| format!("Failed to query SQLite usage events: {error}"))? .collect::, _>>() - .map_err(|error| format!("读取 SQLite 使用事件失败: {error}"))?; + .map_err(|error| format!("Failed to read SQLite usage events: {error}"))?; for item in &mut items { item.source_display = usage_source_display(config, &item.provider, &item.source); } @@ -4287,7 +4287,7 @@ fn total_usage_records() -> Result { row.get::<_, i64>(0) }) .map(from_sql_i64) - .map_err(|error| format!("统计 SQLite 使用记录总数失败: {error}")) + .map_err(|error| format!("Failed to count total SQLite usage records: {error}")) } fn query_window_minutes(query: &UsageQuery, first: Option, last: Option) -> f64 { @@ -4423,14 +4423,14 @@ fn api_key_category_label(remark: String, display: String) -> String { } else if !display.is_empty() { mask_api_key(&display) } else { - "未记录密钥".to_string() + "Unrecorded key".to_string() } } fn usage_source_display(config: &GuiConfigFile, provider: &str, source: &str) -> String { let source = source.trim(); if source.is_empty() { - return "未知来源".to_string(); + return "Unknown source".to_string(); } if let Some(remark) = config.api_access_remark_for_source(provider, source) { return remark.to_string(); @@ -4519,7 +4519,7 @@ mod tests { assert!(redis_pull_can_try_legacy_queue( "ERR unsupported queue 'usage'" )); - assert!(!redis_pull_can_try_legacy_queue("CPA usage 订阅认证失败")); + assert!(!redis_pull_can_try_legacy_queue("CPA usage subscription authentication failed")); assert!(!redis_pull_can_try_legacy_queue("connection refused")); } @@ -4570,7 +4570,7 @@ mod tests { record.source = ["source-a", " ", "source-b", "sk-secret-source"][index % 4].to_string(); record.api_key_hash = ["hash-a", "hash-b", " "][index % 3].to_string(); - record.api_key_remark = ["", " alpha ", "zeta", "测试备注"][index % 4].to_string(); + record.api_key_remark = ["", " alpha ", "zeta", "Test note"][index % 4].to_string(); record.api_key_display = ["", "ab••••", "xy••••"][index % 3].to_string(); record.failed = index % 3 == 0; record.canceled = index % 6 == 0; @@ -4632,7 +4632,7 @@ mod tests { assert_eq!( canonical(actual.providers), canonical( - load_simple_categories(&connection, &query, "provider", "未知 Provider") + load_simple_categories(&connection, &query, "provider", "Unknown Provider") .unwrap() ) ); @@ -5034,7 +5034,7 @@ mod tests { auth_type: "oauth".to_string(), api_key_hash: "hash".to_string(), api_key_display: "12••••".to_string(), - api_key_remark: "内置密钥".to_string(), + api_key_remark: "Built-in key".to_string(), request_id: id.to_string(), generate: true, cached_tokens: 2, @@ -5059,8 +5059,8 @@ mod tests { #[test] fn api_key_category_uses_either_remark_or_masked_key() { assert_eq!( - api_key_category_label("生产环境".to_string(), "12••••".to_string()), - "生产环境" + api_key_category_label("Production".to_string(), "12••••".to_string()), + "Production" ); assert_eq!( api_key_category_label(String::new(), "123456".to_string()), @@ -5076,10 +5076,10 @@ mod tests { provider_section: "codex-api-key".to_string(), api_key_hash: hash_text(key), record_hash: String::new(), - remark: "生产环境".to_string(), + remark: "Production".to_string(), }); - assert_eq!(usage_source_display(&config, "codex", key), "生产环境"); + assert_eq!(usage_source_display(&config, "codex", key), "Production"); assert_eq!( usage_source_display(&GuiConfigFile::default(), "codex", key), "sk-1••••wxyz" @@ -5095,8 +5095,8 @@ mod tests { let key = "sk-1234567890abcdefghijklmnopqrstuvwxyz"; let mut config = GuiConfigFile::default(); for (record_hash, remark) in [ - ("a".repeat(64), "生产环境"), - ("b".repeat(64), "测试环境"), + ("a".repeat(64), "Production"), + ("b".repeat(64), "Test"), ] { config.api_access_remarks.push(crate::GuiApiAccessRemark { provider_section: "codex-api-key".to_string(), @@ -5793,7 +5793,7 @@ mod tests { '2026-07-17-20', 100, 20, 'source', 'auth', 0, 'openai', 'gpt-test', 'alias-test', 'high', '', '', '', 'POST /v1/responses', 'oauth', 'legacy-hash', '12••••', - '旧密钥', 'request-1', 10, 20, 5, 2, 3, 30, + 'Legacy key', 'request-1', 10, 20, 5, 2, 3, 30, '2026-07-17T20:30:01+08:00' ) "#, diff --git a/src-tauri/src/usage/resp.rs b/src-tauri/src/usage/resp.rs index 2a07031..da7059a 100644 --- a/src-tauri/src/usage/resp.rs +++ b/src-tauri/src/usage/resp.rs @@ -54,7 +54,7 @@ impl UsageSubscription { .await?; let acknowledgement = subscription.read_frame().await?; if !is_subscription_ack(&acknowledgement) { - return Err("CPA 未确认 usage 订阅".to_string()); + return Err("CPA did not confirm the usage subscription".to_string()); } Ok(subscription) } @@ -63,8 +63,8 @@ impl UsageSubscription { let address = format!("127.0.0.1:{port}"); let stream = timeout(CONNECT_TIMEOUT, TcpStream::connect(&address)) .await - .map_err(|_| format!("连接 CPA usage 订阅超时: {address}"))? - .map_err(|error| format!("连接 CPA usage 订阅失败 {address}: {error}"))?; + .map_err(|_| format!("Timed out connecting to CPA usage subscription: {address}"))? + .map_err(|error| format!("Failed to connect to CPA usage subscription {address}: {error}"))?; let mut subscription = Self { stream, read_buffer: Vec::new(), @@ -72,8 +72,8 @@ impl UsageSubscription { subscription.send_command(&["AUTH", management_key]).await?; match subscription.read_frame().await? { RespValue::Simple(value) if value.eq_ignore_ascii_case("OK") => {} - RespValue::Error(error) => return Err(format!("CPA usage 订阅认证失败: {error}")), - value => return Err(format!("CPA usage 订阅认证响应无效: {}", value.kind())), + RespValue::Error(error) => return Err(format!("CPA usage subscription authentication failed: {error}")), + value => return Err(format!("Invalid CPA usage subscription authentication response: {}", value.kind())), } Ok(subscription) } @@ -85,7 +85,7 @@ impl UsageSubscription { return Ok(payload); } if let RespValue::Error(error) = frame { - return Err(format!("CPA usage 订阅返回错误: {error}")); + return Err(format!("CPA usage subscription returned an error: {error}")); } } } @@ -99,8 +99,8 @@ impl UsageSubscription { } timeout(IO_TIMEOUT, self.stream.write_all(&command)) .await - .map_err(|_| "写入 CPA usage 订阅命令超时".to_string())? - .map_err(|error| format!("写入 CPA usage 订阅命令失败: {error}")) + .map_err(|_| "Timed out writing CPA usage subscription command".to_string())? + .map_err(|error| format!("Failed to write CPA usage subscription command: {error}")) } async fn read_frame(&mut self) -> Result { @@ -119,7 +119,7 @@ impl UsageSubscription { )? { ParseResult::Complete(value, consumed) => { if consumed > limits.max_frame_bytes { - return Err("CPA usage RESP 响应超过大小限制".to_string()); + return Err("CPA usage RESP response exceeds the size limit".to_string()); } self.read_buffer.drain(..consumed); return Ok(value); @@ -127,15 +127,15 @@ impl UsageSubscription { ParseResult::Incomplete => {} } if self.read_buffer.len() >= limits.max_frame_bytes { - return Err("CPA usage RESP 响应超过大小限制".to_string()); + return Err("CPA usage RESP response exceeds the size limit".to_string()); } let mut chunk = [0_u8; 8192]; let read = timeout(IO_TIMEOUT, self.stream.read(&mut chunk)) .await - .map_err(|_| "读取 CPA usage 订阅响应超时".to_string())? - .map_err(|error| format!("读取 CPA usage 订阅响应失败: {error}"))?; + .map_err(|_| "Timed out reading CPA usage subscription response".to_string())? + .map_err(|error| format!("Failed to read CPA usage subscription response: {error}"))?; if read == 0 { - return Err("CPA usage 订阅连接已关闭".to_string()); + return Err("CPA usage subscription connection was closed".to_string()); } self.read_buffer.extend_from_slice(&chunk[..read]); } @@ -150,7 +150,7 @@ pub(super) async fn pull_usage_queue( ) -> Result, String> { if count == 0 || count > QUEUE_MAX_ARRAY_LENGTH { return Err(format!( - "CPA usage 队列批量大小必须在 1..={QUEUE_MAX_ARRAY_LENGTH} 之间" + "CPA usage queue batch size must be between 1..={QUEUE_MAX_ARRAY_LENGTH}" )); } let mut connection = UsageSubscription::connect_authenticated(port, management_key).await?; @@ -167,15 +167,15 @@ pub(super) async fn pull_usage_queue( .map(|value| { value .text() - .ok_or_else(|| "CPA usage 队列包含非文本消息".to_string()) + .ok_or_else(|| "CPA usage queue contains a non-text message".to_string()) }) .collect(), RespValue::Bulk(Some(value)) => String::from_utf8(value) .map(|value| vec![value]) - .map_err(|_| "CPA usage 队列消息不是 UTF-8".to_string()), + .map_err(|_| "CPA usage queue message is not UTF-8".to_string()), RespValue::Array(None) | RespValue::Bulk(None) => Ok(Vec::new()), - RespValue::Error(error) => Err(format!("CPA usage 队列 LPOP 失败: {error}")), - value => Err(format!("CPA usage 队列 LPOP 响应无效: {}", value.kind())), + RespValue::Error(error) => Err(format!("CPA usage queue LPOP failed: {error}")), + value => Err(format!("Invalid CPA usage queue LPOP response: {}", value.kind())), } } @@ -222,7 +222,7 @@ fn parse_resp_frame_with_limits( remaining_bulk_bytes: &mut usize, ) -> Result { if depth > MAX_NESTING_DEPTH { - return Err("CPA usage RESP 响应嵌套过深".to_string()); + return Err("CPA usage RESP response nesting is too deep".to_string()); } let Some(prefix) = input.get(offset).copied() else { return Ok(ParseResult::Incomplete); @@ -233,13 +233,13 @@ fn parse_resp_frame_with_limits( return Ok(ParseResult::Incomplete); }; let text = String::from_utf8(line.to_vec()) - .map_err(|_| "CPA usage RESP 文本响应不是 UTF-8".to_string())?; + .map_err(|_| "CPA usage RESP text response is not UTF-8".to_string())?; let value = match prefix { b'+' => RespValue::Simple(text), b'-' => RespValue::Error(text), _ => RespValue::Integer( text.parse::() - .map_err(|_| "CPA usage RESP 整数响应无效".to_string())?, + .map_err(|_| "Invalid CPA usage RESP integer response".to_string())?, ), }; Ok(ParseResult::Complete(value, next - offset)) @@ -256,12 +256,12 @@ fn parse_resp_frame_with_limits( )); } let length = - usize::try_from(length).map_err(|_| "CPA usage RESP 字符串长度无效".to_string())?; + usize::try_from(length).map_err(|_| "Invalid CPA usage RESP string length".to_string())?; if length > MAX_BULK_BYTES { - return Err("CPA usage RESP 字符串超过大小限制".to_string()); + return Err("CPA usage RESP string exceeds the size limit".to_string()); } if length > *remaining_bulk_bytes { - return Err("CPA usage RESP 响应超过总字符串大小限制".to_string()); + return Err("CPA usage RESP response exceeds the total string size limit".to_string()); } let data_end = data_start.saturating_add(length); let frame_end = data_end.saturating_add(2); @@ -269,7 +269,7 @@ fn parse_resp_frame_with_limits( return Ok(ParseResult::Incomplete); } if input.get(data_end..frame_end) != Some(b"\r\n") { - return Err("CPA usage RESP 字符串结尾无效".to_string()); + return Err("Invalid CPA usage RESP string terminator".to_string()); } *remaining_bulk_bytes -= length; Ok(ParseResult::Complete( @@ -286,9 +286,9 @@ fn parse_resp_frame_with_limits( return Ok(ParseResult::Complete(RespValue::Array(None), next - offset)); } let length = - usize::try_from(length).map_err(|_| "CPA usage RESP 数组长度无效".to_string())?; + usize::try_from(length).map_err(|_| "Invalid CPA usage RESP array length".to_string())?; if length > limits.max_array_length { - return Err("CPA usage RESP 数组超过大小限制".to_string()); + return Err("CPA usage RESP array exceeds the size limit".to_string()); } let mut values = Vec::with_capacity(length); for _ in 0..length { @@ -311,7 +311,7 @@ fn parse_resp_frame_with_limits( next - offset, )) } - _ => Err("CPA usage RESP 响应类型无效".to_string()), + _ => Err("Invalid CPA usage RESP response type".to_string()), } } @@ -321,12 +321,12 @@ fn resp_line(input: &[u8], start: usize) -> Result, Strin }; let Some(relative_end) = remaining.windows(2).position(|pair| pair == b"\r\n") else { if remaining.len() > MAX_RESP_LINE_BYTES + 1 { - return Err("CPA usage RESP 行超过大小限制".to_string()); + return Err("CPA usage RESP line exceeds the size limit".to_string()); } return Ok(None); }; if relative_end > MAX_RESP_LINE_BYTES { - return Err("CPA usage RESP 行超过大小限制".to_string()); + return Err("CPA usage RESP line exceeds the size limit".to_string()); } let end = start + relative_end; Ok(Some((&input[start..end], end + 2))) @@ -334,9 +334,9 @@ fn resp_line(input: &[u8], start: usize) -> Result, Strin fn parse_resp_length(value: &[u8]) -> Result { std::str::from_utf8(value) - .map_err(|_| "CPA usage RESP 长度不是 UTF-8".to_string())? + .map_err(|_| "CPA usage RESP length is not UTF-8".to_string())? .parse::() - .map_err(|_| "CPA usage RESP 长度无效".to_string()) + .map_err(|_| "Invalid CPA usage RESP length".to_string()) } fn is_subscription_ack(value: &RespValue) -> bool { @@ -404,7 +404,7 @@ mod tests { Err(error) => error, _ => panic!("expected oversized RESP array to fail"), }; - assert!(error.contains("数组超过大小限制")); + assert!(error.contains("RESP array exceeds the size limit")); } #[tokio::test] diff --git a/src/App.tsx b/src/App.tsx index fabf66b..b9e1181 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -193,7 +193,7 @@ function AppContent() { return; } } catch (error) { - console.error('读取关闭行为设置失败', error); + console.error('Failed to read close behavior settings', error); } setWindowsClosePrompt((current) => @@ -216,7 +216,7 @@ function AppContent() { } }) .catch((error) => { - console.error('监听 Windows 关闭确认事件失败', error); + console.error('Failed to listen for Windows close confirmation events', error); }); return () => { @@ -252,7 +252,7 @@ function AppContent() { try { await invoke('open_external_url', { url: CONTACT_URL }); } catch (error) { - console.error('打开联系我们链接失败', error); + console.error('Failed to open the contact link', error); } }; diff --git a/src/AppErrorBoundary.tsx b/src/AppErrorBoundary.tsx index ba0d9a8..cc36a9d 100644 --- a/src/AppErrorBoundary.tsx +++ b/src/AppErrorBoundary.tsx @@ -12,7 +12,7 @@ export class AppErrorBoundary extends Component { } componentDidCatch(error: Error, info: ErrorInfo) { - console.error('EasyCLIProxyAPI 渲染异常', error, info.componentStack); + console.error('EasyCLIProxyAPI render failure', error, info.componentStack); } render() { diff --git a/src/coreRuntime.tsx b/src/coreRuntime.tsx index 2392397..aa88ec0 100644 --- a/src/coreRuntime.tsx +++ b/src/coreRuntime.tsx @@ -99,7 +99,7 @@ export function CoreRuntimeProvider({ children }: { children: ReactNode }) { export function useCoreRuntime() { const context = useContext(CoreRuntimeContext); if (!context) { - throw new Error('useCoreRuntime 必须在 CoreRuntimeProvider 内使用'); + throw new Error('useCoreRuntime must be used inside CoreRuntimeProvider'); } return context; } diff --git a/src/coreUpdate.tsx b/src/coreUpdate.tsx index 5750544..f714df7 100644 --- a/src/coreUpdate.tsx +++ b/src/coreUpdate.tsx @@ -194,7 +194,7 @@ export function CoreUpdateProvider({ children }: { children: ReactNode }) { export function useCoreUpdate() { const context = useContext(CoreUpdateContext); if (!context) { - throw new Error('useCoreUpdate 必须在 CoreUpdateProvider 内使用'); + throw new Error('useCoreUpdate must be used inside CoreUpdateProvider'); } return context; } diff --git a/src/i18n/index.tsx b/src/i18n/index.tsx index 110bac6..2fc78e6 100644 --- a/src/i18n/index.tsx +++ b/src/i18n/index.tsx @@ -16,7 +16,7 @@ export const languageOptions: ReadonlyArray<{ const STORAGE_KEY = 'easy-cli-proxy-api.locale'; const resources = { 'zh-CN': zhCN, 'zh-TW': zhTW, ja, en } as const; -let currentLocale: AppLocale = 'zh-CN'; +let currentLocale: AppLocale = 'en'; export const supportedLocales: readonly AppLocale[] = ['zh-CN', 'zh-TW', 'ja', 'en']; @@ -26,7 +26,6 @@ export function getCurrentLocale(): AppLocale { export function normalizeLocale(value: string | null | undefined): AppLocale { const normalized = value?.trim().toLowerCase() ?? ''; - if (normalized.startsWith('en')) return 'en'; if (normalized.startsWith('ja')) return 'ja'; if ( normalized === 'zh-tw' @@ -34,11 +33,12 @@ export function normalizeLocale(value: string | null | undefined): AppLocale { || normalized === 'zh-mo' || normalized.startsWith('zh-hant') ) return 'zh-TW'; - return 'zh-CN'; + if (normalized.startsWith('zh')) return 'zh-CN'; + return 'en'; } function detectInitialLocale(): AppLocale { - if (typeof window === 'undefined') return 'zh-CN'; + if (typeof window === 'undefined') return 'en'; try { const saved = window.localStorage.getItem(STORAGE_KEY); if (saved) return normalizeLocale(saved); @@ -55,7 +55,7 @@ function interpolate(template: string, variables?: MessageVariables): string { } export function translate(locale: AppLocale, key: MessageKey, variables?: MessageVariables): string { - return interpolate(resources[locale][key] ?? zhCN[key], variables); + return interpolate(resources[locale][key] ?? en[key], variables); } type I18nContextValue = { diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts index b1f95e6..4d02500 100644 --- a/src/i18n/ja.ts +++ b/src/i18n/ja.ts @@ -1461,6 +1461,7 @@ export const jaOverrides = { 'kernel.error.port': 'ポートは 1~65535 の範囲で指定してください', 'kernel.error.installTask': 'インストールタスクを読み込めませんでした:{error}', 'kernel.install.installingVersion': '{version} をインストールしています', + 'kernel.install.installingLatest': '最新バージョンをインストールしています', 'kernel.phase.preparingDownload': 'ダウンロードを準備中', 'kernel.phase.downloading': 'ダウンロード中', 'kernel.phase.extracting': '展開中', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index ce71d14..75b7baa 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -1461,6 +1461,7 @@ export const en: Record = { 'kernel.error.port': 'The port must be between 1 and 65535', 'kernel.error.installTask': 'Failed to read the installation task: {error}', 'kernel.install.installingVersion': 'Installing {version}', + 'kernel.install.installingLatest': 'Installing the latest version', 'kernel.phase.preparingDownload': 'Preparing Download', 'kernel.phase.downloading': 'Downloading', 'kernel.phase.extracting': 'Extracting', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 8628c5b..444c5ba 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -1460,6 +1460,7 @@ export const zhCN = { 'kernel.error.port': '端口必须在 1 到 65535 之间', 'kernel.error.installTask': '读取安装任务失败: {error}', 'kernel.install.installingVersion': '正在安装 {version}', + 'kernel.install.installingLatest': '正在安装最新版本', 'kernel.phase.preparingDownload': '准备下载', 'kernel.phase.downloading': '下载中', 'kernel.phase.extracting': '解压中', diff --git a/src/mocks/browserMock.ts b/src/mocks/browserMock.ts index 13b7851..e8b1024 100644 --- a/src/mocks/browserMock.ts +++ b/src/mocks/browserMock.ts @@ -35,18 +35,18 @@ function installToolbar(scenario: BrowserMockScenario, delayMs: number) { const toolbar = document.createElement('aside'); toolbar.id = 'browser-mock-toolbar'; toolbar.setAttribute('aria-label', 'Browser Mock controls'); - toolbar.title = '仅在普通浏览器开发模式中显示;Tauri 和正式构建不会启用 Mock。'; + toolbar.title = 'Shown only in regular browser development mode; Mock is disabled in Tauri and production builds.'; const badge = document.createElement('strong'); badge.textContent = 'MOCK'; const select = document.createElement('select'); - select.setAttribute('aria-label', 'Browser Mock 场景'); + select.setAttribute('aria-label', 'Browser Mock scenario'); const scenarios: Array<[BrowserMockScenario, string]> = [ - ['running', '内核运行'], - ['stopped', '内核停止'], - ['empty', '未安装内核'], - ['error', '请求错误'], + ['running', 'Core running'], + ['stopped', 'Core stopped'], + ['empty', 'Core not installed'], + ['error', 'Request error'], ]; scenarios.forEach(([value, label]) => { const option = document.createElement('option'); @@ -64,7 +64,7 @@ function installToolbar(scenario: BrowserMockScenario, delayMs: number) { }); const hint = document.createElement('span'); - hint.textContent = delayMs > 0 ? `${delayMs} ms` : '浏览器调试'; + hint.textContent = delayMs > 0 ? `${delayMs} ms` : 'Browser debugging'; toolbar.append(badge, select, hint); document.body.append(toolbar); @@ -74,7 +74,7 @@ function installToolbar(scenario: BrowserMockScenario, delayMs: number) { export function installBrowserMock(): InstalledBrowserMock | null { const options = resolveBrowserMockOptions(window.location.search, readStoredScenario()); if (options.mode === 'off') { - console.info('[Browser Mock] 已通过 ?mock=off 禁用。'); + console.info('[Browser Mock] Disabled via ?mock=off.'); return null; } @@ -98,8 +98,8 @@ export function installBrowserMock(): InstalledBrowserMock | null { installToolbar(options.mode, options.delayMs); console.info( - `[Browser Mock] 已启用场景“${options.mode}”${options.delayMs ? `,延迟 ${options.delayMs} ms` : ''}。`, - '使用 ?mock=running|stopped|empty|error 切换,?mock=off 禁用。', + `[Browser Mock] Enabled scenario “${options.mode}”${options.delayMs ? ` with a ${options.delayMs} ms delay` : ''}.`, + 'Use ?mock=running|stopped|empty|error to switch scenarios, or ?mock=off to disable.', ); return { scenario: options.mode, delayMs: options.delayMs }; } diff --git a/src/mocks/browserMockRuntime.ts b/src/mocks/browserMockRuntime.ts index 96c2765..f4aec89 100644 --- a/src/mocks/browserMockRuntime.ts +++ b/src/mocks/browserMockRuntime.ts @@ -83,7 +83,7 @@ function createCoreStatus(scenario: BrowserMockScenario) { currentVersion: installed ? '7.3.15' : null, installDir: 'C:\\EasyCLIProxyAPI\\cpa-core', binaryPath: installed ? 'C:\\EasyCLIProxyAPI\\cpa-core\\cli-proxy-api.exe' : null, - message: running ? 'Browser Mock 内核运行中' : installed ? 'Browser Mock 内核已停止' : 'Browser Mock 内核未安装', + message: running ? 'Browser Mock core is running' : installed ? 'Browser Mock core is stopped' : 'Browser Mock core is not installed', }; } @@ -138,7 +138,7 @@ function createUsageEvents() { endpoint: '/v1/responses', api_key_hash: 'mock-key-hash', api_key_display: 'sk-mock••••••••2026', - api_key_remark: '浏览器 Mock 密钥', + api_key_remark: 'Browser Mock key', tokens: { input_tokens: input, output_tokens: output, @@ -186,10 +186,10 @@ function createAgentStatuses() { pluginInstalled: id === 'pi', launchTargets: id === 'codex' || id === 'opencode' ? [ - { id: 'app', label: 'Desktop App', detail: 'Browser Mock 桌面入口' }, - { id: 'cli', label: 'CLI', detail: 'Browser Mock 命令行入口' }, + { id: 'app', label: 'Desktop App', detail: 'Browser Mock desktop entry point' }, + { id: 'cli', label: 'CLI', detail: 'Browser Mock command-line entry point' }, ] - : [{ id: id === 'claude-desktop' ? 'app' : 'cli', label: '启动', detail: 'Browser Mock 启动目标' }], + : [{ id: id === 'claude-desktop' ? 'app' : 'cli', label: 'Launch', detail: 'Browser Mock launch target' }], version: `1.${index + 4}.0`, cliVersion: id === 'claude-desktop' ? null : `1.${index + 4}.0`, appVersion: id === 'claude-desktop' ? '0.12.8' : null, @@ -217,7 +217,7 @@ function createAgentStatuses() { ], } : null, - warnings: id === 'openclaw' ? ['Mock:尚未应用配置'] : [], + warnings: id === 'openclaw' ? ['Mock: configuration has not been applied'] : [], error: null, })); } @@ -345,7 +345,7 @@ function createState(scenario: BrowserMockScenario) { { key: 'other', label: 'Other', requests: 34, failures: 1, tokens: 333_300 }, ], apiKeys: [ - { key: 'mock-key-hash', label: '浏览器 Mock 密钥', requests: 312, failures: 7, tokens: 2_187_300 }, + { key: 'mock-key-hash', label: 'Browser Mock key', requests: 312, failures: 7, tokens: 2_187_300 }, ], }; return { @@ -359,8 +359,8 @@ function createState(scenario: BrowserMockScenario) { }, coreConfig: { apiKeys: [ - { apiKey: 'sk-browser-mock-2026', remark: '浏览器 Mock 密钥' }, - { apiKey: 'sk-browser-demo-secondary', remark: '备用演示密钥' }, + { apiKey: 'sk-browser-mock-2026', remark: 'Browser Mock key' }, + { apiKey: 'sk-browser-demo-secondary', remark: 'Secondary demo key' }, ], debug: false, commercialMode: false, @@ -390,7 +390,7 @@ function createState(scenario: BrowserMockScenario) { silentStartEnabled: false, defaultTerminal: 'auto', availableTerminals: [ - { id: 'auto', label: '自动选择' }, + { id: 'auto', label: 'Automatic selection' }, { id: 'windows-terminal', label: 'Windows Terminal' }, { id: 'powershell', label: 'PowerShell' }, ], @@ -512,7 +512,7 @@ function createState(scenario: BrowserMockScenario) { models: ['gpt-5.2-codex', 'gpt-5.1-codex'].map((slug) => { const configuration = { display_name: slug === 'gpt-5.2-codex' ? 'GPT-5.2 Codex' : 'GPT-5.1 Codex', - description: 'Browser Mock 模型目录', + description: 'Browser Mock model catalog', context_window: 272_000, max_context_window: 400_000, effective_context_window_percent: 90, @@ -545,9 +545,9 @@ function createState(scenario: BrowserMockScenario) { configured: true, }, codexSessions: [ - { id: 'mock-session-1', title: '实现 Browser Mock', cwd: 'E:\\projects\\mock-demo', modelProvider: 'cpa-gui', archived: false, updatedAtMs: Date.now() - 20 * 60_000, databasePath: 'C:\\Users\\Mock\\.codex\\state.sqlite' }, - { id: 'mock-session-2', title: 'TypeScript 类型学习', cwd: 'E:\\projects\\typescript-study', modelProvider: 'openai', archived: false, updatedAtMs: Date.now() - 3 * 60 * 60_000, databasePath: 'C:\\Users\\Mock\\.codex\\state.sqlite' }, - { id: 'mock-session-3', title: '旧版界面检查', cwd: 'E:\\projects\\legacy-ui', modelProvider: 'cpa-gui', archived: true, updatedAtMs: Date.now() - 3 * 24 * 60 * 60_000, databasePath: 'C:\\Users\\Mock\\.codex\\state.sqlite' }, + { id: 'mock-session-1', title: 'Implement Browser Mock', cwd: 'E:\\projects\\mock-demo', modelProvider: 'cpa-gui', archived: false, updatedAtMs: Date.now() - 20 * 60_000, databasePath: 'C:\\Users\\Mock\\.codex\\state.sqlite' }, + { id: 'mock-session-2', title: 'Learn TypeScript types', cwd: 'E:\\projects\\typescript-study', modelProvider: 'openai', archived: false, updatedAtMs: Date.now() - 3 * 60 * 60_000, databasePath: 'C:\\Users\\Mock\\.codex\\state.sqlite' }, + { id: 'mock-session-3', title: 'Review legacy UI', cwd: 'E:\\projects\\legacy-ui', modelProvider: 'cpa-gui', archived: true, updatedAtMs: Date.now() - 3 * 24 * 60 * 60_000, databasePath: 'C:\\Users\\Mock\\.codex\\state.sqlite' }, ], }; } @@ -769,21 +769,21 @@ export function createBrowserMockRuntime( case 'get_core_status': return clone(state.coreStatus); case 'start_core_process': { - if (!state.coreStatus.installed) throw new Error('Browser Mock:请先安装内核'); - Object.assign(state.coreStatus, { running: true, ready: true, managed: true, starting: false, processId: 42817, message: 'Browser Mock 内核运行中' }); + if (!state.coreStatus.installed) throw new Error('Browser Mock: install the core first'); + Object.assign(state.coreStatus, { running: true, ready: true, managed: true, starting: false, processId: 42817, message: 'Browser Mock core is running' }); state.guiSettings.runOnStartup = true; emit('core-status-changed', state.coreStatus); return clone(state.coreStatus); } case 'stop_core_process': { - Object.assign(state.coreStatus, { running: false, ready: false, managed: false, starting: false, processId: null, message: 'Browser Mock 内核已停止' }); + Object.assign(state.coreStatus, { running: false, ready: false, managed: false, starting: false, processId: null, message: 'Browser Mock core is stopped' }); state.guiSettings.runOnStartup = false; emit('core-status-changed', state.coreStatus); return clone(state.coreStatus); } case 'restart_core_process': { - if (!state.coreStatus.installed) throw new Error('Browser Mock:请先安装内核'); - Object.assign(state.coreStatus, { running: true, ready: true, managed: true, starting: false, processId: 42818, message: 'Browser Mock 内核已重启' }); + if (!state.coreStatus.installed) throw new Error('Browser Mock: install the core first'); + Object.assign(state.coreStatus, { running: true, ready: true, managed: true, starting: false, processId: 42818, message: 'Browser Mock core restarted' }); emit('core-status-changed', state.coreStatus); return clone(state.coreStatus); } @@ -853,13 +853,13 @@ export function createBrowserMockRuntime( downloadedBytes: 18 * 1024 * 1024, totalBytes: state.appUpdateInfo.downloadSizeBytes, percent: 38, - message: 'Browser Mock 正在下载更新', + message: 'Browser Mock is downloading an update', }); emit('app-update-progress', state.appUpdateTask); return null; } case 'cancel_app_update': { - Object.assign(state.appUpdateTask, { running: false, cancellable: false, phase: 'cancelled', message: 'Browser Mock 已取消更新' }); + Object.assign(state.appUpdateTask, { running: false, cancellable: false, phase: 'cancelled', message: 'Browser Mock update canceled' }); emit('app-update-progress', state.appUpdateTask); return null; } @@ -873,8 +873,8 @@ export function createBrowserMockRuntime( installDir: state.coreStatus.installDir, binaryPath: 'C:\\EasyCLIProxyAPI\\cpa-core\\cli-proxy-api.exe', }; - Object.assign(state.coreStatus, { installed: true, currentVersion: result.version, binaryPath: result.binaryPath, message: 'Browser Mock 内核已安装' }); - Object.assign(state.coreInstallTask, { running: false, cancellable: false, phase: 'completed', downloaded: 64 * 1024 * 1024, total: 64 * 1024 * 1024, percent: 100, message: 'Browser Mock 安装完成', result }); + Object.assign(state.coreStatus, { installed: true, currentVersion: result.version, binaryPath: result.binaryPath, message: 'Browser Mock core installed' }); + Object.assign(state.coreInstallTask, { running: false, cancellable: false, phase: 'completed', downloaded: 64 * 1024 * 1024, total: 64 * 1024 * 1024, percent: 100, message: 'Browser Mock installation complete', result }); emit('core-install-progress', state.coreInstallTask); emit('core-status-changed', state.coreStatus); return clone(result); @@ -915,7 +915,7 @@ export function createBrowserMockRuntime( case 'provider_health_probe': return { firstTokenLatencyMs: 184, responseLatencyMs: 642 }; case 'list_oauth_browsers': return [ - { id: 'default', label: '系统默认浏览器' }, + { id: 'default', label: 'System default browser' }, { id: 'chrome', label: 'Google Chrome (Mock)' }, { id: 'edge', label: 'Microsoft Edge (Mock)' }, ]; @@ -940,7 +940,7 @@ export function createBrowserMockRuntime( case 'get_usage_collector_status': return { state: state.coreStatus.ready ? 'collecting' : 'waiting-core', - message: state.coreStatus.ready ? 'Browser Mock 正在采集使用记录' : '等待内核启动', + message: state.coreStatus.ready ? 'Browser Mock is collecting usage records' : 'Waiting for the core to start', lastCollectedAt: new Date().toISOString(), totalRecords: state.usageOverview.totalRequests, }; @@ -1152,7 +1152,7 @@ export function createBrowserMockRuntime( const ids = asArray(request.sessionIds).map(String); state.codexSessions = state.codexSessions.filter((session) => !ids.includes(session.id)); return { - results: ids.map((id) => ({ sessionId: id, status: 'deleted', message: 'Browser Mock 已删除', backupPath: 'C:\\Users\\Mock\\.codex\\backups\\mock.zip' })), + results: ids.map((id) => ({ sessionId: id, status: 'deleted', message: 'Deleted by Browser Mock', backupPath: 'C:\\Users\\Mock\\.codex\\backups\\mock.zip' })), deletedCount: ids.length, failedCount: 0, }; @@ -1167,15 +1167,15 @@ export function createBrowserMockRuntime( } case 'preview_codex_session_index_cleanup': return { snapshotSha256: 'mock-snapshot-sha256', - candidates: [{ id: 'stale-thread-1', threadName: '已不存在的会话', updatedAt: isoHoursAgo(72) }], + candidates: [{ id: 'stale-thread-1', threadName: 'Missing session', updatedAt: isoHoursAgo(72) }], }; case 'apply_codex_session_index_cleanup': return { prunedEntries: asArray(asObject(payload.request).threadIds).length, backupPath: 'C:\\Users\\Mock\\.codex\\backups\\index.json' }; case 'get_lan_ipv4': return ['192.168.100.100']; default: if (command.startsWith('plugin:window|') || command.startsWith('plugin:webview|')) return null; - console.warn(`[Browser Mock] 尚未实现命令:${command}`, rawPayload); - throw new Error(`Browser Mock 尚未实现命令:${command}`); + console.warn(`[Browser Mock] Command not implemented: ${command}`, rawPayload); + throw new Error(`Browser Mock command not implemented: ${command}`); } }; diff --git a/src/pages/VersionManagementPage.tsx b/src/pages/VersionManagementPage.tsx index b3321c2..c58b112 100644 --- a/src/pages/VersionManagementPage.tsx +++ b/src/pages/VersionManagementPage.tsx @@ -163,7 +163,7 @@ export function VersionManagementPage() { } if (task.message && !task.running) { - showNotice(task.message, task.phase === '安装失败' ? 'error' : 'info'); + showNotice(task.message, task.phase === 'Installation failed' ? 'error' : 'info'); } }; @@ -252,7 +252,7 @@ export function VersionManagementPage() { setProgress({ running: true, cancellable: true, - phase: '准备下载', + phase: 'Preparing download', downloaded: 0, total: null, percent: null, @@ -267,7 +267,7 @@ export function VersionManagementPage() { setProgress({ running: false, cancellable: false, - phase: '安装完成', + phase: 'Installation complete', downloaded: 1, total: 1, percent: 100, @@ -281,11 +281,12 @@ export function VersionManagementPage() { } catch (error) { manualInstallInProgressRef.current = false; const errorMessage = String(error); - showNotice(errorMessage, errorMessage.includes('取消') ? 'info' : 'error'); + const cancelled = /cancel/i.test(errorMessage); + showNotice(errorMessage, cancelled ? 'info' : 'error'); setProgress((current) => ({ running: false, cancellable: false, - phase: errorMessage.includes('取消') ? '已取消' : '安装失败', + phase: cancelled ? 'Canceled' : 'Installation failed', downloaded: current?.downloaded ?? 0, total: current?.total ?? null, percent: current?.percent ?? null, @@ -444,9 +445,9 @@ export function VersionManagementPage() { const progressKnown = computedPercent !== null; const progressPercent = clampPercent(computedPercent ?? 0); const progressText = progress - ? progress.phase === '安装完成' + ? progress.phase === 'Installation complete' ? t('kernel.progress.completed') - : progress.phase === '解压中' + : progress.phase === 'Extracting' ? t('kernel.progress.extracting') : progress.total ? `${formatBytes(progress.downloaded)} / ${formatBytes(progress.total)}` @@ -457,7 +458,7 @@ export function VersionManagementPage() { const installDialogTone: MessageType = progress?.result ? 'success' - : progress?.phase === '安装失败' + : progress?.phase === 'Installation failed' ? 'error' : 'info'; @@ -467,7 +468,7 @@ export function VersionManagementPage() { : t('kernel.install.titleInstalling') : progress?.result ? t('kernel.install.titleCompleted') - : progress?.phase === '已取消' + : progress?.phase === 'Canceled' ? t('kernel.install.titleCancelled') : t('kernel.install.titleFailed'); @@ -895,16 +896,21 @@ function localizeInstallPhase( t: ReturnType['t'], ) { const keys = { - '准备下载': 'kernel.phase.preparingDownload', - '下载中': 'kernel.phase.downloading', - '解压中': 'kernel.phase.extracting', - '准备内置内核': 'kernel.phase.preparingBundled', - '安装完成': 'kernel.phase.completed', - '安装失败': 'kernel.phase.failed', - '已取消': 'kernel.phase.cancelled', + 'Preparing download': 'kernel.phase.preparingDownload', + Downloading: 'kernel.phase.downloading', + Extracting: 'kernel.phase.extracting', + 'Extract bundled kernel': 'kernel.phase.preparingBundled', + 'Installation complete': 'kernel.phase.completed', + 'Installation failed': 'kernel.phase.failed', + Canceled: 'kernel.phase.cancelled', } as const; const key = keys[phase as keyof typeof keys]; - return key ? t(key) : phase; + if (key) return t(key); + if (phase === 'Preparing to install the latest version') { + return t('kernel.install.installingLatest'); + } + const version = phase.match(/^Preparing to install (.+)$/)?.[1]; + return version ? t('kernel.install.installingVersion', { version }) : phase; } function clampPercent(percent: number) { diff --git a/src/services/quotaService.ts b/src/services/quotaService.ts index 5036d87..c9b1745 100644 --- a/src/services/quotaService.ts +++ b/src/services/quotaService.ts @@ -154,7 +154,10 @@ const formatDateTime = (date: Date): string | undefined => { }).format(date); }; -export const formatQuotaTimestamp = (value: string | undefined, locale: AppLocale = 'zh-CN'): string => { +export const formatQuotaTimestamp = ( + value: string | undefined, + locale: AppLocale = getCurrentLocale(), +): string => { if (!value) return '—'; const ms = quotaResetInstant(value); if (ms === undefined) return '—'; diff --git a/src/styles.css b/src/styles.css index 1c30475..607c8cf 100644 --- a/src/styles.css +++ b/src/styles.css @@ -9089,7 +9089,7 @@ textarea:focus-visible { transition: width 0.3s ease; } -/* Events Tab (请求明细) */ +/* Events tab (request details) */ .usage-events-panel { display: flex; width: 100%; diff --git a/tests/appNotice.test.tsx b/tests/appNotice.test.tsx index dc2f0c2..6d41ed1 100644 --- a/tests/appNotice.test.tsx +++ b/tests/appNotice.test.tsx @@ -12,7 +12,7 @@ import { const notice: AppNotice = { owner: 'api-page', source: 'app.nav.api', - message: '接入已启用', + message: 'Connection enabled', tone: 'success', }; @@ -31,11 +31,11 @@ describe('统一操作提示', () => { const first = appNoticeReducer(initialAppNoticeState, { type: 'show', notice }); const next = appNoticeReducer(first, { type: 'show', - notice: { ...notice, message: '接入已停用' }, + notice: { ...notice, message: 'Connection disabled' }, }); - expect(next.notice?.message).toBe('接入已停用'); + expect(next.notice?.message).toBe('Connection disabled'); expect(next.revision).toBe(2); - expect(first.notice?.message).toBe('接入已启用'); + expect(first.notice?.message).toBe('Connection enabled'); }); it('重复的操作结果也产生新的可播报版本', () => { @@ -76,12 +76,12 @@ describe('统一操作提示', () => { it('操作结果包含来源、完整消息和可访问的关闭按钮', () => { const html = renderNotice(appNoticeReducer(initialAppNoticeState, { type: 'show', notice })); expect(html).toContain('action-feedback inline-notice success'); - expect(html).toContain('API 接入'); - expect(html).toContain('接入已启用'); + expect(html).toContain('API Access'); + expect(html).toContain('Connection enabled'); expect(html).toContain('role="status"'); expect(html).toContain('aria-live="polite"'); expect(html).toContain('aria-atomic="true"'); - expect(html).toContain('aria-label="关闭操作提示"'); + expect(html).toContain('aria-label="Dismiss operation feedback"'); expect(html).not.toContain('config-toast'); expect(html).not.toContain(' { it('局部操作可省略重复的模块标题', () => { const html = renderNotice({ notice: { ...notice, source: undefined }, revision: 1 }); - expect(html).toContain('接入已启用'); + expect(html).toContain('Connection enabled'); expect(html).not.toContain('action-feedback-source'); }); it('长错误保留全文、换行且转义外部内容', () => { - const message = `失败:${'详细错误'.repeat(160)}\n`; + const message = `Failure: ${'Detailed error'.repeat(160)}\n`; const html = renderNotice(appNoticeReducer(initialAppNoticeState, { type: 'show', notice: { ...notice, message, tone: 'error' }, })); expect(html).toContain('action-feedback inline-notice error'); expect(html).toContain('role="alert"'); expect(html).toContain('aria-live="assertive"'); - expect(html).toContain('详细错误'.repeat(160)); + expect(html).toContain('Detailed error'.repeat(160)); expect(html).toContain('\n<script>'); expect(html).not.toContain('