diff --git a/_locales/ar/messages.json b/_locales/ar/messages.json index 1724a0bacd..7214fcc4b6 100644 --- a/_locales/ar/messages.json +++ b/_locales/ar/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "صوت قبالة" }, + "voiceOverriddenBySayPi": { + "description": "يظهر فوق شبكة أصوات المساعد في صفحة إعداداته عندما يتولى صوت من Say, Pi تشغيل الردود، لذلك لا يكون صوت المساعد الأصلي المميز هو المسموع؛ $voice$ هو اسم صوت Say, Pi، مثل Shimmer.", + "message": "يتحدث Say, Pi بصوت $voice$ — الأصوات أدناه غير مستخدمة.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "توقف الصوت مؤقتًا -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "يظهر في شريط التحكم بالأصوات عند عدم تحديد صوت من Say, Pi واستجابة المساعد بأحد أصواته المدمجة؛ $host$ هو اسم المساعد، مثل Pi.", + "message": "صوتك: صوت $host$ الأصلي", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "يظهر في شريط التحكم بالأصوات عند عدم تحديد صوت في مساعد لا يملك أصواتًا مدمجة، لذلك لا يُنطق شيء.", + "message": "لا يوجد صوت — لا تُقرأ الردود بصوت عالٍ" + }, "voicesHeardCount": { "description": "مؤشر تقدم في شريط التحكم بالأصوات ضمن الإعدادات: عدد الأصوات المُدرجة التي سبق أن استمع إليها المستخدم. أرقام فقط، لذا يُقرأ بشكل صحيح عند 1 أيضًا — لا يحتوي Chrome i18n على صيغ جمع.", "message": "تم الاستماع إلى $heard$ من $total$", diff --git a/_locales/bg/messages.json b/_locales/bg/messages.json index e6cf9953de..5788871cdd 100644 --- a/_locales/bg/messages.json +++ b/_locales/bg/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Гласувайте" }, + "voiceOverriddenBySayPi": { + "description": "Показва се над мрежата с вградени гласове на асистента в страницата с настройки, когато глас на Say, Pi е поел възпроизвеждането, така че маркираният вграден глас на асистента не е този, който потребителят чува; $voice$ е името на гласа на Say, Pi, например Shimmer.", + "message": "Say, Pi говори с $voice$ — гласовете по-долу не се използват.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Гласът спря -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Показва се в лентата за управление на гласовете, когато не е избран глас на Say, Pi и асистентът отговаря с един от собствените си вградени гласове; $host$ е асистентът, например Pi.", + "message": "Вашият глас: собственият на $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Показва се в лентата за управление на гласовете, когато не е избран глас за асистент, който няма собствени вградени гласове, така че нищо не се произнася.", + "message": "Няма глас — отговорите не се прочитат на глас" + }, "voicesHeardCount": { "description": "Отчет за напредъка в лентата за управление на настройките „Гласове“: колко от изброените гласове потребителят вече е слушал. Само числа, така че да се чете правилно и при 1 — Chrome i18n няма множествени форми.", "message": "Чути $heard$ от $total$", diff --git a/_locales/bn/messages.json b/_locales/bn/messages.json index 7f87cef7b0..d57e7c2885 100644 --- a/_locales/bn/messages.json +++ b/_locales/bn/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "ভয়েস অফ" }, + "voiceOverriddenBySayPi": { + "description": "Say, Pi-এর কোনো ভয়েস প্লেব্যাকের দায়িত্ব নিলে সহকারীর সেটিংস পৃষ্ঠায় নিজস্ব ভয়েসগুলোর গ্রিডের ওপরে দেখানো হয়, তাই সহকারীর হাইলাইট করা নিজস্ব ভয়েসটি ব্যবহারকারী শুনছেন না; $voice$ হলো Say, Pi-এর ভয়েসের নাম, যেমন Shimmer।", + "message": "Say, Pi $voice$ হিসেবে কথা বলছে — নিচের ভয়েসগুলো ব্যবহার হচ্ছে না।", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "ভয়েস বিরতি -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "কোনো SayPi ভয়েস নির্বাচিত না থাকলে এবং সহকারী তার নিজস্ব অন্তর্নির্মিত ভয়েসগুলোর একটিতে উত্তর দিলে Voices নিয়ন্ত্রণ বারে দেখানো হয়; $host$ হলো সহকারীর নাম, যেমন Pi।", + "message": "আপনার ভয়েস: $host$-এর নিজস্ব", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "নিজস্ব কোনো অন্তর্নির্মিত ভয়েস নেই এমন সহকারীর জন্য কোনো ভয়েস নির্বাচিত না থাকলে Voices নিয়ন্ত্রণ বারে দেখানো হয়, তাই কিছুই বলা হয় না।", + "message": "কোনো ভয়েস নেই — উত্তর পড়ে শোনানো হয় না" + }, "voicesHeardCount": { "description": "সেটিংসের Voices কন্ট্রোল বারে অগ্রগতির রিডআউট: তালিকাভুক্ত ভয়েসগুলোর মধ্যে ব্যবহারকারী কতগুলো ইতিমধ্যে শুনেছেন। খালি সংখ্যা, তাই ১ হলেও ঠিকভাবে পড়ে — Chrome i18n-এ plural ফর্ম নেই।", "message": "$total$-এর মধ্যে $heard$টি শোনা হয়েছে", diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json index 88e1dd2968..e1207092a8 100644 --- a/_locales/cs/messages.json +++ b/_locales/cs/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Voice off" }, + "voiceOverriddenBySayPi": { + "description": "Zobrazuje se nad vlastní mřížkou hlasů asistenta na stránce nastavení, když Say, Pi převezme přehrávání, takže zvýrazněný nativní hlas asistenta není hlas, který uživatel slyší; $voice$ je název hlasu Say, Pi, například Shimmer.", + "message": "Say, Pi mluví hlasem $voice$ — hlasy níže se nepoužívají.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Hlas se zastavil -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Zobrazuje se na liště Ovládání hlasu, když není vybrán hlas Say, Pi a asistent odpovídá jedním ze svých vlastních vestavěných hlasů; $host$ je asistent, například Pi.", + "message": "Váš hlas: vlastní hlas $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Zobrazuje se na liště Ovládání hlasu, když u asistenta bez vlastních vestavěných hlasů není vybrán žádný hlas, takže se nic nepřehrává.", + "message": "Bez hlasu — odpovědi se nepředčítají" + }, "voicesHeardCount": { "description": "Ukazatel průběhu v ovládací liště Hlasů v nastavení: kolik z uvedených hlasů si už uživatel poslechl. Holá čísla, takže to zní správně i při 1 — Chrome i18n nemá plurálové tvary.", "message": "Poslechnuto $heard$ z $total$", diff --git a/_locales/da/messages.json b/_locales/da/messages.json index 6209af5fc4..813c1e4d84 100644 --- a/_locales/da/messages.json +++ b/_locales/da/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Stemme væk" }, + "voiceOverriddenBySayPi": { + "description": "Vises over assistentens eget stemmegitter på indstillingssiden, når en Say, Pi-stemme har overtaget afspilningen, så assistentens fremhævede indbyggede stemme ikke er den, brugeren hører; $voice$ er navnet på Say, Pi-stemmen, f.eks. Shimmer.", + "message": "Say, Pi taler med $voice$ — stemmerne nedenfor bruges ikke.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Stemme pausede -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Vises i stemmekontrollinjen, når ingen Say, Pi-stemme er valgt, og assistenten i stedet svarer med en af sine egne indbyggede stemmer; $host$ er assistenten, f.eks. Pi.", + "message": "Din stemme: $host$'s egen", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Vises i stemmekontrollinjen, når ingen stemme er valgt for en assistent, der ikke har egne indbyggede stemmer, så intet bliver læst op.", + "message": "Ingen stemme — svar læses ikke højt" + }, "voicesHeardCount": { "description": "Status i indstillingskontrollen for Stemmer: hvor mange af de viste stemmer brugeren allerede har lyttet til. Kun tal, så det læses korrekt ved 1 — Chrome i18n har ingen flertalsformer.", "message": "$heard$ af $total$ hørt", diff --git a/_locales/de/messages.json b/_locales/de/messages.json index c091c5523f..9925a43fc8 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Stimme ab" }, + "voiceOverriddenBySayPi": { + "description": "Wird auf der Einstellungsseite über dem Stimmenraster des Assistenten angezeigt, wenn eine Say, Pi-Stimme die Wiedergabe übernommen hat und die hervorgehobene integrierte Stimme des Assistenten daher nicht zu hören ist; $voice$ ist der Name der Say, Pi-Stimme, z. B. Shimmer.", + "message": "Say, Pi spricht mit $voice$ – die folgenden Stimmen werden nicht verwendet.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Stimme pausierte -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Wird in der Stimmenleiste angezeigt, wenn keine Say, Pi-Stimme ausgewählt ist und der Assistent stattdessen mit einer seiner integrierten Stimmen antwortet; $host$ ist der Assistent, z. B. Pi.", + "message": "Deine Stimme: die von $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Wird in der Stimmenleiste angezeigt, wenn bei einem Assistenten ohne eigene integrierte Stimmen keine Stimme ausgewählt ist und daher nichts vorgelesen wird.", + "message": "Keine Stimme – Antworten werden nicht vorgelesen" + }, "voicesHeardCount": { "description": "Fortschrittsanzeige in der Steuerleiste „Stimmen“ in den Einstellungen: wie viele der gelisteten Stimmen der Nutzer bereits angehört hat. Nur Zahlen, sodass es bei 1 ebenso korrekt klingt – Chrome i18n hat keine Pluralformen.", "message": "$heard$ von $total$ gehört", diff --git a/_locales/el/messages.json b/_locales/el/messages.json index 620ba5be60..5a6bea153e 100644 --- a/_locales/el/messages.json +++ b/_locales/el/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Εκφοβίζω" }, + "voiceOverriddenBySayPi": { + "description": "Εμφανίζεται πάνω από το πλέγμα φωνών του βοηθού στη σελίδα ρυθμίσεων, όταν μια φωνή του Say, Pi έχει αναλάβει την αναπαραγωγή, επομένως η επιλεγμένη ενσωματωμένη φωνή του βοηθού δεν είναι αυτή που ακούει ο χρήστης· το $voice$ είναι το όνομα της φωνής του Say, Pi, π.χ. Shimmer.", + "message": "Το Say, Pi μιλά με τη φωνή $voice$ — οι παρακάτω φωνές δεν χρησιμοποιούνται.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Η φωνή σταμάτησε -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Εμφανίζεται στη γραμμή ελέγχου «Φωνές» όταν δεν έχει επιλεγεί φωνή του Say, Pi και ο βοηθός απαντά με μία από τις δικές του ενσωματωμένες φωνές· το $host$ είναι ο βοηθός, π.χ. Pi.", + "message": "Η φωνή σας: της/του $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Εμφανίζεται στη γραμμή ελέγχου «Φωνές» όταν δεν έχει επιλεγεί φωνή σε βοηθό που δεν διαθέτει δικές του ενσωματωμένες φωνές, οπότε δεν ακούγεται τίποτα.", + "message": "Χωρίς φωνή — οι απαντήσεις δεν εκφωνούνται" + }, "voicesHeardCount": { "description": "Ένδειξη προόδου στη γραμμή ελέγχου Φωνών στις ρυθμίσεις: πόσες από τις φωνές που εμφανίζονται έχει ήδη ακούσει ο χρήστης. Γυμνοί αριθμοί, ώστε να διαβάζεται σωστά και στο 1 — το Chrome i18n δεν έχει πληθυντικούς τύπους.", "message": "Ακούστηκαν $heard$ από $total$", diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 0021bc94ac..77b4d4a71f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -976,6 +976,16 @@ "message": "More voices…", "description": "Menu row linking from the in-page voice menu to the full voice catalog in the extension settings" }, + "voiceOverriddenBySayPi": { + "message": "Say, Pi is speaking as $voice$ — the voices below aren't in use.", + "description": "Shown above the assistant's own voice grid on its settings page when a Say, Pi voice has taken over playback, so the assistant's highlighted native voice is not what the user hears; $voice$ is the Say, Pi voice name, e.g. Shimmer.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicesSectionTitle": { "message": "Voices", "description": "Heading for the voice catalog section in the settings AI Chat tab" @@ -1214,6 +1224,20 @@ } } }, + "voicesFallbackHostVoice": { + "message": "Your voice: $host$'s own", + "description": "Shown in the Voices control bar when no SayPi voice is selected and the assistant answers in one of its own built-in voices instead; $host$ is the assistant, e.g. Pi.", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "message": "No voice — replies aren't read aloud", + "description": "Shown in the Voices control bar when no voice is selected on an assistant that has no built-in voices of its own, so nothing is spoken." + }, "voicesNoSampleGroup": { "message": "No sample yet ($count$)", "description": "Heading for the group at the end of the settings Voices list holding voices with no sample clip to play; $count$ is how many. Phrased to read correctly at 1 — Chrome i18n has no plural forms.", diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 9c6b4850f5..4b0e6d74d1 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Salir" }, + "voiceOverriddenBySayPi": { + "description": "Se muestra sobre la cuadrícula de voces del asistente en su página de ajustes cuando una voz de Say, Pi se ha impuesto para la reproducción, por lo que la voz nativa destacada del asistente no es la que oye el usuario; $voice$ es el nombre de la voz de Say, Pi, por ejemplo, Shimmer.", + "message": "Say, Pi habla con $voice$; las voces de abajo no se usan.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Voz en pausa -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Se muestra en la barra de control Voces cuando no se ha seleccionado ninguna voz de Say, Pi y el asistente responde con una de sus voces integradas; $host$ es el asistente, por ejemplo, Pi.", + "message": "Tu voz: la propia de $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Se muestra en la barra de control Voces cuando no se ha seleccionado ninguna voz en un asistente que no tiene voces integradas, por lo que no se reproduce nada.", + "message": "Sin voz; las respuestas no se leen en voz alta" + }, "voicesHeardCount": { "description": "Lectura de progreso en la barra de control de Voces en ajustes: cuántas de las voces listadas el usuario ya ha escuchado. Números sin más, para que se lea bien con 1 — Chrome i18n no tiene formas plurales.", "message": "$heard$ de $total$ escuchadas", diff --git a/_locales/fi/messages.json b/_locales/fi/messages.json index ec685cbbbc..bea776e15e 100644 --- a/_locales/fi/messages.json +++ b/_locales/fi/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Äänestää" }, + "voiceOverriddenBySayPi": { + "description": "Näytetään avustajan asetussivun oman äänivalikoiman yläpuolella, kun Say, Pi -ääni on ottanut toiston käyttöön, joten avustajan korostettu oma ääni ei ole käyttäjän kuulema; $voice$ on Say, Pi -äänen nimi, esimerkiksi Shimmer.", + "message": "Say, Pi puhuu äänellä $voice$ — alla olevat äänet eivät ole käytössä.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Ääni pysähtyi -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Näytetään Äänet-ohjauspalkissa, kun Say, Pi -ääntä ei ole valittu ja avustaja vastaa sen omalla sisäänrakennetulla äänellä; $host$ on avustaja, esimerkiksi Pi.", + "message": "Ääni: $host$:n oma", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Näytetään Äänet-ohjauspalkissa, kun ääntä ei ole valittu avustajalla, jolla ei ole omia sisäänrakennettuja ääniä, joten mitään ei puhuta ääneen.", + "message": "Ei ääntä — vastauksia ei lueta ääneen" + }, "voicesHeardCount": { "description": "Edistymisnäyttö asetusten Äänet-ohjauspalkissa: kuinka monta luetelluista äänistä käyttäjä on jo kuunnellut. Pelkät numerot, jotta se toimii oikein myös arvolla 1 — Chrome i18n:ssa ei ole monikkoja.", "message": "Kuunneltu $heard$/$total$", diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index c5b304d028..d5b4a55b27 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Exprimer" }, + "voiceOverriddenBySayPi": { + "description": "Affiché au-dessus de la grille des voix de l’assistant dans sa page de réglages lorsqu’une voix de Say, Pi prend en charge la lecture, de sorte que la voix native sélectionnée de l’assistant n’est pas celle que l’utilisateur entend ; $voice$ est le nom de la voix de Say, Pi, par exemple Shimmer.", + "message": "Say, Pi parle avec $voice$ — les voix ci-dessous ne sont pas utilisées.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "La voix était en pause -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Affiché dans la barre de contrôle des voix lorsqu’aucune voix de Say, Pi n’est sélectionnée et que l’assistant répond avec l’une de ses propres voix intégrées ; $host$ est le nom de l’assistant, par exemple Pi.", + "message": "Votre voix : celle de $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Affiché dans la barre de contrôle des voix lorsqu’aucune voix n’est sélectionnée pour un assistant qui ne possède aucune voix intégrée, de sorte que rien n’est énoncé.", + "message": "Aucune voix — les réponses ne sont pas lues à voix haute" + }, "voicesHeardCount": { "description": "Indicateur de progression dans la barre de contrôle Voix des paramètres : combien de voix listées l’utilisateur a déjà écoutées. Nombres seuls, pour que cela se lise correctement avec 1 — Chrome i18n n’a pas de formes plurielles.", "message": "$heard$ sur $total$ écoutées", diff --git a/_locales/hi/messages.json b/_locales/hi/messages.json index 8bd6f82409..77555fd38e 100644 --- a/_locales/hi/messages.json +++ b/_locales/hi/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "गूंजना" }, + "voiceOverriddenBySayPi": { + "description": "सहायक के सेटिंग पेज पर अपनी आवाज़ों के ग्रिड के ऊपर दिखाया जाता है, जब Say, Pi की आवाज़ ने बोलने की जगह ले ली हो और सहायक की चुनी हुई मूल आवाज़ वह न हो जो उपयोगकर्ता सुनता है; $voice$ Say, Pi की आवाज़ का नाम है, जैसे Shimmer।", + "message": "Say, Pi $voice$ की आवाज़ में बोल रहा है — नीचे दी गई आवाज़ें उपयोग में नहीं हैं।", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "आवाज रोका -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Voices नियंत्रण पट्टी में तब दिखाया जाता है, जब Say, Pi की कोई आवाज़ चुनी न हो और सहायक अपनी अंतर्निहित आवाज़ों में से किसी एक में जवाब दे; $host$ सहायक का नाम है, जैसे Pi।", + "message": "आपकी आवाज़: $host$ की अपनी", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Voices नियंत्रण पट्टी में तब दिखाया जाता है, जब ऐसे सहायक के लिए कोई आवाज़ चुनी न हो जिसकी अपनी अंतर्निहित आवाज़ें नहीं हैं, इसलिए कुछ भी बोलकर नहीं सुनाया जाता।", + "message": "कोई आवाज़ नहीं — जवाब ज़ोर से नहीं पढ़े जाते" + }, "voicesHeardCount": { "description": "सेटिंग्स के Voices कंट्रोल बार में प्रगति रीडआउट: सूचीबद्ध आवाज़ों में से कितनी को उपयोगकर्ता पहले ही सुन चुका है। सिर्फ़ संख्याएँ, ताकि 1 पर भी सही पढ़े — Chrome i18n में बहुवचन रूप नहीं हैं।", "message": "$total$ में से $heard$ सुना गया", diff --git a/_locales/hr/messages.json b/_locales/hr/messages.json index 3a34d7f67c..4381f739a8 100644 --- a/_locales/hr/messages.json +++ b/_locales/hr/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Odbiti" }, + "voiceOverriddenBySayPi": { + "description": "Prikazuje se iznad mreže izvornih glasova asistenta na stranici s postavkama kada je glas Say, Pi preuzeo reprodukciju, pa označeni izvorni glas asistenta nije ono što korisnik čuje; $voice$ je naziv glasa Say, Pi, npr. Shimmer.", + "message": "Say, Pi govori glasom $voice$ — navedeni glasovi nisu u upotrebi.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Glas zastao -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Prikazuje se na traci za upravljanje glasom kada nije odabran glas Say, Pi i asistent odgovara jednim od svojih ugrađenih glasova; $host$ je naziv asistenta, npr. Pi.", + "message": "Vaš glas: izvorni glas asistenta $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Prikazuje se na traci za upravljanje glasom kada na asistentu bez vlastitih ugrađenih glasova nije odabran nijedan glas, pa se ništa ne izgovara.", + "message": "Bez glasa — odgovori se ne čitaju naglas" + }, "voicesHeardCount": { "description": "Prikaz napretka u traci kontrola Glasovi u postavkama: koliko je navedenih glasova korisnik već preslušao. Goli brojevi, pa se ispravno čita i za 1 — Chrome i18n nema oblike množine.", "message": "Preslušano: $heard$ od $total$", diff --git a/_locales/hu/messages.json b/_locales/hu/messages.json index ecb4ae29d0..4dc689eac2 100644 --- a/_locales/hu/messages.json +++ b/_locales/hu/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Lehangol" }, + "voiceOverriddenBySayPi": { + "description": "Az asszisztens beállítási oldalán, a saját hangjait tartalmazó rács felett jelenik meg, amikor egy Say, Pi-hang veszi át a lejátszást, ezért a felhasználó nem az asszisztens kiemelt natív hangját hallja; a $voice$ a Say, Pi hangjának neve, például Shimmer.", + "message": "A Say, Pi a(z) $voice$ hangján beszél — az alábbi hangok nincsenek használatban.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "A hang megállt -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "A Hangok vezérlősávjában jelenik meg, amikor nincs Say, Pi-hang kiválasztva, és az asszisztens a saját beépített hangjainak egyikén válaszol; a $host$ az asszisztens, például Pi.", + "message": "Saját hang: $host$ hangja", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "A Hangok vezérlősávjában jelenik meg, amikor egy saját beépített hangokkal nem rendelkező asszisztensnél nincs hang kiválasztva, ezért semmi nem hangzik el.", + "message": "Nincs hang — a válaszok nem hangzanak el" + }, "voicesHeardCount": { "description": "Állapotjelző a Beállítások Hangok vezérlősávjában: hány hangot hallgatott már meg a felhasználó a listából. Csak számok, így 1-nél is helyesen olvasható — a Chrome i18n nem támogat többes számot.", "message": "$heard$ / $total$ meghallgatva", diff --git a/_locales/id/messages.json b/_locales/id/messages.json index 4ea2553dbd..9ef6007173 100644 --- a/_locales/id/messages.json +++ b/_locales/id/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Menyuarakan" }, + "voiceOverriddenBySayPi": { + "description": "Ditampilkan di atas daftar suara bawaan asisten pada halaman pengaturan saat suara Say, Pi mengambil alih pemutaran, sehingga suara bawaan asisten yang disorot bukan suara yang didengar pengguna; $voice$ adalah nama suara Say, Pi, misalnya Shimmer.", + "message": "Say, Pi berbicara dengan suara $voice$ — suara di bawah tidak digunakan.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Suara berhenti -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Ditampilkan di bilah kontrol Suara saat tidak ada suara Say, Pi yang dipilih dan asisten menjawab dengan salah satu suara bawaannya; $host$ adalah nama asisten, misalnya Pi.", + "message": "Suara Anda: bawaan $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Ditampilkan di bilah kontrol Suara saat tidak ada suara yang dipilih pada asisten yang tidak memiliki suara bawaan, sehingga tidak ada yang dibacakan.", + "message": "Tanpa suara — balasan tidak dibacakan" + }, "voicesHeardCount": { "description": "Tampilan progres di bilah kontrol Suara pada setelan: berapa banyak dari suara yang terdaftar sudah didengarkan pengguna. Angka tanpa kata, sehingga terbaca benar saat 1 — Chrome i18n tidak memiliki bentuk jamak.", "message": "$heard$ dari $total$ sudah didengar", diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 40b78910cb..ea7c8e0f01 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Voce" }, + "voiceOverriddenBySayPi": { + "description": "Mostrato sopra la griglia delle voci dell'assistente nella pagina delle impostazioni quando una voce di Say, Pi ha preso il controllo della riproduzione, quindi la voce nativa evidenziata dell'assistente non è quella che l'utente sente; $voice$ è il nome della voce di Say, Pi, ad esempio Shimmer.", + "message": "Say, Pi parla con $voice$ — le voci sotto non sono in uso.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Voce in pausa -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Mostrato nella barra dei controlli delle voci quando non è selezionata alcuna voce di Say, Pi e l'assistente risponde usando una delle proprie voci integrate; $host$ è l'assistente, ad esempio Pi.", + "message": "La tua voce: quella di $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Mostrato nella barra dei controlli delle voci quando non è selezionata alcuna voce in un assistente che non dispone di voci integrate, quindi non viene pronunciato nulla.", + "message": "Nessuna voce — le risposte non vengono lette ad alta voce" + }, "voicesHeardCount": { "description": "Indicatore di avanzamento nella barra di controllo Voci delle impostazioni: quante delle voci elencate l'utente ha già ascoltato. Solo numeri, quindi è corretto con 1 — Chrome i18n non ha forme plurali.", "message": "$heard$ di $total$ ascoltate", diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 30fb7b193d..2a2d13153c 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "声をかけます" }, + "voiceOverriddenBySayPi": { + "description": "Say, Pi の音声が再生を引き継いだときに、アシスタントの設定ページにある固有音声一覧の上に表示されます。ユーザーが聞くのは、アシスタントでハイライトされた標準音声ではありません。$voice$ は Say, Pi の音声名です(例: Shimmer)。", + "message": "Say, Pi が $voice$ として話しています — 以下の音声は使用されていません。", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "声が一時停止 - " @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Say, Pi の音声が選択されておらず、アシスタントが内蔵音声のいずれかで応答するときに、音声コントロールバーに表示されます。$host$ はアシスタント名です(例: Pi)。", + "message": "音声: $host$ 固有の音声", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "独自の内蔵音声を持たないアシスタントで音声が選択されておらず、何も読み上げられないときに、音声コントロールバーに表示されます。", + "message": "音声なし — 返信は読み上げられません" + }, "voicesHeardCount": { "description": "設定の「音声」コントロールバーの進捗表示。一覧の音声のうち、ユーザーがすでに聴いた数を示す。数値のみなので、Chrome i18n に複数形がなくても1で正しく読める。", "message": "$total$ 件中 $heard$ 件聴取済み", diff --git a/_locales/ko/messages.json b/_locales/ko/messages.json index ca56a3f30c..e60e612412 100644 --- a/_locales/ko/messages.json +++ b/_locales/ko/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "목소리" }, + "voiceOverriddenBySayPi": { + "description": "Say, Pi 음성이 재생을 대신할 때 어시스턴트 설정 페이지의 자체 음성 목록 위에 표시됩니다. 따라서 어시스턴트에서 강조된 기본 음성은 실제로 들리지 않습니다. $voice$는 Say, Pi 음성 이름입니다(예: Shimmer).", + "message": "Say, Pi가 $voice$ 음성으로 말하고 있습니다 — 아래 음성은 사용되지 않습니다.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "음성 일시 정지 -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Say, Pi 음성을 선택하지 않아 어시스턴트가 자체 내장 음성으로 답변할 때 Voices 컨트롤 바에 표시됩니다. $host$는 어시스턴트 이름입니다(예: Pi).", + "message": "내 음성: $host$ 기본 음성", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "자체 내장 음성이 없는 어시스턴트에서 음성을 선택하지 않았을 때 Voices 컨트롤 바에 표시되며, 아무것도 음성으로 재생되지 않습니다.", + "message": "음성 없음 — 답변을 소리 내어 읽지 않음" + }, "voicesHeardCount": { "description": "설정의 음성 제어 막대에 표시되는 진행 상황으로, 사용자가 목록의 음성 중 얼마나 많이 이미 들어봤는지 보여줍니다. 숫자만 사용하므로 1일 때도 올바르게 읽힙니다. Chrome i18n에는 복수형이 없습니다.", "message": "$total$개 중 $heard$개 들음", diff --git a/_locales/ms/messages.json b/_locales/ms/messages.json index 4512f79752..b375a75043 100644 --- a/_locales/ms/messages.json +++ b/_locales/ms/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Suara" }, + "voiceOverriddenBySayPi": { + "description": "Dipaparkan di atas grid suara pembantu sendiri pada halaman tetapannya apabila suara Say, Pi mengambil alih main balik, jadi suara asli pembantu yang disorot bukan suara yang didengar pengguna; $voice$ ialah nama suara Say, Pi, contohnya Shimmer.", + "message": "Say, Pi sedang bercakap menggunakan $voice$ — suara di bawah tidak digunakan.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Suara dijeda -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Dipaparkan dalam bar kawalan Suara apabila tiada suara Say, Pi dipilih dan pembantu menjawab menggunakan salah satu suara terbina dalamnya sendiri; $host$ ialah nama pembantu, contohnya Pi.", + "message": "Suara anda: suara asal $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Dipaparkan dalam bar kawalan Suara apabila tiada suara dipilih pada pembantu yang tidak mempunyai suara terbina dalam sendiri, jadi tiada apa-apa yang dibacakan.", + "message": "Tiada suara — balasan tidak dibacakan" + }, "voicesHeardCount": { "description": "Paparan kemajuan dalam bar kawalan Suara di tetapan: berapa banyak suara yang disenaraikan sudah didengar oleh pengguna. Nombor semata-mata, jadi ia dibaca betul pada 1 — Chrome i18n tiada bentuk jamak.", "message": "$heard$ daripada $total$ didengar", diff --git a/_locales/nl/messages.json b/_locales/nl/messages.json index 12d02da406..145376a68a 100644 --- a/_locales/nl/messages.json +++ b/_locales/nl/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Stemmen" }, + "voiceOverriddenBySayPi": { + "description": "Wordt boven het eigen stemoverzicht van de assistent op de instellingenpagina weergegeven wanneer een Say, Pi-stem het afspelen heeft overgenomen. De gemarkeerde ingebouwde stem van de assistent is dan niet te horen; $voice$ is de naam van de Say, Pi-stem, bijvoorbeeld Shimmer.", + "message": "Say, Pi spreekt met $voice$ — de onderstaande stemmen worden niet gebruikt.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Stem gepauzeerd -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Wordt in de stemmenbalk weergegeven wanneer er geen Say, Pi-stem is geselecteerd en de assistent antwoordt met een van de eigen ingebouwde stemmen; $host$ is de assistent, bijvoorbeeld Pi.", + "message": "Je stem: die van $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Wordt in de stemmenbalk weergegeven wanneer er geen stem is geselecteerd bij een assistent zonder eigen ingebouwde stemmen, zodat er niets wordt uitgesproken.", + "message": "Geen stem — antwoorden worden niet voorgelezen" + }, "voicesHeardCount": { "description": "Voortgangsweergave in de bedieningsbalk Stemmen in de instellingen: hoeveel van de vermelde stemmen de gebruiker al heeft beluisterd. Kale getallen, zodat het ook bij 1 klopt — Chrome i18n heeft geen meervoudsvormen.", "message": "$heard$ van $total$ gehoord", diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index 8206ed5fa9..1f088c61dd 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Głos" }, + "voiceOverriddenBySayPi": { + "description": "Wyświetlane nad własną siatką głosów asystenta na stronie ustawień, gdy głos Say, Pi przejął odtwarzanie, więc wyróżniony natywny głos asystenta nie jest tym, który słyszy użytkownik; $voice$ to nazwa głosu Say, Pi, np. Shimmer.", + "message": "Say, Pi mówi głosem $voice$ — poniższe głosy nie są używane.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Głos wstrzymany -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Wyświetlane na pasku wyboru głosu, gdy nie wybrano głosu Say, Pi i asystent odpowiada jednym z własnych wbudowanych głosów; $host$ to nazwa asystenta, np. Pi.", + "message": "Twój głos: własny głos $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Wyświetlane na pasku wyboru głosu, gdy nie wybrano głosu w asystencie, który nie ma własnych wbudowanych głosów, więc nic nie jest odczytywane.", + "message": "Brak głosu — odpowiedzi nie są odczytywane na głos" + }, "voicesHeardCount": { "description": "Wskaźnik postępu na pasku sterowania Głosy w ustawieniach: ile z wymienionych głosów użytkownik już odsłuchał. Same liczby, więc brzmi poprawnie przy 1 — Chrome i18n nie ma form liczby mnogiej.", "message": "Odsłuchano: $heard$ z $total$", diff --git a/_locales/pt_BR/messages.json b/_locales/pt_BR/messages.json index c2b1d976da..71fe168b8a 100644 --- a/_locales/pt_BR/messages.json +++ b/_locales/pt_BR/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Voz fora" }, + "voiceOverriddenBySayPi": { + "description": "Exibido acima da grade de vozes do próprio assistente na página de configurações quando uma voz do Say, Pi assumiu a reprodução, portanto a voz nativa destacada do assistente não é a que o usuário ouve; $voice$ é o nome da voz do Say, Pi, por exemplo, Shimmer.", + "message": "Say, Pi está falando com $voice$ — as vozes abaixo não estão em uso.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Voz fez uma pausa -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Exibido na barra de controle Vozes quando nenhuma voz do Say, Pi está selecionada e o assistente responde usando uma de suas próprias vozes integradas; $host$ é o assistente, por exemplo, Pi.", + "message": "Sua voz: a própria voz de $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Exibido na barra de controle Vozes quando nenhuma voz está selecionada em um assistente que não tem vozes integradas próprias, portanto nada é falado.", + "message": "Sem voz — as respostas não são lidas em voz alta" + }, "voicesHeardCount": { "description": "Indicador de progresso na barra de controle de Vozes nas configurações: quantas das vozes listadas o usuário já ouviu. Apenas números, para soar correto com 1 — o Chrome i18n não tem formas de plural.", "message": "$heard$ de $total$ ouvidas", diff --git a/_locales/ro/messages.json b/_locales/ro/messages.json index 205d5e734f..477462f332 100644 --- a/_locales/ro/messages.json +++ b/_locales/ro/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Voce oprit" }, + "voiceOverriddenBySayPi": { + "description": "Afișat deasupra grilei cu vocile proprii ale asistentului în pagina de setări, când o voce Say, Pi a preluat redarea, astfel încât vocea nativă evidențiată a asistentului nu este cea auzită de utilizator; $voice$ este numele vocii Say, Pi, de exemplu Shimmer.", + "message": "Say, Pi vorbește cu $voice$ — vocile de mai jos nu sunt folosite.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Vocea pusă în pauză -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Afișat în bara de control Vocile când nu este selectată nicio voce SayPi, iar asistentul răspunde folosind una dintre vocile sale integrate; $host$ este asistentul, de exemplu Pi.", + "message": "Vocea ta: cea proprie a lui $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Afișat în bara de control Vocile când nu este selectată nicio voce pentru un asistent care nu are propriile voci integrate, astfel încât nimic nu este rostit.", + "message": "Nicio voce — răspunsurile nu sunt citite cu voce tare" + }, "voicesHeardCount": { "description": "Indicator de progres în bara de control Voci din setări: câte dintre vocile listate a ascultat deja utilizatorul. Doar numere, ca să se citească corect la 1 — Chrome i18n nu are forme de plural.", "message": "$heard$ din $total$ ascultate", diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 5918be89b5..402fdf72bd 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Голос" }, + "voiceOverriddenBySayPi": { + "description": "Показывается над сеткой собственных голосов ассистента на странице его настроек, когда голос Say, Pi заменяет голос ассистента, поэтому выбранный встроенный голос ассистента не слышен; $voice$ — название голоса Say, Pi, например Shimmer.", + "message": "Say, Pi говорит голосом $voice$ — голоса ниже не используются.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Голос остановился -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Показывается на панели выбора голоса, когда голос Say, Pi не выбран и ассистент отвечает одним из собственных встроенных голосов; $host$ — название ассистента, например Pi.", + "message": "Ваш голос: собственный голос $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Показывается на панели выбора голоса, когда для ассистента без собственных встроенных голосов голос не выбран, поэтому ничего не озвучивается.", + "message": "Нет голоса — ответы не зачитываются" + }, "voicesHeardCount": { "description": "Индикатор прогресса в панели управления «Голоса» в настройках: сколько голосов из списка пользователь уже прослушал. Только числа, чтобы корректно читалось и при 1 — в Chrome i18n нет форм множественного числа.", "message": "Прослушано: $heard$ из $total$", diff --git a/_locales/sk/messages.json b/_locales/sk/messages.json index cf5dcbdc43..05e683d707 100644 --- a/_locales/sk/messages.json +++ b/_locales/sk/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Vypnúť" }, + "voiceOverriddenBySayPi": { + "description": "Zobrazuje sa nad vlastnou mriežkou hlasov asistenta na stránke nastavení, keď hlas Say, Pi prevezme prehrávanie, takže zvýraznený natívny hlas asistenta nie je tým, ktorý používateľ počuje; $voice$ je názov hlasu Say, Pi, napr. Shimmer.", + "message": "Say, Pi hovorí hlasom $voice$ — hlasy nižšie sa nepoužívajú.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Hlas sa zastavil -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Zobrazuje sa v ovládacom paneli Hlas, keď nie je vybraný hlas Say, Pi a asistent odpovedá jedným zo svojich vlastných vstavaných hlasov; $host$ je asistent, napr. Pi.", + "message": "Váš hlas: vlastný hlas asistenta $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Zobrazuje sa v ovládacom paneli Hlas, keď na asistentovi bez vlastných vstavaných hlasov nie je vybraný žiadny hlas, takže sa nič neprečíta nahlas.", + "message": "Žiadny hlas — odpovede sa nečítajú nahlas" + }, "voicesHeardCount": { "description": "Zobrazenie postupu v ovládacom paneli Hlasov v nastaveniach: koľko z uvedených hlasov si používateľ už vypočul. Holé čísla, aby to znelo správne aj pri 1 — Chrome i18n nemá tvary množného čísla.", "message": "Vypočuté: $heard$ z $total$", diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index 54b9d7e44e..498b60ab4c 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Rösta sig" }, + "voiceOverriddenBySayPi": { + "description": "Visas ovanför assistentens egna röstfält på inställningssidan när en Say, Pi-röst har tagit över uppspelningen, så att assistentens markerade inbyggda röst inte är den användaren hör; $voice$ är namnet på Say, Pi-rösten, till exempel Shimmer.", + "message": "Say, Pi talar med $voice$ – rösterna nedan används inte.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Röst pausade -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Visas i röstfältet när ingen Say, Pi-röst är vald och assistenten svarar med en av sina egna inbyggda röster; $host$ är assistenten, till exempel Pi.", + "message": "Din röst: $host$:s egen", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Visas i röstfältet när ingen röst är vald för en assistent som saknar egna inbyggda röster, så inget läses upp.", + "message": "Ingen röst – svar läses inte upp" + }, "voicesHeardCount": { "description": "Förloppsvisning i inställningarnas kontrollrad för Röster: hur många av de listade rösterna användaren redan har lyssnat på. Bara siffror, så att det läses korrekt även vid 1 — Chrome i18n har inga pluralformer.", "message": "$heard$ av $total$ hörda", diff --git a/_locales/ta/messages.json b/_locales/ta/messages.json index 3994fb5069..7193b7393e 100644 --- a/_locales/ta/messages.json +++ b/_locales/ta/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "குரல் அணைக்கவும்" }, + "voiceOverriddenBySayPi": { + "description": "Say, Pi குரல் இயக்கத்தை எடுத்துக்கொண்டால், உதவியாளரின் அமைப்புப் பக்கத்தில் அதன் சொந்தக் குரல் கட்டத்தின் மேலே காட்டப்படும். ஆகவே, உதவியாளரால் முன்னிலைப்படுத்தப்பட்ட சொந்தக் குரலைப் பயனர் கேட்கமாட்டார்; $voice$ என்பது Say, Pi குரலின் பெயர், எடுத்துக்காட்டாக Shimmer.", + "message": "Say, Pi $voice$ குரலில் பேசுகிறது — கீழுள்ள குரல்கள் பயன்பாட்டில் இல்லை.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "குரல் இடைநிறுத்தப்பட்டது -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Say, Pi குரல் எதுவும் தேர்ந்தெடுக்கப்படாமல், உதவியாளர் அதன் உள்ளமைந்த சொந்தக் குரல்களில் ஒன்றில் பதிலளிக்கும்போது Voices கட்டுப்பாட்டுப் பட்டியில் காட்டப்படும்; $host$ என்பது உதவியாளர், எடுத்துக்காட்டாக Pi.", + "message": "உங்கள் குரல்: $host$ இன் சொந்தக் குரல்", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "தனக்கென உள்ளமைந்த குரல்கள் இல்லாத உதவியாளரில் குரல் எதுவும் தேர்ந்தெடுக்கப்படாதபோது Voices கட்டுப்பாட்டுப் பட்டியில் காட்டப்படும்; ஆகவே எதுவும் பேசப்படாது.", + "message": "குரல் இல்லை — பதில்கள் உரக்கப் படிக்கப்படாது" + }, "voicesHeardCount": { "description": "அமைப்புகள் குரல்கள் கட்டுப்பாட்டு பட்டியில் உள்ள முன்னேற்றக் காட்சி; பட்டியலிலுள்ள குரல்களில் பயனர் ஏற்கெனவே எத்தனை கேட்டுள்ளார் என்பதை காட்டும். வெறும் எண்கள், ஆகவே 1 இலும் சரியாக படிக்கும் — Chrome i18n இல் பன்மை வடிவங்கள் இல்லை.", "message": "$total$ இல் $heard$ கேட்டது", diff --git a/_locales/tl/messages.json b/_locales/tl/messages.json index e35ac86821..706b9b33d9 100644 --- a/_locales/tl/messages.json +++ b/_locales/tl/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Boses" }, + "voiceOverriddenBySayPi": { + "description": "Ipinapakita sa itaas ng sariling grid ng boses ng assistant sa pahina ng mga setting kapag isang boses ng Say, Pi ang pumalit sa pag-playback, kaya hindi ang naka-highlight na katutubong boses ng assistant ang naririnig ng user; ang $voice$ ay pangalan ng boses ng Say, Pi, gaya ng Shimmer.", + "message": "Si Say, Pi ang nagsasalita gamit ang $voice$ — hindi ginagamit ang mga boses sa ibaba.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Tumigil ang boses -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Ipinapakita sa control bar ng Mga Boses kapag walang napiling boses ng SayPi at sumasagot ang assistant gamit ang isa sa sarili nitong built-in na boses; ang $host$ ay ang assistant, gaya ng Pi.", + "message": "Boses mo: sariling boses ni $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Ipinapakita sa control bar ng Mga Boses kapag walang napiling boses sa isang assistant na walang sariling built-in na boses, kaya walang binibigkas.", + "message": "Walang boses — hindi binabasa nang malakas ang mga sagot" + }, "voicesHeardCount": { "description": "Progress readout sa control bar ng Voices sa settings: kung ilang boses sa listahan ang napakinggan na ng user. Hubad na numero, kaya bumabasa nang tama sa 1 — walang plural forms ang Chrome i18n.", "message": "$heard$ sa $total$ narinig", diff --git a/_locales/tr/messages.json b/_locales/tr/messages.json index d113b24c58..f3f557d9ae 100644 --- a/_locales/tr/messages.json +++ b/_locales/tr/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Seslendirmek" }, + "voiceOverriddenBySayPi": { + "description": "Say, Pi sesi oynatmayı devraldığında asistanın ayarlar sayfasındaki kendi ses ızgarasının üzerinde gösterilir; bu nedenle asistanın vurgulanan yerleşik sesi kullanıcının duyduğu ses değildir. $voice$, Shimmer gibi Say, Pi sesinin adıdır.", + "message": "Say, Pi $voice$ olarak konuşuyor — aşağıdaki sesler kullanılmıyor.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Ses durakladı -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Hiçbir Say, Pi sesi seçilmediğinde ve asistan bunun yerine kendi yerleşik seslerinden biriyle yanıt verdiğinde Sesler kontrol çubuğunda gösterilir; $host$, Pi gibi asistanın adıdır.", + "message": "Sesiniz: $host$'un kendi sesi", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Kendi yerleşik sesleri olmayan bir asistanda hiçbir ses seçilmediğinde, dolayısıyla hiçbir şey seslendirilmediğinde Sesler kontrol çubuğunda gösterilir.", + "message": "Ses yok — yanıtlar sesli okunmuyor" + }, "voicesHeardCount": { "description": "Ayarlar Sesler kontrol çubuğundaki ilerleme göstergesi: kullanıcı listelenen seslerin kaçını zaten dinledi. Sadece sayılar, bu yüzden 1 için de doğru okunur — Chrome i18n'de çoğul formlar yoktur.", "message": "$total$ sesten $heard$ dinlendi", diff --git a/_locales/uk/messages.json b/_locales/uk/messages.json index 3d413c987c..500683ed42 100644 --- a/_locales/uk/messages.json +++ b/_locales/uk/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Озвучувати" }, + "voiceOverriddenBySayPi": { + "description": "Показується над сіткою власних голосів асистента на його сторінці налаштувань, коли голос Say, Pi замінив відтворення, тому вибраний вбудований голос асистента не чути; $voice$ — назва голосу Say, Pi, наприклад Shimmer.", + "message": "Say, Pi говорить голосом $voice$ — наведені нижче голоси не використовуються.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Голос зробив паузу -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Показується на панелі керування голосами, коли голос Say, Pi не вибрано, а асистент відповідає одним зі своїх вбудованих голосів; $host$ — назва асистента, наприклад Pi.", + "message": "Ваш голос: власний голос $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Показується на панелі керування голосами, коли на асистенті без власних вбудованих голосів не вибрано голос, тому нічого не озвучується.", + "message": "Без голосу — відповіді не озвучуються" + }, "voicesHeardCount": { "description": "Індикатор прогресу в панелі керування «Голоси» в налаштуваннях: скільки голосів зі списку користувач уже прослухав. Лише числа, тож читається правильно при 1 — у Chrome i18n немає форм множини.", "message": "Прослухано: $heard$ з $total$", diff --git a/_locales/vi/messages.json b/_locales/vi/messages.json index b6ab082cf2..38f6bef1f8 100644 --- a/_locales/vi/messages.json +++ b/_locales/vi/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "Giọng nói tắt" }, + "voiceOverriddenBySayPi": { + "description": "Hiển thị phía trên lưới giọng nói riêng của trợ lý trong trang cài đặt khi giọng Say, Pi thay thế giọng phát, vì vậy giọng gốc được trợ lý đánh dấu không phải là giọng người dùng nghe thấy; $voice$ là tên giọng Say, Pi, ví dụ Shimmer.", + "message": "Say, Pi đang nói bằng giọng $voice$ — các giọng bên dưới không được dùng.", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "Giọng nói dừng lại -" @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "Hiển thị trong thanh điều khiển Giọng nói khi chưa chọn giọng Say, Pi và trợ lý trả lời bằng một trong các giọng tích hợp riêng; $host$ là tên trợ lý, ví dụ Pi.", + "message": "Giọng của bạn: giọng riêng của $host$", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "Hiển thị trong thanh điều khiển Giọng nói khi chưa chọn giọng trên trợ lý không có giọng tích hợp riêng, nên không có nội dung nào được đọc.", + "message": "Không có giọng — câu trả lời không được đọc thành tiếng" + }, "voicesHeardCount": { "description": "Chỉ báo tiến độ trong thanh điều khiển Giọng nói của phần cài đặt: số giọng trong danh sách mà người dùng đã nghe. Chỉ là số nên đọc đúng khi là 1 vì Chrome i18n không có dạng số nhiều.", "message": "Đã nghe $heard$ trên $total$", diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index d11178d952..f4b926a008 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -1627,6 +1627,16 @@ "description": "Label for when text-to-speech is disabled", "message": "发出声音" }, + "voiceOverriddenBySayPi": { + "description": "当 Say, Pi 的语音接管播放时,显示在助手设置页自身语音网格上方;助手突出显示的原生语音并不是用户听到的语音;$voice$ 是 Say, Pi 的语音名称,例如 Shimmer。", + "message": "Say, Pi 正在使用 $voice$ 朗读 — 以下语音未在使用。", + "placeholders": { + "voice": { + "content": "$1", + "example": "Shimmer" + } + } + }, "voicePaused": { "description": "Text shown when voice is paused due to insufficient credits", "message": "声音暂停 - " @@ -1787,6 +1797,20 @@ } } }, + "voicesFallbackHostVoice": { + "description": "当未选择 SayPi 语音且助手使用其自带语音回答时,显示在语音控制栏中;$host$ 是助手名称,例如 Pi。", + "message": "你的语音:$host$ 自带语音", + "placeholders": { + "host": { + "content": "$1", + "example": "Pi" + } + } + }, + "voicesFallbackNoVoice": { + "description": "当未在没有自带语音的助手上选择语音时,显示在语音控制栏中,此时不会朗读任何内容。", + "message": "无语音 — 不会朗读回复" + }, "voicesHeardCount": { "description": "设置的“语音”控制栏中的进度读数:用户已试听过列表中多少个语音。为纯数字形式,因此在为 1 时也能读起来自然——Chrome i18n 没有复数形式。", "message": "已听 $heard$ / $total$", diff --git a/entrypoints/settings/tabs/voices/voices-controller.ts b/entrypoints/settings/tabs/voices/voices-controller.ts index 28552baf80..9b4d43f90d 100644 --- a/entrypoints/settings/tabs/voices/voices-controller.ts +++ b/entrypoints/settings/tabs/voices/voices-controller.ts @@ -930,6 +930,10 @@ export class VoicesController { jump.appendChild(arrow); jump.addEventListener("click", () => this.jumpToCurrent()); top.appendChild(jump); + } else { + // Nothing of ours is speaking. Say what is (#599) — the page's one + // statement about the present, in the slot the eye already reads for it. + top.appendChild(this.renderFallbackVoice(vm)); } const chip = document.createElement("button"); @@ -2080,6 +2084,32 @@ export class VoicesController { this.updateControlBar(); } + /** + * What speaks when no row is in use — the sentence that stands where + * `Your voice: … ↗` would be (#599). + * + * The two cases are genuinely different outcomes, not two phrasings of one: + * a host that ships its own voices answers in one of them, and a host that + * doesn't stays silent. Which host this is comes from `host.hasOwnVoice`, NOT + * from `hasBuiltins`: the API doesn't mark Pi's built-ins `default` in + * practice, so the catalog-derived signal is false even on Pi — measured on + * the live host, where it produced exactly the wrong sentence. + */ + private renderFallbackVoice(vm: StudioViewModel): HTMLElement { + const line = document.createElement("span"); + line.classList.add("voice-fallback"); + if (vm.host.hasOwnVoice) { + line.classList.add("voice-fallback-host"); + // No data-i18n: substituted ($host$) text, which replaceI18n would strip. + line.textContent = getMessage("voicesFallbackHostVoice", [vm.host.label]); + } else { + line.classList.add("voice-fallback-none"); + line.setAttribute("data-i18n", "voicesFallbackNoVoice"); + line.textContent = getMessage("voicesFallbackNoVoice"); + } + return line; + } + private jumpToCurrent(): void { const data = this.cache.get(this.activeHost); const currentId = data?.current?.id; diff --git a/entrypoints/settings/tabs/voices/voices-view-model.ts b/entrypoints/settings/tabs/voices/voices-view-model.ts index e512317eaf..79701ff8f0 100644 --- a/entrypoints/settings/tabs/voices/voices-view-model.ts +++ b/entrypoints/settings/tabs/voices/voices-view-model.ts @@ -50,13 +50,26 @@ export const VOICE_HOSTS: ReadonlyArray<{ * that would do nothing. */ menuCap?: number; + /** + * Does this assistant speak on its own, with no voice of ours selected? + * + * Decides what the control bar says when nothing here is in use: Pi answers + * in her own voice, Claude says nothing at all. Declared rather than derived + * from the catalog — the API does not mark a host's built-ins `default` in + * practice, so `hasBuiltins` is false even on Pi and reading it here got the + * sentence backwards on the live host. `voices-view-model.spec` pins this + * against `audioProviders.getDefaultForChatbot`, which is the audio layer's + * own answer to the same question. + */ + hasOwnVoice: boolean; }> = [ - { id: "pi", label: "Pi", logo: "/icons/logos/pi.png" }, + { id: "pi", label: "Pi", logo: "/icons/logos/pi.png", hasOwnVoice: true }, { id: "claude", label: "Claude", logo: "/icons/logos/claude.png", menuCap: CLAUDE_MENU_CAP, + hasOwnVoice: false, }, ]; diff --git a/entrypoints/settings/tabs/voices/voices.css b/entrypoints/settings/tabs/voices/voices.css index 1cc32e3f0d..998d89ec7c 100644 --- a/entrypoints/settings/tabs/voices/voices.css +++ b/entrypoints/settings/tabs/voices/voices.css @@ -242,6 +242,14 @@ .voice-compare-swap:hover { color: #241d14; } +/* The same slot as `Your voice ↗`, saying what speaks when no row does (#599). + A statement, not a control — nothing to press, so nothing that looks + pressable: no pointer, and the bar's quieter ink, since the page's business + is still the rail below it. */ +.voice-fallback { + padding: 2px 0; + opacity: 0.72; +} /* The chip is the arming rule made visible AND its permanent escape hatch: it lights when arrows are auditioning, and pressing it turns focus-plays off for good. One green, one meaning — now. */ diff --git a/src/chatbots/PiVoiceSettings.ts b/src/chatbots/PiVoiceSettings.ts index 3c7be09485..1c4879af21 100644 --- a/src/chatbots/PiVoiceSettings.ts +++ b/src/chatbots/PiVoiceSettings.ts @@ -1,6 +1,7 @@ import getMessage from "../i18n"; import { openSettings } from "../popup/popupopener"; import { VoiceSelector } from "../tts/VoiceMenu"; +import { SpeechSynthesisVoiceRemote } from "../tts/SpeechModel"; import { Chatbot } from "./Chatbot"; import { UserPreferenceModule } from "../prefs/PreferenceModule"; @@ -60,20 +61,62 @@ export class PiVoiceSettings extends VoiceSelector { this.addIdVoiceMenu(element); this.ensureSettingsDoor(); this.observeSettingsGrid(); + // The door needs no data, so it paints immediately — but the override + // notice does, and nothing else fetches it on this surface. The base only + // renders on an auth or preference CHANGE, so without this the notice was + // absent on every first load: measured on the live host with a voice + // selected, door present, notice missing. + void this.refreshOverrideNotice(); } + /** + * The SayPi voice currently overriding Pi's own, as last rendered. + * + * A render cache, not the state of record — the stored preference is still + * the only source of truth, fetched by the base's `refreshMenu`. It exists + * because Pi's React drops our foreign children and the re-injection runs + * from a MutationObserver callback, which is synchronous and has no chance to + * re-read storage: without this, a healed notice would come back blank. + */ + private overridingVoice: SpeechSynthesisVoiceRemote | null = null; + getId(): string { return "saypi-voice-settings"; } - // Also reached on auth changes via the base refreshMenu → re-ensure the door, - // never draw inline voice rows (door-first). - protected override renderMenu(): void { + // Also reached on auth changes via the base refreshMenu → re-ensure SayPi's + // marks on the grid, never draw inline voice rows (door-first). + protected override renderMenu( + _voices: SpeechSynthesisVoiceRemote[], + storedVoice: SpeechSynthesisVoiceRemote | null + ): void { + this.overridingVoice = storedVoice; this.ensureSettingsDoor(); } - // Door-only surface: no per-row selection to reflect. - protected override applySelectedVoice(): void {} + // No per-row selection to reflect on a door-only surface — but the grid's + // whole meaning changes when a SayPi voice takes over, so that much is. + protected override applySelectedVoice( + voice: SpeechSynthesisVoiceRemote | null + ): void { + this.overridingVoice = voice; + this.ensureSettingsDoor(); + } + + /** + * Read the stored voice and paint the notice from it. Only the preference is + * needed — not the catalog — so this asks for that alone rather than going + * through the base's full gather-then-render. Never rejects: a surface that + * can't say what's speaking should still show its door. + */ + private async refreshOverrideNotice(): Promise { + try { + const voice = await this.userPreferences.getVoice(this.chatbot); + this.applySelectedVoice(voice ?? null); + } catch (error) { + console.debug("[SayPi] Could not read the voice in use for Pi", error); + } + } /** * Inject the "More voices" door as the last card of Pi's settings grid, @@ -82,6 +125,7 @@ export class PiVoiceSettings extends VoiceSelector { */ private ensureSettingsDoor(): void { const grid = this.element; + this.ensureOverrideNotice(); if (grid.querySelector(".saypi-more-voices")) return; // Clone a native card for styling. If Pi hasn't rendered its card buttons // yet (empty grid), wait: observeSettingsGrid re-fires when they arrive, so @@ -91,6 +135,45 @@ export class PiVoiceSettings extends VoiceSelector { grid.appendChild(buildMoreVoicesDoor(template)); } + /** + * State, in a sentence, above Pi's cards: which SayPi voice has taken over, + * and that the cards below aren't the ones speaking (#600). + * + * Lives INSIDE the grid, as its first cell spanning every column, rather than + * as a sibling above it — a sibling is outside what `observeSettingsGrid` + * watches, so a React re-render would drop it for good, while a child heals + * on the same mutation that heals the door. + * + * Pi's own highlighted card is left highlighted: it is a real preference, and + * it IS what would speak if the SayPi voice were switched off. It just stops + * looking like the answer to "what do I sound like right now". + */ + private ensureOverrideNotice(): void { + const grid = this.element; + const existing = grid.querySelector( + ".saypi-voice-override-notice" + ); + if (!this.overridingVoice) { + existing?.remove(); + grid.classList.remove("saypi-voices-overridden"); + return; + } + grid.classList.add("saypi-voices-overridden"); + const text = getMessage("voiceOverriddenBySayPi", [ + this.overridingVoice.name, + ]); + if (existing) { + // No data-i18n anywhere on it: the text is substituted, and replaceI18n + // would erase the voice name on the next settings paint. + if (existing.textContent !== text) existing.textContent = text; + return; + } + const notice = document.createElement("p"); + notice.className = "saypi-voice-override-notice"; + notice.textContent = text; + grid.prepend(notice); + } + /** * Pi's React may re-render the grid (e.g. selecting a voice) and drop our * foreign door child. Re-inject on any grid childList change; idempotent, so diff --git a/src/styles/voices.scss b/src/styles/voices.scss index 12c8c3a8ce..2b70220be0 100644 --- a/src/styles/voices.scss +++ b/src/styles/voices.scss @@ -29,6 +29,21 @@ button.saypi-more-voices { opacity: 0.7; font-size: 0.9em; } +// What's actually speaking, stated above the host's own voice cards (#600). +// Spans every column so it reads as a line, not a card — the grid is the +// host's, and its column count is whatever the host's breakpoint says. +.saypi-voice-override-notice { + grid-column: 1 / -1; + margin: 0 0 0.25rem; + font-size: 0.9em; + opacity: 0.75; +} +// The glance-level half of the same statement: the host's highlighted card +// stops claiming to be the voice you hear. Dimmed, never disabled — it is +// still a real preference, and still what would speak with SayPi's voice off. +.saypi-voices-overridden > button:not(.saypi-more-voices) { + opacity: 0.5; +} // ▶ free-sample preview affordance in the in-page voice menus (design §4). // A target of its own, separate from selecting the row. Fully SayPi-styled so // it renders identically on every host (never relying on the host's Tailwind), diff --git a/test/chatbots/PiVoiceSettings-more-voices-door.spec.ts b/test/chatbots/PiVoiceSettings-more-voices-door.spec.ts index 870dfdc70b..9a2f3bc30c 100644 --- a/test/chatbots/PiVoiceSettings-more-voices-door.spec.ts +++ b/test/chatbots/PiVoiceSettings-more-voices-door.spec.ts @@ -1,4 +1,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; // ConfigModule reads injected env at import time; stub it (mirrors the sibling specs). vi.mock("../../src/ConfigModule", () => ({ @@ -18,6 +20,15 @@ vi.mock("../../src/JwtManager", () => ({ }), })); +// The shared chrome.i18n mock returns the KEY and drops substitutions, which +// would hide the one thing the notice has to get right: the voice's name +// reaching the sentence. Render key + substitutions instead; the English +// wording itself is pinned by the locale-backed copy contract specs. +vi.mock("../../src/i18n", () => ({ + default: (key: string, subs: string[] = []) => + subs.length ? `${key}:${subs.join("|")}` : key, +})); + const openSettingsMock = vi.fn(); vi.mock("../../src/popup/popupopener", () => ({ openSettings: (...args: unknown[]) => openSettingsMock(...args), @@ -175,3 +186,80 @@ describe("PiVoiceSettings — 'More voices' door on Pi's Voice settings page (#4 expect(d!.classList.contains("rounded-[10px]")).toBe(true); // ...and native-styled (cloned) }); }); + +/** + * Pi's grid is the surface Pi now sends people to for voice choice, so it is + * where a user checks what they sound like. When a SayPi voice has overridden + * that choice, saying nothing here leaves the highlighted native card claiming + * a job it isn't doing (#600). + */ +describe("PiVoiceSettings — what's actually speaking", () => { + const notice = (grid: HTMLElement) => + grid.querySelector(".saypi-voice-override-notice"); + + const voice = (name: string) => + ({ id: name.toLowerCase(), name, powered_by: "OpenAI" }) as any; + + it("says which SayPi voice is speaking, and that Pi's cards aren't in use", () => { + const grid = buildSettingsGrid(); + const settings = makeSettings(grid); + settings.renderMenu([], voice("Shimmer")); + expect(notice(grid)).not.toBeNull(); + expect(notice(grid)!.textContent).toContain("Shimmer"); + // The glance-level half: the native cards stop reading as active. + expect(grid.classList.contains("saypi-voices-overridden")).toBe(true); + }); + + it("stays out of the way when Pi's own voice is the one speaking", () => { + const grid = buildSettingsGrid(); + const settings = makeSettings(grid); + settings.renderMenu([], null); + expect(notice(grid)).toBeNull(); + expect(grid.classList.contains("saypi-voices-overridden")).toBe(false); + // The door is SayPi's only mark on an un-overridden grid. + expect(door(grid)).not.toBeNull(); + }); + + it("follows a voice chosen elsewhere, without a repopulate", () => { + const grid = buildSettingsGrid(); + const settings = makeSettings(grid); + settings.renderMenu([], null); + settings.applySelectedVoice(voice("Marin")); + expect(notice(grid)!.textContent).toContain("Marin"); + settings.applySelectedVoice(null); + expect(notice(grid)).toBeNull(); + expect(grid.classList.contains("saypi-voices-overridden")).toBe(false); + }); + + it("is idempotent, and re-appears if Pi's re-render drops it", () => { + const grid = buildSettingsGrid(); + const settings = makeSettings(grid); + settings.renderMenu([], voice("Shimmer")); + settings.renderMenu([], voice("Shimmer")); + expect(grid.querySelectorAll(".saypi-voice-override-notice").length).toBe(1); + + notice(grid)!.remove(); // React drops the foreign child + settings.ensureSettingsDoor(); // the same mutation path that heals the door + expect(grid.querySelectorAll(".saypi-voice-override-notice").length).toBe(1); + }); +}); + +describe("PiVoiceSettings — painting the notice on first load", () => { + it("reads the voice in use when the surface is built, not only on a change", () => { + // The base renders on an auth or preference CHANGE. Neither happens on an + // ordinary page load, so a surface that waits for one shows its door and + // nothing else — which is exactly what the live host showed with a voice + // selected. Verified against the real construction path. + const source = readFileSync( + resolve(__dirname, "..", "..", "src/chatbots/PiVoiceSettings.ts"), + "utf8" + ); + const constructorBody = source.match(/constructor\([\s\S]*?\n {2}\}/)?.[0]; + expect(constructorBody).toMatch(/refreshOverrideNotice\(\)/); + const refresh = source.match( + /refreshOverrideNotice\(\): Promise \{[\s\S]*?\n {2}\}/ + )?.[0]; + expect(refresh).toMatch(/getVoice\(this\.chatbot\)/); + expect(refresh).toMatch(/applySelectedVoice\(/); + }); +}); diff --git a/test/settings/tabs/voices-controller.spec.tsx b/test/settings/tabs/voices-controller.spec.tsx index cbf13dd346..a3b6cd439f 100644 --- a/test/settings/tabs/voices-controller.spec.tsx +++ b/test/settings/tabs/voices-controller.spec.tsx @@ -794,6 +794,45 @@ describe("VoicesController — the control bar", () => { const { container } = await mount(deps); expect(rowOf(container, "voice1")).toBeNull(); expect(q(container, ".voice-your-voice")).toBeNull(); + // …but it still says what speaks: a built-in is the host's own voice. + expect(q(container, ".voice-fallback-host")).not.toBeNull(); + }); + + it("names the host's own voice as what speaks when none is chosen", async () => { + // The gap #599 is about: no SayPi voice selected, so Pi answers in her own + // voice — and until now the bar said nothing at all. + const deps = makeDeps({ + pi: [mkVoice("voice1", { default: true, name: "Aria" }), mkVoice("marin")], + piCurrent: null, + }); + const { container } = await mount(deps); + expect(q(container, ".voice-your-voice")).toBeNull(); + const fallback = q(container, ".voice-fallback-host")!; + expect(fallback).not.toBeNull(); + // Substituted ($host$) text must NOT carry data-i18n (replaceI18n clobber). + expect(fallback.dataset.i18n).toBeUndefined(); + }); + + it("says replies aren't read aloud on a host with no voice of its own", async () => { + // Claude ships no built-ins, so "nothing selected" means silence, not a + // fallback voice — a different sentence, from the same missing state. + const deps = makeDeps({ claude: [mkVoice("marin")], claudeCurrent: null }); + const { container } = await mount(deps, { initialHost: "claude" }); + expect(q(container, ".voice-fallback-host")).toBeNull(); + const fallback = q(container, ".voice-fallback-none")!; + expect(fallback).not.toBeNull(); + // No substitution here, so this one is a plain data-i18n key. + expect(fallback.dataset.i18n).toBe("voicesFallbackNoVoice"); + }); + + it("drops the fallback line once a voice is chosen", async () => { + const deps = makeDeps({ + pi: [mkVoice("voice1", { default: true, name: "Aria" }), mkVoice("marin")], + piCurrent: mkVoice("marin"), + }); + const { container } = await mount(deps); + expect(q(container, ".voice-fallback")).toBeNull(); + expect(q(container, ".voice-your-voice")).not.toBeNull(); }); it("carries the keyboard hint, which is the page's only instruction", async () => { diff --git a/test/settings/tabs/voices-studio-copy.spec.ts b/test/settings/tabs/voices-studio-copy.spec.ts index 7db9aaafeb..604ee073ed 100644 --- a/test/settings/tabs/voices-studio-copy.spec.ts +++ b/test/settings/tabs/voices-studio-copy.spec.ts @@ -1,4 +1,6 @@ import { describe, it, expect } from "vitest"; +import { VOICE_HOSTS } from "../../../entrypoints/settings/tabs/voices/voices-view-model"; +import { audioProviders } from "../../../src/tts/SpeechModel"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; @@ -74,6 +76,21 @@ describe("the rail says what it is", () => { expect(en.voicesKeyboardHint.message).toMatch(/^Press /); }); + it("says what speaks when no voice of ours does", () => { + // The bar's one statement about the present. Two different outcomes, so + // two sentences: a host with its own voices answers in one of them; a host + // without stays quiet. One line covering both would have to be vague about + // whichever it wasn't written for. + expect(en.voicesFallbackHostVoice?.message).toBe("Your voice: $host$'s own"); + expect(en.voicesFallbackNoVoice?.message).toBe( + "No voice — replies aren't read aloud" + ); + // The silent case names no host, so it stays a plain data-i18n key. + expect(en.voicesFallbackNoVoice.message).not.toMatch(/\$.+\$/); + expect(controllerSrc).toMatch(/voicesFallbackHostVoice/); + expect(controllerSrc).toMatch(/voicesFallbackNoVoice/); + }); + it("labels the arming toggle in the second person, not as a mode", () => { // `Arrow keys play` names the mechanism; `Play as you move` names what // happens to YOU, and it reads straight into the hint line beneath it. @@ -85,6 +102,7 @@ describe("the rail says what it is", () => { describe("every substituted string declares its placeholders", () => { const substituted = [ ["voicesYourVoice", 1], + ["voicesFallbackHostVoice", 1], ["voicesSwitchBackTo", 1], ["voicesNoSampleGroup", 1], ["voicesNowPlaying", 1], @@ -384,3 +402,26 @@ describe("the two things the ink cannot say out loud", () => { expect(controllerSrc).toMatch(/aria-describedby/); }); }); + +/** + * `hasOwnVoice` decides which sentence the control bar shows when nothing of + * ours is in use, and getting it backwards is silent — the bar still renders, + * it just lies. The audio layer already answers the same question when it picks + * a default provider for a host, so pin the two together rather than trusting + * a hand-maintained flag. + * + * This is the check that would have caught reading it from `hasBuiltins`: the + * API doesn't mark Pi's built-ins `default`, so that signal is false on Pi and + * the studio told live users their replies weren't read aloud at all. + */ +describe("the studio agrees with the audio layer about who speaks", () => { + it.each(VOICE_HOSTS.map((host) => [host.id, host.hasOwnVoice] as const))( + "%s: hasOwnVoice=%s matches its default audio provider", + (id, hasOwnVoice) => { + const speaksWithoutUs = + audioProviders.getDefaultForChatbot(id) !== audioProviders.SayPi && + audioProviders.getDefaultForChatbot(id) !== audioProviders.None; + expect(hasOwnVoice).toBe(speaksWithoutUs); + } + ); +});