feat(launchpad): surface Stories + Voice Gallery; fix gallery key-spread warning (#228)
- Launchpad showed only Clone/Design/Dub. Add ActionCards for the two newer
modes: Stories (multi-voice audiobooks → setMode('stories')) and Voice Gallery
(browse designed-voice archetypes → setMode('gallery')). i18n in en + zh-CN,
backfilled across all 21 locales.
- VoiceGallery: stop spreading a 'key' prop into <ArchetypeCard {...cardProps}>
(React dev warning + ignored). cardProps no longer carries key; pass key={a.id}
directly at the two render sites.
Verified: tsc clean, build OK, vitest green, CJK guard passes.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
94ed7e6347
commit
c2ce92cf69
@@ -8,7 +8,8 @@
|
||||
"busy": "أكمل الدبلجة أولاً — ثم ثبّت التحديث.",
|
||||
"whats_new": "ما هو الجديد",
|
||||
"failed": "فشل التحديث",
|
||||
"retry": "أعد المحاولة"
|
||||
"retry": "أعد المحاولة",
|
||||
"dismiss": "استبعاد"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "استنساخ",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "مغلق",
|
||||
"open": "مفتوح",
|
||||
"try_it": "جربه",
|
||||
"audio_only": "الصوت فقط"
|
||||
"audio_only": "الصوت فقط",
|
||||
"stories_title": "قصص",
|
||||
"stories_desc": "كتب صوتية متعددة الأصوات - ألقي شخصياتك، وأدخل النص، وقم بالتصدير حسب الفصل.",
|
||||
"gallery_title": "معرض الصوت",
|
||||
"gallery_desc": "تصفح الأصوات المصممة الجاهزة حسب اللهجة والعمر والأسلوب - بدون إعداد."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "موجه",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Beende zuerst deine Synchronisation – dann installiere das Update.",
|
||||
"whats_new": "Was ist neu?",
|
||||
"failed": "Update fehlgeschlagen",
|
||||
"retry": "Versuchen Sie es noch einmal"
|
||||
"retry": "Versuchen Sie es noch einmal",
|
||||
"dismiss": "Entlassen"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Klonen",
|
||||
@@ -184,7 +185,11 @@
|
||||
"locked": "GESPERRT",
|
||||
"open": "Offen",
|
||||
"try_it": "Probieren Sie es aus",
|
||||
"audio_only": "Nur Audio"
|
||||
"audio_only": "Nur Audio",
|
||||
"stories_title": "Geschichten",
|
||||
"stories_desc": "Mehrsprachige Hörbücher – Besetzung Ihrer Charaktere, Einfügen eines Skripts, Export nach Kapitel.",
|
||||
"gallery_title": "Sprachgalerie",
|
||||
"gallery_desc": "Durchsuchen Sie vorgefertigte Stimmen nach Akzent, Alter und Stil – ohne Einrichtung."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Prompt",
|
||||
|
||||
@@ -121,6 +121,10 @@
|
||||
"design_desc": "Build a new voice from a sentence. Gender, age, accent, mood — your call.",
|
||||
"dub_title": "Video Dubbing",
|
||||
"dub_desc": "Transcribe, translate, re-voice. Keep each speaker, line up the timing, ship it.",
|
||||
"stories_title": "Stories",
|
||||
"stories_desc": "Multi-voice audiobooks — cast your characters, drop in a script, export by chapter.",
|
||||
"gallery_title": "Voice Gallery",
|
||||
"gallery_desc": "Browse ready-made designed voices by accent, age and style — no setup.",
|
||||
"ab_compare": "A/B Compare",
|
||||
"ab_compare_title": "Try two voices side by side",
|
||||
"cloned_voices": "Cloned Voices",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Termina tu doblaje primero — luego instala la actualización.",
|
||||
"whats_new": "¿Qué hay de nuevo?",
|
||||
"failed": "La actualización falló",
|
||||
"retry": "Reintentar"
|
||||
"retry": "Reintentar",
|
||||
"dismiss": "Descartar"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Clonar",
|
||||
@@ -184,7 +185,11 @@
|
||||
"locked": "BLOQUEADO",
|
||||
"open": "Abierto",
|
||||
"try_it": "Pruébalo",
|
||||
"audio_only": "Sólo audio"
|
||||
"audio_only": "Sólo audio",
|
||||
"stories_title": "Historias",
|
||||
"stories_desc": "Audiolibros con varias voces: elige tus personajes, introduce un guión y exporta por capítulo.",
|
||||
"gallery_title": "Galería de voz",
|
||||
"gallery_desc": "Explore voces diseñadas ya preparadas por acento, edad y estilo, sin configuración."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "rápido",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Terminez d'abord votre doublage, puis installez la mise à jour.",
|
||||
"whats_new": "Quoi de neuf",
|
||||
"failed": "La mise à jour a échoué",
|
||||
"retry": "Réessayer"
|
||||
"retry": "Réessayer",
|
||||
"dismiss": "Rejeter"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Cloner",
|
||||
@@ -184,7 +185,11 @@
|
||||
"locked": "VERROUILLÉ",
|
||||
"open": "Ouvert",
|
||||
"try_it": "Essayez-le",
|
||||
"audio_only": "Audio uniquement"
|
||||
"audio_only": "Audio uniquement",
|
||||
"stories_title": "Histoires",
|
||||
"stories_desc": "Livres audio multi-voix : lancez vos personnages, insérez un script, exportez par chapitre.",
|
||||
"gallery_title": "Galerie vocale",
|
||||
"gallery_desc": "Parcourez les voix prêtes à l’emploi par accent, âge et style – aucune configuration."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Invite",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "पहले अपनी डबिंग पूरी करें — फिर अपडेट इंस्टॉल करें।",
|
||||
"whats_new": "नया क्या है",
|
||||
"failed": "अद्यतन विफल रहा",
|
||||
"retry": "पुनः प्रयास करें"
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"dismiss": "ख़ारिज करें"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "क्लोन",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "ताला लगा दिया",
|
||||
"open": "खुला",
|
||||
"try_it": "इसे आज़माएं",
|
||||
"audio_only": "केवल ऑडियो"
|
||||
"audio_only": "केवल ऑडियो",
|
||||
"stories_title": "कहानियां",
|
||||
"stories_desc": "मल्टी-वॉइस ऑडियोबुक - अपने पात्रों को कास्ट करें, एक स्क्रिप्ट डालें, अध्याय के अनुसार निर्यात करें।",
|
||||
"gallery_title": "आवाज गैलरी",
|
||||
"gallery_desc": "उच्चारण, उम्र और शैली के आधार पर तैयार डिज़ाइन वाली आवाज़ें ब्राउज़ करें - कोई सेटअप नहीं।"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "शीघ्र",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Selesaikan sulih suara Anda dulu — lalu instal pembaruannya.",
|
||||
"whats_new": "Apa yang baru",
|
||||
"failed": "Pembaruan gagal",
|
||||
"retry": "Coba lagi"
|
||||
"retry": "Coba lagi",
|
||||
"dismiss": "Singkirkan"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Kloning",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "TERKUNCI",
|
||||
"open": "Buka",
|
||||
"try_it": "Cobalah",
|
||||
"audio_only": "Hanya Audio"
|
||||
"audio_only": "Hanya Audio",
|
||||
"stories_title": "Cerita",
|
||||
"stories_desc": "Buku audio multi-suara — masukkan karakter Anda, masukkan skrip, ekspor per bab.",
|
||||
"gallery_title": "Galeri Suara",
|
||||
"gallery_desc": "Telusuri suara rancangan siap pakai berdasarkan aksen, usia, dan gaya — tanpa penyiapan."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Cepat",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Completa prima il tuo doppiaggio, poi installa l'aggiornamento.",
|
||||
"whats_new": "Cosa c'è di nuovo",
|
||||
"failed": "Aggiornamento non riuscito",
|
||||
"retry": "Riprova"
|
||||
"retry": "Riprova",
|
||||
"dismiss": "Ignora"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Clona",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "BLOCCATO",
|
||||
"open": "Aperto",
|
||||
"try_it": "Provalo",
|
||||
"audio_only": "Solo audio"
|
||||
"audio_only": "Solo audio",
|
||||
"stories_title": "Storie",
|
||||
"stories_desc": "Audiolibri a più voci: lancia i tuoi personaggi, inserisci una sceneggiatura, esporta per capitolo.",
|
||||
"gallery_title": "Galleria vocale",
|
||||
"gallery_desc": "Sfoglia le voci già pronte per accento, età e stile, senza alcuna configurazione."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Richiedi",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "先に吹き替えを完了してから、アップデートをインストールしてください。",
|
||||
"whats_new": "新機能",
|
||||
"failed": "アップデートに失敗しました",
|
||||
"retry": "再試行"
|
||||
"retry": "再試行",
|
||||
"dismiss": "解雇する"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "複製",
|
||||
@@ -184,7 +185,11 @@
|
||||
"locked": "ロックされています",
|
||||
"open": "開く",
|
||||
"try_it": "試してみてください",
|
||||
"audio_only": "音声のみ"
|
||||
"audio_only": "音声のみ",
|
||||
"stories_title": "ストーリー",
|
||||
"stories_desc": "マルチボイスオーディオブック — キャラクターをキャストし、スクリプトを入力し、章ごとにエクスポートします。",
|
||||
"gallery_title": "ボイスギャラリー",
|
||||
"gallery_desc": "アクセント、年齢、スタイルごとに既製のデザインされた音声を参照できます。セットアップは必要ありません。"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "プロンプト",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "먼저 더빙을 완료한 후 업데이트를 설치하세요.",
|
||||
"whats_new": "새로운 소식",
|
||||
"failed": "업데이트 실패",
|
||||
"retry": "재시도"
|
||||
"retry": "재시도",
|
||||
"dismiss": "닫기"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "복제",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "잠김",
|
||||
"open": "열기",
|
||||
"try_it": "사용해 보세요",
|
||||
"audio_only": "오디오만"
|
||||
"audio_only": "오디오만",
|
||||
"stories_title": "스토리",
|
||||
"stories_desc": "다중 음성 오디오북 — 캐릭터 캐스팅, 스크립트 삽입, 장별로 내보내기.",
|
||||
"gallery_title": "보이스 갤러리",
|
||||
"gallery_desc": "악센트, 연령, 스타일별로 미리 디자인된 음성을 찾아보세요. 설정이 필요 없습니다."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "프롬프트",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Voltooi eerst je nasynchronisatie — installeer daarna de update.",
|
||||
"whats_new": "Wat is er nieuw",
|
||||
"failed": "Update mislukt",
|
||||
"retry": "Opnieuw proberen"
|
||||
"retry": "Opnieuw proberen",
|
||||
"dismiss": "Negeren"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Klonen",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "GESLOTEN",
|
||||
"open": "Openen",
|
||||
"try_it": "Probeer het",
|
||||
"audio_only": "Alleen audio"
|
||||
"audio_only": "Alleen audio",
|
||||
"stories_title": "Verhalen",
|
||||
"stories_desc": "Audioboeken met meerdere stemmen: cast je personages, voeg een script toe en exporteer per hoofdstuk.",
|
||||
"gallery_title": "Stemgalerij",
|
||||
"gallery_desc": "Blader door kant-en-klare ontworpen stemmen op accent, leeftijd en stijl - geen installatie."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Prompt",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Najpierw dokończ dubbing — potem zainstaluj aktualizację.",
|
||||
"whats_new": "Co nowego?",
|
||||
"failed": "Aktualizacja nie powiodła się",
|
||||
"retry": "Spróbuj ponownie"
|
||||
"retry": "Spróbuj ponownie",
|
||||
"dismiss": "Odrzuć"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Klonuj",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "ZABLOKOWANE",
|
||||
"open": "Otwórz",
|
||||
"try_it": "Spróbuj",
|
||||
"audio_only": "Tylko dźwięk"
|
||||
"audio_only": "Tylko dźwięk",
|
||||
"stories_title": "Historie",
|
||||
"stories_desc": "Audiobooki z wieloma głosami — rzucaj postaciami, wstawiaj scenariusz, eksportuj według rozdziałów.",
|
||||
"gallery_title": "Galeria Głosów",
|
||||
"gallery_desc": "Przeglądaj gotowe, zaprojektowane głosy według akcentu, wieku i stylu — bez konieczności konfiguracji."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Podpowiedź",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Termine sua dublagem primeiro — depois instale a atualização.",
|
||||
"whats_new": "O que há de novo",
|
||||
"failed": "Falha na atualização",
|
||||
"retry": "Tentar novamente"
|
||||
"retry": "Tentar novamente",
|
||||
"dismiss": "Dispensar"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Clonar",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "BLOQUEADO",
|
||||
"open": "Abrir",
|
||||
"try_it": "Experimente",
|
||||
"audio_only": "Somente áudio"
|
||||
"audio_only": "Somente áudio",
|
||||
"stories_title": "Histórias",
|
||||
"stories_desc": "Audiolivros com múltiplas vozes – escolha seus personagens, insira um roteiro, exporte por capítulo.",
|
||||
"gallery_title": "Galeria de Voz",
|
||||
"gallery_desc": "Procure vozes projetadas e prontas por sotaque, idade e estilo — sem configuração."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Alerta",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Сначала завершите дубляж — затем установите обновление.",
|
||||
"whats_new": "Что нового",
|
||||
"failed": "Обновление не удалось",
|
||||
"retry": "Повторить попытку"
|
||||
"retry": "Повторить попытку",
|
||||
"dismiss": "Увольнять"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Клонировать",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "ЗАБЛОКИРОВАНО",
|
||||
"open": "Открыть",
|
||||
"try_it": "Попробуйте это",
|
||||
"audio_only": "Только аудио"
|
||||
"audio_only": "Только аудио",
|
||||
"stories_title": "Истории",
|
||||
"stories_desc": "Многоголосые аудиокниги — выбирайте своих персонажей, добавляйте сценарий, экспортируйте по главам.",
|
||||
"gallery_title": "Голосовая галерея",
|
||||
"gallery_desc": "Просматривайте готовые голоса по акценту, возрасту и стилю — без настройки."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Быстрый",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Slutför din dubbning först – installera sedan uppdateringen.",
|
||||
"whats_new": "Vad är nytt",
|
||||
"failed": "Uppdateringen misslyckades",
|
||||
"retry": "Försök igen"
|
||||
"retry": "Försök igen",
|
||||
"dismiss": "Avvisa"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Klona",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "LÅST",
|
||||
"open": "Öppna",
|
||||
"try_it": "Prova det",
|
||||
"audio_only": "Endast ljud"
|
||||
"audio_only": "Endast ljud",
|
||||
"stories_title": "Berättelser",
|
||||
"stories_desc": "Ljudböcker med flera röster – cast dina karaktärer, släpp in ett manus, exportera efter kapitel.",
|
||||
"gallery_title": "Röstgalleri",
|
||||
"gallery_desc": "Bläddra bland färdigdesignade röster efter accent, ålder och stil – inga inställningar."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Fråga",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "ทำการพากย์เสียงของคุณให้เสร็จก่อน แล้วจึงติดตั้งอัปเดต",
|
||||
"whats_new": "มีอะไรใหม่",
|
||||
"failed": "การอัปเดตล้มเหลว",
|
||||
"retry": "ลองอีกครั้ง"
|
||||
"retry": "ลองอีกครั้ง",
|
||||
"dismiss": "ยกเลิก"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "โคลนเสียง",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "ล็อคแล้ว",
|
||||
"open": "เปิด",
|
||||
"try_it": "ลองมัน",
|
||||
"audio_only": "เสียงเท่านั้น"
|
||||
"audio_only": "เสียงเท่านั้น",
|
||||
"stories_title": "เรื่องราว",
|
||||
"stories_desc": "หนังสือเสียงหลายเสียง — เลือกตัวละครของคุณ ใส่สคริปต์ ส่งออกตามบท",
|
||||
"gallery_title": "แกลเลอรี่เสียง",
|
||||
"gallery_desc": "เรียกดูเสียงที่ออกแบบสำเร็จรูปตามสำเนียง อายุ และสไตล์ — ไม่ต้องตั้งค่าใดๆ"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "พรอมต์",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Önce dublajını bitir — sonra güncellemeyi yükle.",
|
||||
"whats_new": "Yenilikler",
|
||||
"failed": "Güncelleme başarısız oldu",
|
||||
"retry": "Yeniden dene"
|
||||
"retry": "Yeniden dene",
|
||||
"dismiss": "Reddet"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Klonla",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "KİLİTLİ",
|
||||
"open": "Açık",
|
||||
"try_it": "Deneyin",
|
||||
"audio_only": "Yalnızca Ses"
|
||||
"audio_only": "Yalnızca Ses",
|
||||
"stories_title": "Hikayeler",
|
||||
"stories_desc": "Çok sesli sesli kitaplar — karakterlerinizi yayınlayın, bir komut dosyası ekleyin, bölüme göre dışa aktarın.",
|
||||
"gallery_title": "Ses Galerisi",
|
||||
"gallery_desc": "Hazır tasarlanmış seslere aksan, yaş ve stile göre göz atın; kurulum gerektirmez."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "İstemi",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Спочатку завершіть дубляж — потім встановіть оновлення.",
|
||||
"whats_new": "Що нового",
|
||||
"failed": "Не вдалося оновити",
|
||||
"retry": "Повторіть спробу"
|
||||
"retry": "Повторіть спробу",
|
||||
"dismiss": "Відхилити"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Клонувати",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "ЗАБЛОКУВАНО",
|
||||
"open": "відкритий",
|
||||
"try_it": "Спробуйте",
|
||||
"audio_only": "Лише аудіо"
|
||||
"audio_only": "Лише аудіо",
|
||||
"stories_title": "оповідання",
|
||||
"stories_desc": "Багатоголосові аудіокниги — вибирайте персонажів, вставляйте сценарій, експортуйте за розділами.",
|
||||
"gallery_title": "Голосова галерея",
|
||||
"gallery_desc": "Переглядайте готові розроблені голоси за акцентом, віком і стилем — без налаштування."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Підкажіть",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "Hoàn tất lồng tiếng của bạn trước — rồi cài đặt bản cập nhật.",
|
||||
"whats_new": "Có gì mới",
|
||||
"failed": "Cập nhật không thành công",
|
||||
"retry": "Thử lại"
|
||||
"retry": "Thử lại",
|
||||
"dismiss": "Loại bỏ"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "Nhân bản",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "ĐÃ KHÓA",
|
||||
"open": "Mở",
|
||||
"try_it": "Hãy thử nó",
|
||||
"audio_only": "Chỉ âm thanh"
|
||||
"audio_only": "Chỉ âm thanh",
|
||||
"stories_title": "Truyện",
|
||||
"stories_desc": "Sách nói nhiều giọng nói — chọn nhân vật, nhập tập lệnh, xuất theo chương.",
|
||||
"gallery_title": "Thư viện giọng nói",
|
||||
"gallery_desc": "Duyệt qua các giọng nói được thiết kế sẵn theo giọng, độ tuổi và phong cách — không cần thiết lập."
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "Lời nhắc",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "请先完成配音,再安装更新。",
|
||||
"whats_new": "更新内容",
|
||||
"failed": "更新失败",
|
||||
"retry": "重试"
|
||||
"retry": "重试",
|
||||
"dismiss": "解雇"
|
||||
},
|
||||
"stories": {
|
||||
"title": "故事编辑器",
|
||||
@@ -120,6 +121,10 @@
|
||||
"design_desc": "用一句话构建新声线。性别、年龄、口音、情绪 — 由你决定。",
|
||||
"dub_title": "视频配音",
|
||||
"dub_desc": "转录、翻译、重新配音。保留每位说话人,对齐时间轴,然后导出。",
|
||||
"stories_title": "故事",
|
||||
"stories_desc": "多角色有声书——为角色分配语音,粘贴脚本,按章节导出。",
|
||||
"gallery_title": "语音库",
|
||||
"gallery_desc": "按口音、年龄和风格浏览现成的设计语音——无需设置。",
|
||||
"ab_compare": "A/B 对比",
|
||||
"ab_compare_title": "并排试听两个声音",
|
||||
"cloned_voices": "克隆语音",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"busy": "請先完成配音,再安裝更新。",
|
||||
"whats_new": "有什麼新消息",
|
||||
"failed": "更新失敗",
|
||||
"retry": "重試"
|
||||
"retry": "重試",
|
||||
"dismiss": "解僱"
|
||||
},
|
||||
"nav": {
|
||||
"clone": "複製聲音",
|
||||
@@ -183,7 +184,11 @@
|
||||
"locked": "鎖定",
|
||||
"open": "打開",
|
||||
"try_it": "嘗試一下",
|
||||
"audio_only": "僅音訊"
|
||||
"audio_only": "僅音訊",
|
||||
"stories_title": "故事",
|
||||
"stories_desc": "多語音有聲書 — 塑造角色、插入劇本、依章節導出。",
|
||||
"gallery_title": "語音畫廊",
|
||||
"gallery_desc": "按口音、年齡和風格瀏覽現成的設計聲音 - 無需設定。"
|
||||
},
|
||||
"clone": {
|
||||
"prompt": "提示",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Scale, Fingerprint, Wand2, Film, Lock,
|
||||
Scale, Fingerprint, Wand2, Film, Lock, BookOpen, LibraryBig,
|
||||
} from 'lucide-react';
|
||||
import { API } from '../api/client';
|
||||
import ReadinessChecklist from '../components/ReadinessChecklist';
|
||||
@@ -138,6 +138,12 @@ export default function Launchpad({
|
||||
<ActionCard hue="#fe8019" Icon={Film} title={t('launchpad.dub_title')} accent="🎬" count={studioProjects.length} onClick={() => setMode('dub')}>
|
||||
{t('launchpad.dub_desc')}
|
||||
</ActionCard>
|
||||
<ActionCard hue="#83a598" Icon={BookOpen} title={t('launchpad.stories_title')} accent="📖" onClick={() => setMode('stories')}>
|
||||
{t('launchpad.stories_desc')}
|
||||
</ActionCard>
|
||||
<ActionCard hue="#fabd2f" Icon={LibraryBig} title={t('launchpad.gallery_title')} accent="🎭" onClick={() => setMode('gallery')}>
|
||||
{t('launchpad.gallery_desc')}
|
||||
</ActionCard>
|
||||
</div>
|
||||
|
||||
{/* Demo profile callout */}
|
||||
|
||||
@@ -242,8 +242,10 @@ function ArchetypesZone({
|
||||
const favSet = useMemo(() => new Set(favorites), [favorites]);
|
||||
const applyFav = (list) => (favOnly ? list.filter((a) => favSet.has(a.id)) : list);
|
||||
|
||||
// NOTE: no `key` here — React keys must be passed directly on the element,
|
||||
// not spread in (spreading a `key` prop triggers a dev warning + is ignored).
|
||||
const cardProps = (a) => ({
|
||||
key: a.id, a, t, viewMode,
|
||||
a, t, viewMode,
|
||||
isFavorite: favSet.has(a.id),
|
||||
isPlaying: playingId === a.id,
|
||||
isLoadingPreview: loadingPreviewId === a.id,
|
||||
@@ -308,7 +310,7 @@ function ArchetypesZone({
|
||||
<section className="archetype-section">
|
||||
<div className="content-header"><div className="content-title">{t('archetypes.featured', { defaultValue: 'Featured' })}</div></div>
|
||||
<div className={`archetype-grid ${viewMode}`}>
|
||||
{applyFav(featured).map((a) => <ArchetypeCard {...cardProps(a)} />)}
|
||||
{applyFav(featured).map((a) => <ArchetypeCard key={a.id} {...cardProps(a)} />)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
@@ -325,7 +327,7 @@ function ArchetypesZone({
|
||||
) : (
|
||||
<>
|
||||
<div className={`archetype-grid ${viewMode}`}>
|
||||
{applyFav(browse).map((a) => <ArchetypeCard {...cardProps(a)} />)}
|
||||
{applyFav(browse).map((a) => <ArchetypeCard key={a.id} {...cardProps(a)} />)}
|
||||
</div>
|
||||
{applyFav(browse).length === 0 && (
|
||||
<div className="empty">{t('gallery.no_matches', { defaultValue: 'No voices match these filters.' })}</div>
|
||||
|
||||
Reference in New Issue
Block a user