Tcl/Tk による I18N(国際化) プログラミングの試み
1999年9月24日 R.川岸  <kawagisi@yk.rim.or.jp>


目次
1.  まえがきと参考文献
2.  方針
3.  メッセージカタログ
4.  表示する言語コードの調査
5.  メッセージカタログの選択と表示


1. まえがきと参考文献

このドキュメントは、Tcl/Tk で I18N(国際化) 対応のプログラムを作るにはどうすればよいかについて私が実験した内容をまとめたものです。

ロケールを英語と日本語に切替えてそれぞれの言語で表示できることは確認していますが、その他の様々な言語環境で試したわけではありません。取り敢えずこれで良さそうな気もするが、何か重大な見落としや勘違いがあるのではないか、と気がかりです。これをたたき台にして多くの方からご意見を頂戴してより良いものに改良できればいいなぁ、と思っています。

最初に I18N 対応のプログラムを作ろうと思ったきっかけですが、私は conv2nmail4 というメーラ移行ツールを作って公開しています。これは Windows などの他のメーラから Nmail4(Ntool) というメーラに乗換える時に過去のメールを mail4(Ntool) で扱えるようにするものです。これを作り始めた頃に Nmail4 の作者 Nobby 平野さんに、その旨お知らせしたところ I18N(国際化)するべきと説得(?)され、挑戦してみることにしました。

そうはいったものの国際化プログラミングの知識など全くありません。そこで I18Nまたは国際化プログラミングというキーワードで書籍やインターネットを探すところから始め、以下の文献をみつけました。

[書籍]
1. 「Linux を 256倍使うための本」 ASCII出版局
2. 「Linux/FreeBSD 日本語環境の構築」 ソフトバンク刊
3. 「マルチリンガル環境の実現」プレンティスホール出版
4. 「ソフトウェアローカリゼーション実践ハンドブック」SRC刊
5. 「国際化プログラミング I18N ハンドブック」共立出版   (書評が日経Linux創刊号194頁にある)

[雑誌]
6. 「Linux国際化プログラムのポイントを学ぶ」   ムック日経Linux (1999年4月30日) 232頁
7. 「GNU automake の GUI作成」(gettext の解説がある)   ムック日経Linux (1999年4月30日) 240頁
8. 「GTK+ アプリケーションの国際化」 (特集記事の中のコラム)   日経Linux創刊前号  93頁
9. オフィス・ソフトの舞台裏(日本語処理の仕組み)  日経Linux創刊号 86頁 (オムロン・ソフトウェア 金田昌石)
10. 「商用Linux 並みの国際化機能をglibcへ Linux研究会NLS分科会の活動」 日経Linux創刊号 150頁
11. 「Linux プログラムにおけるメッセージの国際化」Linux Japan 1999年8月号166頁
12. 「Linux の日本語環境」 Linux Magazine No.2 121頁
13. 「Linux の日本語環境その実像をとらえる」Linux World 第3弾 32頁(鈴木 大輔)
14. 「GTK+とgettextで日本語/国際化対応アプリケーションを開発する」Linux World 第3弾 52頁(北目拓郎)

[インターネット]
15. JF の "Locale mini-HOWTO"  Peeter Joot 著、松田 員幸 訳

http://www.linux.or.jp/JF/JFdocs/Locales.html
16. 「Linux で始める国際化プログラミング」
    http://linux.nikkeibp.co.jp/column/i18n/
17. 「マルチメディアプログラミングの講義内容 ('97 後期 第9回) 国際化機能」  稚内北星短期大学
      (locale, timezone の解説。言語名コード、国・地域名コードへのリンクあり)
  http://www.wakhok.ac.jp/~tatsuo/kouki97/9shuu/kougi.html
18. gtk+1.2 で日本語を使うための tips
    http://seki.math.sci.hokudai.ac.jp:20080/gtk/gtk-jp-tips.html


2.  方針

conv2nmail4 は Tcl/Tk で記述しているので、上記の参考文献の中から Tcl/Tk での実装のしかたの解説、あわよくばそのまま使えるサンプルのコードが無いかなぁと思いながら探しました。しかし C 言語、GTK+ などによる解説ばかりで Tcl/Tk によるものは見当たりませんでした。しかたないので「無いものは自分で作る」というUnix の原則に従い、作ってみることにしました。

なお、Tcl/Tk 自体の国際化ですが、1999年8月にリリースされたバージョン 8.2 から始まったばかり(?)のようです。現状では Tcl7.6/Tk4.2 などのバージョンもまだ多く使用されていると思います。conv2nmail4 も Tcl7.6/Tk4.2 で記述しています。

一般的にアプリケーションを国際化するには、各言語における表示、入力、印刷の方法を検討する必要がありますが conv2nmail4 の場合は、表示の問題だけです。

gtk+ では国際化プログラミングが容易にできるという大きな特徴がありますが、概ね以下のようにして国際化対応の表示をしていることが分りました。詳しくは参考文献をご覧下さい。

(1) ロケールを設定する (gtk_set_locale 関数)
(2) フォントセットをロードする (gdk_fontset_load 関数)
(3) デフォルトフォントを変更
(4) メッセージの国際化 (GNU gettext によるメッセージカタログ)

Tcl/Tk ではこれにならって以下のようにすればよいだろうと考えました。

(1) 各言語別のメッセージカタログファイルを作成しておく。
(2) マシンがどの言語環境なのかをロケールに関する環境変数で調べる。
(3) ロケールに合わせてその言語に対応したメッセージカタログファイルを
    選択する。
(4) メッセージカタログに定義されている内容でメッセージを表示する。
(5) 処理を簡単にするために、フォントはデフォルトのものだけを使用する。
    (これは仕様または制限事項となる)

以下では i18n-sample という簡単な国際化プログラムのサンプルを基に説明します。機能としては選択したラジオボタンに応じてメッセージを表示するというものです。ロケールの設定に応じてボタン名とメッセージが英語または日本語で表示されます。(図1、図2参照)

    図1:英語ロケールの場合

    図2:日本語ロケールの場合

3.  メッセージカタログ
まずメッセージカタログですが、中身はリスト1,2のようにメッセージの変数に、言語別のテキストを設定するスクリプトです。

--------------------------------------------------------------------------
リスト1: 英語ロケール用メッセージカタログ (ファイル名: i18n-sample-msg.en)
--------------------------------------------------------------------------
# I18N sample measage (ENGLISH)

#set msg_I18N "Internationalization  "
 set msg_I18N "Internationalization  "

#set msg_L10N "Localization  "
 set msg_L10N "Localization  "

#set msg_Exit "  Exit  "
 set msg_Exit "  Exit  "
----------------------------------------------------------------------------
リスト2: 日本語ロケール用メッセージカタログ (ファイル名: i18n-sample-msg.ja)
----------------------------------------------------------------------------
# I18N sample measage (JAPANESE)

#set msg_I18N "Internationalization  "
 set msg_I18N "国際化                "

#set msg_L10N "Localization  "
 set msg_L10N "地域化        "

#set msg_Exit "  Exit  "
 set msg_Exit "  終了  "


4.  表示する言語コードの調査

次に言語の種別を、ロケールに関する環境変数の設定から調べる方法を説明します。
LANGUAGE, LC_ALL, LC_MESSAGE, LANG の各環境変数が定義されている場合、ロケールの alias 名からフルロケール名に変換します。

例えば環境変数 LC_ALL が ja_JP.ujis と定義されている場合、これはエイリアス名(別名)であり、これのフルロケール名(正式名)は ja_JP.eucJP です。このフルロケール名から言語名コードを取り出します。言語名コードというのは、フルロケール名の先頭にあって、英小文字2桁で表されます。この例では ja です。

この変換および取り出しの処理は、下記のテーブルと AWK コマンドを使用しています。
/usr/lib/X11/locale/locale.alias
/usr/local/share/locale/locale.alias
(Linux のディストリビューションにより、このディレクトリは異なる可能性あり)

   参考:言語名を表すコード(ISO 639)は、下記にあります。
         http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt
         国または地域を表すコード(ISO 3166)は、下記にあります。
         http://userpage.chemie.fu-berlin.de/diverse/doc/ISO_3166.html

ロケールに関する環境変数として、多くの参考文献には下記の最初の8つしか書いてないですが LANGUAGE という環境変数もあります。

 JF の Locale mini-HOWTO (参考文献15) より引用します。
  ● LANG
     これは locale を設定しますが、他の LC_xxxx という環境変数によって上書きされます。
  ● LC_COLLATE
     並べかえ(ソート)の順番です。
  ● LC_CTYPE
     文字定義、大文字、小文字など。これらは toupper、tolower、islower、isdigit のような関数で用いられます。
  ● LC_MONETARY
     期待される形式で貨幣単位をフォーマットするのに必要な情報を含みます。それは千の区切り文字、小数点区切り文字、通貨記号を意味するものやその位置を定義します。
  ● LC_NUMERIC
     期待される千、小数点区切り文字、数字集合です。
  ● LC_TIME
     時刻、日付を指定する方法です。これは曜日、短縮形/非短縮形での月のようなものです。
  ● LC_MESSAGES
     特に言うことはありません。
  ● LC_ALL
     locale を設定し、他の LC_xxxx 環境変数に上書きされます。

  (参考文献18 から引用)
  ● LANGUAGE
     メッセージに使用する言語の指定で LC_ALL, LC_MESSAGES, LANG より優先度が高い。

  これらの環境変数のチェックには、優先順位に注意する必要があります。
  優先順位が高い方から順に LANGUAGE > LC_ALL > LC_MESSAGES > LANG です。
  また、いずれの環境変数もセットされていない場合、C ロケール(英語)にします。
 

5.  メッセージカタログの選択と表示
前記のようにして言語名コードが決まったら、これに該当するメッセージカタログのファイルを source コマンドで読み込みます。これで各メッセージの変数には、言語に対応したテキストが設定されます。これ以降は、このメッセージ変数を使って表示するようなスクリプトを書けばよいのです。

以下に国際化プログラミングする上での注意点を挙げます。

1. ボタン名やメッセージは、プログラムの中で文字列を直接書いてはいけない。
   必ず、メッセージ変数を使用すること。

2. メッセージを表示する領域の長さは多めにとること。
   例えば日本語で「国際化」という文字は 3文字ですが英語では Internationalization と 20文字にもなります。逆に英語では短いのに、日本語では長くなるものもあります。ボタンの大きさとか配置など画面の設計をする際に、ある言語での文字数ぎりぎりのサイズで作ってしまうと、他の言語では収まりきらないという不都合が生じる可能性が大です。国際化というからには、日本語と英語だけでなくヨーロッパやアジアの多くの言語によるメッセージカタログが作成されることも考慮しなければなりません。例を挙げます。

(1) set msg_I18N "Internationalization"
(2) set msg_I18N "国際化               "
(3) set msg_I18N "国際化"
(4) set msg_I18N 国際化

(2) は OK ですが、(3)(4) は好ましくありません。

3. プログラムは、ASCII 文字だけで記述する。
   私は、長くて複雑なプログラムをデバッグする場合、自分でも追いきれないのでなるべく多くのコメントを日本語で書いて残すようにしています。このような場合、公開する際に日本語は全て英語に直し、念のため kcc -c コマ
ンドで ASCII と表示されることを確認するとよいでしょう。
 


リスト3: 国際化プログラミングのサンプル (ファイル名: i18n-sample)


#!/bin/sh
# the next line restart using wish \
exec wish "$0" "$@"

# Change some variables to fit your system.
# (1) path for "locale.alias" file
set locale_alias_path1  /usr/lib/X11/locale
set locale_alias_path2  /usr/local/share/locale

# (2) path for message catalog (i18n-sample-msg.??)
#set message_file_path  /usr/local/lib/i18n-sample
set message_file_path  ~/my-prog/i18n

#==========================================================================
#    I18N Sample Program <version 1.0>
#
#    File name           : i18n-sample
#    Programing Language : Tcl 7.6, Tk 4.2
#    Date                : Sep 23, 1999
#    Author              : R.Kawagishi <kawagisi@yk.rim.or.jp>
#    Environment         : Plamo Linux 1.4.4 , Tcl7.6jp/Tk4.2jp
#==========================================================================

#--------------------------------------------------------------------------
# convert locale alias to language code
#--------------------------------------------------------------------------
proc conv_locale {alias} {
    global  env envL locale_alias_path1 locale_alias_path2

    set language ""
    if { ( [lsearch $envL $alias] != -1 ) && ($env($alias) != "") } {
        if { $env($alias) == "C" } \
        then { set language "C" } \
        else { set language \
                   [ exec awk "\$1 ~ /^$env($alias)$/ {print \$2}" \
                     $locale_alias_path1/locale.alias | head -c 2 ]
               if { $language == "" } {
                   set language \
                   [ exec awk "\$1 ~ /^$env($alias)$/ {print \$2}" \
                     $locale_alias_path2/locale.alias | head -c 2 ]
               }
        }
    }
    return $language
}
#--------------------------------------------------------------------------
# Main
#--------------------------------------------------------------------------
    #---- check locale with environment variable
    #----     priority :  LANGUAGE >  LC_ALL > LC_MESSAGES > LANG > "C"
    set envL   [ array names env L* ]
    set locale [ conv_locale LANGUAGE ]
    if { $locale == "" } {
        set locale [ conv_locale LC_ALL ]
        if { $locale == "" } {
            set locale [ conv_locale LC_MESSAGES ]
            if { $locale == "" } {
                set locale [ conv_locale LANG ]
                if { $locale == "" } {
                    set locale "C"
                }
            }
        }
    }

    #---- include message catalog file
    if { [ file exists $message_file_path/i18n-sample-msg.$locale ] == 1 } \
    then { source $message_file_path/i18n-sample-msg.$locale } \
    else { source $message_file_path/i18n-sample-msg.en }

    #---- place widgets
    wm title    . "I18N Sample <ver 1.0>"
    wm geometry .
    .  configure -cursor hand2

    frame   .fra1
    frame   .fra2
    pack    .fra1 .fra2 -fill x -side top

    radiobutton .fra1.i18n  -text I18N -variable btn -value 1 \
                            -command { .fra2.ent delete 0 end
                                       .fra2.ent insert 0 $msg_I18N }
    radiobutton .fra1.l10n  -text L10N -variable btn -value 2 \
                            -command { .fra2.ent delete 0 end
                                       .fra2.ent insert 0 $msg_L10N }
    pack    .fra1.i18n .fra1.l10n -side left

    entry   .fra2.ent -width 22
    button  .fra2.exitbut -text $msg_Exit -command { exit }
    pack    .fra2.ent .fra2.exitbut -side left -fill x
#--------------------------------------------------------------------------