機械翻訳をすべてチェックしました。間違っていたのは、そのチェックのほうでした。
言語
翻訳キットをリリースする前に、小さなサンプルアプリでテストしました。6つの形式(JSON、Flutter ARB、Android、Apple .strings、gettext、Markdown)の70文字列を、ドイツ語、ロシア語、日本語、アラビア語、スペイン語に翻訳し、自社マシン上のvLLMで稼働するオープンなQwenモデル(Qwen3.8-Flash-Next、4ビット量子化)を使用しました。翻訳された文字列は書き込み前にすべてチェックを受けます。350件中4件が拒否されましたが、その4件とも誤った拒否でした。
チェックの内容
アプリを壊す翻訳は、翻訳がないより悪いため、すべての文字列はプレースホルダー(%1$s、{name}、{{count}})、タグ、改行、URLを保持しなければなりません。対象言語の文字体系でなければなりません。空欄、暴走(長すぎる出力)、英語のまま残っている状態であってはなりません。そして、複数形はその言語が持つカテゴリーを正確にカバーする必要があります。失敗した文字列は、問題点を明示して再翻訳を依頼し、再度失敗した場合は除外されるため、その文字列だけアプリに英語が表示されます。
4件の却下
- ドイツ語の「Backups」は「Backups」でした。 チェックは未翻訳と判定しました。ドイツ語では英語の単語を使います。1つの単語が同一であることはしばしば正しいですが、2つ以上の単語が同一であることは正しくないため、これが現在のルールになりました。
- メールアドレスを含むロシア語と日本語の行は、文字体系チェックで失敗しました。文字の60%未満がキリル文字または日本語だったためです。
[email protected]はラテン文字で書かれているからです。アドレスはプレースホルダーと同様に扱われるようになりました。変更されずに戻らなければならず、文字数にはカウントされません。 - 日本語の複数形メッセージは
{count, plural, =0{連続記録なし} other{{count}日連続}}として返ってきました。分岐{連続記録なし}にスペースがないため、プレースホルダーパターンがこれを「連続記録なし」という変数と解釈しました。複数形メッセージの分岐の波括弧は構造として扱われ、プレースホルダー名はASCIIでなければなりません。
チェックを修正した後、2回目の実行では、他のすべては翻訳メモリにあるため、モデルにその4つの文字列だけを依頼し、350件すべてを45秒で書き込みました。
失敗すると予想したが失敗しなかった部分
複数形は素朴な翻訳が破綻する箇所です。英語にはoneとotherがあり、ロシア語にはone、few、many、otherがあり、アラビア語には6つのカテゴリーがあり、日本語には単一の形しかありません。ターゲット言語のカテゴリーを正確に指定し、各形のプレースホルダーを確認しました。モデルはロシア語で「1 день, 2 дня, 5 дней」、アラビア語で6つの形すべてを出力しました。Androidの複数形は5言語すべてで初回から合格し、チェックが誤読した日本語の複数形メッセージを除くすべての複数形メッセージも合格しました。
チェックでは捉えきれないもの
次に、例題の22のWeb文字列を、Polyglot が対応する42言語すべてに翻訳しました。924文字列中924文字列が合格しました。それらを読み通したところ、1つが目立ちました。クメール語で「You are offline」が「あなたはオンラインです」という意味の文になっていました。正しい文字で書かれた整ったクメール語で欠落もなく、すべてのチェックを通過していました。チェックはアプリを壊すものを捉えますが、誤った意味は言語を読める人間でなければ捉えられません。
まとめ
機械出力の自動チェックには、検証対象となる実際の出力が必要です。架空の翻訳を用いた単体テストは合格しましたが、実際のモデルが実際の日本語を書いたことで、数分でギャップが見つかりました。Polyglotには、これらのチェックが修正済みで、4つのケースそれぞれに対するテストとともに同梱されています。チェック機能自体は無料でオープンソースです:translation-checks on GitHub。