Home > Zend Framework > Zend Framework(Zend_Validate)におけるZend_Translateを用いたエラーメッセージの日本語化

Zend Framework(Zend_Validate)におけるZend_Translateを用いたエラーメッセージの日本語化

Zend Frameworkではデータ検証用クラスとしてZend_Validateが用意されています。日ごろ使いそうな検証はほぼ揃っているのでありがたく使わせてもらっているのですが、残念ながらこのZend_Validateには日本語のエラーメッセージが実装されていません。なのでエラーメッセージの日本語化を自分で行う必要があります。 Zend Frameworkではメッセージの多言語化を行うためにZend_Translateというクラスが用意されているので、今回はこのZend_Translateを用いてZend_Validateの日本語化を行ってみます。

まずZend_Translateを用いてメッセージの他言語化を行う場合、メッセージの翻訳データを用意する必要があります。そしてZend_Translateではその翻訳データの種類に合わせて多くのアダプターが用意されています。私は今までプログラムの翻訳作業等やったことがないので、とりあえず一番使われていそうなGettextアダプタを使用してみることにします。gettextについての解説は下記サイトが大変参考になりました。

GNU gettextユーティリティ

gettextで翻訳作業を行うために、まずPOファイルと言うものが必要になります。gettext関数を用いて書かれたプログラムであればxgettextなどを用いて自動で翻訳のもとになるメッセージを抽出できるようなのですが、今回はクラスで定義されたプロパティからメッセージを抽出する必要があるので、自作でZend_Validateからメッセージを抽出するプログラムを作ってみました。POファイルはテキストファイルなので手作業で作る事も出来るのですが、今後Zend_Translateのクラスが増えたり、メッセージが増えたりする事があるかもしれませんのでこのようにプログラムを組んでおくと楽なんじゃなかろうかと思います。

xgettext.pl

#!/usr/bin/perl
use File::Basename;

my $start;
my $basename;
my %texts;

while (<>) {
    chomp;
    if (/\$_messageTemplates = array\($/) {
        $start = 1;
        next;
    } elsif ($start) {
        if (/\);$/) {
            $start = 0;
            next;
        }
        s/^.*=> ('|")([^\1]*)\1,?$/$2/;
        $basename = basename(${ARGV});
        push @{$texts{$_}}, $basename . ':' . $.;
    }
    if (eof) {
        close(ARGV);
    }
}

print "msgid \"\"\n";
print "msgstr \"\"\n";
print "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
print "\"Content-Transfer-Encoding: 8bit\\n\"\n\n";

foreach $key (sort keys %texts) {
    foreach my $file_name (@{$texts{$key}}) {
        print "#: ${file_name}\n";
    }
    print "msgid \"${key}\"\n";
    print "msgstr \"\"\n\n";
}

PHPのフレームワークの解説なのにperlで実装してしまいました。私の中でコマンドラインのプログラムを書く際はシェルスクリプトかperlで書くという掟があるのです。許して下さい…。

./xgettext.pl lib/Zend/Validate/* > validate.po

とコマンドラインでやってもらえるとPOファイルが作成できます。
ちなみに現在のZend Frameworkの最新版(1.7.1)でのPOファイルはこんな感じです。

msgid ""
msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: EmailAddress.php:58
msgid "'%hostname%' does not appear to have a valid MX record for the email address '%value%'"
msgstr ""

#: EmailAddress.php:57
msgid "'%hostname%' is not a valid hostname for email address '%value%'"
msgstr ""

#: EmailAddress.php:61
msgid "'%localPart%' is not a valid local part for email address '%value%'"
msgstr ""

#: EmailAddress.php:59
msgid "'%localPart%' not matched against dot-atom format"
msgstr ""

#: EmailAddress.php:60
msgid "'%localPart%' not matched against quoted-string format"
msgstr ""

#: Hostname.php:74
msgid "'%value%' appears to be a DNS hostname but cannot extract TLD part"
msgstr ""

#: Hostname.php:71
msgid "'%value%' appears to be a DNS hostname but cannot match TLD against known list"
msgstr ""

#: Hostname.php:73
msgid "'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'"
msgstr ""

#: Hostname.php:72
msgid "'%value%' appears to be a DNS hostname but contains a dash (-) in an invalid position"
msgstr ""

#: Hostname.php:77
msgid "'%value%' appears to be a local network name but local network names are not allowed"
msgstr ""

#: Hostname.php:70
msgid "'%value%' appears to be an IP address, but IP addresses are not allowed"
msgstr ""

#: Digits.php:61
msgid "'%value%' contains not only digit characters"
msgstr ""

#: Float.php:45
msgid "'%value%' does not appear to be a float"
msgstr ""

#: Ip.php:41
msgid "'%value%' does not appear to be a valid IP address"
msgstr ""

#: Date.php:60
msgid "'%value%' does not appear to be a valid date"
msgstr ""

#: Hostname.php:76
msgid "'%value%' does not appear to be a valid local network name"
msgstr ""

#: Int.php:41
msgid "'%value%' does not appear to be an integer"
msgstr ""

#: Date.php:61
msgid "'%value%' does not fit given date format"
msgstr ""

#: Regex.php:45
msgid "'%value%' does not match against pattern '%pattern%'"
msgstr ""

#: Hostname.php:75
msgid "'%value%' does not match the expected structure for a DNS hostname"
msgstr ""

#: Alnum.php:68
msgid "'%value%' has not only alphabetic and digit characters"
msgstr ""

#: Alpha.php:68
msgid "'%value%' has not only alphabetic characters"
msgstr ""

#: Hex.php:49
msgid "'%value%' has not only hexadecimal digit characters"
msgstr ""

#: Alnum.php:69
#: Alpha.php:69
#: Digits.php:62
msgid "'%value%' is an empty string"
msgstr ""

#: StringLength.php:47
msgid "'%value%' is greater than %max% characters long"
msgstr ""

#: StringLength.php:46
msgid "'%value%' is less than %min% characters long"
msgstr ""

#: EmailAddress.php:56
msgid "'%value%' is not a valid email address in the basic format local-part@hostname"
msgstr ""

#: Between.php:54
msgid "'%value%' is not between '%min%' and '%max%', inclusively"
msgstr ""

#: GreaterThan.php:45
msgid "'%value%' is not greater than '%min%'"
msgstr ""

#: LessThan.php:45
msgid "'%value%' is not less than '%max%'"
msgstr ""

#: Date.php:59
msgid "'%value%' is not of the format YYYY-MM-DD"
msgstr ""

#: Between.php:55
msgid "'%value%' is not strictly between '%min%' and '%max%'"
msgstr ""

#: Ccnum.php:61
msgid "'%value%' must contain between 13 and 19 digits"
msgstr ""

#: InArray.php:45
msgid "'%value%' was not found in the haystack"
msgstr ""

#: Ccnum.php:62
msgid "Luhn algorithm (mod-10 checksum) failed on '%value%'"
msgstr ""

#: Identical.php:47
msgid "No token was provided to match against"
msgstr ""

#: Identical.php:46
msgid "Tokens do not match"
msgstr ""

#: NotEmpty.php:45
msgid "Value is empty, but a non-empty value is required"
msgstr ""

この作成したPOファイルのmsgidに対する日本語訳を下記のようにmsgstrに記述していきます。

#: StringLength.php:47
msgid "'%value%' is greater than %max% characters long"
msgstr "%max%文字以下で入力して下さい"

全ての日本語訳が完成したらmsgfmt等を用いてMOファイルを作成します。

msgfmt -o validate.mo validate.po

msgfmtに関しても詳しい事は上記『GNU gettextユーティリティ』にて解説してありますので参考にしてみて下さい。

以上で翻訳データの準備は完了です。後はこの翻訳データを用いてZend Frameworkでのエラーメッセージの日本語化を行います。

まず翻訳データを格納するディレクトリの構造を決める必要があります。
Zend Frameworkでは次の5つの構造を推奨しています。

  • 単一構造のソース
  • 言語ごとに分けた構造
  • アプリケーションごとに分けた構造
  • Gettext 形式の構造
  • ファイル構造のソース

今回アダプターとしてGettextを選びましたので『Gettext 形式の構造』を選ぼうかと思ったのですが”LC_MESSAGES”の存在意義が見いだせなかったので『 言語ごとに分けた構造』にしてみました。

/languages
  /ja
    validate.mo

あとはindex.phpなどでZend_translateオブジェクトを作りZend_Validate_AbstractにDefautlTranslatorとして登録するだけです。

$translate = new Zend_Translate('gettext', '/var/www/html/languages', null, array('scan' => Zend_Translate::LOCALE_DIRECTORY));
Zend_Validate_Abstract::setDefaultTranslator($translate);

注意事項としてZend_Translateの第二引数として渡しているパスは翻訳データを格納しているディレクトリまでの絶対パスで指定して下さい。

以上でZend_Validateのエラーメッセージの日本語化は終了です。
記述した内容でおかしなところ等あればコメント残して頂けると喜びます!!

コメント:6

hiramatu 08-12-11 (木) 9:56

私のサイトへの初コメントありがとうございました!

http://framework.zend.com/manual/ja/zend.form.i18n.html

を見ると、「1.6.0 以降では、実際のエラーメッセージをメッセージ ID とする翻訳文字列を提供することができます。 1.6.0 以降ではこの方法が推奨となります。 メッセージキーによる翻訳は将来のバージョンで廃止予定です。 」とあります。
将来がどのバージョンかわかりませんが、msgidの部分を変更する必要がありそうですね。

ezzy 08-12-12 (金) 8:23

こちらこそコメントありがとうございます!

ご指摘の箇所なのですが
「1.6.0 以降では、実際のエラーメッセージをメッセージ ID とする翻訳文字列を提供することができます。 1.6.0 以降ではこの方法が推奨となります。 」
とのことなので、今回の私のエントリーでは実際のエラーメッセージをそのままmagidに使っているため推奨の方法に従っているのでは、と思っています。
「メッセージキーによる翻訳は将来のバージョンで廃止予定です。」
のメッセージキーという言葉がマニュアルに初めて出てきているにも関わらずどうにでもとれる曖昧な言葉ですよね。結論でこんな曖昧な言葉を使われているので私も自分の考えが絶対に正しいとは言い切れないです。ちなみにオリジナルの英語版も読んでみましたがやはり曖昧な感じでした。

マニュアルを書くとは難しい事ですねぇ。

また何か気付いた事があればコメントください。
喜びました!

hiramatu 08-12-12 (金) 10:16

言われてみるとそんな気がしてきました^^;
でも英語メッセージの綴りを変えると日本語翻訳ファイルにも影響がでますよね。

英語マニュアルの表現は、

To do so, use the various error code constants from the Zend_Validate validation classes as the message IDs.

という感じで「メッセージキー」なんて言葉は出てこない。
日本語マニュアルは直訳ではなくて内部に詳しい人が訳してるんですかね。

実際のヴァリデーションクラスを見ると以下のように定義されていて、「error code constants」が「const NOT_ALNUM」に相当すると思っていたんですが、message keyとコメントにあるし。IDという表現はソースの中にはないし。

/**
* Validation failure message key for when the value contains non-alphabetic or non-digit characters
*/
const NOT_ALNUM = ‘notAlnum’;

Zend_Validate_Abstractの_createMessage()の中の、どっちかの
translateを無くすよってことなんですよね。

if (null !== ($translator = $this->getTranslator())) {
if ($translator->isTranslated($message)) {
$message = $translator->translate($message);
} elseif ($translator->isTranslated($messageKey)) {
$message = $translator->translate($messageKey);
}
}

文字だけで全てを伝えるのって難しいですね。ほんとに。

ezzy 08-12-12 (金) 11:49

>でも英語メッセージの綴りを変えると日本語翻訳ファイルにも影響がでますよね。

これプログラマーとして良くわかります!!

しかし今回gettextについて色々調べて分かったのですが、アプリケーションの多言語化にあたり翻訳する人はプログラマーでない可能性があるのです。なので翻訳ファイルの中に翻訳すべきオリジナルメッセージを含めるべきで、プログラムソースを覗いてメッセージを確認したり、動きを確認しないと翻訳できない状態はあまり好ましくないみたいです。

それから実際オリジナルメッセージの変更があった場合伝えるべき内容が変更になったはずで、それに伴い翻訳されたメッセージも変更しないと間違った内容を伝える事になってしまいます。それならユーザーがそれを読めるかどうかは分からないですが、正確に内容を伝えている変更されたオリジナルメッセージを表示させるのは妥当な仕様な気もします。

仕様の確認のためにZend_Validate_Abstractのソースまで読まれたんですね!感服です。

このソースの中で$messageKeyという変数がありますのでマニュアルにあったメッセージキーとはこれのことですかね。そうなるとやはり廃止になるのはバリデーションクラスのエラーコード定数ということでしょうか?どちらにしてもわざわざ廃止にしなくても良いような気もしますが。

hiramatu 08-12-12 (金) 21:38

おっしゃるとおりですね。多言語化で日本語対応をうたってるけど日本語になってないソフトいっぱいありますけど^^;

手元に転がっていた1.5.3のZend_Validate_Abstractでは、

if (null !== ($translator = $this->getTranslator())) {
if ($translator->isTranslated($messageKey)) {
$message = $translator->translate($messageKey);
}
}

しかありませんでした。やっぱり昔はメッセージキーの翻訳のみだったけど、1.6からは実際のメッセージでの翻訳を追加して、今後はそっちでということですね。ezzyさんが正解です。

ezzy 08-12-13 (土) 6:23

1.6からしか実際のメッセージでの翻訳ができないのであれば、廃止になるのはバリデーションクラスのエラーコード定数の方で間違いなさそうですね。スッキリしました!

多くの時間をかけて検証されたみたいでhiramatuさんには大変感謝しております。今後も問題ありそうなところは遠慮なくご指摘くださいね。

ありがとうございました!

Comment Form
情報を記憶させる

Trackback+Pingback:2

トラックバック URL
http://www.newbreed-web.net/blog/wp-trackback.php?p=177
Listed below are links to weblogs that reference
Zend Framework(Zend_Validate)におけるZend_Translateを用いたエラーメッセージの日本語化 from ezzyの屈辱
Pingback from 道しるべ - Zend_Validateの日本語化 08-12-10 (水) 16:07

[...] ezzyの屈辱 - Zend Framework(Zend_Validate)におけるZend_Translateを用いたエラーメッ

Trackback from twk @ ふらっと 09-04-09 (木) 21:18

Zend Framework勉強会でZend Formについて発表した資料…

ZendFramework勉強会に参加してきました。
今回はZend_Formについて発表してきました。今回は資料は途中で終わり、サンプルプログラム多数の構成になっています。

サンプルプログラムを含…

Home > Zend Framework > Zend Framework(Zend_Validate)におけるZend_Translateを用いたエラーメッセージの日本語化

Search
ezzy's latest bookmarks (delicious)
Feeds

Page Top