Where is Rails trying to look-up L10N / I18N Strings?

July 21, 2011 by Tilo Sloboda - updated July 18, 2012

 

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..

Find the source code for the Rails i18n 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

Better Monkey-Patching, than modifying the Gem

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"
          end
thanks to zpoley!

Here you go...

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

 

 

 

Appendix

 

Rails 3.0.7 L10N-Key Lookup

Here are some results which L10N-keys Rails 3.0.7 is trying to lookup by default..

Notes:

 

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

 

Notes: