UI in English

Made-with: Cursor
This commit is contained in:
2026-03-06 19:49:38 +01:00
parent c90d2627df
commit e238c429e5
6 changed files with 75 additions and 75 deletions

View File

@@ -12,7 +12,7 @@ use tokio::net::TcpStream as TokioTcpStream;
/// Prüft die TCP-Verbindung zum Companion (synchron, kurzer Timeout). /// Prüft die TCP-Verbindung zum Companion (synchron, kurzer Timeout).
pub fn check_connection(host: &str, port: u16) -> Result<(), String> { pub fn check_connection(host: &str, port: u16) -> Result<(), String> {
if host.trim().is_empty() { if host.trim().is_empty() {
return Err("Host leer".to_string()); return Err("Host empty".to_string());
} }
let addrs: Vec<_> = (host.trim(), port) let addrs: Vec<_> = (host.trim(), port)
.to_socket_addrs() .to_socket_addrs()
@@ -20,7 +20,7 @@ pub fn check_connection(host: &str, port: u16) -> Result<(), String> {
.collect(); .collect();
let addr = addrs let addr = addrs
.first() .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())?; TcpStream::connect_timeout(addr, Duration::from_secs(2)).map_err(|e| e.to_string())?;
Ok(()) Ok(())
} }
@@ -40,7 +40,7 @@ pub async fn read_frame(stream: &mut TokioTcpStream) -> std::io::Result<Option<(
if n < 5 { if n < 5 {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof, std::io::ErrorKind::UnexpectedEof,
"Unvollständiger Companion-Frame-Header", "Incomplete companion frame header",
)); ));
} }
@@ -50,7 +50,7 @@ pub async fn read_frame(stream: &mut TokioTcpStream) -> std::io::Result<Option<(
if length > 10 * 1024 * 1024 { if length > 10 * 1024 * 1024 {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData, 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<Option<(
if r == 0 { if r == 0 {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof, std::io::ErrorKind::UnexpectedEof,
"Verbindung während Companion-Frame abgebrochen", "Connection closed during companion frame",
)); ));
} }
offset += r; offset += r;

View File

@@ -19,7 +19,7 @@ fn show_error(msg: &str) {
.encode_wide() .encode_wide()
.chain(std::iter::once(0)) .chain(std::iter::once(0))
.collect(); .collect();
let title: Vec<u16> = OsStr::new("HotKeet Fehler") let title: Vec<u16> = OsStr::new("HotKeet Error")
.encode_wide() .encode_wide()
.chain(std::iter::once(0)) .chain(std::iter::once(0))
.collect(); .collect();
@@ -35,7 +35,7 @@ fn show_error(msg: &str) {
#[cfg(not(windows))] #[cfg(not(windows))]
fn show_error(msg: &str) { fn show_error(msg: &str) {
eprintln!("Fehler: {}", msg); eprintln!("Error: {}", msg);
} }
#[cfg(windows)] #[cfg(windows)]
@@ -181,11 +181,11 @@ impl eframe::App for AppState {
.status .status
.read() .read()
.map(|s| match *s { .map(|s| match *s {
AppStatus::Bereit => "Bereit", AppStatus::Bereit => "Ready",
AppStatus::Aufnahme => "Aufnahme", AppStatus::Aufnahme => "Recording",
AppStatus::Transkribieren => "Transkribieren", AppStatus::Transkribieren => "Transcribing",
AppStatus::Fertig => "Fertig", AppStatus::Fertig => "Done",
AppStatus::Fehler => "Fehler", AppStatus::Fehler => "Error",
}) })
.unwrap_or("?"); .unwrap_or("?");
let detail = self let detail = self
@@ -198,8 +198,8 @@ impl eframe::App for AppState {
.read() .read()
.ok() .ok()
.and_then(|o| *o) .and_then(|o| *o)
.map(|t| format!("vor {} s", t.elapsed().as_secs())) .map(|t| format!("{} s ago", t.elapsed().as_secs()))
.unwrap_or_else(|| "noch nie empfangen".to_string()); .unwrap_or_else(|| "never received".to_string());
let companion_status = self let companion_status = self
.companion_status .companion_status
@@ -252,7 +252,7 @@ impl eframe::App for AppState {
&self.status, &self.status,
&self.status_detail, &self.status_detail,
AppStatus::Fertig, AppStatus::Fertig,
&format!("Eingefügt: {}", preview), &format!("Pasted: {}", preview),
); );
} }
Err(e) => { Err(e) => {
@@ -261,7 +261,7 @@ impl eframe::App for AppState {
&self.status, &self.status,
&self.status_detail, &self.status_detail,
AppStatus::Fehler, AppStatus::Fehler,
&format!("Einfügen: {}", e), &format!("Paste: {}", e),
); );
} }
} }
@@ -405,12 +405,12 @@ fn main() -> eframe::Result<()> {
|| err_str.to_lowercase().contains("adapter") || err_str.to_lowercase().contains("adapter")
{ {
"\n\nWindows Server ohne GPU:\n\ "\n\nWindows Server ohne GPU:\n\
- opengl32.dll von Mesa3D (fdossena.com/mesa) neben die EXE legen\n\ - Place opengl32.dll from Mesa3D (fdossena.com/mesa) next to the EXE\n\
- oder App auf Arbeitsplatz-PC mit GPU ausführen" - Or run app on a workstation PC with GPU"
} else { } else {
"" ""
}; };
show_error(&format!("Start fehlgeschlagen: {}{}", e, hint)); show_error(&format!("Start failed: {}{}", e, hint));
std::process::exit(1); std::process::exit(1);
} }
Ok(()) Ok(())
@@ -437,7 +437,7 @@ fn run_recording(
if config.sound_on_start_end { if config.sound_on_start_end {
sound::play_start(config.debug_logging); 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(); let use_companion = config.use_companion_microphone && !config.companion_host.is_empty();
if config.debug_logging { if config.debug_logging {
@@ -463,7 +463,7 @@ fn run_recording(
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
eprintln!("Aufnahme fehlgeschlagen: {}", 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; return;
} }
}; };
@@ -485,7 +485,7 @@ fn run_recording(
return; return;
} }
set_status(&status, &status_detail, AppStatus::Transkribieren, "Transkribieren…"); set_status(&status, &status_detail, AppStatus::Transkribieren, "Transcribing");
if config.sound_on_start_end { if config.sound_on_start_end {
sound::play_end(config.debug_logging); sound::play_end(config.debug_logging);
@@ -508,7 +508,7 @@ fn run_recording(
Err(e) => { Err(e) => {
eprintln!("Transkription: {}", e); eprintln!("Transkription: {}", e);
let _ = std::fs::remove_file(&wav_path); 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; return;
} }
}; };
@@ -541,6 +541,6 @@ fn run_recording(
}; };
if paste_tx.send(req).is_err() { if paste_tx.send(req).is_err() {
eprintln!("Paste-Kanal geschlossen"); eprintln!("Paste-Kanal geschlossen");
set_status(&status, &status_detail, AppStatus::Fehler, "Paste-Kanal fehlgeschlagen"); set_status(&status, &status_detail, AppStatus::Fehler, "Paste channel failed");
} }
} }

View File

@@ -13,7 +13,7 @@ use tokio::net::TcpStream;
/// Eintrag für die Eingabequellen-Auswahl: (source_index, Anzeigename) /// Eintrag für die Eingabequellen-Auswahl: (source_index, Anzeigename)
/// source_index 0 = Companion-App, 1.. = Mikrofon (input_device_index = source_index - 1) /// source_index 0 = Companion-App, 1.. = Mikrofon (input_device_index = source_index - 1)
pub fn list_input_sources() -> Vec<(usize, String)> { 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() { let devices = match cpal::default_host().input_devices() {
Ok(d) => d, Ok(d) => d,
@@ -21,8 +21,8 @@ pub fn list_input_sources() -> Vec<(usize, String)> {
}; };
for (idx, device) in devices.enumerate() { for (idx, device) in devices.enumerate() {
let name = device.name().unwrap_or_else(|_| format!("Mikrofon {}", idx)); let name = device.name().unwrap_or_else(|_| format!("Microphone {}", idx));
sources.push((idx + 1, format!("Mikrofon: {}", name))); sources.push((idx + 1, format!("Microphone: {}", name)));
} }
sources sources
} }
@@ -35,8 +35,8 @@ pub fn resolve_input_device_index(config: &DictateConfig) -> usize {
Err(_) => return config.input_device_index, Err(_) => return config.input_device_index,
}; };
for (idx, device) in devices.enumerate() { for (idx, device) in devices.enumerate() {
let name = device.name().unwrap_or_else(|_| format!("Mikrofon {}", idx)); let name = device.name().unwrap_or_else(|_| format!("Microphone {}", idx));
let display = format!("Mikrofon: {}", name); let display = format!("Microphone: {}", name);
if display == config.input_device_name { if display == config.input_device_name {
return idx; return idx;
} }
@@ -92,14 +92,14 @@ pub fn record_local(
let device_index = resolve_input_device_index(config); let device_index = resolve_input_device_index(config);
let device = host let device = host
.input_devices() .input_devices()
.map_err(|e| format!("Kein Audiogerät: {}", e))? .map_err(|e| format!("No audio device: {}", e))?
.nth(device_index) .nth(device_index)
.or_else(|| host.default_input_device()) .or_else(|| host.default_input_device())
.ok_or("Kein Eingabegerät gefunden")?; .ok_or("No input device found")?;
let supported = device let supported = device
.default_input_config() .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 sample_rate = supported.sample_rate();
let collector = Arc::new(SampleCollector::new()); let collector = Arc::new(SampleCollector::new());
@@ -143,7 +143,7 @@ pub fn record_local(
) )
.map_err(|e| format!("Stream build: {}", e))? .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))?; stream.play().map_err(|e| format!("Stream play: {}", e))?;
@@ -195,7 +195,7 @@ pub async fn record_companion(
let addr = format!("{}:{}", host, port); let addr = format!("{}:{}", host, port);
let mut stream = TcpStream::connect(&addr) let mut stream = TcpStream::connect(&addr)
.await .await
.map_err(|e| format!("Companion verbinden: {}", e))?; .map_err(|e| format!("Companion connect: {}", e))?;
let mut all_audio: Vec<i16> = Vec::new(); let mut all_audio: Vec<i16> = Vec::new();
let start = std::time::Instant::now(); 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, sample_format: hound::SampleFormat::Int,
}; };
let mut writer = hound::WavWriter::create(path, spec) 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 { 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(()) Ok(())
} }

View File

@@ -43,15 +43,15 @@ pub fn transcribe(
let output = cmd let output = cmd
.output() .output()
.map_err(|e| format!("parakeet-cli starten: {}", e))?; .map_err(|e| format!("Start parakeet-cli: {}", e))?;
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); 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) 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()) let json: serde_json::Value = serde_json::from_str(stdout.trim())
.map_err(|e| format!("parakeet-cli JSON: {}", e))?; .map_err(|e| format!("parakeet-cli JSON: {}", e))?;

View File

@@ -32,12 +32,12 @@ pub fn create_tray(tx: Sender<TrayMessage>) -> Option<tray_item::TrayItem> {
.ok()?; .ok()?;
let tx_show = tx.clone(); let tx_show = tx.clone();
tray.add_menu_item("Einstellungen", move || { tray.add_menu_item("Settings", move || {
let _ = tx_show.send(TrayMessage::ShowSettings); let _ = tx_show.send(TrayMessage::ShowSettings);
}) })
.ok()?; .ok()?;
tray.add_menu_item("Beenden", move || { tray.add_menu_item("Quit", move || {
let _ = tx.send(TrayMessage::Quit); let _ = tx.send(TrayMessage::Quit);
}) })
.ok()?; .ok()?;

View File

@@ -72,7 +72,7 @@ impl SettingsApp {
companion_status: &Option<Result<(), String>>, companion_status: &Option<Result<(), String>>,
) { ) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("HotKeet Einstellungen"); ui.heading("HotKeet Settings");
ui.add_space(8.0); ui.add_space(8.0);
@@ -84,10 +84,10 @@ impl SettingsApp {
ui.label(status_detail); 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 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(()); let _ = tx.send(());
} }
} }
@@ -97,7 +97,7 @@ impl SettingsApp {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Hotkey:"); ui.label("Hotkey:");
let label = if self.hotkey_capturing { let label = if self.hotkey_capturing {
"Taste drücken… (Esc = Abbrechen)" "Press key… (Esc = Cancel)"
} else { } else {
&self.config.global_hotkey &self.config.global_hotkey
}; };
@@ -119,7 +119,7 @@ impl SettingsApp {
hotkey::format_hotkey(mods.ctrl, mods.shift, mods.alt, key.name()) hotkey::format_hotkey(mods.ctrl, mods.shift, mods.alt, key.name())
{ {
self.config.global_hotkey = s; 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; self.hotkey_capturing = false;
} }
// Nur bei gültiger Haupttaste abbrechen, sonst weiter (Modifier überspringen) // Nur bei gültiger Haupttaste abbrechen, sonst weiter (Modifier überspringen)
@@ -134,7 +134,7 @@ impl SettingsApp {
ui.add_space(4.0); ui.add_space(4.0);
ui.label("Eingabequelle:"); ui.label("Input source:");
let sources = list_input_sources(); let sources = list_input_sources();
let mut selected = self.selected_source_index(&sources); let mut selected = self.selected_source_index(&sources);
let selected = &mut selected; let selected = &mut selected;
@@ -164,11 +164,11 @@ impl SettingsApp {
); );
}); });
match companion_status { match companion_status {
None => ui.label("Prüfe Verbindung"), None => ui.label("Checking connection"),
Some(Ok(())) => ui.label("Verbunden"), Some(Ok(())) => ui.label("Connected"),
Some(Err(e)) => ui.colored_label( Some(Err(e)) => ui.colored_label(
egui::Color32::RED, egui::Color32::RED,
format!("Nicht verbunden: {}", e), format!("Not connected: {}", e),
), ),
}; };
} }
@@ -176,87 +176,87 @@ impl SettingsApp {
ui.add_space(4.0); ui.add_space(4.0);
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("parakeet-cli Pfad:"); ui.label("parakeet-cli path:");
let display = if self.config.parakeet_cli_path.is_empty() { let display = if self.config.parakeet_cli_path.is_empty() {
"(leer = im PATH)".to_string() "(empty = in PATH)".to_string()
} else { } else {
self.config.parakeet_cli_path.clone() self.config.parakeet_cli_path.clone()
}; };
ui.label(egui::RichText::new(&display).color(egui::Color32::GRAY)); ui.label(egui::RichText::new(&display).color(egui::Color32::GRAY));
if ui.button("Durchsuchen").clicked() { if ui.button("Browse").clicked() {
let mut dialog = rfd::FileDialog::new().set_title("parakeet-cli auswählen"); let mut dialog = rfd::FileDialog::new().set_title("Select parakeet-cli");
#[cfg(windows)] #[cfg(windows)]
{ {
dialog = dialog.add_filter("Executable", &["exe"]); 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() { if let Some(p) = dialog.pick_file() {
self.config.parakeet_cli_path = p.display().to_string(); 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() { if !self.config.parakeet_cli_path.is_empty() && ui.small_button("").clicked() {
self.config.parakeet_cli_path.clear(); 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.horizontal(|ui| {
ui.label("Modellpfad:"); ui.label("Model path:");
let display = if self.config.model_path.is_empty() { let display = if self.config.model_path.is_empty() {
"(leer = Standardpfad)".to_string() "(empty = default path)".to_string()
} else { } else {
self.config.model_path.clone() self.config.model_path.clone()
}; };
ui.label(egui::RichText::new(&display).color(egui::Color32::GRAY)); 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() if let Some(p) = rfd::FileDialog::new()
.set_title("Modellordner auswählen") .set_title("Select model folder")
.pick_folder() .pick_folder()
{ {
self.config.model_path = p.display().to_string(); 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() { if !self.config.model_path.is_empty() && ui.small_button("").clicked() {
self.config.model_path.clear(); 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.add_space(4.0);
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Einfügemethode:"); ui.label("Paste method:");
let mut method = self.config.paste_method.clone(); let mut method = self.config.paste_method.clone();
egui::ComboBox::from_id_salt("paste_method") egui::ComboBox::from_id_salt("paste_method")
.selected_text(&method) .selected_text(&method)
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
ui.selectable_value(&mut method, "Auto".to_string(), "Auto (Tastaturpuffer, Fallback Clipboard)"); ui.selectable_value(&mut method, "Auto".to_string(), "Auto (keyboard buffer, fallback clipboard)");
ui.selectable_value(&mut method, "Keyboard".to_string(), "Nur Tastaturpuffer"); ui.selectable_value(&mut method, "Keyboard".to_string(), "Keyboard only");
ui.selectable_value(&mut method, "Clipboard".to_string(), "Nur Zwischenablage"); ui.selectable_value(&mut method, "Clipboard".to_string(), "Clipboard only");
}); });
self.config.paste_method = method; self.config.paste_method = method;
}); });
ui.add_space(4.0); ui.add_space(4.0);
ui.checkbox(&mut self.config.start_minimized, "Beim Start minimieren"); ui.checkbox(&mut self.config.start_minimized, "Minimize on start");
ui.checkbox(&mut self.config.minimize_to_tray, "In Tray minimieren"); ui.checkbox(&mut self.config.minimize_to_tray, "Minimize to tray");
ui.checkbox(&mut self.config.sound_on_start_end, "Signaltöne bei Start und Ende des Diktats"); 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, Konsole)"); ui.checkbox(&mut self.config.debug_logging, "Debug logging (paste-debug.log, console)");
ui.add_space(16.0); ui.add_space(16.0);
if ui.button("Speichern").clicked() { if ui.button("Save").clicked() {
match self.config.save() { match self.config.save() {
Ok(()) => { Ok(()) => {
if let Some(ref arc) = self.config_arc { if let Some(ref arc) = self.config_arc {
let _ = arc.write().map(|mut w| *w = self.config.clone()); 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),
} }
} }