* Fix first-run onboarding UX: naming, locale, voices, Heute route, mobile drawer Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix biome import order and formatting Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix biome formatting on OoStage and E2E specs Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix E2E specs for onboarding name step and Heute reload route Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix German onboarding heading matcher in E2E Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix German signup E2E, Escape menu stacking, and Heute rhythm click Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Make onboarding E2E helpers locale-aware for German UI Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix completeOnboarding language skip for Skip · locale label Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Use language-step Skip · locale button in completeOnboarding Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix German E2E: Nachricht composer, voice loading wait, Heute rhythm Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Stabilize Heute reload URL and German voice E2E assertions Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Fix Heute bootstrap redirect and German onboarding voices When Kokoro lists no locale-matched voices, onboarding now uses offline presets instead of showing English-only options. Shell bootstrap skips bot redirects while the user is on /app/heute. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Relax German voice E2E name matching for voice labels Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Harden Heute route and German voice onboarding E2E Rebased onto main with Expo SDK 57 typecheck fixes. Voice options expose stable test ids; Shell keeps /app/heute across bootstrap and redirects legacy /app/heute bot URLs. Heute coach E2E asserts URL after reload. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Format Shell after Heute route helper Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
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<string, string> = {
|
|
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 ?? "";
|
|
}
|