From 11c1a0da55ba92a60d756d45be4c8de5b1ab1d94 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 20 May 2025 18:39:13 +0100 Subject: [PATCH 1/3] 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 91e12dfa8..7a3cef12f 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 86f5fcacd..390600c6f 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/3] 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 390600c6f..abaffb37f 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/3] Fixup --- manage_translation.py | 314 +++++++++++++++++++++--------------------- 1 file changed, 157 insertions(+), 157 deletions(-) diff --git a/manage_translation.py b/manage_translation.py index abaffb37f..c5059d6ec 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():