Thursday, July 23, 2009

Transparent Dates and Times Internationalization in Rails

The basic internationalization approach in Ruby on Rails supposes that you translate the dates and times like that

Created at: <%= I18n.localize @model.created_at, :format => :short %>

But most of us use the "to_s" method to format times, like that

Created at: <%= @model.created_at.to_s :short %>

Mostly because you usually don't think much about i18n when a project is just starting up, and then it's kinda sweet, nice and short way of doing that.

So, usually when it comes to the internationalization, the project is already pretty much covered with the to_s methods. If you were lucky you might had created a single helper method which processes all the dates and times in your project and then it's simple, but if you didn't you might think about highjacking the "to_s" method of the Time class. Like that.

class Time
def to_s(format=:default)
I18n.localize self, :format => format

But if you do so, you will probably broke the things. Because first of all the "to_s" method supposed to work like an alias for the "strftime" method, secondly this method is used to convert times and dates in the :db format and if it was not translated properly it will break your models, thirdly it's naughty when you completely replace such a method.

Okay here is a better way of doing that

[Date, Time, ActiveSupport::TimeWithZone].each do |klass|
klass.instance_eval do
define_method :to_s_with_i18n do |*args|
format = args.first || :default
if format.is_a?(Symbol) && format != :db
I18n.localize(self, :format => format)
alias_method_chain :to_s, :i18n

It keeps the original method and doesn't touch the custom and database formats. The ActiveSupport::TimeWithZone class is the Rails class for the UTC times you need to process it too.


PhoeniX said...

why not just use `sed -i 's/([^.]+ated_at\.to_s\ :short)/here_is_a_helper/g' or something like that for all *.erb?

Nikolay V. Nemshilov aka St. said...

Because it won't work on windows

Georg said...

Hm, it seems that your code conflicts with YAML, because Date#to_yaml uses to_s (without param), so reloading does not work. Try this with your code:

=> ArgumentError

There is a Rails ticket with a patch, but it's not accepted so far: