PR

プラグインの国際化(英語-日本語対応)!テキストドメインの指定から翻訳ファイルの作成まで

プラグインを自作したら、プログラム内のテキストを「英語」で書き、その上で「国際化(i18n)」の対応をしておきましょう。

(※国際化:WordPressの設定言語に合わせて、日本語などの翻訳を自動で切り替える仕組みのこと)

WordPress公式ディレクトリに登録するなら必須の作業ですが、登録予定がなくても国際化しておくメリットは大きいです。

(例えば、多言語対応させておくことで世界中のユーザーに使ってもらえる可能性が広がりますし、何よりソースコードと表示文字列を分離できるので、後からの文言修正が非常に楽になります)

テキストドメインを設定したり、ソースの中に翻訳文字列の指定を入れたりと、手順的には大きく分けて5つありますが、ここでは「More Tag Auto Insert」というプラグインを自作した時に行ってみた国際化のための手順、方法をご紹介。

参考)プラグイン開発ハンドブック(wordpress公式)
⇒ 国際化

手順1)テキストドメインの設定

プラグイン(やテーマ)の国際化(翻訳対応)では、
ソースコード内で翻訳表示する文字列を以下のような記述で1つ1つ指定しますが、その指定の中で使う「テキストドメイン」というものをまず設定します。

PHP
<?php
	//その場で直接表示する場合
	_e( 'english', 'text-domain' );	

	//変数に入れる場合
	$translate = __( 'english', 'text-domain' );
	
	//単数、複数のある場合
	//変数($post_count)が1の場合「post」、それ以外は「posts」を表示
	echo _n( 'post' , 'posts' , $post_count, 'test-domain' ); 
?>
Jin Simple Code Block

この例の場合、「english」の箇所が翻訳対象の文字列、
「text-domain」の箇所が「テキストドメイン」と呼ばれるもの。

テキストドメインとは、翻訳する文字列(テキスト)がどこにあるかを示す識別子で、作成する翻訳ファイル( .pot, .po, .mo )のファイル名にもなるものです。

※ 重要なルール:
WordPressの公式ディレクトリに登録する場合、「テキストドメイン名」は「プラグインのSlug(URLの末尾)」と完全に一致させる必要があります。
例えば、プラグインのURLが https://wordpress.org/plugins/my-best-plugin/ なら、テキストドメインは必ず my-best-plugin に設定しなければなりません。ここがずれていると、公式の翻訳システムが動かなくなってしまうので注意しましょう。

この識別子を使って各言語の翻訳ファイルが作られます。

例)テキストドメインが「textdomain」という文字列の場合の翻訳ファイル(.mo)の例

  • textdomain-ja.mo (日本語の翻訳ファイル)
  • textdomain-fr.mo (フランス語の翻訳ファイル)
  • textdomain-de.mo (ドイツ語の翻訳ファイル)
  • textdomain-th.mo (タイ語の翻訳ファイル)

こんな感じで、各言語の翻訳ファイルの元となって、翻訳する文字列(テキスト)の領域(ドメイン)を示す識別子となることから、テキストドメインなんて呼ばれるんですね。

(日本語翻訳ファイル textdomain-ja.mo は、textdomainという大きな領域内の ja区域、みたいなものかな?jpではないことに注意。)

プラグインの公式ディレクトリ上にある翻訳ファイルと被らないよう、テキストドメインは唯一の名称である必要があり、ワードプレスの公式ドキュメントでは以下のように説明されてます。

  • プラグインが1つのファイルのみ(例えば my-plugin.php)であれば「my-plugin」をテキストドメインとして設定する
  • 複数ファイルで構成されフォルダ(例えば my-plugin2 フォルダー)になっている場合には、そのプラグインフォルダ名称「my-plugin2」をテキストドメインとして設定する

参考)I18n for WordPress Developers
(※:I18n とは Internationalization(国際化)のこと。最初の"l"と最後の"n"の間に18文字あるからこうした表現になっているのだとか。面白いですね)

この公式ドキュメントからすると、
テキストドメインはプラグインのヘッダ部分にも記載する必要があるようです。

PHP
/*
 * Plugin Name: My Plugin
 * Author: Jin Koyama
 * Text Domain: my-plugin
 */
Jin Simple Code Block

(以前は記載しなくても動くケースもありましたが、現在はプラグインヘッダへの記載が「推奨」から「必須に近い標準」となっています。ここに書いておくことで、WordPressが自動的に翻訳ファイルを読み込む準備をしてくれるため、必ず記述しておきましょう。)

テキストドメインにはアンダースコア( _ )を使わず、ハイフン( – )を使うのがルール
これはプラグインのフォルダ名やファイル名の命名規則と同じですね。

手順2)翻訳ファイルの指定(フォルダ指定)

テキストドメインを決めたら、そのテキストドメインと翻訳ファイル(.moファイル)のありかを load_plugin_textdomain()関数 で記述します。

確実に翻訳を反映させるために、
必ず plugins_loaded というアクションフックを使って呼び出すようにしましょう。

PHP
add_action( 'plugins_loaded', 'my_plugin_load_textdomain' );
function my_plugin_load_textdomain() {
    load_plugin_textdomain( 
        'text-domain', 
        false, 
        dirname( plugin_basename( __FILE__ ) ) . '/languages' 
    );
}
Jin Simple Code Block

関数リファレンス:load_plugin_textdomain(英語)

  • 第1引数:テキストドメイン
  • 第2引数:非推奨になっているので、デフォルトである false を指定
  • 第3引数:翻訳ファイル(.moファイル)を置くパス(フォルダ)を指定

このコードの意味合いとしては、
第三引数で指定されたフォルダの中にあるファイルのうち「テキストドメイン」と名称が合致している翻訳ファイルをワードプレス内に持って来る、みたいになるでしょうか。

第三引数の最後に「 /languages 」とあるように、翻訳ファイルを置く場合の基本として「languages」という名称でフォルダを作成してその中に入れるようにします。

load_plugin_textdomain()関数は、もし翻訳ファイルが見つからなくてもfalse` を返すだけ。エラー画面(Fatal error)などは出さないので、翻訳ファイルを作る前でも先に記述しておいてOKです。

※参考)WordPress.orgの公式コミュニティが作った翻訳ファイル
WordPressの仕様では「自作した翻訳ファイル」よりも「WordPress.orgの公式コミュニティが作った翻訳ファイル(wp-content/languages/plugins内)」が優先される仕組みになっています。将来、公式に登録した後に何かトラブルがあった場合、この優先順位を覚えておくと解決が早く
なる場合がありますね

具体的な記述例)

PHP
<?php
/*
* Plugin Name: More Tag Auto Insert
* Plugin URI:https://tabibitojin.com/add-more-tag-auto
* Description:Automatically insert a more tag (read more tag &lt;!--More--&gt;) just before the first H2 headline (H2 tag) in the article. If the article does not have a more tag, a more tag is automatically inserted when the article is displayed or when the article is saved (when published, updated, or saved as a draft). You can also search for articles that do not have a more tag, and display them in a list.
* Version: 0.9.3
* Author: Jin Koyama
* Author URI:https://tabibitojin.com/
* Text Domain: jin-more-tag-auto
*/

//------------------------------------
// 翻訳追加(アクションフックを利用)
//------------------------------------
define( "JAMT_TEXT_DOMAIN" , "jin-more-tag-auto" );

function jamt_load_textdomain() {
    load_plugin_textdomain( 
        JAMT_TEXT_DOMAIN, 
        false, 
        dirname( plugin_basename( __FILE__ ) ) . '/languages' 
    );
}
add_action( 'init', 'jamt_load_textdomain' );

//------------------------------------
// メインファイル読み込み
//------------------------------------
require( 'jin-more-tag-auto-settings.php' );
require( 'jin-more-tag-auto-when-display.php' );
require( 'jin-more-tag-auto-when-save.php' );

?>
Jin Simple Code Block

この例は「More Tag Auto Insert」というプラグインを作った時のものですが、以下のようにしています。

  • ヘッダ内9行目にテキストドメインの記述
  • 15行目:テキストドメインを定数として定義
  • 17-24行目:load_plugin_textdomain()関数を使用し、`init` アクションフックで実行しています。

テキストドメインとしては、このプラグインを作った時のプラグインフォルダ名称を指定してます。ちなみに15行目でテキストドメインを定数として定義しているのは、使用する場面が多く「定数にしておけば後々便利」ということから。

※)フォルダ名称は必ず languages にすべき?
第三引数で指定するフォルダ名称と翻訳ファイル(.moファイル)を置くフォルダ名称が一致していれば翻訳が機能するのを確認してますし、絶対「languages」という名称にせよ、と公式ドキュメントには書かれてないようです。
WordPress_の国際化 にも「一般にすべての言語ファイルはプラグインの languages ディレクトリにある」とか書かれているので、何か特別な理由がなければ、誰から見ても分かりやすい languages フォルダーにしておくのが良さそうです。

手順3)翻訳したい個所の設定( _e(), __() をなどを使う)

プラグインの設定画面など画面に表示する文字列に対して、
・英語環境では英語で表示、
・日本語環境では日本語で表示、
と、翻訳表示したい文字列を翻訳のための関数で1つ1つ指定していきます。

※)ポイント:元々の日本語はコメントで残しておきましょう
(Poeditなどを使って翻訳ファイルを作成する時に使いますので)

翻訳のための関数として主に使うのは _e(), __()、
たまに _n() を使うぐらいになると思います。

PHP
<?php
	//その場で直接表示する場合
	_e( 'english', 'text-domain' );	

	//変数に入れる場合
	$message = __( 'english', 'text-domain' );
	
	//単数、複数のある場合
	//変数($post_count)が1の場合「post」、それ以外は「posts」を表示
	echo _n( 'post' , 'posts' , $post_count, 'test-domain' ); 
?>
Jin Simple Code Block

※ 他にも、HTMLの属性値(valueやaltなど)の中で使う場合は、セキュリティのためにエスケープ処理も同時に行う esc_attr_e()esc_attr__() を使うのがWordPress開発の鉄則です。

公式ディレクトリの審査では、これらが正しく使い分けられているかをとても厳しくチェックされるので、基本的には「表示箇所に合わせてエスケープ付きの関数を選ぶ」と覚えておくのが良いです。

参照)リファレンスガイド(Wordpress公式)

具体例:_e()

PHP
<?php
	echo '<a href="~">新規</a>';
	echo '<a href="~">編集</a>';
	echo '<a href="~">削除</a>';
	
	// 新規、編集、削除、を翻訳表示の対象としたい
	// まず英語にする
	echo '<a href="~">new</a>'; 	//新規
	echo '<a href="~">edit</a>'; 	//編集
	echo '<a href="~">delete</a>'; 	//削除
	
	// _e() を使って翻訳対応を入れる(echoの中でつなげず、独立させて書く)
	// ↓↓↓↓↓↓
	echo '<a href="~">'; _e( 'new', 'text-domain' ); echo '</a>'; 	//新規
	echo '<a href="~">'; _e( 'edit', 'text-domain' ); echo '</a>'; //編集
	echo '<a href="~">'; _e( 'delete', 'text-domain' ); echo '</a>'; //削除
?>
Jin Simple Code Block

※ 「_e()」はそれ自体が「表示する」という命令を持っているため、echoには含めません。

テキストドメインをdefineで定義している場合には、以下のようになります。

例)defineでテキストドメイン text-domain を TEXT_DOMAIN として定義している場合

PHP
<?php
	define( "TEXT_DOMAIN" , "text-domain" );
 
	// TEXT_DOMAIN はクォーテーションで囲わず、スペースなしで記述します
	echo '<a href="~">'; _e( 'new', TEXT_DOMAIN ); echo '</a>'; 	//新規
	echo '<a href="~">'; _e( 'edit', TEXT_DOMAIN ); echo '</a>'; 	//編集
	echo '<a href="~">'; _e( 'delete', TEXT_DOMAIN ); echo '</a>'; //削除
?>
Jin Simple Code Block

HTMLの中に PHP を細かく埋め込むのが面倒なときは、以下の 「__()」 を使うと echo の中でドット連結ができて便利です

具体例:__()

PHP
<?php
$h1_title		= __( 'More Tag Auto Insert', 'text-domain' );
//moreタグ自動追加設定 
$description1	= __( 'A plugin automatically inserts a more tag before the first h2 tag headline in a post or a page.', 'text-domain' );
//記事中の最初のh2見出し前にmoreタグを自動で挿入するプラグイン。
?>
<div id="jin_amt_settings">
	<div class="settings">
		<h1><?php echo esc_html( $h1_title ); ?></h1>
		<div class="description">
    			<p><?php echo esc_html( $description1 ); ?></p>
		</div>

		<form method="post" action="options.php">
		<?php settings_fields( 'jin-amt-settings-field-group' ); ?>
		<?php do_settings_sections( 'jin-amt-settings' ); ?>
		<?php submit_button(); ?>
		</form>
		
	</div><!--settings-->
</div><!--jin_amt_settings-->
Jin Simple Code Block
  • __() を使って、2行目、4行目で翻訳したい文字列を指定して、
  • 各々 $h1_title, $description1 といった変数へ代入し、
  • 9行目、11行目でHTMLの中に組み込んでいる

といった例。

翻訳対象の文字列が長かったり、HTMLのタグ構造が複雑だったりする場合に「__()」を使うと、ソースコードがスッキリして読みやすくなります。

また、「__()」は値を返すだけなので、「出力する前に文字数を変数に入れてチェックする」などができるのも便利なポイントですね。

具体例:_n()

PHP
<?php
	// 1記事、2記事と表示する
	echo $post_count.'記事';
	
	//↓↓↓↓↓↓(単数と複数とで表示を変える)
	
	printf( _n( '%d post', '%d posts', $post_count, 'text-domain' ), $post_count );
?>
Jin Simple Code Block

この例では、記事数を数えてそれを表示する、という場合ですが、
日本語では1記事、2記事、… と記事数によって「記事」は変化しません。

でも英語になると、1 post, 2 posts, … と、
1記事では単数の「post」、2記事以上は複数になって post に「s」を付ける必要が出てくる(posts にしないといけない)。

単数、複数って面倒!なんて思いますが(笑)
これはもう仕方がないですね。

こうした場合分けがある時に使うのが _n() で以下のように設定します。

  • 第一引数( %d post )は単数の場合に表示する文字列
  • 第二引数( %d posts )は複数の場合に表示する文字列
  • 第三引数( $post_count )は変化する数値

更にprintf() を使うことで %dには数値が入り、その数値は printf の第二引数($post_count)で指定する、ってわけですね。

関数リファレンス:printf()(公式phpマニュアル)

printf()とか使わずに、以下のようにしてもOK。

PHP
<?php
	// 1記事、2記事と表示する
	echo $post_count.'記事';
	
	//↓↓↓↓↓↓(単数と複数とで表示を変える)
	
	echo  $post_count . _n( ' post' , ' posts' , $post_count, 'text-domain' );
?>
Jin Simple Code Block

これら _e(), __(), _n() などを使って、
翻訳対象の文字列を指定していきましょう。

手順4)翻訳ファイルの作成(poedit)

ここまでで、テキストドメインの設定、翻訳ファイルの指定、翻訳したい個所の設定、としてきましたが、あとは実際の翻訳ファイルを作成するだけ。

翻訳ファイルの作成は Poedit というソフトを使えばすぐできます。

(phpファイル内の e(), _() などで指定した翻訳対象の文字列を一気に抽出してくれるので物凄く楽)

※補足:Poeditの無料版では抽出機能に制限がある場合があります。その場合は、プラグインのフォルダ内にある「.potファイル」を読み込むか、一度 .pot ファイルを作成してから作業を始めるとスムーズです)

Poeditの使い方、実際の .po, .mo(と .pot)の作り方は以下で見てみてください。

⇒ Poeditの使い方!翻訳ファイル .pot .po, .moを作る手順を分かりやすく解説

手順5)翻訳ファイルの配置(指定フォルダへ入れる)

Poeditを使って (.poと).moファイルを作る過程で、
翻訳対象プラグインのフォルダー内「languages」フォルダーに .moファイルが作成されると思います。

ブログのフォルダ(ワードプレスのフォルダ)
wp-admin
wp-content
   ├ languages
   ├ plugins
      ├ akismet
      ├ jin-more-tag-auto
         ├ languages
            ├ jin-more-tag-auto.pot(雛形:言語コードなし)
            ├ jin-more-tag-auto-ja.po(日本語設定ファイル)
            ├ jin-more-tag-auto-ja.mo(翻訳本体:これが必要!)
         ├ jin-more-tag-auto.php
         ├ xxxxx.php
         ├ xxxxx.css
         ├ ・・・・
wp-include
.htaccess
index.php
・・・・

プラグインのフォルダ―「jin-more-tag-auto」の中の「languages」フォルダーに、翻訳ファイルとなる .moファイル(jin-more-tag-auto-ja.mo)を配置している例。

「どのフォルダに、どんな名前で置くか」が翻訳がうまくいくかどうかの分かれ道ですので、しっかり確認しておきましょう。

補足)JavaScriptの翻訳について

ここまでの内容で、PHP側の翻訳(.moファイル)は動作しますが、JavaScriptファイルがある場合、その中の文字列は翻訳されません。

JavaScript 側の翻訳を有効にするには、
追加で wp_set_script_translations() を設定し、
JS用の翻訳ファイル(JSON形式)を読み込めるようにする必要があります。

PHP
// Gutenberg(JavaScript側)の翻訳
wp_set_script_translations(
    'jscb-editor',
    'jin-simple-code-block',
    plugin_dir_path( __FILE__ ) . 'languages'
);
Jin Simple Code Block
  • 第1引数:翻訳対象となる JavaScript のハンドル名
    (ここでは例として「jscb-editor」)
  • 第2引数:テキストドメイン
    (子これは例として「jin-simple-code-block」)
  • 第3引数:翻訳ファイル(JSON)を置くフォルダ
    (phpファイル翻訳用のpomoファイルを置くフォルダ)

またPHPファイルの翻訳では 「.mo ファイル」を使用しますが、
JavaScriptファイルでは専用の 「JSON」の翻訳ファイルが必要になります。

WordPress公式では WP-CLI を使った生成方法が紹介されていますが、何か難しい場合には、既に作成した .po ファイルをGeminiなどAIに丸ごと渡して JSONファイルを作れば良いですね。

Prompt例はこちら
JS用の翻訳ファイルを作りたいです。

以下の .po ファイルの翻訳内容を元に、
WordPress Gutenberg 用の JavaScript 翻訳JSONへ変換してください。

条件:
・textdomain は「jin-simple-code-block」
・handle名は「jscb-editor」
・locale は「ja」
・WordPress の wp_set_script_translations() で読み込める形式
・JSONのみ出力
・locale_data.messages を含める
・domain は "messages" を使用
・形式は "Original String": ["翻訳"] にする
・翻訳を変えるのは禁止
・msgstr が空のものは空文字のまま出力する
・fuzzy の有無に関係なく、msgstr の内容だけを使う

(ここに .po ファイルの内容を貼る)
Jin Simple Code Block

このプロンプトでは、通常の .po 翻訳ファイルの内容を元に、WordPress管理画面の JavaScript 側で使用する翻訳JSONファイルへ変換しています。

  • textdomain:プラグイン側で使用している Text Domain 名
  • handle名:wp_set_script_translations() で指定しているスクリプト名
  • locale:翻訳対象の言語コード(日本語なら ja)
  • locale_data.messages:WordPress の JavaScript翻訳で必要になるデータ構造
  • domain: "messages":WordPress の JavaScript翻訳で一般的に使用される domain 名
  • "Original String": ["翻訳"]:WordPress JavaScript翻訳で使用される標準形式

なお、スクリプト名(handle名)や Text Domain が一致していない場合、JSONファイルが存在していても翻訳は読み込まれません。

小規模プラグインでは、
この方法の方が圧倒的に簡単で管理しやすいと思います。

※)上手く行かない場合もある:
AIによって上手く行く場合、行かない場合があるようです。上手く行かない場合には別AIで試してみるなどしてみてください(私の場合、ChatGPTで上手くいかず、Geminiで試したら上手く行ったなどありました)

今回のポイント!

プラグインやテーマの国際化(翻訳対応)は以下を行えばOK!

  • 1)テキストドメインを決める
  • 2)load_plugin_textdomain()関数で、テキストドメインと翻訳ファイルのありかを設定する
  • 3)ソース内で、__()関数や _e()関数などを使って翻訳対象の文字列を指定する
  • 4)Poeditで .moファイル(翻訳ファイル)を作り、正しいファイル名(テキストドメイン-ja.mo)で指定の場所に配置する

こうしてみれば、プラグインの国際化は難しいというものではなく、
淡々と1つ1つやっていくだけ。

私の場合も一番面倒だったのが、
最初phpソース内の日本語であった文字列を __()関数などで英語文字列にして、その後Poeditで英語の文字列に対して元の日本語訳を入れる時に、元の日本語ってなんだっけ?となったケース。

Edit (編集)とか Search(検索)といった単純な単語なら良いですが、プラグインの説明のための文章とか設定画面のガイド文言など、ある程度長い文字列は、以下のように関数の直前にコメントを残しておくと、後からPoeditで開いたときにもメモとして表示されるのでおすすめですよ。

// 日本語:ここにプラグインの説明が入ります
$desc = __( 'This is a plugin description…', 'text-domain' );

(でないと日本語⇒英語に訳す⇒その訳した英語から改めて日本語訳を考える、みたいな結構面倒なことになる(笑))

ではこれでプラグインの国際化もできました。

ネットビジネスでの進め方に迷っているとかブログの収益化の仕方が分からない、という場合には以下のメルマガも併せて見てみてくださいね。

早期退職して海外で奮闘する JIN のメールマガジン

時間や場所に縛られず稼いだJINが教える

~ 最短で月収10万円稼げるようになる方法 ~

お名前/ニックネーム

隣のあの人にも、思わず教えたくなる秘密

配信停止は、いつでもできます

迷惑メールは一切配信されませんので、ご安心くださいね

自らの手で未来を変える力を手に入れる!

コメント