Bash(Shell Script)内メッセージの国際化方法

前書き

BashによるShell Scriptを国際化(メッセージ翻訳)する方法は、C言語/Python/Rubyなどと同様です。つまり、gettextライブラリを使用し、翻訳対象メッセージの対訳を保存したカタログファイル(.moバイナリ、言語毎に必要)を用意する方法です。本記事では、その使い方を説明します。

しかし、そもそも論として、

「Bashで国際化が必要な規模のScriptを書いてはいけません」

この点に関しては、数々の議論があります。以下に、Google先生の見解を引用します。

原文:出典“Shell Style Guide”
If you are writing a script that is more than 100 lines long, you should probably 
be writing it in Python instead. Bear in mind that scripts grow. Rewrite your 
script in another language early to avoid a time-consuming rewrite at a later date.


100行以上の長さのスクリプトを書いているならば、おそらくその代わりにPythonで書くべきです。スクリプトが成長することに注意してください。後の時間のかかる修正を避けるために、別の言語でスクリプトを早く書き直してください。

「Bashは100行以内」と提案されているにも関わらず、本記事の国際化を紹介する背景は、以下の通りです。

国際化を必要とする理由
  • (前提)Bashライブラリを作成し、Script作成時の作業負荷を軽減させたい
  • ライブラリは国際化されている方が望ましい
  • 国際化に関する情報が少ない(=情報共有したい)

「Bashでユーティリティライブラリあるの?」「ライブラリを使うより、自分で書いたほうが早い」と思われる方がいると思います。Bashライブラリの少なさに関しても(正確には定着しない理由に関して)、「stack overflow “Bash utility script library”」で議論されています。ここまでで、何が言いたいかと言えば、本記事は普通にScriptを書くには必要のない情報という事です。

                    

前準備:Bashライブラリ1点、Shell Script1点

検証用にファイル2点を用意します。

まず、Bashライブラリ(libbash.sh)には、ライブラリ関数”mk_empty_file()”を実装します。この関数は、引数で指定されたファイルが存在しない場合はファイル作成およびメッセージを表示し、存在する場合はファイル作成しなかった旨をメッセージで示します。

次に、Shell Script(testshell)では、libbash.shを読み込み、前述のライブラリ関数を使用します。GoogleのShell Scriptコーディングスタイルでは、ライブラリはどの言語向けのものかを知る必要があるため、拡張子(“.sh”)が必要とされています。しかし、実行ファイルは、実行時にどの言語で書かれているかを知る必要がないため、拡張子(“.sh”)が必要ないとされています。

作成したShell Scriptに実行権限をつけて、実行した結果が以下の通りです。

                        

国際化ライブラリの準備

Debian環境では、gettext-baseパッケージが国際化用ライブラリを提供します。ライブラリが存在するかをwhichコマンドで確認し、存在しなければaptパッケージマネージャでインストールしてください。

         

国際化に必要なメッセージカタログとは

メッセージカタログは、「翻訳対象メッセージ(msgid)」と「そのメッセージ(msgid)に対する翻訳メッセージ」を記載した.moバイナリです。メッセージカタログを作成まで、いくつかの手順を実施します。

  1. プログラム内メッセージの中から、翻訳対象メッセージを選択
  2. メッセージカタログの基になる.potファイルを作成
  3. potファイルから、言語毎に.poファイルを作成(例:日本語向けはja.po)
  4. 言語毎の.poファイルをコンパイルし、メッセージカタログ(.mo)に変換

下表に、前述の手順で登場したファイルの役割を説明します。

ファイル拡張子 役割
pot (Potable Object Template)  翻訳対象メッセージIDおよびライン番号(メッセージ位置)を記載したファイル
po(Portable Object)  .poファイルを基に、各言語向けに翻訳したファイル。言語ごとに、別ファイルで管理
mo(Machine Object)  .poファイルをコンパイルしたバイナリファイル 

                

        

メッセージカタログの作成手順

まず、翻訳対象メッセージに対して、”eval_gettext”関数を付与します。C言語であれば、gettext関数(エイリアス”_”)を付与しますが、それと同様です。注意点としては、国際化用ライブラリとなるgettext.shをShell Script先頭で読み込む必要があります。基本的に、”eval_gettext”関数の付与は、

 の形式で記載します。注意すべきは、メッセージ内に変数を含む場合、変数の前にバックスラッシュが必要な事です。この理由は、eval_gettext関数が、変数値がまだ代入されていない文字列を期待するためです。

次に、カタログファイル名(拡張子”.mo”を除く)を環境変数TEXTDOMAINに、メッセージカタログの格納先PATHを環境変数TEXTDOMAINDIRに設定し、exportコマンドで設定します。

基本的には、環境変数TEXTDOMAINは、翻訳対象ファイル名(今回はライブラリ名)にが好ましいです。環境変数TEXTDOMAINDIRは、メッセージカタログの一般的な格納場所である”/usr/local/share/locale”もしくは”/usr/share/locale”とします。

                 

前述のlibbash.shであれば、以下のように修正します。

xgettextコマンドによって、メッセージカタログテンプレートである.potファイルを生成します。「-o」オプションは、生成ファイル名(.pot)の指定です。

potファイルをコピーし、各言語のpoファイルを作成します。今回は、日本語向け(ja.po)およびロシア語向け(ru.po)を作成します。この編集では、poeditを用います。

poeditは、翻訳対象以外の余計な情報を読まずにすむため、他のエディタ(例:vim/emacs/VScode/Atomなど)と比べて編集が楽です。特に、文字コードの設定などを自動で実施してくれる点が良いです。

以下に、poファイル作成後にpoeditをインストールし、poファイルを編集するまでの手順を示します。画像は、poeditの編集イメージです。本番運用では、Copyrightや著者情報も付与すべきでしょう。

             

最後に、各言語の.poファイルをコンパイルして、.moファイルを作成します。poeditで.poファイルを編集した場合は、保存時に自動的に作成されます。以下に、ターミナル上でのコンパイル方法を示し、生成物をシステムにインストールします。

           

動作確認

ここまでの手順で、国際化に必要な事柄が全て終了しています。以下に、動作確認結果として、出力メッセージが英語・日本語・ロシア語に切り替わったログを示します。なお、ロケールは、コマンド実行時に環境変数LANGを変更する事によって、設定しました。

余談ですが、”Создать “ではなく、”Создан”が正しいらしい。

                                   

メッセージカタログのアップデート方法

ソースコードを修正した場合、翻訳対象のメッセージ位置(行番号)が変わります。そのため、.potファイルを再作成しなければいけません。しかし、再作成した.potファイルから.poファイルを作成すると、今までに翻訳したメッセージが無くなってしまいます。

この問題を回避するために、poファイルの差分アップデートが用意されています。以下に例を示します。

              

参考

シェルスクリプト(bash)のメッセージを国際化する 2013年版

Advanced Bash-Scripting Guide: Appendix K. Localization

         

おすすめ