A translation is the source document projected along the language axis, the same shape as a .md projected along the format axis: the translated file holds only embed blocks, each saying with translate-to= which language it wants, the processor projects the source's prose across, and every other byte stays as it was. Ids, order and non-prose content have one home, so drift is not detected but impossible. This page lays out GEP-0010's rules, the one key the profile admits, what a translator must preserve, and how far the reference implementation goes today.
GEP draft defined by GEP-0010; the profile is registered, the spec has not adopted it. Observed what the tool does today. Doc gap documentation that should exist and does not.
| Rule | Source | Status | |
|---|---|---|---|
| 1 | A translation is a projection along the language axis, reusing GEP-0006's shape along the format axis. The translated file holds only embeds, nothing else; block ids, order and non-prose bytes have one home, so drift is impossible. | GEP-0010 Summary | GEP draft |
| 2 | The profile geml-translator/v1 admits one attribute key: translate-to on embed. §8.6.1 lets a profile admit attribute keys, so no spec change is needed. | profiles.ts · §8.6 | GEP draft |
| 3 | translate-to is one key in two places: on the meta it is the document default, on an embed it overrides; none keeps a block untranslated ("absent" means inherit, not "do not", so untranslated needs its own spelling). Not called lang: code {lang=} says what the body already is; this says what to do. | GEP-0010 | GEP draft |
| 4 | translator= is reserved, not shipped: with one engine, a key selecting engines would "parse, do nothing, look supported". How a processor obtains a translator is implementation-defined, and having none is conforming. | GEP-0010 | GEP draft |
| 5 | Prerequisite: the embeds must tile the source, from the end of the meta to the end of the file. Measured: GEML-spec.geml's 16 ## sections tile L5–L1389 with no gap; a 52-line translation projects 1349 lines of Markdown, byte-identical to the source's 1345. A paragraph before the first heading belongs to no addressable unit and is silently lost; geml list's line ranges prove coverage mechanically, and a gap is a diagnostic waiting to be defined. | GEP-0010 | GEP draft |
| 6 | A translator must preserve: verbatim inline atoms (code spans, inline math); every reference and its target (labels may translate, targets may not); every id, class, attribute key, and attribute values that name things; block structure. No partial output: failure, timeout or unavailability leaves the block in the source language. | GEP-0010 | GEP draft |
| 7 | Translate a block in one call, not text node by text node: measured, 133 calls on MANIFESTO.geml had 57 strings under 25 characters, and 35% of prose blocks went out in fragments. Replace the immovable spans with placeholders under three properties: inert in the target language, allowed to move, and recovery must verify (each placeholder back exactly once), else the whole block falls back to the source language. | GEP-0010 | GEP draft |
| 8 | Glossary: the meta's glossary = "#id" points at a hidden table, applied by the projection layer rather than asked of the engine; the table lives in the translation, not the source, because settled renderings are a property of the translation. Three existing rules do all the work: the hidden flag, a reference because the meta cannot hold a table, and the table as an ordinary block. | GEP-0010 | GEP draft |
| 9 | Reference implementation: the CLI's --to md/html has no translator, emits the source text and adds a note that translate-to was not applied; real translation happens in the viewer through the browser's built-in Translator (translate-browser.js); parser-side translate.ts exports resolveTarget / translateInlines / translateBlocks / glossaryFrom. A document declaring the profile checks with 0 diagnostics; undeclared → unknown-attribute warning. | geml-parser/src · viewer | Observed |
| 10 | Doc gap: there is no geml-translator directory under spec/profiles/ and the profiles README index does not list it; only the profiles.ts registration and the GEP-0010 text exist. The README's "the index table and the registry are the same table said twice" no longer holds here. | spec/profiles | Doc gap |
Left, the complete translated file; right, the check result and the no-profile comparison.
=== meta title = "translated" profile = "geml-translator/v1" translate-to = "zh" %% document default === === embed {src=a.geml#budget} %% inherits zh === === embed {src=a.geml#tbl translate-to=none} %% the table stays === %% nothing else: ids, order, tables, code all live in a.geml
=== meta title = "发布" profile = "geml-translator/v1" lang = "zh-cn" source = "PUBLISHING.geml" === === embed {src=PUBLISHING.geml#topology translate-to=zh-cn} === === embed {src=PUBLISHING.geml#prereq translate-to=zh-cn} ===
tr.geml ok: no diagnostics tr-noprofile.geml (same embeds, no profile line) warning: unknown attribute `translate-to` for block type `embed` (line 4) geml tr.geml --to md (CLI export) note: `translate-to=zh` was not applied: this export has no translator, so the source text stands
| Where | What translate-to means |
|---|---|
| meta | document default: embeds without it inherit |
| embed | overrides the default |
embed, value none | this block is not translated. "Absent" means inherit, so "untranslated" needs its own spelling |
translator= | reserved. With one engine it should not be written; auto likewise, choosing from a one-member set |
code {lang=} names a programming language, a statement about what the body is; this names a natural language, an instruction about what to do to the body. Two value spaces, two parts of speech, and in a format where a name means one thing they cannot share a key. translate-to is a verb and cannot be read as either of the other two.--to md emits the source text and leaves a note saying it was not applied; real translation happens in the viewer through the browser Translator. The document does not pretend to be translated.Not "embed works", but "the embedded ids must cover from the end of the meta to the end of the file". Mechanically checkable.
geml list reports the meta at L1–3 and the heading from L7; the lines between belong to no unit. Both headings embedded, the translation reads ok: no diagnostics, and the projection simply lacks that paragraph. Nobody warns, because nobody was asked.
| Requirement | Why |
|---|---|
| Embedded ids tile from the end of the meta to the end of the file | embed projects only addressable units; unaddressable prose cannot cross and cannot be translated |
Coverage is provable from geml list's line ranges | a gap is a diagnostic waiting to be defined, not a design flaw |
Prose nobody can address goes in a text block | §3 already gave it a home; this proposal only makes the cost of not using it visible |
| An embed naming a heading takes the whole section | bare paragraphs and nested blocks come along, not just the heading line (see page 5) |
The spec cannot say how to translate, only what must survive. §4's interpolation set the precedent: no substitution inside code spans and inline math.
Run geml check before you [publish](#pub); see §8 and $x^2$.
Run ⟦1⟧ before you ⟦2⟧; see §8 and ⟦3⟧.
The translator may move the placeholders (the target language puts them where they belong); on return each must appear exactly once before being restored to the code span, the link and the math. One missing, one extra, one damaged, and the whole block falls back to the source language.| What | Rule |
|---|---|
| verbatim inline atoms | code spans, inline math: translate around them, never through |
| references and their targets | [[#id]], [t](#id), [^fn], link hrefs: labels may translate, targets may not, or §8.2(5) turns the translation into a build error |
| ids, classes, attribute keys | and attribute values that name things rather than say them: format=, src=, translate-to= |
| block structure | same blocks, same order, same ids |
| No partial output: failure, timeout, unavailability → the block in the source language. Half a translated sentence is worse than none. | |
" / " sent four times, 12 of 34 prose blocks (35%) split into fragments, up to seven. The reassembled result showed it at once: a full-width bracket opening one fragment, a half-width one closing the next. Splitting guaranteed the atoms stayed put at the cost of never sending a complete sentence.Structure does not drift; vocabulary does. A translator is called per block and remembers nothing, so a term appearing eight times is decided eight times.
=== meta profile = "geml-translator/v1" translate-to = "zh-cn" source = "MANIFESTO.geml" glossary = "#not-translated-terms" %% the meta cannot hold a table, so a reference === === table {#not-translated-terms hidden} | term | zh-cn | |---|---| | Doc-as-a-Base | 文档即真相之源 | | Single Source of Truth | 单一事实来源 | === === embed {src=MANIFESTO.geml} ===
| hidden | §4's flag for "structured content that enters the model and is not displayed"; renderers must omit it. Observed: this document checks clean and the Markdown projection has no such table |
| glossary = "#id" | §4: the meta supports no arrays, dates or nested tables, so the meta key is a reference and the table an ordinary block, the shape a data source already has in this language |
| in the translation | settled renderings are a property of the translation; the source neither knows nor should care which word its readers' language argued over. The translation is still one file |
check uses the current in-repo build, 1.9.2; the tiling and splitting numbers come from GEP-0010's own measurements.
| Probe / source | Result | Maps to |
|---|---|---|
| tr.geml (with profile) | 0 diagnostics | translation, board 2, 9 |
| tr-noprofile.geml | 1 warning unknown attribute translate-to | board 2 |
| geml cli.ts:830–848 | export without a translator emits the source text plus a note | board 9 |
| GEP-0010 · The prerequisite | 16 sections tile L5–L1389, 1345 lines byte-identical; a paragraph before the heading is silently lost | tiling |
| GEP-0010 · Translate a block | 133 calls, 57 short strings, 35% of blocks split | preserve |
| ls spec/profiles | no geml-translator directory; no index row in the profiles README | board 10 |