引擎的本地化
引擎面向人的文本提供英文之外的17种语言:nl、pl、pt-BR、pt-PT、fi、sv、da、nb、cs、uk、ru、de、fr、es、et、ja、zh-Hans。英文是源语言;en-XA是一个伪区域设置,会给每条消息加重音、括号和填充,使测试能够看出文本是否已移入目录。框架是Project Fluent(fluent-bundle、unic-langid);代码位于src/i18n/。
哪些会本地化,哪些永不本地化#
| 会本地化(供人阅读) | 永不本地化(供程序读取) |
|---|---|
| 命令行消息和错误 | result.json和report.json的JSON键和值 |
| 每次运行的摘要行 | CSV表头和各行 |
| SVG图表的文字(标题、坐标轴、图例、图注) | 文件名 |
report.html:标题、表格、说明、图注 |
错误代码(E-CLOCK-VERSION) |
| 判定词(VALIDATED、MODELLED、PARTIAL) | 验证矩阵、单位定义、出处 |
场景类型的说明(list_kinds(lang)) |
|
咨询性声明、免责声明和通知(engine-legal-*) |
同样的文本在JSON之内时,保持英文 |
iq测评活动、研究比较页面、基准排行榜、韧性报告的report.html |
证据包和观测站PNG的summary.html:两者都被哈希写入清单(证据包的清单还有签名),因此无论使用何种语言,它们都以英文生成 |
面向人的文本中的数字使用该语言的小数符号(18种语言中有15种用逗号;en、ja、zh-Hans和en-XA用小数点)。机器文件中的数字始终使用小数点。计数(名为n、count、n_*、num_*或*_count的变量)以数字形式传给Fluent,因此译文可以写{ $n -> [one] ... [few] ... *[other] ... }。
选择语言#
- 命令行:
--lang <tag>,然后是KSHANA_LANG、LC_ALL、LC_MESSAGES、LANG,最后是英文。pt指pt-BR;只有pt-PT才是葡萄牙的。zh指简体中文;繁体中文会被拒绝,并附上所支持标签的列表。 - 库:
api::run_toml_lang(toml, lang)、run_toml_bilingual、run_toml_human、run_scenario_lang、list_scenario_kinds_json_lang(lang)。 - Python:
run、run_full、run_typed、validate_toml、error_kind、list_kinds、scenario_kinds上的lang=None;run_human(toml, lang);error_info(toml, lang)。 - WebAssembly:
run_typed、run、chart_svg、summary、table_csv、run_all、error_kind、list_kinds、scenario_kinds上可选的末尾lang;run_human(toml, lang);error_info(toml, lang);register_catalogue(tag, ftl_text)。 - MCP:
run_scenario和list_scenario_kinds上的lang。
错误及其代码#
每条错误消息都有一个稳定的代码,即大写的消息标识(e-clock-version对应E-CLOCK-VERSION),在每种语言中都相同。英文命令行文本不变。在其他语言中,该行为text [E-CODE];加上--verbose时,每种语言都会在后面多出第二行code: E-CODE。API提供KshanaError::code()和error_info({kind, code, message});MCP错误以{kind, code, message}作为错误数据。应依据代码匹配,绝不依据文本匹配。
非英文运行的工作方式(运行两次规则)#
机器文件不得依赖语言。因此,非英文运行会把场景执行两次:一次用英文,其result.json、report.json和CSV就是机器文件;一次用用户的语言,用于摘要、图表和report.html。英文运行只有一次,逐字节与此前相同。
实测,原生,发布版构建,先英文后--lang de(秒):leo-navmsg-fit-interval-trade 9.2 -> 17.9,cislunar-arc-recovery 7.8 -> 14.1,lunar-station-coverage 7.8 -> 15.0,clock-ensemble 5.8 -> 11.6,leo-navmsg-model-comparison 5.1 -> 10.5,leo-ppp-convergence 2.8 -> 6.2;其余每个内置场景都在4 s之内;测量时内置的173个场景用英文合计耗时64 s。其他语言的代价恰好是两倍。WebAssembly未作测量(此处没有wasm-bindgen);按浏览器约4倍的系数,最慢的类型需要一分钟。
页面可以避免等待:先立即显示英文结果,再调用run_human(toml, lang):那只是第二次运行,它返回{summary, svg},在结果到达之前,Studio可以显示“translating...”。
0.41事项:一次运行。从(message id, arguments)的记录渲染两者。该事项经评估后未实现:t!结果在生成之后才被检查、测量和哈希(图表文字宽度、对输出文本的出处哈希),而num()在调用处完成格式化,因此记录会把占位标记放进机器文件,并使其字节取决于重放。这需要返回标记的t!/num、每个写入器处的一个定稿步骤,以及每个写入器各一份字节一致性证明。
WebAssembly#
wasm构建只内嵌英文目录。其余十六种语言每种约增加150 kB gzip。页面在选择语言时获取i18n/<lang>/engine.ftl并调用register_catalogue(tag, text);如果获取或注册失败,页面保持英文。原生、命令行和Python wheel则内嵌全部18种。
大小,由cargo build --release --target wasm32-unknown-unknown --features wasm --lib生成的未优化.wasm(在wasm-bindgen和wasm-opt之前),基线bc04e98f对照本分支:18.71 MB -> 19.88 MB 原始大小,5.43 MB -> 5.79 MB gzip,+357 kB gzip。英文目录原始大小为521 kB,其中gzip后为153 kB;其余部分是fluent-bundle及其依赖项。这一结果按实测值接受。
目录#
i18n/<tag>/engine.ftl存放各条消息,标识带前缀engine-。i18n/en/engine.ftl是源文件;其他文件由译者维护,可能缺少某些消息(每条缺失的消息各自回退到英文)。i18n/en/中的配套列表:engine-counts.txt(带计数变量的消息,用于复数形式)、engine-numbers.txt(作为占位符传入的数字)、engine-fragments.txt(运行时拼装成句的消息片段:应并入整句消息,绝不增加)。
测试#
tests/i18n_catalogue.rs证明英文:每条消息都有记录在案的原始模板,并恰好渲染为该文本;每个标识都被使用;变量一致;伪区域设置会渲染每条消息。i18n_api.rs、i18n_kinds.rs、i18n_legal.rs覆盖入口点、类型说明和法律文本。如果有人会阅读的新字符串字面量绕过了目录,i18n_ratchet.rs就会失败:每个文件的允许量在tests/fixtures/i18n/allow.json中,只能下降,绝不能上升。scripts/i18n_compare.py用基线二进制文件和当前二进制文件运行完整场景,并比较每个输出文件。
如何把字符串移入目录:docs/design/i18n-migration.md。
依赖项与许可#
该框架增加了fluent-bundle 0.16(Apache-2.0 OR MIT)和unic-langid 0.9(MIT OR Apache-2.0)及其传递依赖的crate:fluent-syntax和fluent-langneg(Apache-2.0 OR MIT)、intl-memoizer(Apache-2.0 OR MIT)、intl_pluralrules(Apache-2.0/MIT)、type-map(MIT/Apache-2.0)、self_cell(Apache-2.0 OR GPL-2.0-only:使用的是Apache-2.0这一选项,deny.toml允许这样做)、tinystr和zerovec(Unicode-3.0)、rustc-hash、smallvec、thiserror、displaydoc(MIT OR Apache-2.0)。每一个都属于deny.toml的allow列表所允许的宽松许可;thiserror出现了两个版本(1和2),multiple-versions = "warn"对此只作报告。许可列表读取自cargo metadata;cargo deny check没有安装在编写环境中,而是在集成CI中运行。