読者です 読者をやめる 読者になる 読者になる

pandoc + sphinx + transifex で既存ドキュメントの翻訳を行う

この間の Sphinx+翻訳 Hack-a-thon で話題に上がった件について調査してみた。

お題は以下のとおり。

  • 元の文書は markdown で記述されている
  • Sphinx を使って翻訳してみようと思っている
  • 元の文書が更新された時に差分管理できるとよい

その場で出た案はタイトルの通りで、

  • pandoc で reST 形式に変換して
  • Sphinx を使って gettext 化して
  • sphinx-intl を使って transifex にアップロードして
  • transifex で翻訳をすると良さそう

というもの。
gettext 化を挟むことで、翻訳しやすく、また原文の変化を追いかけやすいそうな。

というわけで、実際に試してみました。

環境を作る

お題は redis-doc

まずはワークスペースを作る。

$ mkdir redis-doc
$ cd redis-doc
$ git init
$ virtualenv .
$ . bin/activate
$ pip install --pre sphinx sphinx-intl transifex-client
$ pip freeze > requirements.txt
$ rehash
$ git submodule add https://github.com/antirez/redis-doc

virutalenv で環境を作って、その中に Sphinx, sphinx-intl, transifex-client を入れてます*1
そして、翻訳対象の文書を git の submodule として管理します。

Sphinx 環境を作る

つづいて Sphinx 環境を作ります。
translated ディレクトリ以下にプロジェクトを作ります。

$ sphinx-quickstart translated
$ vi translated/source/conf.py
language = 'ja'
locale_dirs = ['locale/']
gettext_compact = False
$ vi Makefile
html:
        tx pull -l ja
        sphinx-intl build --pot-dir build/locale --locale-dir source/locale
        $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
$ cat > .gitignore
bin/
include/
lib/
translated/build
^D
$ git add .
$ git commit -m 'Initial commit'

conf.py では language, locale_dirs, gettext_compact の設定をしています。翻訳する上で必要最低限の設定です*2

また、Makefile の html ターゲットには tx pull と sphinx-intl build の実行を追加しています。
make html する際に transifex から最新の翻訳カタログを取ってくるようにするものです。
しばらく時間を開けるとコマンドを忘れてしまいがちなので、忘れないように記述を追加しておきます。

pandoc, transifex を使うための準備

続いて transifex 上に(アカウントと)プロジェクトを作成します。
サイトにアクセスすると大体わかると思うので説明は省略します。

そして、transifex-client(txコマンド)を使って transifex にアップロードする準備をします。

$ cd translated
$ tx init
$ echo 'type = PO' >> .tx/config
$ cd ..

transifex ディレクトリにて tx init を実行して、設定ファイルを生成しておきます。
また、0.11系では .tx/config ファイルに type オプションが指定されていないので、書き加えておきます。

最後に markdown ファイルを pandoc で reST に変換して、transifex にアップロードするためのスクリプトを用意します。

#!/bin/bash

TRANSIFEX_PROJECT_NAME=redis-doc

mkdir -p translated/source/commands
mkdir -p translated/source/topics

cd redis-doc
find . -name "*.md" -print0 | while read -r -d '' md;
do
    CONVERTED="../translated/source/${md%.*}.rst"
    echo -n "Translating $md ... "
    if [ "$md" -nt "$CONVERTED" ]; then
        pandoc "$md" -o "$CONVERTED"
        touch -r "$CONVERTED" "$md"
        echo "done."
    else
        echo "skipped."
    fi
done

find . \( -name "*.jpg" -or -name "*.png" -or -name "*.gif" \) -print0 | while read -r -d '' img;
do
    COPIED="../translated/source/$img"
    echo -n "Copying $img ... "
    if [ "$img" -nt "$COPIED" ]; then
        cp -a "$img" "$COPIED"
        echo "done."
    else
        echo "skipped."
    fi
done


cd ../translated
make html
sphinx-intl update --locale-dir source/locale --pot-dir build/locale
sphinx-intl update-txconfig-resources --transifex-project-name=$TRANSIFEX_PROJECT_NAME --locale-dir source/locale --pot-dir build/locale
tx push -s

このスクリプトを実行すると、

  • pandoc を使って markdown ファイルを reST 形式に変換して translated/ ディレクトリに配置
  • 画像類も適当にコピー
  • Sphinx を使って gettext 化を実施
  • sphinx-intl を使ってリソース登録
  • transifex-client (txコマンド) を使って、翻訳カタログを transifex に転送

という一連の手順を実行します。

transifex で翻訳する

ひたすら翻訳してください。

HTML に変換する

transifex で翻訳が一息ついたら HTML に変換してみましょう。

変換する前に、目次となる index.rst を作っておきます。:glob: オプションを使って簡易的に並べています。

Redis Documentation (Japanese)
===============================

.. toctree::
   :glob:

   README
   topics/*
   commands/*

準備段階で Makefile をいじっているので make html を実行するだけで ok です。

$ cd translated
$ make html

元の文章が新しくなった場合は submodule を更新し、スクリプトを再実行すれば ok です。

この方法の問題点

この方法を試したところ、いくつか問題がありました。

  • pandoc で適切に変換できない文章がある
    • HTML から変換する際など、うまく変換できないことがありました
    • redis-doc では見出しのないファイルがあるため、HTML に変換するときに toctree に含まれないという問題がありました
  • sphinx-intl はファイル名に空白を含んでいるとうまく扱えない
    • あとで pull request 送ります…

まとめ

Sphinx ではない形式で書かれたドキュメントについても、pandoc を使うことで Sphinx の翻訳フローに乗せることができます。
いくつか問題点があるなど、まだ完璧なフローとはいえませんが、
うまくアレンジしていくと手に馴染んだツールで翻訳を行うことができそうです。

*1:transifex-client は最新のβ版(0.11.1.beta)でないとまともに動かないので --pre オプションを指定しています

*2:language は sphinx-build 実行時に指定する方法もありますが、ここでは簡略化して conf.py に記述しました