From d271fac02cae550e8b051ac358465ad0894ec9fd Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Thu, 1 Aug 2024 18:10:26 -0300 Subject: [PATCH 01/14] Add script for handling translations --- babel.cfg | 1 + babel_runner.py | 108 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 babel.cfg create mode 100755 babel_runner.py diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..692580d --- /dev/null +++ b/babel.cfg @@ -0,0 +1 @@ +[jinja2: **.html] diff --git a/babel_runner.py b/babel_runner.py new file mode 100755 index 0000000..a6359af --- /dev/null +++ b/babel_runner.py @@ -0,0 +1,108 @@ +#!/usr/bin/venv python3 +"""Script for handling translations with Babel""" + +import argparse +import os +import subprocess +import tomllib + +PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) + +# Global variables used by pybabel below +DOMAIN = "messages" +COPYRIGHT_HOLDER = "Python Software Foundation" +LOCALES_DIR = os.path.relpath(os.path.join(PROJECT_DIR, "locales")) +POT_FILE = os.path.relpath(os.path.join(LOCALES_DIR, f"{DOMAIN}.pot"), PROJECT_DIR) +SOURCE_DIR = os.path.relpath( + os.path.join(PROJECT_DIR, "python_docs_theme"), PROJECT_DIR +) +MAPPING_FILE = os.path.relpath(os.path.join(PROJECT_DIR, "babel.cfg"), PROJECT_DIR) + + +def get_project_info() -> dict: + """Retrieve project's info to populate the message catalog template""" + with open(os.path.join(PROJECT_DIR, "pyproject.toml"), "rb") as f: + data = tomllib.load(f) + return data["project"] + + +def extract_messages(): + """Extract messages from all source files into template file""" + os.makedirs(LOCALES_DIR, exist_ok=True) + project_data = get_project_info() + subprocess.run( + [ + "pybabel", + "extract", + "-F", + MAPPING_FILE, + "--copyright-holder", + COPYRIGHT_HOLDER, + "--project", + project_data["name"], + "--version", + project_data["version"], + "--msgid-bugs-address", + project_data["urls"]["Issue tracker"], + "-o", + POT_FILE, + SOURCE_DIR, + ], + check=True, + ) + + +def init_locale(locale: str): + """Initialize a new locale based on existing""" + cmd = ["pybabel", "init", "-i", POT_FILE, "-d", LOCALES_DIR, "-l", locale] + subprocess.run(cmd, check=True) + + +def update_catalogs(locale: str): + """Update translations from existing message catalogs""" + cmd = ["pybabel", "update", "-i", POT_FILE, "-d", LOCALES_DIR] + if locale != "": + cmd.append(["-l", locale]) + subprocess.run(cmd, check=True) + + +def compile_catalogs(locale: str): + """Compile existing message catalogs""" + cmd = ["pybabel", "compile", "-d", LOCALES_DIR] + if locale != "": + cmd.append(["-l", locale]) + subprocess.run(cmd, check=True) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "command", + choices=["init", "extract", "update", "compile"], + help="command to be executed", + ) + parser.add_argument( + "-l", + "--locale", + help="language code (needed for init, optional for update and compile)", + ) + + args = parser.parse_args() + locale = args.locale if args.locale else "" + + os.chdir(PROJECT_DIR) + + if args.command == "extract": + extract_messages() + elif args.command == "init": + if locale == "": + parser.error("init requires passing the --locale option") + init_locale(locale) + elif args.command == "update": + update_catalogs(locale) + elif args.command == "compile": + compile_catalogs(locale) + + +if __name__ == "__main__": + main() From 634a20c0dc9d80f63f41352e74c4994cd6b47dfc Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Fri, 2 Aug 2024 13:31:25 -0300 Subject: [PATCH 02/14] Make ruff tests happy --- babel_runner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/babel_runner.py b/babel_runner.py index a6359af..d447a16 100755 --- a/babel_runner.py +++ b/babel_runner.py @@ -1,9 +1,11 @@ #!/usr/bin/venv python3 """Script for handling translations with Babel""" +from __future__ import annotations import argparse import os import subprocess + import tomllib PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) From a809821c3fd5b06a081188c5c50f542da1676716 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Fri, 2 Aug 2024 15:29:19 -0300 Subject: [PATCH 03/14] Rename mapping file to .babel.cfg --- babel.cfg => .babel.cfg | 0 babel_runner.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename babel.cfg => .babel.cfg (100%) diff --git a/babel.cfg b/.babel.cfg similarity index 100% rename from babel.cfg rename to .babel.cfg diff --git a/babel_runner.py b/babel_runner.py index d447a16..039522f 100755 --- a/babel_runner.py +++ b/babel_runner.py @@ -18,7 +18,7 @@ SOURCE_DIR = os.path.relpath( os.path.join(PROJECT_DIR, "python_docs_theme"), PROJECT_DIR ) -MAPPING_FILE = os.path.relpath(os.path.join(PROJECT_DIR, "babel.cfg"), PROJECT_DIR) +MAPPING_FILE = os.path.relpath(os.path.join(PROJECT_DIR, ".babel.cfg"), PROJECT_DIR) def get_project_info() -> dict: From adf715c41dec595f51de89d752c21511f5ad7a6d Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Fri, 2 Aug 2024 17:19:10 -0300 Subject: [PATCH 04/14] Forbid initializing existent po --- babel_runner.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/babel_runner.py b/babel_runner.py index 039522f..ba5e351 100755 --- a/babel_runner.py +++ b/babel_runner.py @@ -29,7 +29,7 @@ def get_project_info() -> dict: def extract_messages(): - """Extract messages from all source files into template file""" + """Extract messages from all source files into message catalog template""" os.makedirs(LOCALES_DIR, exist_ok=True) project_data = get_project_info() subprocess.run( @@ -55,7 +55,11 @@ def extract_messages(): def init_locale(locale: str): - """Initialize a new locale based on existing""" + """Initialize a new locale based on existing message catalog template""" + pofile = os.path.join(LOCALES_DIR, locale, "LC_MESSAGES", f"{DOMAIN}.po") + if os.path.exists(pofile): + print(f"There is already a message catalog for locale {locale}, skipping.") + return cmd = ["pybabel", "init", "-i", POT_FILE, "-d", LOCALES_DIR, "-l", locale] subprocess.run(cmd, check=True) From 66820a55a24cc6188f52ca1f79c2272833e84f32 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Fri, 2 Aug 2024 17:21:13 -0300 Subject: [PATCH 05/14] Extend instead of append list for locale arg --- babel_runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/babel_runner.py b/babel_runner.py index ba5e351..386f34b 100755 --- a/babel_runner.py +++ b/babel_runner.py @@ -68,7 +68,7 @@ def update_catalogs(locale: str): """Update translations from existing message catalogs""" cmd = ["pybabel", "update", "-i", POT_FILE, "-d", LOCALES_DIR] if locale != "": - cmd.append(["-l", locale]) + cmd.extend(["-l", locale]) subprocess.run(cmd, check=True) @@ -76,7 +76,7 @@ def compile_catalogs(locale: str): """Compile existing message catalogs""" cmd = ["pybabel", "compile", "-d", LOCALES_DIR] if locale != "": - cmd.append(["-l", locale]) + cmd.extend(["-l", locale]) subprocess.run(cmd, check=True) From 6b6f0d2787d30670a00ef4552d24ad1ebe5c7d3a Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Fri, 2 Aug 2024 17:22:24 -0300 Subject: [PATCH 06/14] Add translation tests to tests.yml --- .github/workflows/tests.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70182f0..c551104 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,3 +48,36 @@ jobs: with: name: doc-html-${{ matrix.branch }} path: www/ + + translations: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest", "windows-latest"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3 + allow-prereleases: true + cache: pip + - name: Install dependencies + run: | + pip install -U pip setuptools + pip install Babel jinja2 + - run: python3 babel_runner.py extract + - run: python3 babel_runner.py init -l pt_BR + - run: python3 babel_runner.py update + - run: python3 babel_runner.py update -l pt_BR + - run: python3 babel_runner.py compile + - run: python3 babel_runner.py compile -l pt_BR + - name: Print .pot file + shell: bash + run: cat locales/messages.pot + - name: Print .po file + shell: bash + run: cat locales/pt_BR/LC_MESSAGES/messages.po + - name: list files in locales dir + shell: bash + run: ls -R locales/ From 7878f4e51fbd79310a1cf7d8653ea5c20817fa11 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Sat, 3 Aug 2024 14:45:45 -0300 Subject: [PATCH 07/14] Use python instead of python3 to run babel_runner.py Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c551104..62b1f3c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,14 +64,14 @@ jobs: cache: pip - name: Install dependencies run: | - pip install -U pip setuptools - pip install Babel jinja2 - - run: python3 babel_runner.py extract - - run: python3 babel_runner.py init -l pt_BR - - run: python3 babel_runner.py update - - run: python3 babel_runner.py update -l pt_BR - - run: python3 babel_runner.py compile - - run: python3 babel_runner.py compile -l pt_BR + pip install --upgrade pip + pip install Babel jinja2 setuptools + - run: python babel_runner.py extract + - run: python babel_runner.py init -l pt_BR + - run: python babel_runner.py update + - run: python babel_runner.py update -l pt_BR + - run: python babel_runner.py compile + - run: python babel_runner.py compile -l pt_BR - name: Print .pot file shell: bash run: cat locales/messages.pot From 1fbd5d07695a6b6b58fd114c8de6d5133e9073a8 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Sat, 3 Aug 2024 15:25:06 -0300 Subject: [PATCH 08/14] Replace os with pahlib in babe_runner.py --- babel_runner.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/babel_runner.py b/babel_runner.py index 386f34b..ede0657 100755 --- a/babel_runner.py +++ b/babel_runner.py @@ -3,34 +3,32 @@ from __future__ import annotations import argparse -import os import subprocess +from pathlib import Path import tomllib -PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) +PROJECT_DIR = Path(__file__).resolve().parent -# Global variables used by pybabel below +# Global variables used by pybabel below (paths relative to PROJECT_DIR) DOMAIN = "messages" COPYRIGHT_HOLDER = "Python Software Foundation" -LOCALES_DIR = os.path.relpath(os.path.join(PROJECT_DIR, "locales")) -POT_FILE = os.path.relpath(os.path.join(LOCALES_DIR, f"{DOMAIN}.pot"), PROJECT_DIR) -SOURCE_DIR = os.path.relpath( - os.path.join(PROJECT_DIR, "python_docs_theme"), PROJECT_DIR -) -MAPPING_FILE = os.path.relpath(os.path.join(PROJECT_DIR, ".babel.cfg"), PROJECT_DIR) +LOCALES_DIR = "locales" +POT_FILE = Path(LOCALES_DIR, f"{DOMAIN}.pot") +SOURCE_DIR = "python_docs_theme" +MAPPING_FILE = ".babel.cfg" def get_project_info() -> dict: """Retrieve project's info to populate the message catalog template""" - with open(os.path.join(PROJECT_DIR, "pyproject.toml"), "rb") as f: + with open(Path(PROJECT_DIR / "pyproject.toml"), "rb") as f: data = tomllib.load(f) return data["project"] def extract_messages(): """Extract messages from all source files into message catalog template""" - os.makedirs(LOCALES_DIR, exist_ok=True) + Path(PROJECT_DIR, LOCALES_DIR).mkdir(parents=True, exist_ok=True) project_data = get_project_info() subprocess.run( [ @@ -50,18 +48,19 @@ def extract_messages(): POT_FILE, SOURCE_DIR, ], + cwd=PROJECT_DIR, check=True, ) def init_locale(locale: str): """Initialize a new locale based on existing message catalog template""" - pofile = os.path.join(LOCALES_DIR, locale, "LC_MESSAGES", f"{DOMAIN}.po") - if os.path.exists(pofile): + pofile = PROJECT_DIR / LOCALES_DIR / locale / "LC_MESSAGES" / f"{DOMAIN}.po" + if pofile.exists(): print(f"There is already a message catalog for locale {locale}, skipping.") return cmd = ["pybabel", "init", "-i", POT_FILE, "-d", LOCALES_DIR, "-l", locale] - subprocess.run(cmd, check=True) + subprocess.run(cmd, cwd=PROJECT_DIR, check=True) def update_catalogs(locale: str): @@ -69,7 +68,7 @@ def update_catalogs(locale: str): cmd = ["pybabel", "update", "-i", POT_FILE, "-d", LOCALES_DIR] if locale != "": cmd.extend(["-l", locale]) - subprocess.run(cmd, check=True) + subprocess.run(cmd, cwd=PROJECT_DIR, check=True) def compile_catalogs(locale: str): @@ -77,7 +76,7 @@ def compile_catalogs(locale: str): cmd = ["pybabel", "compile", "-d", LOCALES_DIR] if locale != "": cmd.extend(["-l", locale]) - subprocess.run(cmd, check=True) + subprocess.run(cmd, cwd=PROJECT_DIR, check=True) def main(): @@ -96,8 +95,6 @@ def main(): args = parser.parse_args() locale = args.locale if args.locale else "" - os.chdir(PROJECT_DIR) - if args.command == "extract": extract_messages() elif args.command == "init": From e3e08ebcc6c1201302d73808c7fd8d324501af55 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Sat, 3 Aug 2024 15:58:51 -0300 Subject: [PATCH 09/14] Add tomli import to support python<3.11 --- babel_runner.py | 10 +++++++++- requirements.txt | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 requirements.txt diff --git a/babel_runner.py b/babel_runner.py index ede0657..4d51b64 100755 --- a/babel_runner.py +++ b/babel_runner.py @@ -6,7 +6,15 @@ import subprocess from pathlib import Path -import tomllib +try: + import tomllib +except ImportError: + try: + import tomli as tomllib + except ImportError as ie: + raise ImportError( + "tomli or tomllib is required to parse pyproject.toml" + ) from ie PROJECT_DIR = Path(__file__).resolve().parent diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..18e6c17 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# for babel_runner.py +setuptools +Babel +Jinja2 +tomli; python_version < "3.10" From a845398dc335c9cc37113caee4952d7f47e0913f Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Sat, 3 Aug 2024 16:01:18 -0300 Subject: [PATCH 10/14] CI test for minimum supported python version --- .github/workflows/tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 62b1f3c..e79a125 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,17 +55,19 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "windows-latest"] + # Test minimum supported and latest stable from 3.x series + python-version: ["3.8", "3"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: 3 + python-version: ${{ matrix.python-version }} allow-prereleases: true cache: pip - name: Install dependencies run: | pip install --upgrade pip - pip install Babel jinja2 setuptools + pip install -r requirements.txt - run: python babel_runner.py extract - run: python babel_runner.py init -l pt_BR - run: python babel_runner.py update From bd7c059d3f233b7e4c7035aba798135dcdf816e8 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Mon, 28 Oct 2024 08:23:22 -0300 Subject: [PATCH 11/14] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Co-authored-by: Ezio Melotti --- .github/workflows/tests.yml | 2 +- babel_runner.py | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1c77087..a4dd522 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -56,7 +56,7 @@ jobs: matrix: os: ["ubuntu-latest", "windows-latest"] # Test minimum supported and latest stable from 3.x series - python-version: ["3.8", "3"] + python-version: ["3.9", "3"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/babel_runner.py b/babel_runner.py index 4d51b64..b90b57b 100755 --- a/babel_runner.py +++ b/babel_runner.py @@ -34,7 +34,7 @@ def get_project_info() -> dict: return data["project"] -def extract_messages(): +def extract_messages() -> None: """Extract messages from all source files into message catalog template""" Path(PROJECT_DIR, LOCALES_DIR).mkdir(parents=True, exist_ok=True) project_data = get_project_info() @@ -61,7 +61,7 @@ def extract_messages(): ) -def init_locale(locale: str): +def init_locale(locale: str) -> None: """Initialize a new locale based on existing message catalog template""" pofile = PROJECT_DIR / LOCALES_DIR / locale / "LC_MESSAGES" / f"{DOMAIN}.po" if pofile.exists(): @@ -71,23 +71,23 @@ def init_locale(locale: str): subprocess.run(cmd, cwd=PROJECT_DIR, check=True) -def update_catalogs(locale: str): +def update_catalogs(locale: str) -> None: """Update translations from existing message catalogs""" cmd = ["pybabel", "update", "-i", POT_FILE, "-d", LOCALES_DIR] - if locale != "": + if locale: cmd.extend(["-l", locale]) subprocess.run(cmd, cwd=PROJECT_DIR, check=True) -def compile_catalogs(locale: str): +def compile_catalogs(locale: str) -> None: """Compile existing message catalogs""" cmd = ["pybabel", "compile", "-d", LOCALES_DIR] - if locale != "": + if locale: cmd.extend(["-l", locale]) subprocess.run(cmd, cwd=PROJECT_DIR, check=True) -def main(): +def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "command", @@ -97,16 +97,17 @@ def main(): parser.add_argument( "-l", "--locale", + default="", help="language code (needed for init, optional for update and compile)", ) args = parser.parse_args() - locale = args.locale if args.locale else "" + locale = args.locale if args.command == "extract": extract_messages() elif args.command == "init": - if locale == "": + if not locale: parser.error("init requires passing the --locale option") init_locale(locale) elif args.command == "update": From 65a0360a32bce266ff8b9db49507347c5dbe3d1c Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Mon, 28 Oct 2024 08:51:19 -0300 Subject: [PATCH 12/14] Remove possibly existing translation file Otherwise init command will be skipped --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a4dd522..b0ce07c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -68,6 +68,8 @@ jobs: run: | pip install --upgrade pip pip install -r requirements.txt + - name: Remove locale file for testing + run: rm -rf locales/pt_BR/ - run: python babel_runner.py extract - run: python babel_runner.py init -l pt_BR - run: python babel_runner.py update From 69ab91731e63dc625ff1f7405391ac4b6354dc35 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Mon, 28 Oct 2024 09:25:07 -0300 Subject: [PATCH 13/14] Add shell bash to success in Windows --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b0ce07c..ace177e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,6 +69,7 @@ jobs: pip install --upgrade pip pip install -r requirements.txt - name: Remove locale file for testing + shell: bash run: rm -rf locales/pt_BR/ - run: python babel_runner.py extract - run: python babel_runner.py init -l pt_BR From 2aa3a04d209e7496e2c3f08d5a2db68b83fbf9fb Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Mon, 28 Oct 2024 09:37:18 -0300 Subject: [PATCH 14/14] Swap init and extract to be didactic As one need to have POT before initializing PO --- babel_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/babel_runner.py b/babel_runner.py index b90b57b..da4001c 100755 --- a/babel_runner.py +++ b/babel_runner.py @@ -91,7 +91,7 @@ def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "command", - choices=["init", "extract", "update", "compile"], + choices=["extract", "init", "update", "compile"], help="command to be executed", ) parser.add_argument(