ill-identified diary

所属組織の見解などとは一切関係なく小難しい話しかしません

[小ネタ] 自作 R パッケージのメッセージをローカライズする

この記事は最終更新日から3年以上が経過しています

概要

公式ドキュメント “Translating R Messages, R >= 3.0.0” の記述をパッケージ開発の観点で簡単に紹介する.

イントロダクション

湯谷氏が『Rパッケージ開発入門』の補足として書いた文書にもあるが, この情報だけだとまだつまずくところがいくらかあったのでもう少し詳しく書いてみる. 公式ドキュメントは以下 (ただしこちらは主にR本体のローカライズの話である).

Translating R Messages, R >= 3.0.0

まず大前提として, 自作パッケージならヘルプドキュメントやエラーメッセージをどの言語で書こうが開発者の自由である. しかし CRAN に登録する場合, 英語で読めるものでなくてはならない. そこで, ロケールに応じて表示する言語を変えるように作る必要がある.

該当する湯谷氏の記述は6.1章. ヘルプドキュメントとメッセージの翻訳について触れられている. ただしヘルプドキュメントについては現状日本語を埋め込むことはできても国際化 (internationalization, i18n) の機能はないようなので, 完全に日本語で書かれたドキュメントで CRAN に登録するのは難しそうだ. そこで今回は, エラー/警告メッセージのローカライズ, つまり日本語ロケールでRを操作している時のメッセージを日本語表示する方法のみ紹介する.

ローカライズの下準備

まず, 追加でいくつかの外部プログラムのインストールが必要である. R のメッセージのローカライズは昔からある gettext というプログラムを使っているので, その関連ツールのインストールが必要になる. 逆に言えば他の場面でこれを使ったことがある人間はすぐ使いこなせるだろう. R 関連でなくても gettext の説明をしている資料も参考にすることができる.

  • gettext-tools

  • (オプション) poedit

gettext-tools は Ubuntu なら apt コマンドで gettext という名前でインストールできるので難しいことは無いと思う. Mac だと brew でできるらしいが, インストール後に brew link gettext --force が必要という話もある*1が未確認. Windows もどっかでバイナリを配布してるっぽいが未確認.

Poedit は翻訳ファイルを編集するエディタである. 翻訳ファイルはテキスト形式なので一般的なテキストエディタでも編集できるので poedit は必須ではないが, 翻訳ファイル編集専用エディタとしてシンプルな操作ができるのでそれなりに便利である. こちらも Linux, Mac, Windows に対応している.

ローカライズする場合, R パッケージの最低限のディレクトリ構成は以下のようになる

  • R

  • DESCRIPTION/

  • NAMESPACE/

  • inst/po/

  • po/

このうち, R, DESCRIPTION, NAMESPACEは今回の状況に限らずRパッケージならほぼ常に必要だから説明を略す. po ディレクトリは翻訳作業フォルダで, ここで .pot および .po を作成・編集する. このファイルを .mo ファイルに変換して inst/po/ 以下の言語名ディレクトリに配置したものが, 実際にローカライズメッセージとして使用される. つまり inst/po 以下には, 名前に反して .mo ファイルが配置される. この作業の多くは R の提供する基本パッケージで可能で, 開発者が手作業でやる必要のあるのはメッセージの対訳だけである.

チュートリアル

翻訳ファイルの準備

まずは翻訳作業の準備をする. .Rproj なり setwd() なりで上記のパッケージディレクトリをルートにし, tools パッケージを読み込み, 既に開発したソースコードから翻訳が必要な箇所を抜き取って pot ファイルを作成し, po/ 以下に配置する.

require(tools)
PKGNAME <- "hogehoge" # ここにパッケージ名を記入
dir.create("po")
xgettext2pot("./", potFile = paste0("po/R-", PKGNAME, ".pot"), name = PKGNAME,  version = "XXXX")

これで pot ファイルが作成される. もしいきなりファイルを作るのが怖いなら, xgettext2pot(./) とだけ書くと書き出す内容がコンソールに出る. この関数はディレクトリ内の R ファイルや C のソースコードが捜査対象となる. 私の fontregisterer パッケージで実行した場合の抜粋は以下の通り. これをコピペリネームして同じディレクトリに R-ja.po を作成する. これ以降はこの R-ja.po を編集する. .pot ファイルはテンプレートなので極力いじらないのがマナー (Rパッケージの挙動には直接影響しないのでルールではなく「マナー」と書いた).

冒頭の数行はメタデータで, 少なくとも R では文字コード以外は厳格に記入しなくとも動作する.

msgid ""
msgstr ""
"Project-Id-Version: fontregisterer 0.2\n"
"Report-Msgid-Bugs-To: https://github.com/Gedevan-Aleksizde/fontregisterer/issues\n"
"POT-Creation-Date: 2020-12-05 11:49\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=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

msgid "This function does not register any font because your operating system is neither Mac nor Windows"
msgstr ""
msgid "This function doesn't change font reference names if your operating system is Linux. If you want to change default font, please check out your system fontconfig"
msgstr ""

msgid "`%s` package will use %s as the default font family"
msgstr ""

文字コードの指定は必須である. ”Content-Type: ...” の行を以下のように書き換える (最後の方の \n を忘れずに).

"Content-Type: text/plain; charset=UTF-8\n"

上記の msgid で始まる行が, 元の (英語の) メッセージになる. 場合によっては数行続けて msgid 行が出力されるだろう. その直後にある,

msgstr ""

という行に対訳メッセージを自分で書き込む. gettext は多機能で他の資料では異なる書き方をすることもあるが, R ではなるべく xgettext2pot() の出す pot ファイルのまま翻訳するのが良いだろう. この翻訳には poedit を使ってもよいが, メタデータを勝手に書き換えることもあるので注意.

その後, 公式ドキュメントでは checkPoFiles() で整合性チェックをしろとか書いてあるが, ごく簡単な文法チェック (もちろん英語の文法チェックではない) をしてくれるだけで過信してはいけない.

checkPoFile("po/R-ja.po")

翻訳が終了したら, 最後に .mo ファイルに変換して適切な場所に配置する. この作業は update_pkg_po() で全て行ってくれる.

update_pkg_po("./", copyright = ..., bugs = ...)

...ことになっているが, R 3.6.x だと なんか普通にエラーが出て失敗する. エラーの原因はかなり初歩的なミスなので, この関数誰も使ってなかった可能性がある( .po.mo に変換して適切な名称のディレクトリに置けば動作するので必須とは言えない). 何らかの理由で R 4.0 以降へアップグレードできない/したくない場合のために, gist に修正版である update_pkg_po2 を用意した. 使い方は全く同じ.

最終的に以下のような構成になるはずだ.

  • R/

  • DESCRIPTION/

  • NAMESPACE/

  • inst/po/

    • en@quot/LC_MESSAGES/R-<PAKCAGE-NAME>.mo

    • ja/LC_MESSAGES/R-<PAKCAGE-NAME>.mo

  • po/

    • <PACKAGE-NAME>.pot

    • R-ja.po

    • R-ja.po~

    • R-en@quot.po

R-ja.po~update_pkg_po() 実行時に作成されるバックアップファイルなので不要なら消しても良い.

このような構成のパッケージプロジェクトからインストールすれば, ローカライズが適用される.

R における gettext の仕様について少し細かい話

変換処理の中身

tools::xgettext2pot() は, 指定したディレクトリ内の message, stop, warning, および gettext 系の関数に与えられたメッセージ

R-<パッケージ名>.po とか R-ja.po とかの命名ルールは tools パッケージおよび R パッケージの要件なので, 最終的に inst/po 以下に上記のように en@quot/LC_MESSAGES/R-<パッケージ名>.mo<言語名>/LC_MESSAGES/R-<パッケージ名>.mo というファイルをを置けば動作する. そしてそれらは po/R-en@quot.popo/R-ja.po を変換したものである. この変換は tools::update_pkg_po 内部で msgfmt を呼び出して処理している. 実際にはメタデータの整形とかマージとかの処理もしているが, 最低限必要なのはこの変換処理である. (update_pkg_po がやっぱり動かなくなったなどの理由で) もしコンソールで実行したいなら, 以下のような構文になる.

msgfmt -o inst/po/ja/LC_MESSAGES/R-fontregisterer.mo po/R-ja.po
msgfmt -o inst/po/en@quot/LC_MESSAGES/R-fontregisterer.mo po/R-en@quot.po

gettext の構文とドメイン

メッセージに変数を含みたい場合がある. たとえば既に見せた fontregisterer の pot ファイルの例にあるように, %s や, %d という記号を使う. この記号は C言語printf() に使われているものと同じで, フォーマット指定子とか format spceifier とか呼ばれているので, 記法の全容を知りたければググってほしい (これは昔からあるのでいろんな言語で同じ構文が流用されている). messege のみこの %s 記法は使えないので getttextf() を使う. 例えば fontregisterer では以下のように書いている.

message(gettextf("`%s` package will use %s as the default font family", pkg = "ggplot2", fam = "Sans"))

これが日本語ロケールでは以下と同値になる.

message(gettextf("`%s` パッケージはデフォルトフォントファミリに %s を使用します", pkg = "ggplot2", fam = "Sans"))

いちいちパッケージをインストールし直して動作確認するのが面倒な場合, bindtextdomain() で任意の翻訳ディレクトリを指定してメッセージを表示させてみるのが良いだろう. これでセッション中は指定したドメインの翻訳には, 指定した翻訳ファイルディレクトリが参照される.

bindtextdomain(domain = paste0("R-", PKGNAME), dirname = "inst/po")

ドメイン名は R-<パッケージ名> というフォーマットで定義される. この状態で,

message(gettextf("`%s` package will use %s as the default font family", pkg = "ggplot2", fam = "Sans", domain = paste0("R-", PKGNAME)))

とすればこのメッセージの翻訳は R-<パッケージ名> ドメインに対応したディレクトリのファイルが参照される.