From a6e8fc86511f26f3625d7fe57956157fef30a0ca Mon Sep 17 00:00:00 2001 From: Fero Date: Sun, 15 Mar 2026 15:44:48 +0100 Subject: [PATCH] fix: keep Windows microphone guidance Windows-only (#1032) * fix windows-only mic permission guidance * don't hardcode error strings in rust * format --------- Co-authored-by: CJ Pais --- src-tauri/src/actions.rs | 20 +++++++++++- src-tauri/src/audio_toolkit/audio/mod.rs | 2 +- src-tauri/src/audio_toolkit/audio/recorder.rs | 32 ++++++++++++++----- src-tauri/src/audio_toolkit/mod.rs | 3 +- src/App.tsx | 19 +++++++++-- src/i18n/locales/ar/translation.json | 7 ++++ src/i18n/locales/cs/translation.json | 7 ++++ src/i18n/locales/de/translation.json | 7 ++++ src/i18n/locales/en/translation.json | 7 ++++ src/i18n/locales/es/translation.json | 7 ++++ src/i18n/locales/fr/translation.json | 7 ++++ src/i18n/locales/it/translation.json | 7 ++++ src/i18n/locales/ja/translation.json | 7 ++++ src/i18n/locales/ko/translation.json | 7 ++++ src/i18n/locales/pl/translation.json | 7 ++++ src/i18n/locales/pt/translation.json | 7 ++++ src/i18n/locales/ru/translation.json | 7 ++++ src/i18n/locales/tr/translation.json | 7 ++++ src/i18n/locales/uk/translation.json | 7 ++++ src/i18n/locales/vi/translation.json | 7 ++++ src/i18n/locales/zh-TW/translation.json | 7 ++++ src/i18n/locales/zh/translation.json | 7 ++++ src/lib/types/events.ts | 5 +++ 23 files changed, 186 insertions(+), 14 deletions(-) diff --git a/src-tauri/src/actions.rs b/src-tauri/src/actions.rs index 5966b2c..510b3d7 100644 --- a/src-tauri/src/actions.rs +++ b/src-tauri/src/actions.rs @@ -1,6 +1,7 @@ #[cfg(all(target_os = "macos", target_arch = "aarch64"))] use crate::apple_intelligence; use crate::audio_feedback::{play_feedback_sound, play_feedback_sound_blocking, SoundType}; +use crate::audio_toolkit::is_microphone_access_denied; use crate::managers::audio::AudioRecordingManager; use crate::managers::history::HistoryManager; use crate::managers::transcription::TranscriptionManager; @@ -20,6 +21,12 @@ use std::time::Instant; use tauri::Manager; use tauri::{AppHandle, Emitter}; +#[derive(Clone, serde::Serialize)] +struct RecordingErrorEvent { + error_type: String, + detail: Option, +} + /// Drop guard that notifies the [`TranscriptionCoordinator`] when the /// transcription pipeline finishes — whether it completes normally or panics. struct FinishGuard(AppHandle); @@ -381,7 +388,18 @@ impl ShortcutAction for TranscribeAction { utils::hide_recording_overlay(app); change_tray_icon(app, TrayIconState::Idle); if let Some(err) = recording_error { - let _ = app.emit("recording-error", err); + let error_type = if is_microphone_access_denied(&err) { + "microphone_permission_denied" + } else { + "unknown" + }; + let _ = app.emit( + "recording-error", + RecordingErrorEvent { + error_type: error_type.to_string(), + detail: Some(err), + }, + ); } } diff --git a/src-tauri/src/audio_toolkit/audio/mod.rs b/src-tauri/src/audio_toolkit/audio/mod.rs index c0abd7e..fcc1003 100644 --- a/src-tauri/src/audio_toolkit/audio/mod.rs +++ b/src-tauri/src/audio_toolkit/audio/mod.rs @@ -6,7 +6,7 @@ mod utils; mod visualizer; pub use device::{list_input_devices, list_output_devices, CpalDeviceInfo}; -pub use recorder::AudioRecorder; +pub use recorder::{is_microphone_access_denied, AudioRecorder}; pub use resampler::FrameResampler; pub use utils::save_wav_file; pub use visualizer::AudioVisualiser; diff --git a/src-tauri/src/audio_toolkit/audio/recorder.rs b/src-tauri/src/audio_toolkit/audio/recorder.rs index f9c00ab..3d23e74 100644 --- a/src-tauri/src/audio_toolkit/audio/recorder.rs +++ b/src-tauri/src/audio_toolkit/audio/recorder.rs @@ -148,9 +148,8 @@ impl AudioRecorder { drop(stream); } Err(error_message) => { - let normalized_error = normalize_microphone_error(error_message); - log::error!("{normalized_error}"); - let _ = init_tx.send(Err(normalized_error)); + log::error!("{error_message}"); + let _ = init_tx.send(Err(error_message)); } } }); @@ -292,19 +291,36 @@ impl AudioRecorder { } } -fn is_microphone_access_denied(error_message: &str) -> bool { +pub fn is_microphone_access_denied(error_message: &str) -> bool { let normalized = error_message.to_lowercase(); normalized.contains("access is denied") || normalized.contains("permission denied") || normalized.contains("0x80070005") } -fn normalize_microphone_error(error_message: String) -> String { - if is_microphone_access_denied(&error_message) { - return "Microphone access was denied by the operating system. On Windows, enable Settings → Privacy & security → Microphone (including desktop app access), then restart Handy.".to_string(); +#[cfg(test)] +mod tests { + use super::is_microphone_access_denied; + + #[test] + fn detects_access_is_denied() { + assert!(is_microphone_access_denied("Access is denied")); } - error_message + #[test] + fn detects_permission_denied() { + assert!(is_microphone_access_denied("permission denied")); + } + + #[test] + fn detects_windows_error_code() { + assert!(is_microphone_access_denied("WASAPI error: 0x80070005")); + } + + #[test] + fn does_not_match_unrelated_errors() { + assert!(!is_microphone_access_denied("device not found")); + } } fn run_consumer( diff --git a/src-tauri/src/audio_toolkit/mod.rs b/src-tauri/src/audio_toolkit/mod.rs index 5aaa3ff..2e51fc5 100644 --- a/src-tauri/src/audio_toolkit/mod.rs +++ b/src-tauri/src/audio_toolkit/mod.rs @@ -5,7 +5,8 @@ pub mod utils; pub mod vad; pub use audio::{ - list_input_devices, list_output_devices, save_wav_file, AudioRecorder, CpalDeviceInfo, + is_microphone_access_denied, list_input_devices, list_output_devices, save_wav_file, + AudioRecorder, CpalDeviceInfo, }; pub use text::{apply_custom_words, filter_transcription_output}; pub use utils::get_cpal_host; diff --git a/src/App.tsx b/src/App.tsx index 9ff3c78..e09ade6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import { checkAccessibilityPermission, checkMicrophonePermission, } from "tauri-plugin-macos-permissions-api"; -import { ModelStateEvent } from "./lib/types/events"; +import { ModelStateEvent, RecordingErrorEvent } from "./lib/types/events"; import "./App.css"; import AccessibilityPermissions from "./components/AccessibilityPermissions"; import Footer from "./components/footer"; @@ -97,8 +97,21 @@ function App() { // Listen for recording errors from the backend and show a toast useEffect(() => { - const unlisten = listen("recording-error", (event) => { - toast.error(t("errors.recordingFailed", { error: event.payload })); + const unlisten = listen("recording-error", (event) => { + const { error_type, detail } = event.payload; + + if (error_type === "microphone_permission_denied") { + const currentPlatform = platform(); + const platformKey = `errors.micPermissionDenied.${currentPlatform}`; + const description = t(platformKey, { + defaultValue: t("errors.micPermissionDenied.generic"), + }); + toast.error(t("errors.micPermissionDeniedTitle"), { description }); + } else { + toast.error( + t("errors.recordingFailed", { error: detail ?? "Unknown error" }), + ); + } }); return () => { unlisten.then((fn) => fn()); diff --git a/src/i18n/locales/ar/translation.json b/src/i18n/locales/ar/translation.json index 7fda3c0..ae34e3f 100644 --- a/src/i18n/locales/ar/translation.json +++ b/src/i18n/locales/ar/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "خطأ في تحميل المجلد: {{error}}", + "micPermissionDeniedTitle": "تم رفض الوصول إلى الميكروفون", + "micPermissionDenied": { + "generic": "تم رفض الوصول إلى الميكروفون من قبل نظام التشغيل. يرجى منح إذن الميكروفون في إعدادات النظام.", + "windows": "قم بتمكين الوصول إلى الميكروفون في الإعدادات ← الخصوصية والأمان ← الميكروفون (بما في ذلك وصول تطبيقات سطح المكتب).", + "macos": "امنح الوصول إلى الميكروفون في إعدادات النظام ← الخصوصية والأمان ← الميكروفون.", + "linux": "امنح الوصول إلى الميكروفون في إعدادات الصوت أو الخصوصية في نظامك." + }, "recordingFailed": "فشل في بدء التسجيل: {{error}}", "modelLoadFailed": "فشل في تحميل النموذج: {{model}}", "modelLoadFailedUnknown": "نموذج غير معروف" diff --git a/src/i18n/locales/cs/translation.json b/src/i18n/locales/cs/translation.json index b8003ba..0b3e490 100644 --- a/src/i18n/locales/cs/translation.json +++ b/src/i18n/locales/cs/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Chyba při načítání adresáře: {{error}}", + "micPermissionDeniedTitle": "Přístup k mikrofonu byl odepřen", + "micPermissionDenied": { + "generic": "Přístup k mikrofonu byl odepřen operačním systémem. Povolte prosím přístup k mikrofonu v nastavení systému.", + "windows": "Povolte přístup k mikrofonu v Nastavení → Soukromí a zabezpečení → Mikrofon (včetně přístupu desktopových aplikací).", + "macos": "Povolte přístup k mikrofonu v Nastavení systému → Soukromí a zabezpečení → Mikrofon.", + "linux": "Povolte přístup k mikrofonu v nastavení zvuku nebo soukromí vašeho systému." + }, "recordingFailed": "Nepodařilo se spustit nahrávání: {{error}}", "modelLoadFailed": "Nepodařilo se načíst model: {{model}}", "modelLoadFailedUnknown": "neznámý model" diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json index 651a5af..42d5f58 100644 --- a/src/i18n/locales/de/translation.json +++ b/src/i18n/locales/de/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Fehler beim Laden des Verzeichnisses: {{error}}", + "micPermissionDeniedTitle": "Mikrofonzugriff verweigert", + "micPermissionDenied": { + "generic": "Der Mikrofonzugriff wurde vom Betriebssystem verweigert. Bitte erteilen Sie die Mikrofonberechtigung in Ihren Systemeinstellungen.", + "windows": "Aktivieren Sie den Mikrofonzugriff unter Einstellungen → Datenschutz und Sicherheit → Mikrofon (einschließlich Desktop-App-Zugriff).", + "macos": "Erteilen Sie den Mikrofonzugriff in Systemeinstellungen → Datenschutz & Sicherheit → Mikrofon.", + "linux": "Erteilen Sie den Mikrofonzugriff in den Sound- oder Datenschutzeinstellungen Ihres Systems." + }, "recordingFailed": "Aufnahme konnte nicht gestartet werden: {{error}}", "modelLoadFailed": "Modell konnte nicht geladen werden: {{model}}", "modelLoadFailedUnknown": "unbekanntes Modell" diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index b199e57..b2667bd 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Error loading directory: {{error}}", + "micPermissionDeniedTitle": "Microphone Access Denied", + "micPermissionDenied": { + "generic": "Microphone access was denied by the operating system. Please grant microphone permission in your system settings.", + "windows": "Enable microphone access in Settings → Privacy & security → Microphone (including desktop app access).", + "macos": "Grant microphone access in System Settings → Privacy & Security → Microphone.", + "linux": "Grant microphone access in your system's sound or privacy settings." + }, "recordingFailed": "Failed to start recording: {{error}}", "modelLoadFailed": "Failed to load model: {{model}}", "modelLoadFailedUnknown": "unknown model" diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index 259b7fa..d18843b 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Error al cargar el directorio: {{error}}", + "micPermissionDeniedTitle": "Acceso al micrófono denegado", + "micPermissionDenied": { + "generic": "El sistema operativo denegó el acceso al micrófono. Por favor, conceda el permiso del micrófono en la configuración del sistema.", + "windows": "Active el acceso al micrófono en Configuración → Privacidad y seguridad → Micrófono (incluido el acceso de aplicaciones de escritorio).", + "macos": "Conceda el acceso al micrófono en Ajustes del Sistema → Privacidad y seguridad → Micrófono.", + "linux": "Conceda el acceso al micrófono en la configuración de sonido o privacidad de su sistema." + }, "recordingFailed": "Error al iniciar la grabación: {{error}}", "modelLoadFailed": "Error al cargar el modelo: {{model}}", "modelLoadFailedUnknown": "modelo desconocido" diff --git a/src/i18n/locales/fr/translation.json b/src/i18n/locales/fr/translation.json index c8609f8..58aedec 100644 --- a/src/i18n/locales/fr/translation.json +++ b/src/i18n/locales/fr/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Erreur lors du chargement du répertoire : {{error}}", + "micPermissionDeniedTitle": "Accès au microphone refusé", + "micPermissionDenied": { + "generic": "L'accès au microphone a été refusé par le système d'exploitation. Veuillez accorder l'autorisation du microphone dans les paramètres système.", + "windows": "Activez l'accès au microphone dans Paramètres → Confidentialité et sécurité → Microphone (y compris l'accès des applications de bureau).", + "macos": "Accordez l'accès au microphone dans Réglages du système → Confidentialité et sécurité → Microphone.", + "linux": "Accordez l'accès au microphone dans les paramètres de son ou de confidentialité de votre système." + }, "recordingFailed": "Échec du démarrage de l'enregistrement : {{error}}", "modelLoadFailed": "Échec du chargement du modèle : {{model}}", "modelLoadFailedUnknown": "modèle inconnu" diff --git a/src/i18n/locales/it/translation.json b/src/i18n/locales/it/translation.json index 71dcb82..6c5fc22 100644 --- a/src/i18n/locales/it/translation.json +++ b/src/i18n/locales/it/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Errore di caricamento cartella: {{error}}", + "micPermissionDeniedTitle": "Accesso al microfono negato", + "micPermissionDenied": { + "generic": "L'accesso al microfono è stato negato dal sistema operativo. Concedi l'autorizzazione al microfono nelle impostazioni di sistema.", + "windows": "Abilita l'accesso al microfono in Impostazioni → Privacy e sicurezza → Microfono (incluso l'accesso delle app desktop).", + "macos": "Concedi l'accesso al microfono in Impostazioni di Sistema → Privacy e sicurezza → Microfono.", + "linux": "Concedi l'accesso al microfono nelle impostazioni audio o sulla privacy del tuo sistema." + }, "recordingFailed": "Impossibile avviare la registrazione: {{error}}", "modelLoadFailed": "Impossibile caricare il modello: {{model}}", "modelLoadFailedUnknown": "modello sconosciuto" diff --git a/src/i18n/locales/ja/translation.json b/src/i18n/locales/ja/translation.json index 68cb0e9..f16c9ef 100644 --- a/src/i18n/locales/ja/translation.json +++ b/src/i18n/locales/ja/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "ディレクトリの読み込みエラー: {{error}}", + "micPermissionDeniedTitle": "マイクへのアクセスが拒否されました", + "micPermissionDenied": { + "generic": "オペレーティングシステムによりマイクへのアクセスが拒否されました。システム設定でマイクの許可を付与してください。", + "windows": "設定 → プライバシーとセキュリティ → マイク(デスクトップアプリのアクセスを含む)でマイクへのアクセスを有効にしてください。", + "macos": "システム設定 → プライバシーとセキュリティ → マイクでマイクへのアクセスを許可してください。", + "linux": "システムのサウンドまたはプライバシー設定でマイクへのアクセスを許可してください。" + }, "recordingFailed": "録音の開始に失敗しました: {{error}}", "modelLoadFailed": "モデルの読み込みに失敗しました: {{model}}", "modelLoadFailedUnknown": "不明なモデル" diff --git a/src/i18n/locales/ko/translation.json b/src/i18n/locales/ko/translation.json index 0970283..bda6629 100644 --- a/src/i18n/locales/ko/translation.json +++ b/src/i18n/locales/ko/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "디렉토리 로딩 오류: {{error}}", + "micPermissionDeniedTitle": "마이크 접근이 거부되었습니다", + "micPermissionDenied": { + "generic": "운영 체제에서 마이크 접근을 거부했습니다. 시스템 설정에서 마이크 권한을 허용해 주세요.", + "windows": "설정 → 개인 정보 및 보안 → 마이크(데스크톱 앱 접근 포함)에서 마이크 접근을 활성화하세요.", + "macos": "시스템 설정 → 개인 정보 보호 및 보안 → 마이크에서 마이크 접근을 허용하세요.", + "linux": "시스템의 소리 또는 개인 정보 설정에서 마이크 접근을 허용하세요." + }, "recordingFailed": "녹음을 시작하지 못했습니다: {{error}}", "modelLoadFailed": "모델을 불러오지 못했습니다: {{model}}", "modelLoadFailedUnknown": "알 수 없는 모델" diff --git a/src/i18n/locales/pl/translation.json b/src/i18n/locales/pl/translation.json index 64d2445..0343dab 100644 --- a/src/i18n/locales/pl/translation.json +++ b/src/i18n/locales/pl/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Błąd wczytywania katalogu: {{error}}", + "micPermissionDeniedTitle": "Odmowa dostępu do mikrofonu", + "micPermissionDenied": { + "generic": "System operacyjny odmówił dostępu do mikrofonu. Przyznaj uprawnienia do mikrofonu w ustawieniach systemowych.", + "windows": "Włącz dostęp do mikrofonu w Ustawienia → Prywatność i zabezpieczenia → Mikrofon (w tym dostęp aplikacji klasycznych).", + "macos": "Przyznaj dostęp do mikrofonu w Ustawienia systemowe → Prywatność i ochrona → Mikrofon.", + "linux": "Przyznaj dostęp do mikrofonu w ustawieniach dźwięku lub prywatności systemu." + }, "recordingFailed": "Nie udało się rozpocząć nagrywania: {{error}}", "modelLoadFailed": "Nie udało się załadować modelu: {{model}}", "modelLoadFailedUnknown": "nieznany model" diff --git a/src/i18n/locales/pt/translation.json b/src/i18n/locales/pt/translation.json index 1c822a7..834b16a 100644 --- a/src/i18n/locales/pt/translation.json +++ b/src/i18n/locales/pt/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Erro ao carregar diretório: {{error}}", + "micPermissionDeniedTitle": "Acesso ao microfone negado", + "micPermissionDenied": { + "generic": "O acesso ao microfone foi negado pelo sistema operacional. Conceda a permissão do microfone nas configurações do sistema.", + "windows": "Ative o acesso ao microfone em Configurações → Privacidade e segurança → Microfone (incluindo acesso de aplicativos da área de trabalho).", + "macos": "Conceda o acesso ao microfone em Ajustes do Sistema → Privacidade e Segurança → Microfone.", + "linux": "Conceda o acesso ao microfone nas configurações de som ou privacidade do seu sistema." + }, "recordingFailed": "Falha ao iniciar a gravação: {{error}}", "modelLoadFailed": "Falha ao carregar o modelo: {{model}}", "modelLoadFailedUnknown": "modelo desconhecido" diff --git a/src/i18n/locales/ru/translation.json b/src/i18n/locales/ru/translation.json index 4f3dfc6..a6cd534 100644 --- a/src/i18n/locales/ru/translation.json +++ b/src/i18n/locales/ru/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Ошибка загрузки каталога: {{error}}.", + "micPermissionDeniedTitle": "Доступ к микрофону запрещён", + "micPermissionDenied": { + "generic": "Доступ к микрофону был запрещён операционной системой. Предоставьте разрешение на использование микрофона в настройках системы.", + "windows": "Включите доступ к микрофону в Параметры → Конфиденциальность и безопасность → Микрофон (включая доступ классических приложений).", + "macos": "Предоставьте доступ к микрофону в Системные настройки → Конфиденциальность и безопасность → Микрофон.", + "linux": "Предоставьте доступ к микрофону в настройках звука или конфиденциальности вашей системы." + }, "recordingFailed": "Не удалось начать запись: {{error}}", "modelLoadFailed": "Не удалось загрузить модель: {{model}}", "modelLoadFailedUnknown": "неизвестная модель" diff --git a/src/i18n/locales/tr/translation.json b/src/i18n/locales/tr/translation.json index aa7ab9d..8c27716 100644 --- a/src/i18n/locales/tr/translation.json +++ b/src/i18n/locales/tr/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Dizin yüklenirken hata oluştu: {{error}}", + "micPermissionDeniedTitle": "Mikrofon erişimi reddedildi", + "micPermissionDenied": { + "generic": "Mikrofon erişimi işletim sistemi tarafından reddedildi. Lütfen sistem ayarlarından mikrofon iznini verin.", + "windows": "Ayarlar → Gizlilik ve güvenlik → Mikrofon (masaüstü uygulama erişimi dahil) bölümünden mikrofon erişimini etkinleştirin.", + "macos": "Sistem Ayarları → Gizlilik ve Güvenlik → Mikrofon bölümünden mikrofon erişimini verin.", + "linux": "Sisteminizin ses veya gizlilik ayarlarından mikrofon erişimini verin." + }, "recordingFailed": "Kayıt başlatılamadı: {{error}}", "modelLoadFailed": "Model yüklenemedi: {{model}}", "modelLoadFailedUnknown": "bilinmeyen model" diff --git a/src/i18n/locales/uk/translation.json b/src/i18n/locales/uk/translation.json index 0aeedcb..fac5675 100644 --- a/src/i18n/locales/uk/translation.json +++ b/src/i18n/locales/uk/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Помилка завантаження папки: {{error}}", + "micPermissionDeniedTitle": "Доступ до мікрофона заборонено", + "micPermissionDenied": { + "generic": "Доступ до мікрофона було заборонено операційною системою. Надайте дозвіл на використання мікрофона в налаштуваннях системи.", + "windows": "Увімкніть доступ до мікрофона в Параметри → Конфіденційність і безпека → Мікрофон (включно з доступом класичних програм).", + "macos": "Надайте доступ до мікрофона в Системні налаштування → Конфіденційність і безпека → Мікрофон.", + "linux": "Надайте доступ до мікрофона в налаштуваннях звуку або конфіденційності вашої системи." + }, "recordingFailed": "Не вдалося розпочати запис: {{error}}", "modelLoadFailed": "Не вдалося завантажити модель: {{model}}", "modelLoadFailedUnknown": "невідома модель" diff --git a/src/i18n/locales/vi/translation.json b/src/i18n/locales/vi/translation.json index be4f92c..e651d93 100644 --- a/src/i18n/locales/vi/translation.json +++ b/src/i18n/locales/vi/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "Lỗi khi tải thư mục: {{error}}", + "micPermissionDeniedTitle": "Quyền truy cập micrô bị từ chối", + "micPermissionDenied": { + "generic": "Quyền truy cập micrô đã bị hệ điều hành từ chối. Vui lòng cấp quyền micrô trong cài đặt hệ thống.", + "windows": "Bật quyền truy cập micrô trong Cài đặt → Quyền riêng tư & bảo mật → Micrô (bao gồm quyền truy cập ứng dụng máy tính).", + "macos": "Cấp quyền truy cập micrô trong Cài đặt hệ thống → Quyền riêng tư & Bảo mật → Micrô.", + "linux": "Cấp quyền truy cập micrô trong cài đặt âm thanh hoặc quyền riêng tư của hệ thống." + }, "recordingFailed": "Không thể bắt đầu ghi âm: {{error}}", "modelLoadFailed": "Không thể tải mô hình: {{model}}", "modelLoadFailedUnknown": "mô hình không xác định" diff --git a/src/i18n/locales/zh-TW/translation.json b/src/i18n/locales/zh-TW/translation.json index 1152ecd..49e5150 100644 --- a/src/i18n/locales/zh-TW/translation.json +++ b/src/i18n/locales/zh-TW/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "載入目錄時發生錯誤: {{error}}", + "micPermissionDeniedTitle": "麥克風存取被拒絕", + "micPermissionDenied": { + "generic": "作業系統拒絕了麥克風存取。請在系統設定中授予麥克風權限。", + "windows": "在設定 → 隱私與安全性 → 麥克風(包括桌面應用程式存取)中啟用麥克風存取。", + "macos": "在系統設定 → 隱私與安全性 → 麥克風中授予麥克風存取權限。", + "linux": "在系統的聲音或隱私設定中授予麥克風存取權限。" + }, "recordingFailed": "錄音啟動失敗: {{error}}", "modelLoadFailed": "載入模型失敗: {{model}}", "modelLoadFailedUnknown": "未知模型" diff --git a/src/i18n/locales/zh/translation.json b/src/i18n/locales/zh/translation.json index ca352b1..2dbc934 100644 --- a/src/i18n/locales/zh/translation.json +++ b/src/i18n/locales/zh/translation.json @@ -542,6 +542,13 @@ }, "errors": { "loadDirectory": "加载目录时出错: {{error}}", + "micPermissionDeniedTitle": "麦克风访问被拒绝", + "micPermissionDenied": { + "generic": "操作系统拒绝了麦克风访问。请在系统设置中授予麦克风权限。", + "windows": "在设置 → 隐私和安全性 → 麦克风(包括桌面应用访问)中启用麦克风访问。", + "macos": "在系统设置 → 隐私与安全性 → 麦克风中授予麦克风访问权限。", + "linux": "在系统的声音或隐私设置中授予麦克风访问权限。" + }, "recordingFailed": "录音启动失败: {{error}}", "modelLoadFailed": "加载模型失败: {{model}}", "modelLoadFailedUnknown": "未知模型" diff --git a/src/lib/types/events.ts b/src/lib/types/events.ts index a1fb00d..dcc16e3 100644 --- a/src/lib/types/events.ts +++ b/src/lib/types/events.ts @@ -4,3 +4,8 @@ export interface ModelStateEvent { model_name?: string; error?: string; } + +export interface RecordingErrorEvent { + error_type: string; + detail?: string; +}