Ruby on Rails i18n revisited
October 3rd, 2005Having a requirement for both internationalization and per-instance customizability of translations in our soon to be revealed application, I wanted to go with an as-simple-as-possible pure Ruby solution for translating strings.
While gettext based approaches might have some advantages over this (some tools available, possibly faster speed with C-based variants) I didn’t want to go with a sledgehammerized solution (I’ve very few strings in the app, and I really only need to support a handful of languages), so I’ve come up with this:
# .rb files to define l10ns in (lang/ and lang/custom/)
Localization.define('de_DE') do |l|
l.store "blah", "blub"
l.store "testing %d", ["Singular: %d", "Plural: %d"]
end
# Call from anywhere (extension to Object class):
_('blah')
_('testing %d', 5)
# in .rhtml
<%=_ 'testing %d', 1 %>
# current language is a class var in class
# Localization, so set e.g. in application.rb
Localization.lang = 'de_DE'
# in environment.rb (rails 0.13.1)
Localization.load
All you need to include for this is the following localization.rb
file, which you stick in your lib
directory:
module Localization
mattr_accessor :lang
@@l10s = { :default => {} }
@@lang = :default
def self._(string_to_localize, *args)
translated =
@@l10s[@@lang][string_to_localize] || string_to_localize
return translated.call(*args).to_s if translated.is_a? Proc
translated =
translated[args[0]>1 ? 1 : 0] if translated.is_a?(Array)
sprintf translated, *args
end
def self.define(lang = :default)
@@l10s[lang] ||= {}
yield @@l10s[lang]
end
def self.load
Dir.glob("#{RAILS_ROOT}/lang/*.rb"){ |t| require t }
Dir.glob("#{RAILS_ROOT}/lang/custom/*.rb"){ |t| require t }
end
end
class Object
def _(*args); Localization._(*args); end
end
It should be easy to alter or extend that for your own purposes, or just use it as-is.
Update: Changed to module; and here’s a very quick hack to extract a nice pre-generated guesstimation of a l10n file:
# Generates a best-estimate l10n file from all views by
# collecting calls to _() -- note: use the generated file only
# as a start (this method is only guesstimating)
def self.generate_l10n_file
"Localization.define('en_US') do |l|n" <<
Dir.glob("#{RAILS_ROOT}/app/views/**/*.rhtml").collect do |f|
["# #{f}"] << File.read(f).scan(/<%.*[^w]_s*["'](.*?)["']/)
end.uniq.flatten.collect do |g|
g.starts_with?('#') ? "n #{g}" : " l.store '#{g}', '#{g}'"
end.uniq.join("n") << "nend"
end
To use that, call up script/console
and do a puts Localization.generate_l10n_file
.
Update 2: I’ve added support for using nice lambda
blocks of code for those “speciality” translations. The block gets passed the *args
given to the _
method, so you can basically do anything:
Localization.define do |l|
l.store '(time)', lambda { |t| t.strftime('%I:%M%p') }
end
Localization.define('de_DE') do |l|
l.store '(time)', lambda { |t| t.strftime('%H:%M') }
end
_ '(time)', Time.now =>"10:13PM"
Localization.lang = 'de_DE'
_ '(time)', Time.now => "22:13"
Tweet