├── .gitignore ├── bootstrapper ├── __init__.py └── bootstrapper.py ├── data ├── .gitignore ├── README.md └── Makefile ├── .github └── workflows │ ├── pre-commit.yaml │ └── tests.yaml ├── README.md ├── .pre-commit-config.yaml ├── LICENSE └── pyproject.toml /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | *.egg-info/ 3 | dist/ 4 | build/ 5 | .mypy_cache/ 6 | .pytest_cache/ 7 | -------------------------------------------------------------------------------- /bootstrapper/__init__.py: -------------------------------------------------------------------------------- 1 | from .bootstrapper import Bootstrapper 2 | 3 | __all__ = ["Bootstrapper"] 4 | __version__ = "0.1.2" 5 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | *.mo 2 | *.po.bak 3 | *.pot 4 | .DS_Store 5 | .idea/ 6 | .pospell/ 7 | .potodo/ 8 | .venv/ 9 | .vs/ 10 | .vscode/ 11 | locales/ 12 | venv/ 13 | __pycache__/ 14 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yaml: -------------------------------------------------------------------------------- 1 | name: pre-commit checks 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-python@v5 15 | with: 16 | python-version: "3.x" 17 | - uses: pre-commit/action@v3.0.0 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-docs-bootstrapper 2 | 3 | This repository contains the scripts and data used to bootstrap a new translation of the Python documentation. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | $ pip install python-docs-bootstrapper 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```bash 14 | $ bootstrapper --help 15 | usage: bootstrapper [-h] [-b BRANCH] language 16 | 17 | positional arguments: 18 | language IETF language tag (e.g. tr, pt-br) 19 | 20 | options: 21 | -h, --help show this help message and exit 22 | -b BRANCH, --branch BRANCH 23 | CPython branch (e.g. 3.13) 24 | ``` 25 | 26 | ## Example 27 | 28 | ```bash 29 | $ bootstrapper tr --branch 3.13 30 | ``` 31 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-case-conflict 6 | - id: check-merge-conflict 7 | - id: check-yaml 8 | - id: end-of-file-fixer 9 | - id: trailing-whitespace 10 | 11 | - repo: https://github.com/astral-sh/ruff-pre-commit 12 | rev: v0.11.12 13 | hooks: 14 | - id: ruff-check 15 | args: 16 | - --select 17 | - I 18 | - --fix 19 | - id: ruff-format 20 | 21 | - repo: https://github.com/tox-dev/pyproject-fmt 22 | rev: v2.6.0 23 | hooks: 24 | - id: pyproject-fmt 25 | 26 | - repo: https://github.com/abravalheri/validate-pyproject 27 | rev: v0.24.1 28 | hooks: 29 | - id: validate-pyproject 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ege Akman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # python-docs-{{translation.language}} 2 | 3 | Hello! This is the repository of the {{translation.language}} translation of the Python documentation. 4 | 5 | **You can refer to the following resources throughout this journey:** 6 | 7 | - [DevGuide Translating Section](https://devguide.python.org/documentation/translating/) (add your translation to the list!) 8 | - [PEP 545](https://www.python.org/dev/peps/pep-0545/) (the PEP that describes the translation process) 9 | - [Other Translations](https://github.com/orgs/python/repositories?q=python-docs) (see how other translations are doing it) 10 | - [Python Docs Discord Server](https://discord.com/invite/sMWqvzXvde) (especially the `#translations` channel) 11 | 12 | ## Documentation Contribution Agreement 13 | Python's documentation is maintained using a global network of volunteers. By posting this project on Transifex, Github, and other public places, and inviting you to participate, we are proposing an agreement that you will provide your improvements to Python's documentation or the translation of Python's documentation for the PSF's use under the CC0 license (available at https://creativecommons.org/publicdomain/zero/1.0/legalcode). In return, you may publicly claim credit for the portion of the translation you contributed and if your translation is accepted by the PSF, you may (but are not required to) submit a patch including an appropriate annotation in the TRANSLATORS file. Although nothing in this Documentation Contribution Agreement obligates the PSF to incorporate your textual contribution, your participation in the Python community is welcomed and appreciated. 14 | 15 | You signify acceptance of this agreement by submitting your work to the PSF for inclusion in the documentation. 16 | 17 | ***Do not forget to replace this file with your own README that describes your translation and community, while keeping the above agreement!*** 18 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "hatchling.build" 3 | requires = [ 4 | "hatchling", 5 | ] 6 | 7 | [project] 8 | name = "python-docs-bootstrapper" 9 | description = "Bootstrapper for Python documentation translations" 10 | readme = "README.md" 11 | keywords = [ 12 | "automation", 13 | "CLI", 14 | "documentation", 15 | "i18n", 16 | "python-docs", 17 | "sphinx", 18 | "translation", 19 | "utilities", 20 | ] 21 | license = { text = "MIT" } 22 | authors = [ 23 | { name = "egeakman", email = "me@egeakman.dev" }, 24 | ] 25 | requires-python = ">=3.10" 26 | classifiers = [ 27 | "Development Status :: 5 - Production/Stable", 28 | "Environment :: Console", 29 | "License :: OSI Approved :: MIT License", 30 | "Operating System :: MacOS :: MacOS X", 31 | "Operating System :: Microsoft :: Windows", 32 | "Operating System :: POSIX :: Linux", 33 | "Programming Language :: Python :: 3 :: Only", 34 | "Programming Language :: Python :: 3.10", 35 | "Programming Language :: Python :: 3.11", 36 | "Programming Language :: Python :: 3.12", 37 | "Programming Language :: Python :: 3.13", 38 | "Topic :: Utilities", 39 | ] 40 | dynamic = [ 41 | "version", 42 | ] 43 | dependencies = [ 44 | "sphinx", 45 | ] 46 | urls.Homepage = "https://github.com/egeakman/python-docs-bootstrapper" 47 | urls.Issues = "https://github.com/egeakman/python-docs-bootstrapper/issues" 48 | urls.Releases = "https://github.com/egeakman/python-docs-bootstrapper/releases" 49 | scripts.bootstrapper = "bootstrapper.bootstrapper:main" 50 | 51 | [tool.hatch.build] 52 | packages = [ 53 | "bootstrapper", 54 | ] 55 | isolated = true 56 | 57 | [tool.hatch.build.force-include] 58 | "data" = "python-docs-bootstrapper-data" 59 | 60 | [tool.hatch.version] 61 | path = "bootstrapper/__init__.py" 62 | 63 | [tool.isort] 64 | profile = "black" 65 | known_first_party = "bootstrapper" 66 | 67 | [tool.pyproject-fmt] 68 | max_supported_python = "3.13" 69 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | os: [macos-latest, windows-latest, ubuntu-latest] 12 | python-version: ["3.11", "3.12", "3.13"] 13 | build-version: ["3.11", "3.12", "3.13"] 14 | 15 | steps: 16 | - name: Checkout repo 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | allow-prereleases: true 24 | 25 | - name: Install tree (macOS) 26 | if: matrix.os == 'macos-latest' 27 | run: brew install tree 28 | 29 | - name: Install tree (Windows) 30 | if: matrix.os == 'windows-latest' 31 | run: Install-Module PSScriptTools -scope CurrentUser -force -AllowClobber 32 | 33 | - name: Install tree (Linux) 34 | if: matrix.os == 'ubuntu-latest' 35 | run: sudo apt install tree 36 | 37 | - name: Install the package 38 | run: pip install . 39 | 40 | - name: Run the bootstrapper 41 | run: bootstrapper tr -b ${{ matrix.build-version }} 42 | 43 | - name: See results (non-Windows) 44 | if: matrix.os != 'windows-latest' 45 | run: | 46 | cd python-docs-tr 47 | tree . -L 2 48 | echo "================================" 49 | echo "Check if everything is OK (clean paths, correct syntax)" 50 | echo "================================" 51 | cat about.po 52 | echo "================================" 53 | echo "Check if everything is OK (clean paths, correct syntax)" 54 | echo "================================" 55 | cat library/functions.po 56 | 57 | - name: See results (Windows) 58 | if: matrix.os == 'windows-latest' 59 | run: | 60 | cd python-docs-tr 61 | Show-Tree . -Depth 2 -ShowItem 62 | echo "================================" 63 | echo "Check if everything is OK (clean paths, correct syntax)" 64 | echo "================================" 65 | cat about.po 66 | echo "================================" 67 | echo "Check if everything is OK (clean paths, correct syntax)" 68 | echo "================================" 69 | cat library/functions.po 70 | -------------------------------------------------------------------------------- /data/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for {{translation.language}} Python Documentation 2 | # 3 | # Here is what you can do: 4 | # 5 | # - make # Automatically build an HTML local version 6 | # - make todo # To list remaining tasks and show current progression 7 | # - make verifs # To check for correctness: wrapping, spelling 8 | # - make wrap # To rewrap modified files 9 | # - make spell # To check for spelling 10 | # - make clean # To remove build artifacts 11 | # - make fuzzy # To find fuzzy strings 12 | # 13 | # Modes are: autobuild-stable, autobuild-dev, and autobuild-html 14 | 15 | # Configuration 16 | 17 | # The CPYTHON_CURRENT_COMMIT is the commit, in the cpython repository, 18 | # from which we generated our po files. We use it here so when we 19 | # test build, we're building with the .rst files that generated our 20 | # .po files. 21 | 22 | CPYTHON_CURRENT_COMMIT := {{translation.head}} 23 | LANGUAGE := {{translation.language}} 24 | BRANCH := {{translation.branch}} 25 | 26 | EXCLUDED := \ 27 | whatsnew/2.?.po \ 28 | whatsnew/3.[0-10].po 29 | 30 | # Internal variables 31 | 32 | UPSTREAM := https://github.com/python/cpython 33 | 34 | PYTHON := $(shell which python3) 35 | MODE := html 36 | POSPELL_TMP_DIR := .pospell/ 37 | JOBS := auto 38 | ADDITIONAL_ARGS := --keep-going --color 39 | SPHINXERRORHANDLING = -W 40 | 41 | # Detect OS 42 | 43 | ifeq '$(findstring ;,$(PATH))' ';' 44 | detected_OS := Windows 45 | else 46 | detected_OS := $(shell uname 2>/dev/null || echo Unknown) 47 | detected_OS := $(patsubst CYGWIN%,Cygwin,$(detected_OS)) 48 | detected_OS := $(patsubst MSYS%,MSYS,$(detected_OS)) 49 | detected_OS := $(patsubst MINGW%,MSYS,$(detected_OS)) 50 | endif 51 | 52 | ifeq ($(detected_OS),Darwin) # Mac OS X 53 | CP_CMD := gcp # accessible with `brew install coreutils` or `brew upgrade coreutils` 54 | else 55 | CP_CMD := cp 56 | endif 57 | 58 | .PHONY: all 59 | all: ensure_prerequisites 60 | git -C venv/cpython checkout $(CPYTHON_CURRENT_COMMIT) || (git -C venv/cpython fetch && git -C venv/cpython checkout $(CPYTHON_CURRENT_COMMIT)) 61 | mkdir -p locales/$(LANGUAGE)/LC_MESSAGES/ 62 | $(CP_CMD) -u --parents *.po */*.po locales/$(LANGUAGE)/LC_MESSAGES/ 63 | $(MAKE) -C venv/cpython/Doc/ \ 64 | JOBS='$(JOBS)' \ 65 | SPHINXOPTS='-D locale_dirs=$(abspath locales) \ 66 | -D language=$(LANGUAGE) \ 67 | -D gettext_compact=0 \ 68 | -D latex_engine=xelatex \ 69 | -D latex_elements.inputenc= \ 70 | -D latex_elements.fontenc= \ 71 | $(ADDITIONAL_ARGS)' \ 72 | SPHINXERRORHANDLING=$(SPHINXERRORHANDLING) \ 73 | $(MODE) 74 | @echo "Build success, open file://$(abspath venv/cpython/)/Doc/build/html/index.html or run 'make htmlview' to see them." 75 | 76 | 77 | # We clone cpython/ inside venv/ because venv/ is the only directory 78 | # excluded by cpython' Sphinx configuration. 79 | venv/cpython/.git/HEAD: 80 | git clone https://github.com/python/cpython venv/cpython 81 | 82 | 83 | .PHONY: ensure_prerequisites 84 | ensure_prerequisites: venv/cpython/.git/HEAD 85 | @if ! (blurb help >/dev/null 2>&1 && sphinx-build --version >/dev/null 2>&1); then \ 86 | git -C venv/cpython/ checkout $(BRANCH); \ 87 | echo "You're missing dependencies please install:"; \ 88 | echo ""; \ 89 | echo " python -m pip install -r venv/cpython/Doc/requirements.txt"; \ 90 | exit 1; \ 91 | fi 92 | 93 | .PHONY: htmlview 94 | htmlview: MODE=htmlview 95 | htmlview: all 96 | 97 | .PHONY: todo 98 | todo: ensure_prerequisites 99 | potodo --exclude venv .venv $(EXCLUDED) 100 | 101 | .PHONY: wrap 102 | wrap: ensure_prerequisites 103 | @echo "Re wrapping modified files" 104 | powrap -m 105 | 106 | SRCS = $(shell git diff --name-only $(BRANCH) | grep '.po$$') 107 | # foo/bar.po => $(POSPELL_TMP_DIR)/foo/bar.po.out 108 | DESTS = $(addprefix $(POSPELL_TMP_DIR)/,$(addsuffix .out,$(SRCS))) 109 | 110 | .PHONY: spell 111 | spell: ensure_prerequisites $(DESTS) 112 | 113 | .PHONY: line-length 114 | line-length: 115 | @echo "Searching for long lines..." 116 | @awk '{if (length(gensub(/శ్రీనివాస్/, ".", "g", $$0)) > 80 && length(gensub(/[^ ]/, "", "g")) > 1) {print FILENAME ":" FNR, "line too long:", $$0; ERRORS+=1}} END {if (ERRORS>0) {exit 1}}' *.po */*.po 117 | 118 | .PHONY: sphinx-lint 119 | sphinx-lint: 120 | @echo "Checking all files using sphinx-lint..." 121 | @sphinx-lint --enable all --disable line-too-long *.po */*.po 122 | 123 | $(POSPELL_TMP_DIR)/%.po.out: %.po dict 124 | @echo "Pospell checking $<..." 125 | @mkdir -p $(@D) 126 | pospell -p dict -l tr_TR $< && touch $@ 127 | 128 | .PHONY: fuzzy 129 | fuzzy: ensure_prerequisites 130 | potodo -f --exclude venv .venv $(EXCLUDED) 131 | 132 | .PHONY: verifs 133 | verifs: spell line-length sphinx-lint 134 | 135 | .PHONY: clean 136 | clean: 137 | @echo "Cleaning *.mo and $(POSPELL_TMP_DIR)" 138 | rm -rf $(POSPELL_TMP_DIR) locales/$(LANGUAGE)/LC_MESSAGES/ 139 | find -name '*.mo' -delete 140 | @echo "Cleaning build directory" 141 | $(MAKE) -C venv/cpython/Doc/ clean 142 | -------------------------------------------------------------------------------- /bootstrapper/bootstrapper.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import logging 3 | import os 4 | import re 5 | import shutil 6 | import subprocess 7 | import sys 8 | import urllib.error 9 | import urllib.request 10 | from argparse import ArgumentParser 11 | from pathlib import Path 12 | from subprocess import PIPE 13 | 14 | 15 | class _NoNewLine(logging.StreamHandler): 16 | def emit(self, record): 17 | msg = self.format(record) 18 | stream = self.stream 19 | terminator = "\n" if msg.endswith("\n") else "" 20 | stream.write(msg + terminator) 21 | self.flush() 22 | 23 | 24 | class Bootstrapper: 25 | def __init__( 26 | self, 27 | language: str, 28 | branch: str, 29 | logger: logging.Logger = logging.getLogger(), 30 | ) -> None: 31 | self.language = language 32 | self.branch = branch 33 | self.logger = logger 34 | self.translation_repo = f"python-docs-{self.language}" 35 | self.cpython_repo = f"{self.translation_repo}/venv/cpython" 36 | self.readme_url = "https://raw.githubusercontent.com/egeakman/python-docs-bootstrapper/master/bootstrapper/data/README.md" 37 | self.gitignore_url = "https://raw.githubusercontent.com/egeakman/python-docs-bootstrapper/master/bootstrapper/data/.gitignore" 38 | self.makefile_url = "https://raw.githubusercontent.com/egeakman/python-docs-bootstrapper/master/bootstrapper/data/Makefile" 39 | self.data_dir = f"{os.path.dirname(__file__)}/../python-docs-bootstrapper-data" 40 | 41 | def _request(self, url: str) -> str: 42 | with urllib.request.urlopen(url) as response: 43 | return response.read().decode() 44 | 45 | def create_dirs(self) -> None: 46 | self.logger.info("Creating directories...") 47 | os.makedirs(self.translation_repo, exist_ok=True) 48 | os.makedirs(self.cpython_repo, exist_ok=True) 49 | self.logger.info("✅\n") 50 | 51 | def setup_cpython_repo(self) -> None: 52 | if not os.path.exists(f"{self.cpython_repo}/.git") and not os.path.isdir( 53 | f"{self.cpython_repo}/.git" 54 | ): 55 | self.logger.info("Cloning CPython repo...") 56 | subprocess.run( 57 | [ 58 | "git", 59 | "clone", 60 | "https://github.com/python/cpython.git", 61 | self.cpython_repo, 62 | f"--branch={self.branch}", 63 | "-q", 64 | ], 65 | check=True, 66 | ) 67 | self.logger.info("✅\n") 68 | 69 | self.logger.info("Updating CPython repo...") 70 | subprocess.run( 71 | ["git", "-C", self.cpython_repo, "pull", "--ff-only", "-q"], check=True 72 | ) 73 | self.logger.info("✅\n") 74 | 75 | self.logger.info("Building gettext files...") 76 | subprocess.run( 77 | [ 78 | "sphinx-build", 79 | "-jauto", 80 | "-QDgettext_compact=0", 81 | "-bgettext", 82 | "Doc", 83 | "pot", 84 | ], 85 | cwd=self.cpython_repo, 86 | check=True, 87 | ) 88 | self.logger.info("✅\n") 89 | 90 | def setup_translation_repo(self) -> None: 91 | self.logger.info("Initializing translation repo...") 92 | subprocess.run(["git", "init", "-q"], cwd=self.translation_repo, check=True) 93 | subprocess.run( 94 | ["git", "branch", "-m", self.branch], cwd=self.translation_repo, check=True 95 | ) 96 | self.logger.info("✅\n") 97 | 98 | self.logger.info("Copying gettext files...") 99 | files = glob.glob(f"{self.cpython_repo}/pot/**/*.pot", recursive=True) 100 | files = [path.replace("\\", "/") for path in files] 101 | 102 | for file in files: 103 | dest_path = ( 104 | f"{self.translation_repo}/{'/'.join(file.split('/')[4:])}".replace( 105 | ".pot", ".po" 106 | ) 107 | ) 108 | os.makedirs(os.path.dirname(dest_path), exist_ok=True) 109 | shutil.copyfile(file, dest_path) 110 | files[files.index(file)] = dest_path 111 | self.logger.info("✅\n") 112 | 113 | self.logger.info("Cleaning up gettext files...") 114 | for file in files: 115 | with open(file, "r", encoding="utf-8") as f: 116 | contents = f.read() 117 | contents = re.sub("^#: .*Doc/", "#: ", contents, flags=re.M) 118 | with open(file, "w", encoding="utf-8") as f: 119 | f.write(contents) 120 | self.logger.info("✅\n") 121 | 122 | def create_readme(self) -> None: 123 | self.logger.info("Creating README.md...") 124 | try: 125 | readme = self._request(self.readme_url) 126 | except (urllib.error.HTTPError, urllib.error.URLError): 127 | self.logger.warning( 128 | "\n ⚠️ Failed to fetch README.md from GitHub, using local copy..." 129 | ) 130 | readme = Path(f"{self.data_dir}/README.md").read_text(encoding="utf-8") 131 | readme = readme.replace("{{translation.language}}", self.language) 132 | with open(f"{self.translation_repo}/README.md", "w", encoding="utf-8") as f: 133 | f.write(readme) 134 | self.logger.info("✅\n") 135 | 136 | def create_gitignore(self) -> None: 137 | self.logger.info("Creating .gitignore...") 138 | try: 139 | gitignore = self._request(self.gitignore_url) 140 | except (urllib.error.HTTPError, urllib.error.URLError): 141 | self.logger.warning( 142 | "\n ⚠️ Failed to fetch .gitignore from GitHub, using local copy..." 143 | ) 144 | gitignore = Path(f"{self.data_dir}/.gitignore").read_text(encoding="utf-8") 145 | with open(f"{self.translation_repo}/.gitignore", "w", encoding="utf-8") as f: 146 | f.write(gitignore) 147 | self.logger.info("✅\n") 148 | 149 | def create_makefile(self) -> None: 150 | logging.info("Creating Makefile...") 151 | try: 152 | makefile = self._request(self.makefile_url) 153 | except (urllib.error.HTTPError, urllib.error.URLError): 154 | self.logger.warning( 155 | "\n ⚠️ Failed to fetch Makefile from GitHub, using local copy..." 156 | ) 157 | makefile = Path(f"{self.data_dir}/Makefile").read_text(encoding="utf-8") 158 | head = ( 159 | subprocess.run( 160 | ["git", "-C", self.cpython_repo, "rev-parse", "HEAD"], 161 | stdout=PIPE, 162 | check=True, 163 | ) 164 | .stdout.strip() 165 | .decode() 166 | ) 167 | makefile = makefile.replace("{{translation.language}}", self.language) 168 | makefile = makefile.replace("{{translation.branch}}", self.branch) 169 | makefile = makefile.replace("{{translation.head}}", head) 170 | with open(f"{self.translation_repo}/Makefile", "w", encoding="utf-8") as f: 171 | f.write(makefile) 172 | self.logger.info("✅\n") 173 | 174 | def run(self) -> None: 175 | try: 176 | self.create_dirs() 177 | self.setup_cpython_repo() 178 | self.setup_translation_repo() 179 | self.create_readme() 180 | self.create_gitignore() 181 | self.create_makefile() 182 | self.logger.info( 183 | f"🎉 Done bootstrapping the {self.language} translation ✅\n" 184 | ) 185 | except Exception as e: 186 | self.logger.critical( 187 | f"❌ Bootstrapping of the {self.language} translation failed: {e}\n" 188 | ) 189 | sys.exit(1) 190 | 191 | 192 | def main() -> None: 193 | sys.stdin.reconfigure(encoding="utf-8") 194 | sys.stdout.reconfigure(encoding="utf-8") 195 | parser = ArgumentParser() 196 | parser.add_argument( 197 | "language", 198 | type=str, 199 | help="IETF language tag (e.g. tr, pt-br)", 200 | ) 201 | parser.add_argument( 202 | "-b", "--branch", type=str, default="3.13", help="CPython branch (e.g. 3.13)" 203 | ) 204 | args = parser.parse_args() 205 | logger = logging.getLogger() 206 | logger.setLevel(logging.INFO) 207 | handler = _NoNewLine() 208 | handler.setFormatter(logging.Formatter("%(message)s")) 209 | logger.addHandler(handler) 210 | Bootstrapper(args.language.lower().replace("_", "-"), args.branch).run() 211 | 212 | 213 | if __name__ == "__main__": 214 | main() 215 | --------------------------------------------------------------------------------