There is a 1974 German theology book that exists in almost no library. One copy lives with its owner here in Korea. He wanted it translated into Korean. I offered to help.

First step: get it scanned properly. I visited a professional scanning company. They said the book needed to be taken apart — pages separated, binding cut — before they could run it through their equipment. I relayed this to the owner. He said: do not cut this book. The scanning company said: then we cannot help you.

I also had a deadline. The owner needed the book back the next day. So I took it home, opened it page by page, pressed it flat with one hand, and photographed each spread with my other hand. 111 photos. That evening.

What came out the other end

111 photos later, there is now a 220-page Korean PDF. Properly formatted, with chapter titles and running headers. Bible quotes replaced with the standard Korean Bible text. Nine original illustrations placed at the correct page positions. Front and back covers included.

The translation pipeline — the scripts for translating, validating, and generating the PDF — was built with Claude Code. I am not a programmer.

The plan that changed three times

First plan: convert scans to PDF pages, run German OCR, then translate the text.

I started down this path. The OCR produced something. It looked roughly like German. But I could not read German well enough to judge how much was lost. Unusual characters, old typesetting, words split across lines — I had no way to know what the text extractor had silently dropped.

I asked Claude Code: which approach is more likely to preserve everything — OCR first, then translate, or send the image directly to Gemini?

The answer was clear. A vision model looking at the image directly can recover things OCR silently drops: words at page edges, text in shadow, characters hidden under my pressing hand. OCR commits to what it is confident about and leaves the rest blank. Image-to-translation skips that step.

I stopped the OCR process mid-run and started over with direct image translation.

Second plan: translate all 111 images, then check the result.

The translation finished. I opened a few pages to review.

Paragraphs were missing. Not a word here or there — entire blocks of text, gone. A page that had five separate paragraphs in the German original sometimes came back with only two in Korean. The other three were simply not there.

This was not obvious from a quick read. The Korean text that was there flowed naturally. Nothing looked broken. You had to place the original image next to the translation to see the gaps.

Third plan: validate paragraph counts automatically, retranslate when something is missing.

A few things I did not expect

The images were all upside down. I assumed the rotation was 90 degrees — the book was lying sideways when I photographed it. But when I rotated 90 degrees, the text was still wrong. It was 180 degrees. The book had been photographed from above with the top facing away from me. One-line fix: .rotate(180). But it took a moment to figure out.

While the first full translation was running, I tried to stop it with Ctrl+C. It did not respond. I asked Claude Code what to do. The answer: press Ctrl+Shift+Esc to open Task Manager and kill the Python process from there. That worked. Sometimes the solution is not in the code.

The HTML review tool

Before automating the validation, I built a simple HTML page — original scan on the left, Korean translation on the right — and went through every page manually. The missing paragraphs turned up more often than I expected. Sometimes a page had five paragraphs in the scan and only two in the translation. Clicking retranslate on those pages usually fixed it immediately. The missing content would appear in the new version.

This side-by-side comparison turned out to be the most useful tool I built. It is easy to read a translation and miss what is not there. Seeing the original image beside it makes the gaps obvious.

Counting paragraphs as a quality check

The automated approach: ask Gemini to count the number of independent paragraphs visible in the original image and report that number at the end of the translation. Then count the paragraph breaks in the Korean output. If the Korean has fewer than 85% of what the original reported, retranslate and try again — up to three times.

Attempt 1: original=5  translated=2  → below threshold, retranslate
Attempt 2: original=5  translated=5  → OK

Some pages needed all three retries. A few came back at six Korean paragraphs for five German ones — which happens when the translator naturally splits a longer paragraph. That is fine. The check only flags things going the other direction.

The final script runs automatically through all unfinished files, retranslates when the count is low, and moves on. Each translation also passes the last four lines of the previous page as context, so sentences cut off at the edge of a photo continue correctly into the next file.

What it cost

The Gemini API requires a minimum prepaid top-up of 16,000 KRW per charge. I topped up once, ran out, and topped up again. With the remaining balance, the total spend came to about 18,000 KRW.

Trial and error is not free. Some pages were translated three or four times — first pass, validation failure, retry, sometimes retry again. Every API call costs money. Building the paragraph validation check early would have reduced waste. Building it after all 111 pages had already been translated once meant paying for a full round of retranslation on top of the first pass.

That said, 18,000 KRW for a 220-page translation of a rare German book is not a number I would have believed before doing this.

The PDF — and why it surprised me

I assumed generating a proper PDF would require purchasing software. PDF editors that let you place text, images, and pages where you want them — the ones I knew about were expensive. I had never done it.

With Python and a free library called ReportLab, Claude Code built a script that generates a publication-quality PDF from scratch:

  • Front cover image → page 1
  • Inside front cover → page 2
  • Generated title page → page 3
  • Body text in Korean, Malgun Gothic font, A4 layout, chapter headers
  • Nine illustration images inserted automatically at the correct page numbers (named 26.png, 55.png, etc.)
  • Back cover → last page

The whole thing runs as:

python make_pdf.py --end 111 --output 한국어번역문.pdf

Output: 5.5 MB. No PDF editor purchased.

The illustration placement was the part I found most satisfying. Each image file in the 이미지/ folder is named by its page number. The script reads that number, finds where that page appears in the translated text, and inserts the image right there. It just works.

What I would have done differently

Start with direct image translation and skip OCR entirely. I knew OCR might lose content; I tried it anyway because it felt like the standard first step. That time was wasted.

Build the paragraph validation check from the start, not after a full translation pass. Running all 111 translations first and then discovering many were incomplete meant paying twice — once for the initial pass and again for the retranslations.

Add a Ctrl+C interrupt handler to the very first script. Do not find out the hard way that a long-running API loop cannot be stopped.


Key Takeaways

  • A vision model looking at an image directly recovers things OCR silently drops. For difficult source material, skip OCR.
  • A passing translation is not always a complete one. Missing paragraphs look fine on their own — you only notice them by comparing to the original.
  • A simple paragraph count check (original vs. translated, 85% threshold) catches most translation failures automatically.
  • Trial and error in API calls costs real money. Validation logic is not optional overhead — it is cost control.
  • PDF generation does not require expensive software. Python + ReportLab is free and fully programmable.