218. Every language of the site is derived from the English build and a translation memory
Context
On 2026-10-19 the owner ordered that majordomus.dev be, from then on, always fully multilingual — Czech and English at the least — with a language switch, and that every language be derived, inferred and translated automatically: "I do not want to maintain two language versions; it must work automatically, derived, inferred, the prismatic way."
The site then had 2,137 pages and about 880,000 words, nearly all of them generated from
English records (the plan, the documents, the rules, the decisions, the registry), and four
hand-written Czech pages admitted by project.english-only v2 (ADR 0077). Three ways to make
it bilingual were on the table:
- A second content tree (
*.cs.mdbeside every page, Zola's multilingual mode). Every generator would write twice, every template would need its strings keyed, and the data the templates read (site/data/generated/*.json) would still render in English. It is the "two language versions" the owner refused, written by machines instead of people. - Translation at build time, by asking a model during
scripts/site-build. A build would stop being a pure function of the tree: two builds of one commit would differ, CI would need a credential, and a model outage would take the site down. - A pass over the built pages and a memory of translated segments — chosen.
Decision
-
English is the only source. Nobody writes a page in another language. The four hand-written Czech pages stay as they are, and are carried into the Czech copy unchanged.
-
Every other language is a pass over the built English site. After Zola, the accessibility pass and the diagrams,
scripts/site-buildrunsscripts/lib/site-i18n.mjsover every page it wrote. A page is cut into segments: each run of inline content between block boundaries, and each attribute a reader meets (title, alt, aria-label, the page's description). Code,pre,kbd, SVG and anything markedtranslate="no"are never a segment. The copy under/<lang>/is the English page with each segment the memory knows replaced, every internal link pointed at its counterpart,<html lang>and the canonical URL set,hreflangalternates added, and the machine-translation notice revealed. Because the pass reads the rendered HTML, it covers templates, generated data and content alike: there is nothing a template must do to be translated. -
The memory is keyed by the English, not by the page.
site/i18n/<lang>/memory/*.jsonlholds{k, en, <lang>}per segment,kthe hash of the English with inline markup replaced by numbered placeholders (<t1>…</t1>,<t2/>for code and images). The same words in two templates are one entry; an href that differs from page to page does not split them. A changed English segment has a new key, so a stale translation is never served — the segment falls back to English until it is translated again. -
A translation is admitted only with the structure of its source.
admissiblerequires every placeholder exactly once, nested as in the English;scripts/site-translate importandrunrefuse anything else. A translator can reorder words, never drop a link or code. -
Translation is a separate, automatic step, outside the build. The build lists what the memory lacks in
target/site-i18n/missing.jsonwith the number of pages each segment is on and publishes its coverage at/<lang>/i18n.json.scripts/site-translate run --adapterasks a model through the advisor adapters of ADR 0098, andexport/importcarry batches to translators outside the process (a session's subagents). The glossary and style a translator is given live insite/i18n/<lang>/guide.json. -
The memory is content addressed to a reader, so
project.english-onlyv4 admitssite/i18n/<lang>/as an edition: the directory says its language inedition.toml, andscripts/ci/english-only-allow.txtdeclares it with that language and a reason.
Consequences
- A new language is one line of
site/i18n/languages.json, a guide, anedition.tomland a translation run. No template, generator or page changes. - Coverage is measured, not asserted. It is reported on every build; the gate that refuses a regression, then anything below complete coverage, is the next step of this decision.
- Every change to English prose costs a translation of the segments it changed. The cost is proportional to the change, because unchanged segments keep their keys.
- The translation is machine translation and says so on every translated page, with a link to the original.
- Search, the sitemap and the JavaScript strings of the site are not yet translated; they are outside the pages this pass reads.