WordPress 5.0 の新機能 Gutenberg はブロックエディタと呼ばれるリッチなエディタで、ゴリゴリの JavaScript フロントエンドプロジェクトです。Gutenberg のリポジトリは Lerna を使った monorepo 構成になっていて、 ./packages
フォルダの中に様々な面白いツールが入っています。
この記事では、その Gutenberg の道具箱に同梱されている Babel プラグインの @wordpress/babel-plugin-makepot
について紹介します。
Babel とは?
Babel は JavaScript のトランスパイルを行うためのツールチェーンです。例えば、 ECMAScript 2015 以降のモダンな JavaScript で書かれたコードを「バベる」ことで、古いブラウザなどでも実行できる後方互換な JavaScript を生成したりすることができます。
// main.js
const list = [1, 2, 3].map(n => n ** 2)
$ yarn add @babel/cli @babel/core @babel/preset-env --dev
$ npx babel main.js --presets "@babel/preset-env" --out-file output.js
// output.js
"use strict";
var list = [1, 2, 3].map(function (n) {
return Math.pow(n, 2);
});
Babel は JavaScript の AST (抽象構文木)を扱うライブラリの集まりです。JavaScript のコードをパースして AST を生成したり(パーサー)、AST の木構造を走査して組み替えたり(トラバーサー)、AST から新しい JavaScript コードを生成したり(ジェネレーター)するツールが含まれています。1
フロントエンド界隈では、babel-loader
を webpack に読み込んで React の JSX を変換するのに使ったりと、おなじみのツールですね。
POT とは?
もう一つのトピックは、POT と呼ばれる翻訳テンプレートです。 .po
ファイル・.mo
ファイルとともに、 .pot
ファイルはテーマやプラグインを i18n (国際化)・l10n(地域化)するときに用いられるファイルで、WordPress ユーザーにとって馴染み深いものだと思います。これは gettext という国際化・地域化ライブラリをベースにした仕組みです。
例えば、WordPress のプラグインやテーマのプログラムの中に埋め込まれている "Translate me!"
という文字列を翻訳したいときは、次のような手順になります。 your-domain
は、プラグインやテーマの名前などになります。
-
プログラムに WordPress 組み込みの翻訳関数(
__
など)を適用する<?php echo __('Translate me!', 'your-domain'); ?>
-
プログラムを静的解析して翻訳テンプレート(pot ファイル)を生成する
# main.php:1 msgid: "Translate me!" msgstr: ""
-
pot ファイルをコピーして各ロケールの翻訳を作成する
-
ja_JP.po
# main.js:1 msgid: "Translate me!" msgstr: "翻訳お願い!"
-
zh_CN.po
# main.js:1 msgid: "Translate me!" msgstr: "翻译吧!"
-
-
po ファイル を元にバイナリの mo ファイルを生成してプログラムに同梱する
フロントエンドのプロジェクトで翻訳を適用する
WordPress のプロジェクトでは、フロントエンドであっても pot ファイル・ po ファイル を使った翻訳の手法が取られているようです。この手法が WordPress テーマ・プラグイン開発者に馴染み深いものであること、バックエンド側と pot ファイル・ po ファイルを共通化できることが理由のように思われます。ただし、フロントエンドではバイナリの mo ファイルではなく Jed
と呼ばれる形式の JSON ファイルを用います。この JSON ファイルを @wordpress/i18n
のライブラリで読み込むことで、フロントエンドでも翻訳を適用することができます。
JSON ファイルの生成には、 WP CLI の wp i18n make-json
コマンドや po2json
というライブラリを使いますが、ここでは詳細は省きます。
-
ja_JP.po
から生成したJed
形式のja_JP.json
{ "domain": "your-domain", "locale_data": { "your-domain": { "": { "domain": "your-domain" }, "Translate me!": ["翻訳お願い!"] } } }
-
翻訳対象になる JavaScript プログラムの例
import { __, setLocaleData } from '@wordpress/i18n'
import ja_JP from './languages/ja_JP.json'
// 翻訳辞書を適用
const localeData = ja_JP.locale_data['your-domain']
setLocaleData(localeData, 'your-domain')
console.log(__('Translate me!', 'your-domain')) // 翻訳お願い!
これに先立って、JavaScript を静的解析して pot ファイルを生成しないといけないわけですが、そこで用いられているのが @wordpress/babel-plugin-makepot
です。
POT ファイルを生成する
実際に上記の JavaScript を Babel で変換してみます。
// main.js
import { __, setLocaleData } from '@wordpress/i18n'
import ja_JP from './languages/ja_JP.json'
const localeData = ja_JP.locale_data['your-domain']
setLocaleData(localeData, 'your-domain')
console.log(__('Translate me!', 'your-domain')) // 翻訳お願い!
$ yarn add @wordpress/babel-plugin-makepot --dev
$ npx babel main.js --plugins "@wordpress/babel-plugin-makepot"
$ cat gettext.pot
msgid ""
msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"X-Generator: babel-plugin-makepot\n"
#: translate.js:7
msgid "Translate me!"
msgstr ""
Babel を通した時に副作用として gettext.pot
が生成されています。
translate.js -> (Babel) -> translate.js
└> gettext.pot
この時の Babel の標準出力は不要なので、捨ててしまって OK です。
$ npx babel main.js --plugins "@wordpress/babel-plugin-makepot" > /dev/null
pot ファイルの出力先を指定する
コンフィグファイル .babelrc
を使うとプラグインのオプションを指定できます。
@wordpress/babel-plugin-makepot
のオプションとして pot ファイルの出力先を指定する output
があります。
$ cat .babelrc
{
"plugins": [
["@wordpress/babel-plugin-makepot", {"output":"src/languages/my-domain.pot"}]
]
}
ReactやTypeScript でも使いたい
React、JSX、TypeScript を使ってるんだ!という方はこんな感じですね。
// component.tsx
import { __, sprintf } from '@wordpress/i18n'
type Props = {
name: string
}
export const Component = (props: Props) => {
return <p>{sprintf(__('My name is %s.', 'your-domain'), props.name)}</p>
}
$ cat .babelrc
{
"presets": ["@babel/preset-react", "@babel/preset-typescript"],
"plugins": ["@wordpress/babel-plugin-makepot"]
}
$ yarn add @babel/preset-react @babel/preset-typescript --dev
$ npx babel component.tsx
$ cat gettext.pot
msgid ""
msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"X-Generator: babel-plugin-makepot\n"
#: component.tsx:8
msgid "My name is %s."
msgstr ""
※ 「言語を変えた時に render()
が走らないから翻訳が適用されないじゃないか!」という問題があります。WordPress はロケールの変更の時に必ずページ遷移があるようなので、問題が発生しないのだと思いますが、ページ遷移なしで言語を切り替えたい時は工夫が必要そうです
まとめ
Gutenberg のツール @wordpress/babel-plugin-makepot
について紹介しました。pot ファイルを生成するためにはコードを静的解析する必要があるのですが、AST を走査できる Babel にはうってつけです。栄枯盛衰の激しいフロントエンドの開発ツールが、伝統的な翻訳の手法とシームレスに統合されているところが非常に面白く感じます。
-
簡単 JavaScript AST 入門 https://rabbit-house.tokyo/books/javascript-ast/ ↩