はじめに
以下のような記事を見かけました。
ざっくり日本語訳するとこうなります:
error[E0382]: 移動された値の借用: `s1` --> src/main.rs:5:20 | 3 | let s1 = String::from("Hello, "); | -- `s1` は `Copy` トレイトを実装しない `std::string::String` 型なので、移動が発生します 4 | let s2 = s1; | -- 値はここで移動されました 5 | println!("{}", s1); | ^^ 移動後、値はここで借用されました | help: パフォーマンスコストが許容できる場合、値のクローンを検討してください | 4 | let s2 = s1.clone(); | ++++++++ このエラーについての詳細は、`rustc --explain E0382` を実行してください
わかりやすい!!
おお、たしかにわかりやすい!
あんまりダラダラしてると苛つかせてしまうので、先に結果を貼っておきます。
すごい!
(およそ人様に見せられるようなコードをしていないので、もっといい感じになったら公開します...タブン...)
えー(ケチ
というわけで、自分でも同じようなことを(雑に)やってみました。
RUSTC ラッパーの仕様
元記事には
rustcラッパーに関する情報が全然見つかりませんでした。
とありました。確かに情報はあまりありませんが、第1引数に rustc
のパスを指定されて起動される以外は、基本的に rustc
と同じように動作すればよいのだと考えました。
まずは、何もせずに rustc
を呼び出するだけの空っぽのラッパーを作成しました。
次にラッパー内で、コマンドライン引数と、標準出力、標準エラー出力、rustc
の終了コードを適当なログファイルへ出力するようにして、どのように呼ばれるのか、どのように動いているのかを観察してみました。
どうやら、cargo build
を 1 回だけ実行しただけでも、実は何度か rustc
が呼び出されることがあるようです(バージョン情報を確認したり、機能を確認したり?)。
また、実際のコンパイル時にはコマンドライン引数に --error-format=json
を付けて呼び出していることもわかりました。rustc
のコマンドライン引数の詳細は以下が参考になります。
そして実際のエラー情報は、標準エラー出力へ1行ごとにJSONとして、つまり JSONL (JSON Lines) 形式で出力されているようです。
さらに、JSON にはいくつかの種類があって、$message_type
で種類が判別できるようです。
rustc
の出力する JSON ファイルのフォーマットは以下のページが参考になりました。
今回日本語化したいコンパイルエラーメッセージは(元記事にもある通り)$message_type
が diagnostic
のものです。
日本語化の対象は、おそらく以下の項目を対象とすればよいと判断しました。
message
spans[].label
children[].message
children[].spans[].label
rendered
翻訳は、とりあえず JSON ファイルに英語と日本語のペアを記載して、置き換えるようにしています。
[
{
"en": "borrow of moved value",
"ja": "移動された値の借用しました"
},
{
"en": "value moved here",
"ja": "ここで値を移動しました"
},
{
"en": "value borrowed here after move",
"ja": "移動後の値をここで借用しました"
},
{
"en": "move occurs because `{$name}` has type `{$ty}`, which does not implement the `Copy` trait",
"ja": "`{$ty}`型の`{$name}`は`Copy`トレイトを実装していないので、移動します"
}
]
{$name}
などはプレースホルダで、実際には正規表現でキャプチャして、日本語版のメッセージの同じ場所へ埋め込むようにしました。
このあたりはかなり雑な対応で、メッセージによってはうまく変換できないような気がするのですが、まあ、とりあえず仮版ということで。
元記事では rendered
の日本語化は、自力でメッセージを組み立てる対応をしているようですが、そこまで根性がなかったので、以下のようにしてみました。
上記の message
や label
と全く同じ文字列が、rendered
にも含まれているように思われる(つまり message
や label
のメッセージの途中に ANSI エスケープシーケンスなどが挿入されていない)ので、かつほとんどが行末(あるいは後ろに付加情報がついているだけ)で、文字の長さが多少変わってもレイアウトは崩れなさそうに思いました。そこで rendered
に含まれる「message
や label
の英語文」を「それらを翻訳した日本語文」に単純置換をするようにしました。
もちろん、ソースコードにコンパイルメッセージと同じものがあると、そちらも置換されてしまいます。まあレアなケースだと思うので、重要度の低い既知のバグということにします。
他にも、コンパイルエラーメッセージの中に、他のコンパイルエラーメッセージが含まれているようなケースだと、うまく変換できないこともありえますが、暫定的に「英語文」が長いものから順番に変換するような対策をしてみました(が、うまくいかないケースもあるかもしれません)。
RUSTC ラッパーを起動する設定
元記事にも書いてある通り、RUST の標準的な機能として、環境変数 RUSTC_WRAPPER
にラッパーのパスを指定すると cargo
経由でビルドするときに、直接 rustc
を呼び出すのではなく、ラッパーを呼び出すようです。
環境変数だけではなく設定ファイルでも指定可能なようです。
設定ファイル自体は、いろいろなところに置けるみたいですが、とりあえず PROJECT_ROOT/.cargo/config.toml
に以下のように記載してみました(実際指定したパスは、ラッパーの存在するパスですが)。
[build]
rustc-wrapper = "/path/to/rustc-ja-wrapper"
実行結果は以下のような感じ
$ cargo build
Compiling a v0.1.0 (/tmp/foo)
warning: 変数が使われていません: `b`
--> src/main.rs:4:9
|
4 | let b = a[10];
| ^ help: 意図的ならアンダースコアを前に付けて下さい: `_b`
|
= note: `#[warn(unused_variables)]`はデフォルトで有効です
error: この操作は実行時にパニックします
--> src/main.rs:4:13
|
4 | let b = a[10];
| ^^^^^ 添え字が範囲外です: 長さは3、添え字は10
|
= note: `#[deny(unconditional_panic)]`はデフォルトで有効です
warning: `foo` (bin "foo") generated 1 warning
error: could not compile `foo` (bin "foo") due to 1 previous error; 1 warning emitted
動いた!
リポジトリ
急ぎでっち上げた雑なソースですが、以下のリポジトリに置いてあります。
また、翻訳用の JSON ファイルは assets/translate.json
で、コンパイル時に埋め込んでビルドするようにしています。
(環境変数で外部の JSON ファイルを指定できたほうが、簡単にメッセージを変えられて良かったかも?)
最低限の動作確認しかしていないため、実際の翻訳用 JSON ファイルを見てもらえばわかる通りごくごく一部のメッセージしか翻訳していません。
もし、他のメッセージも翻訳したいとかあれば、フォークしてもらって勝手に改造してもかまいませんし、プルリクしてもらってももちろん構いません。