UI in English
Made-with: Cursor
This commit is contained in:
@@ -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<Option<(
|
||||
if n < 5 {
|
||||
return Err(std::io::Error::new(
|
||||
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 {
|
||||
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<Option<(
|
||||
if r == 0 {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::UnexpectedEof,
|
||||
"Verbindung während Companion-Frame abgebrochen",
|
||||
"Connection closed during companion frame",
|
||||
));
|
||||
}
|
||||
offset += r;
|
||||
|
||||
@@ -19,7 +19,7 @@ fn show_error(msg: &str) {
|
||||
.encode_wide()
|
||||
.chain(std::iter::once(0))
|
||||
.collect();
|
||||
let title: Vec<u16> = OsStr::new("HotKeet – Fehler")
|
||||
let title: Vec<u16> = 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<i16> = 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(())
|
||||
}
|
||||
|
||||
@@ -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))?;
|
||||
|
||||
@@ -32,12 +32,12 @@ pub fn create_tray(tx: Sender<TrayMessage>) -> Option<tray_item::TrayItem> {
|
||||
.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()?;
|
||||
|
||||
@@ -72,7 +72,7 @@ impl SettingsApp {
|
||||
companion_status: &Option<Result<(), String>>,
|
||||
) {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user