Skip to content

CI Integration

TranslateBot includes a check_translations management command that verifies all .po files are fully translated with no fuzzy entries. It exits with code 1 on failure, making it ideal for CI pipelines.

The check_translations Command

python manage.py check_translations

This scans all .po files across your project (LOCALE_PATHS, app locale directories, and the locale/ directory in BASE_DIR when LOCALE_PATHS is empty) and reports:

  • Untranslated entries - strings with an empty msgstr
  • Fuzzy entries - strings flagged for review by makemessages, or translation drafts that failed validation

If any issues are found, the command exits with code 1 and prints the details to stderr.

The --makemessages Flag

In CI, you typically want to ensure .po files are in sync with your source code before checking. The --makemessages flag runs makemessages -a --no-obsolete automatically before checking:

python manage.py check_translations --makemessages

This single command replaces the two-step workflow of running makemessages then check_translations separately.

Note

The --makemessages flag requires gettext to be installed on your system. See the CI examples below for how to install it.

GitHub Actions

.github/workflows/ci.yml
jobs:
  translations:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
        with:
          enable-cache: true
      - run: uv python install
      - run: uv sync --frozen
      - name: Install gettext
        run: sudo apt-get update && sudo apt-get install -y --no-install-recommends gettext
      - name: Check translations
        run: uv run python manage.py check_translations --makemessages

Or if you prefer to run makemessages as a separate step:

      - name: Extract messages
        run: uv run python manage.py makemessages -a --no-obsolete
      - name: Check translations
        run: uv run python manage.py check_translations

GitLab CI

.gitlab-ci.yml
check-translations:
  image: python:3.13
  before_script:
    - apt-get update && apt-get install -y --no-install-recommends gettext
    - pip install uv && uv sync --frozen
  script:
    - uv run python manage.py check_translations --makemessages

What It Catches

When a developer adds a new translatable string but forgets to translate it, the CI check will fail:

/app/locale/de/LC_MESSAGES/django.po: 2 untranslated, 0 fuzzy
/app/locale/nl/LC_MESSAGES/django.po: 0 untranslated, 1 fuzzy
CommandError: Translation check failed

Typical Workflow

  1. Developer adds new strings with {% trans %} or gettext()
  2. CI runs check_translations --makemessages
  3. CI fails because the new strings are untranslated
  4. Developer runs python manage.py translate to translate them
  5. CI passes

This ensures translations never fall out of sync with your source code.

Translating in CI

You can also let CI do step 4: run translate and commit the result. Store your provider's API key as a repository secret; TRANSLATEBOT_API_KEY is read from the environment when it isn't set in Django settings.

.github/workflows/translate.yml
on:
  workflow_dispatch:

jobs:
  translate:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --frozen
      - name: Install gettext
        run: sudo apt-get update && sudo apt-get install -y --no-install-recommends gettext
      - run: uv run python manage.py makemessages -a --no-obsolete
      - name: Translate
        env:
          TRANSLATEBOT_API_KEY: ${{ secrets.TRANSLATEBOT_API_KEY }}
        run: uv run python manage.py translate
      - name: Commit translations
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add -A -- '*.po'
          git diff --cached --quiet || git commit -m "chore: update translations"
          git push

Tip

Run translate --dry-run in pull requests to see what would be translated without spending API credits. A dry run still requires TRANSLATEBOT_API_KEY to be set, but any value works since no API call is made.