fix(pyqt): migrate GUI to Language enums, fix subtitle mode gating

Complete the Language enum unification (commit 0dc491e) for the PyQt
GUI, which was left resolving languages from kokoro letter codes
(voice[0]) while domain and WebUI already used Language enums.

- selected_lang is now always a Language enum: voices resolve via
  language_for_voice_id(), profiles via resolve_profile_language()
- kokoro letter->Language mapping stays in the engine (new public
  language_for_code()); the Language.from_code() domain shim is removed
- legacy profile files with letter codes ("a", "e", ...) are tolerated
  only at the profile read boundary; new profiles save ISO codes
- conversion.py letter comparisons replaced with enum comparisons, and
  SynthParams lang_code= -> language= (would TypeError at runtime)
- subtitle dropdown enabled for all languages; word-count and
  highlighting modes restricted to English with auto-switch to Sentence
- tests: engine mapping + profile language resolution added; 1566 pass
This commit is contained in:
Deniz Şafak
2026-08-20 21:28:30 +03:00
parent f340b976db
commit 11274ad6bf
13 changed files with 183 additions and 68 deletions
+6 -1
View File
@@ -119,7 +119,7 @@ class TestLanguage:
def test_supports_subtitle_tokens(self):
assert Language.EN_US.supports_subtitle_tokens is True
assert Language.EN_GB.supports_subtitle_tokens is True
assert Language.ZH.supports_subtitle_tokens is False
assert Language.ZH.supports_subtitle_tokens is True
def test_from_str_case_insensitive(self):
assert Language.from_str("EN-US") == Language.EN_US
@@ -129,3 +129,8 @@ class TestLanguage:
def test_from_str_invalid(self):
with pytest.raises(ValueError, match="Invalid Language"):
Language.from_str("en")
def test_kokoro_letter_codes_not_accepted_by_domain(self):
# Letter codes are kokoro-engine internals, not domain API.
with pytest.raises(ValueError):
Language.from_str("a")
+39
View File
@@ -196,3 +196,42 @@ class TestKokoroVoiceLister:
assert isinstance(voice.tags, tuple)
assert len(voice.tags) > 0
engine.dispose()
# ──────────────────────────────────────────────────────────────
# Language mapping helpers
# ──────────────────────────────────────────────────────────────
class TestKokoroLanguageMapping:
"""Language resolution helpers: the letter codes live in the engine only."""
def test_language_for_code(self) -> None:
from abogen.domain.enums import Language
from plugins.kokoro.engine import language_for_code
assert language_for_code("a") == Language.EN_US
assert language_for_code("b") == Language.EN_GB
assert language_for_code("e") == Language.ES
assert language_for_code("f") == Language.FR
assert language_for_code("h") == Language.HI
assert language_for_code("i") == Language.IT
assert language_for_code("j") == Language.JA
assert language_for_code("p") == Language.PT_BR
assert language_for_code("z") == Language.ZH
def test_language_for_code_fallbacks(self) -> None:
from abogen.domain.enums import Language
from plugins.kokoro.engine import language_for_code
assert language_for_code("x") == Language.EN_US
assert language_for_code("") == Language.EN_US
def test_language_for_voice_id(self) -> None:
from abogen.domain.enums import Language
from plugins.kokoro.engine import language_for_voice_id
assert language_for_voice_id("af_heart") == Language.EN_US
assert language_for_voice_id("bf_emma") == Language.EN_GB
assert language_for_voice_id("ef_dora") == Language.ES
assert language_for_voice_id("zf_xiaobei") == Language.ZH
assert language_for_voice_id("") == Language.EN_US
+28
View File
@@ -0,0 +1,28 @@
"""Tests for voice profile language resolution."""
from __future__ import annotations
from abogen.domain.enums import Language
from abogen.voice_profiles import resolve_profile_language
class TestResolveProfileLanguage:
def test_iso_code(self) -> None:
assert resolve_profile_language({"language": "en-US"}) == Language.EN_US
assert resolve_profile_language({"language": "es"}) == Language.ES
def test_enum_value(self) -> None:
assert resolve_profile_language({"language": Language.ZH}) == Language.ZH
def test_legacy_kokoro_letter(self) -> None:
assert resolve_profile_language({"language": "a"}) == Language.EN_US
assert resolve_profile_language({"language": "e"}) == Language.ES
assert resolve_profile_language({"language": "z"}) == Language.ZH
def test_missing_or_unparseable_falls_back(self) -> None:
assert resolve_profile_language({}) == Language.EN_US
assert resolve_profile_language({"language": ""}) == Language.EN_US
assert resolve_profile_language({"language": "xx"}) == Language.EN_US
assert resolve_profile_language(None) == Language.EN_US
assert resolve_profile_language([]) == Language.EN_US