feat(ui): localize commit AI and git error messages to English

Translate commit AI prompts, model labels, and git/keychain errors to
English so the app and generated messages are consistent. Also add a
discard confirmation dialog and update the UI text/styles to match the
new flow.

- src-tauri/crates/commit_ai/src/cloud.rs
  - Translate HTTP and API error messages to English.
  - Keep request timeout and token sizing behavior unchanged.
- src-tauri/crates/commit_ai/src/lib.rs
  - Translate model labels, prompt text, and validation errors.
  - Keep diff truncation and message sanitization logic intact.
- src-tauri/src/git.rs
  - Translate git, credential, merge, and history errors.
  - Update AI provider validation messages to English.
- src/lib/components/*
  - Update AI settings, commit panel, credential, and loading UI text.
  - Add discard confirmation dialog for destructive actions.
- src/App.svelte, src/app.css
  - Adjust app layout and styling for the new dialog and text changes.
This commit is contained in:
Christoph Brandau
2026-07-02 22:12:20 +02:00
parent 6b7186d040
commit 70de45e1ba
11 changed files with 537 additions and 187 deletions
+16 -16
View File
@@ -4,16 +4,16 @@ use serde::{Deserialize, Serialize};
use crate::{build_messages, sanitize_message};
// Großzügig bemessen, damit ein ausführlicher Body mit Stichpunkten nicht abgeschnitten wird.
// Generous sizing so a detailed body with bullet points isn't cut off.
const DEFAULT_MAX_TOKENS: u32 = 1500;
// Ohne Timeout würde ein hängender Endpoint den "AI"-Button dauerhaft blockieren.
// Without a timeout, a hanging endpoint would permanently block the "AI" button.
const REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
fn http_client() -> Result<reqwest::Client, String> {
reqwest::Client::builder()
.timeout(REQUEST_TIMEOUT)
.build()
.map_err(|err| format!("HTTP-Client konnte nicht erstellt werden: {err}"))
.map_err(|err| format!("Could not create HTTP client: {err}"))
}
#[derive(Serialize)]
@@ -71,19 +71,19 @@ async fn openai_compatible_request(
let response = request
.send()
.await
.map_err(|err| format!("Anfrage an das KI-Modell fehlgeschlagen: {err}"))?;
.map_err(|err| format!("Request to the AI model failed: {err}"))?;
let status = response.status();
let text = response
.text()
.await
.map_err(|err| format!("Antwort konnte nicht gelesen werden: {err}"))?;
.map_err(|err| format!("Could not read response: {err}"))?;
if !status.is_success() {
return Err(format!("API-Fehler ({status}): {text}"));
return Err(format!("API error ({status}): {text}"));
}
let parsed: OpenAiResponse = serde_json::from_str(&text)
.map_err(|err| format!("Antwort konnte nicht verarbeitet werden: {err}"))?;
.map_err(|err| format!("Could not process response: {err}"))?;
parsed
.choices
@@ -92,7 +92,7 @@ async fn openai_compatible_request(
.and_then(|choice| choice.message.content)
.map(|content| sanitize_message(&content))
.filter(|content| !content.is_empty())
.ok_or_else(|| "Das Modell hat keine Antwort geliefert.".to_string())
.ok_or_else(|| "The model did not return a response.".to_string())
}
pub async fn generate_openai(
@@ -102,7 +102,7 @@ pub async fn generate_openai(
notes: Option<&str>,
) -> Result<String, String> {
if api_key.trim().is_empty() {
return Err("OpenAI-API-Key fehlt.".to_string());
return Err("OpenAI API key is missing.".to_string());
}
openai_compatible_request(
"https://api.openai.com/v1/chat/completions".to_string(),
@@ -122,7 +122,7 @@ pub async fn generate_custom(
notes: Option<&str>,
) -> Result<String, String> {
if base_url.trim().is_empty() {
return Err("Endpoint-URL fehlt.".to_string());
return Err("Endpoint URL is missing.".to_string());
}
let url = format!("{}/chat/completions", base_url.trim_end_matches('/'));
openai_compatible_request(url, api_key, model, diff, notes).await
@@ -161,7 +161,7 @@ pub async fn generate_anthropic(
notes: Option<&str>,
) -> Result<String, String> {
if api_key.trim().is_empty() {
return Err("Anthropic-API-Key fehlt.".to_string());
return Err("Anthropic API key is missing.".to_string());
}
let (system, user) = build_messages(diff, notes)?;
let body = AnthropicRequest {
@@ -179,19 +179,19 @@ pub async fn generate_anthropic(
.json(&body)
.send()
.await
.map_err(|err| format!("Anfrage an Anthropic fehlgeschlagen: {err}"))?;
.map_err(|err| format!("Request to Anthropic failed: {err}"))?;
let status = response.status();
let text = response
.text()
.await
.map_err(|err| format!("Antwort konnte nicht gelesen werden: {err}"))?;
.map_err(|err| format!("Could not read response: {err}"))?;
if !status.is_success() {
return Err(format!("API-Fehler ({status}): {text}"));
return Err(format!("API error ({status}): {text}"));
}
let parsed: AnthropicResponse = serde_json::from_str(&text)
.map_err(|err| format!("Antwort konnte nicht verarbeitet werden: {err}"))?;
.map_err(|err| format!("Could not process response: {err}"))?;
parsed
.content
@@ -199,5 +199,5 @@ pub async fn generate_anthropic(
.find_map(|block| block.text)
.map(|text| sanitize_message(&text))
.filter(|text| !text.is_empty())
.ok_or_else(|| "Das Modell hat keine Antwort geliefert.".to_string())
.ok_or_else(|| "The model did not return a response.".to_string())
}