From 7383bdb403cac93bae4a6800c550bf981fbe07e0 Mon Sep 17 00:00:00 2001 From: prosolis <5590409+prosolis@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:40:41 -0700 Subject: [PATCH] An American voice reading "comum" from the one button nobody had told MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported as "Portuguese docs still use English voices". Not the server: piper-pt is up, has its own model loaded, and synthesizes "Ao fim da tarde fomos ver o mar" into 63KB of RIFF when asked directly. The request never asked it. The word card's pronounce buttons called speak(word) with no locale, which falls back to detectLang — a test that can only tell Han characters from letters. "comum", "carro", "gaivotas" all read as English, so every read-aloud from a word card in a Portuguese document came out in the en_US voice, and Piper's Portuguese sat idle behind a route nothing pointed at. The card was the last surface still guessing. The selection bubble and the garden both pass a locale decided from the document's verdict; WordCard was never given the document, so it had nothing to pass. It takes one now. This is the same mistake the "also in her language" block three elements below it was built to avoid — its comment says so outright, that an English voice reading "comum" is the thing that block exists to prevent — arriving through the button directly above it. A rule held in one place and not the other. Verified by intercepting /api/tts: both paths now ask for pt-PT. Claude-Session: https://claude.ai/code/session_01GJHNvirh7Hzhc9RL3HAvz7 --- web/src/components/Editor/EditorCore.tsx | 1 + web/src/components/Editor/WordCard.tsx | 26 +++++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/web/src/components/Editor/EditorCore.tsx b/web/src/components/Editor/EditorCore.tsx index b596914..142103f 100644 --- a/web/src/components/Editor/EditorCore.tsx +++ b/web/src/components/Editor/EditorCore.tsx @@ -1582,6 +1582,7 @@ export function EditorCore({ saved={wordInfo.saved} onToggleSave={toggleSaveWord} pinyin={wordInfo.pinyin} + lang={docLocale(wordInfo.word, docLang)} style={{ top: wordInfo.top, left: wordInfo.left }} onReplace={replaceWord} /> diff --git a/web/src/components/Editor/WordCard.tsx b/web/src/components/Editor/WordCard.tsx index 4649839..a4c6473 100644 --- a/web/src/components/Editor/WordCard.tsx +++ b/web/src/components/Editor/WordCard.tsx @@ -22,11 +22,31 @@ interface Props { // is spelled in letters, and the slashes would say something untrue about it // in the one place a learner is looking for the truth about pronunciation. pinyin?: string + // The locale to pronounce the headword in — the document's language, decided + // by the caller (see docLang in audio/speech.ts). + // + // It has to be passed rather than guessed. `speak` falls back to detecting the + // script, and that test can only tell Han characters from letters: it reads + // "comum" and "casa" as English, so every read-aloud in a Portuguese document + // came out in the English voice. That is the same mistake the "also in" block + // below was built to avoid, arriving through the one button nobody had told + // about the document. + lang?: string style: React.CSSProperties onReplace: (synonym: string) => void } -export function WordCard({ word, info, loading, saved, onToggleSave, pinyin, style, onReplace }: Props) { +export function WordCard({ + word, + info, + loading, + saved, + onToggleSave, + pinyin, + lang, + style, + onReplace, +}: Props) { const t = usePack() const definitions = info?.definitions ?? [] const synonyms = info?.synonyms ?? [] @@ -90,7 +110,7 @@ export function WordCard({ word, info, loading, saved, onToggleSave, pinyin, sty <>