feat(languages): add Slovenian (sl) to the supported languages (#995)

Slovenian was missing from SUPPORTED_LANGUAGES, so the Settings picker did not offer it and normalize_language_shortcuts dropped any shortcut bound to it. Add it as a Whisper-only entry (whisper code sl; alphacephei publishes no Slovenian VOSK model), map the Slovenian XKB layout si to it for follow-keyboard-layout, and list it on the website languages page.

Fixes #970
This commit is contained in:
Jatin K Malik
2026-10-20 23:29:47 -07:00
committed by GitHub
parent 65de3427fb
commit 3abc106fdb
7 changed files with 82 additions and 1 deletions
+1
View File
@@ -29,6 +29,7 @@ _LAYOUT_TO_LANGUAGE = {
"gb": "en-us",
"cz": "cs",
"se": "sv",
"si": "sl",
"dk": "da",
"gr": "el",
"ua": "uk",
+5
View File
@@ -153,6 +153,11 @@ SUPPORTED_LANGUAGES = {
"whisper": "ru",
"vosk": "vosk-model-small-ru-0.22",
},
"sl": {
"name": "Slovenian",
"whisper": "sl",
"vosk": None,
},
"es": {
"name": "Spanish",
"whisper": "es",
+49
View File
@@ -60,12 +60,14 @@ def test_normalize_language_shortcuts_keeps_valid_bindings() -> None:
[
{"shortcut": "alt+d", "language": "de"},
{"shortcut": "ctrl+alt+g", "language": "hu"},
{"shortcut": "ctrl+alt+s", "language": "sl"},
{"shortcut": "f5", "language": "auto"},
]
)
assert entries == [
{"shortcut": "alt+d", "language": "de"},
{"shortcut": "ctrl+alt+g", "language": "hu"},
{"shortcut": "ctrl+alt+s", "language": "sl"},
{"shortcut": "f5", "language": "auto"},
]
@@ -1023,3 +1025,50 @@ def test_language_shortcut_managers_share_the_evdev_device_layer() -> None:
assert isinstance(german.backend_instance, EvdevKeyboardBackend)
assert isinstance(french.backend_instance, EvdevKeyboardBackend)
assert french.backend_instance._hub is german.backend_instance._hub
@pytest.mark.parametrize(
"engine,offered",
[("whisper_cpp", True), ("whisper", True), ("remote_api", True), ("vosk", False)],
)
def test_settings_language_picker_offers_slovenian_on_whisper_engines(
settings_dialog: Any, dialog_class: type[Any], engine: str, offered: bool
) -> None:
"""Slovenian (#970) is selectable wherever Whisper runs; VOSK has no model for it."""
dialog = MagicMock()
dialog.engine_combo.get_active_text.return_value = settings_dialog.ENGINE_DISPLAY_NAMES[engine]
with patch.object(settings_dialog, "_is_vosk_model_downloaded", return_value=False):
dialog_class._populate_language_options(dialog)
appended = [c.args[0] for c in dialog.language_combo.append.call_args_list]
assert ("sl" in appended) is offered
assert "sk" not in appended
def test_slovenian_shortcut_row_persists_through_config(
dialog_class: type[Any], tmp_path: Any
) -> None:
"""A row picked as Slovenian is saved and read back, not dropped (#970)."""
config_dir = tmp_path / "vocalinux"
config_dir.mkdir()
with (
patch("vocalinux.ui.config_manager.CONFIG_DIR", str(config_dir)),
patch("vocalinux.ui.config_manager.CONFIG_FILE", str(config_dir / "config.json")),
patch("vocalinux.utils.system_language.detect_system_language", return_value=None),
):
dialog = _dialog_stub()
dialog.config_manager = ConfigManager()
dialog.config_manager.save_settings = dialog.config_manager.save_config
picker, entry = MagicMock(), MagicMock()
picker.get_active_id.return_value = "sl"
entry.get_text.return_value = "ctrl+alt+s"
refs = {"language_picker": picker, "shortcut_entry": entry, "row": MagicMock()}
dialog._language_shortcut_rows = [refs]
dialog._report_language_shortcut_rejections = MagicMock()
dialog_class._persist_language_shortcuts(dialog)
assert ConfigManager().get_language_shortcuts() == [
{"shortcut": "ctrl+alt+s", "language": "sl"}
]
+1 -1
View File
@@ -104,7 +104,7 @@ class TestMainModule(unittest.TestCase):
self.assertEqual(LANGUAGE_CHOICES, tuple(SUPPORTED_LANGUAGES.keys()))
# Spot-check newly exposed languages (issue #565 and catalog expansion).
for lang in ["auto", "en-us", "hu", "ja", "ko", "ar", "nl", "pl", "uk"]:
for lang in ["auto", "en-us", "hu", "ja", "ko", "ar", "nl", "pl", "sl", "uk"]:
self.assertIn(lang, LANGUAGE_CHOICES)
with patch("sys.argv", ["vocalinux", "--language", lang]):
args = parse_arguments()
+13
View File
@@ -305,6 +305,18 @@ class TestSpeechRecognition(unittest.TestCase):
self.assertIsNone(hu["vosk"])
self.assertNotIn("hu", VOSK_MODEL_INFO["small"]["languages"])
def test_slovenian_is_whisper_only_in_catalog(self) -> None:
"""Slovenian (issue #970) is exposed for Whisper engines, not VOSK or Slovak."""
from vocalinux.utils.vosk_model_info import SUPPORTED_LANGUAGES
slovenian = SUPPORTED_LANGUAGES["sl"]
self.assertEqual(slovenian["name"], "Slovenian")
self.assertEqual(slovenian["whisper"], "sl")
self.assertIsNone(slovenian["vosk"])
for size in ("small", "medium", "large"):
self.assertNotIn("sl", VOSK_MODEL_INFO[size]["languages"])
self.assertNotIn("sk", SUPPORTED_LANGUAGES)
def test_resolve_whisper_language_maps_english_variants(self):
"""Catalog ids like en-us/en-in must become Whisper's 'en' code."""
from unittest.mock import patch
@@ -314,6 +326,7 @@ class TestSpeechRecognition(unittest.TestCase):
self.assertEqual(resolve_whisper_language("en-us"), "en")
self.assertEqual(resolve_whisper_language("en-in"), "en")
self.assertEqual(resolve_whisper_language("hu"), "hu")
self.assertEqual(resolve_whisper_language("sl"), "sl")
self.assertEqual(resolve_whisper_language("ja"), "ja")
self.assertIsNone(resolve_whisper_language("auto"))
# Unknown catalog ids pass through unchanged.
+4
View File
@@ -24,6 +24,8 @@ def isolated_config(tmp_path, monkeypatch):
("de", "de"),
("cz", "cs"),
("se", "sv"),
("si", "sl"),
("sk", None),
("us", "en-us"),
("gb", "en-us"),
("xyz", None),
@@ -48,6 +50,8 @@ def test_layouts_map_onto_catalogue_entries(layout, expected):
("nb_NO", "no"),
("nb_NO.UTF-8", "no"),
("nn_NO", "no"),
("sl_SI.UTF-8", "sl"),
("sk_SK.UTF-8", None),
("C", None),
("POSIX", None),
("C.UTF-8", None),
+9
View File
@@ -216,6 +216,14 @@ const supportedLanguages = [
description: "Russian language speech-to-text transcription",
engines: [...ALL_ENGINES],
},
{
code: "sl",
name: "Slovenian",
nativeName: "Slovenščina",
flag: "🇸🇮",
description: "Slovenian dictation with multilingual Whisper models",
engines: [...WHISPER_ENGINES],
},
{
code: "es",
name: "Spanish",
@@ -314,6 +322,7 @@ export const metadata: Metadata = buildPageMetadata({
"Italian voice recognition",
"Portuguese voice typing",
"Russian speech recognition",
"Slovenian voice typing",
"Chinese dictation",
"Hindi voice typing",
"Linux multilingual dictation",