fix(gui): populate engine dropdown without environment config
- Defer engine imports until translation starts - Add regression coverage for source downloads without environment settings
This commit is contained in:
parent
3b13575a74
commit
a371223189
2 changed files with 110 additions and 56 deletions
|
|
@ -19,6 +19,7 @@ import traceback
|
||||||
import signal
|
import signal
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import re
|
import re
|
||||||
|
from importlib import import_module
|
||||||
from colorama import Fore
|
from colorama import Fore
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
@ -75,6 +76,39 @@ BATCH_COLLECT_LIVE_CHARGE_NOTE = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
TRANSLATION_MODULE_SPECS = (
|
||||||
|
("RPG Maker MV/MZ", (".json",), "modules.rpgmakermvmz", "handleMVMZ"),
|
||||||
|
("CSV", (".csv",), "modules.csv", "handleCSV"),
|
||||||
|
("Tyrano", (".ks",), "modules.tyrano", "handleTyrano"),
|
||||||
|
("Kirikiri", (".ks",), "modules.kirikiri", "handleKirikiri"),
|
||||||
|
("JSON", (".json",), "modules.json", "handleJSON"),
|
||||||
|
("Lune", (".l",), "modules.lune", "handleLune"),
|
||||||
|
("Yuris", (".json",), "modules.yuris", "handleYuris"),
|
||||||
|
("NScript", (".nscript",), "modules.nscript", "handleOnscripter"),
|
||||||
|
("Wolf RPG (WolfDawn)", (".json",), "modules.wolfdawn", "handleWolfDawn"),
|
||||||
|
("Wolf RPG", (".json",), "modules.wolf", "handleWOLF"),
|
||||||
|
("Wolf RPG 2", (".txt",), "modules.wolf2", "handleWOLF2"),
|
||||||
|
("Regex", (".txt", ".json", ".script", ".csv"), "modules.regex", "handleRegex"),
|
||||||
|
("Text", (".txt", ".srt"), "modules.text", "handleText"),
|
||||||
|
("RenPy", (".rpy",), "modules.renpy", "handleRenpy"),
|
||||||
|
("Unity", (".unity",), "modules.unity", "handleUnity"),
|
||||||
|
("Images", (".png", ".jpg", ".jpeg"), "modules.images", "handleImages"),
|
||||||
|
("RPG Maker Plugin", (".js",), "modules.rpgmakerplugin", "handlePlugin"),
|
||||||
|
("Aquedi4 Prepared JSON", (".json",), "modules.aquedi4", "handleAquedi4"),
|
||||||
|
("SRPG Studio", (".json",), "modules.srpg", "handleSRPG"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _lazy_module_handler(module_name, handler_name):
|
||||||
|
"""Return a handler that imports its engine only when translation starts."""
|
||||||
|
|
||||||
|
def run(*args, **kwargs):
|
||||||
|
module = import_module(module_name)
|
||||||
|
return getattr(module, handler_name)(*args, **kwargs)
|
||||||
|
|
||||||
|
return run
|
||||||
|
|
||||||
|
|
||||||
class _ShimLabel:
|
class _ShimLabel:
|
||||||
"""Plain stand-in for QLabel used by Files-tab row helpers (no real Qt widget)."""
|
"""Plain stand-in for QLabel used by Files-tab row helpers (no real Qt widget)."""
|
||||||
|
|
||||||
|
|
@ -1647,63 +1681,25 @@ class TranslationTab(QWidget):
|
||||||
|
|
||||||
def setup_module_list(self):
|
def setup_module_list(self):
|
||||||
"""Set up the module selection list."""
|
"""Set up the module selection list."""
|
||||||
# Import modules to get the list
|
# Engine modules read translation settings during import. A downloaded
|
||||||
try:
|
# source archive intentionally has no .env yet, so importing every
|
||||||
sys.path.append(str(self.project_root))
|
# engine here used to abort discovery and leave only the fallback item.
|
||||||
from modules.rpgmakermvmz import handleMVMZ
|
# Keep the UI registry independent and defer imports until a handler is
|
||||||
from modules.csv import handleCSV
|
# actually used.
|
||||||
from modules.tyrano import handleTyrano
|
self.modules = [
|
||||||
from modules.kirikiri import handleKirikiri
|
[
|
||||||
from modules.json import handleJSON
|
display_name,
|
||||||
from modules.lune import handleLune
|
list(extensions),
|
||||||
from modules.yuris import handleYuris
|
_lazy_module_handler(module_name, handler_name),
|
||||||
from modules.nscript import handleOnscripter
|
|
||||||
from modules.wolf import handleWOLF
|
|
||||||
from modules.wolf2 import handleWOLF2
|
|
||||||
from modules.wolfdawn import handleWolfDawn
|
|
||||||
from modules.regex import handleRegex
|
|
||||||
from modules.text import handleText
|
|
||||||
from modules.renpy import handleRenpy
|
|
||||||
from modules.unity import handleUnity
|
|
||||||
from modules.images import handleImages
|
|
||||||
from modules.rpgmakerplugin import handlePlugin
|
|
||||||
from modules.aquedi4 import handleAquedi4
|
|
||||||
from modules.srpg import handleSRPG
|
|
||||||
|
|
||||||
self.modules = [
|
|
||||||
["RPG Maker MV/MZ", [".json"], handleMVMZ],
|
|
||||||
["CSV", [".csv"], handleCSV],
|
|
||||||
["Tyrano", [".ks"], handleTyrano],
|
|
||||||
["Kirikiri", [".ks"], handleKirikiri],
|
|
||||||
["JSON", [".json"], handleJSON],
|
|
||||||
["Lune", [".l"], handleLune],
|
|
||||||
["Yuris", [".json"], handleYuris],
|
|
||||||
["NScript", [".nscript"], handleOnscripter],
|
|
||||||
["Wolf RPG (WolfDawn)", [".json"], handleWolfDawn],
|
|
||||||
["Wolf RPG", [".json"], handleWOLF],
|
|
||||||
["Wolf RPG 2", [".txt"], handleWOLF2],
|
|
||||||
["Regex", [".txt", ".json", ".script", ".csv"], handleRegex],
|
|
||||||
["Text", [".txt", ".srt"], handleText],
|
|
||||||
["RenPy", [".rpy"], handleRenpy],
|
|
||||||
["Unity", [".unity"], handleUnity],
|
|
||||||
["Images", [".png", ".jpg", ".jpeg"], handleImages],
|
|
||||||
["RPG Maker Plugin", [".js"], handlePlugin],
|
|
||||||
["Aquedi4 Prepared JSON", [".json"], handleAquedi4],
|
|
||||||
["SRPG Studio", [".json"], handleSRPG],
|
|
||||||
]
|
]
|
||||||
|
for display_name, extensions, module_name, handler_name
|
||||||
for module in self.modules:
|
in TRANSLATION_MODULE_SPECS
|
||||||
extensions = ", ".join(module[1])
|
]
|
||||||
self.module_combo.addItem(f"{module[0]} ({extensions})")
|
|
||||||
if self.module_combo.count():
|
for module in self.modules:
|
||||||
self._on_module_changed(self.module_combo.currentText())
|
extensions = ", ".join(module[1])
|
||||||
|
self.module_combo.addItem(f"{module[0]} ({extensions})")
|
||||||
except Exception as e:
|
if self.module_combo.count():
|
||||||
# Store error for later logging since log_display might not exist yet
|
|
||||||
self.module_load_error = f"Warning: Could not load modules: {str(e)}"
|
|
||||||
# Add a default option
|
|
||||||
self.module_combo.addItem("RPG Maker MV/MZ (.json)")
|
|
||||||
self.modules = [["RPG Maker MV/MZ", [".json"], None]]
|
|
||||||
self._on_module_changed(self.module_combo.currentText())
|
self._on_module_changed(self.module_combo.currentText())
|
||||||
|
|
||||||
def _on_module_changed(self, text: str):
|
def _on_module_changed(self, text: str):
|
||||||
|
|
|
||||||
58
tests/test_translation_engine_dropdown.py
Normal file
58
tests/test_translation_engine_dropdown.py
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
"""Regression tests for Translation-tab engine discovery."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
|
from gui.translation_tab import TRANSLATION_MODULE_SPECS, TranslationTab
|
||||||
|
|
||||||
|
_HAS_QT = True
|
||||||
|
except Exception: # pragma: no cover - PyQt5 not installed
|
||||||
|
_HAS_QT = False
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipUnless(_HAS_QT, "PyQt5 not available")
|
||||||
|
class TranslationEngineDropdownTests(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
cls._app = QApplication.instance() or QApplication([])
|
||||||
|
|
||||||
|
def test_all_engines_are_listed_without_environment_configuration(self) -> None:
|
||||||
|
required_settings = (
|
||||||
|
"model",
|
||||||
|
"language",
|
||||||
|
"timeout",
|
||||||
|
"width",
|
||||||
|
"listWidth",
|
||||||
|
"noteWidth",
|
||||||
|
)
|
||||||
|
without_settings = {
|
||||||
|
key: value
|
||||||
|
for key, value in os.environ.items()
|
||||||
|
if key not in required_settings
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch.dict(os.environ, without_settings, clear=True):
|
||||||
|
tab = TranslationTab()
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
f"{name} ({', '.join(extensions)})"
|
||||||
|
for name, extensions, _module, _handler in TRANSLATION_MODULE_SPECS
|
||||||
|
]
|
||||||
|
actual = [
|
||||||
|
tab.module_combo.itemText(index)
|
||||||
|
for index in range(tab.module_combo.count())
|
||||||
|
]
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
self.assertTrue(all(callable(module[2]) for module in tab.modules))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in a new issue