├── .github ├── dependabot.yml └── workflows │ ├── check.yml │ ├── python-310.yml │ ├── python-311.yml │ ├── python-312.yml │ ├── python-313.yml │ ├── python-314.yml │ ├── python-37.yml │ ├── python-38.yml │ ├── python-39.yml │ └── sync.yml ├── .gitignore ├── LICENSE ├── README.rst ├── requirements.txt └── scripts ├── build.sh ├── commit.sh ├── generate_templates.sh ├── lint.sh ├── potodo.sh ├── prepmsg.sh ├── pull_translations.sh ├── setup.sh ├── stats.py └── tx_stats.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | assignees: 8 | - "rffontenelle" 9 | commit-message: 10 | prefix: "gha" 11 | groups: 12 | github-actions: 13 | patterns: 14 | - "*" 15 | 16 | - package-ecosystem: "pip" 17 | directory: "/" 18 | schedule: 19 | interval: "weekly" 20 | assignees: 21 | - "rffontenelle" 22 | commit-message: 23 | prefix: "pip" 24 | groups: 25 | pip-packages: 26 | patterns: 27 | - "*" 28 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | # Run some tests in the Python Doc translations 2 | 3 | name: Check 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | version: 9 | description: "Branch name corresponding to a Python version" 10 | required: true 11 | type: string 12 | tx_project: 13 | description: "Name of the Transifex translation project" 14 | required: true 15 | type: string 16 | workflow_call: 17 | inputs: 18 | version: 19 | description: "Branch name corresponding to a Python version" 20 | required: true 21 | type: string 22 | tx_project: 23 | description: "Name of the Transifex translation project" 24 | required: true 25 | type: string 26 | secrets: 27 | TELEGRAM_TOKEN: 28 | description: "Token required for interacting with Telegram API" 29 | required: false 30 | TELEGRAM_TO: 31 | description: "Account ID that will receive the telegram notification" 32 | required: false 33 | 34 | permissions: 35 | contents: read 36 | 37 | env: 38 | PYDOC_LANGUAGE: pt_BR 39 | PYDOC_REPO: ${{ github.server_url }}/${{ github.repository }} 40 | PYDOC_VERSION: ${{ inputs.version }} 41 | 42 | jobs: 43 | 44 | # Build documentation handling warnings as errors. 45 | # If success, upload built docs. If failure, notify telegram and upload logs. 46 | build: 47 | name: Build translated docs 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | with: 52 | fetch-depth: 5 53 | 54 | - name: Set up Python 3 55 | uses: actions/setup-python@v5 56 | with: 57 | python-version: ${{ inputs.version }} 58 | cache: pip 59 | allow-prereleases: true 60 | 61 | - name: Make sure the repository is up to date 62 | if: github.event_name != 'pull_request' 63 | run: git pull --rebase 64 | 65 | - name: setup 66 | run: ./scripts/setup.sh 67 | 68 | - name: Add problem matcher 69 | uses: sphinx-doc/github-problem-matcher@v1.1 70 | 71 | - name: Build docs 72 | id: build 73 | run: ./scripts/build.sh 74 | 75 | - name: Upload artifact - docs 76 | if: steps.build.outcome == 'success' 77 | uses: actions/upload-artifact@v4.3.5 78 | with: 79 | name: docs 80 | path: cpython/Doc/build/html 81 | 82 | - name: Prepare notification (only on error) 83 | if: always() && steps.build.outcome == 'failure' 84 | id: prepare 85 | run: | 86 | scripts/prepmsg.sh logs/sphinxwarnings.txt logs/notify.txt 87 | cat logs/notify.txt 88 | env: 89 | GITHUB_JOB: ${{ github.job }} 90 | GITHUB_RUN_ID: ${{ github.run_id }} 91 | 92 | - name: Notify via Telegram 93 | if: always() && steps.prepare.outcome == 'success' && github.event_name == 'schedule' && inputs.tx_project == 'python-newest' 94 | uses: appleboy/telegram-action@v1.0.1 95 | with: 96 | to: ${{ secrets.TELEGRAM_TO }} 97 | token: ${{ secrets.TELEGRAM_TOKEN }} 98 | format: markdown 99 | disable_web_page_preview: true 100 | message_file: logs/notify.txt 101 | 102 | - name: Upload artifact - log files 103 | if: always() && steps.build.outcome == 'failure' 104 | uses: actions/upload-artifact@v4 105 | with: 106 | name: ${{ inputs.version }}-build-logs 107 | path: logs/* 108 | 109 | 110 | # Run sphinx-lint to find wrong reST syntax in PO files. Always store logs. 111 | # If issues are found, notify telegram and upload logs. 112 | lint: 113 | name: Lint translations 114 | runs-on: ubuntu-latest 115 | steps: 116 | - uses: actions/checkout@v4 117 | with: 118 | fetch-depth: 5 119 | 120 | - name: Set up Python 3 121 | uses: actions/setup-python@v5 122 | with: 123 | python-version: ${{ inputs.version }} 124 | cache: pip 125 | allow-prereleases: true 126 | 127 | - name: Make sure the repository is up to date 128 | if: github.event_name != 'pull_request' 129 | run: git pull --rebase 130 | 131 | - name: setup 132 | run: ./scripts/setup.sh 133 | 134 | - name: Add problem matcher 135 | uses: rffontenelle/sphinx-lint-problem-matcher@v1.0.0 136 | 137 | - name: lint translations files 138 | id: lint 139 | run: ./scripts/lint.sh 140 | 141 | - name: Prepare notification (only on error) 142 | if: always() && steps.lint.outcome == 'failure' 143 | id: prepare 144 | run: | 145 | scripts/prepmsg.sh logs/sphinxlint.txt logs/notify.txt 146 | cat logs/notify.txt 147 | env: 148 | GITHUB_JOB: ${{ github.job }} 149 | GITHUB_RUN_ID: ${{ github.run_id }} 150 | 151 | - name: Notify via Telegram 152 | if: always() && steps.prepare.outcome == 'success' && github.event_name == 'schedule' && inputs.tx_project == 'python-newest' 153 | uses: appleboy/telegram-action@v1.0.1 154 | with: 155 | to: ${{ secrets.TELEGRAM_TO }} 156 | token: ${{ secrets.TELEGRAM_TOKEN }} 157 | format: markdown 158 | disable_web_page_preview: true 159 | message_file: logs/notify.txt 160 | 161 | - name: Upload artifact - log files 162 | if: always() && steps.lint.outcome == 'failure' 163 | uses: actions/upload-artifact@v4 164 | with: 165 | name: ${{ inputs.version }}-lint-logs 166 | path: logs/* 167 | -------------------------------------------------------------------------------- /.github/workflows/python-310.yml: -------------------------------------------------------------------------------- 1 | name: python-310 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 * * *' 7 | 8 | jobs: 9 | sync: 10 | uses: ./.github/workflows/sync.yml 11 | with: 12 | tx_project: ${{ github.workflow }} 13 | version: '3.10' 14 | secrets: inherit 15 | check: 16 | uses: ./.github/workflows/check.yml 17 | needs: sync 18 | with: 19 | tx_project: ${{ github.workflow }} 20 | version: '3.10' 21 | secrets: inherit 22 | -------------------------------------------------------------------------------- /.github/workflows/python-311.yml: -------------------------------------------------------------------------------- 1 | name: python-311 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '45 23 * * *' 7 | 8 | jobs: 9 | sync: 10 | uses: ./.github/workflows/sync.yml 11 | with: 12 | tx_project: ${{ github.workflow }} 13 | version: '3.11' 14 | secrets: inherit 15 | check: 16 | uses: ./.github/workflows/check.yml 17 | needs: sync 18 | with: 19 | tx_project: ${{ github.workflow }} 20 | version: '3.11' 21 | secrets: inherit 22 | -------------------------------------------------------------------------------- /.github/workflows/python-312.yml: -------------------------------------------------------------------------------- 1 | name: python-312 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '30 23 * * *' 7 | 8 | jobs: 9 | sync: 10 | uses: ./.github/workflows/sync.yml 11 | with: 12 | tx_project: ${{ github.workflow }} 13 | version: '3.12' 14 | secrets: inherit 15 | check: 16 | uses: ./.github/workflows/check.yml 17 | needs: sync 18 | with: 19 | tx_project: ${{ github.workflow }} 20 | version: '3.12' 21 | secrets: inherit 22 | -------------------------------------------------------------------------------- /.github/workflows/python-313.yml: -------------------------------------------------------------------------------- 1 | name: python-313 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '15 23 * * *' 7 | pull_request: 8 | branches: 9 | - main 10 | - '3.13' 11 | push: 12 | branches: 13 | - main 14 | - '3.13' 15 | 16 | jobs: 17 | sync: 18 | uses: ./.github/workflows/sync.yml 19 | with: 20 | tx_project: ${{ github.workflow }} 21 | version: 3.13 22 | secrets: inherit 23 | check: 24 | uses: ./.github/workflows/check.yml 25 | needs: sync 26 | with: 27 | tx_project: ${{ github.workflow }} 28 | version: 3.13 29 | secrets: inherit 30 | -------------------------------------------------------------------------------- /.github/workflows/python-314.yml: -------------------------------------------------------------------------------- 1 | name: python-314 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 23 * * *' 7 | pull_request: 8 | branches: 9 | - main 10 | - '3.14' 11 | push: 12 | branches: 13 | - main 14 | - '3.13' 15 | 16 | jobs: 17 | sync: 18 | uses: ./.github/workflows/sync.yml 19 | with: 20 | tx_project: python-newest 21 | version: 3.14 22 | secrets: inherit 23 | check: 24 | uses: ./.github/workflows/check.yml 25 | needs: sync 26 | with: 27 | tx_project: python-newest 28 | version: 3.14 29 | secrets: inherit 30 | -------------------------------------------------------------------------------- /.github/workflows/python-37.yml: -------------------------------------------------------------------------------- 1 | name: python-37 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | sync: 8 | uses: ./.github/workflows/sync.yml 9 | with: 10 | tx_project: ${{ github.workflow }} 11 | version: 3.7 12 | secrets: inherit 13 | check: 14 | uses: ./.github/workflows/check.yml 15 | needs: sync 16 | with: 17 | tx_project: ${{ github.workflow }} 18 | version: 3.7 19 | secrets: inherit 20 | -------------------------------------------------------------------------------- /.github/workflows/python-38.yml: -------------------------------------------------------------------------------- 1 | name: python-38 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | sync: 8 | uses: ./.github/workflows/sync.yml 9 | with: 10 | tx_project: ${{ github.workflow }} 11 | version: 3.8 12 | secrets: inherit 13 | check: 14 | uses: ./.github/workflows/check.yml 15 | needs: sync 16 | with: 17 | tx_project: ${{ github.workflow }} 18 | version: 3.8 19 | secrets: inherit 20 | -------------------------------------------------------------------------------- /.github/workflows/python-39.yml: -------------------------------------------------------------------------------- 1 | name: python-39 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '15 0 * * *' 7 | 8 | jobs: 9 | sync: 10 | uses: ./.github/workflows/sync.yml 11 | with: 12 | tx_project: ${{ github.workflow }} 13 | version: 3.9 14 | secrets: inherit 15 | check: 16 | uses: ./.github/workflows/check.yml 17 | needs: sync 18 | with: 19 | tx_project: ${{ github.workflow }} 20 | version: 3.9 21 | secrets: inherit 22 | -------------------------------------------------------------------------------- /.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: Sync 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | version: 7 | description: "Branch name corresponding to a Python version" 8 | required: true 9 | type: string 10 | tx_project: 11 | description: "Name of the Transifex translation project" 12 | required: true 13 | type: string 14 | secrets: 15 | TX_TOKEN: 16 | description: "Token required for interacting with Transifex API" 17 | required: false 18 | 19 | env: 20 | PYDOC_LANGUAGE: pt_BR 21 | PYDOC_TX_PROJECT: ${{ inputs.tx_project }} 22 | PYDOC_VERSION: ${{ inputs.version }} 23 | TX_CLI_VERSION: '1.6.17' 24 | 25 | jobs: 26 | sync: 27 | runs-on: ubuntu-latest 28 | steps: 29 | 30 | # 1- Set up environment 31 | 32 | - name: Check out this repository 33 | uses: actions/checkout@v4 34 | 35 | - name: Checkout CPython ${{ env.PYDOC_VERSION }} 36 | uses: actions/checkout@v4 37 | with: 38 | repository: 'python/cpython' 39 | ref: ${{ env.PYDOC_VERSION }} 40 | path: cpython 41 | 42 | - name: Set language dir variable 43 | run: 44 | echo "LANGUAGE_DIR=cpython/Doc/locales/${{ env.PYDOC_LANGUAGE }}/LC_MESSAGES" >> $GITHUB_ENV 45 | 46 | - name: Checkout this repository ${{ env.PYDOC_VERSION }} 47 | uses: actions/checkout@v4 48 | with: 49 | ref: ${{ env.PYDOC_VERSION }} 50 | path: ${{ env.LANGUAGE_DIR }} 51 | 52 | - uses: actions/setup-python@v5 53 | with: 54 | python-version: ${{ inputs.version }} 55 | allow-prereleases: true 56 | cache: 'pip' 57 | cache-dependency-path: | 58 | requirements.txt 59 | cpython/Doc/requirements.txt 60 | 61 | - name: Check for Transifex API Token availability 62 | id: secret-check 63 | # perform secret check & put boolean result as an output 64 | shell: bash 65 | run: | 66 | available=false 67 | [[ "${{ secrets.TX_TOKEN }}" != '' ]] && available=true 68 | echo "available=$available" >> $GITHUB_OUTPUT 69 | echo "available=$available" 70 | 71 | # 2- Install dependencies 72 | 73 | - name: Install Transifex CLI tool 74 | run: | 75 | cd /usr/local/bin 76 | curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash -s -- v$TX_CLI_VERSION 77 | 78 | - name: Install APT dependencies 79 | run: sudo apt update -y && sudo apt install gettext -y 80 | 81 | - name: Install Python dependencies 82 | run: | 83 | pip install -r requirements.txt 84 | make -C cpython/Doc venv 85 | 86 | # 3- Pull translations 87 | 88 | - name: Generate template files and Transifex config file 89 | run: ./scripts/generate_templates.sh 90 | 91 | - name: Pull translations from Transifex 92 | id: pull 93 | if: ${{ contains(fromJSON('["schedule", "workflow_dispatch"]'), github.event_name) }} 94 | run: | 95 | # Clean up obsolete files 96 | find ./${{ env.LANGUAGE_DIR }} -name '*.po' -exec rm {} \; 97 | ./scripts/pull_translations.sh 98 | env: 99 | TX_TOKEN: ${{ secrets.TX_TOKEN }} 100 | 101 | - name: Merge translations from newer branch 102 | if: inputs.tx_project != 'python-newest' # python-newest doesn't have a newer branch 103 | run: | 104 | newer_branch=${PYDOC_VERSION%%.*}.$((${PYDOC_VERSION##*.}+1)) 105 | git clone --depth 1 --single-branch --branch $newer_branch https://github.com/python/python-docs-pt-br ${newer_branch}-dir 106 | pomerge --from ./${newer_branch}-dir/{**/,}*.po --to ./${{ env.LANGUAGE_DIR }}/{**/,}*.po 107 | rm -rf ./${newer_branch}-dir 108 | 109 | - name: powrap 110 | if: steps.pull.outcome == 'success' 111 | run: | 112 | cd ./${{ env.LANGUAGE_DIR }} 113 | powrap *.po **/*.po 114 | 115 | - name: Update statistics 116 | if: always() 117 | run: | 118 | ./scripts/stats.py 119 | git -C ./${{ env.LANGUAGE_DIR }} diff stats.json 120 | 121 | - name: Update potodo.md 122 | if: always() 123 | run: | 124 | ./scripts/potodo.sh 125 | git diff ./${{ env.LANGUAGE_DIR }}/potodo.md 126 | 127 | # 4- Commit and push translations 128 | 129 | - name: Commit 130 | run: ./scripts/commit.sh 131 | 132 | - name: Push 133 | if: ${{ contains(fromJSON('["schedule", "workflow_dispatch"]'), github.event_name) }} 134 | run: | 135 | cd ./${{ env.LANGUAGE_DIR }} 136 | git push 137 | 138 | 139 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Do not include content specific for versioned branches 2 | *.po 3 | *.po~ 4 | *.mo 5 | *.pot 6 | stats.json 7 | potodo.md 8 | .tx/config 9 | 10 | # Do not include virtual environments 11 | venv/ 12 | .venv/ 13 | 14 | # Do not include temporary directories 15 | logs/ 16 | .potodo/ 17 | cpython 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Brazilian Portuguese Translation of the Python Documentation 2 | ============================================= 3 | 4 | All translations are done on Transifex. 5 | https://explore.transifex.com/python-doc/ 6 | 7 | Please use pull request only for improving scripts, README, etc.; not for translation 8 | 9 | For guides, contacts and more, please see the `Wiki `_. 10 | 11 | Join the translation team at the Telegram group `@pybr_i18n `_. 12 | 13 | Versions translation status 14 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | 16 | See below a table of the translation status for each version, separated by versions that are still maintained and version that reached the end-of-life (EOL). 17 | 18 | Maintained versions: 19 | -------------------- 20 | 21 | 22 | .. list-table:: 23 | :header-rows: 1 24 | 25 | * - Version 26 | - Sync status 27 | - Translation progress 28 | - Total strings 29 | * - `3.14 `_ 30 | - .. image:: https://github.com/python/python-docs-pt-br/workflows/python-314/badge.svg 31 | :target: https://github.com/python/python-docs-pt-br/actions?workflow=python-314 32 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.14%2Fstats.json&query=completion&label=pt_BR 33 | :alt: Brazilian Portuguese translation status for Python 3.14 34 | :target: https://app.transifex.com/python-doc/python-newest/ 35 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.14%2Fstats.json&query=entries&label=3.14 36 | :alt: Total strings for Python 3.14 37 | :target: https://app.transifex.com/python-doc/python-newest/ 38 | * - `3.13 `_ 39 | - .. image:: https://github.com/python/python-docs-pt-br/workflows/python-313/badge.svg 40 | :target: https://github.com/python/python-docs-pt-br/actions?workflow=python-313 41 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.13%2Fstats.json&query=completion&label=pt_BR 42 | :alt: Brazilian Portuguese translation status for Python 3.13 43 | :target: https://app.transifex.com/python-doc/python-newest/ 44 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.13%2Fstats.json&query=entries&label=3.13 45 | :alt: Total strings for Python 3.13 46 | :target: https://app.transifex.com/python-doc/python-newest/ 47 | * - `3.12 `_ 48 | - .. image:: https://github.com/python/python-docs-pt-br/workflows/python-312/badge.svg 49 | :target: https://github.com/python/python-docs-pt-br/actions?workflow=python-312 50 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.12%2Fstats.json&query=completion&label=pt_BR 51 | :alt: Brazilian Portuguese translation status for Python 3.12 52 | :target: https://app.transifex.com/python-doc/python-312/ 53 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.12%2Fstats.json&query=entries&label=3.12 54 | :alt: Total strings for Python 3.12 55 | :target: https://app.transifex.com/python-doc/python-312/ 56 | * - `3.11 `_ 57 | - .. image:: https://github.com/python/python-docs-pt-br/workflows/python-311/badge.svg 58 | :target: https://github.com/python/python-docs-pt-br/actions?workflow=python-311 59 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.11%2Fstats.json&query=completion&label=pt_BR 60 | :alt: Brazilian Portuguese translation status for Python 3.11 61 | :target: https://app.transifex.com/python-doc/python-311/ 62 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.11%2Fstats.json&query=entries&label=3.11 63 | :alt: Total strings for Python 3.11 64 | :target: https://app.transifex.com/python-doc/python-311/ 65 | * - `3.10 `_ 66 | - .. image:: https://github.com/python/python-docs-pt-br/workflows/python-310/badge.svg 67 | :target: https://github.com/python/python-docs-pt-br/actions?workflow=python-310 68 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.10%2Fstats.json&query=completion&label=pt_BR 69 | :alt: Brazilian Portuguese translation status for Python 3.10 70 | :target: https://app.transifex.com/python-doc/python-310/ 71 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.10%2Fstats.json&query=entries&label=3.10 72 | :alt: Total strings for Python 3.10 73 | :target: https://app.transifex.com/python-doc/python-310/ 74 | * - `3.9 `_ 75 | - .. image:: https://github.com/python/python-docs-pt-br/workflows/python-39/badge.svg 76 | :target: https://github.com/python/python-docs-pt-br/actions?workflow=python-39 77 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.9%2Fstats.json&query=completion&label=pt_BR 78 | :alt: Brazilian Portuguese translation status for Python 3.9 79 | :target: https://app.transifex.com/python-doc/python-39/ 80 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.9%2Fstats.json&query=entries&label=3.9 81 | :alt: Total strings for Python 3.9 82 | :target: https://app.transifex.com/python-doc/python-39/ 83 | 84 | 85 | EOL versions: 86 | ------------- 87 | 88 | 89 | .. list-table:: 90 | :header-rows: 1 91 | 92 | * - Version 93 | - Sync status 94 | - Translation progress 95 | - Total strings 96 | * - `3.8 `_ 97 | - .. image:: https://github.com/python/python-docs-pt-br/workflows/python-38/badge.svg 98 | :target: https://github.com/python/python-docs-pt-br/actions?workflow=python-38 99 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.8%2Fstats.json&query=completion&label=pt_BR 100 | :alt: Brazilian Portuguese translation status for Python 3.8 101 | :target: https://app.transifex.com/python-doc/python-38/ 102 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.8%2Fstats.json&query=entries&label=3.8 103 | :alt: Total strings for Python 3.8 104 | :target: https://app.transifex.com/python-doc/python-38/ 105 | * - `3.7 `_ 106 | - .. image:: https://github.com/python/python-docs-pt-br/workflows/python-37/badge.svg 107 | :target: https://github.com/python/python-docs-pt-br/actions?workflow=python-37 108 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.7%2Fstats.json&query=completion&label=pt_BR 109 | :alt: Brazilian Portuguese translation status for Python 3.7 110 | :target: https://app.transifex.com/python-doc/python-37/ 111 | - .. image:: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fpython%2Fpython-docs-pt-br%2Fraw%2F3.7%2Fstats.json&query=entries&label=3.7 112 | :alt: Total strings for Python 3.7 113 | :target: https://app.transifex.com/python-doc/python-37/ 114 | 115 | 116 | Documentation Contribution Agreement 117 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 118 | 119 | 120 | NOTE REGARDING THE LICENSE FOR TRANSLATIONS: Python's documentation is 121 | maintained using a global network of volunteers. By posting this 122 | project on Transifex, GitHub, and other public places, and inviting 123 | you to participate, we are proposing an agreement that you will 124 | provide your improvements to Python's documentation or the translation 125 | of Python's documentation for the PSF's use under the CC0 license 126 | (available at 127 | https://creativecommons.org/publicdomain/zero/1.0/legalcode). In 128 | return, you may publicly claim credit for the portion of the 129 | translation you contributed and if your translation is accepted by the 130 | PSF, you may (but are not required to) submit a patch including an 131 | appropriate annotation in the Misc/ACKS or TRANSLATORS file. Although 132 | nothing in this Documentation Contribution Agreement obligates the PSF 133 | to incorporate your textual contribution, your participation in the 134 | Python community is welcomed and appreciated. 135 | 136 | You signify acceptance of this agreement by submitting your work to 137 | the PSF for inclusion in the documentation. 138 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pomerge==0.1.4 2 | potodo==0.23.1 3 | powrap==1.0.2 4 | sphinx-intl==2.3.1 5 | sphinx-lint==1.0.0 6 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Build translated docs to pop up errors 3 | # 4 | # SPDX-License-Identifier: CC0-1.0 5 | 6 | set -xeu 7 | 8 | # Fail earlier if required variables are not set 9 | test -n ${PYDOC_LANGUAGE+x} 10 | 11 | cd "$(dirname $0)/.." 12 | mkdir -p logs 13 | 14 | # If version is 3.12 or older, set gettext_compact. 15 | # This confval is not needed since 3.12. 16 | # In 3.13, its presence messes 3.13's syntax checking (?) 17 | opts="-D language=${PYDOC_LANGUAGE} --keep-going -w ../../logs/sphinxwarnings.txt" 18 | minor_version=$(git -C cpython/Doc branch --show-current | sed 's|^3\.||') 19 | if [ $minor_version -lt 12 ]; then 20 | opts="$opts -D gettext_compact=False" 21 | fi 22 | 23 | make -C cpython/Doc html SPHINXOPTS="${opts}" 24 | 25 | # Remove empty file 26 | if [ ! -s logs/sphinxwarnings.txt ]; then 27 | rm logs/sphinxwarnings.txt 28 | fi 29 | -------------------------------------------------------------------------------- /scripts/commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Commit changed files filtering to the git repository 3 | # 4 | # SPDX-License-Identifier: CC0-1.0 5 | 6 | set -eu 7 | 8 | cd $(dirname $0)/../cpython/Doc/locales/${PYDOC_LANGUAGE}/LC_MESSAGES 9 | 10 | extra_files=".tx/config stats.json potodo.md" 11 | 12 | set +u 13 | if [ -n "${CI+x}" ]; then 14 | git config user.email "github-actions[bot]@users.noreply.github.com" 15 | git config user.name "github-actions[bot]" 16 | fi 17 | set -u 18 | 19 | # Set for removal the deleted obsolete PO files 20 | git status -s | grep '^ D ' | cut -d' ' -f3 | xargs -r git rm 21 | 22 | # Add only updates that do not consist only of the following header lines 23 | git diff -I'^# Copyright ' -I'^"Project-Id-Version: ' -I'^"POT-Creation-Date: ' -I'^"Language: ' --numstat *.po **/*.po | cut -f3 | xargs -r git add -v 24 | 25 | # Add currently untracked PO files, and update other helper files 26 | untracked_files=$(git ls-files -o --exclude-standard *.po **/*.po) 27 | if [ -n "${untracked_files+x}" ]; then 28 | git add -v $untracked_files 29 | fi 30 | 31 | # Debug in GitHub Actions 32 | set +u 33 | if [ -n "${CI+x}" ]; then 34 | echo "::group::status" 35 | git status 36 | echo "::endgroup::" 37 | echo "::group::diff" 38 | git diff 39 | echo "::endgroup::" 40 | fi 41 | set -u 42 | 43 | # Commit only if there is any cached file 44 | git diff-index --cached --quiet HEAD || { git add -v $extra_files; git commit -vm "Update translations"; } 45 | -------------------------------------------------------------------------------- /scripts/generate_templates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Generate .pot files and Transifex .tx/config file 3 | # 4 | # SPDX-License-Identifier: CC0-1.0 5 | # 6 | # The following need to be set: 7 | # PYDOC_TX_PROJECT (e.g. python-newest) 8 | # PYDOC_LANGUAGE (e.g. pt_BR) 9 | # TX_TOKEN (or have a ~/.transifexrc file) 10 | 11 | set -xeu 12 | 13 | # Fail earlier if required variables are not set (do not expose TX_TOKEN) 14 | test -n ${PYDOC_TX_PROJECT+x} 15 | test -n ${PYDOC_LANGUAGE+x} 16 | 17 | # Make sure to run all commands from CPython docs locales directory 18 | cd $(dirname $0)/../cpython/Doc/locales 19 | 20 | # Generate message catalog template (.pot) files 21 | # TODO: use `make -C .. gettext` when there are only Python >= 3.12 22 | opts='-E -b gettext -D gettext_compact=0 -d build/.doctrees . build/gettext' 23 | make -C .. build ALLSPHINXOPTS="$opts" 24 | 25 | # Generate updated Transifex project configuration file (.tx/config) 26 | rm -rf ./.tx/config 27 | sphinx-intl create-txconfig 28 | sphinx-intl update-txconfig-resources \ 29 | --transifex-organization-name=python-doc \ 30 | --transifex-project-name=$PYDOC_TX_PROJECT \ 31 | --locale-dir=. \ 32 | --pot-dir=../build/gettext 33 | 34 | # Patch .tx/config and store in the repository to enable running tx command 35 | # Explanation: 36 | # - Adds 'trans.$PYDOC_LANGUAGE' to not need to pass tx pull with '-l LANGUAGE' 37 | # - Don't remove 'file_filter' otherwise tx pull complains 38 | # - Replace PO file path to a local directory (easier manual use of tx pull) 39 | mkdir -p "${PYDOC_LANGUAGE}/LC_MESSAGES/.tx/" 40 | sed .tx/config \ 41 | -e 's|.//LC_MESSAGES/||' \ 42 | -e "/^file_filter/{p;s/file_filter/trans.${PYDOC_LANGUAGE}/g;}" \ 43 | > "${PYDOC_LANGUAGE}/LC_MESSAGES/.tx/config" 44 | -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Build translated docs to pop up errors 3 | # 4 | # SPDX-License-Identifier: CC0-1.0 5 | 6 | set -xeu 7 | 8 | # Fail earlier if required variables are not set 9 | test -n ${PYDOC_LANGUAGE+x} 10 | 11 | # Fail earlier if sphinx-lint is not installed 12 | sphinx-lint --help >/dev/null 13 | 14 | rootdir=$(realpath $(dirname $0)/..) 15 | 16 | cd "$rootdir" 17 | 18 | mkdir -p logs 19 | touch logs/sphinxlint.txt 20 | 21 | cd cpython/Doc 22 | 23 | # If version 3.11 or older, disable new 'unnecessary-parentheses' check, 24 | # not fixed before 3.12. 25 | minor_version=$(git branch --show-current | sed 's|^3\.||') 26 | if [ $minor_version -le 11 ]; then 27 | alias sphinx-lint='sphinx-lint --disable unnecessary-parentheses' 28 | fi 29 | 30 | cd locales/${PYDOC_LANGUAGE}/LC_MESSAGES 31 | set +e 32 | sphinx-lint 2> $(realpath "$rootdir/logs/sphinxlint.txt") 33 | set -e 34 | 35 | # Undo changes to undo literal blocks disabling 36 | git checkout . 37 | 38 | cd "$rootdir" 39 | 40 | # Check of logfile is empty 41 | if [ ! -s logs/sphinxlint.txt ]; then 42 | # OK, it is empty. Remove it. 43 | rm logs/sphinxlint.txt 44 | else 45 | # print contents and exit with error status (to trigger notification in CI) 46 | cat logs/sphinxlint.txt 47 | exit 1 48 | fi 49 | -------------------------------------------------------------------------------- /scripts/potodo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Extract the list of incomplete translation files. 3 | # 4 | # SPDX-License-Identifier: CC0-1.0 5 | 6 | set -xeu 7 | 8 | # Fail earlier if required variables are not set 9 | test -n ${PYDOC_VERSION+x} 10 | test -n ${PYDOC_LANGUAGE+x} 11 | 12 | rootdir=$(realpath $(dirname $0)/..) 13 | 14 | cd "$rootdir"/cpython/Doc/locales/${PYDOC_LANGUAGE}/LC_MESSAGES 15 | 16 | potodo --no-cache > potodo.md 17 | 18 | # Show version number instead of the directory name, if present 19 | sed -i "s|LC_MESSAGES|${PYDOC_VERSION}|" potodo.md 20 | 21 | # Remove cache directory 22 | rm -rf .potodo/ 23 | -------------------------------------------------------------------------------- /scripts/prepmsg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Prepare message for Telegram notification 3 | set -ex 4 | 5 | die() { echo "$0: error: $*" >&2; exit 1; } 6 | 7 | [ $# -ne 2 ] && die "Expected 1 input and 1 output files, got $#" 8 | [ ! -f "$1" ] && die "Input file $1 not found, skipping." 9 | [ -z "${PYDOC_REPO}" ] && die "PYDOC_REPO is empty." 10 | [ -z "${PYDOC_VERSION}" ] && die "PYDOC_VERSION is empty." 11 | [ -z "${GITHUB_RUN_ID}" ] && die "GITHUB_RUN_ID is empty." 12 | [ -z "${GITHUB_JOB}" ] && die "GITHUB_JOB is empty." 13 | 14 | URL="${PYDOC_REPO}/actions/runs/${GITHUB_RUN_ID}" 15 | 16 | input="$1" 17 | output="$2" 18 | 19 | touch aux 20 | if [[ "${GITHUB_JOB}" == "build" ]]; then 21 | grep 'cpython/Doc/.*WARNING:' "$input" | \ 22 | sed 's|.*/cpython/Doc/||' | \ 23 | uniq | \ 24 | sed 's|^|```\n|;s|$|\n```\n|' \ 25 | > aux 26 | elif [[ "${GITHUB_JOB}" == "lint" ]]; then 27 | grep -P '^.*\.po:\d+:\s+.*\(.*\)$' "$input" | \ 28 | sed 's|.*/cpython/Doc/||' | \ 29 | sort -u | \ 30 | sed 's|^|```\n|;s|$|\n```\n|' \ 31 | > aux 32 | else 33 | die "Unexpected job name ${GITHUB_JOB}" 34 | fi 35 | 36 | [[ $(cat aux) == "" ]] && die "Unexpected empty output message." 37 | 38 | echo "❌ *${PYDOC_VERSION} ${GITHUB_JOB}* (ID [${GITHUB_RUN_ID}]($URL)):" > "$output"; 39 | { echo ""; cat aux; echo ""; } >> "$output" 40 | rm aux 41 | -------------------------------------------------------------------------------- /scripts/pull_translations.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Pull translations files from Transifex for the given language code 3 | # 4 | # SPDX-License-Identifier: CC0-1.0 5 | # 6 | # Usage examples: 7 | # 8 | # Pull all translations for the given language. 9 | # 10 | # ./scripts/pull_translations.sh 11 | # 12 | # Pull translations for "library/os.po" and "faq/library.po" 13 | # for the Transifex project set in PYDOC_TX_PROJECT. For instance, 14 | # for PYDOC_TX_PROJECT=python-newest, that means pulling the resources 15 | # "python-newest.library--os" and "python-newest.faq--library". 16 | # 17 | # ./scripts/pull_translations.sh library/os.po faq/library.po 18 | # 19 | 20 | set -xeu 21 | 22 | test -n ${PYDOC_TX_PROJECT+x} 23 | 24 | cd "$(dirname $0)/../cpython/Doc/locales/${PYDOC_LANGUAGE}/LC_MESSAGES/" 25 | 26 | # If a PO file is provided as input, convert it into Transifex resource 27 | # and add it be pulled (instead of pulling all translations files). 28 | resources_to_pull="" 29 | if [ $# -gt 0 ]; then 30 | resources_to_pull="-r" 31 | for po_input in $@; do 32 | # trim possible filepath part not used for Transifex resources 33 | po=$(echo $po_input | sed 's|.*LC_MESSAGES/||') 34 | # fail if PO file doesn't exist 35 | [ ! -f "$po" ] && (echo "'$po_input' not found."; exit 1;) 36 | # convert po filename into transifex resource 37 | tx_res=$(echo $po | sed 's|\.po||;s|/|--|g;s|\.|_|g') 38 | # append to a list of resources to be pulled 39 | resources_to_pull="$resources_to_pull ${PYDOC_TX_PROJECT}.${tx_res}" 40 | done 41 | fi 42 | 43 | tx pull -f -l "${PYDOC_LANGUAGE}" ${resources_to_pull} 44 | 45 | # Drop translation of Python's changelog in python 3.12 or older. 46 | minor_version=$(git branch --show-current | sed 's|^3\.||') 47 | if [ $minor_version -le 12 ]; then 48 | git checkout whatsnew/changelog.po 49 | fi 50 | -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Set up environment for operations on Python docs translation 3 | # This is meant for running locally to make it easier to test etc. 4 | # 5 | # SPDX-License-Identifier: CC0-1.0 6 | 7 | set -xeu 8 | 9 | # Fail earlier if required variables are not set 10 | test -n ${PYDOC_VERSION+x} 11 | test -n ${PYDOC_REPO+x} 12 | test -n ${PYDOC_LANGUAGE+x} 13 | 14 | # Make sure to run all commands from repository root directory 15 | cd $(dirname $0)/.. 16 | 17 | # Clean up 18 | rm -rf cpython 19 | 20 | # Check out needed repositories 21 | git clone --depth 1 --single-branch --branch ${PYDOC_VERSION} https://github.com/python/cpython cpython 22 | git clone --depth 1 --single-branch --branch ${PYDOC_VERSION} ${PYDOC_REPO} cpython/Doc/locales/${PYDOC_LANGUAGE}/LC_MESSAGES 23 | 24 | # Install dependencies; Require being in a VENV or in GitHub Actions 25 | set +u 26 | if [ -z "${VIRTUAL_ENV+x}" ] && [ -z "${CI+x}" ]; then 27 | echo "Expected to be in a virtual environment. For instance:" 28 | echo " rm -rf .venv && python -m venv .venv && source ~/venv/bin/activate" 29 | exit 1 30 | fi 31 | set -u 32 | pip install -r requirements.txt 33 | make -C cpython/Doc venv 34 | 35 | if ! command -v tx > /dev/null; then 36 | echo "WARNING: Transifex CLI tool was not found." 37 | echo "If going to pull translations it is needed, can be ignored otherwise." 38 | echo "See https://github.com/transifex/cli for install info" 39 | fi 40 | -------------------------------------------------------------------------------- /scripts/stats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Obtain translation stats from the PO files directory and 4 | store it with JSON format into 'stats.json'. 5 | """ 6 | 7 | import json 8 | import os 9 | import logging 10 | from datetime import datetime, timezone 11 | from pathlib import Path 12 | 13 | from potodo.potodo import scan_path 14 | 15 | logging.basicConfig(level=logging.INFO) 16 | 17 | 18 | def main() -> None: 19 | """Main function to generate translation stats.""" 20 | language = os.environ.get("PYDOC_LANGUAGE") 21 | if not language: 22 | raise ValueError("Environment variable PYDOC_LANGUAGE is not set.") 23 | 24 | pofiles_path = Path(f"cpython/Doc/locales/{language}/LC_MESSAGES") 25 | if not pofiles_path.exists(): 26 | raise FileNotFoundError(f"Path does not exist: {pofiles_path}") 27 | 28 | # Check for PO files inside the pofiles_path 29 | if not list(pofiles_path.rglob("*.po")): 30 | raise FileNotFoundError(f"No PO files found in {pofiles_path}") 31 | 32 | stats = scan_path(pofiles_path, no_cache=True, hide_reserved=False, api_url="") 33 | 34 | stats_data = { 35 | "completion": str(round(stats.completion, 2)) + "%", 36 | "translated": stats.translated, 37 | "entries": stats.entries, 38 | "updated_at": datetime.now(timezone.utc).isoformat(timespec="seconds") + "Z", 39 | } 40 | 41 | stats_json = pofiles_path / "stats.json" 42 | try: 43 | with stats_json.open("w") as output_file: 44 | json.dump(stats_data, output_file) 45 | logging.info(f"Content written to {stats_json}") 46 | except IOError as e: 47 | logging.error(f"Failed to write to {stats_json}: {e}") 48 | raise 49 | 50 | 51 | if __name__ == "__main__": 52 | try: 53 | main() 54 | except Exception as e: 55 | logging.error(f"An error occurred: {e}") 56 | -------------------------------------------------------------------------------- /scripts/tx_stats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """Print translation percentage to update statistics.""" 3 | 4 | import json 5 | import os 6 | import configparser 7 | import urllib.request 8 | from datetime import datetime, timezone 9 | 10 | # Get language and project from environment variables 11 | language = os.environ.get("PYDOC_LANGUAGE") 12 | project = os.environ.get("PYDOC_TX_PROJECT") 13 | if language is None: 14 | raise ValueError("The PYDOC_LANGUAGE environment variable must be set.") 15 | if project is None: 16 | raise ValueError("The PYDOC_TX_PROJECT environment variable must be set.") 17 | 18 | 19 | # Try to read API token from TX_TOKEN env and then from ~/.transifexrc 20 | def get_transifex_token(): 21 | key = os.environ.get("TX_TOKEN") 22 | if key is None: 23 | config = configparser.ConfigParser() 24 | config.read(os.path.expanduser("~/.transifexrc")) 25 | try: 26 | key = config["https://www.transifex.com"]["token"] 27 | except KeyError: 28 | raise ValueError("Unable to retrieve Transifex API token.") 29 | return key 30 | 31 | 32 | # API URL setup 33 | url_template = ( 34 | "https://rest.api.transifex.com/resource_language_stats" 35 | "?filter[project]=o%3Apython-doc%3Ap%3A{project}" 36 | "&filter[language]=l%3A{language}" 37 | ) 38 | 39 | # Get the authorization key 40 | key = get_transifex_token() 41 | 42 | url = url_template.format(project=project, language=language) 43 | 44 | headers = {"accept": "application/vnd.api+json", "authorization": f"Bearer {key}"} 45 | 46 | # Initialize counters 47 | total_strings = 0 48 | translated_strings = 0 49 | 50 | 51 | # Function to make an API request and handle potential errors 52 | def fetch_data(url): 53 | request = urllib.request.Request(url=url, headers=headers) 54 | try: 55 | with urllib.request.urlopen(request) as response: 56 | return json.loads(response.read().decode("utf-8")) 57 | except urllib.error.URLError as e: 58 | raise ConnectionError(f"Error fetching data: {e}") 59 | except json.JSONDecodeError as e: 60 | raise ValueError(f"Error decoding JSON response: {e}") 61 | 62 | 63 | # Fetch and process translation stats 64 | while url: 65 | data = fetch_data(url) 66 | url = data["links"].get("next") 67 | for resource in data["data"]: 68 | translated_strings += resource["attributes"]["translated_strings"] 69 | total_strings += resource["attributes"]["total_strings"] 70 | 71 | # Calculate translation percentage 72 | if total_strings == 0: 73 | raise ValueError("Total strings cannot be zero.") 74 | 75 | percentage = f"{(translated_strings / total_strings):.2%}" 76 | 77 | # Print the result as JSON 78 | print( 79 | json.dumps( 80 | { 81 | "translation": percentage, 82 | "total": total_strings, 83 | "updated_at": datetime.now(timezone.utc).isoformat(timespec="seconds") 84 | + "Z", 85 | } 86 | ) 87 | ) 88 | --------------------------------------------------------------------------------