<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aditya Nile</title>
    <description>The latest articles on DEV Community by Aditya Nile (@adityanile).</description>
    <link>https://dev.to/adityanile</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3988771%2F36af1972-4b1b-4a46-9752-a3b1c398c8db.png</url>
      <title>DEV Community: Aditya Nile</title>
      <link>https://dev.to/adityanile</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adityanile"/>
    <language>en</language>
    <item>
      <title>I Tried to Translate a 300-Page Scanned Textbook to Marathi: Here's Why "OCR + Translate" Isn't Enough</title>
      <dc:creator>Aditya Nile</dc:creator>
      <pubDate>Sat, 18 Jul 2026 12:41:23 +0000</pubDate>
      <link>https://dev.to/adityanile/i-tried-to-translate-a-300-page-scanned-textbook-to-marathi-heres-why-ocr-translate-isnt-bb0</link>
      <guid>https://dev.to/adityanile/i-tried-to-translate-a-300-page-scanned-textbook-to-marathi-heres-why-ocr-translate-isnt-bb0</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/adityanile/BookTranslationPipeline.git" rel="noopener noreferrer"&gt;github.com/adityanile/BookTranslationPipeline&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Scanned English textbook. 300 pages. Old print, imperfect scans, dense paragraphs, tables, section headers. Goal: Marathi translation that &lt;em&gt;looks&lt;/em&gt; like the original — not a wall of translated text that's lost every bit of structure the source had.&lt;/p&gt;

&lt;p&gt;Three things had to be true at the end of this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Layout preserved.&lt;/strong&gt; Headings stay headings. Tables stay tables. Reading order stays intact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meaning preserved.&lt;/strong&gt; This is literary/educational content — word-for-word translation that mangles meaning is worse than useless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proportionate effort.&lt;/strong&gt; This is a one-time job, not a product. It doesn't need a platform, it needs a script that works.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  First attempt: the "obvious" pipeline
&lt;/h2&gt;

&lt;p&gt;Step one was Azure Document Intelligence's Read API solid OCR, extracts text cleanly. Except the moment you extract to plain text, you lose &lt;em&gt;all&lt;/em&gt; structural information. Tables become soup. Headings look identical to body text. That's a non-starter when layout preservation is a hard requirement.&lt;/p&gt;

&lt;p&gt;So I moved to &lt;strong&gt;Azure AI Content Understanding&lt;/strong&gt;, which outputs Markdown instead of flat text meaning headings, tables, and reading order survive extraction as actual Markdown syntax (&lt;code&gt;#&lt;/code&gt;, &lt;code&gt;|&lt;/code&gt;, list markers) rather than getting flattened. This solved layout. It did not solve translation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four ways to translate the extracted text
&lt;/h2&gt;

&lt;p&gt;At this point I had clean, structured English Markdown for every page. The question became: what actually translates it well? I evaluated four options.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;The catch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Multimodal LLM directly on page images&lt;/td&gt;
&lt;td&gt;Simple, but no guaranteed formatting consistency across 300 independently-prompted pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Markdown → raw LLM translation call&lt;/td&gt;
&lt;td&gt;Great quality, but ~5x the per-page cost of what I ended up using&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Markdown → Azure Translator (GPT-5.1 backed mode)&lt;/td&gt;
&lt;td&gt;Cheapest, and — this is the twist — the most robust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Feed the scanned PDF straight to Azure Document Translation&lt;/td&gt;
&lt;td&gt;One API call, internal OCR — but zero visibility into what it's actually extracting or how it reconstructs the layout&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that decided the architecture
&lt;/h2&gt;

&lt;p&gt;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 &lt;strong&gt;"oral"&lt;/strong&gt; extracted as &lt;strong&gt;"or al."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's where it got interesting. I ran that corrupted fragment through two different translation paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Azure Translator's classical NMT engine&lt;/strong&gt; (the default, statistical model): translated it &lt;em&gt;literally&lt;/em&gt; — "or" and "al" as separate fragments — and quietly changed the meaning of the sentence. No error, no warning. Just wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure Translator's GPT-5.1-backed mode&lt;/strong&gt; (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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture I shipped
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9mchv7h4hvog29e7qkw8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9mchv7h4hvog29e7qkw8.png" alt="Doc Processing Architecture" width="760" height="574"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;extracted → translated → done&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;before&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;Managed Identity handles all service-to-service auth no keys sitting in config files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually cost
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Content Understanding extraction&lt;/td&gt;
&lt;td&gt;~$1.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contextualization overhead&lt;/td&gt;
&lt;td&gt;~$0.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Translation (GPT-5.1 backed, via Translator)&lt;/td&gt;
&lt;td&gt;effectively $0 — inside the 2M characters/month free tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total for the full 300-page book&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;under $5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell past-me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test your worst input, not your average input.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep an inspectable checkpoint between every stage that costs money.&lt;/strong&gt; Going straight from scan to translated output is simpler to build and much harder to debug when something's wrong three stages downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't reach for a managed orchestrator out of habit.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;"GPT-backed" translation isn't just a quality upgrade sometimes it's the only thing standing between you and silently wrong output.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Full implementation, orchestrator code, and the architecture diagram are in the repo: &lt;a href="https://github.com/adityanile/BookTranslationPipeline.git" rel="noopener noreferrer"&gt;github.com/adityanile/BookTranslationPipeline&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>ai</category>
      <category>python</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>AKS vs VMs: what I learned scaling a multi-region AI platform on Azure</title>
      <dc:creator>Aditya Nile</dc:creator>
      <pubDate>Wed, 01 Jul 2026 19:00:50 +0000</pubDate>
      <link>https://dev.to/adityanile/aks-vs-vms-what-i-learned-scaling-a-multi-region-ai-platform-on-azure-33jb</link>
      <guid>https://dev.to/adityanile/aks-vs-vms-what-i-learned-scaling-a-multi-region-ai-platform-on-azure-33jb</guid>
      <description>&lt;p&gt;When I started designing the infrastructure for Clynto AI an AI-first Customer Success platform launching in both the US and India at the same time the first real decision wasn't which cloud to use. It was simpler than that, and also harder: do we run this on VMs, or do we put it on Kubernetes from day one?&lt;/p&gt;

&lt;p&gt;There's a version of this post that just says "Kubernetes wins, obviously." That's not useful to anyone actually making the call. So instead, here's what the VM path would have actually cost us, what AKS bought us, and where I think VMs would still have been the right answer if Clynto's requirements were different.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The full architecture, CI/CD pipeline, and implementation write-up for this build are in the &lt;a href="https://github.com/adityanile/ClyntoCaseStudy" rel="noopener noreferrer"&gt;Clynto AI case study repo&lt;/a&gt; if you want the complete picture beyond this comparison.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Clynto needed two regions East US and Central India each with its own production and staging environment, each carrying different compliance obligations (GDPR in one, PCI-DSS in the other), all behind three public domains routed through a single edge. It's a small platform team's worth of complexity for what's still an early-stage company.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzzxmzkepadkn17hdyg54.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzzxmzkepadkn17hdyg54.png" alt="Clynto AI multi-region architecture" width="800" height="355"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Azure Front Door routes by domain and tenant to regional Application Gateways, which front isolated prod/staging AKS environments in East US and Central India.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That last part matters. A lot of the AKS-vs-VM debate online assumes you're either a five-person startup or a thousand-engineer org, and the answer to "use Kubernetes?" looks different at each end. Clynto sat in an uncomfortable middle: compliance and multi-region requirements of a bigger company, headcount of a much smaller one. That tension is what actually decided this, not a general preference for containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where VMs would have struggled
&lt;/h2&gt;

&lt;p&gt;If I'd gone with VM-based deployment say, VM Scale Sets behind a Load Balancer, which is the natural Azure equivalent here's where it would have gotten painful:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replicating two regions by hand.&lt;/strong&gt; With VMs, "mirror this environment in another region" usually means re-running a setup script, or maintaining two slightly-diverging sets of ARM templates / Ansible playbooks. With AKS, the cluster is just another Terraform module with a different region variable. I provisioned both regions' clusters, and both environments (prod/staging) within them, off the same module set. The VM version of this would have meant four mostly-similar-but-not-identical environments to keep in sync by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enforcing policy below the application layer.&lt;/strong&gt; A chunk of Clynto's compliance requirement isn't "is the app secure," it's "can we prove the platform itself enforces controls." On VMs, that means OS hardening baselines, config management tooling, and manual audits of who can SSH into what. On AKS, Kyverno enforces policy at the Kubernetes API level no privileged containers, no images from outside the approved registry and that's enforced automatically on every deploy, not checked quarterly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero-touch deployment.&lt;/strong&gt; GitOps with Argo CD means there's no &lt;code&gt;ssh&lt;/code&gt; step in the release process at all. On a VM fleet, even a well-built CI/CD pipeline usually ends in something touching the box directly a deployment script, a config push, a service restart. That's a smaller surface area difference than people think, but it's a real one when you're trying to make "no manual production access" a literal architectural guarantee instead of a policy on paper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bin-packing under unpredictable AI workload.&lt;/strong&gt; Clynto's workloads sit next to Azure OpenAI calls through Microsoft Foundry, and load isn't steady it spikes with usage patterns that don't map cleanly to a fixed VM count. I used the Vertical Pod Autoscaler to right-size pod resource requests and tuned Horizontal Pod Autoscaling against real Azure Monitor metrics. Getting equivalent elasticity out of a VM Scale Set means scaling whole machines up and down, which is coarser and slower, and it's harder to right-size a VM than a pod.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where VMs would have actually been fine or better
&lt;/h2&gt;

&lt;p&gt;I want to be fair to the other side of this, because most "AKS is better" posts skip it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If Clynto were single-region, single-environment, low-compliance-burden&lt;/strong&gt; say, a single-tenant internal tool a couple of VMs behind a load balancer would have been less infrastructure to operate, less to learn, and genuinely simpler to reason about. Kubernetes has a real operational tax: cluster upgrades, node pool management, learning Argo CD and Kyverno in the first place. If you don't need the multi-region or policy-enforcement benefits, you're paying that tax for nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the workload were a single monolith with predictable, steady load&lt;/strong&gt;, autoscaling pods buys you very little over a couple of right-sized VMs with a basic autoscale rule. The complexity of containerizing an app that doesn't need to scale dynamically is sometimes just complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging is genuinely harder on Kubernetes&lt;/strong&gt; when something goes wrong at 2am. A VM you can SSH into and look at logs directly. A pod that's crash-looping inside a cluster with network policies and admission controllers has more layers between you and the problem. I don't think this is a reason to avoid AKS for Clynto's case, but it's a real cost, not a myth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually tipped it for Clynto
&lt;/h2&gt;

&lt;p&gt;Two regions, two compliance regimes, a staging environment that needed to be a faithful mirror of prod, and a one-person platform team trying to keep all of that consistent without drowning in manual config drift. That combination is where Kubernetes specifically AKS with Terraform, Argo CD, and Kyverno earns its complexity. Not because containers are inherently superior to VMs, but because the thing I actually needed wasn't "run my app," it was "run four nearly-identical, policy-enforced, audit-ready environments without doing the same setup four times by hand."&lt;/p&gt;

&lt;p&gt;If you're making this call for your own platform, the question I'd actually ask isn't "Kubernetes or VMs" it's "how many environments do I need to keep consistent, and how much of my compliance story needs to be enforced by the platform instead of by a person checking a box."&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is based on an independent freelance engagement as the Azure Cloud Architect for Clynto AI. Architecture and implementation details reflect the actual platform delivered.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
