fix(issue 522): surface paste errors as UI toast notification (#1198)

* fix: surface paste errors as UI toast notification (#522)

When pasting the transcription fails (e.g. wtype/xdotool/dotool not
available or returns an error on Linux), the error was silently logged
to the backend console only.  Users had no idea why their text was not
pasted.

Emit a new 'paste-error' Tauri event from the Rust side and listen for
it in the frontend App component, showing a sonner toast with the error
detail — mirroring the existing 'recording-error' pattern exactly.

Files changed:
- src-tauri/src/actions.rs: add PasteErrorEvent struct, emit event on paste failure
- src/App.tsx: add useEffect listener that shows toast.error on paste-error
- src/lib/types/events.ts: add PasteErrorEvent interface
- src/i18n/locales/en/translation.json: add pasteFailedTitle/pasteFailed keys

* chore: apply cargo fmt and prettier formatting

* add translationss

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
This commit is contained in:
odi 2026-04-07 11:36:48 +02:00 committed by GitHub
parent 84d88f911c
commit 30b57c4295
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 79 additions and 21 deletions

View file

@ -612,7 +612,10 @@ impl ShortcutAction for TranscribeAction {
"Text pasted successfully in {:?}",
paste_time.elapsed()
),
Err(e) => error!("Failed to paste transcription: {}", e),
Err(e) => {
error!("Failed to paste transcription: {}", e);
let _ = ah_clone.emit("paste-error", ());
}
}
utils::hide_recording_overlay(&ah_clone);
change_tray_icon(&ah_clone, TrayIconState::Idle);

View file

@ -122,6 +122,21 @@ function App() {
};
}, [t]);
// Listen for paste failures and show a toast.
// The technical error detail is logged to handy.log on the Rust side
// (see actions.rs `error!("Failed to paste transcription: ...")`),
// so we show a localized, user-friendly message here instead of the raw error.
useEffect(() => {
const unlisten = listen("paste-error", () => {
toast.error(t("errors.pasteFailedTitle"), {
description: t("errors.pasteFailed"),
});
});
return () => {
unlisten.then((fn) => fn());
};
}, [t]);
// Listen for model loading failures and show a toast
useEffect(() => {
const unlisten = listen<ModelStateEvent>("model-state-changed", (event) => {

View file

@ -597,7 +597,9 @@
"noInputDevice": "لم يتم اكتشاف أي جهاز إدخال صوتي. يرجى توصيل ميكروفون أو سماعة رأس والمحاولة مرة أخرى.",
"recordingFailed": "فشل في بدء التسجيل: {{error}}",
"modelLoadFailed": "فشل في تحميل النموذج: {{model}}",
"modelLoadFailedUnknown": "نموذج غير معروف"
"modelLoadFailedUnknown": "نموذج غير معروف",
"pasteFailedTitle": "فشل لصق النص",
"pasteFailed": "تعذر لصق النص في التطبيق النشط."
},
"appLanguage": {
"title": "لغة التطبيق",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Не е открито аудио входно устройство. Свържете микрофон или слушалки и опитайте отново.",
"recordingFailed": "Стартирането на записа не бе успешно: {{error}}",
"modelLoadFailed": "Зареждането на модела не бе успешно: {{model}}",
"modelLoadFailedUnknown": "неизвестен модел"
"modelLoadFailedUnknown": "неизвестен модел",
"pasteFailedTitle": "Неуспешно поставяне на текст",
"pasteFailed": "Текстът не можа да бъде поставен в активното приложение."
},
"appLanguage": {
"title": "Език на приложението",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Nebylo detekováno žádné zvukové vstupní zařízení. Připojte mikrofon nebo sluchátka a zkuste to znovu.",
"recordingFailed": "Nepodařilo se spustit nahrávání: {{error}}",
"modelLoadFailed": "Nepodařilo se načíst model: {{model}}",
"modelLoadFailedUnknown": "neznámý model"
"modelLoadFailedUnknown": "neznámý model",
"pasteFailedTitle": "Vložení textu se nezdařilo",
"pasteFailed": "Text nebylo možné vložit do aktivní aplikace."
},
"appLanguage": {
"title": "Jazyk aplikace",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Es wurde kein Audio-Eingabegerät erkannt. Bitte schließen Sie ein Mikrofon oder Headset an und versuchen Sie es erneut.",
"recordingFailed": "Aufnahme konnte nicht gestartet werden: {{error}}",
"modelLoadFailed": "Modell konnte nicht geladen werden: {{model}}",
"modelLoadFailedUnknown": "unbekanntes Modell"
"modelLoadFailedUnknown": "unbekanntes Modell",
"pasteFailedTitle": "Text konnte nicht eingefügt werden",
"pasteFailed": "Der Text konnte nicht in die aktive Anwendung eingefügt werden."
},
"appLanguage": {
"title": "Anwendungssprache",

View file

@ -597,7 +597,9 @@
"noInputDevice": "No audio input device was detected. Please connect a microphone or headset and try again.",
"recordingFailed": "Failed to start recording: {{error}}",
"modelLoadFailed": "Failed to load model: {{model}}",
"modelLoadFailedUnknown": "unknown model"
"modelLoadFailedUnknown": "unknown model",
"pasteFailedTitle": "Failed to Paste Text",
"pasteFailed": "Text could not be pasted into the active application."
},
"appLanguage": {
"title": "Application Language",

View file

@ -597,7 +597,9 @@
"noInputDevice": "No se detectó ningún dispositivo de entrada de audio. Conecta un micrófono o auriculares e inténtalo de nuevo.",
"recordingFailed": "Error al iniciar la grabación: {{error}}",
"modelLoadFailed": "Error al cargar el modelo: {{model}}",
"modelLoadFailedUnknown": "modelo desconocido"
"modelLoadFailedUnknown": "modelo desconocido",
"pasteFailedTitle": "Error al pegar el texto",
"pasteFailed": "No se pudo pegar el texto en la aplicación activa."
},
"appLanguage": {
"title": "Idioma de la aplicación",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Aucun périphérique d'entrée audio n'a été détecté. Veuillez connecter un microphone ou un casque et réessayer.",
"recordingFailed": "Échec du démarrage de l'enregistrement : {{error}}",
"modelLoadFailed": "Échec du chargement du modèle : {{model}}",
"modelLoadFailedUnknown": "modèle inconnu"
"modelLoadFailedUnknown": "modèle inconnu",
"pasteFailedTitle": "Échec du collage du texte",
"pasteFailed": "Le texte n'a pas pu être collé dans l'application active."
},
"appLanguage": {
"title": "Langue de l'application",

View file

@ -597,7 +597,9 @@
"noInputDevice": "לא זוהה התקן קלט שמע. חבר מיקרופון או אוזנייה ונסה שוב.",
"recordingFailed": "הפעלת ההקלטה נכשלה: {{error}}",
"modelLoadFailed": "טעינת המודל נכשלה: {{model}}",
"modelLoadFailedUnknown": "מודל לא ידוע"
"modelLoadFailedUnknown": "מודל לא ידוע",
"pasteFailedTitle": "ההדבקה נכשלה",
"pasteFailed": "לא ניתן היה להדביק את הטקסט ביישום הפעיל."
},
"appLanguage": {
"title": "שפת האפליקציה",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Non è stato rilevato alcun dispositivo di ingresso audio. Collega un microfono o delle cuffie e riprova.",
"recordingFailed": "Impossibile avviare la registrazione: {{error}}",
"modelLoadFailed": "Impossibile caricare il modello: {{model}}",
"modelLoadFailedUnknown": "modello sconosciuto"
"modelLoadFailedUnknown": "modello sconosciuto",
"pasteFailedTitle": "Impossibile incollare il testo",
"pasteFailed": "Non è stato possibile incollare il testo nell'applicazione attiva."
},
"appLanguage": {
"title": "Lingua Applicazione",

View file

@ -597,7 +597,9 @@
"noInputDevice": "オーディオ入力デバイスが検出されませんでした。マイクまたはヘッドセットを接続してから再試行してください。",
"recordingFailed": "録音の開始に失敗しました: {{error}}",
"modelLoadFailed": "モデルの読み込みに失敗しました: {{model}}",
"modelLoadFailedUnknown": "不明なモデル"
"modelLoadFailedUnknown": "不明なモデル",
"pasteFailedTitle": "テキストの貼り付けに失敗しました",
"pasteFailed": "アクティブなアプリケーションにテキストを貼り付けられませんでした。"
},
"appLanguage": {
"title": "アプリケーション言語",

View file

@ -597,7 +597,9 @@
"noInputDevice": "오디오 입력 장치가 감지되지 않았습니다. 마이크 또는 헤드셋을 연결한 후 다시 시도해 주세요.",
"recordingFailed": "녹음을 시작하지 못했습니다: {{error}}",
"modelLoadFailed": "모델을 불러오지 못했습니다: {{model}}",
"modelLoadFailedUnknown": "알 수 없는 모델"
"modelLoadFailedUnknown": "알 수 없는 모델",
"pasteFailedTitle": "텍스트 붙여넣기 실패",
"pasteFailed": "활성 애플리케이션에 텍스트를 붙여넣을 수 없습니다."
},
"appLanguage": {
"title": "애플리케이션 언어",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Nie wykryto żadnego urządzenia wejściowego audio. Podłącz mikrofon lub słuchawki i spróbuj ponownie.",
"recordingFailed": "Nie udało się rozpocząć nagrywania: {{error}}",
"modelLoadFailed": "Nie udało się załadować modelu: {{model}}",
"modelLoadFailedUnknown": "nieznany model"
"modelLoadFailedUnknown": "nieznany model",
"pasteFailedTitle": "Nie udało się wkleić tekstu",
"pasteFailed": "Nie można było wkleić tekstu do aktywnej aplikacji."
},
"appLanguage": {
"title": "Język aplikacji",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Nenhum dispositivo de entrada de áudio foi detectado. Conecte um microfone ou fone de ouvido e tente novamente.",
"recordingFailed": "Falha ao iniciar a gravação: {{error}}",
"modelLoadFailed": "Falha ao carregar o modelo: {{model}}",
"modelLoadFailedUnknown": "modelo desconhecido"
"modelLoadFailedUnknown": "modelo desconhecido",
"pasteFailedTitle": "Falha ao colar o texto",
"pasteFailed": "Não foi possível colar o texto no aplicativo ativo."
},
"appLanguage": {
"title": "Idioma da Aplicação",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Аудиоустройство ввода не обнаружено. Подключите микрофон или гарнитуру и попробуйте снова.",
"recordingFailed": "Не удалось начать запись: {{error}}",
"modelLoadFailed": "Не удалось загрузить модель: {{model}}",
"modelLoadFailedUnknown": "неизвестная модель"
"modelLoadFailedUnknown": "неизвестная модель",
"pasteFailedTitle": "Не удалось вставить текст",
"pasteFailed": "Не удалось вставить текст в активное приложение."
},
"appLanguage": {
"title": "Язык приложения",

View file

@ -597,7 +597,9 @@
"modelLoadFailed": "Misslyckades med att ladda modell: {{model}}",
"modelLoadFailedUnknown": "okänd modell",
"noInputDeviceTitle": "Ingen mikrofon hittades",
"noInputDevice": "Ingen ljudenhet hittades. Anslut en mikrofon eller ett headset och försök igen."
"noInputDevice": "Ingen ljudenhet hittades. Anslut en mikrofon eller ett headset och försök igen.",
"pasteFailedTitle": "Det gick inte att klistra in texten",
"pasteFailed": "Texten kunde inte klistras in i det aktiva programmet."
},
"appLanguage": {
"title": "Applikationsspråk",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Ses giriş aygıtı algılanamadı. Lütfen bir mikrofon veya kulaklık bağlayın ve tekrar deneyin.",
"recordingFailed": "Kayıt başlatılamadı: {{error}}",
"modelLoadFailed": "Model yüklenemedi: {{model}}",
"modelLoadFailedUnknown": "bilinmeyen model"
"modelLoadFailedUnknown": "bilinmeyen model",
"pasteFailedTitle": "Metin yapıştırılamadı",
"pasteFailed": "Metin etkin uygulamaya yapıştırılamadı."
},
"appLanguage": {
"title": "Uygulama Dili",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Не виявлено жодного пристрою введення звуку. Підключіть мікрофон або гарнітуру та спробуйте знову.",
"recordingFailed": "Не вдалося розпочати запис: {{error}}",
"modelLoadFailed": "Не вдалося завантажити модель: {{model}}",
"modelLoadFailedUnknown": "невідома модель"
"modelLoadFailedUnknown": "невідома модель",
"pasteFailedTitle": "Не вдалося вставити текст",
"pasteFailed": "Не вдалося вставити текст до активної програми."
},
"appLanguage": {
"title": "Мова інтерфейсу",

View file

@ -597,7 +597,9 @@
"noInputDevice": "Không phát hiện thiết bị đầu vào âm thanh nào. Vui lòng kết nối micrô hoặc tai nghe và thử lại.",
"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"
"modelLoadFailedUnknown": "mô hình không xác định",
"pasteFailedTitle": "Không thể dán văn bản",
"pasteFailed": "Không thể dán văn bản vào ứng dụng đang hoạt động."
},
"appLanguage": {
"title": "Ngôn ngữ ứng dụng",

View file

@ -597,7 +597,9 @@
"noInputDevice": "未偵測到音訊輸入裝置。請連接麥克風或耳機後重試。",
"recordingFailed": "錄音啟動失敗: {{error}}",
"modelLoadFailed": "載入模型失敗: {{model}}",
"modelLoadFailedUnknown": "未知模型"
"modelLoadFailedUnknown": "未知模型",
"pasteFailedTitle": "貼上文字失敗",
"pasteFailed": "無法將文字貼上至目前作用中的應用程式。"
},
"appLanguage": {
"title": "應用程式語言",

View file

@ -597,7 +597,9 @@
"noInputDevice": "未检测到音频输入设备。请连接麦克风或耳机后重试。",
"recordingFailed": "录音启动失败: {{error}}",
"modelLoadFailed": "加载模型失败: {{model}}",
"modelLoadFailedUnknown": "未知模型"
"modelLoadFailedUnknown": "未知模型",
"pasteFailedTitle": "粘贴文本失败",
"pasteFailed": "无法将文本粘贴到当前活动的应用程序中。"
},
"appLanguage": {
"title": "应用语言",