If you're a Django developer shipping a multi-language app, you know the feeling. The moment you run makemessages and open a fresh .po file, your heart sinks a little. Web page localization isn't just swapping words; it’s adapting your app for a new culture. But the default workflow, manually copy-pasting strings into Google Translate, is a slow process that grinds development to a halt.
It's a huge bottleneck. And it's keeping you from a massive global audience.
Why Manual Web Page Localization Fails

The internet is global, but most websites feel like they were built for one city. As a solo developer or a small team, your time is everything. Wasting it on the grunt work of .po file management is a great way to fall behind.
The numbers are stark. Over 5 billion people are online, but less than 26% of them are native English speakers. Yet a staggering 64% of all websites are English-only. This creates a huge gap between the services we build and the people who want to use them. Research backs this up: one Harvard Business review report found that 90% of users will always choose a site in their native language if it's an option. You can see more surprising data on localization statistics and trends from POEditor.
The Scaling Problem with Manual Workflows
The manual approach to web page localization breaks down with modern, agile development. Every time you run makemessages, you trigger a painful, repetitive dance:
- Open the new
django.pofile. - Copy an untranslated
msgid. - Paste it into a tool like Google Translate or DeepL.
- Copy the translation back into the
msgstrfield. - Repeat this hundreds of times, for every new string, across every single language you support.
I’ve been there. You get into a rhythm, then you accidentally break a template variable like %(name)s or forget a closing </a> tag. You lose all context, which leads to awkward, nonsensical translations. Worst of all, it feels like a chore that lives completely outside your normal dev flow, slowing down every single release.
Here's a quick look at how the old way compares to an automated approach.
Manual Translation vs. Automated CLI Workflow
| Task | Manual Method (e.g., Google Translate) | Automated CLI (e.g., TranslateBot) |
|---|---|---|
| New Strings | Manually copy-paste each one | One command translates all new strings |
| Placeholders | Prone to breaking (%(name)s, HTML) |
Preserved automatically |
| Consistency | Relies on memory or a messy spreadsheet | Managed with a version-controlled TRANSLATING.md |
| Time | Hours per language, per sprint | Seconds or minutes |
| Integration | Outside the dev workflow | Integrated into the terminal and CI/CD |
The difference is clear. One is a bottleneck, the other is a build step.
For small teams, this manual process becomes a significant drag on productivity. It turns localization from a growth strategy into a technical debt that accumulates with every new feature.
A Developer-First Alternative
SaaS platforms like Crowdin or Transifex exist. They’re powerful, but they often feel like bringing a sledgehammer to crack a nut. They pull you out of your editor into a separate web portal, come with monthly subscription fees, and add complexity you might not need. Paying $150/month per seat for a tool you only touch once a sprint is a tough pill to swallow for most of us.
What we really need is a developer-first approach. Something that lives in the terminal, plays nicely with gettext, respects Git, and automates the pain away without sacrificing control.
This guide will show you exactly how to build that: an automated, AI-powered translation pipeline right inside your Django project.
Setting Up Your Django Project For Automation

Alright, let's get this set up. Integrating an automated translation tool should feel like adding any other dev dependency, not like onboarding a whole new enterprise service. This means a quick install and a simple configuration that lives right inside your project.
You can grab TranslateBot with your package manager of choice. It’s a standard Python package with no strange system dependencies.
- For pip users:
pip install django-translate-bot - For poetry users:
poetry add django-translate-bot - For uv users:
uv pip install django-translate-bot
Once it's installed, the tool becomes available as a new Django management command. Now we just need to give it an API key.
Initial Configuration and API Keys
TranslateBot needs an API key to talk to your chosen AI provider, whether that's OpenAI, DeepL, or another service. Don't hardcode keys in your settings.py. The standard and much safer practice is to use a .env file.
If you aren't already, you can use python-decouple to manage environment variables.
pip install python-decouple
Next, create a .env file in your project's root directory, right next to manage.py. Add your API key there, and, most importantly, add .env to your .gitignore file. You don't want to accidentally commit your secrets.
# .env file
OPENAI_API_KEY="sk-..."
Now you can wire it up in settings.py. This is where you tell TranslateBot which AI model to use and where to find the key.
# settings.py
from decouple import config
# ... other settings
TRANSLATE_BOT = {
"model_name": "gpt-4o", # Or "gpt-3.5-turbo", "deepl", etc.
"model_api_key": config("OPENAI_API_KEY"),
}
That's it. This small block connects the tool to your translation provider. For a full list of settings and all the supported models, check the Quickstart documentation.
Creating Your First Glossary
One of the most common ways machine translation fails is with brand names and technical jargon. How do you stop "TranslateBot" from becoming "Robot de traduction" in French? Or ensure a term like "pull request" is handled correctly across languages?
You solve this with a TRANSLATING.md file. It's a plain Markdown file that acts as a version-controlled set of instructions for the AI. You create it in your project root, and it immediately becomes the single source of truth for your app's specific vocabulary.
The structure is simple: just Markdown headings and lists.
Your
TRANSLATING.mdis more than a word list. It’s a set of instructions that gives the AI the context it needs to translate your app correctly, preventing common and embarrassing mistakes.
Here’s a basic example for this very project:
# General Instructions
- TranslateBot is a brand name. Never translate it.
- The source code is in American English. Please translate to formal [target_language_name].
## Terminology
- `pull request`: Keep as "pull request" in English. It's a technical term our users understand.
- `.po file`: Keep as ".po file" in English.
- `user`: Translate as "utilisateur" in French, but as "usuario" in Spanish.
The AI reads this file before every translation run. This makes sure that even if you swap out AI providers or upgrade to a new model in 2026, the core terminology of your app stays consistent. It's a powerful way to guide the AI without having to manually review every string for brand compliance.
With the package installed, keys configured, and a basic glossary in place, your project is ready for automation. You can run makemessages like you always have. Then you're just one command away from getting everything translated. We’ll get to that next.
Running Your First AI Translation
The setup is done. You’ve installed TranslateBot, plugged in your API keys, and sketched out a basic TRANSLATING.md glossary. You've run makemessages, and now your .po files are full of empty msgstr fields. This is usually the part where you'd sigh, grab a coffee, and settle in for hours of copy-pasting.
Not anymore.
With TranslateBot, you replace that entire tedious session with a single command. Just run:
python manage.py translate_bot
That’s it. The tool scans your locale directories, finds every untranslated string in your .po files, and feeds them to the AI model you configured in settings.py.
Preserving Placeholders and HTML Without Breaking Your Templates
One of the biggest fears with any automated translation tool is that it will mangle your templates. We've all seen it happen: a tool turns Hello, %(name)s! into Bonjour, % (nom) s!, and suddenly your views are throwing ValueError exceptions in production. This is where TranslateBot is obsessive about getting it right.
It’s been built with specific safeguards to protect Django's template syntax.
- Named placeholders like
%(name)sare preserved exactly as they are. - Numbered placeholders like
{0}or{count}are kept perfectly intact. - Simple format specifiers like
%sand%dare also protected. - HTML tags like
<strong>or<a href="...">are handled correctly, ensuring your translated content keeps its styling and structure.
This screenshot shows a django.po file before and after the command runs.

Look familiar? The empty msgstr is now filled in, turning an untranslated entry into a ready-to-use one. This happens for every single empty string, across all your language files, all at once. For more detail on how the AI handles different formats, check out our guide on model translation.
Only Translating What's New
Re-translating your entire project every time you run the command would be a colossal waste of API credits and time. TranslateBot is smarter than that. It intelligently finds only the strings that actually need attention.
It specifically targets:
- Empty
msgstrentries: These are the brand-new strings from your latestmakemessagesrun. - Fuzzy strings: Entries marked with
#, fuzzythatgettextthinks need a human review after amsgidchanged.
By focusing only on this delta, the whole process is incredibly fast and cheap. If you add five new strings to your templates, TranslateBot will only process and bill you for those five strings. This is what makes this kind of web page localization so effective for small, fast-moving projects.
You aren't paying a monthly subscription for some big platform. You're paying pennies for the exact number of new strings you've added since your last deployment. This completely changes the economics of localization for indie developers and small teams.
This one-command translation is the core of the whole workflow. It bridges the gap between generating strings with makemessages and shipping them with compilemessages. It automates the tedious, error-prone part in the middle, letting you get back to writing code instead of managing translations.
Next, we'll look at how to review these AI-generated translations just like you would any other code change.
Reviewing Translations and Managing Your Glossary
Just because a bot is doing the translating doesn’t mean you’re flying blind. This developer-centric workflow gives you more transparent control than any external SaaS platform. When TranslateBot writes to your .po files, it’s just modifying code files in your repository. Nothing more.
This means you review AI-generated translations the same way you review any other code change: in a pull request. This is a huge win over SaaS platforms that hide your translation history behind a web portal. With TranslateBot, every single change is a Git commit.
Developer-Centric Review in a Pull Request
The review process is simple because it’s already your daily routine. You run the translate_bot command, it updates the files, and you open a PR.
The diff view in GitHub, GitLab, or your editor tells the whole story.
# locale/fr/LC_MESSAGES/django.po
#: templates/base.html:15
msgid "Dashboard"
- msgstr ""
+ msgstr "Tableau de bord"
#: templates/base.html:20
msgid "Settings"
- msgstr ""
+ msgstr "Paramètres"
#: templates/profile.html:5
msgid "Update Profile"
- msgstr ""
+ msgstr "Mettre à jour le profil"
Any developer on your team can understand this diff instantly. You can see the new msgstr values and approve them. If a teammate speaks the language, they can comment on a specific line just like any other code review. The entire history of your web page localization lives right inside your version control system.
When your translations live in Git, they get all the benefits of code. You get history, blame, and a transparent review process. You can even revert a bad batch of translations with a single
git revertcommand.
This is a fundamental shift away from black-box SaaS tools. You own your data. You control the workflow.
Evolving Your Glossary for Better Translations
Your TRANSLATING.md file isn't a "set it and forget it" document. It’s a living part of your codebase that should evolve with your application. As you add features or refine your product's voice, you'll update the glossary to keep the AI on track. It becomes the single source of truth for your project's terminology.
Let's say the first translation for "Submit" came back as "Soumettre" in French. But after seeing it in context, your team decides "Envoyer" (to send) is a much better fit for sending a message.
You have two jobs:
- Fix the current translation: Manually edit the
django.pofile and change "Soumettre" to "Envoyer". - Prevent future mistakes: Update your
TRANSLATING.mdfile.
You’d add a new instruction to your glossary to guide all future runs.
# TRANSLATING.md
## Terminology
- `Submit`: When used on a button for sending messages, translate as "Envoyer" in French.
Done. The next time you run translate_bot, the AI has this context. It knows your preference for "Envoyer" and will use it, ensuring consistency across the entire app. This simple feedback loop is what makes AI-powered localization so practical.
This version-controlled glossary helps maintain consistency over years of development. To see more advanced examples and best practices for building an effective glossary, you can read our guide on managing glossary terms. Maintaining a good glossary is the most effective way to improve the quality of your automated web page localization efforts over time.
Integrating Localization Into Your CI/CD Pipeline
Reviewing translations in a pull request is a good first step, but it’s still a manual process someone has to remember to kick off. The real goal is to make localization a completely hands-off part of your deployment pipeline.
This is how you turn web page localization from a recurring chore into a predictable, automated background process.
The endgame is simple: a developer merges a feature, and the translations just happen. No one needs to run a command or remember a script. You just code, commit, and merge. Your CI/CD service handles the rest.
This is the secret to how a small team can manage a multilingual app without hiring a dedicated localization manager. It’s not about adding more work; it’s about building a system that eliminates it.
Automating Translations with GitHub Actions
Let's walk through a practical workflow using GitHub Actions. The core logic is the same whether you use GitLab CI, CircleCI, or another provider. This workflow will trigger on every push to your main branch, run makemessages, execute TranslateBot, and then automatically create a pull request with the updated .po files.
You can save the following YAML as .github/workflows/translate.yml in your project. It’s ready to use.
# .github/workflows/translate.yml
name: Translate PO Files
on:
push:
branches:
- main
jobs:
translate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Find new strings
run: python manage.py makemessages --all
- name: Translate new strings
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: python manage.py translate_bot
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
commit-message: 'i18n: Update translations'
title: 'i18n: Automated Translation Updates'
body: |
This PR was automatically generated by the TranslateBot GitHub Action.
It contains new translations for recently added strings.
branch: 'i18n/auto-translations'
labels: 'i18n'
This workflow sets up the environment, finds any new strings that need translating, runs the TranslateBot command, and then uses a common action to package the changes into a new PR.
| Step Name | Command / Action | Purpose |
|---|---|---|
| Checkout code | actions/checkout@v4 |
Checks out your repository's code into the runner. |
| Set up Python | actions/setup-python@v5 |
Installs your specified Python version. |
| Install dependencies | pip install -r requirements.txt |
Installs Django, TranslateBot, and other project needs. |
| Find new strings | python manage.py makemessages --all |
Scans code for new translatable strings and updates .po files. |
| Translate new strings | python manage.py translate_bot |
Sends only the new strings to your AI provider for translation. |
| Create Pull Request | peter-evans/create-pull-request@v6 |
Bundles the modified .po files into a PR for review. |
This simple file automates the entire translation review process. The moment code with new strings hits main, a PR with the translations appears, ready for a quick sanity check before you merge.

The real beauty of this setup is that every step happens inside your existing Git workflow. Translations become transparent, version-controlled artifacts, just like your code.
Why This Automation Matters for Growth
Automating localization is far more than a developer convenience, it’s a direct lever for growth. Consider that 76% of customers prefer buying products with information in their native language.
Studies consistently show that 60% of shoppers rarely or never buy from English-only websites. This automation is how you bridge that gap without slowing down your development cycle. The impact on marketing is just as significant, with localized ad campaigns outperforming English-only versions by 86%. You can dig into more data on how this drives sales in this detailed website localization guide.
By making web page localization a zero-effort, automated step in your CI pipeline, you ensure your app is always ready for a global audience. You ship features, and the translations follow.
This hands-off approach closes the loop. You’ve gone from the manual misery of copy-pasting strings into a spreadsheet to a fully automated system that detects new content, translates it with context, and presents it for a standard code review.
It's the most practical and cost-effective way for a Django developer to build and maintain a truly multilingual application. Now you can get back to focusing on features, confident that your localization work will always keep pace.
Frequently Asked Questions
You've seen the setup and the automated workflow. But I know what you're thinking, because I've been there myself. There are always those lingering "but what about..." questions that pop up when you're considering a new tool. Let's tackle some of the most common ones Django developers ask about this kind of automated localization.
How does the AI handle context for ambiguous strings?
This is the big one, right? An AI's biggest weakness is a lack of real-world context. With this workflow, the model gets context in a couple of clever ways.
First, when TranslateBot sends a string for translation, it also includes a few of the surrounding strings from the .po file. This often gives the AI just enough of a clue. For example, if it sees "Username" and "Password" in the same batch, it knows that "Login" is a verb, not a noun.
More importantly, you give it explicit instructions in your TRANSLATING.md file. Think about a word like 'Check'. Is it a verb? A noun? A checkbox label? You can tell the AI exactly what you mean: 'Translate "Check" as a noun meaning a financial instrument'. Your glossary is the most powerful tool you have for turning a generic AI into a specialist that speaks your product's language.
Your glossary is what delivers accurate, consistent results. It transforms a general-purpose AI into an expert on your app's specific vocabulary.
What are the real costs compared to a SaaS like Lokalise?
Let's talk money, because it matters. A SaaS platform like Lokalise or Transifex usually involves a recurring subscription fee, often priced per user. You might be looking at $50-$150 per month for a small team, and you pay that whether you translate five new strings or five thousand.
TranslateBot is open-source, so there's no subscription. Your only direct cost is the API usage for the AI model you pick, like GPT-4o or DeepL. Translating a single string costs a tiny fraction of a cent.
Imagine you're pushing features and add 50 new strings this month. Your total cost might be less than $1 in API calls. It’s a pay-as-you-go model that’s fundamentally cheaper and more transparent, which is exactly what you want on a lean team.
Can I use a source language other than English?
Yes. While most Django projects default to English (en) as the source, the whole gettext system and TranslateBot are language-agnostic. Your source strings are just the msgid entries in your base .po file, whatever language they happen to be in.
If your project's primary language is German (de), the process is identical. You'd run makemessages to create files for your target languages, say French (fr) and Spanish (es). TranslateBot will then read the German msgid strings and generate the correct French and Spanish translations for the msgstr fields. The workflow doesn't change at all.
What if the AI makes a mistake?
It will. No AI is perfect. The key is that fixing mistakes is not only easy but also directly improves all future translations.
Since the output is just a standard .po file in your repository, you fix errors right in your code editor. Just find the faulty msgstr entry and correct it. This is a huge win compared to hunting for a string in some disconnected web UI.
To make the fix permanent, you update your TRANSLATING.md glossary. Add a new rule that gives the correct context or translation for that specific term. Then commit both the .po file correction and the glossary update. Unlike a fix buried in a proprietary database, your correction is now version-controlled, transparent, and helps "train" the AI for every future run.
TranslateBot makes powerful, automated web page localization practical for every Django developer. Stop wasting time on manual translation and start shipping faster. Get started at https://translatebot.dev.