PR

i18nとPOT・PO・MOファイルの解説

ブログ運用

あなたのプラグインを世界へ!WordPress国際化(i18n)対応ガイド

WordPressの大きな魅力の一つは、世界中のユーザーに利用されていることです。

あなたが作成した個人的なプラグインも、国際化(通称:i18n)に対応させることで、公開申請をした際には、世界中のユーザーが自国の言語でメニューや操作ボタンを使えるようになります。

今回は、WordPressプラグインを多言語対応させる際に、開発者がPHPスクリプト内で具体的に行うべき基本的な作業について、私の体験を交えて解説します。備忘録も兼ねているため、やや冗長な文章になってしまいました。

スポンサーリンク

国際化に必要な2つの作業

WordPressの国際化機能は、「ソースコード中の翻訳関数でラップされた文字列を、テキストドメインに基づいて翻訳ファイルから取得した対応する言語の文字列に置き換えて表示する」という仕組みで動作します。

言い換えると、次の2点を守れば国際化の土台は完成します。

  1. 翻訳可能なすべての文字列を国際化関数(i18n用関数)でラップする
    • 出力する文字列をエスケープ関数でラップする
  2. 翻訳ファイル(.moファイル)を用意する

この仕組みによって、WordPress自体が実行時に自動で翻訳ファイルを読み込み、適切な言語へ置き換えます。

◯ 翻訳可能なすべての文字列を国際化関数(i18n用関数)でラップする

国際化対応の核心は、PHPスクリプト内の出力メッセージ(表示させたい全ての文字列)を、WordPressの提供する国際化関数でラップ(囲む)することです。

これらの関数を使うことで、WordPressは実行時に翻訳対象の文字列として認識し、対応する翻訳ファイル(.moファイル)から適切な訳文を読み込んで表示します。

国際化のための主要なラッパー関数

文字列をどのように使うかに応じて、次のラッパー関数(国際化関数)を使い分けます。

関数用途戻り値使用例
__()翻訳された文字列を取得翻訳済み文字列(エスケープなしecho __( 'Title', 'text-domain' );
_e()翻訳された文字列を出力なし(文字列をechoする)<label><?php _e( 'Name', 'text-domain' ); ?></label>
_n()単数・複数の分岐が必要な場合翻訳済み文字列(エスケープなし$count = 5; _n( 'One item', '%s items', $count, 'text-domain' );
_x()文脈(コンテキスト)が必要な場合翻訳済み文字列(エスケープなし_x( 'Post', 'verb', 'text-domain' );

⚠ 出力する文字列をエスケープ関数でラップする

WordPress開発の基本原則として、画面に出力する文字列は必ずエスケープ処理を行う必要があります。

翻訳された文字列であっても、HTMLに出力する際には用途に応じたエスケープ関数を適用し、XSS 攻撃を防止します。

エスケープの目的

エスケープとは、Webアプリケーションのセキュリティ対策の基本であり、出力されるデータが悪意のあるコードとして解釈されるのを防ぐ処理のことです。

つまり画面への表示やファイルへの出力などどのような場合でもその文字列自体が安全であることが保証されなくてはなりません。

XSS攻撃(クロスサイト・スクリプティング)

ここでは、ユーザーや翻訳者が入力・翻訳した文字列に、HTMLタグやJavaScriptコードが含まれていた場合、そのままブラウザに出力すると、意図しない動作(例えば、マルウェアのダウンロード)を引き起こす可能性があります。この攻撃をクロスサイト・スクリプティング(XSS)と呼びます。

エスケープは、この攻撃(XSS)を防ぐために行われます。

国際化対応(i18n)の規約では、翻訳された文字列をHTMLとして画面に出力する際、その文字列の中に悪意のあるスクリプト(XSS攻撃の原因となるJavaScriptなど)が含まれていないかを確認し、安全な形に変換する作業を定めています。

エスケープ処理の仕組み:特殊文字の無効化

そのためエスケープ処理を行うためのエスケープ関数が用意されています。

エスケープ関数は、危険性のある文字をHTMLエンティティに変換し、ブラウザがただのテキストとして扱うようにします。

HTMLエンティティ

特殊文字エンティティ(置き換え後)ブラウザでの表示
< (小なり)&lt;<
> (大なり)&gt;>
& (アンパサンド)&amp;&
" (二重引用符)&quot;"

コード内で画面出力する文字列のエスケープ処理の例:

エスケープ処理前の文字列: <script>alert('XSS')</script>
エスケープ処理後の文字列: &lt;script&gt;alert('XSS')&lt;/script&gt;

このエスケープされた文字列は、ブラウザによってHTMLタグではなく単なるテキストとして解釈され、画面上にはそのまま<script>alert('XSS')</script>のように表示されます。したがって、このスクリプトはHTMLの一部として実行されることはありません。

エスケープ処理のための主要なラッパー関数

国際化関数(__()_e()など)は、翻訳された文字列を返しますが、そのままでは上記のセキュリティ保護のためのエスケープ処理は行いません
そのため、翻訳済みのテキストを画面へ出力する際は、用途に応じたWordPress のエスケープ関数で適切にラップ(囲んで)する必要があります。

WordPressには次のようなエスケープ専用の関数が用意されています。

関数用途戻り値使用例
esc_html()HTMLにそのまま表示する文字列を安全にエスケープしたい場合HTMLエスケープされた文字列php echo esc_html( $title );
esc_attr()HTML属性値(value, class, id, alt など)として安全に出力したい場合HTML属性用にエスケープされた文字列php <input value="<?php echo esc_attr( $value ); ?>">
esc_url()URLを安全に出力したい場合安全性チェック済みでエスケープされたURLphp <a href="<?php echo esc_url( $link ); ?>">Link</a>
esc_js()JavaScriptの中に文字列を安全に埋め込みたい場合JS用にエスケープされた文字列php <script>var msg = "<?php echo esc_js( $msg ); ?>";</script>
esc_textarea()<textarea>内に安全にテキストを出力したい場合textarea用エスケープ文字列php <textarea><?php echo esc_textarea( $content ); ?></textarea>
esc_html__()翻訳された文字列を、HTMLタグを無効化(エスケープ)して取得したい場合翻訳され、HTMLエスケープされた文字列php $title = esc_html__( 'Product Title', 'text-domain' );
esc_html_e()翻訳された文字列を、HTMLタグを無効化(エスケープ)した上で、そのまま出力したい場合(なし)直接echo出力php <h1><?php esc_html_e( 'Welcome', 'text-domain' ); ?></h1>
esc_attr__()翻訳された文字列を、HTML属性値として安全に使用するためにエスケープして取得したい場合翻訳され、HTML属性用にエスケープされた文字列php $alt = esc_attr__( 'Image description', 'text-domain' ); echo '<img alt="' . $alt . '">';
esc_attr_e()翻訳された文字列を、HTML属性値としてエスケープしたうえで、そのまま出力したい場合(なし)直接echo出力php <input placeholder="<?php esc_attr_e( 'Your Name', 'text-domain' ); ?>">

エスケープ処理の実行例

実際にサンプルコードのプラグインを使用して、エスケープ処理の動作を確認してみます。
このプラグインでは、記事にショートコード[twolines]を設置すると、2 行のサンプルテキストが表示されます。

<?php
/**
 * Plugin Name: Two Line Text Display
 * Description: Two Line Text Display
 * Version: 1.0
 * Author: Kasuga
 * Text Domain: two-line-text-display
 * Domain Path: /languages
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

function twoline_shortcode() {
	return 
		// 翻訳は行うが、エスケープされていないためHTMLタグがそのまま反映される
		'<div>' . __( '<strong>This is a pen.</strong>', 'two-line-text-display' ) . '</div>' .

		// 翻訳した上で、HTMLエスケープも行うため、<strong>タグはエスケープされ、文字列としてそのまま表示される
		'<div>' . esc_html__( '<strong>This is a pen.</strong>', 'two-line-text-display' ) . '</div>';
}

add_shortcode( 'twolines', 'twoline_shortcode' );

ブログ上での表示結果です。
エスケープ処理をした文字列はHTMLタグの効果(強調文字)が消されています。

ブログ編集画面
ブログ表示画面

表示画面のHTMLソースを確認すると、文字がエンティティとして処理されていることが分かります。

ブログ表示画面のソースを表示

翻訳ファイルを読み込むための準備

国際化関数でラップされた文字列を正しく機能させるには、WordPressに対して「このプラグインにはこのテキストドメインの翻訳ファイルを使用する」と明示的に知らせる必要があります。

そのためにプラグインのヘッダーには次の情報が必要です。

/*
* Text Domain: my-plugin
* Domain Path: /languages
*/

この設定により、WordPressは次のいずれかのフォルダーにある .moファイルを自動的に検索して読み込みます

wp-content/languages/my-plugin/
wp-content/plugins/my-plugin/languages/

また、読み込まれる .moファイルは、ja.moja_JP.moのように、WordPressの現在の言語設定に対応したファイルです。
この場合は、my-plugin-ja.mo、またはmy-plugin-ja_JP.moのようなファイル名になります。

ここで重要なのは、アップロードするプラグインのソースコード(.phpファイル)を格納しているフォルダ名を、Text Domain:に指定した名前(ここではmy-plugin)と同じにしておく必要があるという点です。

テキストドメインの役割

全ての国際化関数には、翻訳対象の文字列の後に必ずテキストドメイン(text-domain)を指定します。

このテキストドメインは、「どのプラグインの翻訳ファイル(.moファイル)を使うか」をWordPressに教えるためのIDです。

// 'my-plugin' がテキストドメイン
echo __( 'Welcome to my Plugin!', 'my-plugin' );

また、このテキストドメインは.moファイル名とも関連付けられ、この場合は次のようなファイル名になります。

my-plugin-ja.mo
my-plugin-ja_JP.mo

◯ 翻訳ファイル(.mo)を用意する

プラグインが多言語で表示される仕組みは、国際化関数が「元の言語(ソースコード内の文字列)と翻訳後の言語を対応させる表(.moファイル)」を使うことにあります。

プラグインのソースにて国際化のコーディングが完了したら、翻訳のためのファイルを作成します。

翻訳ファイルの種類と役割

実際に翻訳ファイルとして使用される .moファイルを作成するには、まずソースコードから翻訳関数でラップされた文字列を抽出し、それらの文字列に対して翻訳を行う必要があります。
その翻訳情報を基に、翻訳元と翻訳後の文字列を対応付けた「対応表」をコンパイルしたものが .moファイルです。

翻訳ファイルには、次の3種類のファイルが関わります。

ファイル形式役割
.pot (Portable Object Template)テンプレート・テキスト翻訳対象の文字列だけを抽出した雛形ファイル、開発者はこのファイルを最初に生成します。
.po (Portable Object)編集用テキスト翻訳者向け。msgid (原文)msgstr (訳文)を記述する対応表、開発者(翻訳者)が実際に編集するためのファイルです。
.mo (Machine Object)バイナリ高速化のために.poファイルをコンパイルした実行用ファイル、WordPressがプラグインの実行の際に利用し、.poファイルよりも読み込みが圧倒的に速いのが特徴です。

これらのファイルを順に作成していくことで、.potファイルから .poファイル、そして最終的な .moファイルを作成できます。

.potファイルの中身: msgid

.potファイル翻訳用テンプレートでありテキストファイルです。ファイル名はText Domainがmy-pluginの場合、my-plugin.potとなります。

プラグインのソースコードから抽出された翻訳可能な文字列が、msgidの後に記述されます。

msgid "Hello"
msgstr ""

msgid "Settings"
msgstr ""
  • msgid:Message Identifierの略で、プラグインのソースコードに記述されている原文の文字列(通常は英語)が入ります。

.poファイルの中身: msgstr

.poファイルは、.potファイルのmsgstrに翻訳文を追加したものです。原文と訳文を一対一で対応させて記述します。ファイル名は、ファイル名はText Domainがmy-pluginの場合、my-plugin-ja.poとなります。

msgid "Hello"
msgstr "こんにちは"

msgid "Settings"
msgstr "設定"
  • msgstr:Message Stringの略で、msgidに対応する翻訳後の文字列を記述します。

翻訳ファイルの作成: .pot → .po → .mo

各翻訳ファイルを作成する手順です。

.potファイルの生成

WordPressのWP-CLIに含まれるwp i18n make-potコマンドなどを使用すると、プラグイン内のPHPファイル全体をスキャンし、ソースコード中のすべての国際化関数から文字列を抽出して、.potファイルを生成できます。

WordPressのサーバー環境において、WP-CLIなどが使えない場合は、.potファイル作成のために次のページにて作成できます。

.poファイルの翻訳

.potファイルを基に、Poeditなどのアプリを使って各国語の.poファイル(例:ja.po)を作成し、msgidに対するmsgstrを記入していきます。

少数の翻訳であれば、テキストファイルである.potファイルをコピーし、ファイル名を-ja.poに変更してテキストエディターで編集し、.poファイルを作成することも可能です。その場合は、次のヘッダー情報へ変更します。

"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=1; plural=0;\n"

③ .moファイルの生成(.poファイルのコンパイル)

翻訳が完了した.poファイルを、ツールで.moファイル(実行ファイル)にコンパイルします。これも翻訳アプリにて行えます。

また、次のページでも行えます。

翻訳ファイルの配置

最終的に生成された.moファイルを、textdomainで指定したディレクトリ(ここではmy-plugin/languages)に配置します。

my-plugin/languages/my-plugin-ja.mo

まとめ:実際の作業例

ここでは、これまで解説してきた国際化の手順をふまえ、実際にどのような作業を行うのかを具体的な例としてまとめて紹介します。

以下は、国際化関数を使用したプラグインのPHPソースコード例です。

ファイル名:two-line-text-display.php

<?php
/**
 * Plugin Name: Two Line Text Display
 * Description: Two Line Text Display
 * Version: 1.0
 * Author: Kasuga
 * Text Domain: two-line-text-display
 * Domain Path: /languages
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

function twoline_shortcode() {
	return 
		'<div>' . __( '<strong>This is a pen.</strong>', 'two-line-text-display' ) . '</div>' .
		'<div>' . esc_html__( '<strong>This is a pen.</strong>', 'two-line-text-display' ) . '</div>';
}
add_shortcode( 'twolines', 'twoline_shortcode' );
本来、エスケープされていない__()のみで HTMLを出力するのは正しくありません。

次のページにあるジェネレーターを使用して、.potファイルを生成します。

生成された.potファイルです。

ファイル名:two-line-text-display.pot

# Translation of text-domain in the text-domain plugin.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the text-domain package.
#
# Generated by Web Tool.

msgid ""
msgstr ""
"Project-Id-Version: text-domain\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Full Name <EMAIL@ADDRESS>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2025-11-28 20:09:28\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"

msgid "<strong>This is a pen.</strong>"
msgstr ""

生成された.potファイルを複製し、テキストエディタで内容を編集することで、実際に使用する翻訳用の.poファイルを作成します。赤文字が編集した箇所です。

ファイル名:two-line-text-display-ja.po

# Translation of text-domain in the text-domain plugin.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the text-domain package.
#
# Generated by Web Tool.

msgid ""
msgstr ""
"Project-Id-Version: text-domain\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Full Name <EMAIL@ADDRESS>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"POT-Creation-Date: 2025-11-28 20:09:28\n"
"Plural-Forms: nplurals=1; plural=0;\n"

msgid "<strong>This is a pen.</strong>"
msgstr "<strong>これは鉛筆です。</strong>"

次のページにあるコンパイラーを使って、この.poファイルから.moファイルを生成します。

作成した.moファイルを、プラグインのフォルダ内にあるlanguagesフォルダに配置します。languagesフォルダが存在しない場合は、新たに作成してください。

two-line-text-display/two-line-text-display.php
two-line-text-display/languages/two-line-text-display-ja.mo

このフォルダを圧縮(ZIP 化)します。

two-line-text-display.zip

作成したZIPファイルを、WordPress の管理画面の「プラグイン」メニューからアップロードし、有効化します。
その後、記事にショートコードを追加することで使用できます。

これにより.moファイルに翻訳された文字列が、記事内に次のように表示されます。

Quick Translate POT/PO/MO

プラグインをアップロードした後の翻訳ファイルの生成や編集は、次のプラグインを使うと簡単に行えます。

おわりに

国際化対応(i18n)は、予想以上に手間がかかる作業です。

本来であれば、ソースコード内の文字列を国際化用の関数でラップするだけで済むはずです。しかし、実際には、翻訳ファイルを読み込む必要があるため、セキュリティの観点から、出力時にはエスケープ処理を行う関数も併用しなければなりません。

WordPressには、翻訳だけを行う関数エスケープだけを行う関数、さらに翻訳とエスケープを同時に行う関数などがあり、状況に応じて使い分ける必要があります。慣れれば簡単ですが、場合によってはWordPressのチェックによって、やや回りくどい方法を取らざるを得ないこともあります。

今回はHTMLの出力に関する説明に限定しましたが、従来のスクリプトではユーザーからの入力データも扱います。入力される文字列の安全性を確保するための処理がサニタイズであり、さらなるラップ作業が必要となります。

つまり、入力時にサニタイズ、出力時にエスケープが必ず必要となります。

翻訳処理を追加するだけだと思って始めましたが、実際には思った以上に複雑な作業となりました。

プラグインの国際化を考えている方の参考になれば幸いです。

コメント

タイトルとURLをコピーしました