Detecting untranslated keys in Rails

February 8th, 2014 - Göttingen

In our last project we are using multiple techniques to avoid deploying with untranslated keys. Quite often we put off the translations to just before committing since it can get really cumbersome and break your flow.

One approach is to start those strings with i19 in order to search the project folder for that key (‘i18’ proved quickly to be a bad idea), but this way you don’t find strings that you forgot to translate to all the languages and those with a wrong scope/name.

To make sure that you’ve got a translation for every given language, the gem i18n-spec it’s pretty handy, it checks that your locale files define the same set of keys.

For example, if I wanted to check that my locale files preferences.es.yml, frontpage.es.yml and preferences.en.yml, frontpage.en.yml are consistent between them, I could add this test:

describe 'i18n' do
  ["preferences", "frontpage"].each do |file_prefix|
    describe "config/locales/#{file_prefix}.en.yml" do
      it { should be_a_complete_translation_of "config/locales/#{file_prefix}.es.yml" }
    end
    describe "config/locales/#{file_prefix}.es.yml" do
      it { should be_a_complete_translation_of "config/locales/#{file_prefix}.en.yml" }
    end
  end
end

You could tell rails to raise an exception whenever a missing translation occurs (see http://guides.rubyonrails.org/i18n.html#using-different-exception-handlers), this can be good for testing but we don’t use it in production (where we rather avoid showing a 500 for a missing key), not even in development since it could be really annoying (especially considering that the change in the locale files take sometime to ‘reload’, and sometimes you have to switch to another language in order to force it)

In development we take advantage of our dev bar (a fixed toolbar with shortcuts and useful info for development) to help the missing translations to stand out. We show a warning label, add an outline class to the html element and log the name of the missing key to the console.

in your view

#dev_tools
  #translation_missing_warning.alert-box.alert.hidden
    missing translations

in your styles

  #dev_tools
    background-color: #DDD
    border-top: 2px solid #CCC
    position: fixed
    bottom: 0
    right: 0
    text-align: right
    z-index: 9000
    padding: 2px 20px
    opacity: 0.4
    &:hover
      opacity: 1

  .translation_missing_highlight
    outline: 1px solid red
    background-color: rgba(255, 119, 119, 0.4)
    box-shadow: 4px 4px 5px

in your javascript

  $(function(){
    $(".translation_missing").each(function(i,e){
      $(e).addClass("translation_missing_highlight");
      console.error("Missing translation on", e);
      $("#translation_missing_warning").show();
    })
  });

In order to detect missing translation coming from ajax requests you can add these lines (it only works with modern browsers, which trigger Dom insertion events)

    $('body').on('DOMNodeInserted ', '.translation_missing', function(){
      $(this).addClass("translation_missing_highlight");
      console.error("Missing translation on", this);
      $("#translation_missing_warning").show();  
    });

The last thing that we have is an script in production which silently sends an error to Rollbar if it detects a missing key:

// (...) Rollbar app specific params

$(function(){
  if ($("body").data("env") == "production") {
    (function(w,d){w.onerror=function(e,u,l){_rollbar.push({_t:'uncaught',e:e,u:u,l:l});};var i=function(){var s=d.createElement("script");var
    f=d.getElementsByTagName("script")[0];s.src="//d37gvrvc0wt4s1.cloudfront.net/js/1/rollbar.min.js ";s.async=!0;
    f.parentNode.insertBefore(s,f);};if(w.addEventListener){w.addEventListener("load",i,!1);}else{w.attachEvent("onload",i);}})(window,document);
  }

  var report_missing_translation = function(e){
    _rollbar.push($(e).attr('title') + " - (" + $("body").attr('class') + ")");
  }

  $(".translation_missing").each(function(i,e){ report_missing_translation(this) })

  $('body').on('DOMNodeInserted ', '.translation_missing', function(){ report_missing_translation(this) });

})