Help us understand the problem. What is going on with this article?

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

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

https://github.com/WordPress/gutenberg

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away