結論
WordPress の _e() と __() の違いは、出力するか、返すかだけです。
__( 'テキスト', 'ドメイン' ): 翻訳した文字列を返す。変数に入れたり、他の関数に渡したりするときに使う。_e( 'テキスト', 'ドメイン' ): 翻訳した文字列をその場で echo する。戻り値はない(WordPress 1.2.0 から)。
ただし、どちらもエスケープはしません。_e() のリファレンスも「_e() はエスケープ関数ではない」とし、esc_html_e() を使うよう勧めています。実務では次のように選ぶのが安全です。
| 場面 | 使う関数 |
|---|---|
| HTML の本文として出力 | esc_html_e() |
| HTML の本文用の文字列を返す | esc_html__() |
属性値(title・alt・placeholder など)に出力 |
esc_attr_e() / esc_attr__() |
| 変数を埋め込む | printf( esc_html__( '... %s ...', 'domain' ), esc_html( $var ) ) |
| 同じ英語で意味が違う語 | _x() |
| 単数・複数で変わる語 | _n() |
原因・仕組み
__() は内部で translate() を呼び、テキストドメインに対応する読み込まれた翻訳ファイルから訳を探して返します。_e() はその結果を echo するだけの関数です。
訳文は翻訳ファイルや gettext フィルターで外から差し替えられる値です。翻訳者や他のプラグインが HTML を混ぜた場合、_e() はそれをそのまま出力します。esc_html_e() は「翻訳 → エスケープ → 出力」を1回で行うので、echo esc_html( __( ... ) ) と同じ結果になります。
Common APIs Handbook も、HTML タグの属性で使う翻訳文字列はエスケープしなければならないとし、esc_html__()・esc_html_e()・esc_attr__()・esc_attr_e() を挙げています。
手順(サンプルコード)
基本の使い分け
<h2><?php esc_html_e( 'Related posts', 'wl1' ); ?></h2>
<input type="search" placeholder="<?php esc_attr_e( 'Search…', 'wl1' ); ?>">
<?php
$label = esc_html__( 'Read more', 'wl1' ); // 変数に入れるときは「返す」版
echo '<a href="' . esc_url( get_permalink() ) . '">' . $label . '</a>';
変数を埋め込む
/* translators: %s: user name */
printf( esc_html__( 'Hello, %s', 'wl1' ), esc_html( $name ) );
Plugin Handbook は、_e( "Your city is $city.", ... ) のように変数を文字列の中に直接書くのは誤りで、printf() とプレースホルダーを使うよう説明しています。翻訳者向けのコメントは translators: で始め、翻訳関数の直前の最後の PHP コメントにします。
HTML を含めたい場合
訳文に <strong> などを含めたいときは、esc_html 系ではなく wp_kses() で許可するタグを絞ります。
echo wp_kses(
__( 'Read the <strong>terms</strong> first.', 'wl1' ),
array( 'strong' => array() )
);
動作確認(検証環境と結果)
WordPress 7.1.2(ja、ロケール ja)、PHP 8.2.12 の CLI 検証環境で実行しました。
__( 'Posts' )の戻り値は'投稿'。_e( 'Posts' )は投稿を出力し、戻り値はNULLだった。_x( 'Post', 'post type singular name' )は投稿。gettextフィルターで、wl1ドメインのRead moreの訳を続きを読む<script>alert(1)</script>に差し替えて比較した結果:_e()→続きを読む<script>alert(1)</script>(そのまま出力)esc_html_e()→続きを読む<script>alert(1)</script>esc_attr__()→ 同じくエスケープされ、title="..."の中でも属性が壊れなかったprintf( esc_html__( 'Hello, %s', 'wl1' ), esc_html( '<b>佐藤</b>' ) )はHello, <b>佐藤</b>になった(翻訳ファイルがないため英語のまま)。- 「HTML を含めたい場合」のコードに
<script>x</script>を足して試すと、出力はRead the <strong>terms</strong> first.xで、strongだけが残りscriptタグは除去された。
ログは testse-vs-translate-esc-html-e-difference.md にあります。
WordPress の _e と __ の違いの注意点
- テキストドメインには、変数や定数ではなく文字列そのものを書きます(Plugin Handbook)。翻訳文字列を抽出するツールが、ソースコードを静的に読むためです。
- テーマのテキストドメインは
style.cssのText Domainと一致させます(WordPress テーマ自作の手順|最小構成(style.css・index.php)から作る方法)。 - 第2引数を省略すると
default(WordPress 本体)のドメインになり、コアの訳が使われます。自作の文字列で省略すると、意図しない訳が付くことがあります。 __()の結果をechoするなら、最初からesc_html_e()にまとめた方が漏れがありません。
WordPress の _e と __ の違いでよくあるミス
echo __( ... )や_e( ... )をエスケープせずに使う。- 属性値の中で
esc_html_e()を使う(属性値にはesc_attr_e())。 _e()の戻り値を変数に入れようとして、空になる。- 訳文の中に変数を直接書き、翻訳ファイルに登録できない文字列になる。
WordPress の _e と __ の違いのチェックリスト
- [ ] 出力するのか、返すのかで
_e系 /__系を選んだ - [ ] 本文は
esc_html、属性はesc_attrの付いた関数にした - [ ] 変数は
printf()とプレースホルダーで埋め込んだ - [ ] テキストドメインは文字列リテラルで、テーマ・プラグインと一致している
- [ ] 必要な箇所に
translators:コメントを付けた
WordPress の _e と __ の違いのFAQ(よくある質問)
Q. 日本語サイトしか作らないのに翻訳関数は必要ですか?
A. 必須ではありません。ただし配布テーマ・プラグインや、管理画面の文言を他のコードから変えられるようにしたい場合は使います。使わないなら、日本語を esc_html() で出力すれば十分です。
Q. esc_html_e() だけで HTML を出せますか?
A. 出せません。タグもエスケープされます。HTML を含めるなら wp_kses() を使います。
Q. JavaScript の文字列は?
A. __()・_x()・_n() は JavaScript 版(wp.i18n)もあります(Common APIs Handbook)。
筆者の見解(WordPress の _e と __ の違い)
_e() と __() の違いそのものは単純ですが、実際に問題になるのはエスケープの有無だと考えます。私は、テーマやプラグインのテンプレートでは _e() と echo __() を原則使わず、esc_html_e()・esc_attr_e() を既定にするのが、レビューの手間も減って現実的だと思います。訳文は外から変えられる入力の一つ、と捉えておくと判断を誤りにくいです。
WordPress の _e と __ の違いの関連項目
- _e() の使い方|説明・引数・注意点
- __() の使い方|説明・引数・注意点
- esc_html_e() の使い方|説明・引数・注意点
- esc_html__() の使い方|説明・引数・注意点
- _x() の使い方|説明・引数・注意点
- _n() の使い方|説明・引数・注意点
- load_theme_textdomain() の使い方|説明・引数・注意点
- esc_html と esc_attr の違い|使い分けと esc_url が必要な場面

