RockTranslate v2.0.7

Translate and read PDFs as structured documents

RockTranslate rebuilds a PDF page as HTML with pdf2htmlEX, regroups lines into paragraphs and tables, translates them with the LLM of your choice, and prints the result with the original layout. The same analysis gives you the document's structure as data.

Install

Windows desktop app. Download RockTranslate_Setup_v2.0.7.exe. It includes the layout engine (pdf2htmlEX 0.18.8, native 64-bit).

Python package (Windows, Linux, macOS), Python 3.9 or newer:

pip install rocktranslate            # CLI and Python API
pip install "rocktranslate[gui]"     # plus the desktop interface

Check the setup: the command lists the layout engine, the browser used for measuring and printing, and the API keys it found.

rocktranslate doctor

Nothing else to install by hand. On Windows the engine is downloaded on first use if it is not bundled; on Linux the official pdf2htmlEX AppImage is extracted automatically. Exact line measurement uses an installed Edge or Chrome.

First translation

export DEEPSEEK_API_KEY=sk-...
rocktranslate paper.pdf -l French -p 1-2
# -> paper_translated.pdf (pages 1-2, same layout, in French)

Models and API keys

Any model reachable through LiteLLM works. Pass the route with -m (CLI) or model= (Python). Keys are read from the provider's usual environment variable, from the desktop app's settings, or passed explicitly.

ProviderModel routeKey variable
DeepSeekdeepseek/deepseek-flashDEEPSEEK_API_KEY
Google Geminigemini/<model>GEMINI_API_KEY
OpenAIopenai/<model>OPENAI_API_KEY
Anthropicanthropic/<model>ANTHROPIC_API_KEY
Ollama (local, offline)ollama/<model> with --base-url http://localhost:11434none

Translate from the command line

rocktranslate paper.pdf -l German -p 1-2 -o out/paper_de.pdf

With -p, only the requested pages are converted, so a range of a large PDF starts in seconds. The layout analysis is cached per page range, and translations are cached per document, language and model: a second run of the same pages costs nothing.

Measured on the 40-page reference paper (pages 1-2, Windows laptop): conversion 3 s, layout analysis 8 s, translation 10 s, PDF export 15 s on a warm run.

Translate from Python

import rocktranslate

doc = rocktranslate.translate(
    "paper.pdf",
    lang="Spanish",
    model="deepseek/deepseek-flash",
    pages="1-5",
    render_pdf=True,          # False: structured outputs only, no PDF
)
doc.to_markdown("paper.es.md", bilingual=True)
for e in doc.by_type("Section"):
    print(e.text, "->", e.translation)

translate() returns the same Document as extract(), where every element also carries translation (and tables table_translation). doc.report holds tokens, cost, time and failures.

Outputs and formats

-f valueFileContent
pdfpaper_translated.pdfTranslated PDF with the original layout (default)
jsonpaper_german.jsonElements with page, box, original text and translation
mdpaper_german.mdTranslated Markdown; with --bilingual, each original paragraph followed by its translation
chunkspaper_german.chunks.jsonSection-aware chunks with their boxes

Cost and caching

A page of a scientific article costs about $0.002 with deepseek/deepseek-flash (page 1 of Attention Is All You Need: 3,315 tokens, $0.0019). Numbers, formulas and symbols are not sent to the model. Nothing is paid twice: cached translations are reused when the same text is translated again into the same language with the same model.

Structured extraction

rocktranslate.extract() returns a Document: a list of typed elements in reading order. It runs locally, needs no API key and costs nothing.

import rocktranslate

doc = rocktranslate.extract("paper.pdf")          # pages="1-3", password="..."
print(doc)
# <Document 'paper.pdf': 531 elements (135 NarrativeText, 86 Reference,
#   31 Section, 11 Formula, 5 FigureCaption, 4 Table, 1 Title, ...)>

doc.to_markdown("paper.md")
doc.to_json("paper.json")
print(doc.title)
for table in doc.tables:
    print(table.page, table.table[:2])               # rows x cells
rocktranslate extract paper.pdf -f json,md,chunks -p 1-10
Source table in the PDF
Input: a table from an open-access scientific paper. Output below, unedited.
| 1 | 1.02 | 0.02 | 1.07 | 12.74 | 0.20 | 1.90 | 0.44 | 48.17 | 0.39 | 0.31 |
| 2 | 0.80 | 0.01 | 1.33 | 2.42  | 0.20 | 1.79 | 0.29 | −21.74 | 0.18 | 0.26 |
| 3 | 0.44 | 0.01 | 5.63 | 3.88  | 0.39 | 2.37 | 0.47 | 9.95  | 0.25 | 0.06 |

Element types

TypeMeaningIn Markdown
TitleDocument title (largest multi-word text near the top of page 1)#
SectionHeading; category_depth from its numbering (3.2.1 → 2)## to ######
NarrativeTextParagraph, regrouped across lines and columnsparagraph
ListItem, ReferenceList entries; bibliography entries after a References heading-
TableRows × cells in table, HTML in text_as_htmlpipe table
FigureCaption, TableCaptionCaptions starting with Figure / Fig. / Tableitalic
FormulaDisplayed equation piecescode block
FootnoteSmall text at the bottom of the page, with its markquote
ImageA captioned figure rendered as PNG (photos, maps, vector plots); image_path in JSON![Figure 1](paper_images/…png)
Header, Footer, FigureTextRunning headers, page numbers, stamps, words drawn inside figuresleft out

JSON schema

Each element follows Unstructured's layout. to_json(unstructured=True) also maps our types to Unstructured's names (Section → Title, TableCaption → FigureCaption…), so existing pipelines can read it.

{
  "type": "Section",
  "element_id": "2f93c78ffa05077345dd7301e50c9ccd",
  "text": "3.2.1 Scaled Dot-Product Attention",
  "metadata": {
    "page_number": 4,
    "coordinates": {
      "points": [[108.0, 337.49], [108.0, 358.7], [264.13, 358.7], [264.13, 337.49]],
      "system": "PDFPoints",
      "origin": "top-left"
    },
    "parent_id": "2bc82002054ba39d4452522b7f29bb3e",
    "category_depth": 2,
    "font_size": 9.96
  }
}

Real element from Attention Is All You Need. Ids are stable: the same document gives the same ids. After translate(), the metadata also holds translation (and table_translation for tables).

Chunks for RAG

doc.chunks(max_characters=1500) groups whole elements of one section up to the size limit. A chunk never mixes two sections and never cuts an element. Each one keeps the page and box of its elements, so a retrieved chunk can be shown in the PDF.

{
  "text": "Recurrent neural networks, long short-term memory [13] and gated …",
  "section": "1 Introduction",
  "pages": [2],
  "element_ids": ["b47cb8d9…", "74fe378b…"],
  "boxes": [[2, [108.0, 85.56, 504.36, 150.28]], [2, [108.0, 145.58, 504.47, 243.03]]],
  "translation": null
}

Reading assistant 2.0.6

In the desktop app, read the original and get help on the passage you are reading. Every action uses the model of Settings → AI model and shows its time and cost.

A selection translated in a bubble
Four lines selected, translated in a bubble in 2 s (DeepSeek Flash, $0.0002).
  • Selection: a toolbar appears; Translate (Ctrl+T), Explain (Ctrl+E), Copy (clean text), Copy + citation, Highlight (Ctrl+H). The same actions are in the right-click menu.
  • Links: hover a citation, figure, table or equation link to preview its target; Go there, DOI, Open & translate for arXiv references.
  • Side panel (Ctrl+B): Contents (bookmarks, or detected headings; ∑ summarizes a section), Search in the original and the translation (Ctrl+F), Ask (Ctrl+J) with verbatim quotes that open the exact passage, Notes (exported as Markdown), Glossary.
  • Paper glossary: Generate builds the key terms with one translation each; edit them; when Use for translations is on, every translation of the document follows them.
  • Tools menu: continuous translation of the pages you read, night reading, snapshot of an area (Ctrl+Shift+S), page as an image, bilingual side-by-side PDF, notes export, command palette (Ctrl+K).
  • Reading comfort: the app reopens each document at the last page read; tabs keep several PDFs open; hold Alt in the translation-only view to see the original.
  • Default reader: the installer registers Open with RockTranslate for PDFs and can make it the default app; a PDF opened while the app runs goes to the open window.

Ask, summarize, glossary

The same assistant from the command line and from Python. Inputs can be files or links.

rocktranslate ask paper.pdf "Which dataset is used?" -l French     # answer + quotes with pages
rocktranslate summarize paper.pdf --section 3.2 -l French
rocktranslate glossary paper.pdf -l French -o terms.txt
rocktranslate paper.pdf -l French --glossary terms.txt --side-by-side paper_bilingual.pdf
rocktranslate explain "softmax(QK^T / sqrt(d_k)) V" -l French
import rocktranslate
result = rocktranslate.ask("https://arxiv.org/abs/2109.03697", "What is the test MPE of U-FNO?", lang="French")
result["answer"], result["quotes"]          # [{"page": 32, "quote": "test µ 0.0299 0.0276 0.0224 0.0161"}]
terms = rocktranslate.glossary("paper.pdf", "French")
doc = rocktranslate.translate("paper.pdf", "French", glossary=terms, side_by_side="paper_bilingual.pdf")
rocktranslate.extract("paper.pdf").toc()     # contents, even without bookmarks

Measured on a 37-page paper: a question ≈ $0.006, a glossary ≈ $0.006, a translated selection ≈ $0.0002.

CLI reference

rocktranslate <pdf>

OptionDescription
inputA PDF file, or an http(s) link to one (arXiv /abs/ pages work)
-l, --langTarget language (default: French)
-o, --outputOutput PDF (default: <input>_translated.pdf)
-p, --pagesPage range, e.g. "1-3, 5"; only those pages are converted
-m, --modelLiteLLM route, e.g. deepseek/deepseek-flash
-k, --api-keyAPI key (default: environment variable or desktop settings)
-t, --tempSampling temperature
-f, --formatpdf, json, md, chunks, comma-separated
--bilingualMarkdown with each original paragraph followed by its translation
--skip-referencesKeep the bibliography in its original language (fewer tokens)
--glossary FILETerminology to follow, one term -> translation per line
--auto-glossaryBuild the paper's glossary first, then translate with it
--side-by-side PDFAlso write a bilingual copy: original page | translated page
--parallelConcurrent LLM requests (default: 4)
--base-urlCustom API endpoint (Ollama, proxies)
--passwordPassword of a protected PDF
--keep-htmlAlso save the translated HTML

rocktranslate extract <pdf>

-f json,md,chunks (default json,md), -o output base path, -p page range, --password, --no-images (figures are written as PNG beside the outputs by default).

rocktranslate ask | summarize | glossary | explain

ask <pdf> "question" (--json), summarize <pdf> [--section 3.2], glossary <pdf> [-o FILE] [--max-terms 30], explain "text" (- reads stdin); all take -l, -m, -k, --base-url.

rocktranslate doctor

Checks pdf2htmlEX, the PDF renderer, the GUI package and the API keys.

Python API reference

rocktranslate.extract(pdf_path, pages=None, password=None, measure=True) → Document
Structure only. measure=False skips the browser measurement and estimates line widths. images=True (default) renders every captioned figure as an Image element (PNG); doc.images, doc.save_images(folder).
rocktranslate.translate(pdf_path, lang="French", model=…, api_key=None, pages=None, output_pdf=None, render_pdf=True, parallel=4, password=None, **options) → Document
Translated Document; doc.output_pdf and doc.report (tokens, cost, seconds, failures). Raises RuntimeError when nothing could be translated.
rocktranslate.translate_text(text, lang) / explain(text, lang) → str
A passage copied from a PDF, translated or explained (broken lines and hyphens are rebuilt). on_delta=callable receives the answer while it streams.
rocktranslate.ask(pdf, question, lang) → {"answer", "quotes": [{"page", "quote"}]}
Quotes are verbatim and checked against the paper.
rocktranslate.summarize(pdf, lang, section=None) → str · rocktranslate.glossary(pdf, lang) → [{"term", "translation", "note"}]
Pass the glossary to translate(glossary=…); translate(side_by_side="out.pdf") also writes the bilingual copy (or call rocktranslate.side_by_side(original, translated, output)).
Document
elements, title, tables, by_type(*types), sections(), toc(), section_text(heading), to_json(path=None, unstructured=False), to_markdown(path=None, translated=False, bilingual=False, page_markers=False), to_text(translated=False), chunks(max_characters=1500), search(query), highlight(elements, source_pdf, output_pdf).
Element
type, text, html, page, bbox (x0, y0, x1, y1 in points from the top-left), element_id, parent_id, depth, font_size, table, translation, translation_html, table_translation.
RockTranslator(model, api_key, target_lang, temperature, parallel, glossary, …)
translate_document(pdf, output, pages, structured=False, render_pdf=True) → TranslationReport, for fine control and reuse of one configuration.

Desktop app

  • Side-by-side view: original PDF on the left, reconstructed layout on the right, same zoom, synchronized scrolling.
  • Layout prepared at opening, in the background and at no cost, so translation starts at once.
  • Original text on hover: View → Original text on hover, or hold Ctrl while hovering.
  • Progress panel with pages, segments, time, tokens and cost; collapse or close it from its corner buttons.
  • Export: File → Export opens your browser's print dialog; Export without browser writes the PDF automatically.
  • Settings → Clear the translation cache prepares the open document again from scratch.

Troubleshooting

The conversion stops or takes long on a large PDF
RockTranslate watches pdf2htmlEX: a conversion that makes no progress and uses no CPU is stopped and retried automatically, up to three times. Converting only the pages you need (-p) is the fastest option.
Paragraphs look squeezed
Exact line measurement needs Edge, Chrome or the small headless engine RockTranslate downloads in the background. Run rocktranslate doctor to see which one is used.
The text stays in the original language
Check the API key in the logs (401 / 403), or clear the translation cache from the Settings menu so the document is prepared again.

Known limits

  • Scanned PDFs (images without a text layer) need OCR, which RockTranslate does not do yet.
  • Displayed equations are only partly reassembled into one element.
  • Tables whose rows are drawn as a single text run are not split into cells yet; multi-level table headers can be split.
  • Publisher fonts embed only the glyphs they use: translated characters outside them use a fallback font.