diff --git a/HotKeet/src/companion.rs b/HotKeet/src/companion.rs index 38db31e..0bfb4b6 100644 --- a/HotKeet/src/companion.rs +++ b/HotKeet/src/companion.rs @@ -12,7 +12,7 @@ use tokio::net::TcpStream as TokioTcpStream; /// Prüft die TCP-Verbindung zum Companion (synchron, kurzer Timeout). pub fn check_connection(host: &str, port: u16) -> Result<(), String> { if host.trim().is_empty() { - return Err("Host leer".to_string()); + return Err("Host empty".to_string()); } let addrs: Vec<_> = (host.trim(), port) .to_socket_addrs() @@ -20,7 +20,7 @@ pub fn check_connection(host: &str, port: u16) -> Result<(), String> { .collect(); let addr = addrs .first() - .ok_or_else(|| "Host konnte nicht aufgelöst werden".to_string())?; + .ok_or_else(|| "Host could not be resolved".to_string())?; TcpStream::connect_timeout(addr, Duration::from_secs(2)).map_err(|e| e.to_string())?; Ok(()) } @@ -40,7 +40,7 @@ pub async fn read_frame(stream: &mut TokioTcpStream) -> std::io::Result std::io::Result 10 * 1024 * 1024 { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, - "Ungültige Companion-Frame-Länge", + "Invalid companion frame length", )); } @@ -61,7 +61,7 @@ pub async fn read_frame(stream: &mut TokioTcpStream) -> std::io::Result = OsStr::new("HotKeet – Fehler") + let title: Vec = OsStr::new("HotKeet – Error") .encode_wide() .chain(std::iter::once(0)) .collect(); @@ -35,7 +35,7 @@ fn show_error(msg: &str) { #[cfg(not(windows))] fn show_error(msg: &str) { - eprintln!("Fehler: {}", msg); + eprintln!("Error: {}", msg); } #[cfg(windows)] @@ -181,11 +181,11 @@ impl eframe::App for AppState { .status .read() .map(|s| match *s { - AppStatus::Bereit => "Bereit", - AppStatus::Aufnahme => "Aufnahme", - AppStatus::Transkribieren => "Transkribieren", - AppStatus::Fertig => "Fertig", - AppStatus::Fehler => "Fehler", + AppStatus::Bereit => "Ready", + AppStatus::Aufnahme => "Recording", + AppStatus::Transkribieren => "Transcribing", + AppStatus::Fertig => "Done", + AppStatus::Fehler => "Error", }) .unwrap_or("?"); let detail = self @@ -198,8 +198,8 @@ impl eframe::App for AppState { .read() .ok() .and_then(|o| *o) - .map(|t| format!("vor {} s", t.elapsed().as_secs())) - .unwrap_or_else(|| "noch nie empfangen".to_string()); + .map(|t| format!("{} s ago", t.elapsed().as_secs())) + .unwrap_or_else(|| "never received".to_string()); let companion_status = self .companion_status @@ -252,7 +252,7 @@ impl eframe::App for AppState { &self.status, &self.status_detail, AppStatus::Fertig, - &format!("Eingefügt: {}", preview), + &format!("Pasted: {}", preview), ); } Err(e) => { @@ -261,7 +261,7 @@ impl eframe::App for AppState { &self.status, &self.status_detail, AppStatus::Fehler, - &format!("Einfügen: {}", e), + &format!("Paste: {}", e), ); } } @@ -405,12 +405,12 @@ fn main() -> eframe::Result<()> { || err_str.to_lowercase().contains("adapter") { "\n\nWindows Server ohne GPU:\n\ - - opengl32.dll von Mesa3D (fdossena.com/mesa) neben die EXE legen\n\ - - oder App auf Arbeitsplatz-PC mit GPU ausführen" + - Place opengl32.dll from Mesa3D (fdossena.com/mesa) next to the EXE\n\ + - Or run app on a workstation PC with GPU" } else { "" }; - show_error(&format!("Start fehlgeschlagen: {}{}", e, hint)); + show_error(&format!("Start failed: {}{}", e, hint)); std::process::exit(1); } Ok(()) @@ -437,7 +437,7 @@ fn run_recording( if config.sound_on_start_end { sound::play_start(config.debug_logging); } - set_status(&status, &status_detail, AppStatus::Aufnahme, "Aufnahme läuft…"); + set_status(&status, &status_detail, AppStatus::Aufnahme, "Recording…"); let use_companion = config.use_companion_microphone && !config.companion_host.is_empty(); if config.debug_logging { @@ -463,7 +463,7 @@ fn run_recording( Ok(s) => s, Err(e) => { eprintln!("Aufnahme fehlgeschlagen: {}", e); - set_status(&status, &status_detail, AppStatus::Fehler, &format!("Aufnahme: {}", e)); + set_status(&status, &status_detail, AppStatus::Fehler, &format!("Recording: {}", e)); return; } }; @@ -485,7 +485,7 @@ fn run_recording( return; } - set_status(&status, &status_detail, AppStatus::Transkribieren, "Transkribieren…"); + set_status(&status, &status_detail, AppStatus::Transkribieren, "Transcribing…"); if config.sound_on_start_end { sound::play_end(config.debug_logging); @@ -508,7 +508,7 @@ fn run_recording( Err(e) => { eprintln!("Transkription: {}", e); let _ = std::fs::remove_file(&wav_path); - set_status(&status, &status_detail, AppStatus::Fehler, &format!("Transkription: {}", e)); + set_status(&status, &status_detail, AppStatus::Fehler, &format!("Transcription: {}", e)); return; } }; @@ -541,6 +541,6 @@ fn run_recording( }; if paste_tx.send(req).is_err() { eprintln!("Paste-Kanal geschlossen"); - set_status(&status, &status_detail, AppStatus::Fehler, "Paste-Kanal fehlgeschlagen"); + set_status(&status, &status_detail, AppStatus::Fehler, "Paste channel failed"); } } diff --git a/HotKeet/src/recording.rs b/HotKeet/src/recording.rs index ad1c151..e08d4d1 100644 --- a/HotKeet/src/recording.rs +++ b/HotKeet/src/recording.rs @@ -13,7 +13,7 @@ use tokio::net::TcpStream; /// Eintrag für die Eingabequellen-Auswahl: (source_index, Anzeigename) /// source_index 0 = Companion-App, 1.. = Mikrofon (input_device_index = source_index - 1) pub fn list_input_sources() -> Vec<(usize, String)> { - let mut sources = vec![(0, "Companion-App".to_string())]; + let mut sources = vec![(0, "Companion app".to_string())]; let devices = match cpal::default_host().input_devices() { Ok(d) => d, @@ -21,8 +21,8 @@ pub fn list_input_sources() -> Vec<(usize, String)> { }; for (idx, device) in devices.enumerate() { - let name = device.name().unwrap_or_else(|_| format!("Mikrofon {}", idx)); - sources.push((idx + 1, format!("Mikrofon: {}", name))); + let name = device.name().unwrap_or_else(|_| format!("Microphone {}", idx)); + sources.push((idx + 1, format!("Microphone: {}", name))); } sources } @@ -35,8 +35,8 @@ pub fn resolve_input_device_index(config: &DictateConfig) -> usize { Err(_) => return config.input_device_index, }; for (idx, device) in devices.enumerate() { - let name = device.name().unwrap_or_else(|_| format!("Mikrofon {}", idx)); - let display = format!("Mikrofon: {}", name); + let name = device.name().unwrap_or_else(|_| format!("Microphone {}", idx)); + let display = format!("Microphone: {}", name); if display == config.input_device_name { return idx; } @@ -92,14 +92,14 @@ pub fn record_local( let device_index = resolve_input_device_index(config); let device = host .input_devices() - .map_err(|e| format!("Kein Audiogerät: {}", e))? + .map_err(|e| format!("No audio device: {}", e))? .nth(device_index) .or_else(|| host.default_input_device()) - .ok_or("Kein Eingabegerät gefunden")?; + .ok_or("No input device found")?; let supported = device .default_input_config() - .map_err(|e| format!("Kein Input-Config: {}", e))?; + .map_err(|e| format!("No input config: {}", e))?; let sample_rate = supported.sample_rate(); let collector = Arc::new(SampleCollector::new()); @@ -143,7 +143,7 @@ pub fn record_local( ) .map_err(|e| format!("Stream build: {}", e))? } - _ => return Err("Nur I16/F32 unterstützt".to_string()), + _ => return Err("Only I16/F32 supported".to_string()), }; stream.play().map_err(|e| format!("Stream play: {}", e))?; @@ -195,7 +195,7 @@ pub async fn record_companion( let addr = format!("{}:{}", host, port); let mut stream = TcpStream::connect(&addr) .await - .map_err(|e| format!("Companion verbinden: {}", e))?; + .map_err(|e| format!("Companion connect: {}", e))?; let mut all_audio: Vec = Vec::new(); let start = std::time::Instant::now(); @@ -233,10 +233,10 @@ pub fn write_wav(path: &std::path::Path, samples: &[i16]) -> Result<(), String> sample_format: hound::SampleFormat::Int, }; let mut writer = hound::WavWriter::create(path, spec) - .map_err(|e| format!("WAV erstellen: {}", e))?; + .map_err(|e| format!("Create WAV: {}", e))?; for &s in samples { - writer.write_sample(s).map_err(|e| format!("WAV schreiben: {}", e))?; + writer.write_sample(s).map_err(|e| format!("Write WAV: {}", e))?; } - writer.finalize().map_err(|e| format!("WAV finalisieren: {}", e))?; + writer.finalize().map_err(|e| format!("Finalize WAV: {}", e))?; Ok(()) } diff --git a/HotKeet/src/transcription.rs b/HotKeet/src/transcription.rs index 84a352f..1c22963 100644 --- a/HotKeet/src/transcription.rs +++ b/HotKeet/src/transcription.rs @@ -43,15 +43,15 @@ pub fn transcribe( let output = cmd .output() - .map_err(|e| format!("parakeet-cli starten: {}", e))?; + .map_err(|e| format!("Start parakeet-cli: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("parakeet-cli Fehler: {}", stderr)); + return Err(format!("parakeet-cli error: {}", stderr)); } let stdout = String::from_utf8(output.stdout) - .map_err(|_| "parakeet-cli Ausgabe ist kein UTF-8")?; + .map_err(|_| "parakeet-cli output is not UTF-8")?; let json: serde_json::Value = serde_json::from_str(stdout.trim()) .map_err(|e| format!("parakeet-cli JSON: {}", e))?; diff --git a/HotKeet/src/tray.rs b/HotKeet/src/tray.rs index 8fb726e..4def3a5 100644 --- a/HotKeet/src/tray.rs +++ b/HotKeet/src/tray.rs @@ -32,12 +32,12 @@ pub fn create_tray(tx: Sender) -> Option { .ok()?; let tx_show = tx.clone(); - tray.add_menu_item("Einstellungen", move || { + tray.add_menu_item("Settings", move || { let _ = tx_show.send(TrayMessage::ShowSettings); }) .ok()?; - tray.add_menu_item("Beenden", move || { + tray.add_menu_item("Quit", move || { let _ = tx.send(TrayMessage::Quit); }) .ok()?; diff --git a/HotKeet/src/ui.rs b/HotKeet/src/ui.rs index d4e4dec..53fba12 100644 --- a/HotKeet/src/ui.rs +++ b/HotKeet/src/ui.rs @@ -72,7 +72,7 @@ impl SettingsApp { companion_status: &Option>, ) { egui::CentralPanel::default().show(ctx, |ui| { - ui.heading("HotKeet – Einstellungen"); + ui.heading("HotKeet – Settings"); ui.add_space(8.0); @@ -84,10 +84,10 @@ impl SettingsApp { ui.label(status_detail); } }); - ui.label(format!("Letzter Hotkey: {}", hotkey_info)); + ui.label(format!("Last hotkey: {}", hotkey_info)); if let Some(ref tx) = self.test_tx { - if ui.button("Test: Aufnahme starten (3 s)").clicked() { + if ui.button("Test: Start recording (3 s)").clicked() { let _ = tx.send(()); } } @@ -97,7 +97,7 @@ impl SettingsApp { ui.horizontal(|ui| { ui.label("Hotkey:"); let label = if self.hotkey_capturing { - "Taste drücken… (Esc = Abbrechen)" + "Press key… (Esc = Cancel)" } else { &self.config.global_hotkey }; @@ -119,7 +119,7 @@ impl SettingsApp { hotkey::format_hotkey(mods.ctrl, mods.shift, mods.alt, key.name()) { self.config.global_hotkey = s; - self.status = "Änderungen speichern nicht vergessen.".to_string(); + self.status = "Remember to save changes.".to_string(); self.hotkey_capturing = false; } // Nur bei gültiger Haupttaste abbrechen, sonst weiter (Modifier überspringen) @@ -134,7 +134,7 @@ impl SettingsApp { ui.add_space(4.0); - ui.label("Eingabequelle:"); + ui.label("Input source:"); let sources = list_input_sources(); let mut selected = self.selected_source_index(&sources); let selected = &mut selected; @@ -164,11 +164,11 @@ impl SettingsApp { ); }); match companion_status { - None => ui.label("Prüfe Verbindung…"), - Some(Ok(())) => ui.label("Verbunden"), + None => ui.label("Checking connection…"), + Some(Ok(())) => ui.label("Connected"), Some(Err(e)) => ui.colored_label( egui::Color32::RED, - format!("Nicht verbunden: {}", e), + format!("Not connected: {}", e), ), }; } @@ -176,87 +176,87 @@ impl SettingsApp { ui.add_space(4.0); ui.horizontal(|ui| { - ui.label("parakeet-cli Pfad:"); + ui.label("parakeet-cli path:"); let display = if self.config.parakeet_cli_path.is_empty() { - "(leer = im PATH)".to_string() + "(empty = in PATH)".to_string() } else { self.config.parakeet_cli_path.clone() }; ui.label(egui::RichText::new(&display).color(egui::Color32::GRAY)); - if ui.button("Durchsuchen…").clicked() { - let mut dialog = rfd::FileDialog::new().set_title("parakeet-cli auswählen"); + if ui.button("Browse…").clicked() { + let mut dialog = rfd::FileDialog::new().set_title("Select parakeet-cli"); #[cfg(windows)] { dialog = dialog.add_filter("Executable", &["exe"]); } - dialog = dialog.add_filter("Alle Dateien", &["*"]); + dialog = dialog.add_filter("All files", &["*"]); if let Some(p) = dialog.pick_file() { self.config.parakeet_cli_path = p.display().to_string(); - self.status = "Änderungen speichern nicht vergessen.".to_string(); + self.status = "Remember to save changes.".to_string(); } } if !self.config.parakeet_cli_path.is_empty() && ui.small_button("✕").clicked() { self.config.parakeet_cli_path.clear(); - self.status = "Änderungen speichern nicht vergessen.".to_string(); + self.status = "Remember to save changes.".to_string(); } }); ui.horizontal(|ui| { - ui.label("Modellpfad:"); + ui.label("Model path:"); let display = if self.config.model_path.is_empty() { - "(leer = Standardpfad)".to_string() + "(empty = default path)".to_string() } else { self.config.model_path.clone() }; ui.label(egui::RichText::new(&display).color(egui::Color32::GRAY)); - if ui.button("Durchsuchen…").clicked() { + if ui.button("Browse…").clicked() { if let Some(p) = rfd::FileDialog::new() - .set_title("Modellordner auswählen") + .set_title("Select model folder") .pick_folder() { self.config.model_path = p.display().to_string(); - self.status = "Änderungen speichern nicht vergessen.".to_string(); + self.status = "Remember to save changes.".to_string(); } } if !self.config.model_path.is_empty() && ui.small_button("✕").clicked() { self.config.model_path.clear(); - self.status = "Änderungen speichern nicht vergessen.".to_string(); + self.status = "Remember to save changes.".to_string(); } }); ui.add_space(4.0); ui.horizontal(|ui| { - ui.label("Einfügemethode:"); + ui.label("Paste method:"); let mut method = self.config.paste_method.clone(); egui::ComboBox::from_id_salt("paste_method") .selected_text(&method) .show_ui(ui, |ui| { - ui.selectable_value(&mut method, "Auto".to_string(), "Auto (Tastaturpuffer, Fallback Clipboard)"); - ui.selectable_value(&mut method, "Keyboard".to_string(), "Nur Tastaturpuffer"); - ui.selectable_value(&mut method, "Clipboard".to_string(), "Nur Zwischenablage"); + ui.selectable_value(&mut method, "Auto".to_string(), "Auto (keyboard buffer, fallback clipboard)"); + ui.selectable_value(&mut method, "Keyboard".to_string(), "Keyboard only"); + ui.selectable_value(&mut method, "Clipboard".to_string(), "Clipboard only"); }); self.config.paste_method = method; }); ui.add_space(4.0); - ui.checkbox(&mut self.config.start_minimized, "Beim Start minimieren"); - ui.checkbox(&mut self.config.minimize_to_tray, "In Tray minimieren"); - ui.checkbox(&mut self.config.sound_on_start_end, "Signaltöne bei Start und Ende des Diktats"); - ui.checkbox(&mut self.config.debug_logging, "Debug-Logging (paste-debug.log, Konsole)"); + ui.checkbox(&mut self.config.start_minimized, "Minimize on start"); + ui.checkbox(&mut self.config.minimize_to_tray, "Minimize to tray"); + ui.checkbox(&mut self.config.sound_on_start_end, "Audio feedback on record start/end"); + ui.checkbox(&mut self.config.debug_logging, "Debug logging (paste-debug.log, console)"); ui.add_space(16.0); - if ui.button("Speichern").clicked() { + if ui.button("Save").clicked() { match self.config.save() { Ok(()) => { if let Some(ref arc) = self.config_arc { let _ = arc.write().map(|mut w| *w = self.config.clone()); } - self.status = "Gespeichert.".to_string(); + self.status = "Saved.".to_string(); } - Err(e) => self.status = format!("Fehler: {}", e), + Err(e) => self.status = format!("Error: {}", e), } }