diff --git a/backend/api/routers/dub_translate.py b/backend/api/routers/dub_translate.py index 6695a36a..005c747d 100644 --- a/backend/api/routers/dub_translate.py +++ b/backend/api/routers/dub_translate.py @@ -810,7 +810,7 @@ async def dub_translate(req: TranslateRequest): + " Switch the Engine dropdown to NLLB (local) or an online " "provider, or reinstall the backend, then retry." ) - return JSONResponse(status_code=400, content={"error": friendly}) + return JSONResponse(status_code=400, content={"error": friendly, "detail": {"code": "argos_runtime_unavailable", "message": friendly}}) target_codes = list(dict.fromkeys( seg.target_lang if seg.target_lang else req.target_lang diff --git a/docs/dubbing/translation-engines.md b/docs/dubbing/translation-engines.md index f470e0cf..40825f1c 100644 --- a/docs/dubbing/translation-engines.md +++ b/docs/dubbing/translation-engines.md @@ -242,3 +242,5 @@ panels instead so neither editor becomes unusably small. backend on Python 3.12+, or run `patchelf --clear-execstack` on the library once — and NLLB stays available in the meantime ([#692](https://github.com/debpalash/VoiceStudio/issues/692)). + +If the Argos native runtime cannot load, both desktop and browser clients show localized recovery guidance: reinstall the backend or select NLLB. The API returns the stable `argos_runtime_unavailable` error code without exposing native library paths. diff --git a/electron/src/renderer/src/i18n/locales/ar.json b/electron/src/renderer/src/i18n/locales/ar.json index 2136ae49..03515c67 100644 --- a/electron/src/renderer/src/i18n/locales/ar.json +++ b/electron/src/renderer/src/i18n/locales/ar.json @@ -2492,7 +2492,8 @@ "none_ready_title": "تحويل النص إلى كلام غير جاهز", "none_ready_body": "يمكن لـ VoiceStudio إعداد أفضل خيار متوافق مع هذا الجهاز تلقائيًا.", "active": "المحرك: {{name}}", - "none": "لا يوجد محرك نشط" + "none": "لا يوجد محرك نشط", + "argosRuntimeUnavailable": "بيئة تشغيل Argos غير متاحة. أعد تثبيت الخادم الخلفي أو اختر NLLB." }, "performanceProfile": { "requiresEngine": "ثبّت {{engine}} وحدده لضبط السرعة والجودة.", diff --git a/electron/src/renderer/src/i18n/locales/de.json b/electron/src/renderer/src/i18n/locales/de.json index d84a525d..415850a4 100644 --- a/electron/src/renderer/src/i18n/locales/de.json +++ b/electron/src/renderer/src/i18n/locales/de.json @@ -2484,7 +2484,8 @@ "none_ready_title": "Sprachausgabe ist nicht bereit", "none_ready_body": "VoiceStudio kann automatisch die beste kompatible Option für dieses Gerät einrichten.", "active": "Engine: {{name}}", - "none": "Keine Engine aktiv" + "none": "Keine Engine aktiv", + "argosRuntimeUnavailable": "Die Argos-Laufzeit ist nicht verfügbar. Installiere das Backend neu oder wähle NLLB." }, "performanceProfile": { "requiresEngine": "Installiere und wähle {{engine}}, um Geschwindigkeit und Qualität anzupassen.", diff --git a/electron/src/renderer/src/i18n/locales/en.json b/electron/src/renderer/src/i18n/locales/en.json index 5076254f..b09443be 100644 --- a/electron/src/renderer/src/i18n/locales/en.json +++ b/electron/src/renderer/src/i18n/locales/en.json @@ -114,7 +114,8 @@ "recheck": "Re-check", "rechecking": "Re-checking…", "failed": "failed", - "latencyMs": "{{ms}} ms" + "latencyMs": "{{ms}} ms", + "argosRuntimeUnavailable": "Argos runtime unavailable. Reinstall the backend or select NLLB." }, "clone": { "title": "Voice cloning", diff --git a/electron/src/renderer/src/i18n/locales/es.json b/electron/src/renderer/src/i18n/locales/es.json index ccb08ae3..9c7e038e 100644 --- a/electron/src/renderer/src/i18n/locales/es.json +++ b/electron/src/renderer/src/i18n/locales/es.json @@ -2486,7 +2486,8 @@ "none_ready_title": "La síntesis de voz no está lista", "none_ready_body": "VoiceStudio puede configurar automáticamente la mejor opción compatible con este dispositivo.", "active": "Motor: {{name}}", - "none": "Ningún motor activo" + "none": "Ningún motor activo", + "argosRuntimeUnavailable": "El entorno de ejecución de Argos no está disponible. Reinstala el backend o selecciona NLLB." }, "performanceProfile": { "requiresEngine": "Instala y selecciona {{engine}} para ajustar la velocidad y la calidad.", diff --git a/electron/src/renderer/src/i18n/locales/fr.json b/electron/src/renderer/src/i18n/locales/fr.json index 8d8d7200..f94ebb60 100644 --- a/electron/src/renderer/src/i18n/locales/fr.json +++ b/electron/src/renderer/src/i18n/locales/fr.json @@ -2486,7 +2486,8 @@ "none_ready_title": "La synthèse vocale n’est pas prête", "none_ready_body": "VoiceStudio peut configurer automatiquement la meilleure option compatible avec cet appareil.", "active": "Moteur : {{name}}", - "none": "Aucun moteur actif" + "none": "Aucun moteur actif", + "argosRuntimeUnavailable": "Le moteur Argos est indisponible. Réinstallez le backend ou sélectionnez NLLB." }, "performanceProfile": { "requiresEngine": "Installez et sélectionnez {{engine}} pour régler la vitesse et la qualité.", diff --git a/electron/src/renderer/src/i18n/locales/hi.json b/electron/src/renderer/src/i18n/locales/hi.json index 9be8a615..02e177f2 100644 --- a/electron/src/renderer/src/i18n/locales/hi.json +++ b/electron/src/renderer/src/i18n/locales/hi.json @@ -2484,7 +2484,8 @@ "none_ready_title": "वाक् संश्लेषण तैयार नहीं है", "none_ready_body": "VoiceStudio इस डिवाइस के लिए सबसे अच्छा संगत विकल्प अपने आप सेट कर सकता है।", "active": "इंजन: {{name}}", - "none": "कोई इंजन सक्रिय नहीं है" + "none": "कोई इंजन सक्रिय नहीं है", + "argosRuntimeUnavailable": "Argos रनटाइम उपलब्ध नहीं है। बैकएंड फिर से इंस्टॉल करें या NLLB चुनें।" }, "performanceProfile": { "requiresEngine": "गति और गुणवत्ता समायोजित करने के लिए {{engine}} इंस्टॉल करके चुनें।", diff --git a/electron/src/renderer/src/i18n/locales/id.json b/electron/src/renderer/src/i18n/locales/id.json index d0ffc543..f223c5ef 100644 --- a/electron/src/renderer/src/i18n/locales/id.json +++ b/electron/src/renderer/src/i18n/locales/id.json @@ -2484,7 +2484,8 @@ "none_ready_title": "Sintesis ucapan belum siap", "none_ready_body": "VoiceStudio dapat menyiapkan opsi kompatibel terbaik untuk perangkat ini secara otomatis.", "active": "Mesin: {{name}}", - "none": "Tidak ada mesin aktif" + "none": "Tidak ada mesin aktif", + "argosRuntimeUnavailable": "Runtime Argos tidak tersedia. Instal ulang backend atau pilih NLLB." }, "performanceProfile": { "requiresEngine": "Instal dan pilih {{engine}} untuk menyesuaikan kecepatan dan kualitas.", diff --git a/electron/src/renderer/src/i18n/locales/it.json b/electron/src/renderer/src/i18n/locales/it.json index a069e980..b733d2ee 100644 --- a/electron/src/renderer/src/i18n/locales/it.json +++ b/electron/src/renderer/src/i18n/locales/it.json @@ -2486,7 +2486,8 @@ "none_ready_title": "La sintesi vocale non è pronta", "none_ready_body": "VoiceStudio può configurare automaticamente l’opzione compatibile migliore per questo dispositivo.", "active": "Motore: {{name}}", - "none": "Nessun motore attivo" + "none": "Nessun motore attivo", + "argosRuntimeUnavailable": "Il runtime di Argos non è disponibile. Reinstalla il backend o seleziona NLLB." }, "performanceProfile": { "requiresEngine": "Installa e seleziona {{engine}} per regolare velocità e qualità.", diff --git a/electron/src/renderer/src/i18n/locales/ja.json b/electron/src/renderer/src/i18n/locales/ja.json index a46aacc6..5b7e711c 100644 --- a/electron/src/renderer/src/i18n/locales/ja.json +++ b/electron/src/renderer/src/i18n/locales/ja.json @@ -2484,7 +2484,8 @@ "none_ready_title": "音声合成の準備ができていません", "none_ready_body": "VoiceStudio がこのデバイスに最適な互換オプションを自動で設定できます。", "active": "エンジン:{{name}}", - "none": "有効なエンジンがありません" + "none": "有効なエンジンがありません", + "argosRuntimeUnavailable": "Argos ランタイムを利用できません。バックエンドを再インストールするか、NLLB を選択してください。" }, "performanceProfile": { "requiresEngine": "速度と品質を調整するには、{{engine}} をインストールして選択してください。", diff --git a/electron/src/renderer/src/i18n/locales/ko.json b/electron/src/renderer/src/i18n/locales/ko.json index 5a51576e..9338d0a0 100644 --- a/electron/src/renderer/src/i18n/locales/ko.json +++ b/electron/src/renderer/src/i18n/locales/ko.json @@ -2484,7 +2484,8 @@ "none_ready_title": "음성 합성이 준비되지 않았습니다", "none_ready_body": "VoiceStudio가 이 기기에 가장 적합한 호환 옵션을 자동으로 설정할 수 있습니다.", "active": "엔진: {{name}}", - "none": "활성 엔진 없음" + "none": "활성 엔진 없음", + "argosRuntimeUnavailable": "Argos 런타임을 사용할 수 없습니다. 백엔드를 다시 설치하거나 NLLB를 선택하세요." }, "performanceProfile": { "requiresEngine": "속도와 품질을 조절하려면 {{engine}}을 설치하고 선택하세요.", diff --git a/electron/src/renderer/src/i18n/locales/nl.json b/electron/src/renderer/src/i18n/locales/nl.json index 4b9339fb..b4b54ebd 100644 --- a/electron/src/renderer/src/i18n/locales/nl.json +++ b/electron/src/renderer/src/i18n/locales/nl.json @@ -2484,7 +2484,8 @@ "none_ready_title": "Spraaksynthese is niet gereed", "none_ready_body": "VoiceStudio kan automatisch de beste compatibele optie voor dit apparaat instellen.", "active": "Engine: {{name}}", - "none": "Geen engine actief" + "none": "Geen engine actief", + "argosRuntimeUnavailable": "De Argos-runtime is niet beschikbaar. Installeer de backend opnieuw of kies NLLB." }, "performanceProfile": { "requiresEngine": "Installeer en selecteer {{engine}} om snelheid en kwaliteit af te stellen.", diff --git a/electron/src/renderer/src/i18n/locales/pl.json b/electron/src/renderer/src/i18n/locales/pl.json index 44271405..2ade9e30 100644 --- a/electron/src/renderer/src/i18n/locales/pl.json +++ b/electron/src/renderer/src/i18n/locales/pl.json @@ -2488,7 +2488,8 @@ "none_ready_title": "Synteza mowy nie jest gotowa", "none_ready_body": "VoiceStudio może automatycznie skonfigurować najlepszą zgodną opcję dla tego urządzenia.", "active": "Silnik: {{name}}", - "none": "Brak aktywnego silnika" + "none": "Brak aktywnego silnika", + "argosRuntimeUnavailable": "Środowisko Argos jest niedostępne. Zainstaluj ponownie backend lub wybierz NLLB." }, "performanceProfile": { "requiresEngine": "Zainstaluj i wybierz {{engine}}, aby dostosować szybkość i jakość.", diff --git a/electron/src/renderer/src/i18n/locales/pt.json b/electron/src/renderer/src/i18n/locales/pt.json index c343207d..8857613c 100644 --- a/electron/src/renderer/src/i18n/locales/pt.json +++ b/electron/src/renderer/src/i18n/locales/pt.json @@ -2486,7 +2486,8 @@ "none_ready_title": "A síntese de voz não está pronta", "none_ready_body": "O VoiceStudio pode configurar automaticamente a melhor opção compatível com este dispositivo.", "active": "Motor: {{name}}", - "none": "Nenhum motor ativo" + "none": "Nenhum motor ativo", + "argosRuntimeUnavailable": "O ambiente de execução do Argos está indisponível. Reinstale o backend ou selecione NLLB." }, "performanceProfile": { "requiresEngine": "Instale e selecione {{engine}} para ajustar a velocidade e a qualidade.", diff --git a/electron/src/renderer/src/i18n/locales/ru.json b/electron/src/renderer/src/i18n/locales/ru.json index 7759ed19..af8ed288 100644 --- a/electron/src/renderer/src/i18n/locales/ru.json +++ b/electron/src/renderer/src/i18n/locales/ru.json @@ -2488,7 +2488,8 @@ "none_ready_title": "Синтез речи не готов", "none_ready_body": "VoiceStudio может автоматически настроить лучший совместимый вариант для этого устройства.", "active": "Движок: {{name}}", - "none": "Нет активного движка" + "none": "Нет активного движка", + "argosRuntimeUnavailable": "Среда выполнения Argos недоступна. Переустановите серверную часть или выберите NLLB." }, "performanceProfile": { "requiresEngine": "Установите и выберите {{engine}}, чтобы настроить скорость и качество.", diff --git a/electron/src/renderer/src/i18n/locales/sv.json b/electron/src/renderer/src/i18n/locales/sv.json index 2c602e81..e9458903 100644 --- a/electron/src/renderer/src/i18n/locales/sv.json +++ b/electron/src/renderer/src/i18n/locales/sv.json @@ -2484,7 +2484,8 @@ "none_ready_title": "Talsyntesen är inte redo", "none_ready_body": "VoiceStudio kan automatiskt konfigurera det bästa kompatibla alternativet för den här enheten.", "active": "Motor: {{name}}", - "none": "Ingen motor aktiv" + "none": "Ingen motor aktiv", + "argosRuntimeUnavailable": "Argos körmiljö är inte tillgänglig. Installera om backend eller välj NLLB." }, "performanceProfile": { "requiresEngine": "Installera och välj {{engine}} för att justera hastighet och kvalitet.", diff --git a/electron/src/renderer/src/i18n/locales/th.json b/electron/src/renderer/src/i18n/locales/th.json index 7926a4d5..32cb447f 100644 --- a/electron/src/renderer/src/i18n/locales/th.json +++ b/electron/src/renderer/src/i18n/locales/th.json @@ -2484,7 +2484,8 @@ "none_ready_title": "การสังเคราะห์เสียงยังไม่พร้อม", "none_ready_body": "VoiceStudio สามารถตั้งค่าตัวเลือกที่เข้ากันได้ดีที่สุดสำหรับอุปกรณ์นี้โดยอัตโนมัติ", "active": "เอนจิน: {{name}}", - "none": "ไม่มีเอนจินที่ใช้งานอยู่" + "none": "ไม่มีเอนจินที่ใช้งานอยู่", + "argosRuntimeUnavailable": "ไม่สามารถใช้งานรันไทม์ Argos ได้ โปรดติดตั้งแบ็กเอนด์ใหม่หรือเลือก NLLB" }, "performanceProfile": { "requiresEngine": "ติดตั้งและเลือก {{engine}} เพื่อปรับความเร็วและคุณภาพ", diff --git a/electron/src/renderer/src/i18n/locales/tr.json b/electron/src/renderer/src/i18n/locales/tr.json index 0bf89eff..3d833c58 100644 --- a/electron/src/renderer/src/i18n/locales/tr.json +++ b/electron/src/renderer/src/i18n/locales/tr.json @@ -2484,7 +2484,8 @@ "none_ready_title": "Konuşma sentezi hazır değil", "none_ready_body": "VoiceStudio bu cihaz için en iyi uyumlu seçeneği otomatik olarak ayarlayabilir.", "active": "Motor: {{name}}", - "none": "Etkin motor yok" + "none": "Etkin motor yok", + "argosRuntimeUnavailable": "Argos çalışma ortamı kullanılamıyor. Arka ucu yeniden yükleyin veya NLLB seçin." }, "performanceProfile": { "requiresEngine": "Hız ve kaliteyi ayarlamak için {{engine}} yükleyip seçin.", diff --git a/electron/src/renderer/src/i18n/locales/uk.json b/electron/src/renderer/src/i18n/locales/uk.json index 1fd23547..00d66cf8 100644 --- a/electron/src/renderer/src/i18n/locales/uk.json +++ b/electron/src/renderer/src/i18n/locales/uk.json @@ -2488,7 +2488,8 @@ "none_ready_title": "Синтез мовлення не готовий", "none_ready_body": "VoiceStudio може автоматично налаштувати найкращий сумісний варіант для цього пристрою.", "active": "Рушій: {{name}}", - "none": "Немає активного рушія" + "none": "Немає активного рушія", + "argosRuntimeUnavailable": "Середовище виконання Argos недоступне. Перевстановіть серверну частину або виберіть NLLB." }, "performanceProfile": { "requiresEngine": "Установіть і виберіть {{engine}}, щоб налаштувати швидкість і якість.", diff --git a/electron/src/renderer/src/i18n/locales/vi.json b/electron/src/renderer/src/i18n/locales/vi.json index 07dfd86b..2347553b 100644 --- a/electron/src/renderer/src/i18n/locales/vi.json +++ b/electron/src/renderer/src/i18n/locales/vi.json @@ -2484,7 +2484,8 @@ "none_ready_title": "Tổng hợp giọng nói chưa sẵn sàng", "none_ready_body": "VoiceStudio có thể tự động thiết lập tùy chọn tương thích tốt nhất cho thiết bị này.", "active": "Bộ máy: {{name}}", - "none": "Không có bộ máy đang hoạt động" + "none": "Không có bộ máy đang hoạt động", + "argosRuntimeUnavailable": "Không thể sử dụng môi trường chạy Argos. Hãy cài lại backend hoặc chọn NLLB." }, "performanceProfile": { "requiresEngine": "Cài đặt và chọn {{engine}} để điều chỉnh tốc độ và chất lượng.", diff --git a/electron/src/renderer/src/i18n/locales/zh-CN.json b/electron/src/renderer/src/i18n/locales/zh-CN.json index 153b7e68..618d06f2 100644 --- a/electron/src/renderer/src/i18n/locales/zh-CN.json +++ b/electron/src/renderer/src/i18n/locales/zh-CN.json @@ -2488,7 +2488,8 @@ "none_ready_title": "语音合成尚未就绪", "none_ready_body": "VoiceStudio 可以自动为此设备设置最佳兼容选项。", "active": "引擎:{{name}}", - "none": "没有启用的引擎" + "none": "没有启用的引擎", + "argosRuntimeUnavailable": "Argos 运行时不可用。请重新安装后端或选择 NLLB。" }, "performanceProfile": { "requiresEngine": "安装并选择 {{engine}} 以调整速度和质量。", diff --git a/electron/src/renderer/src/i18n/locales/zh-TW.json b/electron/src/renderer/src/i18n/locales/zh-TW.json index eda10309..44e7fd4e 100644 --- a/electron/src/renderer/src/i18n/locales/zh-TW.json +++ b/electron/src/renderer/src/i18n/locales/zh-TW.json @@ -2484,7 +2484,8 @@ "none_ready_title": "語音合成尚未就緒", "none_ready_body": "VoiceStudio 可以自動為此裝置設定最佳相容選項。", "active": "引擎:{{name}}", - "none": "沒有啟用的引擎" + "none": "沒有啟用的引擎", + "argosRuntimeUnavailable": "Argos 執行環境無法使用。請重新安裝後端或選擇 NLLB。" }, "performanceProfile": { "requiresEngine": "安裝並選取 {{engine}} 以調整速度與品質。", diff --git a/electron/src/renderer/src/lib/api/client.test.ts b/electron/src/renderer/src/lib/api/client.test.ts index 4eb6e922..a9957932 100644 --- a/electron/src/renderer/src/lib/api/client.test.ts +++ b/electron/src/renderer/src/lib/api/client.test.ts @@ -1,3 +1,4 @@ +import i18next from 'i18next'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { _resetBackendContactForTests, @@ -23,6 +24,15 @@ afterEach(() => { }); describe('errorFromResponse', () => { + it('localizes Argos runtime errors and retains diagnostics', async () => { + const translate = vi.spyOn(i18next, 't').mockReturnValue('Localized recovery guidance'); + const detail = { code: 'argos_runtime_unavailable', message: 'Raw native diagnostic' }; + const err = await errorFromResponse(new Response(JSON.stringify({ detail }), { status: 400 })); + expect(err.detail).toBe('Localized recovery guidance'); + expect(translate).toHaveBeenCalledWith('engines.argosRuntimeUnavailable'); + translate.mockRestore(); + expect(err.payload?.detail).toEqual(detail); + }); it('localizes background preservation errors and retains diagnostics', async () => { const detail = { code: 'dub_background_unavailable', message: 'Raw diagnostic' }; const err = await errorFromResponse(new Response(JSON.stringify({ detail }), { status: 409 })); diff --git a/electron/src/renderer/src/lib/api/client.ts b/electron/src/renderer/src/lib/api/client.ts index 9a65fa1d..092e71cb 100644 --- a/electron/src/renderer/src/lib/api/client.ts +++ b/electron/src/renderer/src/lib/api/client.ts @@ -44,7 +44,19 @@ export function describeError(err: unknown): string { } function detailToString(detail: unknown): string { - if (detail && typeof detail === 'object' && 'code' in detail && detail.code === 'dub_background_unavailable') + if ( + detail && + typeof detail === 'object' && + 'code' in detail && + detail.code === 'argos_runtime_unavailable' + ) + return tr('engines.argosRuntimeUnavailable'); + if ( + detail && + typeof detail === 'object' && + 'code' in detail && + detail.code === 'dub_background_unavailable' + ) return tr('dubIntegrity.backgroundUnavailable'); if (typeof detail === 'string') return detail; if (detail == null) return ''; diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 0fab928e..b3c61a66 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,3 +1,4 @@ +import i18n from 'i18next'; import { abortableDelay } from '../utils/abortableDelay.ts'; // Backend base URL. // • VITE_API_URL → explicit override (any deploy). @@ -592,9 +593,16 @@ export async function apiFetch(path: string, opts: ApiFetchOptions = {}): Promis // human-readable `message` — use it for the Error message instead of // letting the object stringify to "[object Object]". const msg = - typeof detail === 'string' - ? detail - : ((detail as { message?: string })?.message ?? JSON.stringify(detail)); + detail && + typeof detail === 'object' && + 'code' in detail && + detail.code === 'argos_runtime_unavailable' + ? i18n.t('engines.argosRuntimeUnavailable', { + defaultValue: 'Argos runtime unavailable. Reinstall the backend or select NLLB.', + }) + : typeof detail === 'string' + ? detail + : ((detail as { message?: string })?.message ?? JSON.stringify(detail)); // The backend names the exception type in `error_class` on its 500s. // Lifting it here is what lets the bug report say which failure it was. const errorClass = diff --git a/frontend/src/i18n/locales/ar.json b/frontend/src/i18n/locales/ar.json index 7682add0..03c89d1b 100644 --- a/frontend/src/i18n/locales/ar.json +++ b/frontend/src/i18n/locales/ar.json @@ -864,7 +864,8 @@ "familyDesc_asr": "يحوّل الصوت إلى نص للنسخ والدبلجة والتسميات التوضيحية والإملاء.", "familyDesc_llm": "مساعد نصي اختياري للترجمة وإعادة الصياغة. يؤدي إيقافه إلى تخطي هذه الخطوات.", "mpsLongText": "يعمل {{engine}} على شريحة Apple Silicon (MPS) هنا، وهذا النص طويل — قد يتجاوز التوليد الحد الزمني. النص الأقصر، أو تقسيمه إلى عدة مقاطع، سيكون أسرع بكثير.", - "inMemory": "في الذاكرة" + "inMemory": "في الذاكرة", + "argosRuntimeUnavailable": "بيئة تشغيل Argos غير متاحة. أعد تثبيت الخادم الخلفي أو اختر NLLB." }, "capture": { "desc": "تعمل مفاتيح التشغيل السريع العامة فقط في تطبيق سطح المكتب. تستخدم واجهة مستخدم الويب اختصارًا <1>Ctrl+Shift+Space داخل الصفحة أثناء التركيز على النافذة.", diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index b6a259e8..ce0ff7c3 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Transkribiert Audio für Synchronisation, Untertitel und Diktat.", "familyDesc_llm": "Optionaler Texthelfer für Übersetzungen und Überarbeitungen. „Aus“ überspringt diese Schritte.", "mpsLongText": "{{engine}} läuft hier auf Apple Silicon (MPS) und dieser Text ist lang — die Generierung überschreitet möglicherweise das Zeitbudget. Kürzerer Text oder eine Aufteilung in mehrere Takes ist deutlich schneller.", - "inMemory": "Im Speicher" + "inMemory": "Im Speicher", + "argosRuntimeUnavailable": "Die Argos-Laufzeit ist nicht verfügbar. Installiere das Backend neu oder wähle NLLB." }, "capture": { "desc": "Globale Hotkeys funktionieren nur in der Desktop-App. Die Web-Benutzeroberfläche verwendet eine In-Page-Verknüpfung <1>Strg+Umschalt+Leertaste, während das Fenster den Fokus hat.", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index a693e664..8f05a960 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -2237,7 +2237,8 @@ "generateCaveat": "{{engine}} may be slow on this machine — {{reason}}", "cpuLongText": "{{engine}} runs on the CPU here, and this text is long — the generation may exceed the time budget. Shorter text, or a CPU-tuned engine (OmniVoice GGUF, Supertonic-3), will be much faster.", "cpuLongTextTuned": "{{engine}} runs on the CPU here, and this text is long — the generation may exceed the time budget. Shorter text, or splitting it into a few takes, will be much faster.", - "mpsLongText": "{{engine}} runs on Apple Silicon (MPS) here, and this text is long — the generation may exceed the time budget. Shorter text, or splitting it into a few takes, will be much faster." + "mpsLongText": "{{engine}} runs on Apple Silicon (MPS) here, and this text is long — the generation may exceed the time budget. Shorter text, or splitting it into a few takes, will be much faster.", + "argosRuntimeUnavailable": "Argos runtime unavailable. Reinstall the backend or select NLLB." }, "errors": { "title": "This tab hit a snag.", diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json index 327ff372..46f569f3 100644 --- a/frontend/src/i18n/locales/es.json +++ b/frontend/src/i18n/locales/es.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Convierte audio en texto para transcripciones, doblaje, subtítulos y dictado.", "familyDesc_llm": "Asistente de texto opcional para traducciones y reescrituras. «Desactivado» omite esos pasos.", "mpsLongText": "{{engine}} se ejecuta en Apple Silicon (MPS) aquí y este texto es largo: la generación puede superar el límite de tiempo. Un texto más corto, o dividirlo en varias tomas, será mucho más rápido.", - "inMemory": "En memoria" + "inMemory": "En memoria", + "argosRuntimeUnavailable": "El entorno de ejecución de Argos no está disponible. Reinstala el backend o selecciona NLLB." }, "capture": { "desc": "Las teclas de acceso rápido globales solo funcionan en la aplicación de escritorio. La interfaz de usuario web utiliza un acceso directo <1>Ctrl+Shift+Espacio en la página mientras la ventana tiene el foco.", diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json index f69da239..064e9242 100644 --- a/frontend/src/i18n/locales/fr.json +++ b/frontend/src/i18n/locales/fr.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Transforme l’audio en texte pour la transcription, le doublage, les sous-titres et la dictée.", "familyDesc_llm": "Assistant textuel facultatif pour la traduction et la réécriture. « Désactivé » ignore ces étapes.", "mpsLongText": "{{engine}} fonctionne ici sur Apple Silicon (MPS) et ce texte est long — la génération risque de dépasser le budget de temps. Un texte plus court, ou découpé en plusieurs prises, sera bien plus rapide.", - "inMemory": "En mémoire" + "inMemory": "En mémoire", + "argosRuntimeUnavailable": "Le moteur Argos est indisponible. Réinstallez le backend ou sélectionnez NLLB." }, "capture": { "desc": "Les raccourcis clavier globaux ne fonctionnent que dans l'application de bureau. L'interface utilisateur Web utilise un raccourci <1>Ctrl+Shift+Espace sur la page lorsque la fenêtre a le focus.", diff --git a/frontend/src/i18n/locales/hi.json b/frontend/src/i18n/locales/hi.json index f152e6c2..96394f25 100644 --- a/frontend/src/i18n/locales/hi.json +++ b/frontend/src/i18n/locales/hi.json @@ -864,7 +864,8 @@ "familyDesc_asr": "डबिंग, कैप्शन और डिक्टेशन के लिए ऑडियो को टेक्स्ट में बदलता है।", "familyDesc_llm": "अनुवाद और पुनर्लेखन के लिए वैकल्पिक टेक्स्ट सहायक। “बंद” होने पर ये चरण छोड़ दिए जाते हैं।", "mpsLongText": "यहाँ {{engine}} Apple Silicon (MPS) पर चलता है और यह टेक्स्ट लंबा है — जनरेशन समय सीमा पार कर सकता है। छोटा टेक्स्ट, या इसे कुछ हिस्सों में बाँटना, कहीं तेज़ रहेगा।", - "inMemory": "मेमोरी में" + "inMemory": "मेमोरी में", + "argosRuntimeUnavailable": "Argos रनटाइम उपलब्ध नहीं है। बैकएंड फिर से इंस्टॉल करें या NLLB चुनें।" }, "capture": { "desc": "ग्लोबल हॉटकीज़ केवल डेस्कटॉप ऐप में काम करती हैं। वेब यूआई इन-पेज <1>Ctrl+Shift+Space शॉर्टकट का उपयोग करता है जबकि विंडो पर फोकस होता है।", diff --git a/frontend/src/i18n/locales/id.json b/frontend/src/i18n/locales/id.json index 192cabff..3ae9967c 100644 --- a/frontend/src/i18n/locales/id.json +++ b/frontend/src/i18n/locales/id.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Mengubah audio menjadi teks untuk transkripsi, sulih suara, teks layar, dan dikte.", "familyDesc_llm": "Pembantu teks opsional untuk terjemahan dan penulisan ulang. “Nonaktif” akan melewati langkah tersebut.", "mpsLongText": "{{engine}} berjalan di Apple Silicon (MPS) di sini dan teks ini panjang — proses mungkin melampaui batas waktu. Teks lebih pendek, atau membaginya menjadi beberapa bagian, akan jauh lebih cepat.", - "inMemory": "Dalam memori" + "inMemory": "Dalam memori", + "argosRuntimeUnavailable": "Runtime Argos tidak tersedia. Instal ulang backend atau pilih NLLB." }, "capture": { "desc": "Tombol pintas global hanya berfungsi di aplikasi desktop. UI web menggunakan pintasan <1>Ctrl+Shift+Space dalam halaman saat jendela memiliki fokus.", diff --git a/frontend/src/i18n/locales/it.json b/frontend/src/i18n/locales/it.json index a99b8cd8..92440b18 100644 --- a/frontend/src/i18n/locales/it.json +++ b/frontend/src/i18n/locales/it.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Trasforma l’audio in testo per trascrizione, doppiaggio, sottotitoli e dettatura.", "familyDesc_llm": "Assistente di testo opzionale per traduzioni e riscritture. “Disattivato” salta questi passaggi.", "mpsLongText": "{{engine}} qui viene eseguito su Apple Silicon (MPS) e questo testo è lungo: la generazione potrebbe superare il limite di tempo. Un testo più breve, o diviso in più riprese, sarà molto più veloce.", - "inMemory": "In memoria" + "inMemory": "In memoria", + "argosRuntimeUnavailable": "Il runtime di Argos non è disponibile. Reinstalla il backend o seleziona NLLB." }, "capture": { "desc": "I tasti di scelta rapida globali funzionano solo nell'app desktop. L'interfaccia utente Web utilizza una scorciatoia <1>Ctrl+Shift+Spazio nella pagina mentre la finestra è attiva.", diff --git a/frontend/src/i18n/locales/ja.json b/frontend/src/i18n/locales/ja.json index 0e50ff46..0b8df331 100644 --- a/frontend/src/i18n/locales/ja.json +++ b/frontend/src/i18n/locales/ja.json @@ -864,7 +864,8 @@ "familyDesc_asr": "音声をテキストに変換し、ダビング、字幕、音声入力の文字起こしに使用します。", "familyDesc_llm": "翻訳や書き換え用の任意のテキスト補助です。「オフ」にするとこれらの手順を省略します。", "mpsLongText": "ここでは {{engine}} が Apple Silicon(MPS)で実行され、このテキストは長いため、生成が時間制限を超える可能性があります。テキストを短くするか、数回に分けたほうが大幅に高速です。", - "inMemory": "メモリに読み込み済み" + "inMemory": "メモリに読み込み済み", + "argosRuntimeUnavailable": "Argos ランタイムを利用できません。バックエンドを再インストールするか、NLLB を選択してください。" }, "capture": { "desc": "グローバル ホットキーはデスクトップ アプリでのみ機能します。 Web UI は、ウィンドウにフォーカスがある間、ページ内 <1>Ctrl+Shift+Space ショートカットを使用します。", diff --git a/frontend/src/i18n/locales/ko.json b/frontend/src/i18n/locales/ko.json index 46afa0d5..9dea19ee 100644 --- a/frontend/src/i18n/locales/ko.json +++ b/frontend/src/i18n/locales/ko.json @@ -1141,7 +1141,8 @@ "unload": "언로드", "unloadFailed": "언로드하지 못했습니다: {{message}}", "unloading": "언로드 중…", - "mpsLongText": "여기서는 {{engine}}이(가) Apple Silicon(MPS)에서 실행되며 이 텍스트는 길어 생성이 시간 제한을 초과할 수 있습니다. 텍스트를 줄이거나 여러 번에 나누면 훨씬 빠릅니다." + "mpsLongText": "여기서는 {{engine}}이(가) Apple Silicon(MPS)에서 실행되며 이 텍스트는 길어 생성이 시간 제한을 초과할 수 있습니다. 텍스트를 줄이거나 여러 번에 나누면 훨씬 빠릅니다.", + "argosRuntimeUnavailable": "Argos 런타임을 사용할 수 없습니다. 백엔드를 다시 설치하거나 NLLB를 선택하세요." }, "capture": { "desc": "전역 단축키는 데스크톱 앱에서만 작동합니다. 웹 UI는 창에 포커스가 있는 동안 페이지 내 <1>Ctrl+Shift+Space 단축키를 사용합니다.", diff --git a/frontend/src/i18n/locales/nl.json b/frontend/src/i18n/locales/nl.json index 1a016b80..18c3e072 100644 --- a/frontend/src/i18n/locales/nl.json +++ b/frontend/src/i18n/locales/nl.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Zet audio om in tekst voor transcriptie, nasynchronisatie, ondertiteling en dicteren.", "familyDesc_llm": "Optionele teksthulp voor vertalen en herschrijven. ‘Uit’ slaat deze stappen over.", "mpsLongText": "{{engine}} draait hier op Apple Silicon (MPS) en deze tekst is lang — de generatie overschrijdt mogelijk het tijdsbudget. Kortere tekst, of opsplitsen in meerdere takes, is veel sneller.", - "inMemory": "In het geheugen" + "inMemory": "In het geheugen", + "argosRuntimeUnavailable": "De Argos-runtime is niet beschikbaar. Installeer de backend opnieuw of kies NLLB." }, "capture": { "desc": "Globale sneltoetsen werken alleen in de desktop-app. De webinterface gebruikt een sneltoets <1>Ctrl+Shift+Spatie op de pagina terwijl het venster focus heeft.", diff --git a/frontend/src/i18n/locales/pl.json b/frontend/src/i18n/locales/pl.json index ffbe580f..6c1427da 100644 --- a/frontend/src/i18n/locales/pl.json +++ b/frontend/src/i18n/locales/pl.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Zamienia dźwięk na tekst do transkrypcji, dubbingu, napisów i dyktowania.", "familyDesc_llm": "Opcjonalny pomocnik tekstowy do tłumaczeń i przeredagowywania. „Wył.” pomija te kroki.", "mpsLongText": "{{engine}} działa tutaj na Apple Silicon (MPS), a ten tekst jest długi — generowanie może przekroczyć limit czasu. Krótszy tekst lub podzielenie go na kilka części będzie znacznie szybsze.", - "inMemory": "W pamięci" + "inMemory": "W pamięci", + "argosRuntimeUnavailable": "Środowisko Argos jest niedostępne. Zainstaluj ponownie backend lub wybierz NLLB." }, "capture": { "desc": "Globalne skróty klawiszowe działają tylko w aplikacji komputerowej. Interfejs WWW korzysta ze skrótu <1>Ctrl+Shift+Spacja znajdującego się na stronie, gdy okno jest aktywne.", diff --git a/frontend/src/i18n/locales/pt.json b/frontend/src/i18n/locales/pt.json index 4823fd93..e748a3ca 100644 --- a/frontend/src/i18n/locales/pt.json +++ b/frontend/src/i18n/locales/pt.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Transforma áudio em texto para transcrição, dublagem, legendas e ditado.", "familyDesc_llm": "Assistente de texto opcional para tradução e reescrita. “Desativado” ignora essas etapas.", "mpsLongText": "{{engine}} é executado em Apple Silicon (MPS) aqui e este texto é longo — a geração pode ultrapassar o limite de tempo. Um texto mais curto, ou dividido em algumas partes, será bem mais rápido.", - "inMemory": "Na memória" + "inMemory": "Na memória", + "argosRuntimeUnavailable": "O ambiente de execução do Argos está indisponível. Reinstale o backend ou selecione NLLB." }, "capture": { "desc": "As teclas de atalho globais funcionam apenas no aplicativo de desktop. A IU da web usa um atalho <1>Ctrl+Shift+Space na página enquanto a janela está em foco.", diff --git a/frontend/src/i18n/locales/ru.json b/frontend/src/i18n/locales/ru.json index 3c3ae5ef..342889d0 100644 --- a/frontend/src/i18n/locales/ru.json +++ b/frontend/src/i18n/locales/ru.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Преобразует аудио в текст для транскрипции, дубляжа, субтитров и диктовки.", "familyDesc_llm": "Необязательный текстовый помощник для перевода и перефразирования. Режим «Выкл.» пропускает эти этапы.", "mpsLongText": "Здесь {{engine}} работает на Apple Silicon (MPS), а этот текст длинный — генерация может превысить лимит времени. Более короткий текст или разбиение на несколько частей будет намного быстрее.", - "inMemory": "В памяти" + "inMemory": "В памяти", + "argosRuntimeUnavailable": "Среда выполнения Argos недоступна. Переустановите серверную часть или выберите NLLB." }, "capture": { "desc": "Глобальные горячие клавиши работают только в настольном приложении. Веб-интерфейс использует внутристраничный ярлык <1>Ctrl+Shift+Space, пока окно находится в фокусе.", diff --git a/frontend/src/i18n/locales/sv.json b/frontend/src/i18n/locales/sv.json index c9b23857..02e6b569 100644 --- a/frontend/src/i18n/locales/sv.json +++ b/frontend/src/i18n/locales/sv.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Gör om ljud till text för transkribering, dubbning, undertexter och diktering.", "familyDesc_llm": "Valfri texthjälp för översättning och omskrivning. ”Av” hoppar över dessa steg.", "mpsLongText": "{{engine}} körs på Apple Silicon (MPS) här och den här texten är lång — genereringen kan överskrida tidsbudgeten. Kortare text, eller uppdelad i flera tagningar, går betydligt snabbare.", - "inMemory": "I minnet" + "inMemory": "I minnet", + "argosRuntimeUnavailable": "Argos körmiljö är inte tillgänglig. Installera om backend eller välj NLLB." }, "capture": { "desc": "Globala snabbtangenter fungerar bara i skrivbordsappen. Webbgränssnittet använder en genväg <1>Ctrl+Skift+Mellanslag på sidan medan fönstret har fokus.", diff --git a/frontend/src/i18n/locales/th.json b/frontend/src/i18n/locales/th.json index fdaee49a..e614172d 100644 --- a/frontend/src/i18n/locales/th.json +++ b/frontend/src/i18n/locales/th.json @@ -864,7 +864,8 @@ "familyDesc_asr": "แปลงเสียงเป็นข้อความสำหรับการถอดเสียง การพากย์ คำบรรยาย และการป้อนตามเสียง", "familyDesc_llm": "ผู้ช่วยข้อความเสริมสำหรับการแปลและเขียนใหม่ เมื่อ “ปิด” จะข้ามขั้นตอนเหล่านี้", "mpsLongText": "{{engine}} ทำงานบน Apple Silicon (MPS) ที่นี่ และข้อความนี้ยาว — การสร้างอาจเกินงบประมาณเวลา ข้อความที่สั้นลง หรือแบ่งเป็นหลายส่วน จะเร็วกว่ามาก", - "inMemory": "อยู่ในหน่วยความจำ" + "inMemory": "อยู่ในหน่วยความจำ", + "argosRuntimeUnavailable": "ไม่สามารถใช้งานรันไทม์ Argos ได้ โปรดติดตั้งแบ็กเอนด์ใหม่หรือเลือก NLLB" }, "capture": { "desc": "ปุ่มลัดส่วนกลางใช้งานได้ในแอปเดสก์ท็อปเท่านั้น UI ของเว็บใช้ทางลัด <1>Ctrl+Shift+Space ในเพจในขณะที่หน้าต่างมีโฟกัส", diff --git a/frontend/src/i18n/locales/tr.json b/frontend/src/i18n/locales/tr.json index 7e98b0bf..18974fcb 100644 --- a/frontend/src/i18n/locales/tr.json +++ b/frontend/src/i18n/locales/tr.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Sesi; transkripsiyon, dublaj, altyazı ve dikte için metne dönüştürür.", "familyDesc_llm": "Çeviri ve yeniden yazma için isteğe bağlı metin yardımcısıdır. “Kapalı” bu adımları atlar.", "mpsLongText": "{{engine}} burada Apple Silicon (MPS) üzerinde çalışıyor ve bu metin uzun — üretim süre bütçesini aşabilir. Daha kısa bir metin ya da birkaç parçaya bölmek çok daha hızlı olacaktır.", - "inMemory": "Bellekte" + "inMemory": "Bellekte", + "argosRuntimeUnavailable": "Argos çalışma ortamı kullanılamıyor. Arka ucu yeniden yükleyin veya NLLB seçin." }, "capture": { "desc": "Genel kısayol tuşları yalnızca masaüstü uygulamasında çalışır. Web kullanıcı arayüzü, pencere odaktayken sayfa içi <1>Ctrl+Shift+Space kısayolunu kullanır.", diff --git a/frontend/src/i18n/locales/uk.json b/frontend/src/i18n/locales/uk.json index 8e29e53f..2785d3bc 100644 --- a/frontend/src/i18n/locales/uk.json +++ b/frontend/src/i18n/locales/uk.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Перетворює аудіо на текст для транскрипції, дубляжу, субтитрів і диктування.", "familyDesc_llm": "Необов’язковий текстовий помічник для перекладу й переписування. «Вимкнено» пропускає ці кроки.", "mpsLongText": "Тут {{engine}} працює на Apple Silicon (MPS), а цей текст довгий — генерація може перевищити ліміт часу. Коротший текст або розбиття на кілька частин буде значно швидшим.", - "inMemory": "У пам’яті" + "inMemory": "У пам’яті", + "argosRuntimeUnavailable": "Середовище виконання Argos недоступне. Перевстановіть серверну частину або виберіть NLLB." }, "capture": { "desc": "Глобальні гарячі клавіші працюють лише в настільній програмі. Веб-інтерфейс використовує ярлик <1>Ctrl+Shift+Пробіл на сторінці, коли вікно має фокус.", diff --git a/frontend/src/i18n/locales/vi.json b/frontend/src/i18n/locales/vi.json index 22ee8108..a652ca0a 100644 --- a/frontend/src/i18n/locales/vi.json +++ b/frontend/src/i18n/locales/vi.json @@ -864,7 +864,8 @@ "familyDesc_asr": "Chuyển âm thanh thành văn bản để phiên âm, lồng tiếng, tạo phụ đề và đọc chính tả.", "familyDesc_llm": "Trợ lý văn bản tùy chọn cho dịch thuật và viết lại. “Tắt” sẽ bỏ qua các bước này.", "mpsLongText": "{{engine}} chạy trên Apple Silicon (MPS) ở đây và văn bản này dài — quá trình tạo có thể vượt quá giới hạn thời gian. Văn bản ngắn hơn, hoặc chia thành vài phần, sẽ nhanh hơn nhiều.", - "inMemory": "Trong bộ nhớ" + "inMemory": "Trong bộ nhớ", + "argosRuntimeUnavailable": "Không thể sử dụng môi trường chạy Argos. Hãy cài lại backend hoặc chọn NLLB." }, "capture": { "desc": "Phím nóng chung chỉ hoạt động trong ứng dụng máy tính để bàn. Giao diện người dùng web sử dụng phím tắt <1>Ctrl+Shift+Space trong trang trong khi cửa sổ đang có tiêu điểm.", diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index 65201498..e2407ea8 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -1093,7 +1093,8 @@ "installStep_fetch_weights": "正在下载模型权重", "installStep_persist": "正在保存配置", "installAria": "安装 {{engine}}", - "mpsLongText": "{{engine}} 在本机上使用 Apple Silicon(MPS)运行,而这段文本较长——生成可能超出时间预算。缩短文本,或分成几次生成会快得多。" + "mpsLongText": "{{engine}} 在本机上使用 Apple Silicon(MPS)运行,而这段文本较长——生成可能超出时间预算。缩短文本,或分成几次生成会快得多。", + "argosRuntimeUnavailable": "Argos 运行时不可用。请重新安装后端或选择 NLLB。" }, "capture": { "desc": "全局热键仅在桌面应用中有效。网页界面使用页面内快捷键 <1>Ctrl+Shift+Space(窗口聚焦时可用)。", diff --git a/frontend/src/i18n/locales/zh-TW.json b/frontend/src/i18n/locales/zh-TW.json index 97a18052..6e5e768c 100644 --- a/frontend/src/i18n/locales/zh-TW.json +++ b/frontend/src/i18n/locales/zh-TW.json @@ -864,7 +864,8 @@ "familyDesc_asr": "將音訊轉換為文字,用於轉錄、配音、字幕和聽寫。", "familyDesc_llm": "用於翻譯和改寫的選用文字助理。「關閉」會略過這些步驟。", "mpsLongText": "{{engine}} 在此機器上使用 Apple Silicon(MPS)執行,而這段文字較長 — 生成可能超出時間預算。縮短文字或分成幾次生成會快得多。", - "inMemory": "已載入記憶體" + "inMemory": "已載入記憶體", + "argosRuntimeUnavailable": "Argos 執行環境無法使用。請重新安裝後端或選擇 NLLB。" }, "capture": { "desc": "全域熱鍵僅適用於桌面應用程式。當視窗具有焦點時,Web UI 使用頁內 <1>Ctrl+Shift+Space 快速鍵。", diff --git a/frontend/src/test/client.retry.test.ts b/frontend/src/test/client.retry.test.ts index b317dc9d..d1436c1c 100644 --- a/frontend/src/test/client.retry.test.ts +++ b/frontend/src/test/client.retry.test.ts @@ -1,3 +1,4 @@ +import i18n from 'i18next'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { apiFetch, ApiError } from '../api/client'; @@ -7,6 +8,17 @@ import { apiFetch, ApiError } from '../api/client'; // few times — but never retry an HTTP error (the backend responded) or a // deliberate abort, and still surface the actionable error if it stays down. describe('apiFetch transport-retry', () => { + it('localizes Argos runtime errors without losing recovery metadata', async () => { + const detail = { code: 'argos_runtime_unavailable', message: 'Raw native diagnostic' }; + vi.spyOn(i18n, 't').mockReturnValue('Localized recovery guidance'); + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue(new Response(JSON.stringify({ detail }), { status: 400 })), + ); + const err = await apiFetch('/dub/translate').catch((error) => error); + expect(err.message).toContain('Localized recovery guidance'); + expect(err.detail).toEqual(detail); + }); afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); diff --git a/tests/test_dub_translate.py b/tests/test_dub_translate.py index c1e15ddd..e48edd3b 100644 --- a/tests/test_dub_translate.py +++ b/tests/test_dub_translate.py @@ -804,3 +804,5 @@ async def test_argos_native_loader_error_does_not_expose_paths(monkeypatch): assert response.status_code == 400 assert private.encode() not in response.body assert b'CTranslate2' in response.body and b'reinstall' in response.body + import json + assert json.loads(response.body)['detail']['code'] == 'argos_runtime_unavailable'