File: babel_runner.py

package info (click to toggle)
python-docs-theme 2025.4.1-1
  • links: PTS
  • area: main
  • in suites: forky, sid, trixie
  • size: 192 kB
  • sloc: javascript: 150; python: 114; makefile: 25
file content (128 lines) | stat: -rwxr-xr-x 3,905 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/venv python3
"""Script for handling translations with Babel"""
from __future__ import annotations

import argparse
import ast
import subprocess
import tomllib
from pathlib import Path

PROJECT_DIR = Path(__file__).resolve().parent
PYPROJECT_TOML = PROJECT_DIR / "pyproject.toml"
INIT_PY = PROJECT_DIR / "python_docs_theme" / "__init__.py"

# Global variables used by pybabel below (paths relative to PROJECT_DIR)
DOMAIN = "messages"
COPYRIGHT_HOLDER = "Python Software Foundation"
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"""
    pyproject_text = PYPROJECT_TOML.read_text(encoding="utf-8")
    project_data = tomllib.loads(pyproject_text)["project"]

    # read __version__ from __init__.py
    for child in ast.parse(INIT_PY.read_bytes()).body:
        if not isinstance(child, ast.Assign):
            continue
        target = child.targets[0]
        if not isinstance(target, ast.Name) or target.id != "__version__":
            continue
        version_node = child.value
        if not isinstance(version_node, ast.Constant):
            continue
        project_data["version"] = version_node.value
        break

    return project_data


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()
    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,
        ],
        cwd=PROJECT_DIR,
        check=True,
    )


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():
        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, cwd=PROJECT_DIR, check=True)


def update_catalogs(locale: str) -> None:
    """Update translations from existing message catalogs"""
    cmd = ["pybabel", "update", "-i", POT_FILE, "-d", LOCALES_DIR]
    if locale:
        cmd.extend(["-l", locale])
    subprocess.run(cmd, cwd=PROJECT_DIR, check=True)


def compile_catalogs(locale: str) -> None:
    """Compile existing message catalogs"""
    cmd = ["pybabel", "compile", "-d", LOCALES_DIR]
    if locale:
        cmd.extend(["-l", locale])
    subprocess.run(cmd, cwd=PROJECT_DIR, check=True)


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "command",
        choices=["extract", "init", "update", "compile"],
        help="command to be executed",
    )
    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.command == "extract":
        extract_messages()
    elif args.command == "init":
        if not 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()