自分の興味の赴くままにIT技術系のネタを取りとめもなくメモっています。
Ruby言語やLinuxのネタが多いです。

July 30, 2006 [長年日記]

[Ruby] bindtextdomain_to(klass, domainname)という考え方

Railsやってて気づかされた点でまだ悩んでいる点があるので書いて整理しようと思う。

オレの今までのRuby-GetTextでの想定というのは、「そのクラス・モジュールが持つ静的なロケール情報をそのクラス・モジュール自身がローカライズできるような機能を提供する」というもの。

例えば、以下のような例。話を単純化するため、「ライブラリ開発者=ローカライズ対象のライブラリを開発する人」「アプリ開発者=そのライブラリを使って開発する人」の二人の登場人物で考える。

class Test
  include GetText
  bindtextdomain("test")
  def test
    _("Foo")
  end
end

上記は、ライブラリ開発者が作るライブラリ。Testクラス自身がローカライズすべき静的な文字列を持ち、それをTestクラスの開発者自身が明示的に指定し、po/moファイル等も提供する、というスタイルだ。

しかし、その枠にはまらないライブラリが出てきた。例えば、ActiveRecord。ActiveRecordはvalidation系のメッセージは上記の通りで問題ないんだけれども、各テーブルのカラムのローカライズは、アプリケーション開発者が行うはずだし、ライブラリのテキストドメインとは別にアプリ用のドメインを持ちたい、という話になる。

といっても、ActiveRecordはサブクラスをアプリケーション開発者が作るので、元々の発想ベースで行くと、ActiveRecordのサブクラス(例:ActiveRecordApp)を一つ作って、そこでbindtextdomain("app")し、他のサブクラス立ちはActiveRecordAppを継承するようにすれば良い。
んだけども・・・・

おっと時間がなくなっちゃった。あとは続く!(というか上記も書き直す可能性ありです。

[Ruby] bindtextdomain_to(klass, domainname)という考え方(2)

ということで続き。ふぅ、今日は楽しかった。いや、そりゃ書く場所が違うな。

まずは、上での話をもうちょっと補足する。上記のTestの例で言うと、Testクラスはライブラリとして提供されるクラスで、アプリケーション開発者側は以下のようにサブクラスを書いてそれを使う。

class AbstractApp < Test
  include GetText
  bindtextdomain "app"
  def foo
    _("foo")
  end
end

サブクラスが複数ある場合、上記をアプリに1つ作って残りはそのサブクラスにする。厳密に言うと、当初(0.x台)のRuby-GetTextを開発していた当時はサブクラス毎にbindtextdomainするべき、という考えだったんだけど、DRYの法則に反するYO!と言う声が聞こえたような気がするのでその案は撤回した。

class ConcreteApp < AbstractApp
  def bar
    _("bar")
  end
end

これがオレが今までに考えていた方法。

でも、じゃぁ、Testクラスを使うために毎回AbstractAppを書かなければいけないのかというと、それも広義な意味ではDRYじゃないよね。
Rubyの場合は幸い既存のクラスに機能を追加できるのでAbstractAppを書かないこともできる。それがbindtextdomain_to。

GetText.bindtextdomain_to(Test, "app")
  
class ConcreteApp < Test
  def bar
    _("bar")
  end
end

GetText.bindtextdomain_toの実装は以下のような感じ。

def bindtextdomain_to(klass, domainname, opts = {}) 
  klass.module_eval {
    include GetText
    bindtextdomain(domainname, opts)
  }
end 

実はほとんど同じ実装がすでにgettext/rails.rbにあったりする。なので、Railsの場合はinit_gettextを呼び出すだけで、使用する全てのモジュール/クラスで一つのテキストドメインを使うことができるようにしている。でも、その時はこういうことを整理して実装したわけではなかったんだよね。

うーん、一般化する必要はあるのかないのか・・・。Rails的にはあった方がよさげなんだけどなー。なやますぃ。

[Rails] init_gettextにブロックを使えるように

これも忘れないように書いておこう。

前述したbindtextdomain_toを用意したとして、今度はそれを呼び出すタイミングをどうするかという問題が残る。

RailsではWWWからアクセスされる度にロケールが変わるので以下のように書くことはできない。

class ApplicationController < ActionController::Base
  bindtextdomain_to(Foo, "blog")
  init_gettext "blog"
end

厳密に言うと、init_gettext内部でset_locale_allを呼び出してロケールをいったんリセットしてあげれば良いんだけど、それやると実行コストが無駄にかかるような気がする(未測定だけどね)ので現状はそうしていない。

そこで、init_gettextでブロックを受け付けるようにしてあげることを考えた。

class ApplicationController < ActionController::Base
  init_gettext("blog"){ |name|
    bindtextdomain_to(Foo, name)
  }
end

これなら、bindtextdomain目的以外でも「ロケールが決定したあとに実行したい初期化」というのをまとめて行うことができる・・・。
と今日のところは考えてみたんだけど、どうだろう。

にしても今日は結局考えがうまくまとまらなかったな。また来週じっくりと考えることとするか。