Err the Blog Atom Feed Icon
Err the Blog
Rubyisms and Railities
  • “Ya Talkin' Gibberish”
    – Chris on May 08, 2007

    Oh man! Just tonight Beast Edge was graced with localization, courtesy of Gibberish. Relevant commits are here and here. There’s already some heated debate on the Mephisto mailing list regarding this change and possible future changes, if you’re into that sort of thing. Dig in.

    Gibba-what-now?

    Yeah, what? Gibberish is a simple Rails localization plugin we developed for FamUpdate and have been slipping into other projects. We’ve got Globalize and we’ve got Localize, but come on. They’re great, and useful, but sometimes you just want something light weight. I know I do.

    I also know keying my translations by a string is not fun. What if I want to change a comma? Or add an exclamation point! Oh, use a symbol. But symbols make it hard to read my views. There must be a middle ground.

    Of course: Gibberish.

    Keyin’

    <%= 'Description'[:description_title] %>
    

    The above will return Description by default. If you’ve another language selected (besides English), it will find the description_title key in that language’s translation file and return the corresponding string instead. Simple.

    You may leave the brackets blank, which will force Gibberish to assume the key is a lowercase, underscore’d version of the string.

    So this:
    "Header description"[]
    
    Is the same as this:
    "Header description"[:header_description]
    

    Real smooth (thanks to technoweenie). Just, be careful. If you change the string too much you could land yourself in the same place we’re trying to escape from. (Trouble.)

    klingon.yml

    Translation files are simple YAML. For instance, es.yml:

    welcome_friend: ¡Recepción, amigo!
    welcome_user: ¡Recepción, {user}!
    love_rails: Amo los carriles.
    

    Something like “Welcome friend”[] will, when the language is set to :es, return ¡Recepción, amigo!. You can change the language quite easily:

    >> Gibberish.current_language = :es
    => :es
    

    It’s even got one of those trendy around_filters:

    class ApplicationController < ActionController::Base
      around_filter :set_language
    
    private
      def set_language
        Gibberish.use_language(session[:language]) { yield }
      end
    end
    

    No big deal.

    Check the languages you’ve loaded with languages:

    >> Gibberish.languages
    => [:es, :fr, :de, :kl]
    

    In dev mode languages are automatically reloaded. From where? RAILS_ROOT + ’/langs’. Gibberish’ll look for .yml files in that directory.

    The above example, we can safely assume, is run within a Rails app with lang/es.yml, lang/fr.yml, lang/de.yml, and lang/kl.yml. Add another language file and it’ll get picked up on the fly.

    Intersomethinglation

    The other cool thing Gibberish provides is interpolation. You may have noticed the {user} in welcome_user way above. Yeah, that’s gonna change.

    Like this:
    >> "Welcome, {user}"[:welcome_user, current_user.name]
    => "Welcome, Chris"
    

    The {user} bit will be replaced with current_user.name when being rendered. Works for translations, too. Naturally.

    Curly brace’d strings are interpolated in the order arguments are passed. The fact that it says user is irrelevant. Really, it’s only to make things easy to remember. The important part is that it’s the first interpolation bullseye in the string.

    Another example (ostensibly to drill home the point but realistically because me and PJ are on a Sandman kick):

    >> "{name} is from {place}"[:hey_place, 'Chris', 'the Dreamworld']
    => "Chris is from the Dreamworld"
    

    Okay, you got the idea.

    Grab it!

    From the svn:

    $ cd vendor/plugins
    $ piston import svn://errtheblog.com/svn/plugins/gibberish
    

    Follow trac: http://require.errtheblog.com/plugins/browser/gibberish

    View the README: http://require.errtheblog.com/plugins/browser/gibberish

    And, as always, report bugs to Lighthouse: http://err.lighthouseapp.com/projects/466-plugins

    Enjoy ya jive talkin’ Gibberish. Patches gladly accepted.

  • evan, about 2 hours later:

    What about auto-generating keyed YAML for new languages? Or a test helper that autotested every key in the app for a full translation set…? Hmm.

  • Luke Stark, about 2 hours later:

    Funny thing, just yesterday my boss was asking about how easy it will be to send out strings for translation in our app. We’ll fiddle and see how it works alongside Globalize.

    Thanks. :)

  • Ben Kittrell, about 3 hours later:

    Freaking awesome. It’s always great when one of Rails’ “weaknesses” is conquered with one of the coolest implementations of all.

    Rats of to ya.

  • DEkart, about 3 hours later:

    Very useful thing. I’ve tried to use Globalize, but your plugin looks more simple and convenient for my needs. Thanks a lot :)

  • Anders Engström, about 3 hours later:

    Everyone is writing their own i18n library it seems :) I’m not entirely sure that is a bad thing, though. Everyone seem to have different requirements (depending on app, nationality etc.).

    I’ve implemented something that looks similar to Gibberish, but the configuration is done in plain ruby code, and the ‘localization context’ is thread-local:

    
    # Define stuff
    Localization.in_context("sv") do |l|
      l.instance_eval do
        define :name, "Namn"
        define :greeting, "Hello, %s"
        define :time, proc{|*args| (t.first || Time.now).strftime("%H:%M")}
      end
    end
    
    # To use..
    Localization.in_context("sv") do
      puts _(:name) #=> "Namn" 
      puts _(:greeting, "Anders") #=> "Hello, Anders" 
      puts _(:time) #=> evaluated to formatted Time.now
      puts _(:time, @some_time_ref)
    end
    
  • Alex Deva, about 5 hours later:

    Typo now uses Anders’ library. I find it even easier to use than Gibberish, and it has support to autogenerate string resource files.

    Anders, I had to patch your library just a tiny bit, to help it support JavaScript localization too…

  • scoopr, about 6 hours later:

    How would caching work? could the htaccess stuff be tweaked to get /users/1/info.sv.html when a language cookie is set to sv ? or just have prefix /sv/users/1/info.html .. would make invalidating a bit hard.

  • Greg Spurrier, about 6 hours later:

    Very cool. I’ll be needing something like this soon, so I’ll keep my eye on it.

    One question: if the string interpolation is strictly based on the order of the arguments, what happens if the translated version uses the arguments in a different order than the English version?

  • Chris, about 21 hours later:

    Anders: Looks cool! I don’t think it’s a bad thing, either.

    scoopr: I haven’t hit this yet. PDI?

    Greg: Great question. Try this changeset which opens up interpolation via hash. If you’ve got a better idea please let me know.

  • Tim Blair, about 23 hours later:

    My first thought was “what if the interpolated variables are in a different order for different languages”. When I come to point it out, someone’s already mentioned it and it’s been fixed. Isn’t the Rails community great?

    Good work Chris, looks like an excellent job.

  • pascal, 1 day later:

    not exactly a gibberish problem… but how do you guys i18n error messages from validates_presence_of and friends ?

  • Saimon Moore, 1 day later:

    Hi Chris,

    From what you said about the reasons that drew you to creating Gibberish:

    "I also know keying my translations by a string is not fun.What if I want to change a comma? Or add an exclamation point!"

    I’d like to point out that using globalize this is not a problem at all.

    Keying by string is exactly the same as keying by symbol in the sense that both are keys.

    i.e. once you define a translatable string key, to add a comma or change the value in any way for the base language (I assume you mean for the base language) then all you need to do is modify/create the translation for the base language. You can have translations for the base language too.

    e.g. Locale.set(‘en’,’US’) “This is a translatable string”.t => This is a translatable string

    Change ‘translation’ of the key in ‘en’ locale

    Locale.set_translation(“This is a translatable string”, “This is a translatable string!”)

    “This is a translatable string”.t => This is a translatable string!

    There’s absolutely no need to change the key or create another key at all.

    Just wanted to point this out in case people thought this was a major drawback of globalize :)

    Regards,

    Saimon

  • Anders Engström, 1 day later:

    Pascal: I was hoping that Rails would resolve :message in validate_presence_of by simply calling #to_s on it. So – in my Localization lib I extend Object with:

      # Returns an object whos to_s will resolve the key in the current context. Used
      # to define a localized reference that will be resolved in runtime
      def __(key, *args)
        LateResolver.new(key, *args)
      end
    

    The LateResolver is a proc that will resolve the message key to the “current” locale when to_s is called on the proc. That would (if my assumptions on how Rails handles stuff was true) allow me to do:

    validates_presence_of :customer_id, :message => __(:no_blank)
    

    Unfortunately, this is not how Rails works (not as far as I can tell anyway). If anyone with deeper knowledge about Rails want to correct me – please go ahead :)

  • Chris, 1 day later:

    Pascal: There exists ActiveRecord::Errors.default_error_messages, a hash of error message strings. Maybe Gibberish can override these with key’d equivalents at runtime? Would make it easier to localize them. Check validations.rb.

  • Fred Brunel, 8 days later:

    I’ve used GLoc (http://wiki.rubyonrails.org/rails/pages/GLoc) and it’s pretty much the same.

    What is the difference with your plugin? Does it use any specific Rails mechanic?

  • Gustavo Beathyate, 9 days later:

    Great! I know it has nothing to do with it but you would say “Bienvenido Amigo”, not “Recepcion Amigo”. Great work though.

  • amaia, 9 days later:

    just a comment about the translation example, i don’t know if in other parts of the spanish speaking world that translation would be ok but here in Spain “Welcome, friend!” would be “¡Bienvenido, amigo!” or “¡Bienvenida, amiga!” if it’s a female friend

    regards, amaia

  • amaia, 9 days later:

    oops, i didn’t see gustavo’s comment, it wasn’t there when i started to write ;)

  • Nick, 10 days later:

    I also use GLoc and it does seem pretty similar ;-)

  • Chris, 10 days later:

    All the localization libraries are pretty similar, yeah. The difference is in the API. GLoc, to my knowledge, doesn’t let you keep both the default string and the string’s translation key in your views. This is the main point of Gibberish.

    If you don’t care about putting your default strings in the view, then Gibberish probably isn’t for you.

    GLoc: l(:welcome_string_key)

    Gibberish: “Welcome”[:welcome]

    GLoc: l(:hello_string_key, name)

    Gibberish: “Hey, {name}”[:hello, name]

  • Jake, 15 days later:

    Maybe i missing something out by why should i use this plugin instead of gettext?

  • gaius, about 1 month later:

    I’m a big fan of the concept, and most of the implementation. This, however, gave me pause: <<< Like this:

    “Welcome, {user}”[:welcome_user, current_user.name] => “Welcome, Chris”

    The {user} bit will be replaced with current_user.name when being rendered. Works for translations, too. Naturally.

    Curly brace’d strings are interpolated in the order arguments are passed. The fact that it says user is irrelevant. Really, it’s only to make things easy to remember. The important part is that it’s the first interpolation bullseye in the string. >>>

    What if the language I’m translating to has a different sentence order? In Chinese, Time often comes before Subject, whereas that order is flexible in English. And I don’t want to have to pick the order I pass the arguments based on what language I’m translating to; that would defeat the whole thing!

    Unless, of course, the translations have an ordering in them, like Java’s I18N: welcome_user: “Hello, {0}. The time is now {1}.”

    I sure hope you mean “the interpolation bullseye with the lowest index in the string.”

  • Chris, about 1 month later:

    gaius: See comments above, this has been addressed here.

  • Kathy, about 1 month later:

    Whenever I try to download from the svn://errtheblog.com/svn/plugins/gibberish subversion account it asks for a username = ? and password = ?. Could someone email those to me? Thanks, Kathy KathysKode@gmail.com

  • Deni, 3 months later:

    Great plugin – simple and very easy to use.

    Btw, there’s a small typo in the post: I think “RAILS_ROOT + ’/langs’” should be “RAILS_ROOT + ’/lang’”.

    Regards, Deni

  • Jamal, 6 months later:

    You will get error when you add empty language yml files :)

    You need to fill them up to not get this error below…

    $ ruby script/console Loading development environment (Rails 1.2.5) /Users/account/Documents/RubyonRailsApps/languages/vendor/plugins/gibberish/lib/gibberish/localize.rb:56:in `load_languages!’:NoMethodError: undefined method `symbolize_keys’ for false:FalseClass

    PS: Just in case

  • Fernando, 6 months later:

    Can this translate data from database?, I used a cookbook “tutorial” and did:

    <% Gibberish.current_language =:es %>

    <% for recipe in @recipes %> <% title = recipe.title[] %> <%= link_to %Q{#{title}}, ...

    But only some recipes titles are translated even though I have all of them in my es.yml. What confuses me the most is that if I use those same titles as strings in my page the translation works, but not when I read them from database.

    I’m sorry to bother you, I’m sure it must be something very simple that I’m not seeing.

    Thanks in advance!

  • Nikolay Petrov, 6 months later:

    Hi,

    I wandered for a little time and then added a non invasive snipped to reload translations on the fly if the mode of the rails is development. Here it is: if ENV[‘RAILS_ENV’] == ‘development’ puts “Development Mode” end

    language_paths = [RAILS_ROOT]
    paths = language_paths.map {|path| Dir[File.join(path, 'lang', '*.{yml,yaml}')]}.flatten
    rfiles = paths.map {|path| File.new(path)}
    Thread.new(rfiles) do |files|
      puts "Lang reload - started"
      start_times = files.map {|file| file.mtime }
      while(true)
        sleep 1
        times = files.map {|file| file.mtime }
        next if start_times == times
        puts "Lang reload - reaload"
        Gibberish.load_languages!
        start_times = times
      end
    end
  • Nikolay Petrov, 6 months later:

    Oh no the formatting:

    if ENV[‘RAILS_ENV’] == ‘development’ puts “Development Mode” language_paths = [RAILS_ROOT] paths = language_paths.map {|path| Dir[File.join(path, ‘lang’, ’*.{yml,yaml}’)]}.flatten rfiles = paths.map {|path| File.new(path)} Thread.new(rfiles) do |files| puts “Lang reload – started” start_times = files.map {|file| file.mtime } while(true) sleep 1 times = files.map {|file| file.mtime } next if start_times == times puts “Lang reload – reaload” Gibberish.load_languages! start_times = times end end end

  • Catata, 8 months later:

    Yidagoo gadaguys adagar adall nadagerds!!!!

  • vjt, 8 months later:

    Active Record validation errors + Gibberish = love -> explains all :).

  • vjt, 8 months later:

    ehm, sorry, the link is not an image ..

  • Chris Eppstein, 8 months later:
    I’ve written an extension to gibberish for my team at http://www.caring.com that provides the following support:
    • storage of strings in the database
    • output of string to html in a way that allows us to know which string is actually being displayed (wraps it in a span that sets the class and lang) when passed through h() in an rhtml file.

    On top of this, we’ve written controllers and some javascript to allow editing-in-place of strings while you’re interacting with the site. It’s very nice – we’re using it to manage our site-copy.

    Checkout the code here: http://www.eppsteins.net/rails-plugins/gibberish_db

    Read the docs here: http://www.eppsteins.net/rails-plugins/gibberish_db/rdoc/index.html

    Hope you find this helpful.

  • face, 8 months later:

    I tried overriding ActiveRecord::Errors.default_error_messages for a site that changes locale based on the user’s preference (HTTP headers or RESTful routes). Unfortunately Rails validations cache strings at object load time, which happens long before the around filter.

    So I made a simple plugin gibberish_rails that overrides some core Rails validation methods. Note: This plugin is in a prototype state and very Rails 2.0.2 specific. However, I think it makes a great example to anyone facing this same problem. I plan to rewrite this plugin and simplify it once Rail’s issue 9726 is fixed.

    Thanks for creating the wonderful Gibberish plugin!

  • Branko Vukelic, 9 months later:

    Are there any plans for Rake tasks to extract all gibberish strings appearing anywhere in the code into a YML file?

  • Tim, 9 months later:

    I can’t download from svn://errtheblog.com/svn/plugins/gibberish/. Is it possible the site is down?

  • Fred, 9 months later:

    Anyone using this plugin with a rails project that is also using Hpricot? After installing Gibberish it seems that perhaps the StringExt is causing collisions with method []

    NoMethodError: undefined method `filter[:]’ for #

  • dani, 10 months later:

    bienvenido is welcome in spanish. great blog. thankyou for share with us!

  • Tom, 10 months later:

    why does this not work?

    class Emailer < ActionMailer::Base

    @body[:sender_name] = "Sent By, #{sender_name}" [:sent_by, sender_name]
    @body[:sender_email] = "Email, #{sender_email}"[:user_email, sender_email]

    end

    It translates the sent by/Email string fine but it doesn’t add in the user specific info????

  • Matt Powell, 11 months later:

    Here’s a little something I’ve added into my application_helper.rb to make select boxes for picking languages easier. Just stick a language_name: French key in your fr.yaml file.

      def language_options
        @language_options ||= returning [] do |languages|
          ([ :en ] + Gibberish.languages).each do |lang|
            Gibberish.use_language(lang) do
              languages << [ lang.to_s[:language_name], lang ]
            end
          end
        end
      end
    
  • Jaryl Sim, 11 months later:

    I’d believe that you’d have to do this instead:

    @body[:sender_name] = “Sent By, {#{sender_name}}” [:sent_by, sender_name] @body[:sender_email] = “Email, {#{sender_email}}” [:user_email, sender_email]

  • Jamie Conlon, about 1 year later:

    Gibberish is great =)

    However, there does appear to be a conflict with it and Hpricot. If an element in Hpricot contains a colon, such as you would find if you try to parse youtube’s api:

    @doc.at(“yt:duration”)

    it tosses up an error:

    NoMethodError: undefined method `filter[:]’ ...

    The problem comes up at line 306 in lib/hpricot/elements.rb, but I could not quickly see what was causing it..

  • SG, about 1 year later:

    Anyone here manage to get localized routes going?

    mysite.com/en/things/1

    mysite.com/es/cosas/1

    Ripping my hair out over here >:(

  • Igor, about 1 year later:

    Anyone have an idea when errtheblog.com will go up again?

  • Forty-four people have commented.
    Chime in.
    Sorry, no more comments :(
This is Err, the weblog of PJ Hyett and Chris Wanstrath.
All original content copyright ©2006-2008 the aforementioned.