gettext のコマンドラインツールを使おう: SuperTuxKart を例に

# はじめに

## 便利な GUI ツールたち

昨今では、翻訳をするにも、GUI なツールや、web ベースのフレームワークなんかがいろいろあったりして、下手をすると、コマンドラインを使わなくても翻訳作業ができたりします。

スタンドアローンのツールとしては、たとえば po エディターの [Poedit](http://www.poedit.net/) があります。これは po だけでなく mo ファイルを書き出すことができます。KDE の [Lokalize](http://userbase.kde.org/Lokalize) は、プロジェクト管理に使うことを想定しており、もっといろんな機能がついています。

web ベースのシステムとしては、[3日目](http://d.hatena.ne.jp/naruoga/20131203/1386063346 "続・フリーなオフィススィートを翻訳すること - なるひこの Linux Printing お勉強日記")で紹介されている pootle 、[6日目](http://smellman.hatenablog.com/entry/2013/12/06/032455 "pgRoutingの例で見るsphinx-intlとtransifixの連携")で紹介されている Transifex、[10日目](http://qiita.com/nyampire/items/cb16a893b86a47898857 "")で紹介されている Translation wiki や、[Launchpad](https://translations.launchpad.net/) などがあります。

一翻訳者としてなら、コマンドラインを一切使わずに作業することはふつうにあることでしょう。

## コマンドライン、やらないか

こういう web ベースのツールは、便利な一方で、翻訳者からすると、内部でなにをやっているかがわかりにくくなってしまうという面があります。

ナウなヤングのなかには、[しゅうう先生に疑問をもたれつつも](https://twitter.com/syuu1228/status/410126920098267137)、[LinuxをWindows並の使い方しかしてないマン](https://twitter.com/lindwurm_/status/410126548495519745)もいます。しかし、その某氏は [SuperTuxKart](https://translations.launchpad.net/stk) の日本語担当者だったり、[mikutter](https://www.transifex.com/projects/p/mikutter/) の翻訳プロジェクト管理者だったりします。

しょせんこんなのは道具なので、コマンドラインを使わずとも目的が達成できればよいのですが、たぶん某氏の場合はコマンドラインを使えばより容易にできることがあると思います。

ついでに、某氏は大学受験のため、しばらく OSS 翻訳から離れるといっていますが、某氏のかわりに作業してくれる人が現れないことには、大変アレな将来が想像されてしまってアレなので、翻訳をやってみたいという人向けにも説明を書いてみることにしました。ナウなヤング求む。除毛クリームあげますから!

# gettext を用意する

ここでは、SuperTuxKart を例に使うので、SuperTuxKart で使われている gettext の説明をします。

## gettext とは

gettext は、国際化のためのフレームワークのひとつです。もともと GNU が作ったわけではありませんが、いまどきは GNU の実装が事実上の標準くさいのでアレつうか、[巨大なマニュアルがある](https://www.gnu.org/software/gettext/ "gettext - GNU Project")ので、詳細はそっちを見てください。

[2日目](http://qiita.com/iwaim@github/items/883e4aeb29085e39581a "WordPressプラグインを題材にGettextでの翻訳方法を紹介") でいわいさんが [po, pot, mo といったファイル](https://github.com/iwaim/doc-ja-sample/tree/master/languages)について書いています。翻訳をメンテナンスするには、これらのファイルを触る必要があり、gettext には便利なコマンドラインツールがいろいろあります。

いまどきの Unix 系 OS では、gettext はかなり広く使われており、国際化が必要な状況では必須でしょう。そんなわけで、デスクトップ用途の Linux **とか**の多くでは、gettext のライブラリーが標準で入っていたりします。

「ライブラリーが」というのは、gettext(3) などのライブラリー関数が使えるだけで、翻訳用の便利なコマンドラインツールは含まれないことをいっています。これらは、国際化されたプログラムを実行するだけなら必要ないものだからです。

Debian なら [gettext-base](http://packages.debian.org/ja/sid/gettext-base) と [gettext](http://packages.debian.org/ja/sid/gettext)、pkgsrc なら (NetBSD 本体の gettext-lib 相当は別実装です) [gettext-lib](http://pkgsrc.se/devel/gettext-lib) と [gettext](http://pkgsrc.se/devel/gettext) というように、ライブラリーとそれ以外が別パッケージに分かれていたりします。

## gettext を入れる

いまどきの Linux **とか**なら、パッケージシステムで簡単に入れられるものが多いと思います。Debian なら `apt-get install gettext` 、pkgsrc なら /usr/pkgsrc/devel/gettext で `make install` のように。

## gettext でできること

いっぱいありますが、某氏と SuperTuxKart (このあとで説明) で必要そうなことのみ。

### po → mo の変換

po ファイルは人間が扱いやすいテキストファイルで、翻訳作業に使うものです。これを実際に国際化されたプログラムで使う場合は mo ファイルにする必要があります。msgfmt(1) で変換できます。

```% msgfmt foo.po -o foo.mo```

引数にファイル名のかわりに - を指定すれば、標準入力/標準出力が使われるので、フィルターとして使うこともできます。また、さまざまなオプションがあるので、詳細は[マニュアル](http://www.gnu.org/software/gettext/manual/html_node/msgfmt-Invocation.html#msgfmt-Invocation "Invoking the msgfmt Program")をご覧ください。

Launchpad などの web システムには、po ファイルがダウンロードできるものがありますが、このように mo に変換して、適切な場所 (たとえば /usr/share/locale/ja/LC_MESSAGES 以下など) に置けば、作業中の翻訳をプログラムに組み込んで動作確認することができます。

### mo → po の変換

何らかの理由で、すでにある mo ファイルの中身を見たり書き換えたりしたいことがあるかもしれません。msgunfmt(1) で変換できます。

```% msgunfmt foo.mo -o foo.po```

po から mo にした時点で、コメントなどが削除されているので、msgunfmt しても元の po と同じものが手に入るわけではありません。
私は、既存の翻訳をちょっと書き換えてみる、といった場合には、msgunfmt で mo から po にして、書き換える、というように使っています。

### ソースからメッセージ抽出 (pot ファイル作成)

po ファイルは何から作られているかといえば、po テンプレート (pot ファイル) をもとにせっせと翻訳して作られているわけです。

ではその pot ファイルはといえば、ソースファイル中の翻訳対象のメッセージを抽出して作られます。xgettext コマンドを使いますが、これは複雑でいろいろオプションとかがあるので、とりあえず、ものすごく単純な使い方として、1個のソースファイル (ここでは、SuperTuxKart の [main.cpp](https://svn.code.sf.net/p/supertuxkart/code/main/trunk/src/main.cpp) ) からメッセージを抽出してみます。

```% xgettext --keyword=_ main.cpp```

こうすると、messages.po というファイル (-o オプションで変更できます) ができあがり、以下のような内容になります。

```
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-12-15 13:53+0900\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: main.cpp:1511
msgid ""
"SuperTuxKart may connect to a server to download add-ons and notify you of "
"updates. Would you like this feature to be enabled? (To change this setting "
"at a later time, go to options, select tab 'User Interface', and edit "
"\"Allow STK to connect to the Internet\")."
msgstr ""
```

複数のソースファイルがある場合は、`xgettext ... foo.cpp bar.cpp baz.cpp` みたいに全部引数に与えてやればよいです。

### po ファイルのマージ

マージといってもいろいろありまして、複数の po ファイルをマージするといったこともあると思いますが、ここでは、「古い po ファイル」と「新しい pot ファイル」をマージして、「新しい po ファイル」を作る方法です。

ソースが更新されると、それをもとに新しい pot ファイル (たとえば supertuxkart.pot) が作られます。新バージョンの翻訳は、この pot ファイルをもとにおこないますが、既存の古い翻訳 (たとえば ja.po) があるなら、使える翻訳は使わないと無駄ですよねってことで、msgmerge の出番なのです。

web ベースの翻訳システムを使っていると、このへんを裏で自動でやってくれたりするので、翻訳者としては意識しなくて済むのですが、翻訳プロジェクト管理者にとっては必要かと。

ja.po (古い翻訳ファイル)
supertuxkart.pot (新しい pot ファイル)
があるときに、以下のようにすると、ja.trunk.po として、マージ後のファイルが保存されます。

```% msgmerge ja.po supertuxkart.pot -o ja-trunk.po```

### その他

po ファイルを操作するためのツールがまだまだあります。詳細は [GNU gettext のマニュアル](http://www.gnu.org/software/gettext/manual/html_node/Manipulating.html#Manipulating "Manipulating PO Files")をご覧ください。

# SuperTuxKart を例に

SuperTuxKart が 0.8 から 0.8.1 になったときを例に、翻訳のメンテナンス作業について説明しましょう。
大規模なプロジェクトですと、言語パックを別配布にしたりするケースがあり、本体より後に言語パックが配布される事例もあるようですが、SuperTuxKart ではすべての言語が本体と一緒に配布されます。
つまり、0.8.1 のリリースより前に 0.8.1 の翻訳をしておかないと、0.8.1 のリリースに日本語が含まれないということになります。

しかし、翻訳プラットフォームの Launchpad では、ソースファイルの変更が即座に反映されてはいませんでした。つまり、新たに追加されたメッセージや、変更が加わったメッセージが、すぐには翻訳できない状態だったということです。

日本語に限ったことでもないので、ここは「Launchpad で翻訳できんで困っとるがや。なんとかしてちょ」というのが正しいかと思いますが (そこでありがちなのが「じゃあ権限やるからお前やれ」)、STK 以外でも応用できますし、ローカルで作業する方法を知っておいても損はないでしょう。

Launchpad に翻訳を登録しないと、アプリケーションの配布に含まれないので、最終的には Launchpad に翻訳を登録する必要がありますが、とりあえずローカルで翻訳して動作確認するまでの作業は Launchpad がなくてもすることができます。

また、いくつかの web ベースの翻訳システムでは、po ファイルをアップロードする機能があるので、そういう場合はローカルで作業したファイルをアップロードすればよく、無駄に作業量が多くなることもないでしょう。

ここでは、gettext のコマンドラインツールを使った作業方法を説明しますが、大きめのプロジェクトなら、たいがい、pot の更新や po へのマージを自動化するための仕組みを用意しているので、そういうのを見て参考にするとよいでしょう。

## ソース取得

[STK 公式サイトの説明] (http://supertuxkart.sourceforge.net/SVN_Guidelines) にしたがい、subversion で最新のソースをとってきましょう。

```% svn checkout https://svn.code.sf.net/p/supertuxkart/code/main/trunk/ stk-trunk```

これで、stk-trunk ディレクトリー以下に、最新のソース一式が用意されます。
git などの分散型 VCS とは異なり、最新のソースを持ってきただけなので、過去のものが必要な場合は、

```% svn checkout https://svn.code.sf.net/p/supertuxkart/code/main/tags/0.8.1/ 0.8.1```

(0.8.1 リリースのソース一式を取得)

```% svn up -r {2013-12-16} foobar.cpp```

(foobar.cpp の、2013/12/16 時点のバージョンを取得)

などのようにして、都度サーバーから取得する必要があります (がっつり作業したい場合はリポジトリー全部をとってくるという手もありますが)。

## pot を作る

最新のソースをもとに pot を作りましょう。前述のように、xgettext(1) を使えばいいので、抽出対象のファイルを調べて、それにあわせたコマンドライン引数を xgettext(1) に渡してやれば……、って、

ソースツリーに [update_pot.sh](https://svn.code.sf.net/p/supertuxkart/code/main/trunk/data/po/update_pot.sh) なんてスクリプトがあるじゃないですかやだー!

ソースツリーの最上層 (上でチェックアウトした stk-trunk ディレクトリー) に移動して、

```% sh data/po/update_pot.sh```

を実行すると、
data/po/supertuxkart.pot
が新しくなっています。
(上書きされるので、元の pot ファイルが必要ならバックアップをとっておきましょう)

ということで、xgettext の説明は無駄になってしまいましたが、この update_pot.sh ファイルを見ると、ソースファイルを直接 xgettext に食わせるほかに、XML ファイルからメッセージを抽出するという処理を別にやっていたりします。[intltool](http://freedesktop.org/wiki/Software/intltool/) は、こういったのも含めてよきにはからってくれるツールだと思いますが、よく知らないので誰か解説してください。

## マージして ja.po を更新

```% msgmerge ja.po supertuxkart.pot -o ja-trunk.po```

のようにすると、新しい po ファイル ja-trunk.po ができます。これは supertuxkart.pot をもとにした日本語翻訳ファイルであり、
- ja.po の翻訳済みメッセージのうち、そのまま使えるものはそのままで
- 似ているものは fuzzy フラグつきで
引っ張ってきているので、未訳や fuzzy となっているメッセージを翻訳してやれば OK です。
fuzzy フラグつきのメッセージは、Launchpad では Suggestions に相当します。

## 翻訳して動作確認

できあがった ja-trunk.po で未訳や fuzzy となっているメッセージを翻訳します。

翻訳が終わったら、

```% msgfmt ja-trunk.po -o supertuxkart.mo
 # cp supertuxkart.mo /usr/share/locale/ja/LC_MESSAGES/```

のようにインストールし、実際に SuperTuxKart をプレイして、問題ないか確認します。

# おまけその1: フォントとか

SuperTuxKart では、ゲーム中のメッセージの表示用の文字に、[あらかじめ用意された画像を使っています](https://svn.code.sf.net/p/supertuxkart/code/main/trunk/data/fonts/) ([処理してるのはこのへん](https://svn.code.sf.net/p/supertuxkart/code/main/trunk/src/guiengine/scalable_font.cpp))。[ラテン文字に装飾的な書体を使っている](https://svn.code.sf.net/p/supertuxkart/code/main/trunk/data/fonts/LayneHansom0.png)ためかと思いますが、[日本語ではふつーの書体 (M+)](https://svn.code.sf.net/p/supertuxkart/code/main/trunk/data/fonts/Mplus2p_JP0.png) なので、[あらかじめ用意された画像にある文字しか翻訳文字列に使えない](https://twitter.com/lindwurm_/status/399155952626069504)という、あまりうれしくない状況のようです。

[CJK 統合漢字が考慮されていない](https://svn.code.sf.net/p/supertuxkart/code/main/trunk/data/fonts/StkFont.xml) (一部の文字が中華フォントになるのはこのせい) とか、日本語のテキストが折り返されないとか、CJK 以外も考えたらもっとアレなつくりですので、翻訳以外でも「俺が STK の国際化をなんとかしてやるぜ」というナウなヤングが現れることを超期待しますが、とりあえず現状ですと、ja.po に存在する「異英鋭憶壊基警古告込残績絶叩投踏覇」の各文字が存在しないので、こいつらを追加しないといけません。

どうも、以前に作った人が、作り方を書いていないのでアレなのですが、これもコマンドラインである程度自動化できれば楽ができるかもしれませんね。

ちょっと時間がなかったので、実際に作るところまでできませんでしたが、以下のようなかんじでしょうか。

## M+ フォントのソースをとってくる

[公式サイトの説明](http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/download/#cvs)どおり、

```
% cvs -d:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/mplus-fonts login
CVS password: (何も入れず Enter)
% cvs -z3 -d:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/mplus-fonts co mplus_outline_fonts
```

これで M+ フォントの最新のソース一式が手元に揃いました。
更新するときは、mplus_outline_fonts ディレクトリーで `cvs update` します。

## 必要な文字のデータを抜き出す

```MPLUS_FULLSET=yes make split-svgs``` で、すべての文字単位に切り出された svg ができますが、これだと時間がかかるので、codemap ファイルをもとに必要な文字・ウェイトだけ抜き出せればよい?

## ラスタ画像に変換

```% convert -geometry 30x30 -alpha On -background "#ffffff00" -negate u4E57.svg u4E57.png``` のようにすると、30x30 サイズの .png ができます。

元の画像にウェイトが Medium と書いてありますが、この方法で作ると、Medium はやや太め、Regular では細すぎというかんじです。

## 配置と XML 作成

ImageMagick の convert(1) とか使って、30x30 サイズの文字画像を合成していけばヨサゲ?
で、テキトーに現行のものと合成した[画像](https://twitter.com/okano_t/status/408342286590693377)と [XML](http://jnug.net/lindwurm/StkJapaneseFontTsuika.xml) をでっちあげてみたわけですが、なんか配置が変 (XML では文字が等間隔じゃなさげとか、画像もちょっとずれるぽいとか) なので、全部作り直しが吉ですかね。

漢字の場合はサイズが全部同じなので、convert -append とか montage とかでもできるかも、とか書いてたら、convert -font なんていうオプションがあることを今知ったのでした。

# おまけその2: mikutter 言語ファイルの配布とか

某氏が困っていることとして、mikutter 翻訳に関して、[Transifex から取得したファイルのディレクトリー構成が、配布用の構成と違っていて困る](https://twitter.com/lindwurm_/status/387974725743308801)というのがありますので、ついでに触れておきましょう。

なにか困ったことがあれば、他にも同じことを考えてる人がいるだろうから、パクれないか、と考えてみるのはよいことです。もちろん本当に困っているなら、助けを求めるのもよいでしょう。

Transifex を使ってるプロジェクトではありませんが、SuperTuxKart のソースツリーを見ると、data/po 以下に [gen-mo.sh](https://svn.code.sf.net/p/supertuxkart/code/main/trunk/data/po/gen-mo.sh) というスクリプトがあります。

ひょっとしてこれが参考になるのではないかと期待しつつ、中身を見てみると、

```
msgfmt ca.po -o ca/LC_MESSAGES/supertuxkart.mo
msgfmt cs.po -o cs/LC_MESSAGES/supertuxkart.mo
 (28行省略)
msgfmt hr.po -o hr/LC_MESSAGES/supertuxkart.mo
```

……orz

Bourne シェル系なら、

```
$ for pofile in *.po
do
msgfmt $pofile -o `basename $pofile .po`/LC_MESSAGES/supertuxkart.mo
done
$
```

みたいにできるので、そんなかんじのスクリプトを書くと、少しすっきりしますかね (ヒントのつもり)

mikutter の場合は basename だけではアレなので、変数展開でなんかできそうですね (ヒントのつもり)

```
#!/bin/sh

tx_path="translations/mikutter.eject/ja.po"

tx_dirname=${tx_path%/*}
tx_filename=${tx_path##*/}
langcode=${tx_filename%.*}
pluginname=${tx_dirname##*.}

echo "mikutter/core/plugin/${pluginname}/po/${langcode}/${pluginname}.po"
```

# さいごに

OSC福岡あわせの DocFest では、困っているナウなヤングがもうひとりいたので、こっちのネタも書きたかったのですが、時間がなくて無理でした。いずれどこかで書きたいと思いますが、nginx のドキュメント翻訳とか po4a のこととか書いてくれるひとがいると死ぬほど喜びます。

# よこく

明日は @syu_cream さんです。