translator¶
Translates XML in place. Every tag, every namespace, every attribute and — for ALTO — every coordinate survives; only the text changes. The output is the same document in another language, not a rendering of it.
Two kinds of input, one core:
- ALTO OCR pages — the positional text layer produced by alto-postprocess.
- AMCR metadata records, bare or inside
OAI-PMH envelopes — translated at XPath targets named
in
amcr-fields.txt.
The default translation engine is LINDAT's CUBBITT service; an OpenAI-compatible LLM API or a self-hosted CTranslate2 model can be selected instead. Archaeological terms are kept consistent through a controlled Czech–English vocabulary.
It runs as a batch CLI (main.py), as a containerised service (POST /translate), and as
an Agent Skill.
What it takes in, what it hands on¶
| In | One ALTO or AMCR XML file, or a directory of them, optionally with an ATRIUM document record to accrete onto |
| Out | <name>_<target_lang>.<ext> — the translated document; a _log.csv of every source/target text pair; a paradata JSON; optionally the updated record |
| Writes in the record | The translations block, and derived_from.translated_xml — the name of the translated file |
| Reads from upstream | PAGE_ALTO/<doc>/<doc>-N.alto.xml from alto-postprocess |
Where it sits — the translator is a terminal branch¶
The translator reads the per-page ALTO that alto-postprocess writes, and its output is an
end product: an English edition of the page or record, for readers and for
publication. The later stages work on the original text — nlp-enrich reads
DOC_LINE_CATEG/ and the original ALTO/ — so no stage reads TRANSLATED/.
What persists in the pipeline is a reference: the translated document is recorded in
the document record as derived_from.translated_xml, and the translations block records
how it was made. So the file topology fans out from alto-postprocess and stops here,
while the record runs on through every stage. Pipelines draws both
layers.
How a translation is made¶
ALTO pages keep their geometry. Each text block is translated twice: once whole, for a fluent translation, and once line by line, only to learn how many words belong on each line. The fluent translation is then split across the original lines and written into the original word boxes, so every coordinate survives. Reference → ALTO dual-pass reconstruction draws it step by step.
Metadata records are translated field by field: each XPath in amcr-fields.txt selects
the free-text fields to translate, and only their text changes. With --output-mode append
the Czech is kept and the English is added beside it, marked xml:lang="en".
Reference → Metadata mode has the detail.
The source language is detected by FastText unless it is given — per text block in ALTO, per field in metadata — so a record that mixes languages is handled piece by piece.
Controlled terms from the AMCR and TEATER thesauri keep their fixed English equivalent: they are masked before translation and restored afterwards (CUBBITT), or given to the model as a glossary (LLM backends). See Reference → Vocabulary protection.
The three backends¶
| Name | Class | Glossary support | Configured by |
|---|---|---|---|
lindat (default) |
LindatTranslator |
✗ | TRANSLATION_URL, defaulting to LINDAT's CUBBITT API. Model names are fetched live from the API and composed as <src>-<tgt> |
openai_compatible |
LLMTranslator |
✓ — glossary injected into the prompt | LLM_BASE_URL, LLM_MODEL, LLM_API_KEY, optional LLM_PROVIDER / LLM_LANGUAGES. Raw REST, no vendor SDK. No model id is hardcoded |
ct2 |
CT2Translator |
✓ for eurollm (prompt); ✗ for NMT families |
CT2_MODEL_DIR, CT2_MODEL_FAMILY (eurollm / madlad / nllb / opus), CT2_SP_MODEL, CT2_DEVICE, CT2_COMPUTE_TYPE (default int8). Needs requirements-ct2.txt |
Selection order: --backend → config.txt translation_backend → TRANSLATION_BACKEND
→ lindat. An unknown name raises with the available list.
The ct2 backend
The CTranslate2 self-hosting backend in processors/ct2_translator.py runs EuroLLM,
MADLAD-400, NLLB-200 or Opus-MT from a converted model directory. Its dependencies are
optional: ctranslate2 and sentencepiece load on the first translation, which fails
with an install hint when requirements-ct2.txt is missing. Before the model loads,
CT2_COMPUTE_TYPE (default int8) is checked against what CT2_DEVICE supports, and a
bad value names the valid ones.
The licence follows the model family: EuroLLM and MADLAD-400 are Apache-2.0, Opus-MT is recorded as CC BY 4.0, and NLLB-200 is CC BY-NC 4.0. For commercial use, choose one of the permissive families; Reference → Licence gives the full permissive recipe.
Evaluating translation quality¶
Archival OCR text rarely comes with a reference translation, so the repository's
evaluation harness, eval/bakeoff.py, measures backends in two ways and reports both side
by side, one summary row per backend:
- with references, when a reference file is supplied: chrF and BLEU through sacreBLEU, and COMET — the standard machine-translation metrics;
- without references, always: whether numbers, dates and codes survive, how often the output is empty, the output-to-input length ratio (a guard against truncation and invented text), how many expected glossary terms appear, and how far each backend's output diverges from the baseline's; reference-free COMET-QE can be switched on as well.
It calls the real backends over the network, so it is run by hand against a chosen sample
rather than in CI. Reference → Other entry points lists
its flags, and docs/translation-backends.md in the repository compares the candidate
models it was built to choose between.
Languages¶
Twenty, mapped ISO 639-3 → ISO 639-1 by the language identifier: cs, en, fr, de,
ru, pl, uk, sk, bg, hr, sl, lv, lt, et, hu, ro, es, it, nl,
hi. Detection uses facebook/fasttext-language-identification; in ALTO mode it runs
once per TextBlock so every line in a block is translated consistently.
Whether a given pair is actually served depends on the backend — CUBBITT is Czech-centric, and offers the pairs the LINDAT service lists. The repository's backend evaluation document compares nine candidates on exactly this axis.
Licence — computed per run, not declared¶
Every component carries a licence and a condition, and the paradata records what the run resolved to. A default run resolves to CC BY-NC-SA 4.0, because CUBBITT's models are CC BY-NC-SA and FastText's weights are CC BY-NC. A permissive run is possible but has to be assembled deliberately — see Reference.
Where to go next¶
- Guide — install it, run it, troubleshoot it
- Reference — every flag, every endpoint, every output field, and how each mode works
- Changelog — the release history, grouped into arcs
- History — why it is shaped the way it is
- Workflow — the workflow step by step, as its SSH Open Marketplace and Galaxy records describe it
- Pipelines — where this stage sits, end to end
- External tools & services — LINDAT, CUBBITT, UDPipe, ALTO, AMCR
Sources¶
Read from ufal/atrium-translator at branch master, commit 71feaef (2026-09-23),
and from the hub's canonical documents. This table records provenance: what this page
was written from, not a build instruction.
| Source | What was taken from it |
|---|---|
README.md §§ Features, Logic Overview, Paradata |
behaviour, language handling, output description |
processors/backend.py, processors/ct2_translator.py |
the registry, the protocol, the selection order, the ct2 backend |
processors/identifier.py |
the 20 language codes |
utils.py |
the ALTO and metadata processing summarised above |
eval/bakeoff.py |
the evaluation metrics |
para_config.txt |
the licence component table |
atrium-project/docs/templates/shared/atrium_document.py |
block ownership |
atrium-project/docs/document_schema.md |
the write/read contract |