Recipe 5.16. Globalizing Your Rails Application


Problem

Contributed by: Christian Romney

You need to support multiple languages, currencies, or date and time formats in your Rails application. Essentially, you want to support internationalization (or i18n).

Solution

The Globalize plug-in provides most of the tools you'll need to prepare your application for the world stage. For this recipe, create an empty Rails application called global:

$ rails global             

Next, use Subversion to export the code for the plug-in into a folder called globalize under vendor/plugins:

$ svn export \ > http://svn.globalize-rails.org/svn/globalize/globalize/branches/for-1.1\ >    vendor/plugins/globalize             

If your application uses a database, you'll need to set it up to store international text. MySQL, for example, supports UTF-8 encoding out of the box. Configure your database.yml file as usual, making sure to specify the encoding parameter:

config/database.yml:

development:   adapter: mysql   database: global_development   username: root   password:   host: localhost   encoding: utf8             

Globalize uses a few database tables to keep track of translations. Prepare your application's globalization tables by running the following command:

$ rake globalize:setup             

Next, add the following lines to your environment:

config/environment.rb:

require 'jcode' $KCODE = 'u' include Globalize  Locale.set_base_language('en-US')

Your application is now capable of globalization. All you need to do is create a model and translate any string data it may contain. To really test Globalize's capabilities, create a Product model complete with a name, unit_price, quantity_on_hand, and updated_at fields. First, generate the model:

$ ruby script/generate model Product             

Now define the schema for the product table in the migration file. You also want to include a redundant model definition here in case future migrations rename or remove the Product class.

db/migrate/001_create_products.rb:

class Product < ActiveRecord::Base    translates :name  end class CreateProducts < ActiveRecord::Migration    def self.up      create_table :products do |t|        t.column :name, :string        t.column :unit_price, :integer        t.column :quantity_on_hand, :integer        t.column :updated_at, :datetime      end           Locale.set('en-US')      Product.new do |product|        product.name = 'Little Black Book'        product.unit_price = 999        product.quantity_on_hand = 9999        product.save      end           Locale.set('es-ES')      product = Product.find(:first)      product.name = 'Pequeño Libro Negro'      product.save    end    def self.down      drop_table :products    end  end

Note that you must change the locale before providing a translation for the name. Go ahead, and migrate the database now:

$ rake db:migrate             

You might have noticed the unit price is an integer field. Using integers eliminates the precision errors that arise when floats are used for currency (a very, very bad idea). Instead, we store the price in cents. After the migration has completed, modify the real model class to map the price to a locale-aware class included with Globalize. (Note that this doesn't perform currency conversion, which is beyond the scope of this recipe.)

app/models/product.rb:

class Product < ActiveRecord::Base    translates :name    composed_of :unit_price, :class_name => "Globalize::Currency",                :mapping => [ %w(unit_price cents) ]  end

Now generate a controller to show off your application's new linguistic abilities. Create a Products controller, with a show action:

$ ruby script/generate controller Products show             

Modify the controller as follows:

app/controllers/products_controller.rb:

class ProductsController < ApplicationController    def show      @product = Product.find(params[:id])    end  end

You can set the locale in a before_filter inside ApplicationController:

app/controllers/application.rb:

class ApplicationController < ActionController::Base   before_filter :set_locale     def set_locale     headers["Content-Type"] = 'text/html; charset=utf-8'           default_locale = Locale.language_code     request_locale = request.env['HTTP_ACCEPT_LANGUAGE']     request_locale = request_locale[/[^,;]+/] if request_locale     @locale = params[:locale] ||        session[:locale] ||       request_locale ||        default_locale     session[:locale] = @locale     begin       Locale.set @locale     rescue ArgumentError       @locale = default_locale       Locale.set @locale     end   end end

Note that the Content-Type header is set to use UTF-8 encoding. Lastly, you'll want to modify the view:

app/views/products/show.rhtml:

<h1><%= @product.name.t %></h1>  <table>  <tr>    <td><strong><%= 'Price'.t %>:</strong></td>    <td><%= @product.unit_price %></td>  </tr>  <tr>    <td><strong><%= 'Quantity'.t %>:</strong></td>    <td><%= @product.quantity_on_hand.localize %></td>  </tr>  <tr>    <td><strong><%= 'Modified'.t %>:</strong></td>    <td><%= @product.updated_at.localize("%d %B %Y") %></td>  </tr>  </table>

Before you run the application, you must provide translations for the literal strings 'Price', 'Quantity', and 'Modified' found in the template. To do so, fire up the Rails console.

$ ruby script/console             

Now enter the following:

>> Locale.set_translation('Price', Language.pick('es-ES'),'Precio')  >> Locale.set_translation('Quantity', Language.pick('es-ES'),'Cantidad')  >> Locale.set_translation('Modified', Language.pick('es-ES'),'Modificado')             

Your application is ready to be viewed. Start your development server:

$ ruby script/server -d             

Assuming your server is running on port 3000, point your browser to http://localhost:3000/products/show/1 to see the English version. To see the Spanish version, point your browser here: http://localhost:3000/products/show/1?locale=es-ES.

Discussion

Figure 5-9 shows how you can specify the locale via a query string parameter. You can also use the standard HTTP Accept-Language header. Explicit parameters take precedence over defaults, and the application can always fall back to 'en-US' if things get scary.

Figure 5-9. A globalized Rails application, displaying content in both English and Spanish


You can also include the locale as a route parameter by modifying routes.rb and replacing the default route.

config/routes.rb:

# Install the default route as the lowest priority.  map.connect ':locale/:controller/:action/:id'

You then access the Spanish language version product page here at http://localhost:3000/es-ES/products/show/1. Globalization takes some effort in any language or framework, and while proper Unicode support is not yet included in Ruby, the Globalize plug-in takes the sting out of the most common localization tasks.

See Also

  • GLoc plug-in, http://www.agilewebdevelopment.com/plugins/gloc

  • Localization Simplified plug-in, http://www.agilewebdevelopment.com/plugins/localization_simplified

  • For more information and examples on using the Globalize plug-in, visit http://www.globalize-rails.org

  • Documentation for the Globalize plug-in is also available at http://globalize.rubyforge.org




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net