From 686a5fc87692362f54324026d2f41730a1f39375 Mon Sep 17 00:00:00 2001 From: janezd Date: Mon, 22 Apr 2024 19:58:30 +0200 Subject: [PATCH 1/3] Add language setting and translation function --- orangecanvas/application/settings.py | 12 +++++++ orangecanvas/config.py | 6 ++++ orangecanvas/utils/localization/__init__.py | 38 +++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/orangecanvas/application/settings.py b/orangecanvas/application/settings.py index c40069f79..ff1610e72 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,17 @@ def __setupUi(self): form = FormLayout() tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + languages = get_languages() + if languages: + cm_lang = QComboBox( + objectName="combo-language", + toolTip=self.tr("Select the application language.") + ) + cm_lang.addItems(languages) + self.bind(cm_lang, "currentText", "application/language") + + form.addRow(self.tr("Language"), cm_lang) + 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 950b65924..ae3a97e2e 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/utils/localization/__init__.py b/orangecanvas/utils/localization/__init__.py index c9bbc4b61..336b77323 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 + 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,35 @@ 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"] + + +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", "English") + # 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", "English.json") + assert os.path.exists(path) + self.m = json.load(open(path)) + + def c(self, idx): + return compile(self.m[idx], '', 'eval') From 34aabb5a45a64f3e8878f358675d46d11ac77b15 Mon Sep 17 00:00:00 2001 From: janezd Date: Thu, 16 May 2024 18:15:08 +0200 Subject: [PATCH 2/3] Language selection: Force widget discovery if language is changed --- orangecanvas/application/settings.py | 10 +++++++++- orangecanvas/main.py | 6 +++++- orangecanvas/utils/localization/__init__.py | 13 +++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/orangecanvas/application/settings.py b/orangecanvas/application/settings.py index ff1610e72..5752e3861 100644 --- a/orangecanvas/application/settings.py +++ b/orangecanvas/application/settings.py @@ -277,14 +277,22 @@ def __setupUi(self): 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)) - form.addRow(self.tr("Language"), cm_lang) + langlay.addWidget(cm_lang) + langlay.addWidget(label) + form.addRow(self.tr("Language"), langlay) nodes = QWidget(self, objectName="nodes") nodes.setLayout(QVBoxLayout()) diff --git a/orangecanvas/main.py b/orangecanvas/main.py index 5a9b051ba..4d9aea404 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 336b77323..fd9d3c3fc 100644 --- a/orangecanvas/utils/localization/__init__.py +++ b/orangecanvas/utils/localization/__init__.py @@ -49,6 +49,19 @@ def get_languages(package=None): if ext == ".json"] +def language_changed(): + s = QSettings() + lang = s.value("application/language", "English") + last_lang = s.value("application/last-used-language", "English") + 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 From 352808d853d4505472e99ec092062011bf1d8fb3 Mon Sep 17 00:00:00 2001 From: janezd Date: Thu, 16 May 2024 18:29:57 +0200 Subject: [PATCH 3/3] Language selection: Use locale to choose default language --- orangecanvas/utils/localization/__init__.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/orangecanvas/utils/localization/__init__.py b/orangecanvas/utils/localization/__init__.py index fd9d3c3fc..3dbb6c21a 100644 --- a/orangecanvas/utils/localization/__init__.py +++ b/orangecanvas/utils/localization/__init__.py @@ -2,7 +2,7 @@ import json import importlib -from AnyQt.QtCore import QSettings +from AnyQt.QtCore import QSettings, QLocale def pl(n: int, forms: str) -> str: # pylint: disable=invalid-name """ @@ -49,10 +49,15 @@ def get_languages(package=None): 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", "English") - last_lang = s.value("application/last-used-language", "English") + lang = s.value("application/language", DEFAULT_LANGUAGE) + last_lang = s.value("application/last-used-language", DEFAULT_LANGUAGE) return lang != last_lang @@ -68,14 +73,14 @@ class Translator: def __init__(self, package, organization="biolab.si", application="Orange"): s = QSettings(QSettings.IniFormat, QSettings.UserScope, organization, application) - lang = s.value("application/language", "English") + 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", "English.json") - assert 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):