msgctxtの使用: Gettext翻訳にコンテキストを追加する

「Book」という英単語をドイツ語に翻訳するよう依頼すると、翻訳者は自信満々に「Buch」と返してきました。3週間後、バグ報告が届きました。予約フォームの「Book a table」ボタンが「Buch a table」と表示されている、名詞であって動詞ではないのです。単語は正しかった。コンテキストが不足していたのです。これはソフトウェアのローカリゼーションにおいて最もよくある、静かな失敗の一つであり、その解決策はGettextに何十年も前から存在しています。それはmsgctxtです。
もしあなたが、ある場所では正しかった単一の英単語が、別の場所では意味不明になってしまうような翻訳を出荷したことがあるなら、それはあいまいさの問題に直面したことになります。同じソースワードが、出現する場所によって異なる翻訳にマッピングされるのです。msgctxtがなければ、翻訳者(人間であろうとAIであろうと)はこれらのケースを区別する方法がありません。なぜなら、彼らが見るのは裸の文字列msgid "Book"だけだからです。このガイドでは、Gettextにおけるmsgctxtの仕組み、_x()と_ex()を使ってソースコードをマークアップする方法、.poファイルにどのように表示されるか、そしてコンテキストを意識した翻訳パイプラインが、これらの文字列を大規模に正しく処理する唯一の信頼できる方法である理由を説明します。
あいまいさの問題:一つの単語、多くの意味
自然言語には、英語では一つのトークンに集約されるが、他の言語では複数の単語に分かれる言葉がたくさんあります。翻訳者はソース文字列しか見ないため、無関係な2つのUI要素が同じ英単語を共有している場合、それらは同じmsgidを共有し、Gettextはそれらを1つのエントリーに結合します。
言語間で分かれる単語
3つの典型的な例を考えてみましょう。
- 「Book」- 名詞(棚の上の物体)対 動詞(「飛行機をBookする」)。ドイツ語ではBuch対buchen。
- 「Post」- コンテンツを公開する対メールを送る。フランス語ではpublier対courrier。
- 「Order」- シーケンス/ソート対購入。スペイン語ではorden対pedido。
英語では、コードは両方の場所で同じリテラル文字列を使用します。
// Somewhere in a library catalog screen
echo __( 'Book', 'mytextdomain' );
// Somewhere in a reservation form button
echo __( 'Book', 'mytextdomain' );
同じ文字列がどのように1つのエントリーに集約されるか
wp i18n make-potまたはxgettextを実行すると、両方の呼び出しが1つの.poエントリーに集約されます。
#: catalog.php:42 reservation-form.php:88
msgid "Book"
msgstr ""
入力すべきmsgstrはちょうど一つしかありません。翻訳者が何を選んだとしても、2つの画面のどちらかが間違った表示になります。翻訳者が問題を理解していたとしても、この問題を修正することはできません。なぜなら、フォーマット自体が1つのmsgidに対して2つの翻訳を提供することを許していないからです。あいまいさはデータに組み込まれているのです。
msgctxtとは何か、そしてどのようにあいまいさを解消するか?
msgctxtは、翻訳エントリーに付加されるコンテキスト文字列です。簡潔に言えば、msgidと並んで2つ目のキーを追加することで、Gettextが(context, msgid)を一意のペアとして扱います。同じmsgidを持ちながら異なるmsgctxtを持つ2つのエントリーは、2つの独立して翻訳可能な文字列になります。
Gettextのルックアップモデルでは、通常、翻訳はソース文字列のみをキーとしています。msgctxtを使用すると、キーはコンテキストとソースの組み合わせになります。これがそのメカニズムの全てであり、これほどきれいに機能する理由です。表示されるテキストを変更するのではなく、内部のルックアップキーのみを変更しているのです。
.poファイル内のmsgctxt行
.poファイルでは、コンテキストはmsgidのすぐ上に独自の行として表示されます。
#: catalog.php:42
msgctxt "noun"
msgid "Book"
msgstr "Buch"
#: reservation-form.php:88
msgctxt "verb"
msgid "Book"
msgstr "Reservieren"
これで2つのエントリーができました。これらは同じmsgid値を持ちますが、異なるmsgctxt値を持つため、それぞれが独自のmsgstrを持ちます。カタログはBuchとレンダリングし、予約ボタンはReservierenとレンダリングします。コードがどのコンテキストを使用すべきかを伝えたため、ランタイムが正しい方を選択します。
上記の壊れた単一エントリーのバージョンと比較してください。違いは翻訳の品質ではなく、データモデルがそもそも正しい答えの存在を許容しているかどうかです。
コードのマークアップ: _x()と_ex()
msgctxtを.poテンプレートに出力するには、通常の翻訳関数ではなく、コンテキストを認識する翻訳関数を呼び出します。WordPressではこれらは_x()とそのエコーする兄弟である_ex()であり、生のGettextではpgettext()にマッピングされます。
_x()と_ex()の選択
単純な__()関数は文字列とテキストドメインを受け取ります。コンテキストバリアントはコンテキストを2番目の引数として挿入します。
// Without context - ambiguous
__( 'Book', 'mytextdomain' );
// With context - the second argument is the msgctxt
_x( 'Book', 'noun', 'mytextdomain' ); // returns the translated string
_ex( 'Book', 'verb', 'mytextdomain' ); // echoes it directly
// Real-world disambiguation
_x( 'Post', 'verb: publish content', 'mytextdomain' );
_x( 'Post', 'noun: mail item', 'mytextdomain' );
_x( 'Order', 'sequence or sorting', 'mytextdomain' );
_x( 'Order', 'customer purchase', 'mytextdomain' );
2番目の引数はコンテキストラベルです。これはユーザーには決して表示されません。Gettextのルックアップと、翻訳を行っている人(またはもの)の両方にとって、純粋にあいまいさを解消するために存在します。コンテキストは短く、説明的なヒントとして記述してください。例:'noun'、'verb'、'button label'、'admin menu'。'1'のような漠然としたコンテキストは技術的には有効ですが、翻訳者には役に立ちません。
テンプレートを再生成すると、エクストラクターはこれらの呼び出しを認識し、msgctxt行を出力します。Poeditや他のPOエディターのようなツールは、エントリーをコンテキスト別に視覚的にグループ化するため、人間翻訳者は「Book(名詞)」と「Book(動詞)」が2つの異なる作業であることをすぐに理解できます。まだ抽出ツールチェーンを構築している途中であったり、変数がこれらの文字列とどのように相互作用するかで悩んでいる場合は、コード変数を壊さずにPOファイルを翻訳する方法に関する私たちのガイドで、関連するワークフローを深く掘り下げて解説しています。
なぜ素朴な翻訳者(そして多くのAIツール)はこれを間違えるのか
ここからが厄介な部分です。ソースにmsgctxtを追加することは必要ですが、それだけでは十分ではありません。コンテキストは、翻訳を行うものが実際にそれを読み取って初めて役立ちます。
破棄されるコンテキストの失敗モード
素朴なバッチ翻訳スクリプトは次のように動作します。エントリーをループし、各msgidを取得し、それを翻訳APIに送信し、結果をmsgstrに書き込みます。msgctxt行を見ることは決してありません。そのため、あなたが_x( 'Book', 'noun' )と_x( 'Book', 'verb' )と慎重に記述したにもかかわらず、スクリプトは2つの同一のリクエスト(「Bookを翻訳せよ」)を送信し、同じ答えを両方に貼り付けます。あなたのマークアップ努力はすべて最終段階で破棄されてしまうのです。
多くの汎用AI翻訳ツールも同じ盲点を持っています。これらはテキストを翻訳するように作られており、msgctxtはメタデータであってテキストではありません。ツールが.poファイルをモデルに送信する前に文字列のリストにフラット化してしまうと、コンテキストはモデルのプロンプトに到達しません。何の信号も持たないモデルは、単語の統計的に最も一般的な意味(通常、「Book」であれば名詞、「Post」であれば公開するという意味)にデフォルト設定され、少数派のケースをサイレントに誤訳します。あなたはエラーを目にすることはないでしょう。数週間後にドイツのユーザーからバグ報告を受け取ることになるでしょう。
コンテキストと複数形は同じ根本原因を共有する
ここで複数形処理とコンテキストが交差します。なぜなら、どちらもファイルが平坦なテキストとして扱われるのではなく、ツールがGettextの構造を理解していることに依存するからです。文字列が数に基づく形式も含む場合、同じ構造的認識が重要になります。これについては、Gettextの複数形の理解で詳しく解説しています。
コンテキストを認識するパイプラインがmsgctxtをどのように使用するか
msgctxtを尊重する翻訳パイプラインは、素朴なスクリプトとは逆のことを行います。.poファイルを構造化データとして解析し、各エントリーのコンテキストをソース文字列に付加し、そのコンテキストをプロンプトの一部としてAIに渡します。これにより、モデルは「Book」を抽象的にではなく、具体的に動詞として翻訳していることを認識します。
第一級のシグナルとしてのコンテキスト
これこそがSimplePoTranslateがこの問題に取り組む方法です。そのコンテキスト認識AIは、msgctxt行を第一級のシグナルとして読み取ります。2つのエントリーがmsgidを共有しながらコンテキストが異なる場合、それらは実際に異なる文字列として翻訳され、コンテキストヒントがモデルの単語選択に影響を与えます。その結果、「Book(名詞)」はBuchとして返され、「Book(動詞)」はbuchenまたはreservierenとして返されます。これは自動的に行われ、あいまいな用語を一つずつ手動で修正する必要はありません。
同様に重要なのは、パイプラインが出力にmsgctxt行を保持することです。機能の劣るツールでは、ラウンドトリップ中にコンテキストを削除し、2つのエントリーを静かに1つに戻してしまい、次のマージで再びあいまいさを導入する可能性があります。完全なGettextサポートとは、コンテキストが翻訳、.moコンパイル、およびその後の再インポートを通じて生き残ることを意味します。これにより、あいまいさの解消は永続的なものであり、一度限りの手動パッチではありません。これらの文字列内のプレースホルダーに対する構文ロックと組み合わせることで、文脈的に正しく、構造的に無傷な翻訳が得られます。
マークアップの決定は依然としてあなた次第です。特定の「Book」が動詞であることを知っているのはあなただけです。しかし、一度ソースを_x()と_ex()でアノテーションすれば、コンテキストを認識するパイプラインが、そのアノテーションをすべてのターゲット言語で正しい翻訳に変換し、文字列ごとに細かく管理する必要がなくなります。
結論
あいまいさの問題(一つの英単語、多くの意味)は無視できる特異なものではなく、Gettextが文字列を保存する方法の構造的特徴です。解決策はmsgctxtです。あいまいな文字列をソースコードで_x()と_ex()を使ってアノテーションし、各出現箇所に明確なコンテキストラベルを与え、Gettextに(context, msgid)ペアで翻訳をキー付けさせます。この部分はあなたの責任であり、数分で完了します。
より難しい部分は、翻訳ステップがそのコンテキストを無視して捨て去るのではなく、実際に尊重するようにすることです。素朴なスクリプトやテキストのみのAIツールはmsgctxtを破棄し、あなたが防ごうとしていたまさにそのバグを再導入します。コンテキストを認識するパイプラインは、コンテキストを読み取り、各あいまいさ解消されたエントリーを正しく翻訳し、コンパイルと再インポートを通じてそれを保持します。
コンテキストを無視した誤訳を出荷するのをやめたいですか? SimplePoTranslateを無料でお試しください — クレジットカードは不要です。無料プランでは、完全なmsgctxtコンテキストサポートを備えた実際の
.poおよび.potファイルを処理し、あいまいさ解消された文字列が初回から正しく翻訳されます。