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.
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.
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. :)
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.
Very useful thing. I’ve tried to use Globalize, but your plugin looks more simple and convenient for my needs. Thanks a lot :)
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:
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…
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.
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?
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.
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.
not exactly a gibberish problem… but how do you guys i18n error messages from validates_presence_of and friends ?
Hi Chris,
From what you said about the reasons that drew you to creating Gibberish:
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
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:
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:
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 :)
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.
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?
Great! I know it has nothing to do with it but you would say “Bienvenido Amigo”, not “Recepcion Amigo”. Great work though.
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
oops, i didn’t see gustavo’s comment, it wasn’t there when i started to write ;)
I also use GLoc and it does seem pretty similar ;-)
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]
Maybe i missing something out by why should i use this plugin instead of gettext?
I’m a big fan of the concept, and most of the implementation. This, however, gave me pause: <<< Like this:
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.”
gaius: See comments above, this has been addressed here.
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
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
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
Can this translate data from database?, I used a cookbook “tutorial” and did:
<% Gibberish.current_language =:es %>
<% for recipe in @recipes %>
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!
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
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
Yidagoo gadaguys adagar adall nadagerds!!!!
Active Record validation errors + Gibberish = love -> explains all :).
ehm, sorry, the link is not an image ..
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.
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!
Are there any plans for Rake tasks to extract all gibberish strings appearing anywhere in the code into a YML file?
I can’t download from svn://errtheblog.com/svn/plugins/gibberish/. Is it possible the site is down?
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 #
bienvenido is welcome in spanish. great blog. thankyou for share with us!
why does this not work?
class Emailer < ActionMailer::Base
end
It translates the sent by/Email string fine but it doesn’t add in the user specific info????
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.
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]
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..
Anyone here manage to get localized routes going?
mysite.com/en/things/1
mysite.com/es/cosas/1
Ripping my hair out over here >:(
Anyone have an idea when errtheblog.com will go up again?
Chime in.
All original content copyright ©2006-2008 the aforementioned.