やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

pybabelを使ってみた

pybabelコマンドで pot, po, mo, ファイルを作ってみた。

成果物

GitHubPython.i18n.Babel.201709151320

前回

pybabel

Babelをインストールすると、pybabelコマンドが使えるようになる。

$ pybabel -h
Usage: pybabel command [options] [args]

Options:
  --version       show program's version number and exit
  -h, --help      show this help message and exit
  --list-locales  print all known locales and exit
  -v, --verbose   print as much as possible
  -q, --quiet     print as little as possible

commands:
  compile  compile message catalogs to MO files
  extract  extract messages from source files and generate a POT file
  init     create new message catalogs from a POT file
  update   update existing message catalogs from a POT file

ヘルプにあるように、POT, MOファイルを作成できる。POファイルはpybabel init -l ja等のコマンドで作成できる。

サブコマンド 生成
extract pypot
init potpo
compile pomo

extract

pypot。コマンドを確認してみる。

 $ pybabel extract -h
Usage: pybabel extract [options] <input-paths>

extract messages from source files and generate a POT file

Options:
  -h, --help            show this help message and exit
  --charset=CHARSET     charset to use in the output file (default "utf-8")
  -k KEYWORDS, --keywords=KEYWORDS, --keyword=KEYWORDS
                        space-separated list of keywords to look for in
                        addition to the defaults (may be repeated multiple
                        times)
  --no-default-keywords
                        do not include the default keywords
  -F MAPPING_FILE, --mapping-file=MAPPING_FILE, --mapping=MAPPING_FILE
                        path to the mapping configuration file
  --no-location         do not include location comments with filename and
                        line number
  --add-location=ADD_LOCATION
                        location lines format. If it is not given or "full",
                        it generates the lines with both file name and line
                        number. If it is "file", the line number part is
                        omitted. If it is "never", it completely suppresses
                        the lines (same as --no-location).
  --omit-header         do not include msgid "" entry in header
  -o OUTPUT_FILE, --output-file=OUTPUT_FILE, --output=OUTPUT_FILE
                        name of the output file
  -w WIDTH, --width=WIDTH
                        set output line width (default 76)
  --no-wrap             do not break long message lines, longer than the
                        output line width, into several lines
  --sort-output         generate sorted output (default False)
  --sort-by-file        sort output by file location (default False)
  --msgid-bugs-address=MSGID_BUGS_ADDRESS
                        set report address for msgid
  --copyright-holder=COPYRIGHT_HOLDER
                        set copyright holder in output
  --project=PROJECT     set project name in output
  --version=VERSION     set project version in output
  -c ADD_COMMENTS, --add-comments=ADD_COMMENTS
                        place comment block with TAG (or those preceding
                        keyword lines) in output file. Separate multiple TAGs
                        with commas(,)
  -s, --strip-comments, --strip-comment-tags
                        strip the comment TAGs from the comments.
  --input-dirs=INPUT_DIRS
                        alias for input-paths (does allow files as well as
                        directories).

以下のように打ってみる。指定ディレクトリ以下を再帰的に検索し、存在する全.pyファイルから_(...)部分を抽出する。

$ pybabel extract --input-dirs=/tmp/Python.i18n.Babel.201709151320/src -o hello.pot
extracting messages from /tmp/Python.i18n.Babel.201709151320/src/main.py
extracting messages from /tmp/Python.i18n.Babel.201709151320/src/sub.py
extracting messages from /tmp/Python.i18n.Babel.201709151320/src/mypackage/mymodule.py
writing PO template file to hello.pot

以下のようなファイルがカレントディレクトリに出力された。

hello.pot

# Translations template for PROJECT.
# Copyright (C) 2017 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-09-15 14:45+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"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.5.1\n"

#: /tmp/Python.i18n.Babel.201709151320/src/main.py:7
msgid "Hello World !!"
msgstr ""

#: /tmp/Python.i18n.Babel.201709151320/src/main.py:17
msgid "Welcome i18n !!"
msgstr ""

#: /tmp/Python.i18n.Babel.201709151320/src/sub.py:1
msgid "Good bye."
msgstr ""

#: /tmp/Python.i18n.Babel.201709151320/src/mypackage/mymodule.py:1
msgid "Good Luck !"
msgstr ""

複数.pyファイルの_()部分が抽出され、このファイルに記載されているのがわかる。

init

potpo

まずは先ほど作ったhello.potをコピペしてhello_ja.potにする。適当にmsgstrを埋めていく。

次に、pybabel initサブコマンドを確認してみる。

$ pybabel init -h
Usage: pybabel init [options] 

create new message catalogs from a POT file

Options:
  -h, --help            show this help message and exit
  -D DOMAIN, --domain=DOMAIN
                        domain of PO file (default 'messages')
  -i INPUT_FILE, --input-file=INPUT_FILE
                        name of the input file
  -d OUTPUT_DIR, --output-dir=OUTPUT_DIR
                        path to output directory
  -o OUTPUT_FILE, --output-file=OUTPUT_FILE
                        name of the output file (default
                        '<output_dir>/<locale>/LC_MESSAGES/<domain>.po')
  -l LOCALE, --locale=LOCALE
                        locale for the new localized catalog
  -w WIDTH, --width=WIDTH
                        set output line width (default 76)
  --no-wrap             do not break long message lines, longer than the
                        output line width, into several lines
$ pybabel init -D hello -l ja -i hello_ja.pot -d ./languages/
creating catalog ./languages/ja/LC_MESSAGES/hello.po based on hello_ja.pot

ドメイン名、ロケール名(言語名)、入力ファイルパス、出力ディレクトリ、の4つを指定する必要がある。

compile

pomo

$ pybabel compile -h
Usage: pybabel compile [options] 

compile message catalogs to MO files

Options:
  -h, --help            show this help message and exit
  -D DOMAIN, --domain=DOMAIN
                        domains of PO files (space separated list, default
                        'messages')
  -d DIRECTORY, --directory=DIRECTORY
                        path to base directory containing the catalogs
  -i INPUT_FILE, --input-file=INPUT_FILE
                        name of the input file
  -o OUTPUT_FILE, --output-file=OUTPUT_FILE
                        name of the output file (default
                        '<output_dir>/<locale>/LC_MESSAGES/<domain>.mo')
  -l LOCALE, --locale=LOCALE
                        locale of the catalog to compile
  -f, --use-fuzzy       also include fuzzy translations
  --statistics          print statistics about translations
$ pybabel compile -D hello -d ./languages/
compiling catalog ./languages/ja/LC_MESSAGES/hello.po to ./languages/ja/LC_MESSAGES/hello.mo

update

potからpoを更新する。

potソースコードから_(...)を抽出したもの。ソースコードに新しいメッセージができたり、既存メッセージの実装行数が変わったりしたら新規にpotを作りなおすことになる。その後、既存のpoを更新してくれるのがこのコマンド。本コマンドで更新後、新しいメッセージは.poファイル内でmsgstr ""として追加されている。

$ pybabel init ...だと.poファイルを上書きしてしまい、これまで入力した翻訳テキストがすべてmsgstr ""になってしまう。それを避けるのが本コマンドの意義。

$ pybabel update -h
Usage: pybabel update [options] 

update existing message catalogs from a POT file

Options:
  -h, --help            show this help message and exit
  -D DOMAIN, --domain=DOMAIN
                        domain of PO file (default 'messages')
  -i INPUT_FILE, --input-file=INPUT_FILE
                        name of the input file
  -d OUTPUT_DIR, --output-dir=OUTPUT_DIR
                        path to base directory containing the catalogs
  -o OUTPUT_FILE, --output-file=OUTPUT_FILE
                        name of the output file (default
                        '<output_dir>/<locale>/LC_MESSAGES/<domain>.po')
  -l LOCALE, --locale=LOCALE
                        locale of the catalog to compile
  -w WIDTH, --width=WIDTH
                        set output line width (default 76)
  --no-wrap             do not break long message lines, longer than the
                        output line width, into several lines
  --ignore-obsolete     whether to omit obsolete messages from the output
  -N, --no-fuzzy-matching
                        do not use fuzzy matching
  --update-header-comment
                        update target header comment
  --previous            keep previous msgids of translated messages
$ pybabel update -D hello -l en -i hello.pot -d ./languages/
updating catalog ./languages/en/LC_MESSAGES/hello.po based on hello.pot

便利な点

pygettext.pymsgfmt.pyの2ツールを使ったときと比べて便利な点は以下。

  • 指定ディレクトリ配下の全pyファイルからpotファイルを作成できる
  • 新規potから既存poファイルを更新(マージ)できる
  • <lang_code>/LC_MESSAGES/<domain>.poなどのディレクトリを自動生成してくれる

だいぶ楽になる。

理想

  • ソースコードファイルの更新と同時にpypot→翻訳→pomoを自動実行してほしい

不足

  • ドメイン名やパスなどは自動管理してくれない
  • 翻訳とそのテキスト入力が自動化できない

メッセージID

運用的な話として、ソースコードに埋め込むMessageIdをどんなものにするか考えたほうがいい。

今は英語っぽいテキストにしているが、「微妙に表現がおかしくて変えたくなった」などの場合が起こりうる。文章でなく識別番号のようなもののほうがいいかもしれない。

たとえばMSG0000。でも、DEBUG, ERROR, INFOなどプレフィックスを変えたほうがいいとか、逆に番号だけのほうがいいとか、細かいことを考えると面倒。IDも自動で割り振ってくれたら楽なのだが、自分で区別が付けられなくなるかも知れない。

そもそも、poファイルを作成する前の本文となるため、識別番号だと意味不明。何かしらの資料と照らし合わせる必要がある。やはり文章のほうが楽かもしれない。少なくとも翻訳やコードリーディングするときは。

所感

pygettext.pymsgfmt.pyの2ツールを使い分けるだけではできないことも可能なので便利。

しかし、ドメイン名やパスなどは自動管理してくれない。コマンドの引数が多くて面倒。