黄中银 2 settimane fa
parent
commit
0650fc1719
5 ha cambiato i file con 120 aggiunte e 22 eliminazioni
  1. 65 18
      src-tauri/src/commands/install.rs
  2. 13 0
      src/App.vue
  3. 16 2
      src/i18n/en-US.ts
  4. 16 2
      src/i18n/zh-CN.ts
  5. 10 0
      src/stores/settings.ts

+ 65 - 18
src-tauri/src/commands/install.rs

@@ -8,13 +8,50 @@ use std::io::Write;
 use std::path::Path;
 use tauri::{Emitter, State};
 
+/// 安装错误,包含 i18nKey 用于前端翻译
+#[derive(Debug, Clone)]
+pub struct InstallError {
+    pub i18n_key: String,
+    pub detail: Option<String>,
+}
+
+impl InstallError {
+    pub fn new(i18n_key: &str) -> Self {
+        Self {
+            i18n_key: i18n_key.to_string(),
+            detail: None,
+        }
+    }
+
+    pub fn with_detail(i18n_key: &str, detail: &str) -> Self {
+        Self {
+            i18n_key: i18n_key.to_string(),
+            detail: Some(detail.to_string()),
+        }
+    }
+
+    /// 转换为前端可用的格式
+    pub fn to_message(&self) -> String {
+        match &self.detail {
+            Some(detail) => format!("{}|{}", self.i18n_key, detail),
+            None => self.i18n_key.clone(),
+        }
+    }
+}
+
+impl std::fmt::Display for InstallError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.to_message())
+    }
+}
+
 /// 下载文件并报告进度(支持断点续传)
 /// progress_callback: (downloaded_bytes, total_bytes, percent)
 async fn download_file_with_progress<F>(
     url: &str,
     dest_path: &Path,
     mut progress_callback: F,
-) -> Result<(), String>
+) -> Result<(), InstallError>
 where
     F: FnMut(u64, u64, f64),
 {
@@ -55,7 +92,7 @@ where
         .await
         .map_err(|e| {
             log::error!("[HTTP] 下载请求失败: {} - {}", url, e);
-            format!("Failed to send request: {}", e)
+            InstallError::with_detail("error.network.requestFailed", &e.to_string())
         })?;
 
     let status = response.status();
@@ -70,11 +107,11 @@ where
                     let _ = std::fs::remove_file(dest_path);
                 }
                 std::fs::rename(&temp_path, dest_path)
-                    .map_err(|e| format!("Failed to rename file: {}", e))?;
+                    .map_err(|e| InstallError::with_detail("error.file.renameFailed", &e.to_string()))?;
                 return Ok(());
             }
         }
-        return Err(format!("HTTP {}: 下载失败", status.as_u16()));
+        return Err(InstallError::with_detail("error.network.httpError", &format!("HTTP {}", status.as_u16())));
     }
 
     // 206 表示部分内容(断点续传成功)
@@ -108,15 +145,15 @@ where
         .append(is_partial_content)
         .truncate(!is_partial_content)
         .open(&temp_path)
-        .map_err(|e| format!("Failed to open file: {}", e))?;
+        .map_err(|e| InstallError::with_detail("error.file.openFailed", &e.to_string()))?;
 
     let mut current_downloaded = if is_partial_content { downloaded_size } else { 0 };
     let mut stream = response.bytes_stream();
 
     while let Some(chunk_result) = stream.next().await {
-        let chunk = chunk_result.map_err(|e| format!("Failed to read chunk: {}", e))?;
+        let chunk = chunk_result.map_err(|e| InstallError::with_detail("error.network.readFailed", &e.to_string()))?;
         file.write_all(&chunk)
-            .map_err(|e| format!("Failed to write chunk: {}", e))?;
+            .map_err(|e| InstallError::with_detail("error.file.writeFailed", &e.to_string()))?;
 
         current_downloaded += chunk.len() as u64;
 
@@ -138,7 +175,7 @@ where
             final_size,
             total_size
         );
-        return Err("下载不完整,请重试".to_string());
+        return Err(InstallError::new("error.download.incomplete"));
     }
 
     // 重命名临时文件为最终文件
@@ -146,7 +183,7 @@ where
         let _ = std::fs::remove_file(dest_path);
     }
     std::fs::rename(&temp_path, dest_path)
-        .map_err(|e| format!("Failed to rename file: {}", e))?;
+        .map_err(|e| InstallError::with_detail("error.file.renameFailed", &e.to_string()))?;
 
     Ok(())
 }
@@ -206,14 +243,20 @@ pub async fn install_software(
 
     // 发送安装状态事件
     let emit_status = |message: &str, progress: f64, i18n_key: Option<&str>, skip_log: bool, i18n_params: Option<serde_json::Value>| {
-        let _ = app.emit("install-status", serde_json::json!({
+        let mut json = serde_json::json!({
             "software": software,
             "message": message,
             "progress": progress,
-            "i18nKey": i18n_key,
             "skipLog": skip_log,
-            "i18nParams": i18n_params,
-        }));
+        });
+        // 显式设置 i18nKey 和 i18nParams,避免 Option 序列化问题
+        if let Some(key) = i18n_key {
+            json["i18nKey"] = serde_json::Value::String(key.to_string());
+        }
+        if let Some(params) = i18n_params {
+            json["i18nParams"] = params;
+        }
+        let _ = app.emit("install-status", json);
     };
 
     // 检查是否取消
@@ -381,7 +424,7 @@ where
                 "downloaded": format!("{:.1}MB", downloaded_mb),
                 "total": format!("{:.1}MB", total_mb)
             })));
-        }).await?;
+        }).await.map_err(|e| e.to_message())?;
 
         if is_cancelled() {
             let _ = std::fs::remove_file(&msi_path);
@@ -587,7 +630,7 @@ where
                 "downloaded": format!("{:.1}MB", downloaded_mb),
                 "total": format!("{:.1}MB", total_mb)
             })));
-        }).await?;
+        }).await.map_err(|e| e.to_message())?;
 
         emit_status("正在安装 VS Code...", 65.0, Some("install.installing"), false, Some(serde_json::json!({
             "software": "VS Code"
@@ -680,7 +723,7 @@ where
                 "downloaded": format!("{:.1}MB", downloaded_mb),
                 "total": format!("{:.1}MB", total_mb)
             })));
-        }).await?;
+        }).await.map_err(|e| e.to_message())?;
 
         emit_status("正在安装 VS Code...", 65.0, Some("install.installing"), false, Some(serde_json::json!({
             "software": "VS Code"
@@ -823,7 +866,7 @@ where
                 "downloaded": format!("{:.1}MB", downloaded_mb),
                 "total": format!("{:.1}MB", total_mb)
             })));
-        }).await?;
+        }).await.map_err(|e| e.to_message())?;
 
         emit_status("正在安装 Git...", 65.0, Some("install.installing"), false, Some(serde_json::json!({
             "software": "Git"
@@ -1137,6 +1180,10 @@ where
             .map(|(name, _)| *name)
             .collect();
 
-        Err(format!("Failed to install: {}", failed.join(", ")))
+        Ok(InstallResult {
+            success: false,
+            message: format!("Failed to install: {}", failed.join(", ")),
+            i18n_key: Some("install.failedToInstall".to_string()),
+        })
     }
 }

+ 13 - 0
src/App.vue

@@ -83,10 +83,23 @@ async function handleCancel(_software: SoftwareTypeWithAll) {
 }
 
 // 获取翻译后的消息,如果有 i18nKey 则使用翻译,否则使用原始消息
+// 支持解析错误消息格式:i18nKey|detail
 function getTranslatedMessage(message: string, i18nKey?: string, i18nParams?: Record<string, string>): string {
   if (i18nKey) {
     return t(i18nKey, i18nParams || {})
   }
+  // 尝试解析错误消息格式:i18nKey|detail
+  if (message.includes('|')) {
+    const [errorKey, detail] = message.split('|', 2)
+    // 检查是否是有效的 i18nKey(以 error. 开头)
+    if (errorKey.startsWith('error.')) {
+      const translated = t(errorKey)
+      // 如果翻译成功(不等于 key 本身),则使用翻译
+      if (translated !== errorKey) {
+        return detail ? `${translated}: ${detail}` : translated
+      }
+    }
+  }
   return message
 }
 

+ 16 - 2
src/i18n/en-US.ts

@@ -153,7 +153,8 @@ export default {
     },
     all: {
       success: 'All software installed successfully'
-    }
+    },
+    failedToInstall: 'Installation failed'
   },
   settings: {
     title: 'Settings',
@@ -214,7 +215,20 @@ export default {
     installFailed: 'Installation Failed',
     unknownError: 'Unknown error, please check permissions/network',
     windowsOnly: 'This feature is only supported on Windows',
-    gitBashNotFound: 'Git Bash not found, please make sure Git is installed'
+    gitBashNotFound: 'Git Bash not found, please make sure Git is installed',
+    network: {
+      requestFailed: 'Network request failed',
+      httpError: 'Download failed',
+      readFailed: 'Failed to read data'
+    },
+    file: {
+      openFailed: 'Failed to open file',
+      writeFailed: 'Failed to write file',
+      renameFailed: 'Failed to rename file'
+    },
+    download: {
+      incomplete: 'Download incomplete, please retry'
+    }
   },
   queue: {
     title: 'Installation Queue',

+ 16 - 2
src/i18n/zh-CN.ts

@@ -153,7 +153,8 @@ export default {
     },
     all: {
       success: '全部软件安装成功'
-    }
+    },
+    failedToInstall: '安装失败'
   },
   settings: {
     title: '设置',
@@ -214,7 +215,20 @@ export default {
     installFailed: '安装失败',
     unknownError: '未知错误,请检查权限/网络',
     windowsOnly: '此功能仅支持 Windows 系统',
-    gitBashNotFound: '未找到 Git Bash,请确保已安装 Git'
+    gitBashNotFound: '未找到 Git Bash,请确保已安装 Git',
+    network: {
+      requestFailed: '网络请求失败',
+      httpError: '下载失败',
+      readFailed: '读取数据失败'
+    },
+    file: {
+      openFailed: '打开文件失败',
+      writeFailed: '写入文件失败',
+      renameFailed: '重命名文件失败'
+    },
+    download: {
+      incomplete: '下载不完整,请重试'
+    }
   },
   queue: {
     title: '安装队列',

+ 10 - 0
src/stores/settings.ts

@@ -4,6 +4,7 @@ import { defineStore } from 'pinia'
 import { ref } from 'vue'
 import { setLocale, getLocale, type LocaleType } from '@/i18n'
 import type { GitMirrorType, NodejsMirrorType } from '@/types/electron'
+import { info } from '@tauri-apps/plugin-log'
 
 type ThemeType = 'light' | 'dark' | 'system'
 
@@ -70,8 +71,17 @@ export const useSettingsStore = defineStore('settings', () => {
 
   // 设置语言
   function setLanguage(newLocale: LocaleType): void {
+    const oldLocale = locale.value
     locale.value = newLocale
     setLocale(newLocale)
+    // 记录语言切换日志
+    if (oldLocale !== newLocale) {
+      const localeNames: Record<LocaleType, string> = {
+        'zh-CN': '中文',
+        'en-US': 'English'
+      }
+      info(`Language changed: ${localeNames[oldLocale]} -> ${localeNames[newLocale]}`)
+    }
   }
 
   // 设置 Git 镜像