diff --git a/.github/ISSUE_TEMPLATE/translation.yml b/.github/ISSUE_TEMPLATE/translation.yml new file mode 100644 index 000000000..780ebc722 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/translation.yml @@ -0,0 +1,46 @@ +name: Translation Issue Report +description: File a translation issue report +title: "[Typo]: " +labels: ["translation"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this translation issue report! + - type: input + id: version + attributes: + label: Python Version + description: Which version of the Python documentation covers this issue? + placeholder: ex. 3.12 + validations: + required: true + - type: input + id: url + attributes: + label: Docs Page + description: What is the url of the page containing the issue? + placeholder: https://docs.python.org/3/about.html + validations: + required: true + - type: textarea + id: zh-original + attributes: + label: Original Translation + description: Which translated paragraph in Chinese contains the issue? + validations: + required: true + - type: textarea + id: en-original + attributes: + label: Original Docs Paragraph + description: Which original paragraph in English contains the issue? + validations: + required: false + - type: textarea + id: zh-suggested + attributes: + label: Suggested Fix + description: What is your suggested fix? + validations: + required: true \ No newline at end of file diff --git a/.github/scripts/build.sh b/.github/scripts/build.sh index bbf4eb060..46c229e10 100755 --- a/.github/scripts/build.sh +++ b/.github/scripts/build.sh @@ -1,6 +1,7 @@ #!/bin/bash set -e +set -u set -o pipefail error() { @@ -13,5 +14,5 @@ error() { cd cpython/Doc || exit 1 mkdir -p locales/"$LOCALE"/ ln -sfn "$(realpath ../../docs)" locales/"$LOCALE"/LC_MESSAGES -make venv -make html SPHINXOPTS="-D language=$LOCALE -D gettext_compact=0 -W --keep-going -j2" 2> >(error) +pip3 install -q -r requirements.txt +sphinx-build -b dummy -d build/doctrees -j auto -D language=$LOCALE -D gettext_compact=0 -E --keep-going -W . build/html 2> >(error) diff --git a/.github/scripts/commit.sh b/.github/scripts/commit.sh index ae7d04096..878c0d57e 100755 --- a/.github/scripts/commit.sh +++ b/.github/scripts/commit.sh @@ -11,5 +11,4 @@ if ! git status -s|grep '\.po'; then fi git add . git commit -m '[po] auto sync' -header="$(echo -n token:"$GITHUB_TOKEN" | base64)" -git -c http.extraheader="AUTHORIZATION: basic $header" push +git push diff --git a/.github/scripts/generate_tx_config.py b/.github/scripts/generate_tx_config.py new file mode 100755 index 000000000..ebcc3b2dd --- /dev/null +++ b/.github/scripts/generate_tx_config.py @@ -0,0 +1,87 @@ +"""Please note that this script requires a Transifex API token to run.""" +import glob +import subprocess +from functools import partial +from pathlib import Path +import re +import os + +run = partial(subprocess.run, check=True) + + +def init_project(): + run(["tx", "init"]) + + +def add_files(project_name: str): + run( + [ + "tx", + "add", + "remote", + "--file-filter", + "trans//.", + f"https://www.transifex.com/python-doc/{project_name}/dashboard/", + ] + ) + + +FILTER_PATTERN = re.compile( + r"^(?Pfile_filter( *)=( *))(?P.+)$", re.MULTILINE +) + + +def name_replacer(match: re.Match[str]): + prefix, resource = match.group("prefix", "resource") + override_prefix = prefix.replace("file_filter", "trans.zh_CN") + pattern = ( + resource.replace("trans//", "") + .replace("glossary_", "glossary") + .replace("--", "/") + .replace("_", "?") + ) + matches = list(glob.glob(pattern.replace(".po", ".rst"))) + if not matches: + print("missing", pattern) + return f"{prefix}{resource}\n{override_prefix}{pattern.replace('?', '_')}" + elif len(matches) == 1: + filename = matches[0].replace(".rst", ".po").replace("\\", "/") + else: + raise ValueError("multi match", resource, pattern, matches) + return f"{prefix}{resource}\n{override_prefix}{filename}" + + +def patch_config(path: str): + tx_config_path = Path(".tx", "config") + + config_content = tx_config_path.read_text("utf-8") + + cwd = os.getcwd() + os.chdir(path) + config_content = FILTER_PATTERN.sub(name_replacer, config_content) + config_content = re.sub(r'replace_edited_strings.*\n','', config_content) + config_content = re.sub(r'keep_translations.*\n','', config_content) + config_content = re.sub(r'0\ntrans\.zh_CN.*\n','0\n', config_content) + config_content = config_content.replace(' =','=') + os.chdir(cwd) + + tx_config_path.write_text(config_content, "utf-8") + + +if __name__ == "__main__": + from argparse import ArgumentParser + + parser = ArgumentParser() + + parser.add_argument("--token", default="") + parser.add_argument("--project-name", required=True) + parser.add_argument("--doc-path", required=True) + + params = parser.parse_args() + + if params.token: + os.environ["TX_TOKEN"] = params.token + + init_project() + add_files(params.project_name) + patch_config(params.doc_path) diff --git a/.github/scripts/prepare.sh b/.github/scripts/prepare.sh index d16dbc056..1ca5daab0 100755 --- a/.github/scripts/prepare.sh +++ b/.github/scripts/prepare.sh @@ -2,10 +2,4 @@ set -ex -git clone --depth=1 --branch="$VERSION" https://github.com/python/cpython cpython -git clone --branch="$VERSION" https://github.com/"$GITHUB_REPOSITORY" docs - -pip3 install --user setuptools -pip3 install --user transifex-client -sudo apt-get update -sudo apt-get install -y python3-venv +curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash \ No newline at end of file diff --git a/.github/scripts/transifex_pull.py b/.github/scripts/transifex_pull.py deleted file mode 100755 index 66c4046d9..000000000 --- a/.github/scripts/transifex_pull.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 - -from txclib import project -from txclib.utils import perform_parallel_requests - - -def pull(path, lang): - skip_decode = False - params = {} - parallel = True - - prj = project.Project(path) - resource_list = prj.get_chosen_resources([]) - for resource in resource_list: - project_slug, resource_slug = resource.split(".", 1) - host = prj.get_resource_host(resource) - prj._set_url_info(host=host, project=project_slug, resource=resource_slug) - - files = prj.get_resource_files(resource) - url = prj._get_url_by_pull_mode(None) - local_file = files.get(lang) - prj.do_url_request( - url, - language=lang, - skip_decode=skip_decode, - params=params, - parallel=parallel, - callback=prj._save_file, - callback_args={"local_file": local_file}, - ) - - perform_parallel_requests() - - -if __name__ == "__main__": - import os - - pull(os.getcwd(), os.getenv("LOCALE", "zh_CN")) diff --git a/.github/scripts/tx_stat.py b/.github/scripts/tx_stat.py new file mode 100644 index 000000000..00dcf965c --- /dev/null +++ b/.github/scripts/tx_stat.py @@ -0,0 +1,33 @@ +import json +import os +import urllib.request +from datetime import datetime + +key = os.environ.get('TX_TOKEN') +project = os.environ.get('TX_PROJECT') + +url = "https://rest.api.transifex.com/resource_language_stats?filter[project]=o%3Apython-doc%3Ap%3A{}&filter[language]=l%3Azh_CN".format(project) + +headers = { + "accept": "application/vnd.api+json", + "authorization": "Bearer " + key +} + +total = 0 +translated = 0 + +while(url): + request = urllib.request.Request(url=url,headers=headers) + + with urllib.request.urlopen(request) as response: + data = json.loads(response.read().decode("utf-8")) + url = data['links'].get('next') + for resourse in data['data']: + translated = translated + resourse['attributes']['translated_strings'] + total = total + resourse['attributes']['total_strings'] + +p = '{:.2%}'.format(translated/total) +print(json.dumps({ + 'translation':p, + 'updated_at':datetime.utcnow().isoformat(timespec='seconds') + 'Z', + })) \ No newline at end of file diff --git a/.github/scripts/update.sh b/.github/scripts/update.sh index 544a31ae5..0f2231d9f 100755 --- a/.github/scripts/update.sh +++ b/.github/scripts/update.sh @@ -1,18 +1,22 @@ #!/bin/bash -set -ex +set -u -script_dir="$(dirname "$(realpath "$0")")" +cd cpython || exit 1 -if [[ -n "$TRANSIFEX_APIKEY" ]]; then - cat > ~/.transifexrc << EOF -[https://www.transifex.com] -api_hostname = https://api.transifex.com -hostname = https://www.transifex.com -password = $TRANSIFEX_APIKEY -username = api -EOF -fi +# Restore git timestamp for enabling build cache +rev=HEAD +for f in $(git ls-tree -r -t --full-name --name-only "$rev" Doc) ; do + touch -d $(git log --pretty=format:%cI -1 "$rev" -- "$f") "$f"; +done +cd .. cd docs || exit 1 -"$script_dir"/transifex_pull.py + +# Restore git timestamp for enabling build cache +rev=HEAD +for f in $(git ls-tree -r -t --full-name --name-only "$rev") ; do + touch -d $(git log --pretty=format:%cI -1 "$rev" -- "$f") "$f"; +done + +$(realpath ../tx) pull --languages "$LOCALE" -t --use-git-timestamps --workers 25 --silent diff --git a/.github/workflows/python-310.yml b/.github/workflows/python-310.yml new file mode 100644 index 000000000..35a148f47 --- /dev/null +++ b/.github/workflows/python-310.yml @@ -0,0 +1,18 @@ +name: python-310 + +on: + workflow_dispatch: + push: + branches: + - master + schedule: + - cron: "22 * * * *" + +jobs: + sync: + uses: ./.github/workflows/sync.yml + with: + version: "3.10" + tx_project: "python-310" + secrets: inherit + \ No newline at end of file diff --git a/.github/workflows/python-311.yml b/.github/workflows/python-311.yml new file mode 100644 index 000000000..4fa85bb77 --- /dev/null +++ b/.github/workflows/python-311.yml @@ -0,0 +1,18 @@ +name: python-311 + +on: + workflow_dispatch: + push: + branches: + - master + schedule: + - cron: "32 * * * *" + +jobs: + sync: + uses: ./.github/workflows/sync.yml + with: + version: "3.11" + tx_project: "python-311" + secrets: inherit + diff --git a/.github/workflows/python-312.yml b/.github/workflows/python-312.yml new file mode 100644 index 000000000..75cc1d35e --- /dev/null +++ b/.github/workflows/python-312.yml @@ -0,0 +1,17 @@ +name: python-312 + +on: + workflow_dispatch: + push: + branches: + - master + schedule: + - cron: "42 * * * *" + +jobs: + sync: + uses: ./.github/workflows/sync.yml + with: + version: "3.12" + tx_project: "python-312" + secrets: inherit diff --git a/.github/workflows/python-313.yml b/.github/workflows/python-313.yml new file mode 100644 index 000000000..5cf9c8340 --- /dev/null +++ b/.github/workflows/python-313.yml @@ -0,0 +1,17 @@ +name: python-313 + +on: + workflow_dispatch: + push: + branches: + - master + schedule: + - cron: "52 * * * *" + +jobs: + sync: + uses: ./.github/workflows/sync.yml + with: + version: "3.13" + tx_project: "python-313" + secrets: inherit diff --git a/.github/workflows/python-314.yml b/.github/workflows/python-314.yml new file mode 100644 index 000000000..9118a8ce8 --- /dev/null +++ b/.github/workflows/python-314.yml @@ -0,0 +1,17 @@ +name: python-314 + +on: + workflow_dispatch: + push: + branches: + - master + schedule: + - cron: "2 * * * *" + +jobs: + sync: + uses: ./.github/workflows/sync.yml + with: + version: "3.14" + tx_project: "python-newest" + secrets: inherit diff --git a/.github/workflows/python-37.yml b/.github/workflows/python-37.yml index cb34a052d..47c224c3b 100644 --- a/.github/workflows/python-37.yml +++ b/.github/workflows/python-37.yml @@ -1,31 +1,10 @@ name: python-37 -on: - push: - branches: - - master - schedule: - - cron: "38 * * * *" +on: workflow_dispatch jobs: sync: - runs-on: ubuntu-latest - env: - LOCALE: zh_CN - VERSION: "3.7" - steps: - - uses: actions/checkout@v1 - with: - fetch-depth: 1 - - name: prepare - run: .github/scripts/prepare.sh - - name: update - run: .github/scripts/update.sh - env: - TRANSIFEX_APIKEY: ${{ secrets.TRANSIFEX_APIKEY }} - - name: build - run: .github/scripts/build.sh - - name: commit - run: .github/scripts/commit.sh - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: ./.github/workflows/sync.yml + with: + version: "3.7" + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/python-38.yml b/.github/workflows/python-38.yml index f9dbfc1b7..9d0bc6daf 100644 --- a/.github/workflows/python-38.yml +++ b/.github/workflows/python-38.yml @@ -1,31 +1,11 @@ name: python-38 -on: - push: - branches: - - master - schedule: - - cron: "18 * * * *" +on: workflow_dispatch jobs: sync: - runs-on: ubuntu-latest - env: - LOCALE: zh_CN - VERSION: "3.8" - steps: - - uses: actions/checkout@v1 - with: - fetch-depth: 1 - - name: prepare - run: .github/scripts/prepare.sh - - name: update - run: .github/scripts/update.sh - env: - TRANSIFEX_APIKEY: ${{ secrets.TRANSIFEX_APIKEY }} - - name: build - run: .github/scripts/build.sh - - name: commit - run: .github/scripts/commit.sh - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: ./.github/workflows/sync.yml + with: + version: "3.8" + tx_project: "python-38" + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/python-39.yml b/.github/workflows/python-39.yml new file mode 100644 index 000000000..39305f585 --- /dev/null +++ b/.github/workflows/python-39.yml @@ -0,0 +1,18 @@ +name: python-39 + +on: + workflow_dispatch: + push: + branches: + - master + schedule: + - cron: "12 * * * *" + +jobs: + sync: + uses: ./.github/workflows/sync.yml + with: + version: "3.9" + tx_project: "python-39" + secrets: inherit + \ No newline at end of file diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml new file mode 100644 index 000000000..0788a37c0 --- /dev/null +++ b/.github/workflows/sync.yml @@ -0,0 +1,68 @@ +name: Reusable workflow example + +on: + workflow_call: + inputs: + version: + required: true + type: string + tx_project: + required: true + type: string + secrets: + TRANSIFEX_APIKEY: + required: true + +jobs: + sync: + runs-on: ubuntu-latest + env: + LOCALE: zh_CN + VERSION: ${{ inputs.version }} + steps: + - uses: actions/checkout@v4 + - name: Checkout CPython + uses: actions/checkout@v4 + with: + repository: 'python/cpython' + ref: ${{env.VERSION}} + path: cpython + - uses: actions/cache/restore@v4 + with: + path: | + cpython/Doc/build + docs + key: cache-${{ inputs.version }}-${{ github.run_id }} + restore-keys: cache-${{ inputs.version }}- + - name: Checkout Current Branch + uses: actions/checkout@v4 + with: + ref: ${{env.VERSION}} + path: docs + clean: false + - name: prepare + run: .github/scripts/prepare.sh + - name: update + run: .github/scripts/update.sh + env: + TX_TOKEN: ${{ secrets.TRANSIFEX_APIKEY }} + - uses: actions/cache/restore@v4 + with: + path: cpython/Doc/build + key: cache-${{ inputs.version }}-${{ github.run_id }} + restore-keys: cache-${{ inputs.version }}- + - name: build + run: .github/scripts/build.sh + - uses: actions/cache/save@v4 + with: + path: | + cpython/Doc/build + docs + key: cache-${{ inputs.version }}-${{ github.run_id }} + - name: stat + run: python .github/scripts/tx_stat.py > ./docs/.stat.json + env: + TX_TOKEN: ${{ secrets.TRANSIFEX_APIKEY }} + TX_PROJECT: ${{ inputs.tx_project }} + - name: commit + run: .github/scripts/commit.sh \ No newline at end of file diff --git a/README.rst b/README.rst index fc3b74b22..f17ac8294 100644 --- a/README.rst +++ b/README.rst @@ -6,6 +6,45 @@ https://www.transifex.com/python-doc/public/ Maintained versions: +.. list-table:: + :header-rows: 1 + + * - Version + - Sync status + - Translation progress + * - `3.14 `_ + - .. image:: https://github.com/python/python-docs-zh-cn/workflows/python-314/badge.svg + :target: https://github.com/python/python-docs-zh-cn/actions?workflow=python-314 + - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fpython%2Fpython-docs-zh-cn%2F3.14%2F.stat.json&query=%24.translation&label=zh-CN + :target: https://app.transifex.com/python-doc/python-newest/ + * - `3.13 `_ + - .. image:: https://github.com/python/python-docs-zh-cn/workflows/python-313/badge.svg + :target: https://github.com/python/python-docs-zh-cn/actions?workflow=python-313 + - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fpython%2Fpython-docs-zh-cn%2F3.13%2F.stat.json&query=%24.translation&label=zh-CN + :target: https://app.transifex.com/python-doc/python-313/ + * - `3.12 `_ + - .. image:: https://github.com/python/python-docs-zh-cn/workflows/python-312/badge.svg + :target: https://github.com/python/python-docs-zh-cn/actions?workflow=python-312 + - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fpython%2Fpython-docs-zh-cn%2F3.12%2F.stat.json&query=%24.translation&label=zh-CN + :target: https://app.transifex.com/python-doc/python-312/ + * - `3.11 `_ + - .. image:: https://github.com/python/python-docs-zh-cn/workflows/python-311/badge.svg + :target: https://github.com/python/python-docs-zh-cn/actions?workflow=python-311 + - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fpython%2Fpython-docs-zh-cn%2F3.11%2F.stat.json&query=%24.translation&label=zh-CN + :target: https://app.transifex.com/python-doc/python-311/ + * - `3.10 `_ + - .. image:: https://github.com/python/python-docs-zh-cn/workflows/python-310/badge.svg + :target: https://github.com/python/python-docs-zh-cn/actions?workflow=python-310 + - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fpython%2Fpython-docs-zh-cn%2F3.10%2F.stat.json&query=%24.translation&label=zh-CN + :target: https://app.transifex.com/python-doc/python-310/ + * - `3.9 `_ + - .. image:: https://github.com/python/python-docs-zh-cn/workflows/python-39/badge.svg + :target: https://github.com/python/python-docs-zh-cn/actions?workflow=python-39 + - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fpython%2Fpython-docs-zh-cn%2F3.9%2F.stat.json&query=%24.translation&label=zh-CN + :target: https://app.transifex.com/python-doc/python-39/ + +EOL versions: + .. list-table:: :header-rows: 1 @@ -15,13 +54,13 @@ Maintained versions: * - `3.8 `_ - .. image:: https://github.com/python/python-docs-zh-cn/workflows/python-38/badge.svg :target: https://github.com/python/python-docs-zh-cn/actions?workflow=python-38 - - .. image:: https://img.shields.io/badge/dynamic/json.svg?label=zh_CN&query=%24.zh_CN&url=http://gce.zhsj.me/python/newest - :target: https://www.transifex.com/python-doc/python-newest/ + - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fpython%2Fpython-docs-zh-cn%2F3.8%2F.stat.json&query=%24.translation&label=zh-CN + :target: https://app.transifex.com/python-doc/python-38/ * - `3.7 `_ - .. image:: https://github.com/python/python-docs-zh-cn/workflows/python-37/badge.svg :target: https://github.com/python/python-docs-zh-cn/actions?workflow=python-37 - - .. image:: https://img.shields.io/badge/dynamic/json.svg?label=zh_CN&query=%24.zh_CN&url=http://gce.zhsj.me/python/37 - :target: https://www.transifex.com/python-doc/python-37/ + - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fpython%2Fpython-docs-zh-cn%2F3.7%2F.stat.json&query=%24.translation&label=zh-CN + :target: https://app.transifex.com/python-doc/python-37/ Documentation Contribution Agreement ------------------------------------