Globalizing Your Rails ApplicationProblemContributed 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). SolutionThe 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. DiscussionFigure 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 SpanishYou 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
|