An American voice reading "comum" from the one button nobody had told

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
This commit is contained in:
prosolis
2026-07-29 18:40:41 -07:00
parent 3cc23b8ea4
commit 7383bdb403
2 changed files with 24 additions and 3 deletions
+1
View File
@@ -1582,6 +1582,7 @@ export function EditorCore({
saved={wordInfo.saved} saved={wordInfo.saved}
onToggleSave={toggleSaveWord} onToggleSave={toggleSaveWord}
pinyin={wordInfo.pinyin} pinyin={wordInfo.pinyin}
lang={docLocale(wordInfo.word, docLang)}
style={{ top: wordInfo.top, left: wordInfo.left }} style={{ top: wordInfo.top, left: wordInfo.left }}
onReplace={replaceWord} onReplace={replaceWord}
/> />
+23 -3
View File
@@ -22,11 +22,31 @@ interface Props {
// is spelled in letters, and the slashes would say something untrue about it // 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. // in the one place a learner is looking for the truth about pronunciation.
pinyin?: string 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 style: React.CSSProperties
onReplace: (synonym: string) => void 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 t = usePack()
const definitions = info?.definitions ?? [] const definitions = info?.definitions ?? []
const synonyms = info?.synonyms ?? [] const synonyms = info?.synonyms ?? []
@@ -90,7 +110,7 @@ export function WordCard({ word, info, loading, saved, onToggleSave, pinyin, sty
<> <>
<button <button
type="button" type="button"
onClick={() => speak(word)} onClick={() => speak(word, lang)}
aria-label={`Pronounce ${word}`} aria-label={`Pronounce ${word}`}
title={t.editor.readAloud} title={t.editor.readAloud}
className="flex h-7 w-7 items-center justify-center rounded-full text-sm" className="flex h-7 w-7 items-center justify-center rounded-full text-sm"
@@ -104,7 +124,7 @@ export function WordCard({ word, info, loading, saved, onToggleSave, pinyin, sty
slowing the tape, so it stays a voice rather than a groan. */} slowing the tape, so it stays a voice rather than a groan. */}
<button <button
type="button" type="button"
onClick={() => speak(word, undefined, true)} onClick={() => speak(word, lang, true)}
aria-label={`Pronounce ${word} slowly`} aria-label={`Pronounce ${word} slowly`}
title={t.editor.readSlowly} title={t.editor.readSlowly}
className="flex h-7 w-7 items-center justify-center rounded-full text-sm" className="flex h-7 w-7 items-center justify-center rounded-full text-sm"