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

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 中的每个诊断都必须移植到使用这个新基础设施,否则它们无法被翻译。这是一项大量的工作,因此诊断工作组选择将翻译工作与过渡到“诊断结构体”(稍后会详细介绍)相结合,并同时完成这两项工作。

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

每个与诊断翻译相关的拉取请求都使用 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 特性中,有一个成员函数将特性转换为要发出的 Diagnostic

使用诊断派生...

诊断派生(整个诊断的 SessionDiagnostic、诊断部分的 SessionSubdiagnostic 或 lint 的 DecorateLint)可用于自动实现诊断特性。

首先,在当前 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,
        )
    }
}

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

示例

有关移植为使用诊断派生或手动编写的诊断的更多示例,请参阅以下拉取请求

有关更多示例,请参阅标记为 A-translation 的拉取请求。

添加翻译资源...

诊断派生中的每个 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 标签添加到您所做的任何拉取请求中,以便我们可以跟踪谁做出了贡献!您可以使用 rustbot 来标记您的 PR(如果它不是由 triagebot 自动标记的)

@rustbot label +A-translation

您还可以通过发布包含以下内容的评论(或将其包含在 PR 描述中)来分配诊断工作组的成员来审查您的 PR

r? rust-lang/diagnostics

即使您不确定如何进行,也可以试一试,您可以在 #t-compiler/wg-diagnostics 中寻求帮助,或联系 @davidtwco。请查看 #100717 以获得有关从哪里开始的指导!

常见问题解答

这是一个有人需要的功能吗?

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

翻译 X 不是更有价值吗?

Rust 项目中有很多不同的领域,国际化将是有益的。诊断并没有比项目中的任何其他部分优先,只是编译器团队有兴趣支持此功能。

难道不应该将编译器开发人员的时间花在其他地方吗?

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

翻译会是可选的吗?

翻译将是可选的,如果你不想使用它们,你就不需要使用。

用户将如何选择语言?

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