ブロックの国際化対応は大変な苦難の道のりだった！

WordPressは翻訳関数というのを用意していて、それを使えばpoファイル、moファイルによってタイトルや説明文を多国籍言語で表示させることができます。
これはWordPressを使うWeb制作者の多くが認識していらっしゃるでしょう。
もちろん私もそうでした。
でも、具体的にコーディングしたことはなく、まあその内と思っていたのです。
ひな型から`import { __ } from '@wordpress/i18n';`とライブラリがインポートされているぐらいだから、手軽に使えるものと思い込み、いつでもマスターできると思っていました。
しかし、とんでもなかったです。３日かかってやっとなんとか翻訳言語が表示できるようになりましたが、とにかく苦労しました。
このノウハウを決して忘れないようにしたいと思うし、これからチャレンジする方には、少しでも苦労せず身に着けていただけたらいいという思いでブログにします。

## POT、PO、MOの各ファイルの役割
まず、この図をご覧ください。




![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po.png)

とりあえず、この図で大まかなイメージを掴んでおいてください。

## 最初の一歩はPOTファイル
まず、POTファイルです。
私は最初POTファイルなるものが何かよくわかりませんでした。いろんな解説記事を見ましたが「POファイルのテンプレート」という表現が多かったです。しかし、これって具体的にイメージしにくいのです。
そこで、私は次のように表現することにしました。
**「プロジェクトのソースフォルダからプログラムファイルを検索し、その中から`()`や`_e()`などの翻訳関数を抽出し、その第１引数をリスト化したファイル」**
厳密さは欠けるように思いますが、どんなファイルか具体的なイメージが湧きやすい気がします。

さらに詳しく説明します。
翻訳関数は引数を２つとります。
第１引数は表示する文字列の原文です。普通は英語でしょうね。
第２引数はテキストドメインです。それってなに？
今の段階ではその説明はちょっと置いておきましょう。POファイルのところで説明します。

とにかく、この関数がプラクラム内で使用されていることが前提になります。POTファイルは、その関数の第１引数、つまり、翻訳すべき原文のリストなのです。そして、それに訳文を入力する「枠」がついていますが、POTファイルの段階では、その部分が空なのです。

なぜ空かというと、それがまさにテンプレートと言われる所以で、そのPOTをもとに日本語訳のついたファイル、中国語訳のついたファイルというように複数のPOファイルを作るためです。

ということで、POTファイルは国際化対応の根幹となるファイルだと思います。これを確実に作ることから始めるべきだと思いました。
Poeditなどの便利なアプリケーションが多くのサイトで紹介されえているのですが、このアプリケーションはPOファイルを生成するためのアプリケーションで、POTファイルは別途用意されていることが前提となっています。しかも、POTファイルなくしてプログラムのソースファイルからいきなりPOファイルを生成する機能ももっています。そのため、初心者が最初にこのアプリケーションを使うと、POTファイルの存在価値を意識しないようになってしまう気がします。これはおすすめしません。まず、POTファイルの作り方を覚えましょう。

### WP-CLIのインストール
WP-CLIはWordPressのよくある作業を管理するための開発者向けのコマンドラインツールです。このツールでPOTファイルを作ります。
Poeditでも作れますが、少なくともGutenbergのブロック開発環境においては、WP-CLIを使うことは必須だと思います。私は最初PoeditでPOTを生成したため、かなり遠回りをしました。PoeditはPOファイルを作るものでPOTを作るものではないように思います（有料版は試していないのでわかりません。）。

WP-CLIのインストールは次の手順で簡単にできます。
### インストール手順１（SCOOPのインストール）
1. PowerShellを管理者権限で開きます。
2. 以下のコマンドを実行して、Scoopをインストールします:
```
Set-ExecutionPolicy RemoteSigned -scope CurrentUser
iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
```

### インストール手順２（WP-CLIのインストール）
1. PowerShellを開きます。
2. 以下のコマンドを実行して、WP-CLIをインストールします:
```
scoop install wp-cli

```
インストールが完了したら、コマンドプロンプトやPowerShellでwp --infoを実行して、正しくインストールされたか確認できます。

### POTファイルの生成
対象のブロックのルートディレクトリでターミナルを開いて、次のコマンドを実行します。
```
wp i18n make-pot ./ languages/itmar_guest_contact_block.pot --exclude=node_modules/*
```
第１引数`./`はルートディレクトリ以下のすべてのディレクトリ内のファイルを対象に関数を検索することを意味します。
第２引数は出力対象のPOTファイル名です。ファイル名は何でもよいのですが、テキストドメイン名を使うのが一般的でしょう。--excludeオプションは、検索対象の中から特定のディレクトリを除外するものです。なくてもよいのですが、ブロックの開発環境には多くの場合node_modulesディレクトリがあり、そこには大量のファイルがあるので、検索対象から外しましょう。

ということで実際に出来上がったファイルは以下のようになります。
```
・・・
#: guest-contact-block.php:163
msgid "Receipt processing completed successfully."
msgstr ""

#: build/index.js:132
#: src/edit.js:103
#: build/index.js:116
msgid "Inquiry information notification email"
msgstr ""
・・・
```

このコードはPOTファイルの一部です。`msgid`が原文の見出しで、`msgstr`が訳文の見出しです。訳文は空になっていますね。
`#:`以下は翻訳関数があったファイルとその行番号です。この情報が非常に重要なのです。これがないと、JSONファイルの作成のところで大きくつまづきます。

## つづいてPOファイルの作成
ここでPoeditというアプリケーションを使います。
インストール方法は簡単で、次の公式ページからダウンロードしてそのファイルをダブルクリックするだけです。
https://poedit.net/download/

POファイルの作成手順はスクリーンショットで説明します。


![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-1.png)






![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-2.png)

WP-CLIで作成したPOTファイルを選択します。



![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-3.png)




![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-4.png)

①で入力項目を選択し、②で訳文を入力、③で保存です。





![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-5.png)

このファイル名は重要です。デフォルトでは「ja.po」となっているので、その前に「テキストドメイン-」と入れます。
ここでテキストドメインについて説明します。
テキストドメインは翻訳関数`__()`等の第２引数に設定すると説明しました。そうすることによって翻訳関数はそのテキストドメインの文字列を含むファイル名を持つファイルから、第１引数にセットした原文の文字列から訳文を検索するようになっているのです。つまり、
```
__("Notification email subject", 'itmar_guest_contact_block')
```
という関数があるとすると`itmar_guest_contact_block`という名前を含むファイルを探し、さらに、第１引数の文字列と一致する訳文を探して表示するのです。ですから、ここでつけるファイル名は重要です。これを間違うと訳文は表示されません。

これで保存すれば無事にPOファイルは出来上がりです。

## MOファイルはなんのためにある？
ではMOファイルは何のためにあるのでしょう。
先ほど翻訳関数がテキストドメイン名のついたファイルを探しにいくといいましたが、実際に探しにいくのはPOファイルではなく、MOファイルなのです。そして重要なのはこの**MOファイルはPHPの翻訳関数の訳文を表示させるファイル**だということです。
**Javascriptの翻訳関数による訳文はMOルがあっても表示されません。**

とりあえず、ここではMOファイルによる訳文の表示に絞って解説していきます。
MOファイルはPOファイルをバイナリ形式でコンパイルしたファイルで、先ほどPoeditでPOファイルを保存しましたが、そのとき自動的に生成されるようになっています。ただし、これは設定で生成されないようにもできるので、設定されているかどうかは確認しておきましょう。
Poeditを立ち上げて[ファイル]ー[設定]で次のダイアログが出るので、そこで確認できます。




![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-10.png)

### load_plugin_textdomainによる読込
そしてさらにブロックのエントリポイントのPHPファイルに次のように記述しなくてはいけません。
```
function itmar_contact_block_block_init() {
	・・・
	//PHP用のテキストドメインの読込（国際化）
	load_plugin_textdomain( 'itmar_guest_contact_block', false, basename( dirname( __FILE__ ) ) . '/languages' );
}
add_action( 'init', 'itmar_contact_block_block_init' );
```
WordPressのinitアクションフックで`load_plugin_textdomain`実行するわけです。第１引数はテキストドメイン、第３引数はMOファイルの保存フォルダへの相対パスです。今回はブロックのルートディレクトリ直下の`languages`フォルダを指しています（第２引数はあまり気にせず`false`でいいようです。）。

これでPHPで記述された翻訳関数の部分は訳文が表示されます。
このように自分で任意のフォルダにMOファイルを保存した場合は`load_plugin_textdomain`で、その場所を指定する必要がありますが、.\wp-content\languages\pluginsというフォルダに保存すれば、`load_plugin_textdomain`での指定は必要ありません。
ただし、このフォルダはプラグインの外にあるフォルダなのでプラグインをインストールしただけでは保存することができず、ユーザーに一手間かけさせることになります。できれば、そうしない方がいいのではないかと思います。

### PHPのコメントヘッダー内の翻訳
プラグインのエントリポイントのPHPファイルにはコメントヘッダーが付いていて、これがあることでプラグイン名等が表示されます。
```
/**
 * Plugin Name:       Guest Contact Block
 * Plugin URI:        https://itmaroon.net
 * Description:       A block with an email submission form.
 * Requires at least: 6.1
 * Requires PHP:      7.0
 * Version:           0.1.0
 * Author:            Web Creator ITmaroon
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       itmar_guest_contact_block
 * Domain Path:  			/languages

 */
```
こんな感じになっていますが、WP-CLIでPOTファイルを作ると、次のように抽出してくれます。
```
#. Plugin Name of the plugin
msgid "Guest Contact Block"
msgstr ""

#. Plugin URI of the plugin
msgid "https://itmaroon.net"
msgstr ""

#. Description of the plugin
msgid "A block with an email submission form."
msgstr ""

#. Author of the plugin
msgid "Web Creator ITmaroon"
msgstr ""
```
この部分については翻訳関数がセットされていなくても、POファイルに訳文を入れてMOファイルを生成するだけで翻訳されます。

## JSONファイルによるJS関数の翻訳
ここまでの手順も相当複雑でしたがPOT、PO、MOの各ファイルの機能を理解していれば、そんなに苦労せずにたどり着けるのではないかと思います。
問題はここからなのです。
なかなか、正確に説明してくれている記事にも巡り合えず、ChatGPTの答えも不正確でした。

そもそも、PHPの関数とJS（JavaScript）の関数で翻訳の仕組みが違い、しかも、MOファイルではなくJSONファイルを用意しないといけないなんて思いもしませんでした。
それに気付くのにも時間がかかりました。
ブロック開発では訳文を表示させたいのは、ほとんどがJSの関数で作られています。それが表示されないと意味がありません。

それはともかく、コードとしては次のようになっています。
```
import { __ } from '@wordpress/i18n';
・・・
return(
	・・・
	<TextControl
		label={__("Notification email subject", 'itmar_guest_contact_block')}
		・・・
	/>
	・・・
)
```
PHPと違うのは関数のimportが必要であるという点だけです。
また、JSONファイルを作ること自体も簡単です。プラグインのルートディレクトリで次のコマンドを実行します。
```
wp i18n make-json languages/ --no-purge
```

これでプラグインのルートディレクトリ直下の`languages`フォルダからpoファイルを探し出してjsonファイルが生成されます。
これで訳文が表示されるなら簡単なのです。
しかし、これからが苦難の始まりです。

### wp_set_script_translationsによるJSONファイルの指定（失敗談）
PHPでは`load_plugin_textdomain`でMOファイルを読み込みましたが、JSONファイルにおいてもそれと同様のプロセスが必要です。
```
wp_set_script_translations( $script_handle, 'itmar_guest_contact_block', plugin_dir_path( __FILE__ ) . 'languages' );
```
このコードを`load_plugin_textdomain`と同様にinitアクションフックで実行します。
第２引数がテキストドメインで、第３引数はJSONファイルが保存されているフォルダへの相対パスです。
問題は第１引数です。これはスクリプトハンドルと呼ばれる文字列です。
WordPressのテーマでもプラグインでも外部のライブラリを読み込むときは`wp_enqueue_script`というコマンドを使います。このコマンドの第１引数で指定するのがスクリプトハンドルです。`wp_enqueue_script`で指定するのは他の`wp_enqueue_script`で使用するスクリプトハンドルと重複しない任意の文字列でよいのですが、`wp_set_script_translations`で使うスクリプトハンドルは、すでに`wp_enqueue_script`等の登録コマンドで使用されている文字列でないとダメなのです。
平たく言うと使用実績があるスクリプトハンドルということですね。それがないなら、あらかじめダミーのスクリプト用意して`wp_enqueue_script`等の登録コマンドを実行しておかなければいけないのです。

コードとしては次のようになります。
```
wp_enqueue_script(
	'itmar_script-handle',
	plugin_dir_url( __FILE__ ) .'dummy.js',
	array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor' ),
	'1.0.0',
	true
);

wp_set_script_translations( 
	'itmar_script-handle', 
	'itmar_guest_contact_block', plugin_dir_path( __FILE__ ) . 'languages' 
);
```
そんな無駄なエンキューしないといけないのかと思うのですが、これで`wp_set_script_translations`は機能してくれているはずなのです。

とおもって、ブロックをリロードして表示を確認しました。
・・・英語のままです。なぜ？？？
かなり、時間をかけて調べました。すると、JSONファイルのファイル名の形式は
`${domain}-${locale}-${handle}.json`または`${domain}-${locale}-${md5}.json`と書いてある記事を見つけました。
WP-CLIが生成したファイル名は`itmar_guest_contact_block-ja-bb1d7dea005e67527e728d4801f74b61.json`で後者の形式です。では、前者の形式にしてみようと思い、次のようにリネームしました。
`itmar_guest_contact_block-ja-itmar_script-handle.json`

これで再度チャレンジ！
訳文が表示されました！やったー！！
リネームするのは面倒だけど、これでなんとかなるならこれでいいやと思いました。

これで他のブロックも同じように国際化対応しようと思い、POファイルを作り、WP-CLIを実行しました。
すると、さっきとは違ってJSONファイルが複数出来上っています。これってどういうことかほんとに悩みました。

調べた結果、WP-CLIはPOファイルから翻訳関数があったファイル名を読み取り、その名前をmd5ハッシュに変換してJSONのファイル名にしていました。そのため、POファイルに複数の元ファイル名が記録されていると、その数だけファイルが生成されます。
こうすることでブラウザで表示されるファイル以外の翻訳ファイルは読みこまずパフォーマンスを向上させる仕組みということもわかりました。
しかし、これをされると先のリネーム作戦は実行できません。同一フォルダに同一名のファイルは保存できないからです。
結局、フリダシに戻りました。

### wp_set_script_translationsによるJSONファイルの指定（ようやく成功）

それから相当色々試してみました。po2jsonというパッケージも試しましたが、今一つしっくりきません。
その紆余曲折を語ると大変なので、最終的な結論だけ紹介します。
コードを示します。
```
function itmar_contact_block_block_init() {
	$script_handle = 'text_domain_handle';
	// スクリプトの登録
	wp_register_script(
		$script_handle,
		plugins_url( 'build/index.js', __FILE__ ),
		array( 'wp-blocks', 'wp-element', 'wp-i18n', 'wp-block-editor' )
	);

	//ブロックの登録
	register_block_type( __DIR__ . '/build',
		array(
			'editor_script' => $script_handle
		)
	);

	// その後、このハンドルを使用してスクリプトの翻訳をセット
	wp_set_script_translations( $script_handle, 'itmar_guest_contact_block', plugin_dir_path( __FILE__ ) . 'languages' );
	
	//PHP用のテキストドメインの読込（国際化）
	load_plugin_textdomain( 'itmar_guest_contact_block', false, basename( dirname( __FILE__ ) ) . '/languages' );
}
add_action( 'init', 'itmar_contact_block_block_init' );

```
このコードは[この公式ページ](https://ja.wordpress.org/team/handbook/block-editor/how-to-guides/internationalization/)を見て考え付きました。
やっぱり、最後は公式ページですね。
コードの解説です。
①「// スクリプトの登録」のセクションでは`wp_register_script`というコマンドを使っています。これは先に紹介した`wp_enqueue_script`と違ってスクリプトファイルをエンキューせず、スクリプトハンドルだけを登録するコマンドです。これでスクリプトハンドルを確保します。
②「//ブロックの登録」セクションでは`register_block_type`でブロックを登録しますが、その時の登録情報の一つである`editor_script`を①で確保したスクリプトハンドルに上書きしています。
③「// その後、このハンドルを使用してスクリプトの翻訳をセット」のセクションでは、そのスクリプトハンドルを使って、`wp_set_script_translations`を実行しているのです。

つまり、`${domain}-${locale}-${md5}.json`の形式のファイルが機能するためには、`wp_set_script_translations`の第１引数は、ブロックの`editor_script`に登録されたスクリプトハンドルである必要があるということです。`editor_script`に登録されたスクリプトハンドルというのは`build/index.js`をロードするものでないといけません。それが上記のコードの`wp_register_script`というわけです。
`@wordpress/create-block`で作ったブロックのプロジェクトではブロックの登録はblock.jsonの情報に基づいて行われるようになっています。その中では`"editorScript": "file:./index.js",`となっています。`wp_register_script`は、それと同等の働きをするということがわかりました。その上でスクリプトハンドルを使い回すことができるようにするというのが、今回の成功への道のりだったと言えます。

もう一点忘れていけないのはPOファイルの翻訳関数の存在していたファイル情報にbuild/index.jsが含まれていないければいけないということです。src/edit.jsだけでは表示されません。これはPOTファイルの生成に関連するもので、PoeditでPOTファイルを生成するとうまくいきませんでした。

## POファイルの更新方法
最後にPOファイルの更新方法を紹介します。これはPoeditの力を借りるのが一番だと思います。
POファイルの更新というのは、ソースファイルの更新により、翻訳関数の追加、削除、内容の変更が起こったとき必要になります。
これはPOTファイルを更新する必要があるので、ソースファイルを更新したら、次のWP-CLIコマンドを実行します。
```
wp i18n make-pot ./ languages/itmar_guest_contact_block.pot --exclude=node_modules/*
```

それからPoeditを立ち上げます。





![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-5-5.png)




![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-7.png)


更新したいPOファイルを選択して、開いてから「POTファイルから更新...」を押します。


![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-6.png)




![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-8.png)



![image](https://itmaroon.net/home/wp-content/uploads/2023/08/po-9.png)

このように新しい入力枠ができています。ここに入力していくことで更新することができます。

この作業が終わってPOファイルを保存すればPoeditがMOファイルは更新してくれます。
しかし、JSONファイルは更新してくれないので、最後に次のコマンドを実行するのを忘れないで下さい。

```
wp i18n make-json languages/ --no-purge
```

長いブログになりましたが、以上にしたいと思います。
これから国際化対応をする方には、重要な情報を詰め込んだつもりです。お役に立てれば光栄です。
最後までお読みいただきありがとうございました。