Meta description: Translating a PDF breaks most Django i18n workflows. Here's a file-based computer assisted translation pdf process that keeps strings reviewable and safe.
Your app ships in multiple languages. makemessages runs in CI. compilemessages catches broken plural forms before deploy. Then someone drops a PDF manual into Slack and says, “Can we localize this too?”
That's where the clean Django workflow falls apart.
A PDF isn't a source format you can feed into gettext. It's usually a final artifact. You can diff a .po file. You can review a template change. You can't do much with a binary blob full of layout instructions, embedded fonts, weird line breaks, and sometimes no real text layer at all. If you try to handle it with copy-paste and a shared doc, you lose version control, context, and any chance of running the same QA checks you trust for your app.
The Problem with Translating PDFs
The painful part isn't translation itself. It's that PDFs sit outside the tooling Django developers already trust.
Your normal app flow looks something like this:
django-admin makemessages --all
django-admin compilemessages
You get deterministic inputs and reviewable outputs. A PDF breaks that because makemessages has nothing to parse inside docs/getting-started.pdf.
Why PDFs break your usual workflow
A PDF often contains one or more of these problems:
- Binary packaging: Git can tell you the file changed, but not what text changed.
- Layout-first structure: Paragraphs may be split into text boxes, columns, or positioned fragments.
- Missing text layer: Scanned PDFs are images, not selectable text.
- Unsafe markup loss: Variables, code samples, and inline formatting often get mangled when copied by hand.
That last one is where bugs creep in. The PDF might contain a sentence like this:
Use <code>{{ account_id }}</code> in your support request.
Or this:
msgid "Hello %(name)s, your export is ready."
msgstr ""
If a manual workflow turns %(name)s into % ( name ) s, or strips braces from {{ account_id }}, the translated document stops being trustworthy. In app code, that can break rendering. In documentation, it can produce wrong instructions that support has to clean up later.
Practical rule: Treat PDF content as source text that must be extracted, normalized, and reviewed before anyone translates it.
The hidden cost is process drift
Teams usually react in one of three bad ways.
One path is pure copy-paste. Someone opens the PDF, dumps text into Google Docs, sends it to a contractor, then manually rebuilds the file later.
Another path is buying a platform built for localization teams, then forcing engineers to work around it.
The third is doing nothing at all. The app is localized, but the guide, report, invoice PDF, or onboarding packet stays English-only.
None of those fit a healthy Django release process. If your codebase is already built around locale/<lang>/LC_MESSAGES/django.po, you want the PDF content to end up in the same review loop. That doesn't mean pretending the PDF is a template. It means extracting its text and converting it into something your existing i18n stack can manage.
How Computer Assisted Translation Unlocks PDF Content
Computer assisted translation pdf work is easier to reason about if you stop thinking in vendor terms and think like an engineer. It's a parsing pipeline.
Historically, CAT evolved from a niche aid into mainstream localization infrastructure built around segmentation, reusable bilingual memory, and controlled reuse across documents, as described in the Journal of the American Society for Information Science and Technology paper on machine translation and translation workflows. That model maps well to PDFs because the task isn't simply to “translate document,” but rather to “extract text, split it into units, preserve structure, then reuse prior translations where safe.”

What the software actually does
A workable pipeline usually follows this order:
Ingest the PDF
Start with the original file, ideally generated from a real source document instead of a scan.Extract text
If the PDF has a text layer, parse that. If it doesn't, OCR is the fallback.Segment content
Break the extracted text into translation units. Sentences work best for reuse. Headings and callouts often need separate treatment.Apply memory and machine assistance
Repeated warnings, labels, and product terms should reuse prior approved translations.Review and reassemble
Put translated text back into a generated document, or feed it into a template system that produces a new localized PDF.
If you've been looking into document ingestion outside localization, understanding the impact of IDP helps frame the extraction side of the problem. CAT handles the translation workflow. IDP-style parsing explains why bad extraction leads to bad downstream translations.
Why segmentation matters more than the PDF itself
The useful output from a PDF isn't another intermediate document editor. It's a clean list of strings you can track.
That's the overlap with Django gettext. Once the content is segmented, you can store those segments in .po files, attach context, and review diffs in Git. You also get the same benefits CAT systems are designed for, such as reusable translations, terminology consistency, and less repetitive work. MotionPoint reports that translation memory can speed up translation time by as much as 40% to 60% when previously translated text is reused in later work, which is why repeat-heavy content benefits so much from structured translation memory in practice, as covered in their guide to computer-assisted translation.
For a broader software view, the computer assisted translation software overview is worth reading after you've decided your PDF content belongs in the same automation loop as your app strings.
Comparing PDF Translation Workflows and Costs
Once the text is extracted, you've got choices. Teams often land in one of three buckets.
The trade-offs in plain terms
Manual copy-paste feels cheap because you can start immediately. It falls apart on revision cycles, glossary consistency, and placeholder safety.
A TMS gives you a polished interface, shared memory, and review workflows. It can also pull your team away from Git into another system of record.
A CLI-based flow fits engineers better. You keep source text, .po files, and validation in the repo. You also accept that you need to own a bit more plumbing.
PDF Translation Workflow Comparison
| Metric | Manual Copy-Paste | TMS Platform | Automated CLI Tool |
|---|---|---|---|
| Source control | Weak, usually outside Git | Partial, often sync-based | Strong, Git-native |
| Reviewable diffs | Poor | Better inside vendor UI | Strong in pull requests |
| Placeholder safety | Fragile | Usually supported | Depends on your parser and checks |
| QA automation | Minimal | Built in on many platforms | Scriptable in CI |
| Layout handling | Manual rebuilds | Better for managed workflows | Best when PDF is regenerated from structured content |
| Team fit for Django devs | Low | Mixed | High |
| Ongoing cost model | Labor-heavy | Subscription-heavy | Usage-based plus engineering time |
| Vendor lock-in | Low | Higher | Low |
The hard truth is quality still depends on review. A recent study reported 88% average accuracy on complex English sentences for automatic-programming-based computer-assisted translation compared with 95% for traditional human translation, and the authors still positioned the system as an aid rather than a replacement in their study on computer-assisted translation accuracy.
Technical text is where “good enough draft” and “safe to publish” stop being the same thing.
That gap matters most in PDFs with setup instructions, legal disclaimers, support steps, and code examples. If the content tells users where to click, what value to enter, or how to configure an integration, a human reviewer should still approve the final output.
What usually works best
For most Django teams, the sweet spot is:
- Use extraction plus
.pofiles for source control and review. - Use machine assistance for first drafts and repeat content.
- Keep human review for technical sections, short labels, and anything with product terminology.
- Regenerate PDFs from structured source when possible, instead of editing translated PDFs by hand.
A File-Based Workflow for Django and PDFs
The cleanest approach is to stop treating the PDF as the thing you translate. Treat it as an export target.
If you can get the original Markdown, HTML, or template source, use that instead. If you can't, extract text from the PDF, normalize it, and write those strings into a file your i18n pipeline can own.

Extract text into a stable intermediate format
A practical first pass is extracting page text into line-based records, then cleaning it before it becomes translatable content.
from pathlib import Path
import fitz # PyMuPDF
source_pdf = Path("docs/getting-started.pdf")
output_txt = Path("docs/pdf_sources/getting_started.txt")
doc = fitz.open(source_pdf)
chunks = []
for page_number, page in enumerate(doc, start=1):
text = page.get_text("text").strip()
if text:
chunks.append(f"[page {page_number}]\n{text}\n")
output_txt.parent.mkdir(parents=True, exist_ok=True)
output_txt.write_text("\n".join(chunks), encoding="utf-8")
That file still won't be clean enough for translation. PDF extraction often introduces broken line wraps, duplicate headers, and split bullets. Normalize those before you generate strings.
Turn extracted content into gettext-managed text
One pattern that works well is storing extracted PDF paragraphs in a Python module that makemessages can scan:
from django.utils.translation import gettext_noop, pgettext_lazy
PDF_STRINGS = [
gettext_noop("Getting Started"),
gettext_noop("Open Settings and select Billing."),
pgettext_lazy("PDF help text", "Your account ID is shown in the top-right corner."),
gettext_noop("Contact support if the export takes more than 10 minutes."),
]
Then run:
django-admin makemessages --locale=fr --locale=de --locale=es
That gives you normal Django catalogs under paths like:
locale/fr/LC_MESSAGES/django.po
locale/de/LC_MESSAGES/django.po
locale/es/LC_MESSAGES/django.po
The upside is obvious. Your translators or reviewers work in the same format as the rest of the app. String changes show up in Git. You can annotate context with pgettext_lazy when a sentence is ambiguous or carries a specific UI meaning.
For teams localizing manuals, onboarding guides, or technical PDFs, the technical document translation guide is a useful complement to this file-based approach.
Regenerate the final PDF from translated content
The final step is rendering a localized document from translated strings instead of trying to patch text back into the original PDF.
That can mean:
- HTML to PDF, using a print stylesheet
- Markdown to HTML to PDF, for manuals and release notes
- Template-driven reports, where translated strings are inserted before rendering
- Static assets plus translated overlays, when only parts of the PDF change
If you own the generation step, you own repeatability. If you only own the final PDF, every edit becomes a document surgery problem.
Preserving Placeholders and Running QA
Most translation errors that hurt engineering teams aren't linguistic. They're structural.
A translated PDF string that drops %s, changes %(name)s, or rewrites HTML tags can break a render path, corrupt a support instruction, or send a user down the wrong setup flow. CAT systems matter here because they're built around machine-detectable checks for spelling, punctuation, placeholder integrity, and consistency. memoQ's documentation explicitly lists checks like these in its CAT QA overview.
Protect variables before anything gets translated
Django already gives you plenty of examples of fragile formatting:
msgid "%(name)s invited you to %(team)s."
msgstr ""
msgid "Processed {0} files"
msgstr ""
msgid "Click <strong>Save</strong> to continue."
msgstr ""
Those patterns need protection rules. At minimum, your workflow should verify that source and target contain the same placeholders and tag structure.
Use context aggressively when the wording is short or overloaded:
from django.utils.translation import pgettext_lazy
button_label = pgettext_lazy("PDF export button", "Download")
menu_label = pgettext_lazy("Navigation item", "Download")
Without context, short strings in PDFs are just as risky as short strings in the UI.
Checks worth running on every translated catalog
You don't need a heavyweight platform to do basic validation. Add these checks to your pipeline:
- Placeholder parity: Source and target must contain the same
%s,%(name)s, or{0}tokens. - HTML tag parity: Opening and closing tags must match the source structure.
- Forbidden terms: Product names and legal terms should stay on your approved list.
- Empty critical strings: Required sections in the document shouldn't ship with blank
msgstr. - Suspicious length changes: Big expansions can indicate extraction or formatting mistakes.
- Compile validation: Run Django's normal catalog compilation before merge.
A lot of teams also benefit from documenting placeholder conventions outside the translation tool itself. If you need a practical reference for variable syntax inside generated documents, this guide to PDF document automation is useful because it shows how placeholders show up in real PDF-driven workflows.
What not to trust blindly
Machine-generated drafts are weakest when the content is short, context-poor, and technical. That includes:
- Acronyms
- Feature labels
- Error text
- Inline code
- Mixed prose and markup
Don't assume a fluent sentence is a safe sentence. Review the strings that can break output or mislead users first. Everything else can follow a lighter pass.
How to Automate PDF Translation in CI
Once the extraction step is stable, the rest belongs in CI.
You want a pipeline that notices PDF source changes, regenerates the translatable strings, updates the catalogs, runs translation, and opens a reviewable diff. General pipeline hygiene still matters here, so it's worth skimming CloudCops' CI/CD optimization guide before you wire this into a noisy monorepo.

A GitHub Actions job can look like this:
name: Localize PDF content
on:
push:
paths:
- "docs/**/*.pdf"
- "docs/pdf_sources/**"
- "locale/**"
jobs:
translate-pdf-content:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
- name: Extract PDF text
run: python manage.py extract_pdf_strings docs/getting-started.pdf
- name: Update message catalogs
run: django-admin makemessages --all
- name: Translate catalogs
run: python manage.py translate --locale=fr --locale=de --locale=es
- name: Compile catalogs
run: django-admin compilemessages
The important part isn't the exact runner. It's the shape of the output. The job should leave you with changed .po files in Git, not hidden state in a portal. That keeps review in pull requests, where engineers already work.
If you want a deeper walkthrough focused on Django locale automation, the guide to automating .po file translation in Django covers the app-side piece in more detail.
If you want to keep PDF and app localization in the same repo, TranslateBot is built for that workflow. It translates Django .po files from the command line, preserves placeholders and HTML, and fits cleanly between makemessages and compilemessages so your PDF-derived strings can move through the same review and CI path as the rest of your app.