Were you ever frustrated that it's not so easy to guess where Rails is trying to look up L10N-strings in it's I18N-backend?
I found that it is pretty poorly documented and pretty hard to guess where Rails tries to lookup things in the I18N-backend; and the naming conventions don't seem to be very consistent (somebody in the Rails core team, please clean up and document this mess..).
But what is perhaps more frustrating is that there is no built-in way to peek at the L10N-keys which are tried during the L10N lookup.
I found a simple solution which let's you listen-in on the I18N lookup()
calls.
Yes, it is a bit of a hack, but until there is a better way to do this, this might be the only solution to see what's really going on during the key-lookup..
The easiest way to find the source code of the lookup() implementation is by installing the "method_source" gem , and typing in your Rails console:
> I18n::Backend::Simple.instance_method(:lookup).source.display def lookup(locale, key, scope = [], options = {}) init_translations unless initialized? keys = I18n.normalize_keys(locale, key, scope, options[:separator]) keys.inject(translations) do |result, _key| _key = _key.to_sym return nil unless result.is_a?(Hash) && result.has_key?(_key) result = result[_key] result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol) result end end => nil
or you could dig around in RVM to find where the i18n gem is installed..
e.g. ~/.rvm/gems/ruby-1.9.2/gems/i18n-0.5.0/
- in there, find the file ./lib/i18n/backend/simple.rb
you basically need to make the following modification:
def lookup(locale, key, scope = [], options = {}) init_translations unless initialized? keys = I18n.normalize_keys(locale, key, scope, options[:separator]) puts "I18N keys: #{keys}" if ENV['I18N_DEBUG'] keys.inject(translations) do |result, _key| _key = _key.to_sym return nil unless result.is_a?(Hash) && result.has_key?(_key) result = result[_key] result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol) puts "\t\t => " + result.to_s + "\n" if ENV['I18N_DEBUG'] && result.class == String result end
But hold on, before you modify the source code of the i18n-Gem -- a better way is to extract the portion of code into a file and put it under ./config/initializers/
, so that Rails can load it at start-up and monkey-patch the I18n Gem. Using a monkey-patch, it's usable by anybody working on the project.
e.g. in your project directory, create a file ./config/initializers/i18n.rb
, with this content:
# add newer versions to this array if the method definition didn't change, otherwise do an if-cascade if ['0.5.0'].include?(I18n::VERSION) module I18n module Backend class Simple # Monkey-patch-in localization debugging.. ( see: http://www.unixgods.org/~tilo/Rails/which_l10n_strings_is_rails_trying_to_lookup.html ) # Enable with ENV['I18N_DEBUG']=1 on the command line in server startup, or ./config/environments/*.rb file. # def lookup(locale, key, scope = [], options = {}) init_translations unless initialized? keys = I18n.normalize_keys(locale, key, scope, options[:separator]) puts "I18N keys: #{keys}" if ENV['I18N_DEBUG'] keys.inject(translations) do |result, _key| _key = _key.to_sym return nil unless result.is_a?(Hash) && result.has_key?(_key) result = result[_key] result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol) puts "\t\t => " + result.to_s + "\n" if ENV['I18N_DEBUG'] && (result.class == String) result end end end end end else puts "\n--------------------------------------------------------------------------------" puts "WARNING: you're using version #{I18n::VERSION} of the i18n gem." puts " Please double check that your monkey-patch still works!" puts " see: \"#{__FILE__}\"" puts " see: http://www.unixgods.org/~tilo/Rails/which_l10n_strings_is_rails_trying_to_lookup.html" puts "--------------------------------------------------------------------------------\n" endthanks to zpoley!
now when you start your rails server or unicorns, just make sure that you set the environment variable I18N_DEBUG to something, and you will see detailed ouput on where Rails is trying to look in the I18N-backend behind the scenes to find translations.. e.g.:
I18N_DEBUG=1 unicorn or I18N_DEBUG=1 rails server [now try to load a page] ... I18N keys: [:de, :titles, :edit_user] => Benutzer verändern I18N keys: [:de, :helpers, :label, :user, :first_name] I18N keys: [:en, :helpers, :label, :user, :first_name] I18N keys: [:de, :activerecord, :attributes, :user, :first_name] => Vorname I18N keys: [:de, :helpers, :label, :user, :last_name] I18N keys: [:en, :helpers, :label, :user, :last_name] I18N keys: [:de, :activerecord, :attributes, :user, :last_name] => Nachname I18N keys: [:de, :helpers, :label, :user, :email] I18N keys: [:en, :helpers, :label, :user, :email] I18N keys: [:de, :activerecord, :attributes, :user, :email] => Email ...You can now see clearly which keys Rails tries to lookup in the I18N-backend, and in which order it tries several keys before giving up. If a match was found, you can see the looked-up result after the =>
I hope you find this useful
./lib/locales/de.yml
was in place
Rails Component | What | Code | URL | I18N-lookup Sequence | Comment |
---|---|---|---|---|---|
View | absolute key | t('something') | /users/2 | [:de, :something] | not inside a form |
View | relative key | t('.something') | /users/2 | [:de, :users, :show, :something] | not inside a form; local scope is resolved via file name |
View | relative key | t '.something' | /users/2/edit | [:de, :users, :form, :something] | inside the _form partial; outside a form; local scope is resolved via file name |
View | Form Label | f.label :something | /users/2/edit |
[:de, :helpers, :label, :user, :something] [:de, :activerecord, :attributes, :user, :something] [:de, :activerecord, :attributes, :user, :something] [:de, :attributes, :something] |
inside the _form partial ; inside a form; with or without lib/locales/de.yml looking in 3 different locations |
View | Form Submit Button | f.submit | /users/2/edit |
[:de, :activerecord, :models, :user] [:de, :activerecord, :models, :user] [:de, :helpers, :submit, :user, :update] [:de, :helpers, :submit, :update] => %{model} aktualisieren |
inside the _form partial; inside a form; with lib/locales/de.yml Model-Name and Verb are looked up separately The result comes from the de.yml file |
View | Form Submit Button | f.submit | /users/2/edit |
[:de, :activerecord, :models, :user] [:de, :activerecord, :models, :user] [:de, :helpers, :submit, :user, :update] [:de, :helpers, :submit, :update] |
inside the _form partial; inside a form; without lib/locales/de.yml Model-Name and Verb are looked up separately |
Controller | absolute key | t('something') | /users/2/edit | [:de, :something] | inside a controller; redirect notice |
Controller | relative key | t('.something') | /users/2/edit | [:de, :something] | inside a controller; redirect notice; local scope is not resolved |
Model | absolute key | t('something') | /users/2/edit | [:de, :something] | inside a model; validation notice |
Model | relative key | t('.something') | /users/2/edit | [:de, :something] | inside a model; validation notice; local scope is not resolved |
Model | Validation Result | :presence => true | /users/2/edit | de.activerecord.errors.models.user.attributes.email.blank | inside a model; automated validation notice for :presence |
[:de, :users, :show, :something]
corresponds to...
de:
users:
show:
something: some-translation