This post originated from an RSS feed registered with Ruby Buzz
by Patrick Lenz.
Original Post: Revisiting date localization for Ruby 1.8.6
Feed Title: poocs.net
Feed URL: http://feeds.feedburner.com/poocsnet
Feed Description: Personal weblog about free and open source software, personal development projects and random geek buzz.
Welcome to my yearly post about a revised version of a simple way to localize Ruby’s Time#strftime method (after my original post in ‘05 and the update in ‘06).
What’s changed?
In my original methodology, the arrays in the Date class containing the
actual day and month names in clear text had to be replaced by strings wrapped
in the infamous _() call of GetText in order to spit out their localized
versions when requested from the modified Time#strftime call.
When Ruby 1.8.6 came along, it had its Date array constants frozen. While I
agree it’s generally not a good idea to modify the contents of a constant
(it’s a constant after all, mind you), the implementation I came up with 2
years ago worked fine up to and including today.
Since, also, most of the sites I’m working on require multiple languages (as
opposed to, say, a single language set at application boot time), the contents
of those Date arrays truly need to be modified at runtime and having those
arrays frozen meant jumping through all sorts of hoops to address a supposedly
simple issue.
What now?
Well, I had to come up with a way to actually execute the gettext
functionality in the context of an object that has the current GetText locale
bound to it. The most obvious object with that kind of property is
ApplicationController.
But this still leaves us with the issue that we cannot modify the frozen
Date constants and that everything we put into these constants is evaluated
right away instead of at a point where we have the desired locale set.
What I came up with was a proxy object derived from the Array class that
would basically just wrap the cleartext contents that are already in each of
the Date arrays but not before they’re actually accessed/output.
# lib/date_localization.rb
class GetTextDateProxy < Array
cattr_accessor :gettext_proxy
def [](key)
_(at(key))
end
def _(string)
return string unless gettext_proxy
gettext_proxy.instance_eval { gettext(string) }
end
end
As you can see, the proxy object is pretty simple after all. It just
overwrites the default of Array#[] to look up the requested key and then
wrap the output in _().
Since I couldn’t actually be bothered to include the whole of Ruby-GetText
into my poor little proxy object, I then made room for it to hold a pointer to
an instance of ApplicationController in a class attribute called
gettext_proxy. The local underscore method of my proxy class then wraps
around the actual gettext implementation available to the gettext proxy (read:
ApplicationController), returning the string unchanged if there is no
gettext proxy and if there is, run the string through the proxy’s gettext
method.
Dealing with a state of not having a gettext proxy available is actually
necessary in cases where there are calls to these arrays before
ApplicationController gets to hook itself up to GetTextDateProxy.
But how does this actually hook into the Date class now? Let’s take a look:
# lib/date_localization.rb
class Date
silence_warnings do
%w(
MONTHNAMES DAYNAMES ABBR_MONTHNAMES ABBR_DAYNAMES
).each do |array|
class_eval %( #{array} = GetTextDateProxy.new(#{array}) )
end
end
end
While this still looks a little tedious, it’s far less tedious than repeating
the arrays’ contents simply wrapped in _() all over the place, like we had
to do in previous incarnations of this methodology.
Basically, now every array is hooked up with their very own instance of
GetTextDateProxy which then takes care of running the strings through
gettext in the appropriate moments.
Wiring it all up
For the last part, we need to get ApplicationController to hook itself into
GetTextDateProxy after having received the appropriate locale for the
request it’s dealing with.
The best fit for this is the after_init_gettext hook provided by
Ruby-GetText. Adding this code to ApplicationController will take care of
the last required step.
Here you go, working (albeit hackish) localization for Ruby’s Time#strftime working up to and including the most recent version of Ruby, which is 1.8.6 as of this writing.