mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Extended script to manage translations to support fetching new translations since a given date.
This commit is contained in:
		| @@ -20,13 +20,86 @@ | ||||
|  | ||||
| import os | ||||
| from argparse import ArgumentParser | ||||
| from collections import defaultdict | ||||
| from configparser import ConfigParser | ||||
| from datetime import datetime | ||||
| from subprocess import run | ||||
|  | ||||
| import requests | ||||
|  | ||||
| import django | ||||
| from django.conf import settings | ||||
| from django.core.management import call_command | ||||
|  | ||||
| HAVE_JS = ["admin"] | ||||
| LANG_OVERRIDES = { | ||||
|     "zh_CN": "zh_Hans", | ||||
|     "zh_TW": "zh_Hant", | ||||
| } | ||||
|  | ||||
|  | ||||
| def list_resources_with_updates(date_since, date_skip=None, verbose=False): | ||||
|     resource_lang_changed = defaultdict(list) | ||||
|     resource_lang_unchanged = defaultdict(list) | ||||
|  | ||||
|     # Read token from ENV, otherwise read from the ~/.transifexrc file. | ||||
|     api_token = os.getenv("TRANSIFEX_API_TOKEN") | ||||
|     if not api_token: | ||||
|         parser = ConfigParser() | ||||
|         parser.read(os.path.expanduser("~/.transifexrc")) | ||||
|         api_token = parser.get("https://www.transifex.com", "token") | ||||
|  | ||||
|     assert api_token, "Please define the TRANSIFEX_API_TOKEN env var." | ||||
|     headers = {"Authorization": f"Bearer {api_token}"} | ||||
|     base_url = "https://rest.api.transifex.com" | ||||
|     base_params = {"filter[project]": "o:django:p:django"} | ||||
|  | ||||
|     resources_url = base_url + "/resources" | ||||
|     resource_stats_url = base_url + "/resource_language_stats" | ||||
|  | ||||
|     response = requests.get(resources_url, headers=headers, params=base_params) | ||||
|     assert response.ok, response.content | ||||
|     data = response.json()["data"] | ||||
|  | ||||
|     for item in data: | ||||
|         if item["type"] != "resources": | ||||
|             continue | ||||
|         resource_id = item["id"] | ||||
|         resource_name = item["attributes"]["name"] | ||||
|         params = base_params.copy() | ||||
|         params.update({"filter[resource]": resource_id}) | ||||
|         stats = requests.get(resource_stats_url, headers=headers, params=params) | ||||
|         stats_data = stats.json()["data"] | ||||
|         for lang_data in stats_data: | ||||
|             lang_id = lang_data["id"].split(":")[-1] | ||||
|             lang_attributes = lang_data["attributes"] | ||||
|             last_update = lang_attributes["last_translation_update"] | ||||
|             if verbose: | ||||
|                 print( | ||||
|                     f"CHECKING {resource_name} for {lang_id=} updated on {last_update}" | ||||
|                 ) | ||||
|             if last_update is None: | ||||
|                 resource_lang_unchanged[resource_name].append(lang_id) | ||||
|                 continue | ||||
|  | ||||
|             last_update = datetime.strptime(last_update, "%Y-%m-%dT%H:%M:%SZ") | ||||
|             if last_update > date_since and ( | ||||
|                 date_skip is None or last_update.date() != date_skip.date() | ||||
|             ): | ||||
|                 if verbose: | ||||
|                     print(f"=> CHANGED {lang_attributes=} {date_skip=}") | ||||
|                 resource_lang_changed[resource_name].append(lang_id) | ||||
|             else: | ||||
|                 resource_lang_unchanged[resource_name].append(lang_id) | ||||
|  | ||||
|     if verbose: | ||||
|         unchanged = "\n".join( | ||||
|             f"\n * resource {res} languages {' '.join(sorted(langs))}" | ||||
|             for res, langs in resource_lang_unchanged.items() | ||||
|         ) | ||||
|         print(f"== SUMMARY for unchanged resources ==\n{unchanged}") | ||||
|  | ||||
|     return resource_lang_changed | ||||
|  | ||||
|  | ||||
| def _get_locale_dirs(resources, include_core=True): | ||||
| @@ -152,27 +225,27 @@ def fetch(resources=None, languages=None): | ||||
|     errors = [] | ||||
|  | ||||
|     for name, dir_ in locale_dirs: | ||||
|         cmd = [ | ||||
|             "tx", | ||||
|             "pull", | ||||
|             "-r", | ||||
|             _tx_resource_for_name(name), | ||||
|             "-f", | ||||
|             "--minimum-perc=5", | ||||
|         ] | ||||
|         # Transifex pull | ||||
|         if languages is None: | ||||
|             run( | ||||
|                 [ | ||||
|                     "tx", | ||||
|                     "pull", | ||||
|                     "-r", | ||||
|                     _tx_resource_for_name(name), | ||||
|                     "-a", | ||||
|                     "-f", | ||||
|                     "--minimum-perc=5", | ||||
|                 ] | ||||
|             ) | ||||
|             run(cmd + ["--all"]) | ||||
|             target_langs = sorted( | ||||
|                 d for d in os.listdir(dir_) if not d.startswith("_") and d != "en" | ||||
|             ) | ||||
|         else: | ||||
|             for lang in languages: | ||||
|                 run(["tx", "pull", "-r", _tx_resource_for_name(name), "-f", "-l", lang]) | ||||
|                 run(cmd + ["-l", lang]) | ||||
|             target_langs = languages | ||||
|  | ||||
|         target_langs = [LANG_OVERRIDES.get(d, d) for d in target_langs] | ||||
|  | ||||
|         # msgcat to wrap lines and msgfmt for compilation of .mo file | ||||
|         for lang in target_langs: | ||||
|             po_path = "%(path)s/%(lang)s/LC_MESSAGES/django%(ext)s.po" % { | ||||
| @@ -197,11 +270,25 @@ def fetch(resources=None, languages=None): | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     RUNABLE_SCRIPTS = ("update_catalogs", "lang_stats", "fetch") | ||||
| def fetch_since(date_since, date_skip=None, verbose=False, dry_run=False): | ||||
|     """ | ||||
|     Fetch translations from Transifex that were modified since the given date. | ||||
|     """ | ||||
|     changed = list_resources_with_updates( | ||||
|         date_since=date_since, date_skip=date_skip, verbose=verbose | ||||
|     ) | ||||
|     if verbose: | ||||
|         print(f"== SUMMARY for changed resources {dry_run=} ==\n") | ||||
|     for res, langs in changed.items(): | ||||
|         if verbose: | ||||
|             print(f"\n * resource {res} languages {' '.join(sorted(langs))}") | ||||
|         if not dry_run: | ||||
|             fetch(resources=[res], languages=sorted(langs)) | ||||
|     if not changed and verbose: | ||||
|         print(f"\n No resource changed since {date_since}") | ||||
|  | ||||
|     parser = ArgumentParser() | ||||
|     parser.add_argument("cmd", nargs=1, choices=RUNABLE_SCRIPTS) | ||||
|  | ||||
| def add_common_arguments(parser): | ||||
|     parser.add_argument( | ||||
|         "-r", | ||||
|         "--resources", | ||||
| @@ -214,6 +301,60 @@ if __name__ == "__main__": | ||||
|         action="append", | ||||
|         help="limit operation to the specified languages", | ||||
|     ) | ||||
|     options = parser.parse_args() | ||||
|  | ||||
|     eval(options.cmd[0])(options.resources, options.languages) | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     parser = ArgumentParser() | ||||
|  | ||||
|     subparsers = parser.add_subparsers( | ||||
|         dest="cmd", help="choose the operation to perform" | ||||
|     ) | ||||
|  | ||||
|     parser_update = subparsers.add_parser( | ||||
|         "update_catalogs", | ||||
|         help="update English django.po files with new/updated translatable strings", | ||||
|     ) | ||||
|     add_common_arguments(parser_update) | ||||
|  | ||||
|     parser_stats = subparsers.add_parser( | ||||
|         "lang_stats", | ||||
|         help="print the approximate number of changed/added strings in the en catalog", | ||||
|     ) | ||||
|     add_common_arguments(parser_stats) | ||||
|  | ||||
|     parser_fetch = subparsers.add_parser( | ||||
|         "fetch", | ||||
|         help="fetch translations from Transifex, wrap long lines, generate mo files", | ||||
|     ) | ||||
|     add_common_arguments(parser_fetch) | ||||
|  | ||||
|     parser_fetch = subparsers.add_parser( | ||||
|         "fetch_since", | ||||
|         help=( | ||||
|             "fetch translations from Transifex modified since a given date " | ||||
|             "(for all languages and all resources)" | ||||
|         ), | ||||
|     ) | ||||
|     parser_fetch.add_argument("-v", "--verbose", action="store_true") | ||||
|     parser_fetch.add_argument( | ||||
|         "-s", | ||||
|         "--since", | ||||
|         required=True, | ||||
|         dest="date_since", | ||||
|         metavar="YYYY-MM-DD", | ||||
|         type=datetime.fromisoformat, | ||||
|         help="fetch new translations since this date (ISO format YYYY-MM-DD).", | ||||
|     ) | ||||
|     parser_fetch.add_argument( | ||||
|         "--skip", | ||||
|         dest="date_skip", | ||||
|         metavar="YYYY-MM-DD", | ||||
|         type=datetime.fromisoformat, | ||||
|         help="skip changes from this date (ISO format YYYY-MM-DD).", | ||||
|     ) | ||||
|     parser_fetch.add_argument("--dry-run", dest="dry_run", action="store_true") | ||||
|  | ||||
|     options = parser.parse_args() | ||||
|     kwargs = options.__dict__ | ||||
|     cmd = kwargs.pop("cmd") | ||||
|     eval(cmd)(**kwargs) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user