diff --git a/orangecanvas/application/settings.py b/orangecanvas/application/settings.py index c40069f7..5752e386 100644 --- a/orangecanvas/application/settings.py +++ b/orangecanvas/application/settings.py @@ -21,6 +21,7 @@ Signal) from .. import config +from ..utils.localization import get_languages from ..utils.settings import SettingChangedEvent from ..utils.propertybindings import ( AbstractBoundProperty, PropertyBinding, BindingManager @@ -274,6 +275,25 @@ def __setupUi(self): form = FormLayout() tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + languages = get_languages() + if languages: + langlay = QHBoxLayout() + label = QLabel( + "Changes will take effect on next application startup.") + label.setHidden(True) + + cm_lang = QComboBox( + objectName="combo-language", + toolTip=self.tr("Select the application language.") + ) + cm_lang.addItems(languages) + self.bind(cm_lang, "currentText", "application/language") + cm_lang.currentTextChanged.connect(lambda: label.setHidden(False)) + + langlay.addWidget(cm_lang) + langlay.addWidget(label) + form.addRow(self.tr("Language"), langlay) + nodes = QWidget(self, objectName="nodes") nodes.setLayout(QVBoxLayout()) nodes.layout().setContentsMargins(0, 0, 0, 0) diff --git a/orangecanvas/config.py b/orangecanvas/config.py index 950b6592..ae3a97e2 100644 --- a/orangecanvas/config.py +++ b/orangecanvas/config.py @@ -364,6 +364,12 @@ def init(): ("startup/load-crashed-workflows", bool, True, "Load crashed scratch workflows on startup"), + ("application/language", str, "English", + "Application language"), + + ("application/last-used-language", str, "English", + "If different from application/language, widget discovery is forced"), + ("stylesheet", str, "orange", "QSS stylesheet to use"), diff --git a/orangecanvas/main.py b/orangecanvas/main.py index 5a9b051b..4d9aea40 100644 --- a/orangecanvas/main.py +++ b/orangecanvas/main.py @@ -16,6 +16,7 @@ from AnyQt.QtGui import QFont, QColor, QPalette from AnyQt.QtCore import Qt, QSettings, QTimer, QUrl, QDir +from orangecanvas.utils import localization from .utils.after_exit import run_after_exit from .styles import style_sheet, breeze_dark as _breeze_dark from .application.application import CanvasApplication @@ -142,7 +143,8 @@ def run_discovery(self) -> WidgetRegistry: Run the widget discovery and return the resulting registry. """ options = self.options - if not options.force_discovery: + language_changed = localization.language_changed() + if not (options.force_discovery or language_changed): reg_cache = cache.registry_cache() else: reg_cache = None @@ -168,6 +170,8 @@ def run_discovery(self) -> WidgetRegistry: with open(cache_filename, "wb") as f: pickle.dump(WidgetRegistry(widget_registry), f) self.registry = widget_registry + if language_changed: + localization.update_last_used_language() self.close_splash_screen() return widget_registry diff --git a/orangecanvas/utils/localization/__init__.py b/orangecanvas/utils/localization/__init__.py index c9bbc4b6..3dbb6c21 100644 --- a/orangecanvas/utils/localization/__init__.py +++ b/orangecanvas/utils/localization/__init__.py @@ -1,3 +1,9 @@ +import os +import json +import importlib + +from AnyQt.QtCore import QSettings, QLocale + def pl(n: int, forms: str) -> str: # pylint: disable=invalid-name """ Choose a singular/plural form for English - or create one, for regular nouns @@ -29,3 +35,53 @@ def pl(n: int, forms: str) -> str: # pylint: disable=invalid-name if forms.isupper(): word = word.upper() return word + + +def get_languages(package=None): + if package is None: + package = "orangecanvas" + package_path = os.path.dirname(importlib.import_module(package).__file__) + msgs_path = os.path.join(package_path, "i18n") + if not os.path.exists(msgs_path): + return [] + return [name + for name, ext in map(os.path.splitext, os.listdir(msgs_path)) + if ext == ".json"] + + +DEFAULT_LANGUAGE = QLocale().languageToString(QLocale().language()) +if DEFAULT_LANGUAGE not in get_languages(): + DEFAULT_LANGUAGE = "English" + + +def language_changed(): + s = QSettings() + lang = s.value("application/language", DEFAULT_LANGUAGE) + last_lang = s.value("application/last-used-language", DEFAULT_LANGUAGE) + return lang != last_lang + + +def update_last_used_language(): + s = QSettings() + lang = s.value("application/language", "English") + s.setValue("application/last-used-language", lang) + + +class Translator: + e = eval + + def __init__(self, package, organization="biolab.si", application="Orange"): + s = QSettings(QSettings.IniFormat, QSettings.UserScope, + organization, application) + lang = s.value("application/language", DEFAULT_LANGUAGE) + # For testing purposes (and potential fallback) + # lang = os.environ.get("ORANGE_LANG", "English") + package_path = os.path.dirname(importlib.import_module(package).__file__) + path = os.path.join(package_path, "i18n", f"{lang}.json") + if not os.path.exists(path): + path = os.path.join(package_path, "i18n", f"{DEFAULT_LANGUAGE}.json") + assert os.path.exists(path), f"Missing language file {path}" + self.m = json.load(open(path)) + + def c(self, idx): + return compile(self.m[idx], '', 'eval')