mirror of
https://github.com/lfnovo/open-notebook.git
synced 2026-05-02 13:20:30 +00:00
Some checks failed
Development Build / extract-version (push) Has been cancelled
Tests / Backend Tests (push) Has been cancelled
Tests / Frontend Tests (push) Has been cancelled
Development Build / build-regular (push) Has been cancelled
Development Build / build-single (push) Has been cancelled
Development Build / summary (push) Has been cancelled
* feat(podcasts): integrate model registry for profiles and credential passthrough Replace loose provider/model string fields with record<model> references in podcast profiles, enabling credential passthrough to podcast-creator. Backend: - EpisodeProfile: outline_llm, transcript_llm (record<model>) replace outline_provider/outline_model strings. New language field (BCP 47). - SpeakerProfile: voice_model (record<model>) replaces tts_provider/ tts_model strings. Per-speaker voice_model override support. - Migration 14: schema changes making legacy fields optional, adding new record<model> fields. - Data migration (migration.py): auto-converts legacy profiles to model registry references on startup. Idempotent. - podcast_commands.py: resolves credentials for ALL profiles before calling podcast-creator. - New /api/languages endpoint (pycountry + babel) with BCP 47 locale codes (pt-BR, en-US, etc.). Frontend: - Episode/speaker profile forms use ModelSelector instead of manual provider/model dropdowns. - Language dropdown with BCP 47 codes in episode profile form. - Per-speaker TTS voice model override in speaker profile form. - "Templates" tab renamed to "Profiles". - Setup required badge on unconfigured profiles. - i18n updated across all 8 locales. Closes #486, closes #552 * fix(i18n): remove unused legacy podcast provider/model keys Remove 10 orphaned i18n keys across all 8 locales that were left behind after replacing manual provider/model dropdowns with ModelSelector. * fix: address review violations in podcast model registry - P1: Remove profiles with failed model resolution from dicts to prevent podcast-creator validation errors on unrelated profiles - P2: Use centralized QUERY_KEYS.languages instead of inline key - P3: Fix ISO 639-1 → BCP 47 in model field description and CLAUDE.md - P3: Update "templates" → "profiles" in locale string values (all 8) * chore: bump version to 1.8.0
83 lines
2.2 KiB
Python
83 lines
2.2 KiB
Python
from typing import List
|
|
|
|
import pycountry
|
|
from babel import Locale
|
|
from babel.core import get_global
|
|
from fastapi import APIRouter
|
|
from pydantic import BaseModel
|
|
|
|
router = APIRouter()
|
|
|
|
# Additional regional variants for languages where the distinction matters
|
|
# (TTS accent, vocabulary, spelling differences)
|
|
_EXTRA_VARIANTS = [
|
|
"pt_PT",
|
|
"en_GB",
|
|
"en_AU",
|
|
"en_IN",
|
|
"es_MX",
|
|
"es_AR",
|
|
"es_CO",
|
|
"fr_CA",
|
|
"fr_CH",
|
|
"zh_TW",
|
|
"zh_HK",
|
|
"de_AT",
|
|
"de_CH",
|
|
"ar_SA",
|
|
"nl_BE",
|
|
]
|
|
|
|
|
|
class LanguageResponse(BaseModel):
|
|
code: str
|
|
name: str
|
|
|
|
|
|
@router.get("/languages", response_model=List[LanguageResponse])
|
|
async def list_languages():
|
|
"""List available languages as BCP 47 locale codes (e.g. pt-BR, en-US)."""
|
|
likely_subtags = get_global("likely_subtags")
|
|
languages = []
|
|
seen = set()
|
|
|
|
# 1. For each language, resolve its default locale via CLDR likely subtags
|
|
for lang in pycountry.languages:
|
|
if not hasattr(lang, "alpha_2"):
|
|
continue
|
|
|
|
code = lang.alpha_2
|
|
likely = likely_subtags.get(code)
|
|
|
|
if likely:
|
|
try:
|
|
loc = Locale.parse(likely)
|
|
if loc.territory:
|
|
bcp47 = f"{loc.language}-{loc.territory}"
|
|
display = loc.get_display_name("en")
|
|
if bcp47 not in seen:
|
|
seen.add(bcp47)
|
|
languages.append(LanguageResponse(code=bcp47, name=display))
|
|
continue
|
|
except Exception:
|
|
pass
|
|
|
|
# Fallback: bare language code
|
|
if code not in seen:
|
|
seen.add(code)
|
|
languages.append(LanguageResponse(code=code, name=lang.name))
|
|
|
|
# 2. Add important regional variants
|
|
for locale_str in _EXTRA_VARIANTS:
|
|
try:
|
|
loc = Locale.parse(locale_str)
|
|
bcp47 = f"{loc.language}-{loc.territory}"
|
|
if bcp47 not in seen:
|
|
seen.add(bcp47)
|
|
display = loc.get_display_name("en")
|
|
languages.append(LanguageResponse(code=bcp47, name=display))
|
|
except Exception:
|
|
pass
|
|
|
|
languages.sort(key=lambda x: x.name)
|
|
return languages
|