/** * Maps 2-letter ISO 639-1 codes (`en`, `ru`, `ko` — what * `@djangocfg/i18n` exposes via `useLocale()`) to BCP-47 tags * (`en-US`, `ru-RU`, `ko-KR`) that the Web Speech API and most cloud * STT services expect. * * We keep a small built-in table for the locales we ship translations * for; codes outside it are looked up in the full Web Speech catalogue * (so `uk` → `uk-UA`, not the invalid `uk-UK`), and only fall through * to `-` as a last resort. The mapping is also * re-exported so consumers can extend it. */ import { WEB_SPEECH_LANGUAGES } from './languages-catalog'; const ISO_TO_BCP47: Record = { en: 'en-US', ru: 'ru-RU', ko: 'ko-KR', ja: 'ja-JP', zh: 'zh-CN', de: 'de-DE', fr: 'fr-FR', it: 'it-IT', es: 'es-ES', nl: 'nl-NL', ar: 'ar-SA', tr: 'tr-TR', pl: 'pl-PL', sv: 'sv-SE', no: 'nb-NO', da: 'da-DK', pt: 'pt-BR', }; export const DEFAULT_ISO_TO_BCP47 = ISO_TO_BCP47; /** ISO-639 primary subtag → default catalogue dialect (e.g. `uk` → `uk-UA`). */ const CATALOG_ISO_TO_TAG: Record = (() => { const map: Record = {}; for (const lang of WEB_SPEECH_LANGUAGES) { const first = lang.dialects[0]; if (first) map[lang.iso.toLowerCase()] = first.code; } return map; })(); /** * Normalise any of: * - BCP-47 ("en-US", "ru-RU") — passed through. * - ISO 639-1 ("en", "ru") — mapped via the built-in table, then * the full Web Speech catalogue, then * `-` as a last * resort. * - `null`/`undefined`/empty — returns `undefined`. */ export function toBCP47( code: string | null | undefined, table: Record = ISO_TO_BCP47, ): string | undefined { if (!code) return undefined; const trimmed = code.trim(); if (!trimmed) return undefined; if (trimmed.includes('-')) return trimmed; // already BCP-47 const lower = trimmed.toLowerCase(); return ( table[lower] ?? CATALOG_ISO_TO_TAG[lower] ?? `${lower}-${lower.toUpperCase()}` ); } /** * Resolve the language tag for a speech session in priority order: * 1. `explicit` prop (always wins) — host-supplied override. * 2. `prefs` — value stored in `useSpeechPrefs` (user picked it * via `` or programmatically). * 3. `i18n` — current i18n locale (2-letter ISO). * 4. `navigator.language` — browser default. * 5. `'en-US'` — last-resort safety net. * * All inputs may be ISO-2 or BCP-47; the function normalises before * returning. */ export function resolveSpeechLanguage(opts: { explicit?: string; prefs?: string | null; i18n?: string | null; }): string { return ( toBCP47(opts.explicit) ?? toBCP47(opts.prefs) ?? toBCP47(opts.i18n) ?? toBCP47(typeof navigator !== 'undefined' ? navigator.language : null) ?? 'en-US' ); }