GEML Illustrated · profile · geml-translator/v1 (GEP-0010, draft; registered) · 中文

GEML Illustrated · geml-translator

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.

Board

10 rules, each with its source and status

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.

GEP draft 7Observed 2Doc gap 1
RuleSourceStatus
1A 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 SummaryGEP draft
2The 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.6GEP draft
3translate-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-0010GEP draft
4translator= 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-0010GEP draft
5Prerequisite: 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-0010GEP draft
6A 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-0010GEP draft
7Translate 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-0010GEP draft
8Glossary: 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-0010GEP draft
9Reference 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 · viewerObserved
10Doc 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/profilesDoc gap
Translation

A translation is embeds only

Left, the complete translated file; right, the check result and the no-profile comparison.

meta default · embed override · none keeps GEP draft check observed
tr.geml
=== 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
The GEP's prototype
=== 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}
===
geml check --root .
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
WhereWhat translate-to means
metadocument default: embeds without it inherit
embedoverrides the default
embed, value nonethis 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
Why not lang
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.
Admission
§8.6: a profile admits names only. Once declared and recognised, the key stops warning; how the processor obtains a translator (browser built-in, an OS service, an MCP tool, a shelled-out CLI) is implementation-defined, and obtaining none is still conforming.
CLI today
The reference CLI export has no translator, so --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.
Prerequisite

The embeds must tile the source

Not "embed works", but "the embedded ids must cover from the end of the meta to the end of the file". Mechanically checkable.

Measured in the GEP: 16 sections tile 1385 lines; one paragraph before a heading is silently lost GEP draft
GEML-spec.geml · 16 ## sections, no gap
Grey: L1–4, the source's H1 and the "English | 中文" switcher line, exactly the two things a translation writes itself. Blue: the 16 ## sections, L5–L1389, no gap. A 52-line translation projects 1349 lines of Markdown, byte-identical to the source's 1345 from the first section on, nested embeds included.
Counter-example: a paragraph before the first heading
Red: a paragraph after the meta and before the first heading. 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.
Rules
RequirementWhy
Embedded ids tile from the end of the meta to the end of the fileembed projects only addressable units; unaddressable prose cannot cross and cannot be translated
Coverage is provable from geml list's line rangesa 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 sectionbare paragraphs and nested blocks come along, not just the heading line (see page 5)
Where the pressure is
The GEP: this puts gentle pressure on source documents, and that is where pressure belongs: prose nobody can address is prose nobody can embed, translate, or block-edit either.
Preserve

What a translator must keep verbatim, and how to send a whole block

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.

Four kinds of immovable content, one no-half-output rule, one placeholder protocol GEP draft
One source sentence before it reaches the translator

Run geml check before you [publish](#pub); see §8 and $x^2$.

The whole block goes out in one call, immovable spans replaced by placeholders:

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.
Must be preserved
WhatRule
verbatim inline atomscode 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 keysand attribute values that name things rather than say them: format=, src=, translate-to=
block structuresame 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.
Why whole blocks
The reference implementation once sent inline text nodes one by one: 133 calls and 9798 characters for MANIFESTO.geml, 57 strings under 25 characters, " / " 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.
Three placeholder properties
Inert in any target language; allowed to move; recovery must verify. A failed verification takes the no-partial-output path: a hole in a sentence is exactly what that rule already forbids.
Glossary

A glossary pins the terms an engine would decide afresh each time

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.

The meta references a hidden table, the projection layer applies it GEP draft
GEML · the translated file
=== 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}
===
Three existing rules
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 translationsettled 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
Evidence

Probes and sources

check uses the current in-repo build, 1.9.2; the tiling and splitting numbers come from GEP-0010's own measurements.

Probe / sourceResultMaps to
tr.geml (with profile)0 diagnosticstranslation, board 2, 9
tr-noprofile.geml1 warning unknown attribute translate-toboard 2
geml cli.ts:830–848export without a translator emits the source text plus a noteboard 9
GEP-0010 · The prerequisite16 sections tile L5–L1389, 1345 lines byte-identical; a paragraph before the heading is silently losttiling
GEP-0010 · Translate a block133 calls, 57 short strings, 35% of blocks splitpreserve
ls spec/profilesno geml-translator directory; no index row in the profiles READMEboard 10