fix(dub): preserve complete speech and reject silent partial output

This commit is contained in:
Palash Debnath
2026-09-15 15:44:57 +05:30
parent c524ea3235
commit 0a6ea976cb
56 changed files with 599 additions and 146 deletions
@@ -1,3 +1,4 @@
import i18next from 'i18next';
import { cleanup, render, screen } from '@testing-library/react';
import { afterEach, expect, it, vi } from 'vitest';
import { publicFailureFromEvent } from '@/lib/api/failure';
@@ -39,3 +40,21 @@ it('rejects a non-web documentation URL and uses the shared safe fallback', () =
'https://github.com/debpalash/VoiceStudio/blob/main/docs/install/troubleshooting.md',
);
});
it.each([
['dub_speech_missing', 'dubIntegrity.missingSpeech'],
['dub_timing_overflow', 'dubIntegrity.timingOverflow'],
])('localizes %s while retaining diagnostics', (errorCode, key) => {
const translate = vi.spyOn(i18next, 't').mockReturnValue('Localized recovery instructions');
try {
const failure = publicFailureFromEvent(
{ error_code: errorCode, reason: 'Raw engine error', diagnostic: 'segment a' },
'Fallback',
);
expect(failure.reason).toBe('Localized recovery instructions');
expect(failure.diagnostic).toBe('segment a');
expect(translate).toHaveBeenCalledWith(key);
} finally {
translate.mockRestore();
}
});
@@ -2578,5 +2578,9 @@
"captured": "تم التقاط {{count}} من أسطر سجل المشكلات",
"contextNotice": "يتلقى الوكيل المحدد هذا البلاغ والشاشة الحالية وسجلات التطبيق والخلفية الحديثة وتشخيصات النظام.",
"complete": "مكتمل"
},
"dubIntegrity": {
"missingSpeech": "الكلام مفقود أو غير قابل للقراءة. أعد توليد المقطع المتأثر قبل التصدير.",
"timingOverflow": "يتجاوز الكلام الوقت المخصص له. اختصر الترجمة أو اختر التوقيت الصارم أو تمديد الفيديو."
}
}
@@ -2570,5 +2570,9 @@
"captured": "{{count}} problematische Protokollzeilen erfasst",
"contextNotice": "Der ausgewählte Agent erhält diesen Bericht, die aktuelle Ansicht, aktuelle App- und Backend-Protokolle sowie Systemdiagnosen.",
"complete": "Abgeschlossen"
},
"dubIntegrity": {
"missingSpeech": "Sprache fehlt oder ist nicht lesbar. Erzeuge das betroffene Segment vor dem Export erneut.",
"timingOverflow": "Die Sprache überschreitet ihr Zeitfenster. Kürze die Übersetzung oder wähle ein festes Zeitfenster oder die Videostreckung."
}
}
@@ -2570,5 +2570,9 @@
"captured": "Captured {{count}} problem log lines",
"contextNotice": "The selected agent receives this report, the current screen, recent app and backend logs, and system diagnostics.",
"complete": "Complete"
},
"dubIntegrity": {
"missingSpeech": "Speech is missing or unreadable. Regenerate the affected segment before exporting.",
"timingOverflow": "Speech exceeds its time slot. Shorten the translation or choose Strict Slot or Stretch Video."
}
}
@@ -2572,5 +2572,9 @@
"captured": "Se capturaron {{count}} líneas de registro con problemas",
"contextNotice": "El agente seleccionado recibe este informe, la pantalla actual, los registros recientes de la aplicación y del backend, y los diagnósticos del sistema.",
"complete": "Completado"
},
"dubIntegrity": {
"missingSpeech": "Falta audio de voz o no se puede leer. Regenera el segmento afectado antes de exportar.",
"timingOverflow": "La voz supera su intervalo de tiempo. Acorta la traducción o elige un intervalo estricto o alargar el vídeo."
}
}
@@ -2572,5 +2572,9 @@
"captured": "{{count}} lignes de journal problématiques capturées",
"contextNotice": "Lagent sélectionné reçoit ce rapport, l’écran actuel, les journaux récents de lapplication et du backend, ainsi que les diagnostics système.",
"complete": "Complet"
},
"dubIntegrity": {
"missingSpeech": "La parole est absente ou illisible. Régénérez le segment concerné avant lexportation.",
"timingOverflow": "La parole dépasse son créneau. Raccourcissez la traduction ou choisissez un créneau strict ou l’étirement vidéo."
}
}
@@ -2570,5 +2570,9 @@
"captured": "समस्या वाली {{count}} लॉग पंक्तियाँ कैप्चर की गईं",
"contextNotice": "चुने गए एजेंट को यह रिपोर्ट, वर्तमान स्क्रीन, हाल के ऐप और बैकएंड लॉग तथा सिस्टम निदान मिलते हैं।",
"complete": "पूरा"
},
"dubIntegrity": {
"missingSpeech": "बोली गायब है या पढ़ी नहीं जा सकती। निर्यात से पहले प्रभावित खंड दोबारा बनाएँ।",
"timingOverflow": "बोली अपने समय खंड से लंबी है। अनुवाद छोटा करें या सख्त समय खंड अथवा वीडियो खिंचाव चुनें।"
}
}
@@ -2570,5 +2570,9 @@
"captured": "{{count}} baris log bermasalah ditangkap",
"contextNotice": "Agen yang dipilih menerima laporan ini, layar saat ini, log aplikasi dan backend terbaru, serta diagnostik sistem.",
"complete": "Selesai"
},
"dubIntegrity": {
"missingSpeech": "Audio ucapan hilang atau tidak dapat dibaca. Buat ulang segmen terkait sebelum mengekspor.",
"timingOverflow": "Ucapan melebihi jatah waktunya. Persingkat terjemahan atau pilih slot ketat atau rentangkan video."
}
}
@@ -2572,5 +2572,9 @@
"captured": "Acquisite {{count}} righe di log problematiche",
"contextNotice": "Lagente selezionato riceve questa segnalazione, la schermata corrente, i log recenti dellapp e del backend e la diagnostica di sistema.",
"complete": "Completato"
},
"dubIntegrity": {
"missingSpeech": "Il parlato manca o non è leggibile. Rigenera il segmento interessato prima di esportare.",
"timingOverflow": "Il parlato supera il tempo disponibile. Accorcia la traduzione oppure scegli uno slot rigoroso o estendi il video."
}
}
@@ -2570,5 +2570,9 @@
"captured": "問題のあるログを{{count}}行取得しました",
"contextNotice": "選択したエージェントには、この報告、現在の画面、最近のアプリとバックエンドのログ、システム診断が渡されます。",
"complete": "完了"
},
"dubIntegrity": {
"missingSpeech": "音声がないか読み取れません。書き出す前に該当セグメントを再生成してください。",
"timingOverflow": "音声が割り当て時間を超えています。翻訳を短くするか、厳密な時間枠または動画の引き伸ばしを選んでください。"
}
}
@@ -2570,5 +2570,9 @@
"captured": "문제 로그 {{count}}줄을 수집했습니다",
"contextNotice": "선택한 에이전트는 이 보고서, 현재 화면, 최근 앱 및 백엔드 로그와 시스템 진단 정보를 받습니다.",
"complete": "완료"
},
"dubIntegrity": {
"missingSpeech": "음성이 없거나 읽을 수 없습니다. 내보내기 전에 해당 구간을 다시 생성하세요.",
"timingOverflow": "음성이 할당된 시간을 초과합니다. 번역을 줄이거나 엄격한 시간 구간 또는 비디오 늘이기를 선택하세요."
}
}
@@ -2570,5 +2570,9 @@
"captured": "{{count}} problematische logregels vastgelegd",
"contextNotice": "De geselecteerde agent ontvangt dit rapport, het huidige scherm, recente app- en backendlogboeken en systeemdiagnostiek.",
"complete": "Voltooid"
},
"dubIntegrity": {
"missingSpeech": "Spraak ontbreekt of is onleesbaar. Genereer het betreffende segment opnieuw voordat je exporteert.",
"timingOverflow": "De spraak overschrijdt het tijdvak. Verkort de vertaling of kies een strikt tijdvak of het uitrekken van de video."
}
}
@@ -2574,5 +2574,9 @@
"captured": "Przechwycono {{count}} problematycznych wierszy dziennika",
"contextNotice": "Wybrany agent otrzyma to zgłoszenie, bieżący ekran, ostatnie dzienniki aplikacji i backendu oraz diagnostykę systemu.",
"complete": "Zakończono"
},
"dubIntegrity": {
"missingSpeech": "Brak mowy lub nie można jej odczytać. Przed eksportem wygeneruj ponownie dany segment.",
"timingOverflow": "Mowa przekracza przydzielony czas. Skróć tłumaczenie albo wybierz ścisły przedział czasu lub wydłużenie filmu."
}
}
@@ -2572,5 +2572,9 @@
"captured": "{{count}} linhas de log com problemas capturadas",
"contextNotice": "O agente selecionado recebe este relatório, a tela atual, os logs recentes do aplicativo e do backend e os diagnósticos do sistema.",
"complete": "Completo"
},
"dubIntegrity": {
"missingSpeech": "A fala está ausente ou ilegível. Gere novamente o segmento afetado antes de exportar.",
"timingOverflow": "A fala excede o intervalo de tempo. Encurte a tradução ou escolha um intervalo estrito ou estique o vídeo."
}
}
@@ -2574,5 +2574,9 @@
"captured": "Собрано строк журнала с проблемами: {{count}}",
"contextNotice": "Выбранный агент получит этот отчёт, текущий экран, последние журналы приложения и бэкенда, а также диагностику системы.",
"complete": "Завершено"
},
"dubIntegrity": {
"missingSpeech": "Речь отсутствует или не читается. Перед экспортом заново сгенерируйте этот сегмент.",
"timingOverflow": "Речь выходит за отведённое время. Сократите перевод или выберите строгий интервал либо растяжение видео."
}
}
@@ -2570,5 +2570,9 @@
"captured": "{{count}} problemrader i loggen samlades in",
"contextNotice": "Den valda agenten får den här rapporten, den aktuella vyn, aktuella app- och backendloggar samt systemdiagnostik.",
"complete": "Klart"
},
"dubIntegrity": {
"missingSpeech": "Tal saknas eller kan inte läsas. Generera det berörda segmentet på nytt före export.",
"timingOverflow": "Talet överskrider sin tidslucka. Korta översättningen eller välj strikt tidslucka eller sträck ut videon."
}
}
@@ -2570,5 +2570,9 @@
"captured": "บันทึกบรรทัดปัญหาจากล็อกแล้ว {{count}} บรรทัด",
"contextNotice": "เอเจนต์ที่เลือกจะได้รับรายงานนี้ หน้าจอปัจจุบัน บันทึกล่าสุดของแอปและแบ็กเอนด์ และข้อมูลวินิจฉัยระบบ",
"complete": "เสร็จสิ้น"
},
"dubIntegrity": {
"missingSpeech": "เสียงพูดหายไปหรืออ่านไม่ได้ สร้างช่วงที่มีปัญหาใหม่ก่อนส่งออก",
"timingOverflow": "เสียงพูดยาวเกินช่วงเวลาที่กำหนด ย่อคำแปลหรือเลือกช่วงเวลาแบบเคร่งครัดหรือยืดวิดีโอ"
}
}
@@ -2570,5 +2570,9 @@
"captured": "{{count}} sorunlu günlük satırı yakalandı",
"contextNotice": "Seçilen aracı bu raporu, mevcut ekranı, son uygulama ve arka uç günlüklerini ve sistem tanılamasını alır.",
"complete": "Tamamlandı"
},
"dubIntegrity": {
"missingSpeech": "Konuşma eksik veya okunamıyor. Dışa aktarmadan önce ilgili bölümü yeniden oluşturun.",
"timingOverflow": "Konuşma ayrılan süreyi aşıyor. Çeviriyi kısaltın veya kesin zaman aralığını ya da videoyu uzatmayı seçin."
}
}
@@ -2574,5 +2574,9 @@
"captured": "Зібрано рядків журналу з проблемами: {{count}}",
"contextNotice": "Вибраний агент отримає цей звіт, поточний екран, останні журнали застосунку й бекенду та діагностику системи.",
"complete": "Завершено"
},
"dubIntegrity": {
"missingSpeech": "Мовлення відсутнє або не читається. Перед експортом повторно згенеруйте цей сегмент.",
"timingOverflow": "Мовлення перевищує відведений час. Скоротіть переклад або виберіть строгий інтервал чи розтягнення відео."
}
}
@@ -2570,5 +2570,9 @@
"captured": "Đã thu thập {{count}} dòng nhật ký có vấn đề",
"contextNotice": "Tác nhân đã chọn nhận báo cáo này, màn hình hiện tại, nhật ký ứng dụng và backend gần đây cùng thông tin chẩn đoán hệ thống.",
"complete": "Hoàn thành"
},
"dubIntegrity": {
"missingSpeech": "Lời nói bị thiếu hoặc không đọc được. Hãy tạo lại đoạn bị ảnh hưởng trước khi xuất.",
"timingOverflow": "Lời nói vượt quá thời gian được phân bổ. Hãy rút ngắn bản dịch hoặc chọn khung thời gian nghiêm ngặt hay kéo dài video."
}
}
@@ -2574,5 +2574,9 @@
"captured": "已捕获 {{count}} 行问题日志",
"contextNotice": "所选代理将收到此报告、当前界面、最近的应用与后端日志以及系统诊断信息。",
"complete": "已完成"
},
"dubIntegrity": {
"missingSpeech": "语音缺失或无法读取。请在导出前重新生成受影响的片段。",
"timingOverflow": "语音超出分配的时长。请缩短译文,或选择严格时间段或拉伸视频。"
}
}
@@ -2570,5 +2570,9 @@
"captured": "已擷取 {{count}} 行問題日誌",
"contextNotice": "所選代理將收到此報告、目前畫面、最近的應用程式與後端記錄以及系統診斷資訊。",
"complete": "已完成"
},
"dubIntegrity": {
"missingSpeech": "語音缺失或無法讀取。請在匯出前重新產生受影響的片段。",
"timingOverflow": "語音超出分配的時長。請縮短譯文,或選擇嚴格時間區段或延展影片。"
}
}
@@ -1,3 +1,4 @@
import i18next from 'i18next';
export interface PublicFailure {
reason: string;
errorClass?: string;
@@ -16,6 +17,8 @@ export function publicFailureFromEvent(
): PublicFailure {
return {
reason:
(event.error_code === 'dub_speech_missing' ? i18next.t('dubIntegrity.missingSpeech') :
event.error_code === 'dub_timing_overflow' ? i18next.t('dubIntegrity.timingOverflow') : undefined) ||
text(event.reason) ||
text(event.detail) ||
text(event.error) ||