Rust 编译器诊断翻译机制:基于 Fluent 的可本地化错误消息(rustc 开发指南)

发布时间:2026/9/12 2:09:20
Rust 编译器诊断翻译机制:基于 Fluent 的可本地化错误消息(rustc 开发指南) Rust 编译器诊断翻译机制基于 Fluent 的可本地化错误消息rustc 开发指南【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust本篇基于 Rust 官方仓库中 rustc 开发指南的诊断翻译文档translation.md系统讲解 rustc 如何使用 Fluent 资源描述可翻译的诊断消息包括消息标识符、属性attribute与参数argument的写法约定、消息命名规范、编译期校验机制以及DiagMessage、set_arg、IntoDiagArg等底层类型的真实源码实现。读完后你将能够正确地为 rustc 新增可翻译诊断并理解消息从 Rust 字符串到最终本地化输出的完整链路。基础设施现状等待重新设计的翻译管线原文档首先以一个醒目的警示框说明了当前状态截至 2024 年 10 月文档内带 date-check 标记的时间rustc 的诊断翻译基础设施给编译器贡献者带来了一些摩擦整套基础设施正在等待一次重新设计而且当时并不存在进行中的重设计提案。文档给出的实用建议是当前翻译基础设施不被强制使用——它仍处于等待重设计与返工的状态如果你想用或者它能让你的代码更干净就使用它如果你需要更高的灵活性可以绕开翻译基础设施。对于贡献者而言这一点直接影响技术选型新写诊断时不必把可翻译性当作硬性门槛可以按 Diagnostic 与 subdiagnostic struct 文档 中介绍的方式自由组织代码。两种编写可翻译诊断的方式rustc 的诊断基础设施基于 Fluent 支持可翻译诊断原文中的 [Fluent] 链接指向 Project Fluent 官方文档此处不再输出外部链接。原文档明确列出两种写法简单诊断使用#[derive(Diagnostic)]/#[derive(Subdiagnostic)]派生。所谓简单诊断指那些在决定输出哪些子诊断时不需要复杂逻辑、因而可以用诊断 struct 静态表示的诊断。详细的属性语法#[diag]、#[label]、#[note]、#[suggestion]等参见 diagnostic-structs.md。复杂诊断在Diagnostic或Subdiagnostic的手写实现中通过DiagAPI 使用带类型的标识符typed identifiers。文档还强调了一条重要的工作流规则新增或修改可翻译诊断时你不需要关心翻译本身只需要更新最初的英文消息。翻译由翻译团队负责贡献者只维护英文源消息。Fluent 基础非对称本地化为什么需要 FluentFluent 围绕非对称本地化asymmetric localization构建其目标是把翻译的表达力与源语言对 rustc 而言是英文的语法解耦。在引入翻译之前rustc 诊断几乎完全依赖插值interpolation来拼出展示给用户的信息。被插值的字符串很难翻译写出通顺的译文可能需要更多、更少、甚至完全不同的插值点而这些差异都要求改动编译器源码才能支持。Fluent 让消息与插值解耦从根上解决了这个问题。消息标识符identifier诊断消息定义在 Fluent 资源中。某一种语言locale如en-US的 Fluent 资源合集称为Fluent bundle。最小的消息定义形如typeck_address_of_temporary_taken cannot take address of a temporary这里typeck_address_of_temporary_taken是该 Fluent 消息的标识符对应英文诊断消息其他语言可以各自写出对应的 Fluent 资源。因此每条诊断至少有一条 Fluent 消息。属性attribute子诊断的约定按约定子诊断subdiagnostic的消息以属性形式挂在主消息上——即.attribute-name语法表示的附加相关消息typeck_address_of_temporary_taken cannot take address of a temporary .label temporary value在上例中label是typeck_address_of_temporary_taken的属性对应该诊断附加的 label 消息。参数argument向消息注入上下文诊断消息经常需要把额外上下文插进展示内容比如某个类型名或变量名。这些上下文以参数的形式提供给 Fluent 消息typeck_struct_expr_non_exhaustive cannot create non-exhaustive {$what} using struct expression该消息引用了一个名为what的参数它必须实际存在参数的具体注入方式见下文参数一节。其余 Fluent 语法用法可查阅 Project Fluent 官方文档。消息命名规范Guideline for message namingFluent 通常用连字符-分隔消息名中的单词但同样接受下划线_。由于 Rust 侧的标识符本身用_分隔为了两边风格一致rustc 内部不允许用-分隔单词统一推荐使用_。唯一例外是前导的-例如-passes_see_issue这类以连字符开头的消息名。编写可翻译消息的准则原文档给出的核心准则只有一条但非常关键为了让消息能被翻译成任何语言任何语言可能需要的全部信息都必须作为参数提供给诊断而不仅仅是英文消息用到的那些信息。文档同时坦承随着编译团队积累写齐所有翻译所需信息的经验本页会持续补充更多指引。目前 Project Fluent 文档中有大量不同 locale 的翻译范例展示了代码需要为翻译提供哪些信息可作为编写时的参照。编译期校验与带类型标识符#[derive(Diagnostic)]宏会对 Fluent 消息做编译期校验构建编译器时Fluent 资源中任何解析错误都会以编译错误的形式报出从而阻止非法的 Fluent 资源在编译器运行时引发 panic如果多条 Fluent 消息使用了相同的标识符同样会在编译期报错。这意味着翻译相关的大部分错误被前移到了编译阶段而不是等到用户运行编译器时才暴露。内部实现诊断系统为支持翻译做了哪些改造下面结合本仓库源码印证文档Internals一节的描述。核心类型集中在 rustc_error_messages 中。消息DiagMessagerustc 全部传统诊断 API例如struct_span_err、note都接受任何能转换为DiagMessage的消息。原文档说不可翻译的消息就是String可翻译消息是携带 Fluent 消息标识符的static str有时还带一个表示属性的static strDiagMessage不需要被直接操作每个 Fluent 资源中的诊断消息都会生成DiagMessage常量或在 diagnostic derive 的宏生成代码中创建DiagMessage为一切可转字符串的类型实现了Into并把它们转换成不可翻译诊断——这正是所有既有诊断调用保持可用的原因。从当前源码看compiler/rustc_error_messages/src/lib.rs 中DiagMessage是一个双变体枚举pub enum DiagMessage { /// 不可翻译的消息或已被提前翻译eagerly translated的消息 Str(Cowstatic, str), /// 内联 Fluent 消息包含待翻译的诊断消息 Inline(Cowstatic, str), }注释还解释了Str变体产生的一条重要路径部分诊断包含重复的子诊断同一批插值变量会被不同取值多次实例化这类子诊断的消息在添加进父诊断时就会被翻译——这是Str变体的主要来源之一。文件头部的类型注释也写明DiagMessage本身旨在在诊断完全可翻译后移除印证了文档中基础设施处于过渡期的定位。此外MultiSpan的 clone_ignoring_labels 方法的注释直接呼应了参数机制克隆MultiSpan时丢弃标签因为已翻译的消息在缺少对应诊断参数的情况下会翻译失败而参数通常不会随Span一起克隆。参数set_arg与DiagArgValueFluent 消息中要插值的上下文需要显式提供。诊断提供set_arg函数注入这些上下文参数有名字如前文的what和值。文档说参数值用DiagArgValue表示就是字符串或数字从当前源码看它已经扩展为三种变体compiler/rustc_error_messages/src/lib.rspub enum DiagArgValue { Str(Cowstatic, str), // 会被转换为 FluentNumber底层是 f64。i32 可安全放入 f64 // 更大的整数在 into_diag_arg 中转为字符串存入 Str 变体 Number(i32), StrListSepByAnd(VecCowstatic, str), }StrListSepByAnd变体用于把字符串列表按语言习惯and 连接的列表本地化渲染其实现 fluent_value_from_str_list_sep_by_and 借助icu_list::ListFormatterICU 数据由 rustc_baked_icu_data 提供locale 解析失败时回退到英文EN。rustc 类型通过实现IntoDiagArgtrait 转换为字符串或数字常见类型如Tytcx已有现成实现。该 trait 定义为自定义 trait 而非From是为了让其他rustc_*crate 的类型也能实现它compiler/rustc_error_messages/src/lib.rspub trait IntoDiagArg { /// 把 Self 转换为可用于诊断展示的 DiagArgValue。 /// 接受一个 path当值太长、不适合直接显示在终端时可写入该路径…… fn into_diag_arg(self, path: mut Optionstd::path::PathBuf) - DiagArgValue; }文档强调的用法差异在这里落地set_arg调用在 diagnostic derive 下是透明处理的宏生成代码自动完成使用诊断 builder API 手写时则需要手动添加。一个典型的宏展开效果可对照 diagnostic-structs.md 中ExpectedReturnTypeLabel的示例derive 生成的代码里diag.set_arg(expected, expected)与diag.span_label(span, expected{$expected}because of return type)是成对出现的——前者注入参数后者引用{$expected}。参数在渲染侧的最终去向可以在错误输出管线中看到compiler/rustc_errors/src/formatting.rs 中format_fluent_str会为消息临时构造一个FluentBundle默认en-US把DiagArgMap转换为FluentArgs后完成插值DiagMessage::Inline变体即走这条提前翻译路径EagerDiagMessageBuilder。而建议suggestion消息同样会带参数插值见 compiler/rustc_errors/src/emitter.rs 中format_diag_message(sugg.msg, fluent_args)的调用。Fluent bundle 上的自定义函数翻译管线还向 bundle 注册了一个名为STREQ的 Fluent 函数compiler/rustc_error_messages/src/lib.rs它对两个字符串参数做相等比较并返回布尔值的字符串形式使 Fluent 消息模板内部可以进行简单的条件比较进一步减少对 Rust 侧逻辑的依赖。Fluent 运行时依赖由 compiler/rustc_error_messages/Cargo.toml 中的fluent-bundle 0.16提供。小结贡献者操作清单结合文档与源码为 rustc 新增或修改可翻译诊断时优先用#[derive(Diagnostic)]/#[derive(Subdiagnostic)]简单诊断复杂场景再手写Diagnostic/Subdiagnostic实现只写英文源消息标识符用_分隔前导-除外子诊断消息写成消息的属性.label等把任何语言可能需要的上下文全部作为参数提供——derive 场景下未加注解的字段自动成为 Fluent 变量手写场景下用set_arg显式注入依赖编译期校验兜底Fluent 解析错误与标识符重复都会在构建阶段暴露不会变成运行时 panic记住当前基础设施处于等待重设计的状态它不被强制使用——需要更高灵活性时可以绕开但能用则优先用保持代码整洁。原文档中指向各 cratemessages.ftl资源文件与DiagMessageAPI 文档的链接均指向仓库外部站点本文按仓库内相对路径给出等价入口消息类型定义见 compiler/rustc_error_messages/src/lib.rs派生宏语法见 src/doc/rustc-dev-guide/src/diagnostics/diagnostic-structs.md。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考