Internationalization (i18n)

Treat the source language as “developer” English, using the language code en_US. Then, use Transifex to translate the “developer” English to “proper” English, using the language code en.

Note

If you are using the source language as “proper” English, then replace en_US with en in the commands and templates below.

Reference: Non-English as a Source Language

Mark strings to translate

When using Django, use its translation functions and template tags. Otherwise, use gettext.gettext().

Configure message extraction

When using Django, this step is already done. Otherwise, create a babel.cfg file, for example:

[python: **.py]
[jinja2: **.html]

Add the Transifex mapping

See Adding resources in bulk.

Note

For reference, the equivalent with the old Python Transifex Client, replacing TXPROJECT and APP, was:

tx config mapping -r TXPROJECT.django -f APP/locale/en_US/LC_MESSAGES/django.po -s en_US -t PO 'APP/locale/<lang>/LC_MESSAGES/django.po'

Translate with Transifex

Whenever text in the interface is added or updated, you must extract the strings to translate from the code files into PO files by running:

Django
django-admin makemessages -l en_US --no-obsolete
Python (example)
pybabel extract -F babel.cfg -o messages.pot .
pybabel update -i messages.pot -d locale

Then, push the PO files to Transifex with:

tx push -s

If you made local changes to translations, push the translations to Transifex. For example:

tx push -t -l en

When ready, pull the translations from Transifex with:

tx pull -f -a

Then, compile the PO files to MO files with:

Django
python manage.py compilemessages
Python (example)
pybabel compile -f -d locale

Reference: Django Translation

Continuous integration

Repositories that support multiple locales should test that translation files are complete. To do that, the workflow extracts messages, updates catalogs, and then counts incomplete translations.

Note

These workflows only run on push for the main branch, so that feature branches don’t fail until a PR is created.

Create a .github/workflows/i18n.yml file.

For a Django application, use the following. Change the python-version to match the version used to compile the requirements_dev.txt file.

name: Translations
on:
  pull_request: {}
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '{{ cookiecutter.python_version }}'
          cache: pip
          cache-dependency-path: '**/requirements*.txt'
      - name: Install translate-toolkit
        run: |
          sudo apt update
          sudo apt install gettext translate-toolkit
      - run: pip install -r requirements.txt
      - run: python manage.py makemessages -a
      - name: Count incomplete translations
        shell: bash
        run: |
          output=$(find . -name LC_MESSAGES -not -path "*/en_US/*" -exec pocount --incomplete --short "{}" +)
          echo $output
          [ "$output" = "" ]

For a Babel project, adapt:

name: Translations
on:
  pull_request: {}
  push:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.10'
      - name: Install translate-toolkit
        run: |
          sudo apt update
          sudo apt install translate-toolkit
      - run: pip install babel
      - name: Update catalogs
        run: |
          pybabel extract -F babel.cfg -o locale/sphinx.pot -k '_ l_ lazy_gettext' .
          pybabel update -i locale/sphinx.pot -d locale -D sphinx
      - name: Count incomplete translations
        shell: bash
        run: |
          output=$(find . -name LC_MESSAGES -exec pocount --incomplete --short "{}" +)
          echo $output
          [ "$output" = "" ]

Warning

If your default branch is not named main, edit the push: key.

Note

If you use the jinja2 extractor, install jinja2 with pip.