一份译文是源文档沿语言轴的投影,和 .md 沿格式轴的投影是同一个形状:译文文件里只有 embed,每个用 translate-to= 说要哪种语言,处理器把源的散文投影过去,其余字节一个不动。id、顺序、非散文内容只有一个家,所以漂移不是被检测出来,而是不可能发生。这一页把 GEP-0010 的规则、profile 放行的那一个键、翻译器必须保留的东西、以及参考实现今天能做到哪一步,全部列出来。
GEP 草案 GEP-0010 定义,profile 已在注册表里,规范未收。实测 工具今天这样做。文档缺口 该有而没有的文档。
| 规则 | 出处 | 状态 | |
|---|---|---|---|
| 1 | 译文是沿语言轴的投影,复用 GEP-0006 沿格式轴的形状。译文文件只含 embed,别的什么都没有;块 id、顺序、非散文字节只有一个家,漂移不可能。 | GEP-0010 Summary | GEP 草案 |
| 2 | profile geml-translator/v1 只放行一个属性键:embed 上的 translate-to。§8.6.1 允许 profile 放行属性键,所以不需要改规范。 | profiles.ts · §8.6 | GEP 草案 |
| 3 | translate-to 一个键两个位置:meta 上是文档默认,embed 上覆盖;none 保留一块不译(「不写」是继承,不是「不译」,所以要有自己的拼法)。不叫 lang:code {lang=} 说正文已经是什么,这里说要做什么。 | GEP-0010 | GEP 草案 |
| 4 | translator= 保留、不发货:只有一个引擎时选引擎的键会「解析、无事发生、看着像支持」。处理器怎么拿到翻译器是实现定义,拿不到也合规。 | GEP-0010 | GEP 草案 |
| 5 | 前提:embed 必须铺满源,从 meta 结束到文件尾。实测过:GEML-spec.geml 的 16 个 ## 节零缝隙铺满 L5–L1389,52 行的译文投影出 1349 行 Markdown,与源 1345 行逐字节相同。第一个标题前的段落不属于任何可寻址单元,会静默丢失;geml list 的行范围可机械证明覆盖,缝隙是待定义的诊断。 | GEP-0010 | GEP 草案 |
| 6 | 翻译器必须保留:verbatim 行内原子(code span、行内数学);每个引用及其目标(标签可译,目标不可);每个 id、class、属性键,和命名事物的属性值;块结构。不许部分输出:失败、超时、不可用就用源语言。 | GEP-0010 | GEP 草案 |
| 7 | 整块一次翻译,不是逐文本节点:实测 MANIFESTO.geml 133 次调用里 57 个字符串短于 25 字符,35% 的散文块被拆碎送出。用占位符替换不可动的 span,三条性质:目标语言里惰性、允许移动、恢复必须校验(每个占位符回来恰一次),否则整块回退源语言。 | GEP-0010 | GEP 草案 |
| 8 | 词表:meta glossary = "#id" 指向一张 hidden 表,由投影层应用而不是问引擎;表在译文里不在源里,因为定下的译法是译文的属性。三条现有规则完成全部工作:hidden 标志、meta 不装表所以用引用、表是普通块。 | GEP-0010 | GEP 草案 |
| 9 | 参考实现:CLI 的 --to md/html 没有翻译器,输出源文并加 note「translate-to 未应用」;真正翻译在 viewer 里由浏览器内置 Translator 完成(translate-browser.js);解析器侧 translate.ts 提供 resolveTarget / translateInlines / translateBlocks / glossaryFrom。声明 profile 的文档 0 诊断,未声明 → unknown-attribute warning。 | geml-parser/src · viewer | 实测 |
| 10 | 文档缺口:spec/profiles/ 下没有 geml-translator 目录,profiles README 索引表也没列它,只有 profiles.ts 里的注册和 GEP-0010 正文。README 说「索引表和注册表是同一张表说了两遍」,此处已不成立。 | spec/profiles | 文档缺口 |
左边是完整的译文文件;右边是 check 结果和没有 profile 时的对照。
=== meta title = "translated" profile = "geml-translator/v1" translate-to = "zh" %% 文档默认 === === embed {src=a.geml#budget} %% 继承 zh === === embed {src=a.geml#tbl translate-to=none} %% 表不译 === %% 没有别的东西:id、顺序、表格、代码都住在 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(同样的 embed,没有 profile 行) warning: unknown attribute `translate-to` for block type `embed` (line 4) geml tr.geml --to md(CLI 导出) note: `translate-to=zh` was not applied: this export has no translator, so the source text stands
| 位置 | translate-to 的意思 |
|---|---|
| meta | 文档默认:没写的 embed 继承它 |
| embed | 覆盖默认 |
embed,值 none | 这一块不译。「不写」是继承,所以「不译」需要自己的拼法 |
translator= | 保留。一个引擎时不该写;auto 同理,在只有一个成员的集合里选 |
code {lang=} 命名编程语言,是关于正文是什么的陈述;这里命名自然语言,是关于要对正文做什么的指令。两个值空间、两种词性,在一个「名字只有一个意思」的格式里不能共用一个键。translate-to 是动词,读不成另外两个。--to md 输出源文并留一条 note 说明没有应用;真正的翻译在 viewer 里由浏览器 Translator 做。文档不会假装被翻译了。这不是「embed 能用」,而是「被嵌的 id 必须从 meta 结束覆盖到文件尾」。可以机械检查。
geml list 报 meta 在 L1–3、标题从 L7 起,中间的行不属于任何单元。两个标题都嵌了,译文 ok: no diagnostics,投影里就是没有那一段。没人警告,因为没人被要求。
| 要求 | 为什么 |
|---|---|
| 被嵌的 id 从 meta 结束铺到文件尾 | embed 只投影可寻址单元;不可寻址的散文投不过去、也翻不了 |
可用 geml list 的行范围证明覆盖 | 缝隙是一条等待定义的诊断,不是设计缺陷 |
源里没人能寻址的散文,包进 text 块 | §3 已经给了它一个家;这条提案只是让不用的代价可见 |
| 命名标题的 embed 取整节 | 裸段落、嵌套块一起来,不只标题行(见第 5 页) |
规范说不了怎么翻译,只能说什么必须活下来。§4 的插值早有先例:code span 和行内数学里不替换。
Run geml check before you [publish](#pub); see §8 and $x^2$.
Run ⟦1⟧ before you ⟦2⟧; see §8 and ⟦3⟧.
翻译器可以移动占位符(目标语言把它放它该在的地方),回来后每个占位符必须恰好出现一次,再还原成 code span、链接、数学。少一个、多一个、坏一个,整块回退源语言。| 什么 | 规则 |
|---|---|
| verbatim 行内原子 | code span、行内数学:绕着译,不穿过去 |
| 引用及其目标 | [[#id]]、[t](#id)、[^fn]、链接 href:标签可译,目标不可,否则 §8.2(5) 把译文变成构建错误 |
| id、class、属性键 | 以及命名事物而非陈述的属性值:format=、src=、translate-to= |
| 块结构 | 同样的块、同样的顺序、同样的 id |
| 不许部分输出:失败、超时、不可用 → 该块用源语言。半句译文比不译更糟。 | |
" / " 送了四次,34 个散文块里 12 个(35%)被拆成碎片,最多七片。拼回去的结果一眼可见:全角括号一片开、半角一片关。拆分保证了原子不动,代价是从不送出一个完整句子。结构不会漂,词汇会。翻译器按块调用、不记前事,一个术语出现八次就被决定八次。
=== meta profile = "geml-translator/v1" translate-to = "zh-cn" source = "MANIFESTO.geml" glossary = "#not-translated-terms" %% meta 装不了表,所以是引用 === === table {#not-translated-terms hidden} | term | zh-cn | |---|---| | Doc-as-a-Base | 文档即真相之源 | | Single Source of Truth | 单一事实来源 | === === embed {src=MANIFESTO.geml} ===
| hidden | §4 给「进模型不显示的结构化内容」定义的标志;渲染器必须省略它。实测:这份文档 check 干净,Markdown 投影里没有那张表 |
| glossary = "#id" | §4:meta 不支持数组、日期、嵌套表,所以 meta 键是引用,表是普通块——数据源在这个语言里本来就是这个形状 |
| 在译文里 | 定下的译法是译文的属性,源不知道也不该在意读者的语言争论过哪个词。译文仍是一个文件 |
check 用仓库内当前构建,1.9.2;铺满和拆分的数字来自 GEP-0010 正文自己的实测。
| 探针 / 来源 | 结果 | 对应 |
|---|---|---|
| tr.geml(有 profile) | 0 诊断 | 译文、看板 2、9 |
| tr-noprofile.geml | 1 warning unknown attribute translate-to | 看板 2 |
| geml cli.ts:830–848 | 导出无翻译器时输出源文并加 note | 看板 9 |
| GEP-0010 · The prerequisite | 16 节铺满 L5–L1389,1345 行逐字节相同;标题前段落静默丢失 | 铺满源 |
| GEP-0010 · Translate a block | 133 次调用、57 个短串、35% 块被拆 | 保留 |
| ls spec/profiles | 无 geml-translator 目录;profiles README 无索引行 | 看板 10 |