Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

i18n translations in rspec

I want to isolate my test from internalization. I use rails 3.2.8 and rspec 2.11.1

I put this code to spec/support/translations.rb

module I18nHelpers
  def with_translations(locale, translations)
    I18n.backend.store_translations locale, translations
    yield
  ensure
    I18n.reload!
  end

end

RSpec.configure do |config|
  config.include I18nHelpers
end

Then I test application helper:

describe ApplicationHelper do
  context "messages" do
    it "show body" do
       with_translations :en, navigation: {messages: 'foo'} do
           concat messages_navigation
           assert_test 'span', 'foo'
       end
    end
  end
end

But this test falls with message

Failure/Error: assert_select 'span', text: /foo/
     MiniTest::Assertion:
        </foo/> expected but was
        <"Messages">.

'Messages' is from my real config/locales/en.yml I test #store_translations from console and it works. but when i put line p I18n.t(translations.key.first) before ensure word in helper module, it shows me old translations.

Thanks for any help!

like image 908
caulfield Avatar asked Oct 18 '12 20:10

caulfield


2 Answers

To have a complete isolation from I18n you may want to switch backends like this:

def with_translations(locale, translations)
  original_backend = I18n.backend

  I18n.backend = I18n::Backend::KeyValue.new Hash.new, true
  I18n.backend.store_translations locale, translations

  yield
ensure
  I18n.backend = original_backend
end
like image 64
Anton Styagun Avatar answered Oct 05 '22 15:10

Anton Styagun


I've dug into this problem and I think I have a rough answer. The problem seems to be caused by the fact that rails does not load translations in the locale files until the first call to I18n.t is made.

This is a bit old, but still relevant:

https://groups.google.com/forum/?fromgroups=#!msg/rails-i18n/QFe0GDVRIa0/G7K09NAgqJMJ

We've considered a lot of different approaches to this and in the end our solution to this is to defer the actual translation loading to the latest possible point. So clients (like Rails) would only register translation sources to I18n and the backend would actually lazy-load them when the first translation needs to be looked up.

So basically, the existing translations are only loaded when you make a call to I18n.t. I discovered this while playing around in the console, when I noticed very strange behaviour:

irb(main):001:0> def with_translations(locale, translations)
irb(main):002:1>   I18n.backend.store_translations locale, translations
irb(main):003:1>   yield
irb(main):004:1> ensure
irb(main):005:1*   I18n.reload!
irb(main):006:1> end
=> nil
irb(main):007:0> I18n.reload!
=> false
irb(main):008:0> with_translations(:en, { :messages => "foo" }) { puts I18n.t :messages }
Messages
=> nil
irb(main):009:0> I18n.t :some_other_string
=> "Some translation"
irb(main):010:0> with_translations(:en, { :messages => "foo" }) { puts I18n.t :messages }
foo
=> nil

What is happening has nothing to do with the ensure block. We can remove it and the same thing will happen:

irb(main):011:0> I18n.reload!
=> false
irb(main):012:0> def with_translations(locale, translations)
irb(main):013:1>   I18n.backend.store_translations locale, translations
irb(main):014:1>   yield
irb(main):015:1> end    
=> nil
irb(main):016:0> with_translations(:en, { :messages => "foo" }) { puts I18n.t :messages }
Messages

Even without the reload, it still spits out the original string because the translation in the I18n.backend.store_translations call is being overwritten with the original string when I18n.t is called. If however you call I18n.t before the above code, it will work (and leave the translation since there is no ensure block reloading the translations).

So basically, the answer is to just call I18n.t on an actual translation string to trigger rails to load the existing translations before overriding the one you want to change:

def with_translations(locale, translations)
  I18n.t translations.keys.first         # to make sure translations are loaded
  I18n.backend.store_translations locale, translations
  yield
ensure
  I18n.reload!
end

I have only tried this in the console, but I believe it should work for your rspec test as well. If anyone has deeper insights into this issue I'd be eager to hear them.

like image 27
Chris Salzberg Avatar answered Oct 05 '22 15:10

Chris Salzberg