import type { VoiceInfo } from "@rakazo/contracts"; import { defaultVoiceForLocale, GERMAN_VOICE_PRESETS, voicesForLocale } from "@rakazo/core"; /** English presets when the local voice server is unreachable during onboarding. */ const ENGLISH_ONBOARDING_VOICES: VoiceInfo[] = [ { id: "af_heart", label: "Heart", description: "warm" }, { id: "af_bella", label: "Bella", description: "expressive" }, { id: "af_sky", label: "Sky", description: "bright" }, { id: "af_nova", label: "Nova", description: "energetic" }, { id: "am_adam", label: "Adam", description: "clear" }, { id: "am_michael", label: "Michael", description: "steady" }, ]; const OFFLINE_CATALOG = [...GERMAN_VOICE_PRESETS, ...ENGLISH_ONBOARDING_VOICES]; const KOKORO_LANGUAGE_PREFIXES: Record = { de: "d", en: "ab", es: "e", fr: "f", hi: "h", it: "i", ja: "j", pt: "p", zh: "z", }; function listedVoicesSpeakLocale(listed: VoiceInfo[], locale: string): boolean { const language = locale.toLowerCase().split("-")[0] ?? ""; const prefixes = KOKORO_LANGUAGE_PREFIXES[language]; if (!prefixes) return listed.length > 0; return listed.some((voice) => { const prefix = /^([a-z])[fm]_/.exec(voice.id)?.[1]; return Boolean(prefix && prefixes.includes(prefix)); }); } /** Locale-aware voice choices for onboarding when listing from the server fails. */ export function offlineOnboardingVoices(locale: string): VoiceInfo[] { return voicesForLocale(OFFLINE_CATALOG, locale).slice(0, 6); } /** Prefer server voices that speak the UI language; otherwise use offline presets. */ export function onboardingVoicesFromList(listed: VoiceInfo[], locale: string): VoiceInfo[] { if (listedVoicesSpeakLocale(listed, locale)) { return voicesForLocale(listed, locale).slice(0, 6); } return offlineOnboardingVoices(locale); } export function defaultOnboardingVoiceId(locale: string, voices: VoiceInfo[]): string { return defaultVoiceForLocale(voices, locale)?.id ?? ""; }