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

WordPress 5.0 の新機能 Gutenberg はブロックエディタと呼ばれるリッチなエディタで、ゴリゴリの JavaScript フロントエンドプロジェクトです。Gutenberg のリポジトリは [Lerna](https://lernajs.io/) を使った monorepo 構成になっていて、 `./packages` フォルダの中に様々な面白いツールが入っています。

https://github.com/WordPress/gutenberg

この記事では、その Gutenberg の道具箱に同梱されている Babel プラグインの `@wordpress/babel-plugin-makepot` について紹介します。

# Babel とは?

[Babel](https://babeljs.io/) は JavaScript のトランスパイルを行うためのツールチェーンです。例えば、 ECMAScript 2015 以降のモダンな JavaScript で書かれたコードを「バベる」ことで、古いブラウザなどでも実行できる後方互換な JavaScript を生成したりすることができます。

```JavaScript
// main.js
const list = [1, 2, 3].map(n => n ** 2)
```

```shell
$ yarn add @babel/cli @babel/core @babel/preset-env --dev
$ npx babel main.js --presets "@babel/preset-env" --out-file output.js
```

```JavaScript
// 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](https://www.gnu.org/software/gettext/) という国際化・地域化ライブラリをベースにした仕組みです。
例えば、WordPress のプラグインやテーマのプログラムの中に埋め込まれている `"Translate me!"` という文字列を翻訳したいときは、次のような手順になります。 `your-domain` は、プラグインやテーマの名前などになります。

- プログラムに WordPress 組み込みの[翻訳関数](https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/_2)（`__` など）を適用する

   ```php
   <?php echo __('Translate me!', 'your-domain'); ?>
   ```

- プログラムを静的解析して翻訳テンプレート（pot ファイル）を生成する

   ```shell
   # main.php:1
   msgid: "Translate me!"
   msgstr: ""
   ```

- pot ファイルをコピーして各ロケールの翻訳を作成する

   - `ja_JP.po`

       ```shell
       # main.js:1
       msgid: "Translate me!"
       msgstr: "翻訳お願い！"
       ```

   - `zh_CN.po`

       ```shell
       # main.js:1
       msgid: "Translate me!"
       msgstr: "翻译吧！"
       ```

- po ファイル を元にバイナリの mo ファイルを生成してプログラムに同梱する

## フロントエンドのプロジェクトで翻訳を適用する

WordPress のプロジェクトでは、フロントエンドであっても pot ファイル・ po ファイル を使った翻訳の手法が取られているようです。この手法が WordPress テーマ・プラグイン開発者に馴染み深いものであること、バックエンド側と pot ファイル・ po ファイルを共通化できることが理由のように思われます。ただし、フロントエンドではバイナリの mo ファイルではなく [`Jed`](https://messageformat.github.io/Jed/) と呼ばれる形式の JSON ファイルを用います。この JSON ファイルを `@wordpress/i18n` のライブラリで読み込むことで、フロントエンドでも翻訳を適用することができます。

JSON ファイルの生成には、 [WP CLI](https://developer.wordpress.org/cli/commands/i18n/make-json/) の `wp i18n make-json` コマンドや [`po2json`](https://www.npmjs.com/package/po2json) というライブラリを使いますが、ここでは詳細は省きます。

- `ja_JP.po` から生成した `Jed` 形式の `ja_JP.json`

    ```json
    {
      "domain": "your-domain",
      "locale_data": {
        "your-domain": {
          "": {
            "domain": "your-domain"
          },
          "Translate me!": ["翻訳お願い！"]
        }
      }
    }
    ```

- 翻訳対象になる JavaScript プログラムの例

```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 で変換してみます。

```javascript
// 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')) // 翻訳お願い！
```

```shell
$ yarn add @wordpress/babel-plugin-makepot --dev
$ npx babel main.js --plugins "@wordpress/babel-plugin-makepot"
```

```shell
$ 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 です。

```shell
$ npx babel main.js --plugins "@wordpress/babel-plugin-makepot" > /dev/null
```

### pot ファイルの出力先を指定する

コンフィグファイル `.babelrc` を使うとプラグインのオプションを指定できます。
`@wordpress/babel-plugin-makepot` のオプションとして pot ファイルの出力先を指定する `output` があります。

```json

$ cat .babelrc
{
  "plugins": [
    ["@wordpress/babel-plugin-makepot", {"output":"src/languages/my-domain.pot"}]
  ]
}
```

### ReactやTypeScript でも使いたい

React、JSX、TypeScript を使ってるんだ！という方はこんな感じですね。

```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>
}
```

```json
$ cat .babelrc
{
  "presets": ["@babel/preset-react", "@babel/preset-typescript"],
  "plugins": ["@wordpress/babel-plugin-makepot"]
}
```

```shell
$ yarn add @babel/preset-react @babel/preset-typescript --dev
$ npx babel component.tsx
```

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

[^1]: 簡単 JavaScript AST 入門 https://rabbit-house.tokyo/books/javascript-ast/