If you’ve ever written an app that uses Rails’
distance_of_time_in_words
(as well as the related
time_ago_in_words
) helper method and the app was either
translated to locales other than English or wasn’t in English in the
first place, you’ve probably been bitten by its lack of proper grammar.
I18n.locale = :en
time_ago_in_words(2.days.ago) + ' ago' # => 2 days ago
I18n.locale = :de
'vor ' + time_ago_in_words(2.days.ago) # => vor 2 Tage (should be "vor 2 Tagen")
People wrote all kinds of sick
workarounds to get around this
problem but even with these workarounds it was still a pain if you
needed a few variations (such as a future_time_in_words
helper).
Even if grammar wasn’t an issue (e.g. in English), it was annoying that you always had to add the “ago” either manually or using a second translation with an interpolation argument.
# assuming a locale file containing the following:
# en:
# time_ago_in_words: '%{days} ago'
I18n.t('time_ago_in_words', :days => time_ago_in_words(2.days.ago)) # => 2 days ago
You had to do this even if you used the time_ago_in_words
helper that suggested the inclusion of “ago” even in its name although
it didn’t include it.
These issues have been fixed in Rails’ master branch for a while (see commit e22e78545112eaad857ab1e02119e20ce10065d0) by simply making the translation scope configurable via the options hash. Thanks to Steve Klabnik is has also been backported to Rails 3.2 and was released in the Rails 3.2.13. So now, even in Rails 3.2, you can do stuff like this:
# assuming a locale file containing the following:
# de:
# datetime:
# time_ago_in_words:
# x_days:
# one: vor einem Tag
# other: ! 'vor %{count} Tagen'
I18n.locale = :de
distance_of_time_in_words(2.days.ago, Time.current, false, :scope => :'datetime.time_ago_in_words') # => vor 2 Tagen
And if you need something like the aforementioned
future_time_in_words
, it’s easy to write it yourself and
just add an appropriate scope:
# assuming a locale file containing the following:
# de:
# datetime:
# future_time_in_words:
# x_days:
# one: in einem Tag
# other: ! 'in %{count} Tagen'
def future_time_in_words(to_time, include_seconds = false, options = {})
options.reverse_merge!(:scope => :'datetime.future_time_in_words')
distance_of_time_in_words(to_time, Time.current, include_seconds, options)
end
I18n.locale = :de
future_time_in_words(2.days.from_now) # => in 2 Tagen
The only remaining pain is that time_ago_in_words
hasn’t
been backported yet. You can fix this by just overwriting it:
def time_ago_in_words(from_time, include_seconds = false, options = {})
options.reverse_merge!(:scope => :'datetime.time_ago_in_words')
distance_of_time_in_words(from_time, Time.current, include_seconds, options)
end
I18n.locale = :de
time_ago_in_words(2.days.from_now) # => vor 2 Tagen
I will probably whip up a pull request for that (and maybe include the future time helper as well) to fix that. Other than that, we can finally get rid of our scary workarounds.