Forum: Rails I18n "private method `gsub'" error caused by addition of a line

Posted by Max Williams (max-williams)
on 2010-04-20 12:12
hi all.  I just added this line to my config/locals/en.yml file and now
rails is blowing up when i try to start mongrel/console.

old version:

  title:
    main: Who Wants To Be A Millionaire? For Schools

  flashes:
    quiz:
      saved: Your quiz has been saved

new version:

  title:
    main: Who Wants To Be A Millionaire? For Schools

  taggings:
    create_failed: "Could not add this tag because {{message}}"

  flashes:
    quiz:
      saved: Your quiz has been saved

error:
/usr/lib/ruby/1.8/uri/common.rb:289:in `escape': private method `gsub'
called for {:create_failed=>"Could not add this tag because"}:Hash
(NoMethodError)

I tried it without the quotes originally and it blew up.  It still blows
up if i remove the quotes and the the variable part, ie this still
causes the error:

  taggings:
    create_failed: Could not add this tag because

I don't understand what's gone wrong here.  If i delete the line then
it's fine, but what's wrong with this line?  As far as i can tell
there's nothing in there that would break the formatting syntax.

any ideas welcomed...thanks, max
Posted by Andrés Gutiérrez (andresgutgon)
on 2010-04-20 15:04
(Received via mailing list)
Can you show your view, please

2010/4/20 Max Williams <lists@ruby-forum.com>
Posted by Max Williams (max-williams)
on 2010-04-20 15:22
Andrés Gutiérrez wrote:
> Can you show your view, please
> 
> 2010/4/20 Max Williams <lists@ruby-forum.com>

Hi andres - it's not my view that's at fault (and it's actually called 
from a controller action as it happens) - i can't even start the app. 
Rails is blowing up when it tries to parse the en.yml file.

What's weird is if i nest it inside another block, eg change it to this

  title:
    main: Who Wants To Be A Millionaire? For Schools

  flashes:
    taggings:
      save_failed: Could not add this tag because {{message}}
    quiz:
      saved: Your quiz has been saved

then it's fine.  But if i change it back to this:

  title:
    main: Who Wants To Be A Millionaire? For Schools

  taggings:
    save_failed: Could not add this tag because {{message}}

  flashes:
    quiz:
      saved: Your quiz has been saved

then i get the error:
/usr/lib/ruby/1.8/uri/common.rb:289:in `escape': private method `gsub' 
called for {:save_failed=>"Could not add this tag because 
{{message}}"}:Hash (NoMethodError)
Posted by Andrés Gutiérrez (andresgutgon)
on 2010-04-20 15:28
(Received via mailing list)
well, can you show me your controller? :)

2010/4/20 Max Williams <lists@ruby-forum.com>
Posted by Max Williams (max-williams)
on 2010-04-20 15:40
Andrés Gutiérrez wrote:
> well, can you show me your controller? :)
> 
> 2010/4/20 Max Williams <lists@ruby-forum.com>

Well, yes, but i promise you that it is irrelevant to the problem since 
the controller code is not executed before the problem occurs!

  def create_tagging
    @taggable = 
params[:tagging][:taggable_type].constantize.find(params[:tagging][:taggable_id])
    @tagging = @taggable.tag_with(params[:tagging][:tag_name], :user_id 
=> params[:tagging][:user_id])
    unless @tagging && @tagging.id
      add_error t('taggings.save_failed', :message => 
@tagging.errors.full_messages.join(", "))
    end
  end
Posted by Andrés Gutiérrez (andresgutgon)
on 2010-04-20 16:06
(Received via mailing list)
Sorry, but what is "add_error "? is a rails method? or what?

2010/4/20 Max Williams <lists@ruby-forum.com>
Posted by Max Williams (max-williams)
on 2010-04-20 16:17
Andrés Gutiérrez wrote:
> Sorry, but what is "add_error "? is a rails method? or what?
> 
> 2010/4/20 Max Williams <lists@ruby-forum.com>

it's effectively a way of putting something into flash[:error], it's 
from a plugin called "flash-message-conductor".  But, at the risk of 
sounding like a broken record, the actually call to I18n.t isn't the 
issue, since the app breaks as soon as i try to start it, NOT when the 
translate call is made.
Posted by Rodrigo Rosenfeld Rosas (Guest)
on 2010-04-20 19:55
(Received via mailing list)
Can you reproduce your error with a fresh new Rails application with the
same en.yml?

If so, could you send the entire en.yml and tell us your Rails version?

I guess you mean "config/locales/en.yml" instead of
"config/locals/en.yml", right?

Really strange issue...

Rodrigo.

Em 20-04-2010 11:17, Max Williams escreveu:
Posted by Henrik --- (malesca)
on 2010-04-20 23:04
(Received via mailing list)
Could you reduce it to an absolutely minimal failing yml file?

Perhaps the issue is something like a non-breaking space instead of a
regular space. No idea what errors that would give, if any.
Posted by Max Williams (max-williams)
on 2010-04-21 10:40
Rodrigo Rosenfeld Rosas wrote:
> Can you reproduce your error with a fresh new Rails application with the
> same en.yml?

Hi Rodrigo

I've done as you suggested:
1) i made a new rails app 'foo'

2) In my new app 'foo' i uncommented the following in my environment.rb

   config.i18n.load_path += Dir[Rails.root.join('config', 'locales', 
'*.{rb,yml}')]
   config.i18n.default_locale = :en

athough as a side note, this line, when commented, said

Dir[Rails.root.join('my', 'locales'

.. which seems weird - does locales live in a 'my' folder by default, 
rather than 'config'??

3) Anyway, i set up the new app, with the above line, copied my en.yml 
file into locales, started the app, fine.

4) Set up a basic route / to the welcome controller, made the welcome 
controller and views/welcome/index.html.erb, which calls t, deleted 
public/index.html and started the app.  Fine.

5) The page loads fine and the translation works, with the problematic 
line back in the position that was making it break before.

What's weird is if i now deliberately break the syntax of en.yml, eg by 
shoving a : in on it's own line, i only get the complaint when i make 
the t call, NOT when the app starts, as is the case in my other app.  In 
other words, if i add a spurious : to the en.yml in my 'proper' rails 
app it blows up as soon as i start mongrel.

I don't know what the difference would be between them.  One possible 
thing is that in my 'proper' rails app i have frozen rails and all of 
the gems i use into the vendor folder.  Would this have anything to do 
with it do you think?
Posted by Max Williams (max-williams)
on 2010-04-21 10:46
Hi Henri, thanks.

Yes, i've took out everything else and it still breaks, which should 
make diagnosis easier.  I too suspected it might be some kind of rogue 
invisible character breaking it so rather than copy the text into my 
post, here's a download link for the actual file (i copied it into my 
public dropbox)

http://dl.dropbox.com/u/846812/en.yml

Is there a nice way in bash/linux to view the contents of a file showing 
all of the formatting characters?

thanks again, max
Posted by Max Williams (max-williams)
on 2010-04-21 10:47
err "hi henrik" that should have read.  Finger problem rather than eye 
problem :)
Posted by Rodrigo Rosenfeld Rosas (Guest)
on 2010-04-21 15:24
(Received via mailing list)
Hi Max.

Please note this comment in a new fresh Rails app:

# The default locale is :en and all translations from
config/locales/*.rb,yml are auto loaded.

It means you don't need to uncomment the following lines if you will be
setting default_locale to :en and put your translations into
config/locales, following Rails conventions.

About your problem, as I suspected before, it is not related to the
locale file itself, but some other code being loaded on Rails startup.
Could you send the stacktrace so we can help you better?

You could try disable your plugins and moving your scripts in
config/initializers to other place to see if the error disappears.

I would start from the stacktrace, then would probably debug the
relevant part where it is breaking to see what is happening.

You can also turn your plugins back one by one if the errors disappear
when you disable all your plugins. Same for the initializers scripts.

>  I don't know what the difference would be between them.  One possible
>  thing is that in my 'proper' rails app i have frozen rails and all of
>  the gems i use into the vendor folder.  Would this have anything to do
>  with it do you think?

I don't think this make any differences here, but you can freeze your 
new Rails app as well if you think this might be a problem...

Good luck,

Rodrigo.

Em 21-04-2010 05:40, Max Williams escreveu:
Posted by Max Williams (max-williams)
on 2010-04-21 16:44
I think i found what is causing it, and if i'm right it's a gotcha of 
the highest order.  The rule seems to be

"In your <locale>.yml file you cannot have a top-level key which has the 
same name as one of your controllers and which points to a further 
nested set of key-value pairs"

It seems that the controller names (or maybe resource names from 
routes.rb) are pushed into the hash of translated terms automatically, 
as part of the general mechanism of I18n - i guess this is what hooks 
into routes to give you translated urls?  I should have guessed this 
before because i noticed that if i had these as top-level pairs in my 
en.yml file:

en:
  #url translations
  users: users
  account: account
  quizzes: quizzes
  questions: questions
  user_session: user_session

these are all controllers in my app, and i automatically get translated 
urls without having to do anything, if the urls contain these words.  I 
also have a taggings controller, so when i had this:

  title:
    main: Who Wants To Be A Millionaire? For Schools

  taggings:
    save_failed: Could not add this tag because {{message}}

  flashes:
    quiz:
      saved: Your quiz has been saved



...the part that was breaking was that i added a top level key, 
"taggings", which points to another hash {"save_failed" => "Could not 
add this tag because {{message}}"}.  The actual bug was caused because 
rails was expecting this to be a string, probably because of the 
automatically added entry for "taggings" which DOES point to a string 
(i'm guessing "taggings" is the default).   So, it calls gsub on it, 
it's a hash rather than a string, and you get the error because you 
can't call gsub on a hash (it's a private method).

That's also why indenting it to be under flashes was fine:  it doesn't 
fight with the default top level "taggings" key any more and there's no 
problem.

Does that make sense?  I'm not sure if i explained it well.

So, another way to state my rule is:

"Controller names CAN be used as top level keys, and must point to a 
simple value rather than another hash.  If present, these will be 
translated in urls."

or

"If you have a top level key which is the same name as one of your 
controllers/resources, and it has further nested translations below it, 
kiss your app goodbye"
Posted by Rodrigo Rosenfeld Rosas (Guest)
on 2010-04-22 03:53
(Received via mailing list)
Em 21-04-2010 11:44, Max Williams escreveu:
> into routes to give you translated urls?  I should have guessed this
>
>    flashes:
> (i'm guessing "taggings" is the default).   So, it calls gsub on it,
> it's a hash rather than a string, and you get the error because you
> can't call gsub on a hash (it's a private method).
>
> That's also why indenting it to be under flashes was fine:  it doesn't
> fight with the default top level "taggings" key any more and there's no
> problem.
>
> Does that make sense?  I'm not sure if i explained it well.
>    

Completely, you stated it very clear.

Maybe you should add this note to Rails I18n API and as a comment in the
en.yml template, besides stating this behavior in the Rails guides. All
through the railsdoc project. What do you say? This breaks the least
surprise principle, I guess... :)

Best regards,

Rodrigo.
Posted by Max Williams (max-williams)
on 2010-04-22 10:18
Hi Rodrigo, i will, it'd be nice to give something back for a change.

thanks a lot
max
Posted by Iain Hecker (Guest)
on 2010-04-22 11:30
(Received via mailing list)
So to put it in other words, your keys were doubly defined, like this:

en:
  foo: bar
  foo:
    baz: bang

And YAML being as it is (a hash), it allows for only one unique key,
so one of them wins.
When I18n receives a string instead of a hash for nested translation
keys (or the other way round) it gives strange errors.

A problem can arise with default translations, provided by gems or
rails itself, which you know nothing about, that get added to the
I18n.load_paths.
You cannot see the duplication because it's spread around different
files, sometimes tucked away inside a gem.

A solution might be something that checks the translations for doubled
keys, although I think this is tricky to make.
Another solution might be that I18n gives a more sensible error (or
doesn't try to interpolate when it's not a string)

Am I formulating this right?

Iain
Posted by Max Williams (max-williams)
on 2010-04-22 11:56
Yeah, ultimately the problem was that the 'tagging' key was double 
defined - one pointed to a string and the other pointed to a hash.  The 
tricky part was that i only defined the pointing-to-a-hash version in my 
yaml file, and the pointing-to-a-string one was invisibly added by 
rails.  This is the gotcha part.
Posted by Max Williams (max-williams)
on 2010-04-22 11:59
And yes, it would be good if I18n didn't blow up but instead just 
'chose' one of them.  To me the most sensible approach would be that 
keys explicitly defined in the yaml override anything invisbly added by 
rails/I18n/plugins/gems etc.
Posted by Iain Hecker (Guest)
on 2010-04-22 13:32
(Received via mailing list)
The problem is that I18n can't choose anything, because when the file
is read, the second one overwrites the first one. This is done outside
I18n.
It's logical too, because you're actually saying something like this:
{ :foo => 1, :foo => 2 } which will always result in { :foo => 2 }.

Gem authors should put their locales in front of the load_paths, so
that the locales from gems are overwritten by your own locales.
Posted by Max Williams (max-williams)
on 2010-04-22 13:40
Iain Hecker wrote:
> The problem is that I18n can't choose anything, because when the file
> is read, the second one overwrites the first one. This is done outside
> I18n.
> It's logical too, because you're actually saying something like this:
> { :foo => 1, :foo => 2 } which will always result in { :foo => 2 }.
> 
> Gem authors should put their locales in front of the load_paths, so
> that the locales from gems are overwritten by your own locales.

right, yeah.  Is there any way i can make that happen in my 
environment.rb do you know?  In this case it's not a problem, i'm just 
wondering for the future.
Posted by Rodrigo Rosenfeld Rosas (Guest)
on 2010-04-22 13:44
(Received via mailing list)
Hi Max, Iain.

I guess you misunderstood what is happening here... But you are right
that I18n can give better hints instead of failing that way.

The key is not defined twice, I guess. I would guess that I18n searchs
for a translation of a key with the name of the route (or are you using
some plugin that does translate the route?). If it is a common behavior
of I18n, it could be written to verify if the value is a String before
calling gsub. It could assume the default (not translating the key)
otherwise.

I'll take a look at the documentation to see how I18n behaves about
route translations.

Rodrigo.

Em 22-04-2010 08:30, Iain Hecker escreveu:
Posted by Max Williams (max-williams)
on 2010-04-22 13:49
Rodrigo Rosenfeld Rosas wrote:
> The key is not defined twice, I guess. I would guess that I18n searchs
> for a translation of a key with the name of the route (or are you using
> some plugin that does translate the route?). 

No, this is all just the default I18n behaviour.

thanks for helping investigate :)
max
Posted by Rodrigo Rosenfeld Rosas (Guest)
on 2010-04-22 14:00
(Received via mailing list)
Em 22-04-2010 08:49, Max Williams escreveu:
>    
I'm not sure about this. I think I18n won't translate routes
automatically... Are you really sure you are not using any of these 
plugins:

translate_routes: http://github.com/raul/translate_routes
locale_rails: http://www.yotabanana.com/hiki/ruby-locale-rails.html

I created a fresh new Rails app and generated a new controller named
TestsController. I modified the en.yml to look like this:

en:
   tests:
     hi: ola
   test
     hi: ola

Rails didn't failed to start. So I guess this behavior is due to some
plugin. Could you send us your plugin list or your error stacktrace?

Rodrigo.
Posted by Max Williams (max-williams)
on 2010-04-22 14:10
I am a colossal ass.  I am using the translate_routes plugin and 
completely forgot.  That'll be the source of all of these problems, 
nothing to do with I18n.  I'm going to go and punch myself in the balls 
a few times.

Sorry for all the confusion guys, and thanks again for your help.
Posted by Rodrigo Rosenfeld Rosas (Guest)
on 2010-04-22 14:17
(Received via mailing list)
Em 22-04-2010 09:10, Max Williams escreveu:
> I am a colossal ass.  I am using the translate_routes plugin and
> completely forgot.  That'll be the source of all of these problems,
> nothing to do with I18n.  I'm going to go and punch myself in the balls
> a few times.
>
> Sorry for all the confusion guys, and thanks again for your help.
>    
Don't torture yourself. Do something more useful instead: fill an issue
in their github Issues page stating this problem. They could improve
their plugin for not breaking the application on these cases... Or you
could save your energy avoiding puching your balls and try to write some
patch to the plugin that verify if the translation is a string before
using it... :)

Best regards,

Rodrigo.
Posted by Max Williams (max-williams)
on 2010-04-22 14:21
Rodrigo Rosenfeld Rosas wrote:
> Em 22-04-2010 09:10, Max Williams escreveu:
>> I am a colossal ass.  I am using the translate_routes plugin and
>> completely forgot.  That'll be the source of all of these problems,
>> nothing to do with I18n.  I'm going to go and punch myself in the balls
>> a few times.
>>
>> Sorry for all the confusion guys, and thanks again for your help.
>>    
> Don't torture yourself. Do something more useful instead: fill an issue
> in their github Issues page stating this problem. They could improve
> their plugin for not breaking the application on these cases... Or you
> could save your energy avoiding puching your balls and try to write some
> patch to the plugin that verify if the translation is a string before
> using it... :)
> 
> Best regards,
> 
> Rodrigo.

thanks rodrigo, that does sound like a better use of my time and energy.

cheers, max
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.