@wordpress/babel-plugin-makepot で pot ファイル(翻訳テンプレート)を抽出する


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 CLIwp 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 にはうってつけです。栄枯盛衰の激しいフロントエンドの開発ツールが、伝統的な翻訳の手法とシームレスに統合されているところが非常に面白く感じます。