From 11c1a0da55ba92a60d756d45be4c8de5b1ab1d94 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 20 May 2025 18:39:13 +0100 Subject: [PATCH 1/4] Add script --- .github/workflows/update-lint-and-build.yml | 2 +- manage_translation.py | 42 ++++++++++++++++++--- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/.github/workflows/update-lint-and-build.yml b/.github/workflows/update-lint-and-build.yml index 91e12dfa82..7a3cef12f8 100644 --- a/.github/workflows/update-lint-and-build.yml +++ b/.github/workflows/update-lint-and-build.yml @@ -56,7 +56,7 @@ jobs: run: > ! git diff -I'^"POT-Creation-Date: ' -I'^"Language-Team: ' -I'^# ' -I'^"Last-Translator: ' -I'^"Project-Id-Version: ' --exit-code && echo "SIGNIFICANT_CHANGES=1" >> $GITHUB_ENV || exit 0 - run: git add . - - run: git commit -m 'Update translation from Transifex' + - run: git commit -m '$(python manage_translation.py generate_commit_msg)' if: env.SIGNIFICANT_CHANGES - name: Push commit uses: ad-m/github-push-action@master diff --git a/manage_translation.py b/manage_translation.py index 86f5fcacd6..390600c6fb 100755 --- a/manage_translation.py +++ b/manage_translation.py @@ -11,6 +11,7 @@ # files. # * recreate_tx_config: recreate configuration for all resources. # * warn_about_files_to_delete: lists files that are not available upstream +# * generate_commit_msg: generates commit message with co-authors from argparse import ArgumentParser import os @@ -19,7 +20,7 @@ from difflib import SequenceMatcher from logging import info from pathlib import Path -from subprocess import call +from subprocess import call, run, CalledProcessError import sys from tempfile import TemporaryDirectory from typing import Self, Generator, Iterable @@ -29,6 +30,8 @@ from transifex.api import transifex_api LANGUAGE = 'pl' +PROJECT_SLUG = 'python-newest' +VERSION = '3.14' def fetch(): @@ -49,10 +52,6 @@ def _call(command: str): exit(return_code) -PROJECT_SLUG = 'python-newest' -VERSION = '3.14' - - def recreate_tx_config(): """ Regenerate Transifex client config for all resources. @@ -187,8 +186,39 @@ def language_switcher(entry: ResourceLanguageStatistics) -> bool: return any(entry.name.startswith(prefix) for prefix in language_switcher_resources_prefixes) +def generate_commit_msg(): + """Generate a commit message + Parses staged files and generates a commit message with Last-Translator's as + co-authors. + """ + translators: set[str] = set() + + result = run(["git", "diff", "--cached", "--name-only", "--diff-filter=ACM"], capture_output=True, text=True, check=True) + staged = [filename for filename in result.stdout.splitlines() if filename.endswith(".po")] + + for file in staged: + staged_file = run(["git", "show", f":{file}"], capture_output=True, text=True, check=True).stdout + try: + old_file = run(["git", "show", f"HEAD:{file}"], capture_output=True, text=True, check=True).stdout + except CalledProcessError: + old_file = "" + + new_po = pofile(staged_file) + old_po = pofile(old_file) if old_file else POFile() + old_entries = {entry.msgid: entry.msgstr for entry in old_po} + + for entry in new_po: + if entry.msgstr and (entry.msgid not in old_entries or old_entries[entry.msgid] != entry.msgstr): + translator = new_po.metadata.get("Last-Translator") + translator = translator.split(",")[0].strip() + if translator: + translators.add(f'Co-Authored-By: {translator}') + break + + print('Update translation from Transifex\n' + "\n".join(translators)) + if __name__ == "__main__": - RUNNABLE_SCRIPTS = ('fetch', 'recreate_tx_config', 'warn_about_files_to_delete') + RUNNABLE_SCRIPTS = ('fetch', 'recreate_tx_config', 'warn_about_files_to_delete', 'generate_commit_msg') parser = ArgumentParser() parser.add_argument('cmd', choices=RUNNABLE_SCRIPTS) From de849c84b3b9320c628c1c1e74c3f24694cd9018 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 21 May 2025 18:24:47 +0100 Subject: [PATCH 2/4] One more line --- manage_translation.py | 316 +++++++++++++++++++++--------------------- 1 file changed, 158 insertions(+), 158 deletions(-) diff --git a/manage_translation.py b/manage_translation.py index 390600c6fb..abaffb37f1 100755 --- a/manage_translation.py +++ b/manage_translation.py @@ -27,163 +27,163 @@ from warnings import warn from polib import pofile -from transifex.api import transifex_api - -LANGUAGE = 'pl' -PROJECT_SLUG = 'python-newest' -VERSION = '3.14' - - -def fetch(): - """ - Fetch translations from Transifex, remove source lines. - """ - if (code := call("tx --version", shell=True)) != 0: - sys.stderr.write("The Transifex client app is required.\n") - exit(code) - lang = LANGUAGE - _call(f'tx pull -l {lang} --minimum-perc=1 --force --skip') - for file in Path().rglob('*.po'): - _call(f'msgcat --no-location -o {file} {file}') - - -def _call(command: str): - if (return_code := call(command, shell=True)) != 0: - exit(return_code) - - -def recreate_tx_config(): - """ - Regenerate Transifex client config for all resources. - """ - with TemporaryDirectory() as directory: - with chdir(directory): - _clone_cpython_repo(VERSION) - _build_gettext() - with chdir(Path(directory) / 'cpython/Doc/build'): - _create_txconfig() - _update_txconfig_resources() - with open('.tx/config', 'r') as file: - contents = file.read() - contents = contents.replace('.//LC_MESSAGES/', '') - with open('.tx/config', 'w') as file: - file.write(contents) - warn_about_files_to_delete() - -def warn_about_files_to_delete(): - files = list(_get_files_to_delete()) - if not files: - return - warn(f'Found {len(files)} file(s) to delete: {", ".join(files)}.') - -def _get_files_to_delete(): - with open('.tx/config') as config_file: - config = config_file.read() - for file in Path().rglob('*.po'): - if os.fsdecode(file) not in config: - yield os.fsdecode(file) - - -def _clone_cpython_repo(version: str): - _call(f'git clone -b {version} --single-branch https://github.com/python/cpython.git --depth 1') - - -def _build_gettext(): - _call("make -C cpython/Doc/ gettext") - - -def _create_txconfig(): - _call('sphinx-intl create-txconfig') - - -def _update_txconfig_resources(): - _call( - f'sphinx-intl update-txconfig-resources --transifex-organization-name python-doc ' - f'--transifex-project-name={PROJECT_SLUG} --locale-dir . --pot-dir gettext' - ) - - -@dataclass -class ResourceLanguageStatistics: - name: str - total_words: int - translated_words: int - total_strings: int - translated_strings: int - - @classmethod - def from_api_entry(cls, data: transifex_api.ResourceLanguageStats) -> Self: - return cls( - name=data.id.removeprefix(f'o:python-doc:p:{PROJECT_SLUG}:r:').removesuffix(f':l:{LANGUAGE}'), - total_words=data.attributes['total_words'], - translated_words=data.attributes['translated_words'], - total_strings=data.attributes['total_strings'], - translated_strings=data.attributes['translated_strings'], - ) - - -def _get_tx_token() -> str: - if os.path.exists('.tx/api-key'): - with open('.tx/api-key') as f: - transifex_api_key = f.read() - else: - transifex_api_key = os.getenv('TX_TOKEN', '') - return transifex_api_key - - -def _get_resources() -> list[transifex_api.Resource]: - transifex_api.setup(auth=_get_tx_token()) - return transifex_api.Resource.filter(project=f'o:python-doc:p:{PROJECT_SLUG}').all() - - -def get_resource_language_stats() -> list[ResourceLanguageStatistics]: - transifex_api.setup(auth=_get_tx_token()) - resources = transifex_api.ResourceLanguageStats.filter( - project=f'o:python-doc:p:{PROJECT_SLUG}', language=f'l:{LANGUAGE}' - ).all() - return [ResourceLanguageStatistics.from_api_entry(entry) for entry in resources] - - -def progress_from_resources(resources: Iterable[ResourceLanguageStatistics]) -> float: - pairs = ((e.translated_words, e.total_words) for e in resources) - translated_total, total_total = (sum(counts) for counts in zip(*pairs)) - return translated_total / total_total * 100 - - -def get_number_of_translators(): - translators = set(_fetch_translators()) - _remove_bot(translators) - translators = _eliminate_aliases(translators) - return len(translators) - - -def _fetch_translators() -> Generator[str, None, None]: - for file in Path().rglob('*.po'): - header = pofile(file).header.splitlines() - for translator_record in header[header.index('Translators:') + 1:]: - translator, _year = translator_record.split(', ') - yield translator - - -def _remove_bot(translators: set[str]) -> None: - translators.remove("Transifex Bot <>") - - -def _eliminate_aliases(translators: set[str]) -> set[str]: - unique = set() - for name in translators: - for match in unique: - if (ratio := SequenceMatcher(lambda x: x in '<>@', name, match).ratio()) > 0.64: - info(f"{name} and {match} are similar ({ratio:.3f}). Deduplicating.") - break - else: - unique.add(name) - return unique - - -def language_switcher(entry: ResourceLanguageStatistics) -> bool: - language_switcher_resources_prefixes = ('bugs', 'tutorial', 'library--functions') - return any(entry.name.startswith(prefix) for prefix in language_switcher_resources_prefixes) +# from transifex.api import transifex_api +# +# LANGUAGE = 'pl' +# PROJECT_SLUG = 'python-newest' +# VERSION = '3.14' +# +# +# def fetch(): +# """ +# Fetch translations from Transifex, remove source lines. +# """ +# if (code := call("tx --version", shell=True)) != 0: +# sys.stderr.write("The Transifex client app is required.\n") +# exit(code) +# lang = LANGUAGE +# _call(f'tx pull -l {lang} --minimum-perc=1 --force --skip') +# for file in Path().rglob('*.po'): +# _call(f'msgcat --no-location -o {file} {file}') +# +# +# def _call(command: str): +# if (return_code := call(command, shell=True)) != 0: +# exit(return_code) +# +# +# def recreate_tx_config(): +# """ +# Regenerate Transifex client config for all resources. +# """ +# with TemporaryDirectory() as directory: +# with chdir(directory): +# _clone_cpython_repo(VERSION) +# _build_gettext() +# with chdir(Path(directory) / 'cpython/Doc/build'): +# _create_txconfig() +# _update_txconfig_resources() +# with open('.tx/config', 'r') as file: +# contents = file.read() +# contents = contents.replace('.//LC_MESSAGES/', '') +# with open('.tx/config', 'w') as file: +# file.write(contents) +# warn_about_files_to_delete() +# +# def warn_about_files_to_delete(): +# files = list(_get_files_to_delete()) +# if not files: +# return +# warn(f'Found {len(files)} file(s) to delete: {", ".join(files)}.') +# +# def _get_files_to_delete(): +# with open('.tx/config') as config_file: +# config = config_file.read() +# for file in Path().rglob('*.po'): +# if os.fsdecode(file) not in config: +# yield os.fsdecode(file) +# +# +# def _clone_cpython_repo(version: str): +# _call(f'git clone -b {version} --single-branch https://github.com/python/cpython.git --depth 1') +# +# +# def _build_gettext(): +# _call("make -C cpython/Doc/ gettext") +# +# +# def _create_txconfig(): +# _call('sphinx-intl create-txconfig') +# +# +# def _update_txconfig_resources(): +# _call( +# f'sphinx-intl update-txconfig-resources --transifex-organization-name python-doc ' +# f'--transifex-project-name={PROJECT_SLUG} --locale-dir . --pot-dir gettext' +# ) +# +# +# @dataclass +# class ResourceLanguageStatistics: +# name: str +# total_words: int +# translated_words: int +# total_strings: int +# translated_strings: int +# +# @classmethod +# def from_api_entry(cls, data: transifex_api.ResourceLanguageStats) -> Self: +# return cls( +# name=data.id.removeprefix(f'o:python-doc:p:{PROJECT_SLUG}:r:').removesuffix(f':l:{LANGUAGE}'), +# total_words=data.attributes['total_words'], +# translated_words=data.attributes['translated_words'], +# total_strings=data.attributes['total_strings'], +# translated_strings=data.attributes['translated_strings'], +# ) +# +# +# def _get_tx_token() -> str: +# if os.path.exists('.tx/api-key'): +# with open('.tx/api-key') as f: +# transifex_api_key = f.read() +# else: +# transifex_api_key = os.getenv('TX_TOKEN', '') +# return transifex_api_key +# +# +# def _get_resources() -> list[transifex_api.Resource]: +# transifex_api.setup(auth=_get_tx_token()) +# return transifex_api.Resource.filter(project=f'o:python-doc:p:{PROJECT_SLUG}').all() +# +# +# def get_resource_language_stats() -> list[ResourceLanguageStatistics]: +# transifex_api.setup(auth=_get_tx_token()) +# resources = transifex_api.ResourceLanguageStats.filter( +# project=f'o:python-doc:p:{PROJECT_SLUG}', language=f'l:{LANGUAGE}' +# ).all() +# return [ResourceLanguageStatistics.from_api_entry(entry) for entry in resources] +# +# +# def progress_from_resources(resources: Iterable[ResourceLanguageStatistics]) -> float: +# pairs = ((e.translated_words, e.total_words) for e in resources) +# translated_total, total_total = (sum(counts) for counts in zip(*pairs)) +# return translated_total / total_total * 100 +# +# +# def get_number_of_translators(): +# translators = set(_fetch_translators()) +# _remove_bot(translators) +# translators = _eliminate_aliases(translators) +# return len(translators) +# +# +# def _fetch_translators() -> Generator[str, None, None]: +# for file in Path().rglob('*.po'): +# header = pofile(file).header.splitlines() +# for translator_record in header[header.index('Translators:') + 1:]: +# translator, _year = translator_record.split(', ') +# yield translator +# +# +# def _remove_bot(translators: set[str]) -> None: +# translators.remove("Transifex Bot <>") +# +# +# def _eliminate_aliases(translators: set[str]) -> set[str]: +# unique = set() +# for name in translators: +# for match in unique: +# if (ratio := SequenceMatcher(lambda x: x in '<>@', name, match).ratio()) > 0.64: +# info(f"{name} and {match} are similar ({ratio:.3f}). Deduplicating.") +# break +# else: +# unique.add(name) +# return unique +# +# +# def language_switcher(entry: ResourceLanguageStatistics) -> bool: +# language_switcher_resources_prefixes = ('bugs', 'tutorial', 'library--functions') +# return any(entry.name.startswith(prefix) for prefix in language_switcher_resources_prefixes) def generate_commit_msg(): @@ -215,7 +215,7 @@ def generate_commit_msg(): translators.add(f'Co-Authored-By: {translator}') break - print('Update translation from Transifex\n' + "\n".join(translators)) + print('Update translation from Transifex\n\n' + "\n".join(translators)) if __name__ == "__main__": RUNNABLE_SCRIPTS = ('fetch', 'recreate_tx_config', 'warn_about_files_to_delete', 'generate_commit_msg') From 6f78e8c76b235ed83b075f700994a3d88f0777d1 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 21 May 2025 18:25:09 +0100 Subject: [PATCH 3/4] Fixup --- manage_translation.py | 314 +++++++++++++++++++++--------------------- 1 file changed, 157 insertions(+), 157 deletions(-) diff --git a/manage_translation.py b/manage_translation.py index abaffb37f1..c5059d6ec6 100755 --- a/manage_translation.py +++ b/manage_translation.py @@ -27,163 +27,163 @@ from warnings import warn from polib import pofile -# from transifex.api import transifex_api -# -# LANGUAGE = 'pl' -# PROJECT_SLUG = 'python-newest' -# VERSION = '3.14' -# -# -# def fetch(): -# """ -# Fetch translations from Transifex, remove source lines. -# """ -# if (code := call("tx --version", shell=True)) != 0: -# sys.stderr.write("The Transifex client app is required.\n") -# exit(code) -# lang = LANGUAGE -# _call(f'tx pull -l {lang} --minimum-perc=1 --force --skip') -# for file in Path().rglob('*.po'): -# _call(f'msgcat --no-location -o {file} {file}') -# -# -# def _call(command: str): -# if (return_code := call(command, shell=True)) != 0: -# exit(return_code) -# -# -# def recreate_tx_config(): -# """ -# Regenerate Transifex client config for all resources. -# """ -# with TemporaryDirectory() as directory: -# with chdir(directory): -# _clone_cpython_repo(VERSION) -# _build_gettext() -# with chdir(Path(directory) / 'cpython/Doc/build'): -# _create_txconfig() -# _update_txconfig_resources() -# with open('.tx/config', 'r') as file: -# contents = file.read() -# contents = contents.replace('.//LC_MESSAGES/', '') -# with open('.tx/config', 'w') as file: -# file.write(contents) -# warn_about_files_to_delete() -# -# def warn_about_files_to_delete(): -# files = list(_get_files_to_delete()) -# if not files: -# return -# warn(f'Found {len(files)} file(s) to delete: {", ".join(files)}.') -# -# def _get_files_to_delete(): -# with open('.tx/config') as config_file: -# config = config_file.read() -# for file in Path().rglob('*.po'): -# if os.fsdecode(file) not in config: -# yield os.fsdecode(file) -# -# -# def _clone_cpython_repo(version: str): -# _call(f'git clone -b {version} --single-branch https://github.com/python/cpython.git --depth 1') -# -# -# def _build_gettext(): -# _call("make -C cpython/Doc/ gettext") -# -# -# def _create_txconfig(): -# _call('sphinx-intl create-txconfig') -# -# -# def _update_txconfig_resources(): -# _call( -# f'sphinx-intl update-txconfig-resources --transifex-organization-name python-doc ' -# f'--transifex-project-name={PROJECT_SLUG} --locale-dir . --pot-dir gettext' -# ) -# -# -# @dataclass -# class ResourceLanguageStatistics: -# name: str -# total_words: int -# translated_words: int -# total_strings: int -# translated_strings: int -# -# @classmethod -# def from_api_entry(cls, data: transifex_api.ResourceLanguageStats) -> Self: -# return cls( -# name=data.id.removeprefix(f'o:python-doc:p:{PROJECT_SLUG}:r:').removesuffix(f':l:{LANGUAGE}'), -# total_words=data.attributes['total_words'], -# translated_words=data.attributes['translated_words'], -# total_strings=data.attributes['total_strings'], -# translated_strings=data.attributes['translated_strings'], -# ) -# -# -# def _get_tx_token() -> str: -# if os.path.exists('.tx/api-key'): -# with open('.tx/api-key') as f: -# transifex_api_key = f.read() -# else: -# transifex_api_key = os.getenv('TX_TOKEN', '') -# return transifex_api_key -# -# -# def _get_resources() -> list[transifex_api.Resource]: -# transifex_api.setup(auth=_get_tx_token()) -# return transifex_api.Resource.filter(project=f'o:python-doc:p:{PROJECT_SLUG}').all() -# -# -# def get_resource_language_stats() -> list[ResourceLanguageStatistics]: -# transifex_api.setup(auth=_get_tx_token()) -# resources = transifex_api.ResourceLanguageStats.filter( -# project=f'o:python-doc:p:{PROJECT_SLUG}', language=f'l:{LANGUAGE}' -# ).all() -# return [ResourceLanguageStatistics.from_api_entry(entry) for entry in resources] -# -# -# def progress_from_resources(resources: Iterable[ResourceLanguageStatistics]) -> float: -# pairs = ((e.translated_words, e.total_words) for e in resources) -# translated_total, total_total = (sum(counts) for counts in zip(*pairs)) -# return translated_total / total_total * 100 -# -# -# def get_number_of_translators(): -# translators = set(_fetch_translators()) -# _remove_bot(translators) -# translators = _eliminate_aliases(translators) -# return len(translators) -# -# -# def _fetch_translators() -> Generator[str, None, None]: -# for file in Path().rglob('*.po'): -# header = pofile(file).header.splitlines() -# for translator_record in header[header.index('Translators:') + 1:]: -# translator, _year = translator_record.split(', ') -# yield translator -# -# -# def _remove_bot(translators: set[str]) -> None: -# translators.remove("Transifex Bot <>") -# -# -# def _eliminate_aliases(translators: set[str]) -> set[str]: -# unique = set() -# for name in translators: -# for match in unique: -# if (ratio := SequenceMatcher(lambda x: x in '<>@', name, match).ratio()) > 0.64: -# info(f"{name} and {match} are similar ({ratio:.3f}). Deduplicating.") -# break -# else: -# unique.add(name) -# return unique -# -# -# def language_switcher(entry: ResourceLanguageStatistics) -> bool: -# language_switcher_resources_prefixes = ('bugs', 'tutorial', 'library--functions') -# return any(entry.name.startswith(prefix) for prefix in language_switcher_resources_prefixes) +from transifex.api import transifex_api + +LANGUAGE = 'pl' +PROJECT_SLUG = 'python-newest' +VERSION = '3.14' + + +def fetch(): + """ + Fetch translations from Transifex, remove source lines. + """ + if (code := call("tx --version", shell=True)) != 0: + sys.stderr.write("The Transifex client app is required.\n") + exit(code) + lang = LANGUAGE + _call(f'tx pull -l {lang} --minimum-perc=1 --force --skip') + for file in Path().rglob('*.po'): + _call(f'msgcat --no-location -o {file} {file}') + + +def _call(command: str): + if (return_code := call(command, shell=True)) != 0: + exit(return_code) + + +def recreate_tx_config(): + """ + Regenerate Transifex client config for all resources. + """ + with TemporaryDirectory() as directory: + with chdir(directory): + _clone_cpython_repo(VERSION) + _build_gettext() + with chdir(Path(directory) / 'cpython/Doc/build'): + _create_txconfig() + _update_txconfig_resources() + with open('.tx/config', 'r') as file: + contents = file.read() + contents = contents.replace('.//LC_MESSAGES/', '') + with open('.tx/config', 'w') as file: + file.write(contents) + warn_about_files_to_delete() + +def warn_about_files_to_delete(): + files = list(_get_files_to_delete()) + if not files: + return + warn(f'Found {len(files)} file(s) to delete: {", ".join(files)}.') + +def _get_files_to_delete(): + with open('.tx/config') as config_file: + config = config_file.read() + for file in Path().rglob('*.po'): + if os.fsdecode(file) not in config: + yield os.fsdecode(file) + + +def _clone_cpython_repo(version: str): + _call(f'git clone -b {version} --single-branch https://github.com/python/cpython.git --depth 1') + + +def _build_gettext(): + _call("make -C cpython/Doc/ gettext") + + +def _create_txconfig(): + _call('sphinx-intl create-txconfig') + + +def _update_txconfig_resources(): + _call( + f'sphinx-intl update-txconfig-resources --transifex-organization-name python-doc ' + f'--transifex-project-name={PROJECT_SLUG} --locale-dir . --pot-dir gettext' + ) + + +@dataclass +class ResourceLanguageStatistics: + name: str + total_words: int + translated_words: int + total_strings: int + translated_strings: int + + @classmethod + def from_api_entry(cls, data: transifex_api.ResourceLanguageStats) -> Self: + return cls( + name=data.id.removeprefix(f'o:python-doc:p:{PROJECT_SLUG}:r:').removesuffix(f':l:{LANGUAGE}'), + total_words=data.attributes['total_words'], + translated_words=data.attributes['translated_words'], + total_strings=data.attributes['total_strings'], + translated_strings=data.attributes['translated_strings'], + ) + + +def _get_tx_token() -> str: + if os.path.exists('.tx/api-key'): + with open('.tx/api-key') as f: + transifex_api_key = f.read() + else: + transifex_api_key = os.getenv('TX_TOKEN', '') + return transifex_api_key + + +def _get_resources() -> list[transifex_api.Resource]: + transifex_api.setup(auth=_get_tx_token()) + return transifex_api.Resource.filter(project=f'o:python-doc:p:{PROJECT_SLUG}').all() + + +def get_resource_language_stats() -> list[ResourceLanguageStatistics]: + transifex_api.setup(auth=_get_tx_token()) + resources = transifex_api.ResourceLanguageStats.filter( + project=f'o:python-doc:p:{PROJECT_SLUG}', language=f'l:{LANGUAGE}' + ).all() + return [ResourceLanguageStatistics.from_api_entry(entry) for entry in resources] + + +def progress_from_resources(resources: Iterable[ResourceLanguageStatistics]) -> float: + pairs = ((e.translated_words, e.total_words) for e in resources) + translated_total, total_total = (sum(counts) for counts in zip(*pairs)) + return translated_total / total_total * 100 + + +def get_number_of_translators(): + translators = set(_fetch_translators()) + _remove_bot(translators) + translators = _eliminate_aliases(translators) + return len(translators) + + +def _fetch_translators() -> Generator[str, None, None]: + for file in Path().rglob('*.po'): + header = pofile(file).header.splitlines() + for translator_record in header[header.index('Translators:') + 1:]: + translator, _year = translator_record.split(', ') + yield translator + + +def _remove_bot(translators: set[str]) -> None: + translators.remove("Transifex Bot <>") + + +def _eliminate_aliases(translators: set[str]) -> set[str]: + unique = set() + for name in translators: + for match in unique: + if (ratio := SequenceMatcher(lambda x: x in '<>@', name, match).ratio()) > 0.64: + info(f"{name} and {match} are similar ({ratio:.3f}). Deduplicating.") + break + else: + unique.add(name) + return unique + + +def language_switcher(entry: ResourceLanguageStatistics) -> bool: + language_switcher_resources_prefixes = ('bugs', 'tutorial', 'library--functions') + return any(entry.name.startswith(prefix) for prefix in language_switcher_resources_prefixes) def generate_commit_msg(): From 3d6a1487df3f4820e6da44a0eefe35e2dd9a96b6 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Mon, 30 Jun 2025 14:00:57 +0100 Subject: [PATCH 4/4] Pre-commit --- .github/workflows/update-lint-and-build.yml | 2 +- manage_translation.py | 55 +- translation_progress_en.svg | 40304 +++++++++--------- 3 files changed, 20194 insertions(+), 20167 deletions(-) diff --git a/.github/workflows/update-lint-and-build.yml b/.github/workflows/update-lint-and-build.yml index c315f25953..117f89ec05 100644 --- a/.github/workflows/update-lint-and-build.yml +++ b/.github/workflows/update-lint-and-build.yml @@ -56,7 +56,7 @@ jobs: run: > ! git diff -I'^"POT-Creation-Date: ' -I'^"Language-Team: ' -I'^# ' -I'^"Last-Translator: ' -I'^"Project-Id-Version: ' --exit-code && echo "SIGNIFICANT_CHANGES=1" >> "$GITHUB_ENV" || exit 0 - run: git add . - - run: git commit -m '$(python manage_translation.py generate_commit_msg)' + - run: git commit -m "$(python manage_translation.py generate_commit_msg)" if: env.SIGNIFICANT_CHANGES - name: Push commit uses: ad-m/github-push-action@master diff --git a/manage_translation.py b/manage_translation.py index a950a8a781..a41b4a91c3 100755 --- a/manage_translation.py +++ b/manage_translation.py @@ -26,7 +26,7 @@ from typing import Self, Generator, Iterable from warnings import warn -from polib import pofile +from polib import pofile, POFile from transifex.api import transifex_api LANGUAGE = 'pl' @@ -150,12 +150,17 @@ def get_resource_language_stats() -> list[ResourceLanguageStatistics]: return [ResourceLanguageStatistics.from_api_entry(entry) for entry in resources] -def progress_from_resources(resources: Iterable[ResourceLanguageStatistics]) -> tuple[float, float]: +def progress_from_resources( + resources: Iterable[ResourceLanguageStatistics], +) -> tuple[float, float]: word_pairs = ((e.translated_words, e.total_words) for e in resources) string_pairs = ((e.translated_strings, e.total_strings) for e in resources) translated_total_words, total_words = (sum(counts) for counts in zip(*word_pairs)) translated_total_strs, total_strs = (sum(counts) for counts in zip(*string_pairs)) - return translated_total_words / total_words * 100, translated_total_strs / total_strs * 100 + return ( + translated_total_words / total_words * 100, + translated_total_strs / total_strs * 100, + ) def get_number_of_translators(): @@ -205,33 +210,55 @@ def generate_commit_msg(): """ translators: set[str] = set() - result = run(["git", "diff", "--cached", "--name-only", "--diff-filter=ACM"], capture_output=True, text=True, check=True) - staged = [filename for filename in result.stdout.splitlines() if filename.endswith(".po")] + result = run( + ['git', 'diff', '--cached', '--name-only', '--diff-filter=ACM'], + capture_output=True, + text=True, + check=True, + ) + staged = [ + filename for filename in result.stdout.splitlines() if filename.endswith('.po') + ] for file in staged: - staged_file = run(["git", "show", f":{file}"], capture_output=True, text=True, check=True).stdout + staged_file = run( + ['git', 'show', f':{file}'], capture_output=True, text=True, check=True + ).stdout try: - old_file = run(["git", "show", f"HEAD:{file}"], capture_output=True, text=True, check=True).stdout + old_file = run( + ['git', 'show', f'HEAD:{file}'], + capture_output=True, + text=True, + check=True, + ).stdout except CalledProcessError: - old_file = "" + old_file = '' new_po = pofile(staged_file) old_po = pofile(old_file) if old_file else POFile() old_entries = {entry.msgid: entry.msgstr for entry in old_po} for entry in new_po: - if entry.msgstr and (entry.msgid not in old_entries or old_entries[entry.msgid] != entry.msgstr): - translator = new_po.metadata.get("Last-Translator") - translator = translator.split(",")[0].strip() + if entry.msgstr and ( + entry.msgid not in old_entries + or old_entries[entry.msgid] != entry.msgstr + ): + translator = new_po.metadata.get('Last-Translator') + translator = translator.split(',')[0].strip() if translator: translators.add(f'Co-Authored-By: {translator}') break - print('Update translation from Transifex\n\n' + "\n".join(translators)) + print('Update translation from Transifex\n\n' + '\n'.join(translators)) -if __name__ == "__main__": - RUNNABLE_SCRIPTS = ('fetch', 'recreate_tx_config', 'warn_about_files_to_delete', 'generate_commit_msg') +if __name__ == '__main__': + RUNNABLE_SCRIPTS = ( + 'fetch', + 'recreate_tx_config', + 'warn_about_files_to_delete', + 'generate_commit_msg', + ) parser = ArgumentParser() parser.add_argument('cmd', choices=RUNNABLE_SCRIPTS) diff --git a/translation_progress_en.svg b/translation_progress_en.svg index 3d067a3358..79e28244d6 100644 --- a/translation_progress_en.svg +++ b/translation_progress_en.svg @@ -21,7647 +21,7647 @@ - - - - @@ -7669,186 +7669,186 @@ z - - @@ -7856,168 +7856,168 @@ z - - @@ -8025,204 +8025,204 @@ z - - @@ -8230,220 +8230,220 @@ z - - @@ -8451,212 +8451,212 @@ z - - @@ -8664,210 +8664,210 @@ z - - @@ -8877,42 +8877,42 @@ z - - @@ -8920,66 +8920,66 @@ z - - @@ -8987,60 +8987,60 @@ z - - @@ -9048,84 +9048,84 @@ z - - @@ -9133,11274 +9133,11274 @@ z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy