バリデーションメッセージをDryにしつつ国際化
CakePHP1.2.3を利用しています。
CakePHPのバリデーションエラーメッセージは、各モデルに書いたりしますが、ここではgettextの__()を使った国際化の記述ができません。CakeBookにそのための回避策が一応書いてありました。
http://book.cakephp.org/ja/view/163/Localization-in-CakePHP
下記の記述をapp_model.phpに入れとけば、エラーメッセージ出力時に__()を付けてくれるので、言語ごとにエラーメッセージが切り替わります。
function invalidate($field, $value = true) { return parent::invalidate($field, __($value, true)); }
上記が一番楽なパターンではあるのですが、国際化対応する箇所をコマンド一発で抽出してくれる"cake i18n"コマンドだと、バリデーションエラーメッセージは抽出されません(エラーメッセージ自体は__()で記述されていないので)
ちなみにcake i18nの使い方は下記が参考になります。
http://cakephp.seesaa.net/article/87269708.html
cake i18nコマンドでもエラーメッセージが抽出できる方法はないかとIRCでK1LoWさんと話してて、それを実現する簡単なapp_modelを作ったので公開します。
ダウンロードはこちらから
http://cake.eizoku.com/source/validation_i18n.zip
機能は下記の通りです(最初の2つはvalidationのメッセージ出力をDRYにしてみる by Writing Some Codeなどで実現されています)
- 各モデルのバリデーションエラー定義を一箇所で管理可能
- メッセージ中の数値などは、printf系の変換指定が可能(%dなど)
- エラーメッセージは定義箇所に__()を使って、国際化の定義が可能(cake i18nで取得対象となる)
- オプション:app_modelにまとめたエラーメッセージ定義を各モデルで上書き可能
- オプション:エラーメッセージにフィールド名を自動付与
最後のエラーメッセージにフィールド名は、画面の上部にエラーメッセージをまとめて出力したい場合に、フィールド名がないと、どの項目のエラーか分からないので、そのための機能です。フィールド名も国際化して出力します。
ソースコード
基本の流れは、_getDefaultErrorMessagesI18n()メソッド内で連想配列にエラーメッセージをgettext形式で定義し、それをbeforeValidate()時にモデルのバリデーションエラーメッセージにセットしているだけです。
追記
この記事で説明するコードは色々と機能を付け足した後のやつで、もっとシンプルな初期段階のソースコードはここにあります。こっちの方が理解しやすいかもしれません。
<?php class AppModel extends Model { /** * Concatenate a field name with each validation error message in replaceValidationErrorMessagesI18n(). * Field name is set with gettext __() * true: Concatenate * false: not Concatenate * * @var boolean * @access protected */ var $_withFieldName = false; /** * Error messages * * @var array * @access protected */ var $_error_messages = array(); /** * Define default validation error messages * $default_error_messages can include gettext __() value. * * @return array * @access protected */ function _getDefaultErrorMessagesI18n(){ //Write Default Error Message $default_error_messages = array( 'require' => 'Please be sure to input.', 'email_invalid' => __('Invalid Email address.',true), 'between' => __('Between %2$d and %3$d characters.',true), ); return $default_error_messages; } /** * Set validation error messages. * * To change default validation error messages, * set $add_error_message in each model. * * @param array $add_error_message * @param boolean $all_change_flag * true: change all default validation error messages * false: merge $add_error_message with default validation error messages * @access public */ function setErrorMessageI18n( $add_error_message = null, $all_change_flag=false ) { $default_error_messages = $this->_getDefaultErrorMessagesI18n(); if( !empty( $add_error_message ) && is_array( $add_error_message ) ){ if( $all_change_flag ){ $default_error_messages = $add_error_message; }else{ $default_error_messages = array_merge( $default_error_messages, $add_error_message ); } $this->_error_messages = $default_error_messages; }elseif( empty($this->_error_messages) ){ $this->_error_messages = $default_error_messages; } } /** * get validation error messages * * @return array * @access protected */ function _getErrorMessageI18n(){ return $this->_error_messages; } /** * Replace validation error messages for i18n * * @access public */ function replaceValidationErrorMessagesI18n(){ $this->setErrorMessageI18n(); foreach( $this->validate as $fieldname => $ruleSet ){ foreach( $ruleSet as $rule => $rule_info ){ $rule_option = array(); if(!empty($this->validate[$fieldname][$rule]['rule'])) { $rule_option = $this->validate[$fieldname][$rule]['rule']; } $error_message_list = $this->_getErrorMessageI18n(); $error_message = ( array_key_exists($rule, $error_message_list ) ? $error_message_list[$rule] : null ) ; if( !empty( $error_message ) ) { $this->validate[$fieldname][$rule]['message'] = vsprintf($error_message, $rule_option); }elseif( !empty($this->validate[$fieldname][$rule]['message']) ){ $this->validate[$fieldname][$rule]['message'] = __( $this->validate[$fieldname][$rule]['message'], true); } if( $this->_withFieldName && !empty($this->validate[$fieldname][$rule]['message']) ){ $this->validate[$fieldname][$rule]['message'] = __( $fieldname ,true) . ' : ' . $this->validate[$fieldname][$rule]['message']; } } } } function beforeValidate(){ $this->replaceValidationErrorMessagesI18n(); return true; } }
使い方 (基本編)
各モデルのバリデーション定義では、下記の規則に従って
var $validate = array( '項目名' => array( '規則名' => array( 'rule' => array( 'バリデーション関数' ) ) ) );
今までと同じように定義します(下記サンプル)
var $validate = array( 'email' => array( "email_invalid" => array( 'rule' => VALID_EMAIL, 'required' => true, ), ), )
app_modelの下記の箇所にシステムで共通して使うエラーメッセージ(今後はデフォルトエラーメッセージと呼ぶことにします)を連想配列で記述します。配列のキーは各モデルで定義するバリデーションの規則名になります。
<?php function _getDefaultErrorMessagesI18n(){ //Write Default Error Message $default_error_messages = array( 'require' => 'Please be sure to input.', 'email_invalid' => __('Invalid Email address.',true), 'between' => __('Between %2$d and %3$d characters.',true), ); return $default_error_messages; }
今回の例だと、email_invalidのルールでエラーになった場合は、「Invalid Email address.」が国際化されて表示されます。betweenのように何文字以上という数値が変わるものも、上記例のようにすれば対応できます。
使い方 (各モデルごとにエラーメッセージを変えたい)
デフォルトエラーメッセージは、上記のようにすれば一元管理可能ですが、同じバリデーションルールでも、あるモデルではエラーメッセージを変えたい場合は、モデル側のファイルを下記のようにして上書き(マージ)が可能です
<?php class User extends AppModel { function beforeValidate(){ $error_messages = array( 'email_invalid' => __('Oh, Invalid Email address!!!',true), ); $this->setErrorMessageI18n($error_messages, false); parent::beforeValidate(); return true; } }
$this->setErrorMessageI18n()をapp_modelのbeforeValidate前に( parent::beforeValidate()前に )実行すれば、上書きできます。この場合だと、Userモデルの場合のみ、メールのエラーメッセージがapp_modelで定義したものから変わります。
この方法は、デフォルトエラーメッセージを残しつつ上書きしたいものだけマージする方法ですが、デフォルトエラーメッセージを全て使いたくない場合は、マージではなくて入れ替えが可能です。$this->setErrorMessageI18n()の第2引数にtrueを入れればそれが実現可能です。
使い方 (エラーメッセージにフィールド名を自動付与)
エラーメッセージを画面上部に一括で出したい場合などのために、エラーメッセージ毎にフィールド名の自動付与が可能です。app_modelの下記のプロパティをtrueにしてください。
var $_withFieldName = true;
下記のような画面になります。viewファイル側で__('email')とかフィールド名を定義して、poファイルを作っておけば、このエラーメッセージのフィールド名も日本語などになります(email:という箇所がメール:などに変わります)。
参考
今回の実装にあたり、下記を参考にさせていただきました。
「validationのメッセージ出力をDRYにしてみる by Writing Some Code」
「CakePHPによる実践Webアプリケーション開発」の本(85ページあたり)