Translating a Django project usually goes like this: makemessages, opening each .po, copying the msgid into a translator or ChatGPT, pasting the msgstr one by one, praying that no %(name)s breaks and running compilemessages. And repeating it with every new text.
In this guide we do it with a single command, python manage.py translate, using the translate-projects-django package. Everything you see below (terminal and .po files) is the real output of a run on a test project.
What you need
- Python 3.8 or higher and Django 3.2 or higher.
- GNU gettext (
xgettextandmsgfmt), the same as for any translation in Django:sudo apt-get install gettextorbrew install gettext. - A free Translate Projects account with a project and its API token ([Getting started](https://doc.translateprojects.dev/docs/primeros-pasos?utm_source=blog&utm_medium=articulo&utm_campaign=blog-django)).
- The AI API key of your provider (Claude, OpenAI, DeepSeek or Gemini). There are no plans or credit card: you only pay the tokens you use with your provider.
1. Install the package
pip install translate-projects-django
2. Configure settings.py
import os
INSTALLED_APPS = [
# ...
"translate_projects_django",
]
MIDDLEWARE = [
# ...
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
# ...
]
USE_I18N = True
LANGUAGE_CODE = "es" # idioma base
LANGUAGES = [
("es", "Español"),
("en", "English"),
("pt-br", "Português (Brasil)"),
("fr", "Français"),
]
LOCALE_PATHS = [BASE_DIR / "locale"]
TRANSLATE_PROJECTS_API_KEY = os.environ.get("TRANSLATE_PROJECTS_API_KEY")
Django codes (pt-br, zh-hans) are converted automatically to those of the API. LANGUAGE_CODE is your base language and is never translated.
3. Mark your texts as usual
Nothing you already do in Django changes: gettext in Python and {% translate %} in the templates.
# views.py (ejemplo)
from django.utils.translation import gettext as _
def home(request):
message = _("Cerrar sesión")
{# templates/home.html (ejemplo) #}
{% load i18n %}
<h1>{% translate "Bienvenido a tu panel" %}</h1>
<p>{% blocktranslate %}Hola {{ name }}, tienes mensajes nuevos{% endblocktranslate %}</p>
4. Translate
export TRANSLATE_PROJECTS_API_KEY=tu_token
python manage.py translate
Real output (6 texts to English, Portuguese and French):
[05:19:47 PM] 🎯 Start Generate Translations
📂 6 texts found in es
[05:19:48 PM] ✅ Sources unchanged, upload skipped
📝 Translating (en) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
📝 Translating (pt-br) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
📝 Translating (fr) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
[05:19:50 PM] ⏱ extract 0.0s · upload 0.4s · compile 0.0s · total 2.5s · 7 HTTP requests
en: 6 filled, 0 missing, 0 rejected · translate 1.4s · download 0.4s ·
write 0.0s
pt-br: 6 filled, 0 missing, 0 rejected · translate 1.6s · download 0.1s ·
fr: 6 filled, 0 missing, 0 rejected · translate 1.8s · download 0.3s ·
🎉 Translations completed ✅
Two and a half seconds in total. The command runs makemessages, translates up to 3 languages in parallel, writes the .po files and finishes with compilemessages. This is how the French looks:
# locale/fr/LC_MESSAGES/django.po
#: demo/templates/demo.html:2
msgid "Bienvenido a tu panel"
msgstr "Bienvenue sur votre tableau de bord"
#: demo/templates/demo.html:3
#, python-format
msgid "Hola %(name)s, tienes mensajes nuevos"
msgstr "Bonjour %(name)s, vous avez de nouveaux messages"
#: demo/templates/demo.html:4
msgid "<b>Guardar</b> cambios"
msgstr "<b>Enregistrer</b> les modifications"
#: demo/templates/demo.html:5
#, python-format
msgid "100 %% gratis"
msgstr "100 %% gratuit"
#: demo/views.py:3
msgid "Cerrar sesión"
msgstr "Se déconnecter"
#: demo/views.py:3
msgid "Configuración de la cuenta"
msgstr "Paramètres du compte"
Files it generates
locale/
├── en/LC_MESSAGES/django.po ← y su django.mo
├── pt_BR/LC_MESSAGES/django.po
├── fr/LC_MESSAGES/django.po
└── .translate-projects.json ← caché
Commit the .po files and .translate-projects.json so that other machines and your CI skip the languages that are already up to date.
Useful commands
python manage.py translate --locale en # solo un idioma
python manage.py translate --force # descarga todo otra vez
- A second run without new texts makes a single request and skips the complete languages: what is already translated doesn't spend tokens.
- Don't edit the
.pofiles by hand: they are rebuilt on every run. Edit the translation in the panel and run with--force. - Current limitation: plurals (
ngettext,blocktranslate count) are not translated yet and stay in the base language.
Common errors
- TRANSLATE_PROJECTS_API_KEY is not set in settings.py: export the variable and read it in
settings.py. - The source language is not the default:
LANGUAGE_CODEmust be the project's default language. - MISSING_API_KEY: configure provider, model and AI key in the project.
- CommandError: Can't find msguniq o xgettext: GNU gettext is missing.
The full guide is available at doc.translateprojects.dev. Create your project for free and translate your Django project today.