You shipped Spanish first. Now product wants Brazil live next sprint, and someone says you can just translate Spanish to Portuguese because the languages are close.
That's how teams end up with broken placeholders, wrong market terminology, and UI copy that feels off the moment a Brazilian user reads it. In Django, the failure is rarely one bad sentence. It's a process bug. Strings drift, .po files get edited by hand, HTML gets mangled, and nobody knows which terminology is canonical.
The fix isn't “pick a better translator.” The fix is to treat localization like code. Your source strings live in Git. Your translations live in Git. A glossary lives in Git. CI updates locale files, and reviewers inspect diffs before merge.
Meta description: Translate Spanish to Portuguese in Django without copy-paste. Build a Git-based workflow with .po files, glossary rules, CI, and review.
The Pitfalls of Translating Spanish to Portuguese
A Spanish Django app moving into Portugal and Brazil hits a trap fast. Spanish and Portuguese look close enough that teams assume direct conversion will hold. It won't.
A lot of “translate Spanish to Portuguese” tooling often assumes Brazilian Portuguese. That default shows up in consumer translation flows, but it skips the decision your team needs to make: are you shipping pt-BR or pt-PT? The distinction matters for product copy, support text, legal wording, and user trust. Translate.com's Spanish to Portuguese Brazil flow reflects that pt-BR default, which is exactly why the variant choice needs to be explicit.

Similar words still break production copy
The risky part isn't only grammar. It's operational language. Billing, onboarding, consent screens, support states, and account settings all depend on stable terminology.
Practical rule: lexical similarity is not a release strategy.
You also need to remember the history here. A foundational milestone in Spanish-to-Portuguese research came in 2008, when Aziz et al. published statistical machine translation work between European Spanish and Brazilian Portuguese, trained on a small parallel corpus. Their preliminary result was that statistical methods were comparable to a rule-based system while requiring considerably less effort to develop, which made the language pair an important early test case for later multilingual workflows in software and localization (Aziz et al.).
The market question comes before the tool question
If you need broad vendor context before choosing process, reviews of best multilingual translation services are useful because they force you to compare workflow fit, not just language dropdowns.
For Django teams, the bigger problem is that generic machine translation doesn't understand your message context. A button label, an email subject, and a validation error can share one English idea and still need different Portuguese wording. If you want a quick sanity check on why generic tools often miss that nuance, the write-up on Google Translate accuracy in production use is worth reading.
What usually goes wrong
- Wrong locale target: you generate
ptcopy when the business neededpt-BR. - Literal carryover: strings look familiar but don't match how your market speaks.
- UI context loss: short labels have no surrounding context, so translations drift.
- Template breakage: placeholders or inline HTML get changed by hand.
Building a Repeatable Translation Workflow in Django
Before any model touches your copy, lock down the workflow.
Your source of truth should be Django message catalogs. That means extracting strings with makemessages and committing the resulting files under the locale layout:
python manage.py makemessages --locale=es
python manage.py makemessages --locale=pt_BR
A healthy tree looks like this:
locale/
├── es/
│ └── LC_MESSAGES/
│ └── django.po
└── pt_BR/
└── LC_MESSAGES/
└── django.po
Keep translators away from template syntax
Django strings aren't plain sentences. They carry format variables and markup that must survive unchanged.
#: billing/templates/billing/summary.html:12
#, python-format
msgid "Hola %(name)s, your plan renews on %(date)s."
msgstr ""
#: accounts/templates/accounts/delete.html:8
msgid "Delete <strong>%(project_name)s</strong>?"
msgstr ""
If a workflow doesn't preserve %(name)s, %(date)s, and <strong>...</strong>, it's not safe for production. That's true whether you use a CLI, a hosted platform, or something lightweight like RewriteBar's AI translator for ad hoc checks.
Treat translations like code
That gives you a repeatable path:
| Step | What you commit | Why it matters |
|---|---|---|
| Extraction | updated .po files |
new msgids are visible in diffs |
| Translation | filled msgstr values |
changes stay auditable |
| Review | pull request comments | terminology and syntax get checked |
| Build | compiled .mo files at deploy |
runtime uses current catalogs |
A
.pofile is not content inventory. It's part of your codebase.
A few rules save pain later:
- One locale per market: use
pt_BRorpt_PT, not ambiguous folder names. - Context where needed: use
pgettextwhen one source string has multiple meanings. - No hand-editing in random tools: if it doesn't round-trip to Git cleanly, skip it.
Automating Translation with a Management Command
A common failure case looks like this. A developer adds new Spanish strings on Friday, someone pastes a few entries into a browser translator, and Monday's deploy ships a pt_BR catalog with missing entries, inconsistent terminology, and placeholders that no longer match the source. The fix is to run translation the same way you run migrations or tests. In the repo, with one command, and with output that survives code review.
One option is TranslateBot, a Django package that translates locale files through a management command instead of a manual copy-and-paste step.

Install and configure
pip install translatebot-django
Keep provider credentials in environment variables and load them in settings.py:
import os
TRANSLATEBOT_PROVIDER = "openai"
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
If Spanish is your source locale, create the Portuguese catalog before translating:
python manage.py makemessages --locale=pt_BR
Then run the translation command against the extracted strings:
python manage.py translate --source-lang=es --target-lang=pt_BR
If you want the command in the full Django flow, from extraction to compilation, see this command-based Django translation workflow.
What the command should do
The command should update only empty msgstr values or entries whose msgid changed. That keeps pull requests small and avoids paying to translate the same stable strings again.
In practice, that means your locale/pt_BR/LC_MESSAGES/django.po diff stays readable:
#: accounts/views.py:44
#, python-format
msgid "Hola %(name)s"
msgstr "Olá %(name)s"
#: billing/forms.py:18
msgid "Suscripción cancelada"
msgstr "Assinatura cancelada"
That behavior matters more than convenience. Rewriting the whole file on every run creates noisy diffs, hides actual copy changes, and makes reviewers miss broken entries.
I also recommend wrapping the command in a project-level script so the team runs the same sequence every time:
python manage.py makemessages --locale=pt_BR
python manage.py translate --source-lang=es --target-lang=pt_BR
python manage.py compilemessages
For larger projects, put that in make translate or a CI job instead of relying on memory.
What fails in real projects
- Pasting
.pocontent into chat windows - Editing generated translations directly in production branches
- Running translation before
makemessages - Using a generic
pttarget when your release is Brazil-only
Those shortcuts create bugs that are hard to spot in English-only review. A management command fixes the repeatability problem first. Then Git can do its job.
Ensuring Consistency with a Glossary File
Teams usually notice glossary problems after launch. Support tickets come in because one screen says one thing and the invoice email says another.
The common offender is a product term with multiple valid translations. Spanish source copy like suscripción, factura, or plan can land differently depending on context. If your app mixes financial, legal, and SaaS language, the model will often pick different Portuguese terms across files unless you give it stable instructions.

Put terminology rules in version control
A plain TRANSLATING.md file at the project root is enough.
# Translation rules
Target locale: pt-BR
Terminology
- "suscripción" -> "assinatura"
- "plan gratuito" -> "plano grátis"
- "factura" -> "fatura"
- Keep product name "Acme Cloud" untranslated
Style
- Use informal second person only where the existing product voice already does so
- Prefer concise UI labels over literal sentence mirroring
- Preserve all placeholders and HTML tags
That file does two jobs. It guides the model, and it documents decisions for future reviewers. When someone asks why one billing term was chosen over another, you point to Git history instead of memory.
Glossary control beats literal substitution
Expert guidance on technical translation is consistent here. Quality improves through structured workflows and glossary-based terminology control, not literal word substitution. Process controls matter because they keep terminology stable across projects and reviews (LatinoBridge on translation process controls).
Here's the kind of drift you want to prevent:
| Spanish source | Bad mixed output | Preferred pt-BR |
|---|---|---|
| Suscripción activa | Inscrição ativa | Assinatura ativa |
| Descargar factura | Baixar nota fiscal | Baixar fatura |
| Cambiar plan | Alterar programa | Alterar plano |
Write glossary rules for business nouns first. Brand, billing, permissions, and support language cause the most visible inconsistency.
For jargon-heavy products, the same discipline matters outside billing too. If your app contains sales or operations language, the examples in business jargon translation for AI-assisted workflows are a useful pattern to copy into your own glossary file.
Review the glossary like code
Don't hide glossary rules in a wiki.
- Commit it with app code: reviewers see terminology changes in the same PR.
- Keep locale-specific notes:
pt-BRandpt-PTshouldn't share one vague rule set. - Update it after disputes: every reviewer comment that repeats twice belongs in the glossary.
Running Translations in Your CI Pipeline
If developers have to remember a translation command locally, someone will forget. Then your branch lands with fresh Spanish msgids and stale Portuguese catalogs.
Put the translation step in CI and keep the output in the pull request. That turns localization into a normal code review problem.

A GitHub Actions workflow you can drop in
name: Update pt-BR translations
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
translate:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Check out PR branch
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install gettext
run: |
sudo apt-get update
sudo apt-get install -y gettext
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Extract messages
run: |
python manage.py makemessages --locale=es
python manage.py makemessages --locale=pt_BR
- name: Translate new strings
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
python manage.py translate --source-lang=es --target-lang=pt_BR
- name: Commit locale changes
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add locale/es/LC_MESSAGES/django.po locale/pt_BR/LC_MESSAGES/django.po
git diff --cached --quiet || git commit -m "Update pt-BR translations"
- name: Push changes
run: |
git push
A short product demo helps if you're wiring this up for the first time:
What to enforce in CI
- Fail on dirty catalogs: if
makemessageschanges files, commit them. - Keep locale names explicit:
pt_BRin code, paths, and review comments. - Review generated diffs in PRs: never hide translation changes in deploy artifacts.
The point isn't full autonomy. It's predictable automation. Developers write code. CI extracts strings, updates Portuguese catalogs, and pushes a diff back to the branch.
Reviewing and Merging AI-Generated Translations
Once the PR includes locale changes, review it like any other generated code. You don't need to be fluent in Portuguese to catch the failures that matter.
Start with syntax safety. Check placeholders, HTML, and plural blocks. Then check the glossary terms you've declared as fixed. If a billing noun changed unexpectedly, reject the diff and update the glossary before rerunning.
#: templates/account/welcome.html:7
#, python-format
msgid "Hola %(name)s, tienes %(count)s proyecto activo."
msgstr "Olá %(name)s, você tem %(count)s projeto ativo."
That kind of diff is easy to inspect in GitHub. You're not reviewing prose in the abstract. You're reviewing code-adjacent artifacts with context.
Use the back-translation mindset
In regulated contexts, the more reliable workflow isn't one-way translation. It uses back-translation and independent review to catch semantic drift, and Stanford's translation methodology notes that one-way translation is the most widely used but also the most unreliable because it lacks that extra review layer (Stanford Geriatrics translation methodology).
A non-expert review is still valuable if it catches drift, broken variables, or terminology violations.
That principle applies cleanly in engineering. One person runs the automated translation. Another reviews the diff. If your team already thinks this way for generated code or infrastructure plans, the same habit transfers well. The discipline is similar to comparing AI agent frameworks for production, where the hard part isn't generating output. It's making output reviewable, reproducible, and safe to merge.
What to run before deploy
- Compile catalogs:
python manage.py compilemessages - Smoke-test affected pages: forms, emails, billing, onboarding
- Verify locale routing: make sure users receive
pt-BR - Merge only reviewed diffs: generated content still needs human sign-off
If your team is tired of browser-based translation workflows and copy-pasting .po files around, TranslateBot is worth a look. It plugs into manage.py, writes back to your locale files, preserves placeholders and HTML, and fits the Git and CI workflow you already use for Django.