I needed to convert a 300-page scanned English textbook into Marathi while keeping the original layout intact — headings, tables, the works. The "obvious" approach (OCR the pages, translate the text) works right up until it doesn't: OCR throws away structure, and generic machine translation throws away meaning the moment your input has any noise in it. This post walks through the four architectures I evaluated, the one bug that killed my first real approach, and the pipeline I ended up shipping for under $5.
Repo: github.com/adityanile/BookTranslationPipeline
The problem
Scanned English textbook. 300 pages. Old print, imperfect scans, dense paragraphs, tables, section headers. Goal: Marathi translation that looks like the original — not a wall of translated text that's lost every bit of structure the source had.
Three things had to be true at the end of this:
- Layout preserved. Headings stay headings. Tables stay tables. Reading order stays intact.
- Meaning preserved. This is literary/educational content — word-for-word translation that mangles meaning is worse than useless.
- Proportionate effort. This is a one-time job, not a product. It doesn't need a platform, it needs a script that works.
First attempt: the "obvious" pipeline
Step one was Azure Document Intelligence's Read API solid OCR, extracts text cleanly. Except the moment you extract to plain text, you lose all structural information. Tables become soup. Headings look identical to body text. That's a non-starter when layout preservation is a hard requirement.
So I moved to Azure AI Content Understanding, which outputs Markdown instead of flat text meaning headings, tables, and reading order survive extraction as actual Markdown syntax (#, |, list markers) rather than getting flattened. This solved layout. It did not solve translation.
Four ways to translate the extracted text
At this point I had clean, structured English Markdown for every page. The question became: what actually translates it well? I evaluated four options.
| # | Approach | The catch |
|---|---|---|
| 1 | Multimodal LLM directly on page images | Simple, but no guaranteed formatting consistency across 300 independently-prompted pages |
| 2 | Markdown → raw LLM translation call | Great quality, but ~5x the per-page cost of what I ended up using |
| 3 | Markdown → Azure Translator (GPT-5.1 backed mode) | Cheapest, and — this is the twist — the most robust |
| 4 | Feed the scanned PDF straight to Azure Document Translation | One API call, internal OCR — but zero visibility into what it's actually extracting or how it reconstructs the layout |
Cost, on its own, barely separated these at 300 pages, every option landed somewhere between $5 and $25 total. The real differentiator turned out to be something I hadn't planned to test for.
The bug that decided the architecture
Old scanned print isn't clean. Content Understanding, running OCR on aged pages, occasionally produced small artifacts the kind of thing you'd never catch by eyeballing a summary. One page had the word "oral" extracted as "or al."
Here's where it got interesting. I ran that corrupted fragment through two different translation paths:
- Azure Translator's classical NMT engine (the default, statistical model): translated it literally — "or" and "al" as separate fragments — and quietly changed the meaning of the sentence. No error, no warning. Just wrong.
- Azure Translator's GPT-5.1-backed mode (Azure now offers "flexible translation" through foundation models, not just the classical engine): correctly inferred the intended word from context and translated the sentence properly.
That one test case ruled out the default translation engine entirely. It wasn't a fluency preference — it was a correctness requirement. If your source material has any realistic noise in it (and scanned historical print always does), a foundation-model-backed translator isn't a nice-to-have. It's the difference between a pipeline that silently corrupts meaning and one that doesn't.
The architecture I shipped
Scanned Pages
│
▼
Content Understanding ──► Markdown (structure preserved)
│
▼
Storage (Source / Markdown / Target containers)
│
▼
Azure AI Translator — Text Translation, GPT-5.1 backed
│
▼
Marathi Markdown (structure intact, meaning intact)
A Python script orchestrates the whole thing — no Durable Functions, no Logic Apps, no managed workflow engine. This was a deliberate call: it's a one-time job, and a manifest-driven script that tracks each page through extracted → translated → done gives you the same resumability a hosted orchestrator would, without any of the deployment overhead. If a run dies at page 220 of 300, you rerun the same command and it picks up where it left off.
Three storage containers Source, Markdown, Target act as durable checkpoints between stages. The Markdown checkpoint matters more than it looks: it's an inspectable, human-readable English artifact you can spot-check before you spend a single translation call on it. Skip that checkpoint (like Option 4 does, translating straight from the scanned PDF) and you lose your only chance to catch an extraction error before it's baked permanently into the output.
Managed Identity handles all service-to-service auth no keys sitting in config files.
What it actually cost
| Stage | Cost |
|---|---|
| Content Understanding extraction | ~$1.50 |
| Contextualization overhead | ~$0.30 |
| Translation (GPT-5.1 backed, via Translator) | effectively $0 — inside the 2M characters/month free tier |
| Total for the full 300-page book | under $5 |
Routing translation through the managed Translator service instead of calling a foundation model directly for full-page translation is what made this free the free-tier character allowance doesn't exist for raw model calls, only for the Translator service itself.
What I'd tell past-me
- Test your worst input, not your average input. The whole architecture pivoted on one degraded OCR case. If I'd only benchmarked against clean pages, I'd have shipped the classical NMT path and never known it was silently mistranslating noisy sections.
- Keep an inspectable checkpoint between every stage that costs money. Going straight from scan to translated output is simpler to build and much harder to debug when something's wrong three stages downstream.
- Don't reach for a managed orchestrator out of habit. Durable Functions is the right tool for a recurring pipeline. For a run-once job, a manifest file and a plain Python script gets you the same reliability guarantees for a fraction of the setup.
- "GPT-backed" translation isn't just a quality upgrade sometimes it's the only thing standing between you and silently wrong output.
Full implementation, orchestrator code, and the architecture diagram are in the repo: github.com/adityanile/BookTranslationPipeline
If you're building something similar — scanned documents, layout preservation, low-resource language pairs happy to talk through what worked and what didn't. Drop a comment or open an issue on the repo.

Top comments (0)