diff --git a/web/src/audio/speech.ts b/web/src/audio/speech.ts index f540e56..3d7cda8 100644 --- a/web/src/audio/speech.ts +++ b/web/src/audio/speech.ts @@ -59,12 +59,23 @@ function speakWebSpeech(text: string, lang: string): void { synth.speak(utterance) } +// detectLang guesses a BCP-47 locale from the text so the right Piper voice is +// chosen. Any Han/kana character routes to Chinese (zh-CN) — a selection is +// essentially never mixed at the level that matters, and reading a Chinese +// passage with the English voice spells each character out ("Chinese letter…"). +// Everything else defaults to US English, the language she's learning. +const CJK = /[぀-ヿ㐀-䶿一-鿿豈-﫿ヲ-゚]/ +export function detectLang(text: string): string { + return CJK.test(text) ? 'zh-CN' : 'en-US' +} + // speak reads `text` aloud, cancelling anything already in flight so rapid taps -// don't queue up. `lang` defaults to US English (the language she's learning); -// pass a zh locale to hear a Chinese passage. It tries the server's neural voice -// first and silently falls back to the browser voice if that's unavailable (route -// off, network error, or a 404 for a language with no configured voice). -export function speak(text: string, lang = 'en-US'): void { +// don't queue up. `lang` defaults to a guess from the text (Chinese vs English) +// so callers can just pass the selection; pass an explicit locale to override. +// It tries the server's neural voice first and silently falls back to the browser +// voice if that's unavailable (route off, network error, or a 404 for a language +// with no configured voice). +export function speak(text: string, lang = detectLang(text)): void { if (!text.trim()) return stopCurrent() const seq = ++requestSeq