Back to blog

Django: Translate Spanish to Portuguese Automatically

2026-06-06 10 min read
Django: Translate Spanish to Portuguese Automatically

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.

A contemplative young man looking at his laptop screen between speech bubbles labeled Spanish and Portuguese.

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

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 .po file is not content inventory. It's part of your codebase.

A few rules save pain later:

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.

Screenshot from https://translatebot.dev

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

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.

A robot analyzing various international invoice terminology displayed in a glossary book with a human hand pointing.

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.

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.

Screenshot from https://translatebot.dev

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

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


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.

Stop editing .po files manually

TranslateBot automates Django translations with AI. One command, all your languages, pennies per translation.