自分のプラグインに日本語訳を入れたのに、利用者の管理画面がずっと英語のまま。この状態、原因が1つではありません。翻訳の質でもなく、訳し忘れでもなく、実装と配信の途中のどこかで止まっています。
止まる場所は、順番が決まっています。訳文が translate.wordpress.org に入る前で止まっているのか、入ったあと言語パックにならずに止まっているのか、パックはあるのにプラグイン側が読めていないのか。この順に見ていけば、だいたい1回で当たります。
検証環境:【WordPress のバージョン/確認日を入れる】。translate.wordpress.org と make.wordpress.org/polyglots の画面は【確認日】時点のものです。
Text Domain を書き間違えていないか
いちばん最初に見るのはここです。プラグインヘッダーの Text Domain が、WordPress.org のスラッグと一致していること。my-plugin というスラッグで公開しているなら、テキストドメインも my-plugin です。
一致していないと、翻訳文字列がプロジェクトに紐づきません。訳を書いても、そのプラグインの訳として扱われないので、いつまで待っても配信されない。ここがずれていると、以降のどの対策も効きません。
__() や _e() の第2引数も、全部このドメインで揃えます。ファイルを分けて書いていると、ここだけ古い名前が残りがちです。
grep -rn "__(\s*'" . --include="*.php" | grep -v "'my-plugin'"
拾われなかった行が、そのままドメイン違いの候補になります。
load_plugin_textdomain() は要ると思っていた
WordPress.org 経由で配布しているプラグインなら、load_plugin_textdomain() の呼び出しは不要です。コアが必要になった時点で翻訳を読みます。4.6 以降そうなっているので、いま新しく書くコードに入れる理由はありません。
問題は、残っていること自体ではなく、呼ぶタイミングです。WordPress 6.7.0 から、init より前に翻訳を読もうとすると Notice が出るようになりました。
Notice: Function _load_textdomain_just_in_time was called incorrectly.
Translation loading for the my-plugin domain was triggered too early.
This is usually an indicator for some code in the plugin or theme running too early.
Translations should be loaded at the init action or later.
出どころは自分のプラグインとは限りません。ドメイン名が出るので、そこを見ます。自分のドメインが出ているなら、プラグインのメインファイル直下やクラスのコンストラクタで __() を呼んでいます。設定配列のデフォルト値にラベルを入れている、管理メニューのタイトルをプロパティで持っている、そのあたりが多いです。
直し方は、翻訳が要る処理を init 以降に寄せるだけです。定数やプロパティに訳文を入れるのをやめて、必要になった場所で呼ぶ。フォーラムを見ると、この Notice はデバッグ表示を切って消す話に流れがちですが、消えるのは表示だけです。
訳したのに、90%に届いていなかった
translate.wordpress.org でプラグインを開くと、サブプロジェクトが分かれています。Stable (latest release)、Development、Stable Readme、Development Readme。
言語パックが生成されるのは、Stable (latest release) が90%以上翻訳されたときだと案内されています。Readme を100%にしても、本体が90%に届いていなければパックは作られません。プラグインページの説明文だけ日本語になっていて、管理画面は英語のまま、という状態はこれです。
自分のプラグインでは【翻訳率・パックが出るまでにかかった日数を入れる】でした。
ここで効いてくるのが、原文の文字列数です。デバッグ用のメッセージや管理者にしか出ないエラー文まで全部 __() で囲っていると、分母が膨らんで90%が遠くなります。訳す価値のない文字列は、そもそも翻訳関数に入れない。これは翻訳の手抜きではなく、分母の設計です。
.pot は WP-CLI で作ります。
wp i18n make-pot . languages/my-plugin.pot --slug=my-plugin
JavaScript の文字列だけ、ずっと英語だった
PHP 側が全部日本語になったあとに残るのがこれです。ブロックエディタや管理画面のスクリプトで wp.i18n.__() を使っている場合、.po / .mo とは別に JSON が要ります。
wp i18n make-json languages/ --no-purge
そのうえで、スクリプトの登録時に紐づけます。
wp_set_script_translations( 'my-plugin-admin', 'my-plugin', plugin_dir_path( __FILE__ ) . 'languages' );
--no-purge を付けずに実行すると、JSON に移した文字列が .po から消えます。同梱配布も併用しているなら、消えて困る側を先に確認してください。
言語パックで配信される場合、JSON はサーバー側で生成されます。手元で作った JSON は、同梱配布や開発中の確認のためのものです。この2系統が混ざると、手元では日本語なのに配布したら英語、という食い違いが出ます。
同梱した languages/ が、パックに勝っていた
languages/ に .mo を同梱したまま公開すると、言語パックが来ても同梱側が読まれることがあります。開発中に手元で訳を確認するために置いたファイルが、そのまま残っているパターンです。
6.5 以降は .l10n.php という形式も使われるようになったそうです。同梱ファイルを整理するときは、.mo だけ消して .l10n.php が残っている状態を作らないよう、拡張子をまとめて確認します。
ls languages/
WordPress.org で配布していて、言語パックに任せる方針なら、languages/ は .pot だけで足ります。
表記ルールで落ちるとは思っていなかった
ここから先は承認の話です。訳文を入れても、承認する権限がなければ Waiting のまま止まります。作者自身がプラグイン単位で承認できるようになる仕組みが PTE です。
日本語ロケールでは、訳の意味が合っていても表記が揃っていないと差し戻されます。半角と全角のあいだのスペース、コロンのあと、括弧の全角半角。実際に Polyglots の申請スレッドでも、日本語翻訳スタイルガイドの該当箇所を名指しで指摘されている例が並んでいます。
これは訳文だけの話ではありません。原文をこちらが書いている以上、Requires at least: 6.5 のような文字列も、訳したときにスタイルガイドに沿う形になっているかを見られます。
PTE の申請そのものの手順と、自分が一度で通らなかった理由は、ブログのほうに書きました。
順番だけ置いておきます
上から順に確認すれば、どこで止まっているか切り分けられます。
-
Text Domainヘッダーとスラッグが一致しているか -
__()の第2引数が全ファイルで揃っているか - init より前に翻訳関数を呼んでいないか(6.7 以降の Notice)
- Stable (latest release) が90%を超えているか(Readme とは別カウント)
- JS 側の JSON を生成し、
wp_set_script_translations()で紐づけているか -
languages/に古い.moや.l10n.phpが残っていないか - 訳文が日本語翻訳スタイルガイドの表記に沿っているか
【この記事で扱った6か所のうち、実際に自分が踏んだのはどれか。踏んだ順番と、そのときのバージョンを1行ずつ足す】
ところで、翻訳の分母を減らすために文字列を翻訳関数から外す判断は、どこまでやっていいものなんでしょうか。管理者にしか出ないエラー文は英語のままでいい、と自分は決めていますが、そのプラグインを日本語で使っている人にとって、エラー文こそ日本語であってほしい場面もあるはずです。ここはまだ答えが出ていません。
ふだんはraplsworks.comで、WordPressプラグイン開発やClaude Codeまわりのことを書いています。