[TurboGears] turbogears.widgets.CalendarDatePicker(およびturbogears.widgets.DateTimePicker)を日本語で使用する方法

環境

この記事の内容は、Ubuntu Linux 6.10, TurboGears 1.0.1で確認しました。

問題点

turobgears.widgets.CalendarDatePicker(とturbogears.widgets.CalendarDateTimePicker)は、デフォルトでは月の名前や年月日の形式を英語流に表示します。日本語流に表示させるには、特別な設定が必要です。

解決方法

次のようにしてturobgears.widgets.CalendarDatePickerクラスのインスタンスを作成します。

import turbogears from validators, widgets
(略)
    format = "%Y/%m/%d"

    issue_date_widget = widgets.CalendarDatePicker(name="issue_date",
        calendar_lang="jp", button_text=u"選択", format=format, not_empty=False,
        validator=validators.DateTimeConverter(format=format, not_empty=False, 
            messages=dict(
                badFormat=u"年月日を\"/\"で区切って入力してください。")))

/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/widgets/static/calendar/lang/calendar-jp.jsを、次のように書き換えます。

// ** I18N
Calendar._DN = new Array
("日曜日",
 "月曜日",
 "火曜日",
 "水曜日",
 "木曜日",
 "金曜日",
 "土曜日",
 "日曜日");
Calendar._SDN = new Array
("日",
 "月",
 "火",
 "水",
 "木",
 "金",
 "土",
 "日");
Calendar._FD = 0;
Calendar._MN = new Array
("1月",
 "2月",
 "3月",
 "4月",
 "5月",
 "6月",
 "7月",
 "8月",
 "9月",
 "10月",
 "11月",
 "12月");
Calendar._SMN = new Array
("1月",
 "2月",
 "3月",
 "4月",
 "5月",
 "6月",
 "7月",
 "8月",
 "9月",
 "10月",
 "11月",
 "12月");

// tooltips
Calendar._TT = {};
Calendar._TT["INFO"] = "カレンダーについて";

Calendar._TT["ABOUT"] =
"DHTML Date/Time Selector\n" +
"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
"最新バージョンの配布元: http://www.dynarch.com/projects/calendar/\n" +
"GNU LGPLの下で配布されています。詳しくは、http://gnu.org/licenses/lgpl.htmlを参照して下さい。" +
"\n\n" +
"日付の選択の仕方:\n" +
"- \xab, \xbbボタンで、年を選択します。\n" +
"- " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "ボタンで、月を選択します。\n" +
"- ボタンを押しっぱなしにすると、素早く選択できます。";
Calendar._TT["ABOUT_TIME"] = "\n\n" +
"時刻の選択の仕方:\n" +
"- 数字をクリックすると、その部分が増加します。\n" +
"- シフトボタンを押しながら数字をクリックすると、その部分が減少します。\n" +
"- クリックしてドラッグすると、素早く選択できます。";

Calendar._TT["PREV_YEAR"] = "前年";
Calendar._TT["PREV_MONTH"] = "前月";
Calendar._TT["GO_TODAY"] = "今日";
Calendar._TT["NEXT_MONTH"] = "翌月";
Calendar._TT["NEXT_YEAR"] = "翌年";
Calendar._TT["SEL_DATE"] = "日付選択";
Calendar._TT["DRAG_TO_MOVE"] = "ウィンドウの移動";
Calendar._TT["PART_TODAY"] = " (今日)";

Calendar._TT["DAY_FIRST"] = "%sを最初に表示する";
Calendar._TT["WEEKEND"] = "0,6";

Calendar._TT["CLOSE"] = "閉じる";
Calendar._TT["TODAY"] = "今日";
Calendar._TT["TIME_PART"] = "(シフトキーを押しながら)クリックまたはドラッグして、値を変更してください";

// date formats
Calendar._TT["DEF_DATE_FORMAT"] = "y-mm-dd";
Calendar._TT["TT_DATE_FORMAT"] = "%m月 %d日 (%a)";

Calendar._TT["WK"] = "週";
Calendar._TT["TIME"] = "時刻:";

このファイルは、http://nekomimists.ddo.jp/~tom/repository/calendar-jp.jsからダウンロードできます。

以上です。

詳細

以下で、各ポイントについて詳細に記述します。

calendar_lang

turbogears.widgets.CalendarDatePickerクラスは、http://www.dynarch.com/projects/calendarで配布されているDHTML Calendarを使用しています。このプログラムは国際化を考えて作られており、/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/widgets/static/calendar/langに、各言語用のファイルが存在します。

$ ls
calendar-af.js
calendar-al.js
calendar-bg.js
calendar-big5-utf-8.js
calendar-big5-utf8.js
:

日本語用のファイルは、calendar-jp.jsです。CalendarDatePickerクラスにこのファイルを使うように指示するには、calendar_lang引数をコンストラクタに与えます。

    issue_date = widgets.CalendarDatePicker(name="issue_date", 
        calendar_lang="jp")

これにより、calendar-jp.jsを読み込むための<SCRIPT>タグが、ヘッダに追加されます。

format

画面に表示する年月日の形式は、コンストラクタに与えるformat引数で指定します。日本流の「年/月/日」という表示にするためには、以下のようにします。

    issue_date = widgets.CalendarDatePicker(name="issue_date", 
        format="%Y/%m/%d")

なお、残念ながら、format引数にu"%Y年%m月%d日"といったユニコード文字列を指定することはできません。これは、turbogears.validators.DateTimeConverterクラスの_from_pythonメソッド(以下)で、

    def _from_python(self, value, state):
        if not value:
            return None
        elif isinstance(value, datetime):
            # Python stdlib can only handle dates with year greater than 1900
            if value.year <= 1900:
                return strftime_before1900(value, self.format)
            else:
                return value.strftime(self.format)
        else:
            return value

次のようにdatetime.datetimeクラスのstrftimeメソッドを用いて文字列表記を得ているのですが、このstrftimeメソッドがユニコード文字列を受け付けないためです。

                return value.strftime(self.format)
not_empty

not_empty引数を、ヴァリデータだけではなく、CalendarDatePickerクラスのコンストラクタにも指定する必要があります。これは、not_emptyがTrueだった場合(つまり空欄を許可しない場合)、デフォルトで今日の日付を表示するためです。コードでいうと、/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/widgets/big_widgets.pyにある、CalendarDatePickerクラスの_defaultメソッドに当たります。

class CalendarDatePicker(FormField):
(略)
    def _get_default(self):
        if self._default is None and self.not_empty:
            return datetime.now()
        return self._default
    default = property(_get_default)
button_text

コンストラクタのbutton_text引数は、その名の通り、テキストボックスの右側に表示されるボタンに表示される文字列を設定します。

DateTimeConverter

turbogears.widgets.CalendarDatePickerクラスは、コンストラクタにvalidators引数を与えなかったら、他の引数から適当に設定されたturbogears.validators.DateTimeConverterインスタンスをヴァリデータにします。が、このデフォルトのヴァリデータではエラーメッセージが英語になっているので、別途日本語用のヴァリデータを設定する必要があります。

    format = "%Y/%m/%d"

    self.issue_date_widget = widgets.CalendarDatePicker(name="issue_date",
        format=format, not_empty=False,
        validator=validators.DateTimeConverter(format=format, not_empty=False, 
            messages=dict(
                badFormat=u"年月日を\"/\"で区切って入力してください。")))

format引数とnot_empty引数に、CalendarDatePickerクラスのコンストラクタに与えるのと同じ値を指定しなければならないことに注意して下さい。

また、DateTimeConverterクラスは、入力値をdatetime.datetimeインスタンスに変換します(たとえ時刻を表示していなくても)。このため、モデルのカラムがDateColクラスであった場合は、

record.issue_date = issue_date.date()

として、datetime.dateインスタンスを設定する必要があります。

TurboGearsがヴァリデーションに利用しているFormEncodeには、formencode.validators.DateConverterというdatetime.dateインスタンスに変換するヴァリデータもあるのですが、これは日付の形式を指定できないようで、日本語では使えません。

calendar-jp.js

先述したように、DHTML Calendarは国際化を意識して作られています。しかし、以前作ったきりメンテナンスされていないのか(2003年11月5日にリリースされたバージョン0.9.5のときからほとんど変わっていません)、/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/widgets/static/calendar/langにインストールされているcalendar-jp.jsでは動作しません。よって、これを冒頭のように書き換える必要があります。私が/usr/lib/python2.4/site-packages/TurboGears-1.0.1-py2.4.egg/turbogears/widgets/static/calendar/calendar.jsと突き合わせて調べた結果、以下の変更を行いました。

--- calendar-jp.js.orig 2007-03-09 17:02:14.835851250 +0900
+++ calendar-jp.js      2007-03-09 17:02:01.955046250 +0900
@@ -1,5 +1,14 @@
 // ** I18N
 Calendar._DN = new Array
+("日曜日",
+ "月曜日",
+ "火曜日",
+ "水曜日",
+ "木曜日",
+ "金曜日",
+ "土曜日",
+ "日曜日");
+Calendar._SDN = new Array
 ("日",
  "月",
  "火",
@@ -8,6 +17,7 @@
  "金",
  "土",
  "日");
+Calendar._FD = 0;
 Calendar._MN = new Array
 ("1月",
  "2月",
@@ -21,10 +31,41 @@
  "10月",
  "11月",
  "12月");
+Calendar._SMN = new Array
+("1月",
+ "2月",
+ "3月",
+ "4月",
+ "5月",
+ "6月",
+ "7月",
+ "8月",
+ "9月",
+ "10月",
+ "11月",
+ "12月");

 // tooltips
 Calendar._TT = {};
-Calendar._TT["TOGGLE"] = "週の最初の曜日を切り替え";
+Calendar._TT["INFO"] = "カレンダーについて";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"最新バージョンの配布元: http://www.dynarch.com/projects/calendar/\n" +
+"GNU LGPLの下で配布されています。詳しくは、http://gnu.org/licenses/lgpl.htmlを 参照して下さい。" +
+"\n\n" +
+"日付の選択の仕方:\n" +
+"- \xab, \xbbボタンで、年を選択します。\n" +
+"- " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "ボタンで、月を選択します。\n" +
+"- ボタンを押しっぱなしにすると、素早く選択できます。";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"時刻の選択の仕方:\n" +
+"- 数字をクリックすると、その部分が増加します。\n" +
+"- シフトボタンを押しながら数字をクリックすると、その部分が減少します。\n" +
+"- クリックしてドラッグすると、素早く選択できます。";
+
+//Calendar._TT["TOGGLE"] = "週の最初の曜日を切り替え";
 Calendar._TT["PREV_YEAR"] = "前年";
 Calendar._TT["PREV_MONTH"] = "前月";
 Calendar._TT["GO_TODAY"] = "今日";
@@ -33,13 +74,19 @@
 Calendar._TT["SEL_DATE"] = "日付選択";
 Calendar._TT["DRAG_TO_MOVE"] = "ウィンドウの移動";
 Calendar._TT["PART_TODAY"] = " (今日)";
-Calendar._TT["MON_FIRST"] = "月曜日を先頭に";
-Calendar._TT["SUN_FIRST"] = "日曜日を先頭に";
+//Calendar._TT["MON_FIRST"] = "月曜日を先頭に";
+//Calendar._TT["SUN_FIRST"] = "日曜日を先頭に";
+
+Calendar._TT["DAY_FIRST"] = "%sを最初に表示する";
+Calendar._TT["WEEKEND"] = "0,6";
+
 Calendar._TT["CLOSE"] = "閉じる";
 Calendar._TT["TODAY"] = "今日";
+Calendar._TT["TIME_PART"] = "(シフトキーを押しながら)クリックまたはドラッグして、値を変更してください";

 // date formats
 Calendar._TT["DEF_DATE_FORMAT"] = "y-mm-dd";
 Calendar._TT["TT_DATE_FORMAT"] = "%m月 %d日 (%a)";

 Calendar._TT["WK"] = "週";
+Calendar._TT["TIME"] = "時刻:";

まとめると、以下のようになります。

  1. Calendar._DNを、長い曜日名に変更しました。
  2. Calendar._SDNを追加しました。
  3. Calendar._FDを追加しました。
  4. Calendar._SMNを追加しました。
  5. Calendar._TT["INFO"]を追加しました。
  6. Calendar._TT["ABOUT"]を追加しました。
  7. Calendar._TT["ABOUT_TIME"]を追加しました。
  8. Calendar._TT["DAY_FIRST"]を追加しました。
  9. Calendar._TT["WEEKEND"]を追加しました。
  10. Calendar._TT["TIME_PART"]を追加しました。
  11. Calendar._TT["TIME"]を追加しました。
  12. Calendar._TT["TOGGLE"]を削除しました。
  13. Calendar._TT["MON_FIRST"]を削除しました。
  14. Calendar._TT["SUN_FIRST"]を削除しました。

今回の結果を、http://sourceforge.net/tracker/index.php?func=detail&aid=1677122&group_id=75569&atid=544285に登録しました。次のバージョンアップでこれが反映され、さらにTurboGearsのバージョンアップにも反映されることを望みます。