├── .dockerignore ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md.license ├── bin │ ├── docker-build │ └── get-buildx-args ├── labels.yml ├── matchers │ ├── mypy.json │ └── mypy.json.license ├── renovate.json ├── renovate.json.license └── workflows │ ├── closing.yml │ ├── dockerimage.yml │ ├── hadolint.yml │ ├── label-sync.yml │ ├── labels.yml │ ├── mypy.yml │ ├── pre-commit.yml │ ├── pull_requests.yaml │ ├── readme-sync.yml │ ├── setup.yml │ ├── stale.yml │ └── test.yml ├── .gitignore ├── .markdownlint.yml ├── .pre-commit-config.yaml ├── .reuse └── dep5 ├── .yamllint.yml ├── CHANGES.rst ├── Dockerfile ├── LICENSE ├── LICENSES ├── CC0-1.0.txt └── GPL-3.0-or-later.txt ├── MANIFEST.in ├── README.md ├── SECURITY.md ├── codecov.yml ├── completion └── wlc ├── pyproject.toml ├── wl └── wlc ├── __init__.py ├── config.py ├── main.py ├── py.typed ├── test_base.py ├── test_data ├── .weblate ├── api │ ├── categories │ ├── categories-1 │ ├── changes │ ├── components │ ├── components-hello-android │ ├── components-hello-android-file │ ├── components-hello-android-file--GET--format=zip │ ├── components-hello-olderweblate │ ├── components-hello-weblate │ ├── components-hello-weblate--DELETE-- │ ├── components-hello-weblate--PATCH--priority=80 │ ├── components-hello-weblate-changes │ ├── components-hello-weblate-file │ ├── components-hello-weblate-file--GET--format=zip │ ├── components-hello-weblate-lock │ ├── components-hello-weblate-lock--POST--lock=0 │ ├── components-hello-weblate-lock--POST--lock=1 │ ├── components-hello-weblate-repository │ ├── components-hello-weblate-repository--POST--operation=cleanup │ ├── components-hello-weblate-repository--POST--operation=commit │ ├── components-hello-weblate-repository--POST--operation=pull │ ├── components-hello-weblate-repository--POST--operation=push │ ├── components-hello-weblate-repository--POST--operation=reset │ ├── components-hello-weblate-statistics │ ├── components-hello-weblate-statistics--GET--page=2 │ ├── components-hello-weblate-statistics--GET--page=3 │ ├── components-hello-weblate-translations │ ├── components-hello-weblate-translations--GET--page=2 │ ├── components-hello-weblate-translations--POST--language_code=nl_BE │ ├── languages │ ├── languages--GET--page=2 │ ├── languages--GET--page=3 │ ├── languages--POST--code=tst--name=Test-Language--direction=rtl--plural=number=2--formula=n-!=-1 │ ├── projects │ ├── projects--POST--name=Hello--slug=hello--web=http---example.com---source_language=name=Malayalam--code=ml │ ├── projects--POST--name=denied_json--slug=denide_json--web=http---some-weblate-app.com- │ ├── projects-acl │ ├── projects-empty │ ├── projects-empty-components │ ├── projects-hello │ ├── projects-hello--DELETE-- │ ├── projects-hello-categories │ ├── projects-hello-changes │ ├── projects-hello-components │ ├── projects-hello-components--POST--9c51d0bc │ ├── projects-hello-components--POST--c9c26539 │ ├── projects-hello-languages │ ├── projects-hello-repository │ ├── projects-hello-repository--POST--operation=cleanup │ ├── projects-hello-repository--POST--operation=commit │ ├── projects-hello-repository--POST--operation=pull │ ├── projects-hello-repository--POST--operation=push │ ├── projects-hello-repository--POST--operation=reset │ ├── projects-hello-statistics │ ├── projects-invalid │ ├── translations │ ├── translations--GET--page=2 │ ├── translations--GET--page=3 │ ├── translations-hello-android-en-units │ ├── translations-hello-android-en-units--POST--key=test-monolingual--value=-test-me- │ ├── translations-hello-weblate-cs │ ├── translations-hello-weblate-cs--DELETE-- │ ├── translations-hello-weblate-cs-changes │ ├── translations-hello-weblate-cs-file │ ├── translations-hello-weblate-cs-file--GET--format=csv │ ├── translations-hello-weblate-cs-file--POST--3748506a │ ├── translations-hello-weblate-cs-file--POST--5c6a101f │ ├── translations-hello-weblate-cs-file--POST--85762547 │ ├── translations-hello-weblate-cs-file--POST--90a4957d │ ├── translations-hello-weblate-cs-file--POST--f8672b4c │ ├── translations-hello-weblate-cs-file--POST--fe040a0c │ ├── translations-hello-weblate-cs-repository │ ├── translations-hello-weblate-cs-repository--POST--operation=cleanup │ ├── translations-hello-weblate-cs-repository--POST--operation=commit │ ├── translations-hello-weblate-cs-repository--POST--operation=pull │ ├── translations-hello-weblate-cs-repository--POST--operation=push │ ├── translations-hello-weblate-cs-repository--POST--operation=reset │ ├── translations-hello-weblate-cs-statistics │ ├── translations-hello-weblate-cs-units │ ├── translations-hello-weblate-cs-units--GET--q=source%3A%3D%22mr%22 │ ├── units │ ├── units-123 │ ├── units-123--DELETE-- │ ├── units-123--PATCH--target=-foo---state=30 │ └── units-123--PUT--target=-foo---state=30 ├── mock │ └── project-local-file.pot ├── section ├── weblate.ini └── wlc ├── test_main.py └── test_wlc.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | .gitmodules 4 | .dockerignore 5 | .venv 6 | SECURITY.md 7 | Dockerfile 8 | docker-compose 9 | requirements-lint.txt 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | 7 | root = true 8 | 9 | [*] 10 | indent_style = space 11 | indent_size = 2 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | spaces_around_operators = true 17 | 18 | [*.bat] 19 | end_of_line = crlf 20 | 21 | [*.html] 22 | indent_size = 2 23 | 24 | [*.py] 25 | indent_size = 4 26 | 27 | [*.rst] 28 | indent_size = 3 29 | 30 | [*.js] 31 | quote_type = double 32 | 33 | [*.{markdown,md}] 34 | trim_trailing_whitespace = false 35 | 36 | [[shell]] 37 | indent_style = space 38 | indent_size = 4 39 | space_redirects = true 40 | simplify = true 41 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | github: WeblateOrg 7 | open_collective: weblate 8 | liberapay: Weblate 9 | custom: https://weblate.org/donate/ 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | # and generated from .github/ISSUE_TEMPLATE/snippets there. 7 | name: Reproducible bug report 8 | description: Create a report to help us improve 9 | body: 10 | - type: markdown 11 | attributes: 12 | value: | 13 | Thank you for reporting an issue. 14 | This form guides you in creating a useful issue report. 15 | 16 | Want your answer quickly and guaranteed? Visit https://weblate.org/support/ to reach our dedicated support team. As a subscriber, you will always have priority and help Weblate growing. 17 | - type: textarea 18 | id: what-happened 19 | attributes: 20 | label: Describe the issue 21 | description: > 22 | A clear and concise description of the problem you are facing. 23 | 24 | Please include important information, like the file format you are using and installed add-ons. 25 | placeholder: Tell us what you see! 26 | validations: 27 | required: true 28 | - type: checkboxes 29 | id: tried 30 | attributes: 31 | label: I already tried 32 | description: If you didn’t try already, try searching the documentation and existing issues. 33 | options: 34 | - label: I've read and searched [the documentation](https://docs.weblate.org/). 35 | required: true 36 | - label: I've searched for similar filed issues in this repository. 37 | required: true 38 | - type: textarea 39 | id: reproducer 40 | attributes: 41 | label: Steps to reproduce the behavior 42 | description: What did you do before the problem appeared? 43 | placeholder: | 44 | 1. Go to '…' 45 | 2. Scroll down to '…' 46 | 3. Click on '…' 47 | 4. The error occurs 48 | validations: 49 | required: true 50 | - type: textarea 51 | id: expected 52 | attributes: 53 | label: Expected behavior 54 | description: A clear and concise description of what you expected to happen. 55 | placeholder: Tell us what you want to see! 56 | - type: textarea 57 | id: screenshots 58 | attributes: 59 | label: Screenshots 60 | description: If applicable, add screenshots to better explain your problem. 61 | - type: textarea 62 | id: traceback 63 | attributes: 64 | label: Exception traceback 65 | description: > 66 | In case you observed a server error or crash, please read 67 | [the debugging documentation](https://docs.weblate.org/en/latest/contributing/debugging.html) 68 | for information on obtaining the relevant logs. 69 | 70 | This field will be rendered as a Python traceback automatically. 71 | render: pytb 72 | - type: textarea 73 | id: additional 74 | attributes: 75 | label: Additional context 76 | description: Add any other contextual info about the problem here. 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | # 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | # and generated using update-issue-config there. 7 | contact_links: 8 | - name: Read our clear, thorough and localized docs 9 | url: https://docs.weblate.org/ 10 | about: Save your time! There is an instant solution for many issues in the docs appreciated by numerous users. And it might be in your preferred language. 11 | - name: Get professional support 12 | url: https://weblate.org/support/ 13 | about: As a subscriber, you will always receive fast and helpful replies from our dedicated support team. More responsible and faster for your business, also makes Weblate stronger. 14 | - name: Ask the community 15 | url: https://github.com/WeblateOrg/weblate/discussions 16 | about: Want to discuss something with a community? Do it in discussions! 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | # and generated from .github/ISSUE_TEMPLATE/snippets there. 7 | name: Feature request 8 | description: Suggest an idea for this project 9 | body: 10 | - type: markdown 11 | attributes: 12 | value: | 13 | Thank you for requesting a change. 14 | This form guides you in creating a useful feature request. 15 | 16 | Want your answer quickly and guaranteed? Visit https://weblate.org/support/ to reach our dedicated support team. As a subscriber, you will always have priority and help Weblate growing. 17 | - type: textarea 18 | id: describe 19 | attributes: 20 | label: Describe the problem 21 | description: > 22 | Is your feature request related to a problem? If so, please provide 23 | a clear and concise description of what the problem is. 24 | placeholder: I'm always frustrated when… 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: solution 29 | attributes: 30 | label: Solution brainstorm 31 | description: We know you have ideas how to address this, please share it. 32 | placeholder: I'd like to get… 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: alternatives 37 | attributes: 38 | label: Describe alternatives you have considered 39 | description: A clear and concise description of any alternative solutions or features you have considered. 40 | placeholder: The issue can also be addressed by… 41 | - type: textarea 42 | id: screenshots 43 | attributes: 44 | label: Screenshots 45 | description: If applicable, add screenshots to better explain your problem. 46 | - type: textarea 47 | id: additional 48 | attributes: 49 | label: Additional context 50 | description: Add any other contextual info about the problem here. 51 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md.license: -------------------------------------------------------------------------------- 1 | Copyright © Michal Čihař 2 | 3 | SPDX-License-Identifier: CC0-1.0 4 | 5 | This file is maintained in https://github.com/WeblateOrg/meta/ 6 | -------------------------------------------------------------------------------- /.github/bin/docker-build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright © Michal Čihař 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | 7 | eval "docker buildx build $(.github/bin/get-buildx-args "$@" | sed 's/$/ /' | tr -d \\n)" 8 | -------------------------------------------------------------------------------- /.github/bin/get-buildx-args: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright © Michal Čihař 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | 7 | # Generate command-line options for docker buildx build 8 | 9 | # Load from local cache 10 | if [ -n "$MATRIX_ARCHITECTURE" ]; then 11 | echo --cache-from "type=local,src=/tmp/.buildx-cache/$MATRIX_ARCHITECTURE" 12 | else 13 | for arch in linux/amd64 linux/arm64; do 14 | echo --cache-from "type=local,src=/tmp/.buildx-cache/$arch" 15 | done 16 | fi 17 | 18 | # Expose annotations 19 | if [ "$1" != "load" ]; then 20 | sed -n "s/^LABEL \(org.opencontainers.image.*\)/--annotation index:\1/p" Dockerfile 21 | fi 22 | 23 | # Write to local cache unless publishing (not compatible with push) 24 | if [ "$1" != "publish" ]; then 25 | echo --cache-to "type=local,dest=/tmp/.buildx-cache/$MATRIX_ARCHITECTURE,mode=max" 26 | fi 27 | 28 | if [ "$1" = "load" ]; then 29 | # Ommit list of platforms loading, see https://github.com/docker/buildx/issues/59 30 | echo --output=type=docker 31 | # Test tag, to avoid collistion with real ones 32 | echo --tag weblate/wlc:test 33 | else 34 | # List of platforms 35 | echo --platform "${MATRIX_ARCHITECTURE:-linux/amd64,linux/arm64}" 36 | # Enable more platforms in future: 37 | # linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/ppc64le,linux/s390x 38 | 39 | if [ "$1" = "publish-bleeding" ]; then 40 | # Publishing bleeding edge image 41 | echo --output "type=image,push=true" 42 | echo --tag "${DOCKER_IMAGE:-weblate/wlc}:bleeding" 43 | echo --tag "${DOCKER_IMAGE:-weblate/wlc}:bleeding-$(date -I)-$(git rev-parse --short HEAD)" 44 | elif [ "$1" = "publish" ]; then 45 | # Publishing normal image 46 | echo --output "type=image,push=true" 47 | 48 | # Generate tags 49 | case "$GITHUB_REF" in 50 | refs/tags/[0-9]*) 51 | RELEASE="${GITHUB_REF#refs/tags/}" 52 | MINOR="${RELEASE%.*.*}" 53 | MAJOR="${MINOR%.*}" 54 | echo --tag "${DOCKER_IMAGE:-weblate/wlc}:$RELEASE" 55 | echo --tag "${DOCKER_IMAGE:-weblate/wlc}:$MAJOR" 56 | echo --tag "${DOCKER_IMAGE:-weblate/wlc}:latest" 57 | ;; 58 | refs/heads/main) 59 | echo --tag "${DOCKER_IMAGE:-weblate/wlc}:edge" 60 | echo --tag "${DOCKER_IMAGE:-weblate/wlc}:edge-$(date -I)-${GITHUB_SHA}" 61 | ;; 62 | esac 63 | else 64 | echo --output "type=image,push=false" 65 | fi 66 | fi 67 | 68 | # Location 69 | echo --file ./Dockerfile . 70 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | 7 | # Default GitHub labels 8 | - color: d73a4a 9 | name: bug 10 | description: Something is broken. 11 | - color: ffffff 12 | name: duplicate 13 | description: Similar issue or pull request already exists. 14 | - color: a2eeef 15 | name: enhancement 16 | description: Adding or requesting a new feature. 17 | - color: 7057ff 18 | name: good first issue 19 | description: Opportunity for newcoming contributors. 20 | - color: 7057ff 21 | name: help wanted 22 | description: Extra attention is needed. 23 | - color: ffffff 24 | name: invalid 25 | description: This doesn’t seem right. 26 | - color: d876e3 27 | name: question 28 | description: This is more a question for the support than an issue. 29 | - color: ffffff 30 | name: wontfix 31 | description: Nobody will work on this. 32 | # Automated dependency updates 33 | - color: e3f49c 34 | name: dependencies 35 | description: Third-party library dependencies. 36 | # Hacktoberfest 37 | - color: 7057ff 38 | name: hacktoberfest 39 | description: This is suitable for Hacktoberfest. Don’t try to spam. 40 | # Weblate specific 41 | - color: 87a2f2 42 | name: backlog 43 | description: This is not on the Weblate roadmap for now. Can be prioritized by sponsorship. 44 | - color: 0075ca 45 | name: documentation 46 | description: Improvements or additions to the documentation. 47 | - color: e3f49c 48 | name: hosted 49 | description: Issues affecting Hosted Weblate service. 50 | - color: e3f49c 51 | name: naming 52 | description: Discussions about naming Weblate features. 53 | - color: fef2c0 54 | name: translate-toolkit 55 | description: Issues which need to be fixed in the translate-toolkit 56 | - color: 87a2f2 57 | name: undecided 58 | description: These features might not be implemented. Can be prioritized by sponsorship. 59 | - color: e3f49c 60 | name: ux 61 | description: Issues related to user experience. 62 | - color: b38ddd 63 | name: needinfo 64 | description: This needs more information from the reporter to be implemented. 65 | -------------------------------------------------------------------------------- /.github/matchers/mypy.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "mypy", 5 | "pattern": [ 6 | { 7 | "regexp": "^([^:]*):(\\d+):(?:(\\d+):)? ([^:]*): (.*?)(?: \\[(\\S+)\\])?$", 8 | "file": 1, 9 | "line": 2, 10 | "column": 3, 11 | "severity": 4, 12 | "message": 5, 13 | "code": 6 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.github/matchers/mypy.json.license: -------------------------------------------------------------------------------- 1 | Copyright © Michal Čihař 2 | 3 | SPDX-License-Identifier: CC0-1.0 4 | 5 | This file is maintained in https://github.com/WeblateOrg/meta/ 6 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>WeblateOrg/meta:renovate" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.github/renovate.json.license: -------------------------------------------------------------------------------- 1 | Copyright © Michal Čihař 2 | 3 | SPDX-License-Identifier: CC0-1.0 4 | 5 | This file is maintained in https://github.com/WeblateOrg/meta/ 6 | -------------------------------------------------------------------------------- /.github/workflows/closing.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | name: Issue closed 7 | 8 | on: 9 | issues: 10 | types: [closed] 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event.issue.id }} 14 | cancel-in-progress: true 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | issueClosed: 21 | permissions: 22 | issues: write # for peter-evans/create-or-update-comment to create or update comment 23 | pull-requests: write # for peter-evans/create-or-update-comment to create or update comment 24 | runs-on: ubuntu-24.04 25 | steps: 26 | - name: Add closed question comment 27 | uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 28 | if: | 29 | github.actor != 'renovate[bot]' && 30 | github.event.issue.state_reason == 'completed' && 31 | (( 32 | contains(github.event.issue.labels.*.name, 'question') && 33 | ! contains(github.event.issue.labels.*.name, 'duplicate') && 34 | ! contains(github.event.issue.labels.*.name, 'wontfix') 35 | ) || join(github.event.issue.labels.*.name) == '') 36 | with: 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | issue-number: ${{ github.event.issue.number }} 39 | body: | 40 | The issue you have reported is now resolved. If you don’t feel it’s right, please follow its labels to get a clue for further steps. 41 | 42 | * In case you see a similar problem, please open a separate issue. 43 | * If you are happy with the outcome, don’t hesitate to support Weblate by making a [donation](https://weblate.org/donate/). 44 | - name: Add closed issue comment 45 | uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 46 | if: | 47 | github.actor != 'renovate[bot]' && 48 | github.event.issue.state_reason == 'completed' && 49 | ! contains(github.event.issue.labels.*.name, 'invalid') && 50 | ! contains(github.event.issue.labels.*.name, 'question') && 51 | ! contains(github.event.issue.labels.*.name, 'wontfix') && 52 | ! contains(github.event.issue.labels.*.name, 'duplicate') && 53 | ! contains(github.event.issue.labels.*.name, 'undecided') && 54 | ! contains(github.event.issue.labels.*.name, 'needinfo') && 55 | join(github.event.issue.labels.*.name) != '' 56 | with: 57 | token: ${{ secrets.GITHUB_TOKEN }} 58 | issue-number: ${{ github.event.issue.number }} 59 | body: | 60 | Thank you for your report; the issue you have reported has just been fixed. 61 | 62 | * In case you see a problem with the fix, please comment on this issue. 63 | * In case you see a similar problem, please open a separate issue. 64 | * If you are happy with the outcome, don’t hesitate to support Weblate by making a [donation](https://weblate.org/donate/). 65 | -------------------------------------------------------------------------------- /.github/workflows/dockerimage.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | name: Docker Image CI 6 | 7 | on: 8 | push: 9 | branches-ignore: 10 | - renovate/** 11 | tags: 12 | - '*' 13 | pull_request: 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-24.04 21 | name: Build, linux/${{ matrix.architecture }} 22 | strategy: 23 | matrix: 24 | architecture: 25 | - amd64 26 | env: 27 | MATRIX_ARCHITECTURE: linux/${{ matrix.architecture }} 28 | steps: 29 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 30 | with: 31 | persist-credentials: false 32 | - name: Set up Docker Buildx 33 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 34 | - name: Configure Docker build 35 | run: .github/bin/get-buildx-args 36 | - name: Build the Docker image 37 | run: .github/bin/docker-build 38 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 39 | with: 40 | name: Docker cache ${{ matrix.architecture }} 41 | path: /tmp/.buildx-cache/linux/${{ matrix.architecture }} 42 | retention-days: 1 43 | 44 | buildx: 45 | runs-on: ubuntu-24.04-arm 46 | name: Build, linux/${{ matrix.architecture }} 47 | strategy: 48 | matrix: 49 | architecture: 50 | - arm64 51 | env: 52 | MATRIX_ARCHITECTURE: linux/${{ matrix.architecture }} 53 | steps: 54 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 55 | with: 56 | persist-credentials: false 57 | - name: Set up Docker Buildx 58 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 59 | - name: Configure Docker build 60 | run: .github/bin/get-buildx-args 61 | - name: Build the Docker image 62 | run: .github/bin/docker-build 63 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 64 | with: 65 | name: Docker cache ${{ matrix.architecture }} 66 | path: /tmp/.buildx-cache/linux/${{ matrix.architecture }} 67 | retention-days: 1 68 | 69 | test: 70 | runs-on: ubuntu-24.04 71 | name: Test, ${{ matrix.architecture }} 72 | needs: [build] 73 | strategy: 74 | matrix: 75 | architecture: [linux/amd64] 76 | env: 77 | MATRIX_ARCHITECTURE: ${{ matrix.architecture }} 78 | COMPOSE_PROJECT_NAME: wl 79 | steps: 80 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 81 | with: 82 | persist-credentials: false 83 | - name: Set up Docker Buildx 84 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 85 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 86 | with: 87 | name: Docker cache amd64 88 | path: /tmp/.buildx-cache/linux/amd64 89 | - name: Build the Docker image 90 | run: .github/bin/docker-build load 91 | - name: List Docker images 92 | run: docker image ls --all 93 | - name: Test the Docker image 94 | run: docker run --rm weblate/wlc:test version | grep "version" 95 | 96 | anchore: 97 | runs-on: ubuntu-24.04 98 | name: Anchore Container Scan, ${{ matrix.architecture }} 99 | needs: 100 | - build 101 | permissions: 102 | security-events: write 103 | strategy: 104 | matrix: 105 | architecture: [linux/amd64] 106 | env: 107 | MATRIX_ARCHITECTURE: ${{ matrix.architecture }} 108 | steps: 109 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 110 | with: 111 | persist-credentials: false 112 | - name: Set up Docker Buildx 113 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 114 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 115 | with: 116 | name: Docker cache amd64 117 | path: /tmp/.buildx-cache/linux/amd64 118 | - name: Build the Docker image 119 | run: .github/bin/docker-build load 120 | - name: List Docker images 121 | run: docker image ls --all 122 | - name: Checkout the code 123 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 124 | with: 125 | persist-credentials: false 126 | - name: Anchore scan action 127 | uses: anchore/scan-action@2c901ab7378897c01b8efaa2d0c9bf519cc64b9e # v6.2.0 128 | id: scan 129 | with: 130 | image: weblate/wlc:test 131 | fail-build: false 132 | severity-cutoff: high 133 | - name: Upload Anchore Scan Report 134 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 135 | with: 136 | sarif_file: ${{ steps.scan.outputs.sarif }} 137 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 138 | with: 139 | name: Anchore scan SARIF 140 | path: ${{ steps.scan.outputs.sarif }} 141 | 142 | trivy: 143 | runs-on: ubuntu-24.04 144 | name: Trivy Container Scan, ${{ matrix.architecture }} 145 | needs: 146 | - build 147 | permissions: 148 | security-events: write 149 | strategy: 150 | matrix: 151 | architecture: [linux/amd64] 152 | env: 153 | MATRIX_ARCHITECTURE: ${{ matrix.architecture }} 154 | steps: 155 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 156 | with: 157 | persist-credentials: false 158 | - name: Set up Docker Buildx 159 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 160 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 161 | with: 162 | name: Docker cache amd64 163 | path: /tmp/.buildx-cache/linux/amd64 164 | - name: Build the Docker image 165 | run: .github/bin/docker-build load 166 | - name: List Docker images 167 | run: docker image ls --all 168 | - name: Checkout the code 169 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 170 | with: 171 | persist-credentials: false 172 | - name: Run Trivy vulnerability scanner 173 | uses: aquasecurity/trivy-action@76071ef0d7ec797419534a183b498b4d6366cf37 # 0.31.0 174 | with: 175 | image-ref: weblate/wlc:test 176 | format: template 177 | template: '@/contrib/sarif.tpl' 178 | output: trivy-results.sarif 179 | severity: CRITICAL,HIGH 180 | 181 | - name: Upload Trivy scan results to GitHub Security tab 182 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 183 | with: 184 | sarif_file: trivy-results.sarif 185 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 186 | with: 187 | name: Trivy scan SARIF 188 | path: trivy-results.sarif 189 | 190 | push_dockerhub: 191 | runs-on: ubuntu-24.04 192 | name: Publish to Docker Hub 193 | needs: 194 | - test 195 | - buildx 196 | - anchore 197 | - trivy 198 | if: ${{ (startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main')) && github.repository == 'WeblateOrg/wlc' }} 199 | steps: 200 | - name: Checkout 201 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 202 | with: 203 | persist-credentials: false 204 | - name: Set up QEMU 205 | uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 206 | with: 207 | platforms: all 208 | - name: Set up Docker Buildx 209 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 210 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 211 | with: 212 | name: Docker cache amd64 213 | path: /tmp/.buildx-cache/linux/amd64 214 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 215 | with: 216 | name: Docker cache arm64 217 | path: /tmp/.buildx-cache/linux/arm64 218 | - name: DockerHub login 219 | run: echo "${{ secrets.DOCKERHUB_ACCESS_TOKEN }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin 220 | - name: Configure Docker build 221 | run: .github/bin/get-buildx-args publish 222 | - name: Publish the Docker images 223 | run: .github/bin/docker-build publish 224 | 225 | push_github: 226 | runs-on: ubuntu-24.04 227 | name: Publish to GitHub 228 | permissions: 229 | packages: write 230 | needs: 231 | - test 232 | - buildx 233 | - anchore 234 | - trivy 235 | if: ${{ (startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main')) && github.repository == 'WeblateOrg/wlc' }} 236 | env: 237 | DOCKER_IMAGE: ghcr.io/weblateorg/wlc 238 | steps: 239 | - name: Checkout 240 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 241 | with: 242 | persist-credentials: false 243 | - name: Set up QEMU 244 | uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 245 | with: 246 | platforms: all 247 | - name: Set up Docker Buildx 248 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 249 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 250 | with: 251 | name: Docker cache amd64 252 | path: /tmp/.buildx-cache/linux/amd64 253 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 254 | with: 255 | name: Docker cache arm64 256 | path: /tmp/.buildx-cache/linux/arm64 257 | - name: Login to GitHub Container Registry 258 | if: ${{ github.event_name != 'pull_request'}} 259 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 260 | with: 261 | registry: ghcr.io 262 | username: ${{ github.actor }} 263 | password: ${{ secrets.GITHUB_TOKEN }} 264 | - name: Configure Docker build 265 | run: .github/bin/get-buildx-args publish 266 | - name: Publish the Docker images 267 | run: .github/bin/docker-build publish 268 | -------------------------------------------------------------------------------- /.github/workflows/hadolint.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | name: Hadolint 7 | 8 | on: 9 | push: 10 | branches-ignore: 11 | - renovate/** 12 | - weblate 13 | pull_request: 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | hadolint: 20 | 21 | runs-on: ubuntu-24.04 22 | 23 | steps: 24 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 25 | with: 26 | persist-credentials: false 27 | - uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0 28 | -------------------------------------------------------------------------------- /.github/workflows/label-sync.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | name: Sync labels 7 | 8 | on: 9 | push: 10 | branches: 11 | - main 12 | paths: 13 | - .github/labels.yml 14 | - .github/workflows/label-sync.yml 15 | 16 | permissions: 17 | issues: write 18 | 19 | jobs: 20 | build: 21 | name: Sync labels 22 | runs-on: ubuntu-24.04 23 | steps: 24 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 25 | with: 26 | persist-credentials: false 27 | - uses: srealmoreno/label-sync-action@850ba5cef2b25e56c6c420c4feed0319294682fd # v2.0.0 28 | with: 29 | clean-labels: true 30 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | name: Issue labeled 7 | 8 | on: 9 | issues: 10 | types: [labeled] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | issueLabeled: 17 | permissions: 18 | issues: write # for peter-evans/create-or-update-comment to create or update comment 19 | pull-requests: write # for peter-evans/create-or-update-comment to create or update comment 20 | runs-on: ubuntu-24.04 21 | steps: 22 | - name: Add backlog comment 23 | uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 24 | if: ${{ github.event.label.name == 'backlog' }} 25 | with: 26 | token: ${{ secrets.GITHUB_TOKEN }} 27 | issue-number: ${{ github.event.issue.number }} 28 | body: > 29 | This issue has been added to the backlog. It is not scheduled 30 | on the Weblate roadmap, but it eventually might be implemented. 31 | 32 | 33 | In case you need this feature soon, please consider helping 34 | or push it by [funding the development](https://weblate.org/support/). 35 | - name: Add undecided comment 36 | uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 37 | if: ${{ github.event.label.name == 'undecided' }} 38 | with: 39 | token: ${{ secrets.GITHUB_TOKEN }} 40 | issue-number: ${{ github.event.issue.number }} 41 | body: > 42 | This issue has been put aside. It is currently unclear if it will 43 | ever be implemented as it seems to cover too narrow of a use case 44 | or doesn't seem to fit into Weblate. 45 | 46 | 47 | Please try to clarify the use case or consider proposing something more generic to make it useful to more users. 48 | - name: Add question comment 49 | uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 50 | if: ${{ github.event.label.name == 'question' }} 51 | with: 52 | token: ${{ secrets.GITHUB_TOKEN }} 53 | issue-number: ${{ github.event.issue.number }} 54 | body: > 55 | This issue has been marked as a question by a Weblate team member. 56 | Why? Because it belongs more to the professional [Weblate Care](https://care.weblate.org/) 57 | or community [Discussions](https://github.com/WeblateOrg/weblate/discussions) than here. 58 | We strive to answer these reasonably fast here, too, but 59 | [purchasing the support subscription](https://weblate.org/support/) 60 | is more responsible and faster for your business. 61 | And it makes Weblate stronger as well. Thanks! 62 | 63 | 64 | In case your question is already answered, [making a donation](https://weblate.org/donate/) is the right way to say thank you! 65 | - name: Add translate-toolkit comment 66 | uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 67 | if: ${{ github.event.label.name == 'translate-toolkit' }} 68 | with: 69 | token: ${{ secrets.GITHUB_TOKEN }} 70 | issue-number: ${{ github.event.issue.number }} 71 | body: > 72 | The issue you've reported needs to be addressed in the [translate-toolkit](https://github.com/translate/translate/). 73 | Please file the issue there, and include links to any relevant specifications about the formats (if applicable). 74 | - name: Add good first issue comment 75 | uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 76 | if: ${{ github.event.label.name == 'good first issue' }} 77 | with: 78 | token: ${{ secrets.GITHUB_TOKEN }} 79 | issue-number: ${{ github.event.issue.number }} 80 | body: > 81 | This issue seems to be a good fit for newbie contributors. 82 | You are welcome to contribute to Weblate! Don't hesitate to 83 | ask any questions you would have while implementing this. 84 | 85 | 86 | You can learn about how to get started in our 87 | [contributors documentation](https://docs.weblate.org/en/latest/contributing/index.html). 88 | -------------------------------------------------------------------------------- /.github/workflows/mypy.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | name: mypy 6 | 7 | on: 8 | push: 9 | branches-ignore: 10 | - renovate/** 11 | - weblate 12 | pull_request: 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | mypy: 19 | runs-on: ubuntu-24.04 20 | 21 | steps: 22 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 23 | with: 24 | fetch-depth: 2 25 | 26 | persist-credentials: false 27 | - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 28 | with: 29 | cache-suffix: test 30 | - name: Setup Python 31 | uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 32 | with: 33 | python-version: '3.13' 34 | - name: Install pip dependencies 35 | run: uv pip install --system -e ".[types,test]" 36 | 37 | - name: Run mypy 38 | run: | 39 | echo "::add-matcher::.github/matchers/mypy.json" 40 | mypy --show-column-numbers wlc 41 | echo "::remove-matcher owner=mypy::" 42 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | name: Pre-commit check 7 | 8 | on: 9 | push: 10 | branches-ignore: 11 | - renovate/** 12 | - weblate 13 | pull_request: 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | pre-commit: 20 | runs-on: ubuntu-24.04 21 | 22 | steps: 23 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 24 | with: 25 | persist-credentials: false 26 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 27 | with: 28 | path: ~/.cache/pre-commit 29 | key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} 30 | - name: Setup Python 31 | uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 32 | with: 33 | python-version: '3.13' 34 | - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 35 | - name: detect method 36 | id: detect 37 | run: | 38 | if test -f requirements-lint.txt ; then 39 | echo "method=requirements" >> "$GITHUB_OUTPUT" 40 | elif test -f pyproject.toml && grep -q dependency-groups pyproject.toml ; then 41 | echo "method=pep735" >> "$GITHUB_OUTPUT" 42 | elif test -f pyproject.toml && grep -q dependency-groups pyproject.toml ; then 43 | echo "method=pyproject" >> "$GITHUB_OUTPUT" 44 | else 45 | echo "method=uvx" >> "$GITHUB_OUTPUT" 46 | fi 47 | - name: pre-commit (PEP 735) 48 | if: steps.detect.outputs.method == 'pep735' 49 | run: uv run --only-group pre-commit pre-commit run --all --show-diff-on-failure 50 | env: 51 | RUFF_OUTPUT_FORMAT: github 52 | REUSE_OUTPUT_FORMAT: github 53 | - name: pre-commit (uvx) 54 | if: steps.detect.outputs.method == 'uvx' 55 | run: uvx pre-commit run --all --show-diff-on-failure 56 | env: 57 | RUFF_OUTPUT_FORMAT: github 58 | REUSE_OUTPUT_FORMAT: github 59 | - name: Install dependencies 60 | if: steps.detect.outputs.method == 'requirements' 61 | run: uv pip install --system -r requirements-lint.txt 62 | - name: Install dependencies 63 | if: steps.detect.outputs.method == 'pyproject' 64 | run: uv pip install --system "$(sed -n 's/.*"\(pre-commit==\([^"]*\)\)".*/\1/p' pyproject.toml)" 65 | - name: pre-commit (installed) 66 | if: steps.detect.outputs.method == 'requirements' || steps.detect.outputs.method == 'pyproject' 67 | run: pre-commit run --all --show-diff-on-failure 68 | env: 69 | RUFF_OUTPUT_FORMAT: github 70 | REUSE_OUTPUT_FORMAT: github 71 | - name: diff 72 | run: git diff 73 | if: always() 74 | - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 75 | if: always() 76 | with: 77 | msg: 'chore(pre-commit): apply code formatting' 78 | -------------------------------------------------------------------------------- /.github/workflows/pull_requests.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | 7 | name: Pull request automation 8 | 9 | on: # zizmor: ignore[dangerous-triggers] 10 | pull_request_target: 11 | types: opened 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | weblate_automerge: 18 | permissions: 19 | pull-requests: write 20 | contents: write 21 | runs-on: ubuntu-24.04 22 | name: Weblate automerge 23 | if: github.actor == 'weblate' 24 | steps: 25 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | with: 27 | persist-credentials: false 28 | - name: Enable Pull Request Automerge 29 | run: gh pr merge --rebase --auto "${{ github.event.pull_request.number }}" 30 | env: 31 | GH_TOKEN: ${{ secrets.WEBLATE_CI_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/readme-sync.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | name: Docker Hub Description 6 | on: 7 | push: 8 | branches: 9 | - main 10 | paths: 11 | - README.md 12 | - .github/workflows/readme-sync.yml 13 | jobs: 14 | sync-readme: 15 | runs-on: ubuntu-24.04 16 | if: ${{ github.repository == 'WeblateOrg/wlc' }} 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 20 | with: 21 | persist-credentials: false 22 | - name: Docker Hub Description 23 | uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2 24 | with: 25 | username: ${{ secrets.DOCKERHUB_USERNAME }} 26 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 27 | repository: weblate/weblate 28 | short-description: ${{ github.event.repository.description }} 29 | permissions: 30 | contents: read 31 | -------------------------------------------------------------------------------- /.github/workflows/setup.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | name: Distribution 6 | 7 | on: [push, pull_request] 8 | 9 | jobs: 10 | setup: 11 | name: Build packages 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 15 | with: 16 | persist-credentials: false 17 | - name: Setup Python 18 | uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 19 | with: 20 | python-version: '3.13' 21 | - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 22 | with: 23 | enable-cache: true 24 | cache-dependency-glob: '' 25 | - name: Install dependencies 26 | run: uv pip install --system -e .[dev] 27 | - name: build sdist 28 | run: uv build 29 | - name: twine 30 | run: uvx twine check dist/* 31 | - name: pydistcheck 32 | run: uvx pydistcheck --inspect dist/* 33 | - name: pyroma 34 | run: uvx pyroma dist/*.tar.gz 35 | - name: check-wheel-contents 36 | run: uvx check-wheel-contents dist/*.whl 37 | - name: check-manifest 38 | run: uvx check-manifest -v 39 | 40 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 41 | with: 42 | path: dist/* 43 | name: dist 44 | 45 | release_pypi: 46 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'WeblateOrg/language-data' 47 | runs-on: ubuntu-24.04 48 | name: Publish release to PyPI 49 | permissions: 50 | # this permission is mandatory for trusted publishing 51 | id-token: write 52 | needs: 53 | - setup 54 | steps: 55 | - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0 56 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 57 | with: 58 | name: dist 59 | path: dist 60 | - name: Publish package 61 | run: uv publish --trusted-publishing always 62 | 63 | release_github: 64 | runs-on: ubuntu-24.04 65 | name: Create release on GitHub 66 | permissions: 67 | contents: write 68 | needs: 69 | - setup 70 | if: startsWith(github.ref, 'refs/tags/') && github.repository == 'WeblateOrg/language-data' 71 | steps: 72 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 73 | with: 74 | name: dist 75 | path: dist 76 | - uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1.16.0 77 | with: 78 | generateReleaseNotes: true 79 | artifacts: dist/* 80 | permissions: 81 | contents: read 82 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | name: Close stale 7 | 8 | on: 9 | schedule: 10 | - cron: 30 1 * * * 11 | push: 12 | branches: 13 | - main 14 | paths: 15 | - .github/workflows/stale.yml 16 | workflow_dispatch: 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | stale-issues: 23 | runs-on: ubuntu-24.04 24 | permissions: 25 | issues: write 26 | pull-requests: write 27 | 28 | steps: 29 | - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 30 | with: 31 | days-before-pr-stale: 30 32 | days-before-pr-close: 14 33 | days-before-stale: 14 34 | days-before-close: 5 35 | exempt-issue-labels: bug,enhancement,documentation,security,dependencies 36 | exempt-pr-labels: backlog 37 | stale-issue-label: wontfix 38 | stale-pr-label: wontfix 39 | repo-token: ${{ secrets.GITHUB_TOKEN }} 40 | stale-issue-message: | 41 | This issue has been automatically marked as stale because there wasn’t any recent activity. 42 | 43 | It will be closed soon if no further action occurs. 44 | 45 | Thank you for your contributions! 46 | stale-pr-message: | 47 | This pull request has been automatically marked as stale because there wasn’t any recent activity. 48 | 49 | It will be closed soon if no further action occurs. 50 | 51 | Thank you for your contributions! 52 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | name: Test 6 | 7 | on: [push, pull_request] 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: 15 | - ubuntu-latest 16 | python-version: 17 | - '3.9' 18 | - '3.10' 19 | - '3.11' 20 | - '3.12' 21 | - '3.13' 22 | include: 23 | - os: windows-latest 24 | python-version: '3.13' 25 | - os: macos-latest 26 | python-version: '3.13' 27 | - os: ubuntu-24.04-arm 28 | python-version: '3.13' 29 | name: ${{ matrix.os }}, Python ${{ matrix.python-version }} 30 | 31 | steps: 32 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 33 | with: 34 | persist-credentials: false 35 | - name: Set up Python ${{ matrix.python-version }} 36 | uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 37 | with: 38 | python-version: ${{ matrix.python-version }} 39 | cache: pip 40 | cache-dependency-path: pyproject.toml 41 | - name: Install pip dependencies 42 | run: | 43 | python -m pip install --upgrade pip wheel 44 | pip install -e ".[test]" 45 | - name: Test 46 | run: | 47 | py.test --cov=wlc wlc 48 | - name: Coverage 49 | run: | 50 | coverage xml 51 | - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 52 | with: 53 | token: ${{secrets.CODECOV_TOKEN}} 54 | flags: unittests 55 | name: Python ${{ matrix.python-version }}, ${{ matrix.os }} 56 | permissions: 57 | contents: read 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | release.sh 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | /.pytest_cache/ 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | .venv/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | 58 | # Sphinx documentation 59 | docs/_build/ 60 | 61 | # PyBuilder 62 | target/ 63 | 64 | #Ipython Notebook 65 | .ipynb_checkpoints 66 | -------------------------------------------------------------------------------- /.markdownlint.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | # 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | 7 | first-line-heading: false 8 | line-length: false 9 | html: 10 | allowed_elements: [a, img] 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-toml 11 | - id: check-merge-conflict 12 | - id: check-json 13 | - id: debug-statements 14 | - id: mixed-line-ending 15 | args: [--fix=lf] 16 | - id: pretty-format-json 17 | args: [--no-sort-keys, --autofix] 18 | - repo: https://github.com/adrienverge/yamllint 19 | rev: v1.37.1 20 | hooks: 21 | - id: yamllint 22 | - repo: https://github.com/astral-sh/ruff-pre-commit 23 | rev: v0.11.12 24 | hooks: 25 | - id: ruff-check 26 | args: 27 | - --fix 28 | - --exit-non-zero-on-fix 29 | - id: ruff-format 30 | - repo: meta 31 | hooks: 32 | - id: check-hooks-apply 33 | - id: check-useless-excludes 34 | - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks 35 | rev: v2.14.0 36 | hooks: 37 | - id: pretty-format-yaml 38 | args: [--autofix, --indent, '2'] 39 | - id: pretty-format-toml 40 | args: [--autofix] 41 | - repo: https://github.com/executablebooks/mdformat 42 | rev: 0.7.22 43 | hooks: 44 | - id: mdformat 45 | additional_dependencies: 46 | - mdformat-gfm==0.4.1 47 | - mdformat-ruff==0.1.3 48 | - mdformat-shfmt==0.2.0 49 | - mdformat_tables==1.0.0 50 | - repo: https://github.com/scop/pre-commit-shfmt 51 | rev: v3.11.0-1 52 | hooks: 53 | - id: shfmt 54 | - repo: https://github.com/igorshubovych/markdownlint-cli 55 | rev: v0.45.0 56 | hooks: 57 | - id: markdownlint 58 | - repo: https://github.com/fsfe/reuse-tool 59 | rev: v5.0.2 60 | hooks: 61 | - id: reuse 62 | - repo: https://github.com/rhysd/actionlint 63 | rev: v1.7.7 64 | hooks: 65 | - id: actionlint 66 | - repo: https://github.com/adamchainz/blacken-docs 67 | rev: 1.19.1 68 | hooks: 69 | - id: blacken-docs 70 | - repo: https://github.com/zizmorcore/zizmor-pre-commit 71 | rev: v1.9.0 72 | hooks: 73 | - id: zizmor 74 | ci: 75 | autoupdate_schedule: quarterly 76 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: wlc 3 | Upstream-Contact: Michal Čihař 4 | Source: https://weblate.org/ 5 | 6 | Files: wlc/test_data/* 7 | Copyright: Michal Čihař 8 | License: GPL-3.0-or-later 9 | 10 | Files: .coveragerc .dockerignore .gitignore .isort.cfg .pre-commit-config.yaml MANIFEST.in codecov.yml requirements*.txt setup.cfg pyproject.toml .editorconfig 11 | Copyright: Michal Čihař 12 | License: GPL-3.0-or-later 13 | 14 | Files: CHANGES.rst README.md 15 | Copyright: Michal Čihař 16 | License: GPL-3.0-or-later 17 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | # 5 | # This file is maintained in https://github.com/WeblateOrg/meta/ 6 | # 7 | extends: default 8 | 9 | rules: 10 | line-length: 11 | max: 500 12 | level: error 13 | document-start: disable 14 | indentation: 15 | indent-sequences: false 16 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | 1.16 2 | ---- 3 | 4 | * Not yet released. 5 | * Fixed downloading components based on configration. 6 | * Improved handling of categorized components. 7 | * Dropped support for Python 3.8. 8 | * Added support for Python 3.13. 9 | 10 | 1.15 11 | ---- 12 | 13 | * Released on 3rd September 2024. 14 | * Fixed installation on certain filesystems. 15 | 16 | 1.14 17 | ---- 18 | 19 | * Released on 23rd February 2024. 20 | * Dropped support for Python 3.6 and 3.7. 21 | * Tested against Python 3.11 and 3.12. 22 | * Added support for categories. 23 | * Added support for the linked_component field. 24 | 25 | 1.13 26 | ---- 27 | 28 | * Released on 24th March 2022. 29 | * Support all wlc upload methods. 30 | * Tested against Python 3.10. 31 | * Allow uploading files when creating a component. 32 | * Allow downloading at component, project, and site level. 33 | 34 | 1.12 35 | ---- 36 | 37 | * Released on 19th May 2021. 38 | * Improved error messages when permission was denied. 39 | * Added type hints to the codebase. 40 | 41 | 1.11 42 | ---- 43 | 44 | * Released on 17th March 2021. 45 | * Fixed long filenames in test fixtures. 46 | * Updated base docker image. 47 | * Add support for specifying format when uploading files. 48 | 49 | 1.10 50 | ---- 51 | 52 | * Released on 11th February 2021. 53 | * Added ability to create new projects, components, and translations 54 | * Added ability to add new source strings to a component 55 | * Added Retry mechanism to handle failed requests 56 | * Added ability to create a new language 57 | * Added the source_lang attribute to Components 58 | * Added Unit class which provides access to the units api endpoints. 59 | * Added the PATCH HTTP method to the white list in order to enable access to new api endpoints. 60 | * Using requests.session to keep a single http request open instead of reinitializing them. 61 | * Added timeout config variable 62 | 63 | 1.9 64 | --- 65 | 66 | * Released on 21st December 2020. 67 | * Added Docker image published as weblate/wlc on Docker Hub. 68 | 69 | 1.8 70 | --- 71 | 72 | * Released on 11th September 2020. 73 | * Compatibility with Weblate 4.3 which moves source language from component to a project. 74 | 75 | 1.7 76 | --- 77 | 78 | * Released on 3rd September 2020. 79 | * Fixed installation of wlc command-line. 80 | 81 | 1.6 82 | --- 83 | 84 | * Released on 1st September 2020. 85 | * Post payload as JSON to allow handling complex data structures. 86 | * Added support for finding configuration file on Windows in AppData dir. 87 | * Improved error reporting. 88 | 89 | 1.5 90 | --- 91 | 92 | * Released on 19th June 2020. 93 | * Fixed compatibility of some API calls with Weblate 4.1. 94 | 95 | 1.4 96 | --- 97 | 98 | * Released on 3rd June 2020. 99 | * Fixed compatibility of some API calls with Weblate 4.1. 100 | 101 | 1.3 102 | --- 103 | 104 | * Released on 6th May 2020. 105 | * Mark Python 3.8 as supported, dropped support for Python 3.5. 106 | * Added support for shell completion. 107 | * Improved error messages to give better understanding of actual problem. 108 | * Fixed repr() and str() behavior on returned objects. 109 | * Added support for filtering components and translations. 110 | * Better report errors when accessing API. 111 | 112 | 1.2 113 | --- 114 | 115 | * Released on 15th October 2019. 116 | * Fix stats invocation. 117 | * Improved timestamps handling. 118 | * Added support for replace upload. 119 | * Added support for project, component and translation removal. 120 | 121 | 1.1 122 | --- 123 | 124 | * Released on 1st February, 2019. 125 | * Fixed listing of language objects. 126 | 127 | 1.0 128 | --- 129 | 130 | * Released on 31st January, 2019. 131 | * Added support for more parameters on file upload. 132 | 133 | 0.10 134 | ---- 135 | 136 | * Released on 21th October, 2018. 137 | * Fixed POST operations in the API. 138 | * Added --debug parameter to diagnose HTTP problems. 139 | 140 | 0.9 141 | --- 142 | 143 | * Released on 17th October, 2018. 144 | * Switched to requests 145 | * Added support for cleanup command. 146 | * Added support for upload command. 147 | 148 | 0.8 149 | --- 150 | 151 | * Released on 3rd March, 2017. 152 | * Various code cleanups. 153 | * Tested with Python 3.6. 154 | 155 | 0.7 156 | --- 157 | 158 | * Released on 16th December, 2016. 159 | * Added reset operation. 160 | * Added statistics for project. 161 | * Added changes listing. 162 | * Added file downloads. 163 | 164 | 0.6 165 | --- 166 | 167 | * Released on 20th September, 2016. 168 | * Fixed error when invoked without command. 169 | * Tested on Windows and OS X (in addition to Linux). 170 | 171 | 0.5 172 | --- 173 | 174 | * Released on 11th July, 2016. 175 | * Added locking commands. 176 | 177 | 0.4 178 | --- 179 | 180 | * Released on 8th July, 2016. 181 | * Moved Git repository. 182 | 183 | 0.3 184 | --- 185 | 186 | * Released on 19th May, 2016. 187 | * First version for general usage. 188 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | FROM weblate/base:2025.23.0@sha256:2b0c1b5e1222a73124473f6e7e288daa12e486d5dcc64ffab5f0546c139d9b92 5 | 6 | LABEL name="wlc" 7 | LABEL maintainer="Michal Čihař " 8 | LABEL org.opencontainers.image.url="https://weblate.org/" 9 | LABEL org.opencontainers.image.documentation="https://docs.weblate.org/en/latest/wlc.html" 10 | LABEL org.opencontainers.image.source="https://github.com/WeblateOrg/wlc" 11 | LABEL org.opencontainers.image.author="Michal Čihař " 12 | LABEL org.opencontainers.image.vendor="Weblate" 13 | LABEL org.opencontainers.image.title="wlc" 14 | LABEL org.opencontainers.image.description="Command-line client for Weblate" 15 | LABEL org.opencontainers.image.licenses="GPL-3.0-or-later" 16 | 17 | COPY README.md LICENSE pyproject.toml /app/src/ 18 | COPY ./wlc/ /app/src/wlc 19 | 20 | # This hack is widely applied to avoid python printing issues in docker containers. 21 | # See: https://github.com/Docker-Hub-frolvlad/docker-alpine-python3/pull/13 22 | ENV PYTHONUNBUFFERED=1 23 | 24 | # hadolint ignore=SC1091 25 | RUN \ 26 | uv venv /app/venv && \ 27 | source /app/venv/bin/activate && \ 28 | uv pip install --no-cache-dir -e /app/src 29 | 30 | WORKDIR /home/weblate 31 | USER weblate 32 | 33 | ENTRYPOINT ["/app/venv/bin/wlc"] 34 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include SECURITY.md 3 | include LICENSE 4 | include wl 5 | recursive-include LICENSES * 6 | include CHANGES.rst 7 | include MANIFEST.in 8 | include completion/wlc 9 | include requirements.txt 10 | include requirements-test.txt 11 | recursive-include .reuse * 12 | include wlc/*.py 13 | recursive-include wlc/test_data * 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Weblate 2 | 3 | **Weblate is libre software web-based continuous localization system, 4 | used by over 2500 libre projects and companies in more than 165 countries.** 5 | 6 | # wlc 7 | 8 | wlc is a [Weblate](https://weblate.org/) command-line client using [Weblate's REST API](https://docs.weblate.org/en/latest/api.html). 9 | 10 | [![Website](https://img.shields.io/badge/website-weblate.org-blue.svg)](https://weblate.org/) 11 | [![Translation status](https://hosted.weblate.org/widgets/weblate/-/svg-badge.svg)](https://hosted.weblate.org/engage/weblate/?utm_source=widget) 12 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/552/badge)](https://bestpractices.coreinfrastructure.org/projects/552) 13 | [![PyPI package](https://img.shields.io/pypi/v/wlc.svg)](https://pypi.org/project/wlc/) 14 | [![Documenation](https://readthedocs.org/projects/weblate/badge/)](https://docs.weblate.org/en/latest/wlc.html) 15 | 16 | ## PIP Installation 17 | 18 | Install using pip: 19 | 20 | ```console 21 | pip3 install wlc 22 | ``` 23 | 24 | Sources are available at . 25 | 26 | ## Usage 27 | 28 | Please see [Weblate documentation](https://docs.weblate.org/en/latest/wlc.html) for more complete documentation. 29 | 30 | Command-line usage: 31 | 32 | ```console 33 | wlc list-projects 34 | wlc list-components 35 | wlc list-translations 36 | wlc list-languages 37 | wlc show 38 | wlc ls 39 | wlc commit 40 | wlc push 41 | wlc pull 42 | wlc repo 43 | wlc stats 44 | wlc lock 45 | wlc unlock 46 | wlc lock-status 47 | wlc download 48 | wlc upload 49 | ``` 50 | 51 | Configuration is stored in `~/.config/weblate`. The key/values (`retries`, 52 | `timeout`, `method_whitelist`, `backoff_factor`, `status_forcelist`) are closely 53 | coupled with the [urllib3 parameters](https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html) and allows the user to configure request 54 | parameters. 55 | 56 | ```ini 57 | [weblate] 58 | url = https://hosted.weblate.org/api/ 59 | retries = 3 60 | method_whitelist = PUT,POST,GET 61 | backoff_factor = 0.2 62 | status_forcelist = 429,500,502,503,504 63 | timeout = 30 64 | 65 | [keys] 66 | https://hosted.weblate.org/api/ = APIKEY 67 | ``` 68 | 69 | ## Docker image 70 | 71 | The image is published on [Docker Hub](https://hub.docker.com/r/weblate/wlc). 72 | 73 | Building locally: 74 | 75 | ```console 76 | docker build -t weblate/wlc . 77 | ``` 78 | 79 | Detailed documentation is available in [Weblate documentation](https://docs.weblate.org/en/latest/wlc.html#docker-wlc). 80 | 81 | ## Docker hub tags 82 | 83 | You can use following tags on Docker hub: 84 | 85 | | Tag name | Description | Use case | 86 | | -------- | --------------------------------------------------------------------------------- | ----------------------------------------------- | 87 | | `latest` | wlc stable release, matches latest tagged release | Rolling updates in a production environment | 88 | | `edge` | wlc development | Staging environment | 89 | | version | wlc stable release, see [weblate/wlc](https://hub.docker.com/r/weblate/wlc/tags/) | Well defined deploy in a production environment | 90 | 91 | Every image is tested by our CI before it gets published, so even the `bleeding` version should be quite safe to use. 92 | 93 | ## Contributing 94 | 95 | Contributions are welcome! See [documentation](https://docs.weblate.org/en/latest/contributing/modules.html) for more information. 96 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | # Weblate security 10 | 11 | The Weblate team takes security and related transparency very seriously. 12 | We welcome any peer review of our 100% open-source code to ensure nobody's Weblate 13 | is ever compromised or hacked. 14 | 15 | If you think you have identified a security issue with a Weblate project, **do 16 | not open a public issue**. 17 | 18 | To responsibly report a security issue, please navigate to the Security tab for 19 | the repository and click “Report a vulnerability”. 20 | 21 | Be sure to include as much detail as necessary in your report. As with 22 | reporting normal issues, a minimal reproducible example will help the 23 | maintainers address the issue faster. 24 | 25 | More information about practices for reporting and fixing security issues is 26 | described in [our documentation][1]. This ensures all vulnerabilities are 27 | solved securely, quickly, and transparently. 28 | 29 | [1]: https://docs.weblate.org/en/latest/security/issues.html 30 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: reach, changes, diff, files 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: '100' 8 | -------------------------------------------------------------------------------- /completion/wlc: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | _python_argcomplete() { 6 | local IFS=$'\013' 7 | local SUPPRESS_SPACE=0 8 | if compopt +o nospace 2> /dev/null; then 9 | SUPPRESS_SPACE=1 10 | fi 11 | COMPREPLY=( $(IFS="$IFS" \ 12 | COMP_LINE="$COMP_LINE" \ 13 | COMP_POINT="$COMP_POINT" \ 14 | COMP_TYPE="$COMP_TYPE" \ 15 | _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \ 16 | _ARGCOMPLETE=1 \ 17 | _ARGCOMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \ 18 | "$1" 8>&1 9>&2 1>/dev/null 2>/dev/null) ) 19 | if [[ $? != 0 ]]; then 20 | unset COMPREPLY 21 | elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "$COMPREPLY" =~ [=/:]$ ]]; then 22 | compopt -o nospace 23 | fi 24 | } 25 | complete -o nospace -o default -F _python_argcomplete "wlc" 26 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = ["setuptools>=78.0.2"] 4 | 5 | [project] 6 | classifiers = [ 7 | "Development Status :: 5 - Production/Stable", 8 | "Intended Audience :: Developers", 9 | "Operating System :: OS Independent", 10 | "Programming Language :: Python", 11 | "Programming Language :: Python :: 3", 12 | "Programming Language :: Python :: 3 :: Only", 13 | "Programming Language :: Python :: 3.9", 14 | "Programming Language :: Python :: 3.10", 15 | "Programming Language :: Python :: 3.11", 16 | "Programming Language :: Python :: 3.12", 17 | "Programming Language :: Python :: 3.13", 18 | "Topic :: Software Development :: Internationalization", 19 | "Topic :: Software Development :: Localization", 20 | "Topic :: Utilities" 21 | ] 22 | dependencies = [ 23 | "argcomplete", 24 | "python-dateutil", 25 | "pyxdg", 26 | "requests>=2.25.0", 27 | "urllib3>=1.26" 28 | ] 29 | description = "A command-line utility for Weblate, translation tool with tight version control integration" 30 | dynamic = ["version"] 31 | keywords = [ 32 | "i18n", 33 | "l10n", 34 | "gettext", 35 | "git", 36 | "mercurial", 37 | "translate" 38 | ] 39 | license = "GPL-3.0-or-later" 40 | license-files = ["LICENSE"] 41 | name = "wlc" 42 | requires-python = ">=3.9" 43 | 44 | [[project.authors]] 45 | email = "michal@weblate.org" 46 | name = "Michal Čihař" 47 | 48 | [project.optional-dependencies] 49 | lint = ["pre-commit==4.2.0"] 50 | test = [ 51 | "pytest", 52 | "pytest-cov", 53 | "responses>=0.10.1,<0.26.0", 54 | "build==1.2.2.post1", 55 | "twine==6.1.0" 56 | ] 57 | types = [ 58 | "mypy==1.16.0", 59 | "types-python-dateutil==2.9.0.20250516", 60 | "types-requests==2.32.0.20250602" 61 | ] 62 | 63 | [project.readme] 64 | content-type = "text/markdown" 65 | file = "README.md" 66 | 67 | [project.scripts] 68 | wlc = "wlc.main:main" 69 | 70 | [project.urls] 71 | Documentation = "https://docs.weblate.org/" 72 | Download = "https://github.com/WeblateOrg/wlc" 73 | Funding = "https://weblate.org/donate/" 74 | Homepage = "https://weblate.org/" 75 | "Issue Tracker" = "https://github.com/WeblateOrg/wlc/issues" 76 | "Source Code" = "https://github.com/WeblateOrg/wlc" 77 | Twitter = "https://twitter.com/WeblateOrg" 78 | 79 | [tool.check-manifest] 80 | ignore = [ 81 | ".dockerignore", 82 | "Dockerfile", 83 | ".editorconfig", 84 | "*.toml", 85 | "*.yaml", 86 | "*.yml" 87 | ] 88 | 89 | [tool.check-wheel-contents] 90 | ignore = [ 91 | "W002" # Triggered by API mocks 92 | ] 93 | 94 | [tool.coverage.paths] 95 | source = [ 96 | "." 97 | ] 98 | 99 | [tool.coverage.report] 100 | exclude_also = [ 101 | "@(abc\\.)?abstractmethod", 102 | # Don't complain about missing debug-only code: 103 | "def __repr__", 104 | # Don't complain if non-runnable code isn't run: 105 | "if 0:", 106 | "if __name__ == .__main__.:", 107 | "if self\\.debug", 108 | # Type checking 109 | "if TYPE_CHECKING:", 110 | # Have to re-enable the standard pragma 111 | "pragma: no cover", 112 | # Don't complain if tests don't hit defensive assertion code: 113 | "raise AssertionError", 114 | "raise NotImplementedError" 115 | ] 116 | 117 | [tool.isort] 118 | profile = "black" 119 | 120 | [tool.ruff] 121 | target-version = "py38" 122 | 123 | [tool.ruff.format] 124 | docstring-code-format = true 125 | 126 | [tool.ruff.lint] 127 | ignore = [ 128 | "D10", # TODO: we are missing many docstrings 129 | "D203", # CONFIG: incompatible with D211 130 | "D212", # CONFIG: incompatible with D213 131 | "D401", # TODO: many strings need rephrasing 132 | "T201", # WONTFIX: using print() (maybe add noqa) 133 | "TRY003", # WONTFIX: Avoid specifying long messages outside the exception class 134 | "PLR0913", # WONTFIX: Too many arguments to function call 135 | "PLR2004", # TODO: Magic value used in comparison, consider replacing 201 with a constant variable 136 | "COM", # CONFIG: No trailing commas 137 | "PT", # CONFIG: Not using pytest 138 | "PTH", # TODO: Not using pathlib 139 | "EM", # TODO: Exception strings 140 | "FBT", # TODO: Boolean in function definition 141 | "ANN", # TODO: type annotations 142 | "N818" # TODO: exception naming 143 | ] 144 | select = ["ALL"] 145 | 146 | [tool.ruff.lint.mccabe] 147 | max-complexity = 16 148 | 149 | [tool.setuptools] 150 | include-package-data = true 151 | platforms = ["any"] 152 | 153 | [tool.setuptools.dynamic.version] 154 | attr = "wlc.__version__" 155 | 156 | [tool.setuptools.package-dir] 157 | wlc = "wlc" 158 | 159 | [tool.setuptools.packages.find] 160 | include = ["wlc*"] 161 | namespaces = true 162 | -------------------------------------------------------------------------------- /wl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright © Michal Čihař 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | 7 | """Simple wrapper to execute wlc command-line.""" 8 | 9 | import sys 10 | 11 | import wlc.main 12 | 13 | sys.exit(wlc.main.main()) 14 | -------------------------------------------------------------------------------- /wlc/config.py: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | """Weblate API library, configuration.""" 6 | 7 | from __future__ import annotations 8 | 9 | import os.path 10 | from configparser import NoOptionError, RawConfigParser 11 | from typing import TYPE_CHECKING 12 | 13 | from xdg.BaseDirectory import load_config_paths # type: ignore[import-untyped] 14 | 15 | import wlc 16 | 17 | if TYPE_CHECKING: 18 | from collections.abc import Generator 19 | 20 | __all__ = ["NoOptionError", "WeblateConfig"] 21 | 22 | 23 | class WeblateConfig(RawConfigParser): 24 | """Configuration parser wrapper with defaults.""" 25 | 26 | def __init__(self, section="weblate"): 27 | """Construct WeblateConfig object.""" 28 | super().__init__(delimiters=("=",)) 29 | self.section = section 30 | self.set_defaults() 31 | 32 | def set_defaults(self): 33 | """Set default values.""" 34 | self.add_section("keys") 35 | self.add_section(self.section) 36 | self.set(self.section, "key", "") 37 | self.set(self.section, "url", wlc.API_URL) 38 | self.set(self.section, "retries", 0) 39 | self.set(self.section, "timeout", 300) 40 | self.set(self.section, "status_forcelist", None) 41 | self.set( 42 | self.section, "method_whitelist", "HEAD\nTRACE\nDELETE\nOPTIONS\nPUT\nGET" 43 | ) 44 | self.set(self.section, "backoff_factor", 0) 45 | 46 | @staticmethod 47 | def find_configs() -> Generator[str]: 48 | # Handle Windows specifically 49 | for envname in ("APPDATA", "LOCALAPPDATA"): 50 | if path := os.environ.get(envname): 51 | win_path = os.path.join(path, "weblate.ini") 52 | if os.path.exists(win_path): 53 | yield win_path 54 | 55 | # Generic XDG paths 56 | yield from load_config_paths("weblate") 57 | yield from load_config_paths("weblate.ini") 58 | 59 | def load(self, path=None): 60 | """Load configuration from XDG paths.""" 61 | if path is None: 62 | path = list(self.find_configs()) 63 | self.read(path) 64 | 65 | # Try reading from current dir 66 | cwd = os.path.abspath(".") 67 | prev = None 68 | while cwd != prev: 69 | for name in (".weblate", ".weblate.ini", "weblate.ini"): 70 | conf_name = os.path.join(cwd, name) 71 | if os.path.exists(conf_name): 72 | self.read(conf_name) 73 | break 74 | prev = cwd 75 | cwd = os.path.dirname(cwd) 76 | 77 | def get_url_key(self): 78 | """Get API URL and key.""" 79 | url = self.get(self.section, "url") 80 | key = self.get(self.section, "key") 81 | if not key: 82 | try: 83 | key = self.get("keys", url) 84 | except NoOptionError: 85 | key = "" 86 | return url, key 87 | 88 | def get_request_options(self): 89 | retries = int(self.get(self.section, "retries")) 90 | timeout = int(self.get(self.section, "timeout")) 91 | status_forcelist = self.get(self.section, "status_forcelist") 92 | if status_forcelist is not None: 93 | status_forcelist = [int(option) for option in status_forcelist.split(",")] 94 | method_whitelist = self.get(self.section, "method_whitelist").split(",") 95 | backoff_factor = float(self.get(self.section, "backoff_factor")) 96 | return retries, status_forcelist, method_whitelist, backoff_factor, timeout 97 | -------------------------------------------------------------------------------- /wlc/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeblateOrg/wlc/d67137f8e8774f4088b7ad97cd323cdbb9698eb6/wlc/py.typed -------------------------------------------------------------------------------- /wlc/test_base.py: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | """Test helpers.""" 6 | 7 | from __future__ import annotations 8 | 9 | import os 10 | from email import message_from_string 11 | from hashlib import blake2b 12 | from unittest import TestCase 13 | 14 | import responses 15 | from requests.exceptions import RequestException 16 | 17 | DATA_TEST_BASE = os.path.join(os.path.dirname(__file__), "test_data", "api") 18 | 19 | 20 | class ResponseHandler: 21 | """responses response handler.""" 22 | 23 | def __init__(self, body, filename, auth=False): 24 | """Construct response handler object.""" 25 | self.body = body 26 | self.filename = filename 27 | self.auth = auth 28 | 29 | def __call__(self, request): 30 | """Call interface for responses.""" 31 | if self.auth and request.headers.get("Authorization") != "Token KEY": 32 | return 403, {}, "" 33 | 34 | content = self.get_content(request) 35 | 36 | return 200, {}, content 37 | 38 | def get_content(self, request): 39 | """Return content for given request.""" 40 | filename = self.get_filename(request) 41 | 42 | if filename is not None: 43 | try: 44 | with open(filename, "rb") as handle: 45 | return handle.read() 46 | except FileNotFoundError as error: 47 | error.strerror = "Failed to find response mock" 48 | raise error # noqa: TRY201 49 | 50 | return self.body 51 | 52 | @staticmethod 53 | def format_body(body) -> str: 54 | if not body: 55 | return "" 56 | body = body.decode() 57 | result = ( 58 | body.replace(": ", "=") 59 | .replace("{", "") 60 | .replace("}", "") 61 | .replace('"', "") 62 | .replace(":", "-") 63 | .replace("/", "-") 64 | .replace(", ", "--") 65 | .replace(" ", "-") 66 | .replace("[", "-") 67 | .replace("]", "-") 68 | .replace("*", "-") 69 | ) 70 | if len(result) < 100: 71 | return result 72 | digest = blake2b(digest_size=4) 73 | digest.update(result.encode()) 74 | return digest.hexdigest() 75 | 76 | def get_filename(self, request): 77 | """Return filename for given request.""" 78 | filename_parts = [self.filename, request.method] 79 | if request.method != "GET": 80 | content_type = request.headers.get("content-type", None) 81 | 82 | if content_type is not None and content_type.startswith( 83 | "multipart/form-data" 84 | ): 85 | filename_parts.append( 86 | self.format_multipart_body(request.body, content_type) 87 | ) 88 | else: 89 | filename_parts.append(self.format_body(request.body)) 90 | return "--".join(filename_parts) 91 | if "?" in request.path_url: 92 | filename_parts.append(request.path_url.split("?", 1)[-1]) 93 | return "--".join(filename_parts) 94 | return None 95 | 96 | @staticmethod 97 | def format_multipart_body(body, content_type): 98 | message = message_from_string( 99 | f"Content-Type: {content_type}\n\n{body.decode()}" 100 | ) 101 | payload = [] 102 | for part in message.get_payload(): 103 | name = part.get_param("name", header="content-disposition") 104 | value = part.get_payload() 105 | if isinstance(value, bytes): 106 | value = value.decode() 107 | payload.append((name, value)) 108 | digest = blake2b(digest_size=4) 109 | digest.update(repr(sorted(payload)).encode()) 110 | return digest.hexdigest() 111 | 112 | 113 | def register_uri(path, domain="http://127.0.0.1:8000/api", auth=False): 114 | """Simplified URL registration.""" 115 | filename = os.path.join(DATA_TEST_BASE, path.replace("/", "-")) 116 | url = f"{domain}/{path}/" 117 | with open(filename, "rb") as handle: 118 | responses.add_callback( 119 | responses.GET, 120 | url, 121 | callback=ResponseHandler(handle.read(), filename, auth), 122 | content_type="application/json", 123 | ) 124 | responses.add_callback( 125 | responses.POST, 126 | url, 127 | callback=ResponseHandler(handle.read(), filename, auth), 128 | content_type="application/json", 129 | ) 130 | responses.add_callback( 131 | responses.DELETE, 132 | url, 133 | callback=ResponseHandler(handle.read(), filename, auth), 134 | content_type="application/json", 135 | ) 136 | responses.add_callback( 137 | responses.PATCH, 138 | url, 139 | callback=ResponseHandler(handle.read(), filename, auth), 140 | content_type="application/json", 141 | ) 142 | responses.add_callback( 143 | responses.PUT, 144 | url, 145 | callback=ResponseHandler(handle.read(), filename, auth), 146 | content_type="application/json", 147 | ) 148 | 149 | 150 | def raise_error(request): 151 | """Raise IOError.""" 152 | if "/io" in request.path_url: 153 | raise RequestException("Some error") 154 | raise FileNotFoundError("Bug") 155 | 156 | 157 | def register_error( 158 | path, code, domain="http://127.0.0.1:8000/api", method=responses.GET, **kwargs 159 | ): 160 | """Simplified URL error registration.""" 161 | url = f"{domain}/{path}/" 162 | if "callback" in kwargs: 163 | responses.add_callback(method, url, **kwargs) 164 | else: 165 | responses.add(method, url, status=code, **kwargs) 166 | 167 | 168 | def register_uris(): 169 | """Register URIs for responses.""" 170 | paths = ( 171 | "categories", 172 | "categories/1", 173 | "changes", 174 | "components", 175 | "components/hello/android", 176 | "components/hello/android/file", 177 | "components/hello/olderweblate", 178 | "components/hello/weblate", 179 | "components/hello/weblate/file", 180 | "components/hello/weblate/changes", 181 | "components/hello/weblate/lock", 182 | "components/hello/weblate/repository", 183 | "components/hello/weblate/statistics", 184 | "components/hello/weblate/translations", 185 | "languages", 186 | "projects", 187 | "projects/empty", 188 | "projects/empty/components", 189 | "projects/hello", 190 | "projects/hello/categories", 191 | "projects/hello/changes", 192 | "projects/hello/components", 193 | "projects/hello/languages", 194 | "projects/hello/repository", 195 | "projects/hello/statistics", 196 | "projects/invalid", 197 | "translations", 198 | "translations/hello/weblate/cs", 199 | "translations/hello/weblate/cs/changes", 200 | "translations/hello/weblate/cs/file", 201 | "translations/hello/weblate/cs/repository", 202 | "translations/hello/weblate/cs/statistics", 203 | "translations/hello/weblate/cs/units", 204 | "translations/hello/android/en/units", 205 | "units", 206 | "units/123", 207 | ) 208 | for path in paths: 209 | register_uri(path) 210 | 211 | register_uri("projects/acl", auth=True) 212 | 213 | register_uri("projects", domain="https://example.net") 214 | register_error("projects/nonexisting", 404) 215 | register_error("projects/denied", 403) 216 | register_error( 217 | "projects/denied_json/components", 218 | 403, 219 | method=responses.POST, 220 | json={"detail": "Can not create components"}, 221 | ) 222 | register_error( 223 | "projects/throttled", 224 | 429, 225 | headers={"X-RateLimit-Limit": "100", "Retry-After": "81818"}, 226 | ) 227 | register_error("projects/error", 500) 228 | register_error("projects/io", 500, callback=raise_error) 229 | register_error("projects/bug", 500, callback=raise_error) 230 | register_error("projects", 401, domain="http://denied.example.com") 231 | 232 | 233 | class APITest(TestCase): 234 | """Base class for API testing.""" 235 | 236 | def setUp(self): 237 | """Enable responses and register urls.""" 238 | responses.mock.start() 239 | register_uris() 240 | 241 | def tearDown(self): 242 | """Disable responses.""" 243 | responses.mock.stop() 244 | responses.mock.reset() 245 | -------------------------------------------------------------------------------- /wlc/test_data/.weblate: -------------------------------------------------------------------------------- 1 | [weblate] 2 | url = http://127.0.0.1:8000/api/ 3 | translation = hello/weblate 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/categories: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "category": null, 8 | "name": "3.12", 9 | "project": "http://127.0.0.1:8000/api/projects/hello/", 10 | "slug": "latest", 11 | "url": "http://127.0.0.1:8000/api/categories/1/" 12 | }, 13 | { 14 | "category": null, 15 | "name": "3.11", 16 | "project": "http://127.0.0.1:8000/api/projects/hello/", 17 | "slug": "3-11", 18 | "url": "http://127.0.0.1:8000/api/categories/2/" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /wlc/test_data/api/categories-1: -------------------------------------------------------------------------------- 1 | { 2 | "category": null, 3 | "name": "Hi", 4 | "project": "http://127.0.0.1:8000/api/projects/hello/", 5 | "slug": "hi", 6 | "url": "http://127.0.0.1:8000/api/categories/1/" 7 | } 8 | -------------------------------------------------------------------------------- /wlc/test_data/api/changes: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "unit": null, 8 | "component": "http://127.0.0.1:8000/api/components/hello/android/", 9 | "url": "http://127.0.0.1:8000/api/changes/1/", 10 | "translation": null, 11 | "dictionary": null, 12 | "user": null, 13 | "author": null, 14 | "timestamp": "2016-11-18T10:47:01.355911Z", 15 | "action": 20, 16 | "target": "", 17 | "id": 353, 18 | "action_name": "Sloučen repozitář" 19 | }, 20 | { 21 | "unit": "http://127.0.0.1:8000/api/units/227/", 22 | "component": "http://127.0.0.1:8000/api/components/hello/weblate/", 23 | "url": "http://127.0.0.1:8000/api/changes/2/", 24 | "translation": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/", 25 | "dictionary": null, 26 | "user": 2, 27 | "author": 2, 28 | "timestamp": "2016-10-24T07:21:54.121348Z", 29 | "action": 26, 30 | "target": "", 31 | "id": 350, 32 | "action_name": "Odstraněn návrh" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /wlc/test_data/api/components: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "agreement": "", 8 | "branch": "main", 9 | "category": "http://127.0.0.1:8000/api/categories/1/", 10 | "file_format": "aresource", 11 | "filemask": "android/values-*/strings.xml", 12 | "git_export": "", 13 | "license": "", 14 | "license_url": "", 15 | "lock_url": "http://127.0.0.1:8000/api/components/hello/android/lock/", 16 | "name": "Android", 17 | "new_base": "", 18 | "priority": 100, 19 | "project": { 20 | "components_list_url": "http://127.0.0.1:8000/api/projects/hello/components/", 21 | "name": "Hello", 22 | "repository_url": "http://127.0.0.1:8000/api/projects/hello/repository/", 23 | "slug": "hello", 24 | "url": "http://127.0.0.1:8000/api/projects/hello/", 25 | "web": "http://weblate.org/", 26 | "web_url": "http://127.0.0.1:8000/projects/hello/" 27 | }, 28 | "repo": "https://github.com/WeblateOrg/test.git", 29 | "repository_url": "http://127.0.0.1:8000/api/components/hello/android/repository/", 30 | "slug": "android", 31 | "source_language": { 32 | "code": "en", 33 | "name": "English", 34 | "plural": { 35 | "id": 82, 36 | "source": 0, 37 | "number": 2, 38 | "formula": "n != 1", 39 | "type": 1 40 | }, 41 | "aliases": [ 42 | "english", 43 | "en_en", 44 | "base", 45 | "source", 46 | "eng" 47 | ], 48 | "direction": "ltr", 49 | "web_url": "http://127.0.0.1:8000/api/languages/en/", 50 | "url": "http://127.0.0.1:8000/api/languages/en/", 51 | "statistics_url": "http://127.0.0.1:8000/api/languages/en/statistics/" 52 | }, 53 | "statistics_url": "http://127.0.0.1:8000/api/components/hello/android/statistics/", 54 | "template": "android/values/strings.xml", 55 | "translations_url": "http://127.0.0.1:8000/api/components/hello/android/translations/", 56 | "url": "http://127.0.0.1:8000/api/components/hello/android/", 57 | "vcs": "git", 58 | "web_url": "http://127.0.0.1:8000/projects/hello/android/" 59 | }, 60 | { 61 | "agreement": "", 62 | "branch": "main", 63 | "category": null, 64 | "file_format": "po", 65 | "filemask": "po/*.po", 66 | "git_export": "", 67 | "license": "", 68 | "license_url": "", 69 | "lock_url": "http://127.0.0.1:8000/api/components/hello/weblate/lock/", 70 | "name": "Weblate", 71 | "new_base": "", 72 | "priority": 100, 73 | "project": { 74 | "components_list_url": "http://127.0.0.1:8000/api/projects/hello/components/", 75 | "name": "Hello", 76 | "repository_url": "http://127.0.0.1:8000/api/projects/hello/repository/", 77 | "slug": "hello", 78 | "source_language": { 79 | "code": "en", 80 | "direction": "ltr", 81 | "name": "English", 82 | "nplurals": 2, 83 | "pluralequation": "n != 1", 84 | "url": "http://127.0.0.1:8000/api/languages/en/", 85 | "web_url": "http://127.0.0.1:8000/languages/en/" 86 | }, 87 | "url": "http://127.0.0.1:8000/api/projects/hello/", 88 | "web": "http://weblate.org/", 89 | "web_url": "http://127.0.0.1:8000/projects/hello/" 90 | }, 91 | "repo": "file:///home/WeblateOrg/work/weblate-hello", 92 | "repository_url": "http://127.0.0.1:8000/api/components/hello/weblate/repository/", 93 | "slug": "weblate", 94 | "source_language": { 95 | "code": "en", 96 | "name": "English", 97 | "plural": { 98 | "id": 82, 99 | "source": 0, 100 | "number": 2, 101 | "formula": "n != 1", 102 | "type": 1 103 | }, 104 | "aliases": [ 105 | "english", 106 | "en_en", 107 | "base", 108 | "source", 109 | "eng" 110 | ], 111 | "direction": "ltr", 112 | "web_url": "http://127.0.0.1:8000/api/languages/en/", 113 | "url": "http://127.0.0.1:8000/api/languages/en/", 114 | "statistics_url": "http://127.0.0.1:8000/api/languages/en/statistics/" 115 | }, 116 | "statistics_url": "http://127.0.0.1:8000/api/components/hello/weblate/statistics/", 117 | "template": "", 118 | "translations_url": "http://127.0.0.1:8000/api/components/hello/weblate/translations/", 119 | "url": "http://127.0.0.1:8000/api/components/hello/weblate/", 120 | "vcs": "git", 121 | "web_url": "http://127.0.0.1:8000/projects/hello/weblate/" 122 | } 123 | ] 124 | } 125 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-android: -------------------------------------------------------------------------------- 1 | { 2 | "branch": "main", 3 | "file_format": "aresource", 4 | "filemask": "android/values-*/strings.xml", 5 | "category": "http://127.0.0.1:8000/api/categories/1/", 6 | "git_export": "", 7 | "license": "", 8 | "license_url": "", 9 | "is_glossary": true, 10 | "lock_url": "http://127.0.0.1:8000/api/components/hello/android/lock/", 11 | "name": "Android", 12 | "new_base": "", 13 | "project": { 14 | "components_list_url": "http://127.0.0.1:8000/api/projects/hello/components/", 15 | "name": "Hello", 16 | "repository_url": "http://127.0.0.1:8000/api/projects/hello/repository/", 17 | "slug": "hello", 18 | "url": "http://127.0.0.1:8000/api/projects/hello/", 19 | "web": "http://weblate.org/", 20 | "web_url": "http://127.0.0.1:8000/projects/hello/" 21 | }, 22 | "repo": "https://github.com/WeblateOrg/test.git", 23 | "repository_url": "http://127.0.0.1:8000/api/components/hello/android/repository/", 24 | "slug": "android", 25 | "statistics_url": "http://127.0.0.1:8000/api/components/hello/android/statistics/", 26 | "source_language": { 27 | "code": "en", 28 | "name": "English", 29 | "plural": { 30 | "id": 82, 31 | "source": 0, 32 | "number": 2, 33 | "formula": "n != 1", 34 | "type": 1 35 | }, 36 | "aliases": [ 37 | "english", 38 | "en_en", 39 | "base", 40 | "source", 41 | "eng" 42 | ], 43 | "direction": "ltr", 44 | "web_url": "http://127.0.0.1:8000/api/languages/en/", 45 | "url": "http://127.0.0.1:8000/api/languages/en/", 46 | "statistics_url": "http://127.0.0.1:8000/api/languages/en/statistics/" 47 | }, 48 | "template": "android/values/strings.xml", 49 | "translations_url": "http://127.0.0.1:8000/api/components/hello/android/translations/", 50 | "url": "http://127.0.0.1:8000/api/components/hello/android/", 51 | "vcs": "git", 52 | "web_url": "http://127.0.0.1:8000/projects/hello/android/" 53 | } 54 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-android-file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeblateOrg/wlc/d67137f8e8774f4088b7ad97cd323cdbb9698eb6/wlc/test_data/api/components-hello-android-file -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-android-file--GET--format=zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeblateOrg/wlc/d67137f8e8774f4088b7ad97cd323cdbb9698eb6/wlc/test_data/api/components-hello-android-file--GET--format=zip -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-olderweblate: -------------------------------------------------------------------------------- 1 | { 2 | "branch": "main", 3 | "file_format": "po", 4 | "filemask": "po/*.po", 5 | "git_export": "", 6 | "license": "", 7 | "license_url": "", 8 | "lock_url": "http://127.0.0.1:8000/api/components/hello/weblate/lock/", 9 | "name": "Weblate", 10 | "new_base": "", 11 | "priority": 100, 12 | "project": { 13 | "components_list_url": "http://127.0.0.1:8000/api/projects/hello/components/", 14 | "name": "Hello", 15 | "repository_url": "http://127.0.0.1:8000/api/projects/hello/repository/", 16 | "slug": "hello", 17 | "url": "http://127.0.0.1:8000/api/projects/hello/", 18 | "web": "http://weblate.org/", 19 | "web_url": "http://127.0.0.1:8000/projects/hello/" 20 | }, 21 | "repo": "file:///home/WeblateOrg/work/weblate-hello", 22 | "repository_url": "http://127.0.0.1:8000/api/components/hello/weblate/repository/", 23 | "slug": "weblate", 24 | "statistics_url": "http://127.0.0.1:8000/api/components/hello/weblate/statistics/", 25 | "template": "", 26 | "translations_url": "http://127.0.0.1:8000/api/components/hello/weblate/translations/", 27 | "changes_list_url": "http://127.0.0.1:8000/api/components/hello/weblate/changes/", 28 | "url": "http://127.0.0.1:8000/api/components/hello/weblate/", 29 | "vcs": "git", 30 | "web_url": "http://127.0.0.1:8000/projects/hello/weblate/" 31 | } 32 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate: -------------------------------------------------------------------------------- 1 | { 2 | "agreement": "", 3 | "branch": "main", 4 | "file_format": "po", 5 | "filemask": "po/*.po", 6 | "git_export": "", 7 | "is_glossary": false, 8 | "license": "", 9 | "license_url": "", 10 | "lock_url": "http://127.0.0.1:8000/api/components/hello/weblate/lock/", 11 | "name": "Weblate", 12 | "new_base": "", 13 | "priority": 100, 14 | "project": { 15 | "components_list_url": "http://127.0.0.1:8000/api/projects/hello/components/", 16 | "name": "Hello", 17 | "repository_url": "http://127.0.0.1:8000/api/projects/hello/repository/", 18 | "slug": "hello", 19 | "url": "http://127.0.0.1:8000/api/projects/hello/", 20 | "web": "http://weblate.org/", 21 | "web_url": "http://127.0.0.1:8000/projects/hello/" 22 | }, 23 | "repo": "file:///home/WeblateOrg/work/weblate-hello", 24 | "repository_url": "http://127.0.0.1:8000/api/components/hello/weblate/repository/", 25 | "slug": "weblate", 26 | "statistics_url": "http://127.0.0.1:8000/api/components/hello/weblate/statistics/", 27 | "source_language": { 28 | "code": "en", 29 | "name": "English", 30 | "plural": { 31 | "id": 82, 32 | "source": 0, 33 | "number": 2, 34 | "formula": "n != 1", 35 | "type": 1 36 | }, 37 | "aliases": [ 38 | "english", 39 | "en_en", 40 | "base", 41 | "source", 42 | "eng" 43 | ], 44 | "direction": "ltr", 45 | "web_url": "http://127.0.0.1:8000/api/languages/en/", 46 | "url": "http://127.0.0.1:8000/api/languages/en/", 47 | "statistics_url": "http://127.0.0.1:8000/api/languages/en/statistics/" 48 | }, 49 | "template": "", 50 | "translations_url": "http://127.0.0.1:8000/api/components/hello/weblate/translations/", 51 | "changes_list_url": "http://127.0.0.1:8000/api/components/hello/weblate/changes/", 52 | "url": "http://127.0.0.1:8000/api/components/hello/weblate/", 53 | "vcs": "git", 54 | "web_url": "http://127.0.0.1:8000/projects/hello/weblate/" 55 | } 56 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate--DELETE--: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeblateOrg/wlc/d67137f8e8774f4088b7ad97cd323cdbb9698eb6/wlc/test_data/api/components-hello-weblate--DELETE-- -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate--PATCH--priority=80: -------------------------------------------------------------------------------- 1 | --patched-- 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-changes: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "unit": null, 8 | "component": "http://127.0.0.1:8000/api/components/hello/android/", 9 | "url": "http://127.0.0.1:8000/api/changes/1/", 10 | "translation": null, 11 | "dictionary": null, 12 | "user": null, 13 | "author": null, 14 | "timestamp": "2016-11-18T10:47:01.355911Z", 15 | "action": 20, 16 | "target": "", 17 | "id": 353, 18 | "action_name": "Sloučen repozitář" 19 | }, 20 | { 21 | "unit": "http://127.0.0.1:8000/api/units/227/", 22 | "component": "http://127.0.0.1:8000/api/components/hello/weblate/", 23 | "url": "http://127.0.0.1:8000/api/changes/2/", 24 | "translation": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/", 25 | "dictionary": null, 26 | "user": 2, 27 | "author": 2, 28 | "timestamp": "2016-10-24T07:21:54.121348Z", 29 | "action": 26, 30 | "target": "", 31 | "id": 350, 32 | "action_name": "Odstraněn návrh" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeblateOrg/wlc/d67137f8e8774f4088b7ad97cd323cdbb9698eb6/wlc/test_data/api/components-hello-weblate-file -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-file--GET--format=zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeblateOrg/wlc/d67137f8e8774f4088b7ad97cd323cdbb9698eb6/wlc/test_data/api/components-hello-weblate-file--GET--format=zip -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-lock: -------------------------------------------------------------------------------- 1 | { 2 | "locked": false 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-lock--POST--lock=0: -------------------------------------------------------------------------------- 1 | { 2 | "locked": false 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-lock--POST--lock=1: -------------------------------------------------------------------------------- 1 | { 2 | "locked": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-repository: -------------------------------------------------------------------------------- 1 | { 2 | "merge_failure": null, 3 | "needs_commit": false, 4 | "needs_merge": false, 5 | "needs_push": true, 6 | "remote_commit": { 7 | "author": "Michal \u010ciha\u0159 ", 8 | "author_email": "michal@cihar.com", 9 | "author_name": "Michal \u010ciha\u0159", 10 | "authordate": "2014-11-19T12:50:24+01:00", 11 | "commit": "Michal \u010ciha\u0159 ", 12 | "commit_email": "michal@cihar.com", 13 | "commit_name": "Michal \u010ciha\u0159", 14 | "commitdate": "2014-11-19T12:50:24+01:00", 15 | "message": "Add Arabic\n\nSigned-off-by: Michal \u010ciha\u0159 ", 16 | "revision": "8ba2d7e113dd58a3695d1b196f24e37a7b5bcb80", 17 | "shortrevision": "8ba2d7e", 18 | "summary": "Add Arabic" 19 | }, 20 | "status": "On branch main\nYour branch is ahead of 'origin/main' by 1 commit.\n (use \"git push\" to publish your local commits)\nnothing to commit, working directory clean\n", 21 | "url": "http://127.0.0.1:8000/api/components/hello/weblate/repository/" 22 | } 23 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-repository--POST--operation=cleanup: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-repository--POST--operation=commit: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-repository--POST--operation=pull: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-repository--POST--operation=push: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "Push is disabled for Hello/Weblate.", 3 | "result": false 4 | } 5 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-repository--POST--operation=reset: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-statistics: -------------------------------------------------------------------------------- 1 | { 2 | "count": 33, 3 | "next": "http://127.0.0.1:8000/api/components/hello/weblate/statistics/?page=2", 4 | "previous": null, 5 | "results": [ 6 | { 7 | "code": "ar", 8 | "failing": 0, 9 | "failing_percent": 0.0, 10 | "fuzzy": 0, 11 | "fuzzy_percent": 0.0, 12 | "last_author": null, 13 | "last_change": null, 14 | "recent_changes": 0, 15 | "name": "Arabic", 16 | "total": 4, 17 | "total_words": 15, 18 | "translated": 0, 19 | "translated_percent": 0.0, 20 | "translated_words": 0, 21 | "url": "http://127.0.0.1:8000/engage/hello/ar/", 22 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/ar/" 23 | }, 24 | { 25 | "code": "hy", 26 | "failing": 0, 27 | "failing_percent": 0.0, 28 | "fuzzy": 0, 29 | "fuzzy_percent": 0.0, 30 | "last_author": null, 31 | "last_change": null, 32 | "recent_changes": 0, 33 | "name": "Armenian", 34 | "total": 4, 35 | "total_words": 15, 36 | "translated": 0, 37 | "translated_percent": 0.0, 38 | "translated_words": 0, 39 | "url": "http://127.0.0.1:8000/engage/hello/hy/", 40 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/hy/" 41 | }, 42 | { 43 | "code": "ca", 44 | "failing": 0, 45 | "failing_percent": 0.0, 46 | "fuzzy": 0, 47 | "fuzzy_percent": 0.0, 48 | "last_author": null, 49 | "last_change": null, 50 | "recent_changes": 0, 51 | "name": "Catalan", 52 | "total": 4, 53 | "total_words": 15, 54 | "translated": 0, 55 | "translated_percent": 0.0, 56 | "translated_words": 0, 57 | "url": "http://127.0.0.1:8000/engage/hello/ca/", 58 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/ca/" 59 | }, 60 | { 61 | "code": "zh_CN", 62 | "failing": 0, 63 | "failing_percent": 0.0, 64 | "fuzzy": 0, 65 | "fuzzy_percent": 0.0, 66 | "last_author": null, 67 | "last_change": null, 68 | "recent_changes": 0, 69 | "name": "Chinese (China)", 70 | "total": 4, 71 | "total_words": 15, 72 | "translated": 0, 73 | "translated_percent": 0.0, 74 | "translated_words": 0, 75 | "url": "http://127.0.0.1:8000/engage/hello/zh_CN/", 76 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/zh_CN/" 77 | }, 78 | { 79 | "code": "zh_TW", 80 | "failing": 0, 81 | "failing_percent": 0.0, 82 | "fuzzy": 0, 83 | "fuzzy_percent": 0.0, 84 | "last_author": null, 85 | "last_change": null, 86 | "recent_changes": 0, 87 | "name": "Chinese (Taiwan)", 88 | "total": 4, 89 | "total_words": 15, 90 | "translated": 0, 91 | "translated_percent": 0.0, 92 | "translated_words": 0, 93 | "url": "http://127.0.0.1:8000/engage/hello/zh_TW/", 94 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/zh_TW/" 95 | }, 96 | { 97 | "code": "cs", 98 | "failing": 3, 99 | "failing_percent": 75.0, 100 | "fuzzy": 0, 101 | "fuzzy_percent": 0.0, 102 | "last_author": "Weblate Admin", 103 | "last_change": "2016-03-07T10:20:05.499", 104 | "recent_changes": 123, 105 | "name": "Czech", 106 | "total": 4, 107 | "total_words": 15, 108 | "translated": 4, 109 | "translated_percent": 100.0, 110 | "translated_words": 15, 111 | "url": "http://127.0.0.1:8000/engage/hello/cs/", 112 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/cs/" 113 | }, 114 | { 115 | "code": "da", 116 | "failing": 0, 117 | "failing_percent": 0.0, 118 | "fuzzy": 0, 119 | "fuzzy_percent": 0.0, 120 | "last_author": null, 121 | "last_change": null, 122 | "recent_changes": 0, 123 | "name": "Danish", 124 | "total": 4, 125 | "total_words": 15, 126 | "translated": 0, 127 | "translated_percent": 0.0, 128 | "translated_words": 0, 129 | "url": "http://127.0.0.1:8000/engage/hello/da/", 130 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/da/" 131 | }, 132 | { 133 | "code": "nl", 134 | "failing": 0, 135 | "failing_percent": 0.0, 136 | "fuzzy": 0, 137 | "fuzzy_percent": 0.0, 138 | "last_author": null, 139 | "last_change": null, 140 | "recent_changes": 0, 141 | "name": "Dutch", 142 | "total": 4, 143 | "total_words": 15, 144 | "translated": 0, 145 | "translated_percent": 0.0, 146 | "translated_words": 0, 147 | "url": "http://127.0.0.1:8000/engage/hello/nl/", 148 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/nl/" 149 | }, 150 | { 151 | "code": "en_GB", 152 | "failing": 0, 153 | "failing_percent": 0.0, 154 | "fuzzy": 0, 155 | "fuzzy_percent": 0.0, 156 | "last_author": null, 157 | "last_change": null, 158 | "recent_changes": 0, 159 | "name": "English (United Kingdom)", 160 | "total": 4, 161 | "total_words": 15, 162 | "translated": 1, 163 | "translated_percent": 25.0, 164 | "translated_words": 2, 165 | "url": "http://127.0.0.1:8000/engage/hello/en_GB/", 166 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/en_GB/" 167 | }, 168 | { 169 | "code": "fi", 170 | "failing": 0, 171 | "failing_percent": 0.0, 172 | "fuzzy": 0, 173 | "fuzzy_percent": 0.0, 174 | "last_author": null, 175 | "last_change": null, 176 | "recent_changes": 0, 177 | "name": "Finnish", 178 | "total": 4, 179 | "total_words": 15, 180 | "translated": 0, 181 | "translated_percent": 0.0, 182 | "translated_words": 0, 183 | "url": "http://127.0.0.1:8000/engage/hello/fi/", 184 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/fi/" 185 | }, 186 | { 187 | "code": "fr", 188 | "failing": 0, 189 | "failing_percent": 0.0, 190 | "fuzzy": 0, 191 | "fuzzy_percent": 0.0, 192 | "last_author": null, 193 | "last_change": null, 194 | "recent_changes": 0, 195 | "name": "French", 196 | "total": 4, 197 | "total_words": 15, 198 | "translated": 0, 199 | "translated_percent": 0.0, 200 | "translated_words": 0, 201 | "url": "http://127.0.0.1:8000/engage/hello/fr/", 202 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/fr/" 203 | }, 204 | { 205 | "code": "gl", 206 | "failing": 0, 207 | "failing_percent": 0.0, 208 | "fuzzy": 0, 209 | "fuzzy_percent": 0.0, 210 | "last_author": null, 211 | "last_change": null, 212 | "recent_changes": 0, 213 | "name": "Galician", 214 | "total": 4, 215 | "total_words": 15, 216 | "translated": 0, 217 | "translated_percent": 0.0, 218 | "translated_words": 0, 219 | "url": "http://127.0.0.1:8000/engage/hello/gl/", 220 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/gl/" 221 | }, 222 | { 223 | "code": "ka", 224 | "failing": 0, 225 | "failing_percent": 0.0, 226 | "fuzzy": 0, 227 | "fuzzy_percent": 0.0, 228 | "last_author": null, 229 | "last_change": null, 230 | "recent_changes": 0, 231 | "name": "Georgian", 232 | "total": 4, 233 | "total_words": 15, 234 | "translated": 0, 235 | "translated_percent": 0.0, 236 | "translated_words": 0, 237 | "url": "http://127.0.0.1:8000/engage/hello/ka/", 238 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/ka/" 239 | }, 240 | { 241 | "code": "de", 242 | "failing": 0, 243 | "failing_percent": 0.0, 244 | "fuzzy": 0, 245 | "fuzzy_percent": 0.0, 246 | "last_author": null, 247 | "last_change": null, 248 | "recent_changes": 0, 249 | "name": "German", 250 | "total": 4, 251 | "total_words": 15, 252 | "translated": 0, 253 | "translated_percent": 0.0, 254 | "translated_words": 0, 255 | "url": "http://127.0.0.1:8000/engage/hello/de/", 256 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/de/" 257 | }, 258 | { 259 | "code": "el", 260 | "failing": 0, 261 | "failing_percent": 0.0, 262 | "fuzzy": 0, 263 | "fuzzy_percent": 0.0, 264 | "last_author": null, 265 | "last_change": null, 266 | "recent_changes": 0, 267 | "name": "Greek", 268 | "total": 4, 269 | "total_words": 15, 270 | "translated": 0, 271 | "translated_percent": 0.0, 272 | "translated_words": 0, 273 | "url": "http://127.0.0.1:8000/engage/hello/el/", 274 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/el/" 275 | }, 276 | { 277 | "code": "he", 278 | "failing": 0, 279 | "failing_percent": 0.0, 280 | "fuzzy": 0, 281 | "fuzzy_percent": 0.0, 282 | "last_author": null, 283 | "last_change": null, 284 | "recent_changes": 0, 285 | "name": "Hebrew", 286 | "total": 4, 287 | "total_words": 15, 288 | "translated": 0, 289 | "translated_percent": 0.0, 290 | "translated_words": 0, 291 | "url": "http://127.0.0.1:8000/engage/hello/he/", 292 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/he/" 293 | }, 294 | { 295 | "code": "hu", 296 | "failing": 0, 297 | "failing_percent": 0.0, 298 | "fuzzy": 0, 299 | "fuzzy_percent": 0.0, 300 | "last_author": null, 301 | "last_change": null, 302 | "recent_changes": 0, 303 | "name": "Hungarian", 304 | "total": 4, 305 | "total_words": 15, 306 | "translated": 0, 307 | "translated_percent": 0.0, 308 | "translated_words": 0, 309 | "url": "http://127.0.0.1:8000/engage/hello/hu/", 310 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/hu/" 311 | }, 312 | { 313 | "code": "it", 314 | "failing": 0, 315 | "failing_percent": 0.0, 316 | "fuzzy": 0, 317 | "fuzzy_percent": 0.0, 318 | "last_author": null, 319 | "last_change": null, 320 | "recent_changes": 0, 321 | "name": "Italian", 322 | "total": 4, 323 | "total_words": 15, 324 | "translated": 0, 325 | "translated_percent": 0.0, 326 | "translated_words": 0, 327 | "url": "http://127.0.0.1:8000/engage/hello/it/", 328 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/it/" 329 | }, 330 | { 331 | "code": "ja", 332 | "failing": 0, 333 | "failing_percent": 0.0, 334 | "fuzzy": 0, 335 | "fuzzy_percent": 0.0, 336 | "last_author": null, 337 | "last_change": null, 338 | "recent_changes": 0, 339 | "name": "Japanese", 340 | "total": 4, 341 | "total_words": 15, 342 | "translated": 0, 343 | "translated_percent": 0.0, 344 | "translated_words": 0, 345 | "url": "http://127.0.0.1:8000/engage/hello/ja/", 346 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/ja/" 347 | }, 348 | { 349 | "code": "lt", 350 | "failing": 0, 351 | "failing_percent": 0.0, 352 | "fuzzy": 0, 353 | "fuzzy_percent": 0.0, 354 | "last_author": null, 355 | "last_change": null, 356 | "recent_changes": 0, 357 | "name": "Lithuanian", 358 | "total": 4, 359 | "total_words": 15, 360 | "translated": 0, 361 | "translated_percent": 0.0, 362 | "translated_words": 0, 363 | "url": "http://127.0.0.1:8000/engage/hello/lt/", 364 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/lt/" 365 | } 366 | ] 367 | } 368 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-statistics--GET--page=2: -------------------------------------------------------------------------------- 1 | { 2 | "count": 33, 3 | "next": null, 4 | "previous": "http://127.0.0.1:8000/api/components/hello/weblate/statistics/", 5 | "results": [ 6 | { 7 | "code": "mn", 8 | "failing": 0, 9 | "failing_percent": 0.0, 10 | "fuzzy": 0, 11 | "fuzzy_percent": 0.0, 12 | "last_author": null, 13 | "last_change": null, 14 | "recent_changes": 0, 15 | "name": "Mongolian", 16 | "total": 4, 17 | "total_words": 15, 18 | "translated": 0, 19 | "translated_percent": 0.0, 20 | "translated_words": 0, 21 | "url": "http://127.0.0.1:8000/engage/hello/mn/", 22 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/mn/" 23 | }, 24 | { 25 | "code": "nb", 26 | "failing": 0, 27 | "failing_percent": 0.0, 28 | "fuzzy": 0, 29 | "fuzzy_percent": 0.0, 30 | "last_author": null, 31 | "last_change": null, 32 | "recent_changes": 0, 33 | "name": "Norwegian Bokm\u00e5l", 34 | "total": 4, 35 | "total_words": 15, 36 | "translated": 0, 37 | "translated_percent": 0.0, 38 | "translated_words": 0, 39 | "url": "http://127.0.0.1:8000/engage/hello/nb/", 40 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/nb/" 41 | }, 42 | { 43 | "code": "fa", 44 | "failing": 0, 45 | "failing_percent": 0.0, 46 | "fuzzy": 0, 47 | "fuzzy_percent": 0.0, 48 | "last_author": null, 49 | "last_change": null, 50 | "recent_changes": 0, 51 | "name": "Persian", 52 | "total": 4, 53 | "total_words": 15, 54 | "translated": 0, 55 | "translated_percent": 0.0, 56 | "translated_words": 0, 57 | "url": "http://127.0.0.1:8000/engage/hello/fa/", 58 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/fa/" 59 | }, 60 | { 61 | "code": "pl", 62 | "failing": 0, 63 | "failing_percent": 0.0, 64 | "fuzzy": 0, 65 | "fuzzy_percent": 0.0, 66 | "last_author": null, 67 | "last_change": null, 68 | "recent_changes": 0, 69 | "name": "Polish", 70 | "total": 4, 71 | "total_words": 15, 72 | "translated": 0, 73 | "translated_percent": 0.0, 74 | "translated_words": 0, 75 | "url": "http://127.0.0.1:8000/engage/hello/pl/", 76 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/pl/" 77 | }, 78 | { 79 | "code": "pt_BR", 80 | "failing": 0, 81 | "failing_percent": 0.0, 82 | "fuzzy": 0, 83 | "fuzzy_percent": 0.0, 84 | "last_author": null, 85 | "last_change": null, 86 | "recent_changes": 0, 87 | "name": "Portuguese (Brazil)", 88 | "total": 4, 89 | "total_words": 15, 90 | "translated": 0, 91 | "translated_percent": 0.0, 92 | "translated_words": 0, 93 | "url": "http://127.0.0.1:8000/engage/hello/pt_BR/", 94 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/pt_BR/" 95 | }, 96 | { 97 | "code": "ro", 98 | "failing": 0, 99 | "failing_percent": 0.0, 100 | "fuzzy": 0, 101 | "fuzzy_percent": 0.0, 102 | "last_author": null, 103 | "last_change": null, 104 | "recent_changes": 0, 105 | "name": "Romanian", 106 | "total": 4, 107 | "total_words": 15, 108 | "translated": 0, 109 | "translated_percent": 0.0, 110 | "translated_words": 0, 111 | "url": "http://127.0.0.1:8000/engage/hello/ro/", 112 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/ro/" 113 | }, 114 | { 115 | "code": "ru", 116 | "failing": 0, 117 | "failing_percent": 0.0, 118 | "fuzzy": 0, 119 | "fuzzy_percent": 0.0, 120 | "last_author": null, 121 | "last_change": null, 122 | "recent_changes": 0, 123 | "name": "Russian", 124 | "total": 4, 125 | "total_words": 15, 126 | "translated": 0, 127 | "translated_percent": 0.0, 128 | "translated_words": 0, 129 | "url": "http://127.0.0.1:8000/engage/hello/ru/", 130 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/ru/" 131 | }, 132 | { 133 | "code": "sr", 134 | "failing": 0, 135 | "failing_percent": 0.0, 136 | "fuzzy": 0, 137 | "fuzzy_percent": 0.0, 138 | "last_author": null, 139 | "last_change": null, 140 | "recent_changes": 0, 141 | "name": "Serbian", 142 | "total": 4, 143 | "total_words": 15, 144 | "translated": 0, 145 | "translated_percent": 0.0, 146 | "translated_words": 0, 147 | "url": "http://127.0.0.1:8000/engage/hello/sr/", 148 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/sr/" 149 | }, 150 | { 151 | "code": "sk", 152 | "failing": 0, 153 | "failing_percent": 0.0, 154 | "fuzzy": 0, 155 | "fuzzy_percent": 0.0, 156 | "last_author": null, 157 | "last_change": null, 158 | "recent_changes": 0, 159 | "name": "Slovak", 160 | "total": 4, 161 | "total_words": 15, 162 | "translated": 0, 163 | "translated_percent": 0.0, 164 | "translated_words": 0, 165 | "url": "http://127.0.0.1:8000/engage/hello/sk/", 166 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/sk/" 167 | }, 168 | { 169 | "code": "sl", 170 | "failing": 0, 171 | "failing_percent": 0.0, 172 | "fuzzy": 0, 173 | "fuzzy_percent": 0.0, 174 | "last_author": null, 175 | "last_change": null, 176 | "recent_changes": 0, 177 | "name": "Slovenian", 178 | "total": 4, 179 | "total_words": 15, 180 | "translated": 0, 181 | "translated_percent": 0.0, 182 | "translated_words": 0, 183 | "url": "http://127.0.0.1:8000/engage/hello/sl/", 184 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/sl/" 185 | }, 186 | { 187 | "code": "es", 188 | "failing": 0, 189 | "failing_percent": 0.0, 190 | "fuzzy": 0, 191 | "fuzzy_percent": 0.0, 192 | "last_author": null, 193 | "last_change": null, 194 | "recent_changes": 0, 195 | "name": "Spanish", 196 | "total": 4, 197 | "total_words": 15, 198 | "translated": 0, 199 | "translated_percent": 0.0, 200 | "translated_words": 0, 201 | "url": "http://127.0.0.1:8000/engage/hello/es/", 202 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/es/" 203 | }, 204 | { 205 | "code": "sv", 206 | "failing": 0, 207 | "failing_percent": 0.0, 208 | "fuzzy": 0, 209 | "fuzzy_percent": 0.0, 210 | "last_author": null, 211 | "last_change": null, 212 | "recent_changes": 0, 213 | "name": "Swedish", 214 | "total": 4, 215 | "total_words": 15, 216 | "translated": 0, 217 | "translated_percent": 0.0, 218 | "translated_words": 0, 219 | "url": "http://127.0.0.1:8000/engage/hello/sv/", 220 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/sv/" 221 | }, 222 | { 223 | "code": "tr", 224 | "failing": 0, 225 | "failing_percent": 0.0, 226 | "fuzzy": 0, 227 | "fuzzy_percent": 0.0, 228 | "last_author": null, 229 | "last_change": null, 230 | "recent_changes": 0, 231 | "name": "Turkish", 232 | "total": 4, 233 | "total_words": 15, 234 | "translated": 0, 235 | "translated_percent": 0.0, 236 | "translated_words": 0, 237 | "url": "http://127.0.0.1:8000/engage/hello/tr/", 238 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/tr/" 239 | } 240 | ] 241 | } 242 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-statistics--GET--page=3: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "Invalid page \"3\": That page contains no results." 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/components-hello-weblate-translations--POST--language_code=nl_BE: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "language": { 4 | "code": "nl_BE", 5 | "name": "Flemish", 6 | "plural": { 7 | "id": 305, 8 | "source": 0, 9 | "number": 2, 10 | "formula": "n != 1", 11 | "type": 1 12 | }, 13 | "aliases": [ 14 | "swe" 15 | ], 16 | "direction": "ltr", 17 | "web_url": "http://127.0.0.1:8000/languages/nl_BE/", 18 | "url": "http://127.0.0.1:8000/api/languages/nl_BE/", 19 | "statistics_url": "http://127.0.0.1:8000/api/languages/nl_BE/statistics/" 20 | }, 21 | "language_code": "nl_BE", 22 | "id": 827, 23 | "filename": "po/sv.po", 24 | "revision": "da6ea2777f61fbe1d2a207ff6ebdadfa15f26d1a", 25 | "web_url": "http://127.0.0.1:8000/projects/hello/weblate/nl_BE/", 26 | "share_url": "http://127.0.0.1:8000/engage/hello/nl_BE/", 27 | "translate_url": "http://127.0.0.1:8000/translate/hello/weblate/nl_BE/", 28 | "url": "http://127.0.0.1:8000/api/translations/hello/weblate/nl_BE/", 29 | "is_template": false, 30 | "is_source": false, 31 | "total": 0, 32 | "total_words": 0, 33 | "translated": 0, 34 | "translated_words": 0, 35 | "translated_percent": 0.0, 36 | "fuzzy": 0, 37 | "fuzzy_words": 0, 38 | "fuzzy_percent": 0.0, 39 | "failing_checks": 0, 40 | "failing_checks_words": 0, 41 | "failing_checks_percent": 0.0, 42 | "have_suggestion": 0, 43 | "have_comment": 0, 44 | "last_change": null, 45 | "last_author": null, 46 | "repository_url": "http://127.0.0.1:8000/api/translations/hello/weblate/nl_BE/repository/", 47 | "file_url": "http://127.0.0.1:8000/api/translations/hello/weblate/nl_BE/file/", 48 | "statistics_url": "http://127.0.0.1:8000/api/translations/hello/weblate/nl_BE/statistics/", 49 | "changes_list_url": "http://127.0.0.1:8000/api/translations/hello/weblate/nl_BE/changes/", 50 | "units_list_url": "http://127.0.0.1:8000/api/translations/hello/weblate/nl_BE/units/" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /wlc/test_data/api/languages: -------------------------------------------------------------------------------- 1 | { 2 | "count": 47, 3 | "next": "http://127.0.0.1:8000/api/languages/?page=2", 4 | "previous": null, 5 | "results": [ 6 | { 7 | "code": "ach", 8 | "direction": "ltr", 9 | "name": "Acholi", 10 | "nplurals": 2, 11 | "pluralequation": "(n > 1)", 12 | "url": "http://127.0.0.1:8000/api/languages/ach/", 13 | "web_url": "http://127.0.0.1:8000/languages/ach/" 14 | }, 15 | { 16 | "code": "af", 17 | "direction": "ltr", 18 | "name": "Afrikaans", 19 | "nplurals": 2, 20 | "pluralequation": "n != 1", 21 | "url": "http://127.0.0.1:8000/api/languages/af/", 22 | "web_url": "http://127.0.0.1:8000/languages/af/" 23 | }, 24 | { 25 | "code": "ak", 26 | "direction": "ltr", 27 | "name": "Akan", 28 | "nplurals": 2, 29 | "pluralequation": "n > 1", 30 | "url": "http://127.0.0.1:8000/api/languages/ak/", 31 | "web_url": "http://127.0.0.1:8000/languages/ak/" 32 | }, 33 | { 34 | "code": "sq", 35 | "direction": "ltr", 36 | "name": "Albanian", 37 | "nplurals": 2, 38 | "pluralequation": "n != 1", 39 | "url": "http://127.0.0.1:8000/api/languages/sq/", 40 | "web_url": "http://127.0.0.1:8000/languages/sq/" 41 | }, 42 | { 43 | "code": "am", 44 | "direction": "ltr", 45 | "name": "Amharic", 46 | "nplurals": 2, 47 | "pluralequation": "n > 1", 48 | "url": "http://127.0.0.1:8000/api/languages/am/", 49 | "web_url": "http://127.0.0.1:8000/languages/am/" 50 | }, 51 | { 52 | "code": "anp", 53 | "direction": "ltr", 54 | "name": "Angika", 55 | "nplurals": 2, 56 | "pluralequation": "(n != 1)", 57 | "url": "http://127.0.0.1:8000/api/languages/anp/", 58 | "web_url": "http://127.0.0.1:8000/languages/anp/" 59 | }, 60 | { 61 | "code": "ar", 62 | "direction": "rtl", 63 | "name": "Arabic", 64 | "nplurals": 6, 65 | "pluralequation": "n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5", 66 | "url": "http://127.0.0.1:8000/api/languages/ar/", 67 | "web_url": "http://127.0.0.1:8000/languages/ar/" 68 | }, 69 | { 70 | "code": "hy", 71 | "direction": "ltr", 72 | "name": "Armenian", 73 | "nplurals": 2, 74 | "pluralequation": "(n != 1)", 75 | "url": "http://127.0.0.1:8000/api/languages/hy/", 76 | "web_url": "http://127.0.0.1:8000/languages/hy/" 77 | }, 78 | { 79 | "code": "ast", 80 | "direction": "ltr", 81 | "name": "Asturian", 82 | "nplurals": 2, 83 | "pluralequation": "n != 1", 84 | "url": "http://127.0.0.1:8000/api/languages/ast/", 85 | "web_url": "http://127.0.0.1:8000/languages/ast/" 86 | }, 87 | { 88 | "code": "de_AT", 89 | "direction": "ltr", 90 | "name": "Austrian German", 91 | "nplurals": 2, 92 | "pluralequation": "n != 1", 93 | "url": "http://127.0.0.1:8000/api/languages/de_AT/", 94 | "web_url": "http://127.0.0.1:8000/languages/de_AT/" 95 | }, 96 | { 97 | "code": "ay", 98 | "direction": "ltr", 99 | "name": "Aymar\u00e1", 100 | "nplurals": 1, 101 | "pluralequation": "0", 102 | "url": "http://127.0.0.1:8000/api/languages/ay/", 103 | "web_url": "http://127.0.0.1:8000/languages/ay/" 104 | }, 105 | { 106 | "code": "az", 107 | "direction": "ltr", 108 | "name": "Azerbaijani", 109 | "nplurals": 2, 110 | "pluralequation": "n != 1", 111 | "url": "http://127.0.0.1:8000/api/languages/az/", 112 | "web_url": "http://127.0.0.1:8000/languages/az/" 113 | }, 114 | { 115 | "code": "ba", 116 | "direction": "ltr", 117 | "name": "Bashkir", 118 | "nplurals": 2, 119 | "pluralequation": "(n != 1)", 120 | "url": "http://127.0.0.1:8000/api/languages/ba/", 121 | "web_url": "http://127.0.0.1:8000/languages/ba/" 122 | }, 123 | { 124 | "code": "bs", 125 | "direction": "ltr", 126 | "name": "Bosnian", 127 | "nplurals": 3, 128 | "pluralequation": "n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", 129 | "url": "http://127.0.0.1:8000/api/languages/bs/", 130 | "web_url": "http://127.0.0.1:8000/languages/bs/" 131 | }, 132 | { 133 | "code": "ca", 134 | "direction": "ltr", 135 | "name": "Catalan", 136 | "nplurals": 2, 137 | "pluralequation": "n != 1", 138 | "url": "http://127.0.0.1:8000/api/languages/ca/", 139 | "web_url": "http://127.0.0.1:8000/languages/ca/" 140 | }, 141 | { 142 | "code": "zh_CN", 143 | "direction": "ltr", 144 | "name": "Chinese (China)", 145 | "nplurals": 1, 146 | "pluralequation": "0", 147 | "url": "http://127.0.0.1:8000/api/languages/zh_CN/", 148 | "web_url": "http://127.0.0.1:8000/languages/zh_CN/" 149 | }, 150 | { 151 | "code": "zh_TW", 152 | "direction": "ltr", 153 | "name": "Chinese (Taiwan)", 154 | "nplurals": 1, 155 | "pluralequation": "0", 156 | "url": "http://127.0.0.1:8000/api/languages/zh_TW/", 157 | "web_url": "http://127.0.0.1:8000/languages/zh_TW/" 158 | }, 159 | { 160 | "code": "cs", 161 | "direction": "ltr", 162 | "name": "Czech", 163 | "nplurals": 3, 164 | "pluralequation": "(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2", 165 | "url": "http://127.0.0.1:8000/api/languages/cs/", 166 | "web_url": "http://127.0.0.1:8000/languages/cs/" 167 | }, 168 | { 169 | "code": "da", 170 | "direction": "ltr", 171 | "name": "Danish", 172 | "nplurals": 2, 173 | "pluralequation": "n != 1", 174 | "url": "http://127.0.0.1:8000/api/languages/da/", 175 | "web_url": "http://127.0.0.1:8000/languages/da/" 176 | }, 177 | { 178 | "code": "nl", 179 | "direction": "ltr", 180 | "name": "Dutch", 181 | "nplurals": 2, 182 | "pluralequation": "n != 1", 183 | "url": "http://127.0.0.1:8000/api/languages/nl/", 184 | "web_url": "http://127.0.0.1:8000/languages/nl/" 185 | } 186 | ] 187 | } 188 | -------------------------------------------------------------------------------- /wlc/test_data/api/languages--GET--page=2: -------------------------------------------------------------------------------- 1 | { 2 | "count": 47, 3 | "next": "http://127.0.0.1:8000/api/languages/?page=3", 4 | "previous": "http://127.0.0.1:8000/api/languages/", 5 | "results": [ 6 | { 7 | "code": "en", 8 | "direction": "ltr", 9 | "name": "English", 10 | "nplurals": 2, 11 | "pluralequation": "n != 1", 12 | "url": "http://127.0.0.1:8000/api/languages/en/", 13 | "web_url": "http://127.0.0.1:8000/languages/en/" 14 | }, 15 | { 16 | "code": "en_GB", 17 | "direction": "ltr", 18 | "name": "English (United Kingdom)", 19 | "nplurals": 2, 20 | "pluralequation": "n != 1", 21 | "url": "http://127.0.0.1:8000/api/languages/en_GB/", 22 | "web_url": "http://127.0.0.1:8000/languages/en_GB/" 23 | }, 24 | { 25 | "code": "fi", 26 | "direction": "ltr", 27 | "name": "Finnish", 28 | "nplurals": 2, 29 | "pluralequation": "n != 1", 30 | "url": "http://127.0.0.1:8000/api/languages/fi/", 31 | "web_url": "http://127.0.0.1:8000/languages/fi/" 32 | }, 33 | { 34 | "code": "fr", 35 | "direction": "ltr", 36 | "name": "French", 37 | "nplurals": 2, 38 | "pluralequation": "n > 1", 39 | "url": "http://127.0.0.1:8000/api/languages/fr/", 40 | "web_url": "http://127.0.0.1:8000/languages/fr/" 41 | }, 42 | { 43 | "code": "gl", 44 | "direction": "ltr", 45 | "name": "Galician", 46 | "nplurals": 2, 47 | "pluralequation": "n != 1", 48 | "url": "http://127.0.0.1:8000/api/languages/gl/", 49 | "web_url": "http://127.0.0.1:8000/languages/gl/" 50 | }, 51 | { 52 | "code": "ka", 53 | "direction": "ltr", 54 | "name": "Georgian", 55 | "nplurals": 1, 56 | "pluralequation": "0", 57 | "url": "http://127.0.0.1:8000/api/languages/ka/", 58 | "web_url": "http://127.0.0.1:8000/languages/ka/" 59 | }, 60 | { 61 | "code": "de", 62 | "direction": "ltr", 63 | "name": "German", 64 | "nplurals": 2, 65 | "pluralequation": "n != 1", 66 | "url": "http://127.0.0.1:8000/api/languages/de/", 67 | "web_url": "http://127.0.0.1:8000/languages/de/" 68 | }, 69 | { 70 | "code": "el", 71 | "direction": "ltr", 72 | "name": "Greek", 73 | "nplurals": 2, 74 | "pluralequation": "n != 1", 75 | "url": "http://127.0.0.1:8000/api/languages/el/", 76 | "web_url": "http://127.0.0.1:8000/languages/el/" 77 | }, 78 | { 79 | "code": "he", 80 | "direction": "rtl", 81 | "name": "Hebrew", 82 | "nplurals": 2, 83 | "pluralequation": "n != 1", 84 | "url": "http://127.0.0.1:8000/api/languages/he/", 85 | "web_url": "http://127.0.0.1:8000/languages/he/" 86 | }, 87 | { 88 | "code": "hu", 89 | "direction": "ltr", 90 | "name": "Hungarian", 91 | "nplurals": 2, 92 | "pluralequation": "n != 1", 93 | "url": "http://127.0.0.1:8000/api/languages/hu/", 94 | "web_url": "http://127.0.0.1:8000/languages/hu/" 95 | }, 96 | { 97 | "code": "it", 98 | "direction": "ltr", 99 | "name": "Italian", 100 | "nplurals": 2, 101 | "pluralequation": "n != 1", 102 | "url": "http://127.0.0.1:8000/api/languages/it/", 103 | "web_url": "http://127.0.0.1:8000/languages/it/" 104 | }, 105 | { 106 | "code": "ja", 107 | "direction": "ltr", 108 | "name": "Japanese", 109 | "nplurals": 1, 110 | "pluralequation": "0", 111 | "url": "http://127.0.0.1:8000/api/languages/ja/", 112 | "web_url": "http://127.0.0.1:8000/languages/ja/" 113 | }, 114 | { 115 | "code": "lt", 116 | "direction": "ltr", 117 | "name": "Lithuanian", 118 | "nplurals": 3, 119 | "pluralequation": "n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2", 120 | "url": "http://127.0.0.1:8000/api/languages/lt/", 121 | "web_url": "http://127.0.0.1:8000/languages/lt/" 122 | }, 123 | { 124 | "code": "mn", 125 | "direction": "ltr", 126 | "name": "Mongolian", 127 | "nplurals": 2, 128 | "pluralequation": "n != 1", 129 | "url": "http://127.0.0.1:8000/api/languages/mn/", 130 | "web_url": "http://127.0.0.1:8000/languages/mn/" 131 | }, 132 | { 133 | "code": "nb", 134 | "direction": "ltr", 135 | "name": "Norwegian Bokm\u00e5l", 136 | "nplurals": 2, 137 | "pluralequation": "n != 1", 138 | "url": "http://127.0.0.1:8000/api/languages/nb/", 139 | "web_url": "http://127.0.0.1:8000/languages/nb/" 140 | }, 141 | { 142 | "code": "fa", 143 | "direction": "rtl", 144 | "name": "Persian", 145 | "nplurals": 2, 146 | "pluralequation": "n != 1", 147 | "url": "http://127.0.0.1:8000/api/languages/fa/", 148 | "web_url": "http://127.0.0.1:8000/languages/fa/" 149 | }, 150 | { 151 | "code": "pl", 152 | "direction": "ltr", 153 | "name": "Polish", 154 | "nplurals": 3, 155 | "pluralequation": "n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", 156 | "url": "http://127.0.0.1:8000/api/languages/pl/", 157 | "web_url": "http://127.0.0.1:8000/languages/pl/" 158 | }, 159 | { 160 | "code": "pt_BR", 161 | "direction": "ltr", 162 | "name": "Portuguese (Brazil)", 163 | "nplurals": 2, 164 | "pluralequation": "n > 1", 165 | "url": "http://127.0.0.1:8000/api/languages/pt_BR/", 166 | "web_url": "http://127.0.0.1:8000/languages/pt_BR/" 167 | }, 168 | { 169 | "code": "ro", 170 | "direction": "ltr", 171 | "name": "Romanian", 172 | "nplurals": 3, 173 | "pluralequation": "n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2", 174 | "url": "http://127.0.0.1:8000/api/languages/ro/", 175 | "web_url": "http://127.0.0.1:8000/languages/ro/" 176 | }, 177 | { 178 | "code": "ru", 179 | "direction": "ltr", 180 | "name": "Russian", 181 | "nplurals": 3, 182 | "pluralequation": "n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", 183 | "url": "http://127.0.0.1:8000/api/languages/ru/", 184 | "web_url": "http://127.0.0.1:8000/languages/ru/" 185 | } 186 | ] 187 | } 188 | -------------------------------------------------------------------------------- /wlc/test_data/api/languages--GET--page=3: -------------------------------------------------------------------------------- 1 | { 2 | "count": 47, 3 | "next": null, 4 | "previous": "http://127.0.0.1:8000/api/languages/?page=2", 5 | "results": [ 6 | { 7 | "code": "sr", 8 | "direction": "ltr", 9 | "name": "Serbian", 10 | "nplurals": 3, 11 | "pluralequation": "n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", 12 | "url": "http://127.0.0.1:8000/api/languages/sr/", 13 | "web_url": "http://127.0.0.1:8000/languages/sr/" 14 | }, 15 | { 16 | "code": "sk", 17 | "direction": "ltr", 18 | "name": "Slovak", 19 | "nplurals": 3, 20 | "pluralequation": "(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2", 21 | "url": "http://127.0.0.1:8000/api/languages/sk/", 22 | "web_url": "http://127.0.0.1:8000/languages/sk/" 23 | }, 24 | { 25 | "code": "sl", 26 | "direction": "ltr", 27 | "name": "Slovenian", 28 | "nplurals": 4, 29 | "pluralequation": "n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3", 30 | "url": "http://127.0.0.1:8000/api/languages/sl/", 31 | "web_url": "http://127.0.0.1:8000/languages/sl/" 32 | }, 33 | { 34 | "code": "es", 35 | "direction": "ltr", 36 | "name": "Spanish", 37 | "nplurals": 2, 38 | "pluralequation": "n != 1", 39 | "url": "http://127.0.0.1:8000/api/languages/es/", 40 | "web_url": "http://127.0.0.1:8000/languages/es/" 41 | }, 42 | { 43 | "code": "es_AR", 44 | "direction": "ltr", 45 | "name": "Spanish (Argentina)", 46 | "nplurals": 2, 47 | "pluralequation": "(n != 1)", 48 | "url": "http://127.0.0.1:8000/api/languages/es_AR/", 49 | "web_url": "http://127.0.0.1:8000/languages/es_AR/" 50 | }, 51 | { 52 | "code": "sv", 53 | "direction": "ltr", 54 | "name": "Swedish", 55 | "nplurals": 2, 56 | "pluralequation": "n != 1", 57 | "url": "http://127.0.0.1:8000/api/languages/sv/", 58 | "web_url": "http://127.0.0.1:8000/languages/sv/" 59 | }, 60 | { 61 | "code": "tr", 62 | "direction": "ltr", 63 | "name": "Turkish", 64 | "nplurals": 2, 65 | "pluralequation": "n > 1", 66 | "url": "http://127.0.0.1:8000/api/languages/tr/", 67 | "web_url": "http://127.0.0.1:8000/languages/tr/" 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /wlc/test_data/api/languages--POST--code=tst--name=Test-Language--direction=rtl--plural=number=2--formula=n-!=-1: -------------------------------------------------------------------------------- 1 | { 2 | "code": "tst", 3 | "name": "Test Language", 4 | "plural": { 5 | "id": 389, 6 | "source": 0, 7 | "number": 2, 8 | "formula": "n != 1", 9 | "type": 1 10 | }, 11 | "aliases": [], 12 | "direction": "rtl", 13 | "web_url": "http://example.com/languages/test/", 14 | "url": "http://127.0.0.1:8080/api/languages/test/", 15 | "statistics_url": "http://127.0.0.1:8080/api/languages/test/statistics/" 16 | } 17 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "name": "ACL", 8 | "slug": "acl", 9 | "web": "https://weblate.org/", 10 | "source_language": { 11 | "code": "en", 12 | "name": "English", 13 | "nplurals": 2, 14 | "pluralequation": "n != 1", 15 | "direction": "ltr", 16 | "web_url": "http://127.0.0.1:8000/languages/en/", 17 | "url": "http://127.0.0.1:8000/api/languages/en/" 18 | }, 19 | "web_url": "http://127.0.0.1:8000/projects/acl/", 20 | "url": "http://127.0.0.1:8000/api/projects/acl/" 21 | }, 22 | { 23 | "name": "Hello", 24 | "slug": "hello", 25 | "web": "http://weblate.org/", 26 | "source_language": { 27 | "code": "en", 28 | "name": "English", 29 | "nplurals": 2, 30 | "pluralequation": "n != 1", 31 | "direction": "ltr", 32 | "web_url": "http://127.0.0.1:8000/languages/en/", 33 | "url": "http://127.0.0.1:8000/api/languages/en/" 34 | }, 35 | "web_url": "http://127.0.0.1:8000/projects/hello/", 36 | "url": "http://127.0.0.1:8000/api/projects/hello/" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects--POST--name=Hello--slug=hello--web=http---example.com---source_language=name=Malayalam--code=ml: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Hello", 3 | "slug":"hello", 4 | "id":14, 5 | "web":"http://example.com/", 6 | "source_language":{ 7 | "code":"ml", 8 | "name":"Malayalam", 9 | "plural":{ 10 | "id":218, 11 | "source":0, 12 | "number":2, 13 | "formula":"n != 1", 14 | "type":1 15 | }, 16 | "aliases":[ 17 | "mal" 18 | ], 19 | "direction":"ltr", 20 | "web_url":"http://example.com/languages/ml/", 21 | "url":"http://127.0.0.1:8000/api/languages/ml/", 22 | "statistics_url":"http://127.0.0.1:8000/api/languages/ml/statistics/" 23 | }, 24 | "web_url":"http://example.com/projects/hello/", 25 | "url":"http://127.0.0.1:8000/api/projects/hello/", 26 | "components_list_url":"http://127.0.0.1:8000/api/projects/hello/components/", 27 | "repository_url":"http://127.0.0.1:8000/api/projects/hello/repository/", 28 | "statistics_url":"http://127.0.0.1:8000/api/projects/hello/statistics/", 29 | "changes_list_url":"http://127.0.0.1:8000/api/projects/hello/changes/", 30 | "languages_url":"http://127.0.0.1:8000/api/projects/hello/languages/" 31 | } 32 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects--POST--name=denied_json--slug=denide_json--web=http---some-weblate-app.com-: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-acl: -------------------------------------------------------------------------------- 1 | { 2 | "components_list_url": "http://127.0.0.1:8000/api/projects/acl/components/", 3 | "name": "ACL", 4 | "repository_url": "http://127.0.0.1:8000/api/projects/acl/repository/", 5 | "slug": "acl", 6 | "source_language": { 7 | "code": "en", 8 | "direction": "ltr", 9 | "name": "English", 10 | "nplurals": 2, 11 | "pluralequation": "n != 1", 12 | "url": "http://127.0.0.1:8000/api/languages/en/", 13 | "web_url": "http://127.0.0.1:8000/languages/en/" 14 | }, 15 | "url": "http://127.0.0.1:8000/api/projects/acl/", 16 | "statistics_url": "http://127.0.0.1:8000/api/projects/acl/statistics/", 17 | "changes_list_url": "http://127.0.0.1:8000/api/projects/acl/changes/", 18 | "web": "http://weblate.org/", 19 | "web_url": "http://127.0.0.1:8000/projects/acl/" 20 | } 21 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-empty: -------------------------------------------------------------------------------- 1 | { 2 | "components_list_url": "http://127.0.0.1:8000/api/projects/empty/components/", 3 | "name": "Empty", 4 | "repository_url": "http://127.0.0.1:8000/api/projects/empty/repository/", 5 | "slug": "empty", 6 | "source_language": { 7 | "code": "en", 8 | "direction": "ltr", 9 | "name": "English", 10 | "nplurals": 2, 11 | "pluralequation": "n != 1", 12 | "url": "http://127.0.0.1:8000/api/languages/en/", 13 | "web_url": "http://127.0.0.1:8000/languages/en/" 14 | }, 15 | "url": "http://127.0.0.1:8000/api/projects/empty/", 16 | "statistics_url": "http://127.0.0.1:8000/api/projects/empty/statistics/", 17 | "changes_list_url": "http://127.0.0.1:8000/api/projects/empty/changes/", 18 | "web": "http://weblate.org/", 19 | "web_url": "http://127.0.0.1:8000/projects/empty/" 20 | } 21 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-empty-components: -------------------------------------------------------------------------------- 1 | { 2 | "count": 0, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello: -------------------------------------------------------------------------------- 1 | { 2 | "categories_url": "http://127.0.0.1:8000/api/projects/hello/categories/", 3 | "components_list_url": "http://127.0.0.1:8000/api/projects/hello/components/", 4 | "name": "Hello", 5 | "repository_url": "http://127.0.0.1:8000/api/projects/hello/repository/", 6 | "slug": "hello", 7 | "source_language": { 8 | "code": "en", 9 | "direction": "ltr", 10 | "name": "English", 11 | "nplurals": 2, 12 | "pluralequation": "n != 1", 13 | "url": "http://127.0.0.1:8000/api/languages/en/", 14 | "web_url": "http://127.0.0.1:8000/languages/en/" 15 | }, 16 | "url": "http://127.0.0.1:8000/api/projects/hello/", 17 | "statistics_url": "http://127.0.0.1:8000/api/projects/hello/statistics/", 18 | "languages_url": "http://127.0.0.1:8000/api/projects/hello/languages/", 19 | "changes_list_url": "http://127.0.0.1:8000/api/projects/hello/changes/", 20 | "web": "http://weblate.org/", 21 | "web_url": "http://127.0.0.1:8000/projects/hello/" 22 | } 23 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello--DELETE--: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeblateOrg/wlc/d67137f8e8774f4088b7ad97cd323cdbb9698eb6/wlc/test_data/api/projects-hello--DELETE-- -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-categories: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "category": null, 8 | "name": "hi", 9 | "project": "http://127.0.0.1:8000/api/projects/hello/", 10 | "slug": "hi", 11 | "url": "http://127.0.0.1:8000/api/categories/1/" 12 | }, 13 | { 14 | "category": null, 15 | "name": "hiya", 16 | "project": "http://127.0.0.1:8000/api/projects/hello/", 17 | "slug": "hiya", 18 | "url": "http://127.0.0.1:8000/api/categories/2/" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-changes: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "unit": null, 8 | "component": "http://127.0.0.1:8000/api/components/hello/android/", 9 | "url": "http://127.0.0.1:8000/api/changes/1/", 10 | "translation": null, 11 | "dictionary": null, 12 | "user": null, 13 | "author": null, 14 | "timestamp": "2016-11-18T10:47:01.355911Z", 15 | "action": 20, 16 | "target": "", 17 | "id": 353, 18 | "action_name": "Sloučen repozitář" 19 | }, 20 | { 21 | "unit": "http://127.0.0.1:8000/api/units/227/", 22 | "component": "http://127.0.0.1:8000/api/components/hello/weblate/", 23 | "url": "http://127.0.0.1:8000/api/changes/2/", 24 | "translation": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/", 25 | "dictionary": null, 26 | "user": 2, 27 | "author": 2, 28 | "timestamp": "2016-10-24T07:21:54.121348Z", 29 | "action": 26, 30 | "target": "", 31 | "id": 350, 32 | "action_name": "Odstraněn návrh" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-components: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "agreement": "", 8 | "branch": "main", 9 | "category": "http://127.0.0.1:8000/api/categories/1/", 10 | "file_format": "aresource", 11 | "filemask": "android/values-*/strings.xml", 12 | "git_export": "", 13 | "license": "", 14 | "license_url": "", 15 | "lock_url": "http://127.0.0.1:8000/api/components/hello/android/lock/", 16 | "name": "Android", 17 | "new_base": "", 18 | "priority": 100, 19 | "repo": "https://github.com/WeblateOrg/test.git", 20 | "repository_url": "http://127.0.0.1:8000/api/components/hello/android/repository/", 21 | "slug": "android", 22 | "statistics_url": "http://127.0.0.1:8000/api/components/hello/android/statistics/", 23 | "source_language": { 24 | "code": "en", 25 | "name": "English", 26 | "plural": { 27 | "id": 82, 28 | "source": 0, 29 | "number": 2, 30 | "formula": "n != 1", 31 | "type": 1 32 | }, 33 | "aliases": [ 34 | "english", 35 | "en_en", 36 | "base", 37 | "source", 38 | "eng" 39 | ], 40 | "direction": "ltr", 41 | "web_url": "http://127.0.0.1:8000/api/languages/en/", 42 | "url": "http://127.0.0.1:8000/api/languages/en/", 43 | "statistics_url": "http://127.0.0.1:8000/api/languages/en/statistics/" 44 | }, 45 | "template": "android/values/strings.xml", 46 | "translations_url": "http://127.0.0.1:8000/api/components/hello/android/translations/", 47 | "url": "http://127.0.0.1:8000/api/components/hello/android/", 48 | "vcs": "git", 49 | "web_url": "http://127.0.0.1:8000/projects/hello/android/" 50 | }, 51 | { 52 | "agreement": "", 53 | "branch": "main", 54 | "category": null, 55 | "file_format": "po", 56 | "filemask": "po/*.po", 57 | "git_export": "", 58 | "license": "", 59 | "license_url": "", 60 | "lock_url": "http://127.0.0.1:8000/api/components/hello/weblate/lock/", 61 | "name": "Weblate", 62 | "new_base": "", 63 | "priority": 100, 64 | "repo": "file:///home/WeblateOrg/work/weblate-hello", 65 | "repository_url": "http://127.0.0.1:8000/api/components/hello/weblate/repository/", 66 | "slug": "weblate", 67 | "statistics_url": "http://127.0.0.1:8000/api/components/hello/weblate/statistics/", 68 | "source_language": { 69 | "code": "en", 70 | "name": "English", 71 | "plural": { 72 | "id": 82, 73 | "source": 0, 74 | "number": 2, 75 | "formula": "n != 1", 76 | "type": 1 77 | }, 78 | "aliases": [ 79 | "english", 80 | "en_en", 81 | "base", 82 | "source", 83 | "eng" 84 | ], 85 | "direction": "ltr", 86 | "web_url": "http://127.0.0.1:8000/api/languages/en/", 87 | "url": "http://127.0.0.1:8000/api/languages/en/", 88 | "statistics_url": "http://127.0.0.1:8000/api/languages/en/statistics/" 89 | }, 90 | "template": "", 91 | "translations_url": "http://127.0.0.1:8000/api/components/hello/weblate/translations/", 92 | "url": "http://127.0.0.1:8000/api/components/hello/weblate/", 93 | "vcs": "git", 94 | "web_url": "http://127.0.0.1:8000/projects/hello/weblate/" 95 | } 96 | ] 97 | } 98 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-components--POST--9c51d0bc: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Weblate", 3 | "slug":"weblate", 4 | "id":23, 5 | "project":{ 6 | "name":"Hello", 7 | "slug":"hello", 8 | "id":15, 9 | "web":"http://example.com", 10 | "web_url":"http://example.com/projects/hello/", 11 | "url":"http://127.0.0.1:8000/api/projects/hello/", 12 | "components_list_url":"http://127.0.0.1:8000/api/projects/hello/components/", 13 | "repository_url":"http://127.0.0.1:8000/api/projects/hello/repository/", 14 | "statistics_url":"http://127.0.0.1:8000/api/projects/hello/statistics/", 15 | "changes_list_url":"http://127.0.0.1:8000/api/projects/hello/changes/", 16 | "languages_url":"http://127.0.0.1:8000/api/projects/hello/languages/" 17 | }, 18 | "vcs":"local", 19 | "repo":"local:", 20 | "git_export":"http://example.com/git/hello/weblate/", 21 | "branch":"main", 22 | "push_branch":"", 23 | "filemask":"po/*.po", 24 | "template":"", 25 | "edit_template":true, 26 | "intermediate":"", 27 | "new_base":"", 28 | "file_format":"po", 29 | "license":"", 30 | "license_url":null, 31 | "agreement":"", 32 | "web_url":"http://example.com/projects/hello/weblate/", 33 | "url":"http://127.0.0.1:8000/api/components/hello/weblate/", 34 | "repository_url":"http://127.0.0.1:8000/api/components/hello/weblate/repository/", 35 | "translations_url":"http://127.0.0.1:8000/api/components/hello/weblate/translations/", 36 | "statistics_url":"http://127.0.0.1:8000/api/components/hello/weblate/statistics/", 37 | "source_language": { 38 | "code": "en", 39 | "name": "English", 40 | "plural": { 41 | "id": 82, 42 | "source": 0, 43 | "number": 2, 44 | "formula": "n != 1", 45 | "type": 1 46 | }, 47 | "aliases": [ 48 | "english", 49 | "en_en", 50 | "base", 51 | "source", 52 | "eng" 53 | ], 54 | "direction": "ltr", 55 | "web_url": "http://127.0.0.1:8000/api/languages/en/", 56 | "url": "http://127.0.0.1:8000/api/languages/en/", 57 | "statistics_url": "http://127.0.0.1:8000/api/languages/en/statistics/" 58 | }, 59 | "lock_url":"http://127.0.0.1:8000/api/components/hello/weblate/lock/", 60 | "changes_list_url":"http://127.0.0.1:8000/api/components/hello/weblate/changes/", 61 | "new_lang":"add", 62 | "language_code_style":"", 63 | "push":"", 64 | "check_flags":"", 65 | "priority":100, 66 | "enforced_checks":"[]", 67 | "restricted":false, 68 | "repoweb":"", 69 | "report_source_bugs":"", 70 | "merge_style":"rebase", 71 | "commit_message":"Translated using Weblate ({{ language_name }})\n\nCurrently translated at {{ stats.translated_percent }}% ({{ stats.translated }} of {{ stats.all }} strings)\n\nTranslation: {{ project_name }}/{{ component_name }}\nTranslate-URL: {{ url }}", 72 | "add_message":"Added translation using Weblate ({{ language_name }})\n\n", 73 | "delete_message":"Deleted translation using Weblate ({{ language_name }})\n\n", 74 | "merge_message":"Merge branch '{{ component_remote_branch }}' into Weblate.\n\n", 75 | "addon_message":"Update translation files\n\nUpdated by \"{{ addon_name }}\" hook in Weblate.\n\nTranslation: {{ project_name }}/{{ component_name }}\nTranslate-URL: {{ url }}", 76 | "allow_translation_propagation":true, 77 | "enable_suggestions":true, 78 | "suggestion_voting":false, 79 | "suggestion_autoaccept":0, 80 | "push_on_commit":true, 81 | "commit_pending_age":24, 82 | "auto_lock_error":true, 83 | "language_regex":"^[^.]+$", 84 | "variant_regex":"" 85 | } 86 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-components--POST--c9c26539: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Weblate", 3 | "slug":"weblate", 4 | "id":23, 5 | "project":{ 6 | "name":"Hello", 7 | "slug":"hello", 8 | "id":15, 9 | "web":"http://example.com", 10 | "web_url":"http://example.com/projects/hello/", 11 | "url":"http://127.0.0.1:8000/api/projects/hello/", 12 | "components_list_url":"http://127.0.0.1:8000/api/projects/hello/components/", 13 | "repository_url":"http://127.0.0.1:8000/api/projects/hello/repository/", 14 | "statistics_url":"http://127.0.0.1:8000/api/projects/hello/statistics/", 15 | "changes_list_url":"http://127.0.0.1:8000/api/projects/hello/changes/", 16 | "languages_url":"http://127.0.0.1:8000/api/projects/hello/languages/" 17 | }, 18 | "vcs":"git", 19 | "repo":"file:///home/nijel/work/weblate-hello", 20 | "git_export":"http://example.com/git/hello/weblate/", 21 | "branch":"main", 22 | "push_branch":"", 23 | "filemask":"po/*.po", 24 | "template":"", 25 | "edit_template":true, 26 | "intermediate":"", 27 | "new_base":"", 28 | "file_format":"po", 29 | "license":"", 30 | "license_url":null, 31 | "agreement":"", 32 | "web_url":"http://example.com/projects/hello/weblate/", 33 | "url":"http://127.0.0.1:8000/api/components/hello/weblate/", 34 | "repository_url":"http://127.0.0.1:8000/api/components/hello/weblate/repository/", 35 | "translations_url":"http://127.0.0.1:8000/api/components/hello/weblate/translations/", 36 | "statistics_url":"http://127.0.0.1:8000/api/components/hello/weblate/statistics/", 37 | "source_language": { 38 | "code": "en", 39 | "name": "English", 40 | "plural": { 41 | "id": 82, 42 | "source": 0, 43 | "number": 2, 44 | "formula": "n != 1", 45 | "type": 1 46 | }, 47 | "aliases": [ 48 | "english", 49 | "en_en", 50 | "base", 51 | "source", 52 | "eng" 53 | ], 54 | "direction": "ltr", 55 | "web_url": "http://127.0.0.1:8000/api/languages/en/", 56 | "url": "http://127.0.0.1:8000/api/languages/en/", 57 | "statistics_url": "http://127.0.0.1:8000/api/languages/en/statistics/" 58 | }, 59 | "lock_url":"http://127.0.0.1:8000/api/components/hello/weblate/lock/", 60 | "changes_list_url":"http://127.0.0.1:8000/api/components/hello/weblate/changes/", 61 | "new_lang":"add", 62 | "language_code_style":"", 63 | "push":"", 64 | "check_flags":"", 65 | "priority":100, 66 | "enforced_checks":"[]", 67 | "restricted":false, 68 | "repoweb":"", 69 | "report_source_bugs":"", 70 | "merge_style":"rebase", 71 | "commit_message":"Translated using Weblate ({{ language_name }})\n\nCurrently translated at {{ stats.translated_percent }}% ({{ stats.translated }} of {{ stats.all }} strings)\n\nTranslation: {{ project_name }}/{{ component_name }}\nTranslate-URL: {{ url }}", 72 | "add_message":"Added translation using Weblate ({{ language_name }})\n\n", 73 | "delete_message":"Deleted translation using Weblate ({{ language_name }})\n\n", 74 | "merge_message":"Merge branch '{{ component_remote_branch }}' into Weblate.\n\n", 75 | "addon_message":"Update translation files\n\nUpdated by \"{{ addon_name }}\" hook in Weblate.\n\nTranslation: {{ project_name }}/{{ component_name }}\nTranslate-URL: {{ url }}", 76 | "allow_translation_propagation":true, 77 | "enable_suggestions":true, 78 | "suggestion_voting":false, 79 | "suggestion_autoaccept":0, 80 | "push_on_commit":true, 81 | "commit_pending_age":24, 82 | "auto_lock_error":true, 83 | "language_regex":"^[^.]+$", 84 | "variant_regex":"" 85 | } 86 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-languages: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "total_words": 30, 4 | "code": "cs", 5 | "translated_words": 19, 6 | "language": "čeština", 7 | "translated": 5, 8 | "translated_percent": 62.5, 9 | "total": 8, 10 | "url": "http://127.0.0.1:8000/projects/hello/-/cs/", 11 | "words_percent": 63.3 12 | }, 13 | { 14 | "total_words": 30, 15 | "code": "en", 16 | "translated_words": 15, 17 | "language": "angličtina", 18 | "translated": 4, 19 | "translated_percent": 50.0, 20 | "total": 8, 21 | "url": "http://127.0.0.1:8000/projects/hello/-/en/", 22 | "words_percent": 50.0 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-repository: -------------------------------------------------------------------------------- 1 | { 2 | "needs_commit": false, 3 | "needs_merge": false, 4 | "needs_push": true, 5 | "url": "http://127.0.0.1:8000/api/projects/hello/repository/" 6 | } 7 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-repository--POST--operation=cleanup: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-repository--POST--operation=commit: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-repository--POST--operation=pull: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-repository--POST--operation=push: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "Push is disabled for Hello/Weblate.", 3 | "result": false 4 | } 5 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-repository--POST--operation=reset: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-hello-statistics: -------------------------------------------------------------------------------- 1 | { 2 | "failing": 3, 3 | "failing_percent": 75.0, 4 | "fuzzy": 0, 5 | "fuzzy_percent": 0.0, 6 | "last_change": "2016-03-07T10:20:05.499", 7 | "recent_changes": 123, 8 | "name": "Hello", 9 | "total": 4, 10 | "total_words": 15, 11 | "translated": 4, 12 | "translated_percent": 100.0, 13 | "translated_words": 15, 14 | "url": "http://127.0.0.1:8000/engage/hello/", 15 | "url_translate": "http://127.0.0.1:8000/projects/hello/" 16 | } 17 | -------------------------------------------------------------------------------- /wlc/test_data/api/projects-invalid: -------------------------------------------------------------------------------- 1 | Invalid JSON 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-android-en-units: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": "http://127.0.0.1:8000/api/translations/hello/android/en/units/", 4 | "previous": null, 5 | "results": [ 6 | { 7 | "translation": "http://127.0.0.1:8000/api/translations/hello/android/en/", 8 | "source": "mr", 9 | "previous_source": "", 10 | "target": "mr", 11 | "id_hash": 3515632860997702012, 12 | "content_hash": 3515632860997702012, 13 | "location": "", 14 | "context": "", 15 | "note": "", 16 | "flags": "", 17 | "fuzzy": false, 18 | "translated": true, 19 | "approved": false, 20 | "position": 987, 21 | "has_suggestion": false, 22 | "has_comment": false, 23 | "has_failing_check": false, 24 | "num_words": 1, 25 | "priority": 100, 26 | "id": 35664, 27 | "web_url": "http://127.0.0.1:8000/projects/hello/android/en/", 28 | "url": "http://127.0.0.1:8000/api/units/35664/" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-android-en-units--POST--key=test-monolingual--value=-test-me-: -------------------------------------------------------------------------------- 1 | {"changes_list_url": "http://127.0.0.1:8000/api/translations/hello/android/en/units/en/changes/", 2 | "component": {"branch": "main", 3 | "changes_list_url": "http://127.0.0.1:8000/api/components/hello/android/changes/", 4 | "check_flags": "", 5 | "edit_template": true, 6 | "enforced_checks": "[]", 7 | "file_format": "po-mono", 8 | "filemask": "android/*.po", 9 | "git_export": "http://127.0.0.1:8000/git/hello/android/", 10 | "id": 754, 11 | "license": "", 12 | "license_url": "", 13 | "lock_url": "http://127.0.0.1:8000/api/components/hello/android/lock/", 14 | "name": "Android", 15 | "new_base": "", 16 | "new_lang": "add", 17 | "project": {"changes_list_url": "http://127.0.0.1:8000/api/projects/hello/changes/", 18 | "components_list_url": "http://127.0.0.1:8000/api/projects/hello/components/", 19 | "id": 7, 20 | "languages_url": "http://127.0.0.1:8000/api/projects/hello/languages/", 21 | "name": "Bilingual Vs android", 22 | "repository_url": "http://127.0.0.1:8000/api/projects/hello/repository/", 23 | "slug": "hello", 24 | "source_language": {"aliases": ["english", "en_en", "base", "source", "eng"], 25 | "code": "en", 26 | "direction": "ltr", 27 | "name": "English", 28 | "plural": {"formula": "n != 1", "id": 82, "number": 2, "source": 0, "type": 1}, 29 | "statistics_url": "http://127.0.0.1:8000/api/languages/en/statistics/", 30 | "url": "http://127.0.0.1:8000/api/languages/en/", 31 | "web_url": "http://127.0.0.1:8000/languages/en/"}, 32 | "statistics_url": "http://127.0.0.1:8000/api/projects/hello/statistics/", 33 | "url": "http://127.0.0.1:8000/api/projects/hello/", 34 | "web": "https://bilingual-is-something.com", 35 | "web_url": "http://127.0.0.1:8000/projects/hello/"}, 36 | "push": "file:///home/WeblateOrg/work/weblate-hello", 37 | "repo": "file:///home/WeblateOrg/work/weblate-hello", 38 | "repository_url": "http://127.0.0.1:8000/api/components/hello/android/repository/", 39 | "restricted": false, 40 | "slug": "android", 41 | "statistics_url": "http://127.0.0.1:8000/api/components/hello/android/statistics/", 42 | "template": "android/values/strings.xml", 43 | "translations_url": "http://127.0.0.1:8000/api/components/hello/android/translations/", 44 | "url": "http://127.0.0.1:8000/api/components/hello/android/", 45 | "vcs": "git", 46 | "web_url": "http://127.0.0.1:8000/projects/hello/android/"}, 47 | "failing_checks": 0, 48 | "failing_checks_percent": 0.0, 49 | "failing_checks_words": 0, 50 | "file_url": "http://127.0.0.1:8000/api/translations/hello/android/en/units/en/file/", 51 | "filename": "templates/android_base_file.po", 52 | "fuzzy": 0, 53 | "fuzzy_percent": 0.0, 54 | "fuzzy_words": 0, 55 | "have_comment": 0, 56 | "have_suggestion": 0, 57 | "id": 1646, 58 | "is_source": true, 59 | "is_template": true, 60 | "language": {"aliases": ["english", "en_en", "base", "source", "eng"], 61 | "code": "en", 62 | "direction": "ltr", 63 | "name": "English", 64 | "plural": {"formula": "n != 1", "id": 82, "number": 2, "source": 0, "type": 1}, 65 | "statistics_url": "http://127.0.0.1:8000/api/languages/en/statistics/", 66 | "url": "http://127.0.0.1:8000/api/languages/en/", 67 | "web_url": "http://127.0.0.1:8000/languages/en/"}, 68 | "language_code": "en", 69 | "last_author": "Approver Man", 70 | "last_change": "2020-08-03T11:56:20.257895Z", 71 | "repository_url": "http://127.0.0.1:8000/api/translations/hello/android/en/units/en/repository/", 72 | "revision": "f17c1670cba3ecd805702bb81268beb899a11f14,f17c1670cba3ecd805702bb81268beb899a11f14", 73 | "share_url": "http://127.0.0.1:8000/engage/hello/en/", 74 | "statistics_url": "http://127.0.0.1:8000/api/translations/hello/android/en/units/en/statistics/", 75 | "total": 17, 76 | "total_words": 17, 77 | "translate_url": "http://127.0.0.1:8000/translate/hello/android/en/", 78 | "translated": 17, 79 | "translated_percent": 100.0, 80 | "translated_words": 17, 81 | "units_list_url": "http://127.0.0.1:8000/api/translations/hello/android/en/units/en/units/", 82 | "url": "http://127.0.0.1:8000/api/translations/hello/android/en/units/en/", 83 | "web_url": "http://127.0.0.1:8000/projects/hello/android/en/"} 84 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs: -------------------------------------------------------------------------------- 1 | { 2 | "component": { 3 | "branch": "main", 4 | "file_format": "po", 5 | "filemask": "po/*.po", 6 | "git_export": "", 7 | "license": "", 8 | "license_url": "", 9 | "lock_url": "http://127.0.0.1:8000/api/components/hello/weblate/lock/", 10 | "name": "Weblate", 11 | "new_base": "", 12 | "project": { 13 | "components_list_url": "http://127.0.0.1:8000/api/projects/hello/components/", 14 | "name": "Hello", 15 | "repository_url": "http://127.0.0.1:8000/api/projects/hello/repository/", 16 | "slug": "hello", 17 | "source_language": { 18 | "code": "en", 19 | "direction": "ltr", 20 | "name": "English", 21 | "nplurals": 2, 22 | "pluralequation": "n != 1", 23 | "url": "http://127.0.0.1:8000/api/languages/en/", 24 | "web_url": "http://127.0.0.1:8000/languages/en/" 25 | }, 26 | "url": "http://127.0.0.1:8000/api/projects/hello/", 27 | "web": "http://weblate.org/", 28 | "web_url": "http://127.0.0.1:8000/projects/hello/" 29 | }, 30 | "repo": "file:///home/WeblateOrg/work/weblate-hello", 31 | "repository_url": "http://127.0.0.1:8000/api/components/hello/weblate/repository/", 32 | "slug": "weblate", 33 | "statistics_url": "http://127.0.0.1:8000/api/components/hello/weblate/statistics/", 34 | "template": "", 35 | "translations_url": "http://127.0.0.1:8000/api/components/hello/weblate/translations/", 36 | "url": "http://127.0.0.1:8000/api/components/hello/weblate/", 37 | "vcs": "git", 38 | "web_url": "http://127.0.0.1:8000/projects/hello/weblate/" 39 | }, 40 | "failing_checks": 3, 41 | "failing_checks_percent": 75.0, 42 | "failing_checks_words": 11, 43 | "file_url": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/file/", 44 | "filename": "po/cs.po", 45 | "fuzzy": 0, 46 | "fuzzy_percent": 0.0, 47 | "fuzzy_words": 0, 48 | "have_comment": 0, 49 | "have_suggestion": 0, 50 | "is_template": false, 51 | "language": { 52 | "code": "cs", 53 | "direction": "ltr", 54 | "name": "Czech", 55 | "nplurals": 3, 56 | "pluralequation": "(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2", 57 | "url": "http://127.0.0.1:8000/api/languages/cs/", 58 | "web_url": "http://127.0.0.1:8000/languages/cs/" 59 | }, 60 | "language_code": "cs", 61 | "last_author": "Weblate Admin", 62 | "last_change": "2016-03-07T10:20:05.499", 63 | "repository_url": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/repository/", 64 | "revision": "c0e48e09bd6d1c3a8502176d5f79c9c7e6463653", 65 | "share_url": "http://127.0.0.1:8000/engage/hello/cs/", 66 | "statistics_url": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/statistics/", 67 | "changes_list_url": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/changes/", 68 | "total": 4, 69 | "total_words": 15, 70 | "translate_url": "http://127.0.0.1:8000/translate/hello/weblate/cs/", 71 | "translated": 4, 72 | "translated_percent": 100.0, 73 | "translated_words": 15, 74 | "url": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/", 75 | "web_url": "http://127.0.0.1:8000/projects/hello/weblate/cs/", 76 | "units_list_url": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/units/" 77 | } 78 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs--DELETE--: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeblateOrg/wlc/d67137f8e8774f4088b7ad97cd323cdbb9698eb6/wlc/test_data/api/translations-hello-weblate-cs--DELETE-- -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-changes: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "unit": null, 8 | "component": "http://127.0.0.1:8000/api/components/hello/android/", 9 | "url": "http://127.0.0.1:8000/api/changes/1/", 10 | "translation": null, 11 | "dictionary": null, 12 | "user": null, 13 | "author": null, 14 | "timestamp": "2016-11-18T10:47:01.355911Z", 15 | "action": 20, 16 | "target": "", 17 | "id": 353, 18 | "action_name": "Sloučen repozitář" 19 | }, 20 | { 21 | "unit": "http://127.0.0.1:8000/api/units/227/", 22 | "component": "http://127.0.0.1:8000/api/components/hello/weblate/", 23 | "url": "http://127.0.0.1:8000/api/changes/2/", 24 | "translation": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/", 25 | "dictionary": null, 26 | "user": 2, 27 | "author": 2, 28 | "timestamp": "2016-10-24T07:21:54.121348Z", 29 | "action": 26, 30 | "target": "", 31 | "id": 350, 32 | "action_name": "Odstraněn návrh" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-file: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: Weblate Hello World 2012\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2012-03-14 15:54+0100\n" 6 | "PO-Revision-Date: 2012-03-05 15:55+0100\n" 7 | "Last-Translator: Automatically generated\n" 8 | "Language-Team: none\n" 9 | "Language: cs\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=ASCII\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" 14 | 15 | #: main.c:11 16 | #, c-format 17 | msgid "Hello, world!\n" 18 | msgstr "" 19 | 20 | #: main.c:12 21 | #, c-format 22 | msgid "Orangutan has %d banana.\n" 23 | msgid_plural "Orangutan has %d bananas.\n" 24 | msgstr[0] "" 25 | msgstr[1] "" 26 | msgstr[2] "" 27 | 28 | #: main.c:13 29 | #, c-format 30 | msgid "Try Weblate at !\n" 31 | msgstr "" 32 | 33 | #: main.c:14 34 | msgid "Thank you for using Weblate." 35 | msgstr "" 36 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-file--GET--format=csv: -------------------------------------------------------------------------------- 1 | "location","source","target","id","fuzzy","context","translator_comments","developer_comments" 2 | "main.c:11","Hello, world! 3 | ","fdfd 4 | ","","False","","","" 5 | "main.c:12","Orangutan has %d banana. 6 | ","Opička má %d banán. 7 | ","","False","","","" 8 | "main.c:13","Try Weblate at ! 9 | ","","","False","","","" 10 | "main.c:14","Thank you for using Weblate.","","","False","","","" 11 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-file--POST--3748506a: -------------------------------------------------------------------------------- 1 | {"detail":"Not found."} 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-file--POST--5c6a101f: -------------------------------------------------------------------------------- 1 | {"not_found":1,"skipped":141,"accepted":1,"total":143,"result":true,"count":143} 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-file--POST--85762547: -------------------------------------------------------------------------------- 1 | {"not_found":1,"skipped":142,"accepted":0,"total":143,"result":false,"count":143} 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-file--POST--90a4957d: -------------------------------------------------------------------------------- 1 | {"not_found":2,"skipped":141,"accepted":0,"total":143,"result":false,"count":143} 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-file--POST--f8672b4c: -------------------------------------------------------------------------------- 1 | {"not_found":1,"skipped":141,"accepted":1,"total":143,"result":true,"count":143} 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-file--POST--fe040a0c: -------------------------------------------------------------------------------- 1 | {"not_found":2,"skipped":141,"accepted":0,"total":143,"result":false,"count":143} 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-repository: -------------------------------------------------------------------------------- 1 | { 2 | "merge_failure": null, 3 | "needs_commit": false, 4 | "needs_merge": false, 5 | "needs_push": true, 6 | "remote_commit": { 7 | "author": "Michal \u010ciha\u0159 ", 8 | "author_email": "michal@cihar.com", 9 | "author_name": "Michal \u010ciha\u0159", 10 | "authordate": "2014-11-19T12:50:24+01:00", 11 | "commit": "Michal \u010ciha\u0159 ", 12 | "commit_email": "michal@cihar.com", 13 | "commit_name": "Michal \u010ciha\u0159", 14 | "commitdate": "2014-11-19T12:50:24+01:00", 15 | "message": "Add Arabic\n\nSigned-off-by: Michal \u010ciha\u0159 ", 16 | "revision": "8ba2d7e113dd58a3695d1b196f24e37a7b5bcb80", 17 | "shortrevision": "8ba2d7e", 18 | "summary": "Add Arabic" 19 | }, 20 | "status": "On branch main\nYour branch is ahead of 'origin/main' by 1 commit.\n (use \"git push\" to publish your local commits)\nnothing to commit, working directory clean\n", 21 | "url": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/repository/" 22 | } 23 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-repository--POST--operation=cleanup: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-repository--POST--operation=commit: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-repository--POST--operation=pull: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-repository--POST--operation=push: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "Push is disabled for Hello/Weblate.", 3 | "result": false 4 | } 5 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-repository--POST--operation=reset: -------------------------------------------------------------------------------- 1 | { 2 | "result": true 3 | } 4 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-statistics: -------------------------------------------------------------------------------- 1 | { 2 | "code": "cs", 3 | "failing": 3, 4 | "failing_percent": 75.0, 5 | "fuzzy": 0, 6 | "fuzzy_percent": 0.0, 7 | "last_author": "Weblate Admin", 8 | "last_change": "2016-03-07T10:20:05.499", 9 | "recent_changes": 123, 10 | "name": "Czech", 11 | "total": 4, 12 | "total_words": 15, 13 | "translated": 4, 14 | "translated_percent": 100.0, 15 | "translated_words": 15, 16 | "url": "http://127.0.0.1:8000/engage/hello/cs/", 17 | "url_translate": "http://127.0.0.1:8000/projects/hello/weblate/cs/" 18 | } 19 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-units: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "translation": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/", 8 | "source": "mr", 9 | "previous_source": "", 10 | "target": "mr", 11 | "id_hash": 3515632860997702012, 12 | "content_hash": 3515632860997702012, 13 | "location": "", 14 | "context": "", 15 | "note": "", 16 | "flags": "", 17 | "fuzzy": false, 18 | "translated": true, 19 | "approved": false, 20 | "position": 987, 21 | "has_suggestion": false, 22 | "has_comment": false, 23 | "has_failing_check": false, 24 | "num_words": 1, 25 | "priority": 100, 26 | "id": 35664, 27 | "web_url": "http://127.0.0.1:8000/projects/hello/weblate/cs/", 28 | "url": "http://127.0.0.1:8000/api/units/35664/" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /wlc/test_data/api/translations-hello-weblate-cs-units--GET--q=source%3A%3D%22mr%22: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "translation": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/", 8 | "source": "mr", 9 | "previous_source": "", 10 | "target": "mr", 11 | "id_hash": 3515632860997702012, 12 | "content_hash": 3515632860997702012, 13 | "location": "", 14 | "context": "", 15 | "note": "", 16 | "flags": "", 17 | "fuzzy": false, 18 | "translated": true, 19 | "approved": false, 20 | "position": 987, 21 | "has_suggestion": false, 22 | "has_comment": false, 23 | "has_failing_check": false, 24 | "num_words": 1, 25 | "priority": 100, 26 | "id": 117, 27 | "web_url": "http://127.0.0.1:8000/projects/hello/weblate/cs/", 28 | "url": "http://127.0.0.1:8000/api/units/117/" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /wlc/test_data/api/units: -------------------------------------------------------------------------------- 1 | { 2 | "count": 4, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "translation": "http://127.0.0.1:8080/api/translations/test-project/mono_comp/en/", 8 | "source": [ 9 | "String 'A'" 10 | ], 11 | "previous_source": "", 12 | "target": [ 13 | "String 'A'" 14 | ], 15 | "id_hash": -6306725157533644379, 16 | "content_hash": 6706964138259559415, 17 | "location": "string_a", 18 | "context": "A", 19 | "note": "", 20 | "flags": "", 21 | "state": 20, 22 | "fuzzy": false, 23 | "translated": true, 24 | "approved": false, 25 | "position": 1, 26 | "has_suggestion": false, 27 | "has_comment": false, 28 | "has_failing_check": false, 29 | "num_words": 2, 30 | "source_unit": "http://127.0.0.1:8080/api/units/1/", 31 | "priority": 100, 32 | "id": 1, 33 | "web_url": "http://127.0.0.1:8080/translate/test-project/mono_comp/en/?checksum=287a02e2393009a5", 34 | "url": "http://127.0.0.1:8080/api/units/1/", 35 | "explanation": "", 36 | "extra_flags": "" 37 | }, 38 | { 39 | "translation": "http://127.0.0.1:8080/api/translations/test-project/mono_comp/en/", 40 | "source": [ 41 | "String 'B'" 42 | ], 43 | "previous_source": "", 44 | "target": [ 45 | "String 'B'" 46 | ], 47 | "id_hash": 94239120442185084, 48 | "content_hash": 5871207942681564553, 49 | "location": "string_b", 50 | "context": "B", 51 | "note": "", 52 | "flags": "", 53 | "state": 20, 54 | "fuzzy": false, 55 | "translated": true, 56 | "approved": false, 57 | "position": 2, 58 | "has_suggestion": false, 59 | "has_comment": false, 60 | "has_failing_check": false, 61 | "num_words": 2, 62 | "source_unit": "http://127.0.0.1:8080/api/units/2/", 63 | "priority": 100, 64 | "id": 2, 65 | "web_url": "http://127.0.0.1:8080/translate/test-project/mono_comp/en/?checksum=814ecdfb11e6d57c", 66 | "url": "http://127.0.0.1:8080/api/units/2/", 67 | "explanation": "", 68 | "extra_flags": "" 69 | }, 70 | { 71 | "translation": "http://127.0.0.1:8080/api/translations/hello/weblate/cs/", 72 | "source": [ 73 | "String 'A'" 74 | ], 75 | "previous_source": "", 76 | "target": [ 77 | "foo" 78 | ], 79 | "id_hash": -6306725157533644379, 80 | "content_hash": 6706964138259559415, 81 | "location": "string_a", 82 | "context": "A", 83 | "note": "", 84 | "flags": "", 85 | "state": 30, 86 | "fuzzy": false, 87 | "translated": true, 88 | "approved": true, 89 | "position": 1, 90 | "has_suggestion": false, 91 | "has_comment": false, 92 | "has_failing_check": false, 93 | "num_words": 2, 94 | "source_unit": "http://127.0.0.1:8080/api/units/1/", 95 | "priority": 100, 96 | "id": 3, 97 | "web_url": "http://127.0.0.1:8080/translate/hello/weblate/cs/?checksum=287a02e2393009a5", 98 | "url": "http://127.0.0.1:8080/api/units/3/", 99 | "explanation": "", 100 | "extra_flags": "" 101 | }, 102 | { 103 | "translation": "http://127.0.0.1:8080/api/translations/hello/weblate/cs/", 104 | "source": [ 105 | "String 'B'" 106 | ], 107 | "previous_source": "", 108 | "target": [ 109 | "" 110 | ], 111 | "id_hash": 94239120442185084, 112 | "content_hash": 5871207942681564553, 113 | "location": "string_b", 114 | "context": "B", 115 | "note": "", 116 | "flags": "", 117 | "state": 0, 118 | "fuzzy": false, 119 | "translated": false, 120 | "approved": false, 121 | "position": 2, 122 | "has_suggestion": false, 123 | "has_comment": false, 124 | "has_failing_check": false, 125 | "num_words": 2, 126 | "source_unit": "http://127.0.0.1:8080/api/units/2/", 127 | "priority": 100, 128 | "id": 4, 129 | "web_url": "http://127.0.0.1:8080/translate/hello/weblate/cs/?checksum=814ecdfb11e6d57c", 130 | "url": "http://127.0.0.1:8080/api/units/4/", 131 | "explanation": "", 132 | "extra_flags": "" 133 | } 134 | ] 135 | } 136 | -------------------------------------------------------------------------------- /wlc/test_data/api/units-123: -------------------------------------------------------------------------------- 1 | { 2 | "translation": "http://127.0.0.1:8000/api/translations/hello/weblate/cs/", 3 | "source": [ 4 | "String 'B'" 5 | ], 6 | "previous_source": "", 7 | "target": [ 8 | "B" 9 | ], 10 | "id_hash": 94239120442185084, 11 | "content_hash": 5871207942681564553, 12 | "location": "string_b", 13 | "context": "B", 14 | "note": "", 15 | "flags": "", 16 | "state": 0, 17 | "fuzzy": false, 18 | "translated": false, 19 | "approved": false, 20 | "position": 2, 21 | "has_suggestion": false, 22 | "has_comment": false, 23 | "has_failing_check": false, 24 | "num_words": 2, 25 | "source_unit": "http://127.0.0.1:8000/api/units/1/", 26 | "priority": 100, 27 | "id": 123, 28 | "web_url": "http://127.0.0.1:8000/translate/hello/weblate/cs/?checksum=814ecdfb11e6d57c", 29 | "url": "http://127.0.0.1:8000/api/units/123/", 30 | "explanation": "", 31 | "extra_flags": "" 32 | } 33 | -------------------------------------------------------------------------------- /wlc/test_data/api/units-123--DELETE--: -------------------------------------------------------------------------------- 1 | --deleted-- 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/units-123--PATCH--target=-foo---state=30: -------------------------------------------------------------------------------- 1 | --patched-- 2 | -------------------------------------------------------------------------------- /wlc/test_data/api/units-123--PUT--target=-foo---state=30: -------------------------------------------------------------------------------- 1 | --put-- 2 | -------------------------------------------------------------------------------- /wlc/test_data/mock/project-local-file.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Content-Type: text/plain; charset=utf-8\n" 4 | "Plural-Forms: nplurals = 2; plural = (n != 1);\n" 5 | "Language: en\n" 6 | "mime-version: 1.0\n" 7 | "Content-Transfer-Encoding: 8bit\n" 8 | 9 | 10 | #: src/pages/about/impact.tsx:22 11 | msgctxt "Title" 12 | msgid "How to make a better world" 13 | msgstr "" 14 | 15 | #: src/pages/about/impact.tsx:28 16 | msgctxt "Title" 17 | msgid "Community informations" 18 | msgstr "" 19 | -------------------------------------------------------------------------------- /wlc/test_data/section: -------------------------------------------------------------------------------- 1 | [custom] 2 | url = https://example.net/ 3 | -------------------------------------------------------------------------------- /wlc/test_data/weblate.ini: -------------------------------------------------------------------------------- 1 | [weblate] 2 | url = http://127.0.0.1:8000/api/ 3 | key = KEY 4 | -------------------------------------------------------------------------------- /wlc/test_data/wlc: -------------------------------------------------------------------------------- 1 | [weblate] 2 | url = https://example.net/ 3 | retries = 999 4 | method_whitelist = PUT,POST 5 | backoff_factor = 0.2 6 | status_forcelist = 429,500,502,503,504 7 | timeout = 30 8 | 9 | [withkey] 10 | url = http://127.0.0.1:8000/api/ 11 | key = KEY 12 | 13 | [withcomponent] 14 | url = http://127.0.0.1:8000/api/ 15 | translation = hello/weblate 16 | 17 | [withproject] 18 | url = http://127.0.0.1:8000/api/ 19 | translation = hello 20 | -------------------------------------------------------------------------------- /wlc/test_main.py: -------------------------------------------------------------------------------- 1 | # Copyright © Michal Čihař 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | """Test command-line interface.""" 6 | 7 | from __future__ import annotations 8 | 9 | import json 10 | import os 11 | import sys 12 | from io import BytesIO, StringIO, TextIOWrapper 13 | from tempfile import NamedTemporaryFile, TemporaryDirectory 14 | 15 | import wlc 16 | from wlc.config import WeblateConfig 17 | from wlc.main import Version, main 18 | 19 | from .test_base import APITest 20 | 21 | TEST_DATA = os.path.join(os.path.dirname(__file__), "test_data") 22 | TEST_CONFIG = os.path.join(TEST_DATA, "wlc") 23 | TEST_SECTION = os.path.join(TEST_DATA, "section") 24 | 25 | 26 | class CLITestBase(APITest): 27 | def execute(self, args, settings=None, stdout=None, stdin=None, expected=0): 28 | """Execute command and return output.""" 29 | if settings is None: 30 | settings = () 31 | elif not settings: 32 | settings = None 33 | output = StringIO() 34 | output.buffer = BytesIO() 35 | backup = sys.stdout 36 | backup_err = sys.stderr 37 | try: 38 | sys.stdout = output 39 | sys.stderr = output 40 | if stdout: 41 | stdout = output 42 | result = main(args=args, settings=settings, stdout=stdout, stdin=stdin) 43 | self.assertEqual(result, expected) 44 | finally: 45 | sys.stdout = backup 46 | sys.stderr = backup_err 47 | result = output.buffer.getvalue() 48 | if result: 49 | return result 50 | return output.getvalue() 51 | 52 | 53 | class TestSettings(CLITestBase): 54 | """Test settings handling.""" 55 | 56 | def test_commandline(self): 57 | """Configuration using command-line.""" 58 | output = self.execute(["--url", "https://example.net/", "list-projects"]) 59 | self.assertIn("Hello", output) 60 | 61 | def test_stdout(self): 62 | """Configuration using params.""" 63 | output = self.execute(["list-projects"], stdout=True) 64 | self.assertIn("Hello", output) 65 | 66 | def test_debug(self): 67 | """Debug mode.""" 68 | output = self.execute(["--debug", "list-projects"], stdout=True) 69 | self.assertIn("api/projects", output) 70 | 71 | def test_settings(self): 72 | """Configuration using settings param.""" 73 | output = self.execute( 74 | ["list-projects"], settings=(("weblate", "url", "https://example.net/"),) 75 | ) 76 | self.assertIn("Hello", output) 77 | 78 | def test_config(self): 79 | """Configuration using custom config file.""" 80 | output = self.execute( 81 | ["--config", TEST_CONFIG, "list-projects"], settings=False 82 | ) 83 | self.assertIn("Hello", output) 84 | 85 | def test_config_section(self): 86 | """Configuration using custom config file section.""" 87 | output = self.execute( 88 | ["--config", TEST_SECTION, "--config-section", "custom", "list-projects"], 89 | settings=False, 90 | ) 91 | self.assertIn("Hello", output) 92 | 93 | def test_config_key(self): 94 | """Configuration using custom config file section and key set.""" 95 | output = self.execute( 96 | ["--config", TEST_CONFIG, "--config-section", "withkey", "show", "acl"], 97 | settings=False, 98 | ) 99 | self.assertIn("ACL", output) 100 | 101 | def test_config_appdata(self): 102 | """Configuration using custom config file section and key set.""" 103 | output = self.execute(["show", "acl"], settings=False, expected=1) 104 | try: 105 | os.environ["APPDATA"] = TEST_DATA 106 | output = self.execute(["show", "acl"], settings=False) 107 | self.assertIn("ACL", output) 108 | finally: 109 | del os.environ["APPDATA"] 110 | 111 | def test_config_cwd(self): 112 | """Test loading settings from current dir.""" 113 | current = os.path.abspath(".") 114 | try: 115 | os.chdir(os.path.join(os.path.dirname(__file__), "test_data")) 116 | output = self.execute(["show"], settings=False) 117 | self.assertIn("Weblate", output) 118 | finally: 119 | os.chdir(current) 120 | 121 | def test_default_config_values(self): 122 | """Test default parser values.""" 123 | config = WeblateConfig() 124 | self.assertEqual(config.get("weblate", "key"), "") 125 | self.assertEqual(config.get("weblate", "retries"), 0) 126 | self.assertEqual(config.get("weblate", "timeout"), 300) 127 | self.assertEqual( 128 | config.get("weblate", "method_whitelist"), 129 | "HEAD\nTRACE\nDELETE\nOPTIONS\nPUT\nGET", 130 | ) 131 | self.assertEqual(config.get("weblate", "backoff_factor"), 0) 132 | self.assertIsNone(config.get("weblate", "status_forcelist")) 133 | 134 | def test_parsing(self): 135 | """Test config file parsing.""" 136 | config = WeblateConfig() 137 | self.assertEqual(config.get("weblate", "url"), wlc.API_URL) 138 | config.load() 139 | config.load(TEST_CONFIG) 140 | self.assertEqual(config.get("weblate", "url"), "https://example.net/") 141 | self.assertEqual(config.get("weblate", "retries"), "999") 142 | self.assertEqual(config.get("weblate", "method_whitelist"), "PUT,POST") 143 | self.assertEqual(config.get("weblate", "backoff_factor"), "0.2") 144 | self.assertEqual( 145 | config.get("weblate", "status_forcelist"), "429,500,502,503,504" 146 | ) 147 | 148 | def test_get_request_options(self): 149 | """Test the get_request_options method when all options are in config.""" 150 | config = WeblateConfig() 151 | config.load() 152 | config.load(TEST_CONFIG) 153 | ( 154 | retries, 155 | status_forcelist, 156 | method_whitelist, 157 | backoff_factor, 158 | _timeout, 159 | ) = config.get_request_options() 160 | self.assertEqual(retries, 999) 161 | self.assertEqual(status_forcelist, [429, 500, 502, 503, 504]) 162 | self.assertEqual(method_whitelist, ["PUT", "POST"]) 163 | self.assertEqual(backoff_factor, 0.2) 164 | 165 | def test_argv(self): 166 | """Test sys.argv processing.""" 167 | backup = sys.argv 168 | try: 169 | sys.argv = ["wlc", "version"] 170 | output = self.execute(None) 171 | self.assertIn(f"version: {wlc.__version__}", output) 172 | finally: 173 | sys.argv = backup 174 | 175 | 176 | class TestOutput(CLITestBase): 177 | """Test output formatting.""" 178 | 179 | def test_version_text(self): 180 | """Test version printing.""" 181 | output = self.execute(["--format", "text", "version"]) 182 | self.assertIn(f"version: {wlc.__version__}", output) 183 | 184 | def test_version_json(self): 185 | """Test version printing.""" 186 | output = self.execute(["--format", "json", "version"]) 187 | values = json.loads(output) 188 | self.assertEqual({"version": wlc.__version__}, values) 189 | 190 | def test_version_csv(self): 191 | """Test version printing.""" 192 | output = self.execute(["--format", "csv", "version"]) 193 | self.assertIn(f"version,{wlc.__version__}", output) 194 | 195 | def test_version_html(self): 196 | """Test version printing.""" 197 | output = self.execute(["--format", "html", "version"]) 198 | self.assertIn(wlc.__version__, output) 199 | 200 | def test_projects_text(self): 201 | """Test projects printing.""" 202 | output = self.execute(["--format", "text", "list-projects"]) 203 | self.assertIn("name: {}".format("Hello"), output) 204 | 205 | def test_projects_json(self): 206 | """Test projects printing.""" 207 | output = self.execute(["--format", "json", "list-projects"]) 208 | values = json.loads(output) 209 | self.assertEqual(2, len(values)) 210 | 211 | def test_projects_csv(self): 212 | """Test projects printing.""" 213 | output = self.execute(["--format", "csv", "list-projects"]) 214 | self.assertIn("Hello", output) 215 | 216 | def test_projects_html(self): 217 | """Test projects printing.""" 218 | output = self.execute(["--format", "html", "list-projects"]) 219 | self.assertIn("Hello", output) 220 | 221 | def test_json_encoder(self): 222 | """Test JSON encoder.""" 223 | output = StringIO() 224 | cmd = Version(args=[], config=WeblateConfig(), stdout=output) 225 | with self.assertRaises(TypeError): 226 | cmd.print_json(self) 227 | 228 | 229 | class TestCommands(CLITestBase): 230 | """Individual command tests.""" 231 | 232 | def test_version_bare(self): 233 | """Test version printing.""" 234 | output = self.execute(["version", "--bare"]) 235 | self.assertEqual(f"{wlc.__version__}\n", output) 236 | 237 | def test_ls(self): 238 | """Project listing.""" 239 | output = self.execute(["ls"]) 240 | self.assertIn("Hello", output) 241 | output = self.execute(["ls", "hello"]) 242 | self.assertIn("Weblate", output) 243 | output = self.execute(["ls", "empty"]) 244 | self.assertEqual("", output) 245 | 246 | def test_list_languages(self): 247 | """Language listing.""" 248 | output = self.execute(["list-languages"]) 249 | self.assertIn("Turkish", output) 250 | 251 | def test_list_projects(self): 252 | """Project listing.""" 253 | output = self.execute(["list-projects"]) 254 | self.assertIn("Hello", output) 255 | 256 | def test_list_components(self): 257 | """Components listing.""" 258 | output = self.execute(["list-components"]) 259 | self.assertIn("/hello/weblate", output) 260 | 261 | output = self.execute(["list-components", "hello"]) 262 | self.assertIn("/hello/weblate", output) 263 | 264 | output = self.execute(["list-components", "hello/weblate"], expected=1) 265 | self.assertIn("Not supported", output) 266 | 267 | def test_list_translations(self): 268 | """Translations listing.""" 269 | output = self.execute(["list-translations"]) 270 | self.assertIn("/hello/weblate/cs/", output) 271 | 272 | output = self.execute(["list-translations", "hello/weblate"]) 273 | self.assertIn("/hello/weblate/cs/", output) 274 | 275 | output = self.execute(["list-translations", "hello/weblate"]) 276 | self.assertIn("/hello/weblate/cs/", output) 277 | 278 | output = self.execute( 279 | ["--format", "json", "list-translations", "hello/weblate"] 280 | ) 281 | self.assertIn("/hello/weblate/cs/", output) 282 | 283 | def test_show(self): 284 | """Project show.""" 285 | output = self.execute(["show", "hello"]) 286 | self.assertIn("Hello", output) 287 | 288 | output = self.execute(["show", "hello/weblate"]) 289 | self.assertIn("Weblate", output) 290 | 291 | output = self.execute(["show", "hello/weblate/cs"]) 292 | self.assertIn("/hello/weblate/cs/", output) 293 | 294 | def test_show_error(self): 295 | self.execute(["show", "io"], expected=10) 296 | with self.assertRaises(FileNotFoundError): 297 | self.execute(["show", "bug"]) 298 | 299 | def test_delete(self): 300 | """Project delete.""" 301 | output = self.execute(["delete", "hello"]) 302 | self.assertEqual("", output) 303 | 304 | output = self.execute(["delete", "hello/weblate"]) 305 | self.assertEqual("", output) 306 | 307 | output = self.execute(["delete", "hello/weblate/cs"]) 308 | self.assertEqual("", output) 309 | 310 | def test_commit(self): 311 | """Project commit.""" 312 | output = self.execute(["commit", "hello"]) 313 | self.assertEqual("", output) 314 | 315 | output = self.execute(["commit", "hello/weblate"]) 316 | self.assertEqual("", output) 317 | 318 | output = self.execute(["commit", "hello/weblate/cs"]) 319 | self.assertEqual("", output) 320 | 321 | def test_push(self): 322 | """Project push.""" 323 | msg = "Error: Failed to push changes!\nPush is disabled for Hello/Weblate.\n" 324 | output = self.execute(["push", "hello"], expected=1) 325 | self.assertEqual(msg, output) 326 | 327 | output = self.execute(["push", "hello/weblate"], expected=1) 328 | self.assertEqual(msg, output) 329 | 330 | output = self.execute(["push", "hello/weblate/cs"], expected=1) 331 | self.assertEqual(msg, output) 332 | 333 | def test_pull(self): 334 | """Project pull.""" 335 | output = self.execute(["pull", "hello"]) 336 | self.assertEqual("", output) 337 | 338 | output = self.execute(["pull", "hello/weblate"]) 339 | self.assertEqual("", output) 340 | 341 | output = self.execute(["pull", "hello/weblate/cs"]) 342 | self.assertEqual("", output) 343 | 344 | def test_reset(self): 345 | """Project reset.""" 346 | output = self.execute(["reset", "hello"]) 347 | self.assertEqual("", output) 348 | 349 | output = self.execute(["reset", "hello/weblate"]) 350 | self.assertEqual("", output) 351 | 352 | output = self.execute(["reset", "hello/weblate/cs"]) 353 | self.assertEqual("", output) 354 | 355 | def test_cleanup(self): 356 | """Project cleanup.""" 357 | output = self.execute(["cleanup", "hello"]) 358 | self.assertEqual("", output) 359 | 360 | output = self.execute(["cleanup", "hello/weblate"]) 361 | self.assertEqual("", output) 362 | 363 | output = self.execute(["cleanup", "hello/weblate/cs"]) 364 | self.assertEqual("", output) 365 | 366 | def test_repo(self): 367 | """Project repo.""" 368 | output = self.execute(["repo", "hello"]) 369 | self.assertIn("needs_commit", output) 370 | 371 | output = self.execute(["repo", "hello/weblate"]) 372 | self.assertIn("needs_commit", output) 373 | 374 | output = self.execute(["repo", "hello/weblate/cs"]) 375 | self.assertIn("needs_commit", output) 376 | 377 | def test_stats(self): 378 | """Project stats.""" 379 | output = self.execute(["stats", "hello"]) 380 | self.assertIn("translated_percent", output) 381 | 382 | output = self.execute(["stats", "hello/weblate"]) 383 | self.assertIn("failing_percent", output) 384 | 385 | output = self.execute(["stats", "hello/weblate/cs"]) 386 | self.assertIn("failing_percent", output) 387 | 388 | def test_locks(self): 389 | """Project locks.""" 390 | output = self.execute(["lock-status", "hello"], expected=1) 391 | self.assertIn("This command is supported only at component level", output) 392 | 393 | output = self.execute(["lock-status", "hello/weblate"]) 394 | self.assertIn("locked", output) 395 | output = self.execute(["lock", "hello/weblate"]) 396 | self.assertEqual("", output) 397 | output = self.execute(["unlock", "hello/weblate"]) 398 | self.assertEqual("", output) 399 | 400 | output = self.execute(["lock-status", "hello/weblate/cs"], expected=1) 401 | self.assertIn("This command is supported only at component level", output) 402 | 403 | def test_changes(self): 404 | """Project changes.""" 405 | output = self.execute(["changes", "hello"]) 406 | self.assertIn("action_name", output) 407 | 408 | output = self.execute(["changes", "hello/weblate"]) 409 | self.assertIn("action_name", output) 410 | 411 | output = self.execute(["changes", "hello/weblate/cs"]) 412 | self.assertIn("action_name", output) 413 | 414 | def test_download(self): 415 | """Translation file downloads.""" 416 | output = self.execute(["download"], expected=1) 417 | self.assertIn("Output is needed", output) 418 | 419 | with TemporaryDirectory() as tmpdirname: 420 | self.execute(["download", "--output", tmpdirname]) 421 | 422 | output = self.execute(["download", "hello/weblate/cs"]) 423 | self.assertIn(b"Plural-Forms:", output) 424 | 425 | output = self.execute(["download", "hello/weblate/cs", "--convert", "csv"]) 426 | self.assertIn(b'"location"', output) 427 | 428 | with NamedTemporaryFile() as handle: 429 | handle.close() 430 | self.execute(["download", "hello/weblate/cs", "-o", handle.name]) 431 | with open(handle.name, "rb") as tmp: 432 | output = tmp.read() 433 | self.assertIn(b"Plural-Forms:", output) 434 | 435 | output = self.execute(["download", "hello/weblate"], expected=1) 436 | self.assertIn("Output is needed", output) 437 | 438 | with TemporaryDirectory() as tmpdirname: 439 | self.execute(["download", "hello/weblate", "--output", tmpdirname]) 440 | 441 | output = self.execute(["download", "hello"], expected=1) 442 | self.assertIn("Output is needed", output) 443 | 444 | with TemporaryDirectory() as tmpdirname: 445 | self.execute(["download", "hello", "--no-glossary", "--output", tmpdirname]) 446 | # The hello-android should not be present as it is flagged as a glossary 447 | self.assertEqual(os.listdir(tmpdirname), ["hello-weblate.zip"]) 448 | 449 | with TemporaryDirectory() as tmpdirname: 450 | self.execute( 451 | [ 452 | "download", 453 | "hello", 454 | "--convert", 455 | "zip", 456 | "--output", 457 | os.path.join(tmpdirname, "output"), 458 | ] 459 | ) 460 | self.assertEqual(os.listdir(tmpdirname), ["output"]) 461 | 462 | def test_download_config(self): 463 | with TemporaryDirectory() as tmpdirname: 464 | self.execute( 465 | [ 466 | "--config", 467 | TEST_CONFIG, 468 | "--config-section", 469 | "withcomponent", 470 | "download", 471 | "--output", 472 | tmpdirname, 473 | ], 474 | settings=False, 475 | ) 476 | self.assertEqual(os.listdir(tmpdirname), ["hello-weblate.zip"]) 477 | with TemporaryDirectory() as tmpdirname: 478 | self.execute( 479 | [ 480 | "--config", 481 | TEST_CONFIG, 482 | "--config-section", 483 | "withproject", 484 | "download", 485 | "--output", 486 | tmpdirname, 487 | ], 488 | settings=False, 489 | ) 490 | self.assertEqual( 491 | set(os.listdir(tmpdirname)), {"hello-weblate.zip", "hello-android.zip"} 492 | ) 493 | 494 | def test_upload(self): 495 | """Translation file uploads.""" 496 | msg = "Error: Failed to upload translations!\nNot found.\n" 497 | 498 | output = self.execute(["upload", "hello/weblate"], expected=1) 499 | self.assertEqual( 500 | "Error: This command is supported only at translation level\n", output 501 | ) 502 | 503 | with self.get_text_io_wrapper("test upload data") as stdin: 504 | output = self.execute(["upload", "hello/weblate/cs"], stdin=stdin) 505 | self.assertEqual("", output) 506 | 507 | with self.get_text_io_wrapper("wrong upload data") as stdin: 508 | output = self.execute( 509 | ["upload", "hello/weblate/cs"], stdin=stdin, expected=1 510 | ) 511 | self.assertEqual(msg, output) 512 | 513 | with NamedTemporaryFile(delete=False) as handle: 514 | handle.write(b"test upload overwrite") 515 | handle.close() 516 | output = self.execute( 517 | ["upload", "hello/weblate/cs", "-i", handle.name, "--overwrite"] 518 | ) 519 | self.assertEqual("", output) 520 | 521 | @staticmethod 522 | def get_text_io_wrapper(string): 523 | """Create a text io wrapper from a string.""" 524 | return TextIOWrapper(BytesIO(string.encode()), "utf8") 525 | 526 | 527 | class TestErrors(CLITestBase): 528 | """Error handling tests.""" 529 | 530 | def test_commandline_missing_key(self): 531 | """Configuration using command-line.""" 532 | output = self.execute( 533 | ["--url", "http://denied.example.com", "list-projects"], expected=1 534 | ) 535 | self.assertIn("Missing API key", output) 536 | 537 | def test_commandline_wrong_key(self): 538 | """Configuration using command-line.""" 539 | output = self.execute( 540 | ["--key", "x", "--url", "http://denied.example.com", "list-projects"], 541 | expected=1, 542 | ) 543 | self.assertIn("was rejected by server", output) 544 | --------------------------------------------------------------------------------