コンテンツにスキップ

抽出

抽出は、ソースコード中のマークされたすべてのメッセージを、翻訳者向けの .pot templateへ集める工程です。チュートリアルのループの ステップ3にあたります。このページはその工程のリファレンスです。設定、独自の 関数名、CI向けのstrictモード、そしてカタログをその後守り続ける検証を 説明します。

抽出にはbabel extraが必要です。

python -m pip install "gettext-tstrings[babel]"

ワークフロー

babel.cfgを作成します。

[gettext_tstrings: **.py]
encoding = utf-8

その後は通常のBabelコマンドを使います。

pybabel extract -F babel.cfg -c "Translators:" -o locales/messages.pot .
pybabel init -i locales/messages.pot -d locales -l ja
pybabel compile -d locales

initを実行するのは言語ごとに一度きりです。それ以降は、pybabel updateが 新しいtemplateを既存のカタログへ折り込みます。この繰り返しのサイクルと、 そのfuzzyエントリがリリースにとって何を意味するかは、 実運用で一巡します。

gettext_tstrings extractorは通常の_()gettext()ngettext()呼び出しも 処理するため、混在したcodebaseを1つのmappingで抽出できます。_()、4つの標準 gettext名、tr() / ntr() alias、遅延用のlazy_gettext() / lazy_pgettext()を認識します。

-cで翻訳者向けコメントを有効にする

通常のgettext呼び出しと同じく、pybabel extractで翻訳者向けコメントを 収集するには-c "Translators:"を渡す必要があります。渡さなくても抽出 自体は動きますが、コメントがカタログへ届かなくなります。カタログ上の コメントは、ワークフロー全体で 最も安上がりな品質向上の手段です。

独自の関数名を登録する

[gettext_tstrings: **.py]
tr_functions = tr translate
ntr_functions = ntr
[[mappings]]
method = "gettext_tstrings"
pattern = "**.py"
tr_functions = ["tr", "translate"]
ntr_functions = ["ntr"]

iniファイルでは1つの文字列、TOML mappingではlistを指定します。文字列内の名前は 空白またはcommaで区切れます。4通りの表記すべてに対応します。

optionはtr_functionsntr_functionsgettext_functionsngettext_functionspgettext_functionsnpgettext_functionsです。

-kはt-stringへ届きません

mytr(t"…")のような独自helperは、上記optionのいずれかへ名前を登録する必要が あります。Babelの--keyword機構はt-string literalを読めないため、 pybabel extract -k mytrは何も見つけず、警告も出しません。メッセージがPOTに 入らないだけです。併せて抽出される通常のgettext呼び出しには、引き続き-kを 使用できます。

対応するのは標準の引数順だけです。通常はmessageが先、pgettextではcontextの 次にmessage、npgettextではcontext、単数形、複数形の順です。

ローカルでは寛容に、CIでは厳密に

既定では、1つの不正なファイルで抽出全体が停止することはありません。

  • extractorが拒否するt-string(属性アクセス、式、不正な引数)は警告して skipします。
  • parseできないファイルも同様にskipします。
  • astでは受理されてもtokenizeだけが拒否するファイルもskipします。そうしないと Babel自身のpassが停止するためです。

これは編集中には便利ですが、そうでないときには危険です。skipされたメッセージは 単にPOTに現れないため、翻訳されることもなく、それを知らせるものもありません。 人の目が抽出を見ていない場所では、mapping optionでstrict = trueを指定して ください。

[gettext_tstrings: **.py]
encoding = utf-8
strict = true
[[mappings]]
method = "gettext_tstrings"
pattern = "**.py"
strict = true

これで上記の警告はすべてhard failureになります。こちらを本番向けの設定、既定を ローカル向けの設定と考えてください。

既存toolchainによるカタログ検証

Babelは抽出した各メッセージへ標準flagを付けます。この1行だけで、すでに使っている ツールのプレースホルダー検証が有効になります。

#. gettext-tstrings
#: app.py:4
#, python-brace-format
msgid "Hello {name}"
msgstr ""

こんにちは {nombre}と翻訳すると、追加設定なしで誤りが検出されます。

$ msgfmt --check-format -o /dev/null locales/ja/LC_MESSAGES/messages.po
locales/ja/LC_MESSAGES/messages.po:25: a format specification for argument
'name' doesn't exist in 'msgstr'
msgfmt: found 1 fatal error

Weblateでは同じ検証がPython brace formatとして文書化されており、 商用platformにも同じflagに基づくplaceholder QAがあります。各platformの挙動は それぞれのものです。ここで検証しているのは以下の2ツールです。

さらに、このパッケージはBabel checkerを登録します。そのため pybabel compilegettext-tstrings marker commentを持つすべてのメッセージへ 仕様の規則を適用します。

$ pybabel compile -d locales -l ja
error: locales/ja/LC_MESSAGES/messages.po:24: translation does not match the
source placeholders: {name} is missing; {nombre} is not in the source message
1 errors encountered.

複数形メッセージでは対象のformも示します。Babelが報告する行番号はmsgidの行であり、 ロシア語のblockにはその下に3つのmsgstrがあるためです。

error: locales/ru/LC_MESSAGES/messages.po:31: msgstr[1]: translation does not
match the source placeholders: {n} is missing

pybabel compileはそれでも.moを書き出します

上記エラーは報告され、終了statusは1ですが、不正なカタログもコンパイル されます。出荷を止められるのはその終了statusだけです。それを働かせるビルド ステップはCIで防ぐことで示します。

2つの検証は重複していません。このパッケージのcheckerの方が厳密な場面が 少なくとも2つあります。

  • msgidの波括弧がescapeされたものだけ(Config {{raw}} only)なら python-brace-format flagが付かないため、外部ツールは一切検証しません。
  • 複数形はformごとに検証します。上記ファイルをmsgfmt --check-formatで読むと status 0になります。兄弟formが保持するプレースホルダーを1つのformだけが 省略してもmsgfmtでは受理され、こちらのcheckerでは拒否されます。

msgfmtが検証するのは、Python brace formatとしてparseできるプレースホルダー名だけ です。ASCII名を使えば、toolchain内のすべてのツールがメッセージを検証できます。 ライブラリ自体はstr.isidentifier()を満たすすべての名前を受理します。

Templateとその他のツール

t-stringはPython構文なので、このライブラリの対象はPython sourceです。template言語は それぞれのi18n機能(Jinja2の{% trans %}、Djangoのtemplate tag)とBabel extractor を引き続き使います。すべて同じPOカタログへ入るため、混在したcodebaseでも1つの 翻訳ワークフローを維持できます。

現在のpygettextはt-stringをparseできないため、抽出にはBabelを使います。他の extractorや将来のpygettextが同じ規約を実装できるよう、 仕様として文書化しています。