diff --git a/.github/workflows/translation-lint.yml b/.github/workflows/translation-lint.yml index a4d622e0b..096c34a47 100644 --- a/.github/workflows/translation-lint.yml +++ b/.github/workflows/translation-lint.yml @@ -4,14 +4,20 @@ on: pull_request: paths: - 'src/duckstation-qt/translations/*.ts' + - 'src/duckstation-qt/translations/*.sh' + - 'src/duckstation-qt/translations/*.bat' - 'scripts/translation/**' + - '.github/workflows/translation-lint.yml' push: branches: - master - dev paths: - 'src/duckstation-qt/translations/*.ts' + - 'src/duckstation-qt/translations/*.sh' + - 'src/duckstation-qt/translations/*.bat' - 'scripts/translation/**' + - '.github/workflows/translation-lint.yml' workflow_dispatch: permissions: @@ -29,20 +35,27 @@ jobs: - name: Test Translation Tools run: python -m unittest discover -s scripts/translation/tests -v - - name: Check Translation Placeholders + - name: Check Translation Catalogs shell: bash env: EVENT_NAME: ${{ github.event_name }} BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} + BEFORE_SHA: ${{ github.event.before }} + AFTER_SHA: ${{ github.sha }} run: | if [[ "$EVENT_NAME" == "pull_request" ]]; then mapfile -t files < <( git diff --name-only --diff-filter=ACMRT "$BASE_SHA...$HEAD_SHA" -- \ 'src/duckstation-qt/translations/*.ts' ) + elif [[ "$EVENT_NAME" == "push" ]]; then + mapfile -t files < <( + git diff --name-only --diff-filter=ACMRT "$BEFORE_SHA..$AFTER_SHA" -- \ + 'src/duckstation-qt/translations/*.ts' + ) else - files=(src/duckstation-qt/translations/*.ts) + files=() fi if (( ${#files[@]} == 0 )); then @@ -51,5 +64,6 @@ jobs: fi for file in "${files[@]}"; do - python scripts/translation/validate_ts.py "$file" + python scripts/translation/validate_ts.py \ + --reject-obsolete --reject-locations "$file" done diff --git a/scripts/translation/tests/test_translation_tools.py b/scripts/translation/tests/test_translation_tools.py index 0d96782ae..1e7c16042 100644 --- a/scripts/translation/tests/test_translation_tools.py +++ b/scripts/translation/tests/test_translation_tools.py @@ -549,6 +549,18 @@ class TranslationToolTests(unittest.TestCase): strict = self.run_validator(catalog, "--strict-required-tags", expect=1) self.assertIn("missing rich-text tags", strict.stdout) + def test_validation_can_reject_locations_and_old_messages(self) -> None: + with tempfile.TemporaryDirectory() as directory: + catalog = Path(directory) / "catalog.ts" + catalog.write_text(FIXTURE, encoding="utf-8") + + locations = self.run_validator(catalog, "--reject-locations", expect=1) + self.assertIn("source location information is not permitted", locations.stdout) + + obsolete = self.run_validator(catalog, "--reject-obsolete", expect=1) + self.assertIn("vanished message is not permitted", obsolete.stdout) + self.assertIn("obsolete message is not permitted", obsolete.stdout) + if __name__ == "__main__": unittest.main() diff --git a/scripts/translation/validate_ts.py b/scripts/translation/validate_ts.py index 06f22a86f..c2b5ab6f5 100644 --- a/scripts/translation/validate_ts.py +++ b/scripts/translation/validate_ts.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Validate Qt TS structure, completeness, placeholders, plurals, and rich text.""" +"""Validate Qt TS structure, metadata, completeness, placeholders, plurals, and rich text.""" from __future__ import annotations @@ -44,6 +44,16 @@ def parse_args() -> argparse.Namespace: help="treat missing or unbalanced rich-text tags as errors", ) parser.add_argument("--strict-extra-tags", action="store_true", help="treat added rich-text tags as errors") + parser.add_argument( + "--reject-obsolete", + action="store_true", + help="fail if the catalog contains obsolete or vanished messages", + ) + parser.add_argument( + "--reject-locations", + action="store_true", + help="fail if the catalog contains source location information", + ) return parser.parse_args() @@ -90,12 +100,16 @@ def main() -> int: type_counts: collections.Counter[str] = collections.Counter() for message in messages: type_counts[message.translation_type] += 1 + label = diagnostic_label(args.catalog, message) + if args.reject_obsolete and message.translation_type in SKIPPED_TYPES: + errors.append(f"{label}: {message.translation_type} message is not permitted") + if args.reject_locations and message.locations: + errors.append(f"{label}: source location information is not permitted") if message.translation_type in SKIPPED_TYPES: continue if selected_ids is not None and message.identifier not in selected_ids: continue checked += 1 - label = diagnostic_label(args.catalog, message) if not args.placeholders_only: if message.identity.numerus and not message.plural_translations: errors.append(f"{label}: numerus message has no forms")