-
Notifications
You must be signed in to change notification settings - Fork 380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: begin adding settings menu #647
Draft
python357-1
wants to merge
7
commits into
TagStudioDev:main
Choose a base branch
from
python357-1:add_settings_menu
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
+354
−111
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CyanVoxel
added
Type: UI/UX
User interface and/or user experience
Priority: High
An important issue requiring attention
labels
Dec 16, 2024
this might be helpful for you translation json files should be converted to localizationimport json
import os
from pathlib import Path
from xml.etree import ElementTree
import structlog
from PySide6.QtCore import QLocale, QTranslator
from PySide6.QtWidgets import QApplication
TRANSLATIONS_PATH: Path = Path("tagstudio/resources/translations").absolute()
logger = structlog.getLogger()
installed_translators: list[QTranslator] = []
"List of currently installed QTranslator objects."
def _convert_json_to_ts(json_data: dict[str, str], language: str) -> str:
"""Convert JSON data to a TS (Qt Linguist XML) string format.
This function takes a dictionary representing JSON data and a language code,
and converts it into a TS XML string used for localization in Qt applications.
The JSON data should have keys in the format "context.source" and values as
translations. The function organizes the data into contexts and messages,
constructs an XML tree, and returns the serialized XML as a string.
Args:
json_data (dict): A dictionary with keys as "context.source" and values as translations.
language (str): The language code for the TS file.
Returns:
str: String representing the TS XML data.
"""
# get contexts, sources and translations
# `"home.base_title": "TagStudio Alpha"` -> `{"home": [("source", "TagStudio Alpha")]}`
contexts: dict[str, list[tuple[str, str]]] = {} # Contexts to sources and translations mapping.
for key, translation in json_data.items():
context_name, source = key.split(".", 1)
if context_name not in contexts:
contexts[context_name] = []
contexts[context_name].append((source, translation))
# make xml tree from contexts
ts_root = ElementTree.Element("TS", version="2.1", language=language)
for context_name, messages in contexts.items():
context_element = ElementTree.SubElement(ts_root, "context")
name_element = ElementTree.SubElement(context_element, "name")
name_element.text = context_name
for message in messages:
source = message[0]
translation = message[1]
message_element = ElementTree.SubElement(context_element, "message")
source_element = ElementTree.SubElement(message_element, "source")
source_element.text = source
translation_element = ElementTree.SubElement(message_element, "translation")
translation_element.text = translation
return ElementTree.tostring(ts_root, encoding="utf-8", xml_declaration=True).decode("utf-8")
def compile_language(json_path: Path) -> Path | None:
"""Compile a JSON localization file to a QM file using PySide6 tools.
This function reads a JSON file containing localization data, converts it
to a TS XML format, and then compiles it into a QM file using the
`pyside6-lrelease` command. The compiled QM file is stored in a "compiled"
directory within the same parent directory as the JSON file.
Args:
json_path (Path): The path to the JSON file to be compiled.
Returns:
Path | None: The path to the compiled QM file, or None if the compilation fails.
"""
assert json_path.exists() and json_path.is_file()
language_code = json_path.stem
ts_path = json_path.with_suffix(".ts")
qm_path = json_path.parent / "compiled" / json_path.with_suffix(".qm").name
with json_path.open("r") as json_f:
json_data = json.load(json_f)
ts_data = _convert_json_to_ts(json_data, language_code)
ts_path.write_text(ts_data)
qm_path.parent.mkdir(exist_ok=True)
command = f"pyside6-lrelease {ts_path} -qm {qm_path}"
if os.system(command) != 0:
logger.error(f"os.system({command}) failed")
qm_path = None
ts_path.unlink()
return qm_path
def get_available_languages() -> list[tuple[str, QLocale.Language, QLocale.Country | None]]:
"""Retrieve a list of available languages from list of JSON translation files.
This function scans the TRANSLATIONS_PATH directory for JSON files, extracts
language codes from the filenames, and uses QLocale to determine the language
and country associated with each code. It returns a list of tuples containing
the language code, QLocale.Language, and optionally QLocale.Country.
Returns:
list[tuple[str, QLocale.Language, QLocale.Country | None]]: A list of
tuples with language code, language, and country information.
"""
languages = []
for file in os.listdir(str(TRANSLATIONS_PATH)):
if file.endswith(".json"):
code = file.split(".")[0]
locale = QLocale(code)
language, country = locale.language(), locale.country()
languages.append((code, language, country if "_" in code else None))
return languages
def set_application_language(language: str, fallback_language: str) -> None:
"""Set the language and fallback language for the application.
This function sets the language and fallback language for the application
based on the provided language and fallback language codes.
Args:
language (str): The language code for the application's language.
fallback_language (str): The language code for the application's fallback language.
"""
app = QApplication.instance()
if app is None:
logger.error("Set Language failed. QApplication instance not found.")
return
_installed_translators: list[QTranslator] = []
for json_file in {
TRANSLATIONS_PATH / f"{fallback_language}.json",
TRANSLATIONS_PATH / f"{language}.json",
}:
if not json_file.exists():
logger.warning(f"Translation json file not found: {json_file}")
continue
qm_file = compile_language(json_file)
if qm_file is None:
logger.warning(f"Failed to compile translation file: {json_file}")
continue
translator = QTranslator()
if translator.load(str(qm_file)):
if app.installTranslator(translator):
_installed_translators.append(translator)
else:
logger.error("Failed to install translator", translator=translator)
else:
logger.error("Failed to load file into translator", file=qm_file)
# remove old translators
for translator in installed_translators:
if app.removeTranslator(translator):
installed_translators.remove(translator)
else:
logger.error("Failed to remove translator", translator=translator)
installed_translators.extend(_installed_translators) tests: import os
from pathlib import Path
import py
import pytest
from PySide6.QtCore import QLocale, QTranslator
from PySide6.QtWidgets import QApplication
from src.qt.localization import compile_language, get_available_languages
def test_compile_language(tmpdir: py.path.local) -> None:
json_path = tmpdir / "en.json"
json_path.write_text('{"home.thumbnail_size": "Thumbnail Size"}', encoding="utf-8")
tm_file = compile_language(Path(json_path))
assert tm_file is not None
assert tm_file.exists()
assert tm_file.name == "en.qm"
translator = QTranslator()
translator.load(QLocale.Language.English, str(tm_file))
instance = QApplication.instance() or QApplication([])
instance.installTranslator(translator)
assert instance.translate("home", "thumbnail_size") == "Thumbnail Size"
def test_get_available_languages(monkeypatch: pytest.MonkeyPatch) -> None:
# NOTE: add more varied language codes to test if weblate uses them
monkeypatch.setattr(
os, "listdir", lambda _: ["en.json", "tok.json", "nb_NO.json", "yue_Hant.json"]
)
available_languages = get_available_languages()
lang = QLocale.Language
coun = QLocale.Country
expect_code = ("en", "tok", "nb_NO", "yue_Hant")
expect_lang = (lang.English, lang.TokiPona, lang.NorwegianBokmal, lang.Cantonese)
expect_coun = (None, None, coun.Norway, coun.HongKong)
for result, expected in zip(available_languages, zip(expect_code, expect_lang, expect_coun)):
assert result == expected |
python357-1
force-pushed
the
add_settings_menu
branch
from
December 24, 2024 02:19
dcf34fd
to
037ebde
Compare
oops
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
Priority: High
An important issue requiring attention
Type: UI/UX
User interface and/or user experience
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR addresses the "Settings Menu" item in the tagstudio roadmap