From 0244562cf05ead0c3ef2d5b9d2de30356323c622 Mon Sep 17 00:00:00 2001 From: ann0see <20726856+ann0see@users.noreply.github.com> Date: Sat, 10 Jan 2026 21:53:24 +0100 Subject: [PATCH 1/5] Add annotation tests for translations --- .github/workflows/translation-check.yml | 3 + tools/check-translations.py | 151 ++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 tools/check-translations.py diff --git a/.github/workflows/translation-check.yml b/.github/workflows/translation-check.yml index 60d8a2d40d..a30354127b 100644 --- a/.github/workflows/translation-check.yml +++ b/.github/workflows/translation-check.yml @@ -10,6 +10,7 @@ on: push: paths: - 'src/translation/wininstaller/**' + - 'src/translation/*.ts' - 'tools/check-wininstaller-translations.sh' - '.github/workflows/translation-check.yml' @@ -24,5 +25,7 @@ jobs: uses: actions/checkout@v6 - name: "Check Windows installer translations" run: ./tools/check-wininstaller-translations.sh + - name: "Check application translations" + run: ./tools/check-translations.py #- name: "Check for duplicate hotkeys (will not fail)" # run: sudo apt install libxml-simple-perl && cd src/translation/ && perl ./tools/checkkeys.pl diff --git a/tools/check-translations.py b/tools/check-translations.py new file mode 100644 index 0000000000..c76b11a140 --- /dev/null +++ b/tools/check-translations.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# +############################################################################## +# Copyright (c) 2022-2026 +# +# Author(s): +# ChatGPT +# ann0see +# The Jamulus Development Team +# +############################################################################## +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +# +############################################################################## + +import re +import xml.etree.ElementTree as ET +from pathlib import Path + +TS_DIR = Path("src/translation") +TS_FILES = sorted(TS_DIR.glob("translation_*.ts")) + +placeholder_re = re.compile(r"%\d+") +html_tag_re = re.compile(r"<[^>]+>") + +total_warnings = 0 +file_warnings = {} + + +def gh_warning(file_path, line, message): + # Output GitHub Actions annotation + print(f"::warning file={file_path},line={line}::{message}") + + +print("== Qt Translation files validation (GitHub Actions annotations) ==") + +for ts_file in TS_FILES: + warnings = 0 + file_warnings[ts_file.name] = 0 + + # Pre-read the file lines to approximate line numbers + try: + with ts_file.open(encoding="utf-8") as f: + lines = f.readlines() + except Exception as e: + gh_warning(ts_file, 0, f"Could not read file: {e}") + warnings += 1 + file_warnings[ts_file.name] = warnings + total_warnings += warnings + continue + + # Parse XML + try: + tree = ET.parse(ts_file) + root = tree.getroot() + except ET.ParseError as e: + gh_warning(ts_file, 0, f"Could not parse XML: {e}") + warnings += 1 + file_warnings[ts_file.name] = warnings + total_warnings += warnings + continue + + # Language header + file_lang = ts_file.stem.replace("translation_", "") + lang_attr = root.attrib.get("language", "") + if lang_attr != file_lang: + gh_warning( + ts_file, + 0, + f"Language header '{lang_attr}' does not match filename '{file_lang}'", + ) + warnings += 1 + + # Iterate messages + message_index = 0 + line_idx = 0 + for context in root.findall("context"): + for message in context.findall("message"): + message_index += 1 + + # Approximate line number by searching for after last index + approx_line = 0 + for i in range(line_idx, len(lines)): + if " Date: Sat, 10 Jan 2026 21:54:33 +0100 Subject: [PATCH 2/5] Make script executable --- tools/check-translations.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tools/check-translations.py diff --git a/tools/check-translations.py b/tools/check-translations.py old mode 100644 new mode 100755 From 42835548bfcde90b3ba89b5e415987b5fdf20f34 Mon Sep 17 00:00:00 2001 From: ann0see <20726856+ann0see@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:10:27 +0100 Subject: [PATCH 3/5] Add PR commenting logic --- tools/check-translations.py | 250 ++++++++++++++++++++++-------------- 1 file changed, 154 insertions(+), 96 deletions(-) diff --git a/tools/check-translations.py b/tools/check-translations.py index c76b11a140..5e1dc2cc9a 100755 --- a/tools/check-translations.py +++ b/tools/check-translations.py @@ -28,124 +28,182 @@ import re import xml.etree.ElementTree as ET +from collections import defaultdict from pathlib import Path +from github import Github + +# --- Configuration --- +# Directory where the TS translation files are stored TS_DIR = Path("src/translation") TS_FILES = sorted(TS_DIR.glob("translation_*.ts")) -placeholder_re = re.compile(r"%\d+") -html_tag_re = re.compile(r"<[^>]+>") - -total_warnings = 0 -file_warnings = {} - +# Regex patterns for placeholders (%1, %2...) and HTML tags +PLACEHOLDER_RE = re.compile(r"%\d+") +HTML_TAG_RE = re.compile(r"<[^>]+>") -def gh_warning(file_path, line, message): - # Output GitHub Actions annotation - print(f"::warning file={file_path},line={line}::{message}") +# GitHub environment variables for optional PR commenting +GH_TOKEN = os.environ.get("GH_TOKEN") +REPO = os.environ.get("GITHUB_REPOSITORY") +PR_NUMBER = os.environ.get("PR_NUMBER") -print("== Qt Translation files validation (GitHub Actions annotations) ==") +# --- Function: detect warnings in a TS file --- +def detect_warnings(ts_file): + """ + Scans a TS file and returns a list of warning dictionaries. + Each warning contains the file, approximate line number, and message. + """ + warnings = [] -for ts_file in TS_FILES: - warnings = 0 - file_warnings[ts_file.name] = 0 - - # Pre-read the file lines to approximate line numbers - try: - with ts_file.open(encoding="utf-8") as f: - lines = f.readlines() - except Exception as e: - gh_warning(ts_file, 0, f"Could not read file: {e}") - warnings += 1 - file_warnings[ts_file.name] = warnings - total_warnings += warnings - continue - - # Parse XML try: + # Read all lines to help approximate line numbers + lines = ts_file.read_text(encoding="utf-8").splitlines() tree = ET.parse(ts_file) root = tree.getroot() - except ET.ParseError as e: - gh_warning(ts_file, 0, f"Could not parse XML: {e}") - warnings += 1 - file_warnings[ts_file.name] = warnings - total_warnings += warnings - continue - - # Language header + except Exception as e: + warnings.append( + { + "file": ts_file, + "line": 0, + "message": f"Error reading or parsing XML: {e}", + } + ) + return warnings + + # Ensure language in the TS header matches the filename file_lang = ts_file.stem.replace("translation_", "") - lang_attr = root.attrib.get("language", "") - if lang_attr != file_lang: - gh_warning( - ts_file, - 0, - f"Language header '{lang_attr}' does not match filename '{file_lang}'", + if root.attrib.get("language", "") != file_lang: + warnings.append( + { + "file": ts_file, + "line": 0, + "message": f"Language header mismatch '{root.attrib.get('language', '')}' != '{file_lang}'", + } ) - warnings += 1 - # Iterate messages - message_index = 0 line_idx = 0 for context in root.findall("context"): for message in context.findall("message"): - message_index += 1 - - # Approximate line number by searching for after last index - approx_line = 0 - for i in range(line_idx, len(lines)): - if " + approx_line = next( + (i + 1 for i in range(line_idx, len(lines)) if " Date: Sat, 10 Jan 2026 22:12:39 +0100 Subject: [PATCH 4/5] Add pygithub dependency --- .github/workflows/translation-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/translation-check.yml b/.github/workflows/translation-check.yml index a30354127b..e3e9ccc494 100644 --- a/.github/workflows/translation-check.yml +++ b/.github/workflows/translation-check.yml @@ -26,6 +26,6 @@ jobs: - name: "Check Windows installer translations" run: ./tools/check-wininstaller-translations.sh - name: "Check application translations" - run: ./tools/check-translations.py + run: pip install PyGithub && ./tools/check-translations.py #- name: "Check for duplicate hotkeys (will not fail)" # run: sudo apt install libxml-simple-perl && cd src/translation/ && perl ./tools/checkkeys.pl From 707f15e89cae710b4ab12d3a4e43d18b52a62fa8 Mon Sep 17 00:00:00 2001 From: ann0see <20726856+ann0see@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:13:27 +0100 Subject: [PATCH 5/5] Fix import --- tools/check-translations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/check-translations.py b/tools/check-translations.py index 5e1dc2cc9a..78552c8b70 100755 --- a/tools/check-translations.py +++ b/tools/check-translations.py @@ -27,6 +27,7 @@ ############################################################################## import re +import os import xml.etree.ElementTree as ET from collections import defaultdict from pathlib import Path