simplyglobal : A simple globalization plugin for Rails

simplyglobal : A simple globalization plugin for Rails

The following is a guest post from Dan Simard
Sometimes, you have to reinvent the wheel. It’s really sad to say and you will probably hate me for saying that (and I know that you’ll do because I hate myself for it). I’ve written a new globalization plugin for Rails.

Why did I reinvented the wheel?

I searched and tried a lot of other globalisation plugins… and I really tried them. I spent hours with Globalize. It was just too much. I tried the gettext_Localize that works with the good ol’ gettext command. Fuck it. Too complicated. It just didn’t fit my needs at all.
All I wanted was a wheel that you can put a wood-pole in the middle and then it could start spinning. I made one.
You can go on the simplyglobal project homepage to learn on to install it and use it.
In fact, this is not really a globalization plugin because there’s no localization handling or anything that can look like it. The name should have been simplytranslated but I already created the project with the name simplyglobal and it was an hassle to change it.

How to install

1. Execute ./script/plugin install http://simplyglobal.googlecode.com/svn/trunk/simplyglobal
2. Create a file named simplyglobal.rb in the config/initializers directory
3. In simplyglobal.rb, create hashes of language
Add the language hashes to the objectYou will end up with a file named simplyglobal.rb that looks like this :

#français
fr = {    "hi" => "bonjour",    "welcome" => "bienvenue"  }
# espanol
es = {    "hi" => "hola",    "welcome" => "bienvenida"}
SimplyGlobal.add_language_hash(:fr, fr)
SimplyGlobal.add_language_hash(:es, es)

In development, this file will be loaded every request. In production, it is loaded once.

How to use it with strings

After you installed it, you can use it in these various ways.
SimplyGlobal adds a t() method to all string objects that will return the translated string. Example, if you have defined a language hash that looks like this (note : normally, the languages hash are defined in config/initializers/simplyglobal.rb but I put it inline for the sake of the example) :

fr = {"hi" => "bonjour"} # Create the language hash
SimplyGlobal.add_language_hash(:fr, fr) # Add the language hash to simplyglobal
SimplyGlobal.locale = :fr # Assigns the locale to use
"hi".t # returns "bonjour"

As simple as that!
You can use it like the % method of the String class.

fr = {"hi %s%d" => "bonjour %s%d"} # Create the language hash
SimplyGlobal.add_language_hash(:fr, fr) # Add the language hash to simplyglobal
SimplyGlobal.locale = :fr # Assigns the locale to use
"hi".t("Johnny", 5) # returns "bonjour Johnny5"

You can also return all translations for a word. That is a special feature developed only for Frank.

SimplyGlobal.add_language_hash(:fr, {"hi" => "bonjour"}) # Add the french language hash
SimplyGlobal.add_language_hash(:es, {"hi" => "hola"}) # Add the spanish language hash
"hi".t(:all) # returns a hash : {:fr => "bonjour", :es => "hola"}

Using it with views

Just create a view ending with _fr and simplyglobal will use it.

def index
  SimplyGlobal.locale = :fr
  # Will try to render index_fr.html.erb
  # rather than index.html.erb
end

It also works for the partial.

<%= render :partial => "info" %>

will try to render _info_fr.html.erb rather than _info.html.erb.

How to display a collection grouped by an attribute value in Rails

Flashback time. We are in 1999 and you are coding in ASP thinking that this language is the future. You use ADODB recordsets to iterate over collections. You have a recordset containing some records from a “quotes” table. The “quotes” table contains a author column (varchar) and a body column (varchar). Now you want to display the results grouped by author name so it looks like this :
Georges Brassens
– Les filles quand ça dit “je t’aime”, c’est comme un second baptême
– Aucune idée sur terre n’est digne d’un trépas
Billie Holiday
– Don’t threaten me with love, baby. Let’s just go walking in the rain.
– I never hurt nobody but myself and that’s nobody’s business but my own.
Assuming your recordset is ordered by author, you do something like this (remember, we’re in 1999) :
[vb]<TABLE style="color:fushia;font-style:MSONormal generated=frontpage">
<%while not objRS.eof %>
<%if(current_author <> objRS("author"))%>
<h1><%=objRS("author")%></h1>
<%end if%>
<Tr><td><%=objRS("body")</td></TR>
<%current_author = objRS("author")%>
<%objRS.MoveNext%>
<%loop%>
<%objRS.close%>
</TABLE>[/vb]
Ok welcome back in 2008. ASP is dead. You are coding in rails and you want to do the same thing. How will you do it? Storing the author name in a buffer variable like in 1999? Not too sure about it.
This is a job for Enumerable#group_by (Enumerable is a module that is mixed in the Array class)

all_quotes = Quote.find(:all)
@authors = all_quotes.group_by(&:author)

Enumerable#group_by will create different sets of quotes based on the “author” values. Why the “&” sign? It’s because group_by expects a block. I could have done it this way : @authors = all_quotes.group_by{|quote| quote.author}
So @authors is now a hash that will look like this:
{“George Brassens” => [#<Quote id:131 …>, #<Quote id:331 …>], “Billie Holiday” => [#<Quote id:111 …>, #<Quote id:911 …>] }
Now you can iterate over it like this :

@authors.each_pair do |author_name, quotes|
  <h1><%=author_name%></h1>
  <%quotes.each do |quote| %>
    - <%=quote.body%><br />
  <%end%>
<%end%>

One more thing to note : @authors is a hash, and hashes cannot be ordered. If you want to display quotes by sorted author name, you could do this :

@authors.keys.sort.each do |author_name|
  <h1><%=author_name%></h1>
  <%@authors[author_name].each do |quote|%>
    - <%=quote.body%><br />
  <%end%>
<%end%>

Note : Enumerable#group_by does not exist in ruby 1.8, it only exists in Rails. The method will be in ruby 1.9 however.

XMPP4r 0.4 has been released

It’s been a while since the latest release of XMPP4r. I was starting to think that the development for this great library had been stopped.
Fortunately, version 0.4 has been released on August 5th 2008. We’re going to try this new version internally and eventually use it for TimmyOnTime. I am personnally hoping for less memory consumption, more speed and more stability. This new version highlights are :

  1. The beginning of ruby 1.9 support
  2. Refactoring of error classes (I’m really looking forward to this)

Here is the full changelog :
XMPP4R 0.4 (05/08/2008)
=======================
* Initial support for Ruby 1.9 (see README_ruby19.txt)
* Complete PubSub API Change – more logical and better for
childclasses, support for collection node creation
* a Helper to assist with XEP-0115 Entity Capabilities
* SASL anonymous support
* File transfer fixes
* MUC room configuration fixes
* initial support for XEP-0118 User Tune
* fix for an xmlrpc exception-during-serialisation bug, which would cause
a hang
* Support auto-gem generation on GitHub with improved and DRY’er RakeFile and
gemspec.
* Add support for the old SSL protocol (needed to connect to GTalk)
* Changed API for Client, Component, Connection, Stream to remove
need for antiquated ‘threaded’ param in the initializer.
* Use a Logger instance instead of directly writing to stdout
* Re-factored & consolidated Error classes. See xmpp4r/errors.rb for all
custom errors that can be caught. All inherit from Jabber::Error which
itself inherits from Ruby’s StandardError. This is a first step in
re-factoring errors. The next step will be to convert all ‘raise’ calls to
raise a custom Jabber::Error or one of its children instead of anonymous
RuntimeErrors. This allows much more granularity in catching and handling
errors later.
If you were catching Jabber::ErrorException before you should probably
change that in your code to now catch Jabber::Error if you want to
catch everything or one of the custom children of Jabber::Error defined in
‘lib/xmpp4r/errors.rb’. Additionally, the Error class which encapsulated
the xmpp error response, has been renamed to ErrorResponse to reflect its
real usage. This free’s up ‘Jabber::Error’ for use as our base Error class.

Get the intersection of 2 arrays

Pretty easy stuff today, but this is not something you may need to do often so maybe you don’t know how to do it.
Say I have these 2 arrays :

colors1 = [:blue, :red, :green, :orange, :purple]
colors2 = [:yellow, :cyan, :green, :blue, :purple]

Now what if I want a new array that contains only the elements present in both arrays?

beautiful_colors = colors1 & colors2
#beautiful_colors contains [:blue, :green, :purple]

Have a nice weekend!