为诊断信息翻译工作贡献力量!

2022 年 8 月 16 日 · David Wood 代表 编译器团队

Rust 诊断工作组正在主导一项工作,旨在为编译器中的错误消息添加国际化支持,使编译器能够输出非英语语言的信息。

例如,考虑以下诊断信息,其中用户使用冒号而非箭头来指定函数的返回类型

error: return types are denoted using `->`
 --> src/main.rs:1:21
  |
1 | fn meaning_of_life(): u32 { 42 }
  |                     ^ help: use `->` instead

我们可以用中文输出该诊断信息

错误: 返回类型使用`->`表示
 --> src/main.rs:1:21
  |
1 | fn meaning_of_life(): u32 { 42 }
  |                     ^ 帮助: 使用`->`来代替

甚至用西班牙语

error: el tipo de retorno se debe indicar mediante `->`
 --> src/main.rs:1:21
  |
1 | fn meaning_of_life(): u32 { 42 }
  |                     ^ ayuda: utilice `->` en su lugar

翻译后的错误消息将允许非英语母语用户以他们偏好的语言使用 Rust。

当前状态如何?

诊断信息翻译的实现已经开始,但我们正在寻求帮助!

诊断信息翻译的核心基础设施已在 rustc 中实现;这使得 Rust 能够发出包含翻译后消息的诊断信息。然而,rustc 中的每个诊断信息都必须移植到使用这个新的基础设施,否则它们就无法被翻译。这是大量工作,因此诊断工作组决定将翻译工作与向“诊断结构体”(后续会详细介绍)的过渡结合起来,并同时完成这两项工作。

一旦大多数诊断信息消息被移植到新的基础设施,诊断工作组就会开始创建工作流程,供翻译团队将所有诊断信息消息翻译成不同的语言。

每一个与诊断信息翻译相关的 Pull Request 都被打上了标签 A-translation

如何参与

诊断信息翻译工作量很大,但好消息是很多工作可以并行完成,而且不需要有编译器开发背景或熟悉 rustc 就可以做出贡献!

如果你有兴趣参与,请查看 #100717 了解如何开始! 你可以在 #t-compiler/wg-diagnostics 中寻求帮助或联系 @davidtwco

注意:随着工作组迭代和改进诊断信息翻译的工作流程,这篇文章不会更新,因此请始终参考开发者指南获取最新的 诊断结构体诊断信息翻译 文档。

1. 设置本地开发环境

在帮助诊断信息翻译工作之前,你需要设置好你的开发环境,因此请按照 rustc 开发者指南上的说明进行操作

2. 准备移植你的第一个诊断信息

rustc 中几乎所有的诊断信息都使用传统的 DiagnosticBuilder API 实现,其形式如下

self.struct_span_err(self.prev_token.span, "return types are denoted using `->`")
    .span_suggestion_short(
        self.prev_token.span,
        "use `->` instead",
        "->".to_string(),
        Applicability::MachineApplicable,
    )
    .emit();

struct_span_err 创建一个新的诊断信息,需要两个东西 - 一个 Span 和一条消息。struct_span_err 并不是你在编译器源代码中遇到的唯一诊断函数,但其他函数都非常相似。你可以在 rustc 开发者指南中 阅读更多关于 rustc 诊断基础设施的信息。

Span 仅用于标识用户源代码中的某个位置,你可以在整个编译器中找到它们用于报告诊断信息(例如,前面例子中 main.rs:1:21 的位置就是 self.prev_token.span)。

在这个例子中,消息只是一个字符串字面量(一个 &'static str),它需要被一个标识符替换,以表示在请求的任何语言中对应的同一条消息。

将诊断信息移植到新的基础设施有两种方法

  1. 如果是简单的诊断信息,没有任何决定是否添加建议、注意、帮助或标签的逻辑,就像上面的例子一样,那么...
  2. 否则...

在这两种情况下,诊断信息都表示为类型。使用类型表示诊断信息是诊断工作组的一个目标,因为这有助于将诊断逻辑与主要代码路径分离。

每种诊断类型都应该实现 SessionDiagnostic(手动或自动)。在 SessionDiagnostic trait 中,有一个成员函数将该 trait 转换为要发出的 Diagnostic

使用诊断派生宏...

诊断派生宏(可以是用于整个诊断信息的 SessionDiagnostic,用于诊断信息部分的 SessionSubdiagnostic,或用于 lint 的 DecorateLint)可用于自动实现诊断 trait。

首先,在当前 crate 的 errors 模块中创建一个以你的诊断信息命名的新类型(例如 rustc_typeck::errorsrustc_borrowck::errors)。在我们的例子中,可能看起来像

struct ReturnTypeArrow {

}

接下来,我们需要添加包含所有需要信息的字段 - 对我们来说,这只是一个 Span

struct ReturnTypeArrow {
    span: Span,
}

在大多数情况下,这将只是原始诊断信息发出逻辑使用的 Span 以及插入到诊断信息消息中的值。

之后,我们应该添加派生宏,添加我们的错误属性,并标注主 Span(即传递给 struct_span_err 的那个)。

#[derive(SessionDiagnostic)]
#[error(parser_return_type_arrow)]
struct ReturnTypeArrow {
    #[primary_span]
    span: Span,
}

每个诊断信息都应该有一个唯一的 slug。按照惯例,它们总是以错误所属的 crate 名称开头(本例中为 parser)。这个 slug 将被用来在我们的翻译资源中查找实际的诊断信息消息,我们很快就会看到。

最后,我们需要添加任何标签、注意、帮助或建议

#[derive(SessionDiagnostic)]
#[error(parser_return_type_arrow)]
struct ReturnTypeArrow {
    #[primary_span]
    #[suggestion(applicability = "machine-applicable", code = "->")]
    span: Span,
}

在这个例子中,只有一个建议 - 将 : 替换为 ->

在完成之前,我们必须将诊断信息消息添加到翻译资源中..

有关诊断派生宏的更多文档,请参阅rustc 开发者指南中的诊断结构体章节

手动实现 SessionDiagnostic...

有些诊断信息过于复杂,无法使用诊断派生宏从诊断类型生成。在这些情况下,可以手动实现 SessionDiagnostic

使用与“使用诊断派生宏...”中相同的类型,我们可以如下实现 SessionDiagnostic

use rustc_errors::{fluent, SessionDiagnostic};

struct ReturnTypeArrow { span: Span }

impl SessionDiagnostic for ReturnTypeArrow {
    fn into_diagnostic(self, sess: &'_ rustc_session::Session) -> DiagnosticBuilder<'_> {
        sess.struct_span_err(
            self.span,
            fluent::parser_return_type_arrow,
        )
        .span_suggestion_short(
            self.span,
            fluent::suggestion,
            "->".to_string(),
            Applicability::MachineApplicable,
        )
    }
}

不再像原始的诊断信息发出逻辑那样使用字符串作为消息,而是使用指向翻译资源的类型化标识符。现在我们只需将诊断信息消息添加到翻译资源中..

示例

有关已移植到使用诊断派生宏或手动编写的诊断信息的更多示例,请参阅以下 Pull Request

更多示例,请参阅打上标签 A-translation 的 Pull Request。

添加翻译资源...

诊断派生宏中的每个 slug 或手动实现中的类型化标识符都需要对应翻译资源中的一条消息。

rustc 的翻译使用Fluent,这是一个非对称翻译系统。对于编译器中每个发出诊断信息的 crate,在 compiler/rustc_error_messages/locales/en-US/$crate.ftl 都有一个相应的 Fluent 资源。

错误消息需要添加到这个资源中(一个宏将随后生成对应于该消息的类型化标识符)。

对于我们的例子,我们应该将以下 Fluent 内容添加到 compiler/rustc_error_messages/locales/en-US/parser.ftl

parser_return_type_arrow = return types are denoted using `->`
    .suggestion = use `->` instead

parser_return_type_arrow 将生成一个 parser::return_type_arrow 类型(在 rustc_errors::fluent 中),可以与诊断结构体和诊断构建器一起使用。

子诊断信息是主要 Fluent 消息的“属性” - 按照惯例,属性的名称就是子诊断信息的类型,例如“suggestion”,但当同一种子诊断信息有多个时,这可以改变。

现在 Fluent 资源包含该消息,我们的诊断信息就移植好了!更复杂的消息,带有插值,将能够引用诊断类型中的其他字段(手动实现时,这些作为参数提供)。有关更多示例,请参阅rustc 开发者指南中的诊断信息翻译文档。

3. 移植诊断信息

现在你已经大致了解要做什么了,你需要找到一些要移植的诊断信息。有很多诊断信息需要移植,所以诊断工作组已经将工作分派开来,以避免有人处理与他人相同的诊断信息 - 但目前参与的人不多,所以随便选一个 crate 开始移植吧 :)

请将 A-translation 标签添加到你提交的任何 pull request 中,这样我们可以追踪谁做出了贡献!你可以使用 rustbot 给你的 PR 打标签(如果 triagebot 没有自动给你打标签的话)

@rustbot label +A-translation

你还可以通过发布以下内容的评论(或将其包含在 PR 描述中)来指定诊断工作组的一名成员来评审你的 PR

r? rust-lang/diagnostics

即使你不确定具体如何进行,也可以尝试一下,并且可以在 #t-compiler/wg-diagnostics 中寻求帮助或联系 @davidtwco。查看 #100717 获取关于如何开始的指导!

常见问题

有人需要这个功能吗?

是的!一些语言社区偏好使用本地化资源,而另一些则不偏好(并且偏好在这些社区内部也会有所不同)。例如,中文社区拥有成熟的编程语言资源生态系统,这些资源不需要了解英语。

翻译 X 不会更有价值吗?

Rust 项目中有许多不同的领域,国际化会有益处。诊断信息并没有比项目的其他任何部分更优先,只是编译器团队对支持这个功能有兴趣。

编译器开发者的时间花在其他地方不是更好吗?

编译器实现不是零和博弈:在编译器其他部分的工作不受这些努力的影响,并且进行诊断信息翻译工作不会阻止贡献者进行其他工作。

翻译是选择加入的吗?

翻译将是选择加入的,如果你不想使用它们,则无需使用。

用户如何选择语言?

用户将如何选择使用翻译后的错误消息尚未决定。