Example application
NOTE
If you want the latest API reference for globalize plugin, run rake doc:plugins to generate rdoc documentation. You can find it later in app/doc/plugins/globalize directory.
NOTE
You need to apply some changes below to make it work with Oracle.
2. Creating rails application
Firstly create a very simple rails app (just one table) to work with.
rails globalizeThis should create a skeleton of your application.
Now you need to create a database to work with, i.e. “globalize_development” and configure config/database.yml file correctly.
Next create the only model:
script/generate model ProductThis will create also a migrate file
db/migrate/001_create_products.rb, which you can use to create products table.
class CreateProducts < ActiveRecord::Migration
def self.up
create_table :products do |t|
t.column :name, :string
t.column :company, :string
t.column :description, :text
t.column :price, :integer, :default => 0
t.column :created_on, :date
t.column :updated_on, :date
end
end
def self.down
drop_table :products
end
end
Run
rake migrateand if there were no errors you should have new
products table in your database.
Now you can add some functionality to your app:
script/generate scaffold admin/product
script/generate controller product
The first line will generate the whole admin panel for your
products table and the second one will add a controller, which will be used by “user” part of the app.
Now it’s time for some cosmetic changes:
- remove created_on and updated_on input elements from
app/views/admin/products/_form.rhtmlpartial – you don’t need them as they these fields are updated automatically - copy index, list and show actions from
app/controllers/admin/products_controller.rbtoapp/controllers/product_controller.rb - copy
list.rhtmlandshow.rhtmlfiles fromapp/views/admin/products/toapp/views/product/ - remove
edit,destroyandnewfunctionality fromlist.rhtmlandshow.rhtmlfiles you’ve just copied - delete or rename
public/index.html - add layouts for admin and user parts
Next add routing rules for user and admin parts. Add these 2 lines to your config/routes.rb file:
# You can have the root of your site routed by hooking up ''
# -- just remember to delete public/index.html.
map.connect '', :controller => 'product', :action => 'index'
map.connect 'admin', :controller => 'admin/products', :action => 'index'
Now you’ve got your own basic rails app with full CRUD functionality for admin and read functionality for user.
3. Installing globalize plugin
To install the plugin just type:
script/plugin install svn://svn.globalize-rails.org/globalize/branches/for-1.1
Now go to vendor/plugins and rename for-1.1 directory to globalize.
Now run:
rake globalize:setupand you’ve just finished installing globalize!
NOTE
You can run rake -T to see all available rake tasks – including these added by globalize.
WARNING
In current revision (197) there’s a problem with built_in column in globalize_translation table, which causes all translations added by the user to be marked as built in.
I’m not sure if it’s just problem with my configuration, but to check if you also have this problem, type this in the console:
include Globalize
Locale.set "en-US"
Locale.set_translation("foo", "bar")
ViewTranslation.find(:first, :conditions => "tr_key = 'foo'").built_in?
If you get false, then your translation is not being set as built-in and everything is ok.
If not, close the console and open the file
vendor/plugins/globalize/tasks/data.rake and change line #63:t.column :built_in, :boolean, :default => trueto
t.column :built_in, :boolean#, :default => trueNow run
rake globalize:teardown
rake globalize:setup
Open the console and set all fields in globalize_translation table as built_in
include Globalize
ViewTranslation.update_all "built_in = true"
Now it should work correctly: all translations that already exist in the table should be marked as built-in and all new translations added by you won’t be anymore.
Note that due to the unorthodox way Globalize handles the database (without migrations), this change won’t be reflected in
schema.rb.
4. Making your application globalized
4.1 Configuration
First you need to set the base language. Add these lines to config/environment.rb file:
# Include your application configuration below
include Globalize
Locale.set_base_language 'en-US'
LOCALES = {'pl' => 'pl-PL',
'en' => 'en-US',
'es' => 'es-ES',
'fr' => 'fr-FR'}.freeze
Of course you can select any language as your base language. In LOCALES hash we will store all available locales for this app. Remember to restart the server after you made changes in
environment.rb file.
NOTE
It’s important to never change the base language once you’ve started populating the database.
You can pass the information about the language, which should be displayed, in the session variable or in the url. Here just the second method is presented, as it allows you i.e. to bookmark the page in selected language etc.
Now you need to modify your config/routes.rb file a bit. Add this line before the lines you added before:
map.connect ':locale/:controller/:action/:id'
You also need to set the current language somewhere. Edit your app/controllers/application.rb file:
class ApplicationController < ActionController::Base
before_filter :set_locale
def set_locale
if !params[:locale].nil? && LOCALES.keys.include?(params[:locale])
Locale.set LOCALES[params[:locale]]
else
redirect_to params.merge( 'locale' => Locale.base_language.code )
end
end
end
Try to display your main page again – you should see “en” in the url before the controller name.
Now just add possibility to switch between languages and move to the next chapter.
To switch between languages you can use i.e. following link:
<%= link_to "pl", {:controller => controller.controller_name, :action => controller.action_name, :locale => 'pl', :id => params[:id]} %>
This will display current page in selected language, but it will only work with the default route.
NOTE
You could iterate through LOCALE hash to create a link for every supported language or create select input field.
NOTE by Marc-André Lafortune
I prefer to use the preferences set in the user’s client to determine the default locale. The user can then decide to override it by clicking on a button you provide, in which case I want to store that in a cookie so that preference is remembered forever. Here’s my filter:
# Will set the cookie 'locale' if (and only if) an explicit parameter 'locale'
# is passed (and is acceptable)
# If no cookie exists, we look through the list of desired languages for the
# first one we can accept.
#
def set_locale
accept_locales = LOCALES.keys # change this line as needed, must be an array of strings
cookies[:locale] = params[:locale] if accept_locales.include?(params[:locale])
Locale.set(cookies[:locale] || (request.env["HTTP_ACCEPT_LANGUAGE"] || "").scan(/[^,;]+/).find{|l| accept_locales.include?(l)})
end
4.2 Translating static content.
First you need to generate translate controller.
script/generate controller admin/translate
Now edit your new controller:
class Admin::TranslateController < ApplicationController
def index
@view_translations = ViewTranslation.find(:all, :conditions => [ 'built_in IS NULL AND language_id = ?', Locale.language.id ], :order => 'text')
end
def translation_text
@translation = ViewTranslation.find(params[:id])
render :text => @translation.text || ""
end
def set_translation_text
@translation = ViewTranslation.find(params[:id])
previous = @translation.text
@translation.text = params[:value]
@translation.text = previous unless @translation.save
render :partial => "translation_text", :object => @translation.text
end
end
Let’s focus on the index action. We fetch all non-built-in translations for current locale and order them by
text column. This will put all untranslated fields at the beginning of @view_translation array.You can change the conditions i.e.:
def index
@view_translations = ViewTranslation.find(:all,
:conditions => [ 'text IS NULL AND language_id = ?', Locale.language.id ], :order => 'tr_key')
end
This way you’ll get untranslated fields only.
Remember to set
admin layout for this controller.
Now create app/views/admin/translate/_translation_text.rhtml partial, which is used in translation_text action.
<%= translation_text || '[no translation]' %>
To translate the static content you can use in_place_editor helper, which is built-in in Rails 1.1. Just remember to add
<%= javascript_include_tag :defaults %>
in the layout file for the admin part (e.g. create an application-wide
app/views/layouts/application.rhtml layout – you can copy most of the generated products.rhtml layout there, just change the title and add the javascript tag).
Next create app/views/admin/translate/_translation_form.rhtml partial for the form, which will be used to provide translations.
<!--[form:translate]-->
<p>
<label for="tr_<%= tr.id %>"><%=tr.tr_key%></label>
<br />
<span id="tr_<%= tr.id %>">
<%= render :partial => 'translation_text', :object => tr.text %>
</span>
<%= in_place_editor "tr_#{tr.id}",
:url => { :action => :set_translation_text, :id => tr.id },
:load_text_url => url_for({ :action => :translation_text, :id => tr.id })%>
</p>
<!--[eoform:translate]-->
NOTE
If you want more information about in_place_editor helper look here.
In the next step we will use 2 useful helpers. Add the following code to app/helpers/application_helper.rb file:
def base_language_only
yield if Locale.base?
end
def not_base_language
yield unless Locale.base?
end
NOTE
These helpers come from Jeremy Voorhis’ Canada on Rails slides, which you can find here.
Still in app/views/admin/translate, create index.rhtml and edit it:
<% base_language_only do -%>
<div id="language"><h1>Please choose language for translation</h1></div>
<% end -%>
<% not_base_language do -%>
<div id="language"><h1><%= "Language: " + Locale.language.native_name %></h1></div>
<div>
<% @view_translations.each do |tr| -%>
<%= render :partial => 'translation_form', :locals => {:tr => tr}%>
<% end -%>
</div>
<% end -%>
We don’t want to translate english strings to english again, so the translation forms will only be displayed if current language is different than the base one.
Add a new product if you haven’t done this already – go to localhost:3000/admin/, press ‘New product’ and fill out the form.
Open app/views/product/list.rhtml and show.rhtml and add .t method to all strings you want to translate i.e. change 'Show' to 'Show'.t etc. This way you’ll get translated user part of the application.
To add these strings to the database they must be displayed first, so go to localhost:3000/pl/product. Next go to localhost:3000/pl/admin/translate. You should see the list of strings to which you’ve added .t method. Translate them and go to localhost:3000/pl/product again.
NOTE
If you don’t get any strings on localhost:3000/pl/admin/translate page to translate, make sure that:
- you’re not displaying that page with the base locale
- you added
.tmethod to the strings - you displayed pages, to which you added
.tmethod
NOTE
Here’s useful thread showing how to add untranslated strings automatically: http://rubyforge.org/pipermail/railsi18n-discussion/2006-August/000124.html
4.3 Translating model data.
First you need to decide which fields you want to translate. In our Product model we’ll translate name and description fields.
Add this line to app/models/product.rb:
translates :name, :description
We will allow adding new products only for the base language. Modify app/views/admin/products/list.rhtml file:
<% base_language_only do -%>
<%= link_to 'New product', :action => 'new' %>
<% end -%>
In the same directory create new file _translation_form.rhtml. Copy contents of _form.rhtml partial to this new file, but remove company and price input fields as they won’t be translated. As you may have already figured out this form will be used to provide translations for the Product model.
Now create another file _translation_data.rhtml and put the following code inside:
<div>
<b>Name:</b> <%=h @product.name %>
<b>Description:</b> <%=h @product.description %>
</div>
This file will be used as the reference and will show data in the base language.
Now it’s time to modify edit.rhtml file.
<h1>Editing product</h1>
<% base_language_only do -%>
<%= start_form_tag :action => 'update', :id => @product %>
<%= render :partial => 'form' %>
<%= submit_tag 'Edit' %>
<%= end_form_tag %>
<% end -%>
<% not_base_language do -%>
<div style="float:left;">
<h2><%= Locale.language.native_name%></h2>
<%= start_form_tag( {:action => 'update', :id => @product} ) %>
<%= render :partial => 'translation_form' %>
<%= submit_tag 'save translation' %>
<%= end_form_tag %>
</div>
<div style="float:right;">
<% @product.switch_language(Locale.base_language.code) do -%>
<h2><%= Locale.base_language.native_name%></h2>
<%= render :partial => 'translation_data' %>
<% end -%>
</div>
<% end -%>
<%= link_to 'Show', :action => 'show', :id => @product %> |
<%= link_to 'Back', :action => 'list' %>
Navigate to
localhost:3000/pl/admin/products, find the product you added, and click on Edit.If the current language is the base one, the full version of the edit form is displayed. If not, the translation form is displayed on the left and the original data on the right, as the reference for the translator.
NOTE.switch_language method used in this code is provided by globalize_extension plugin written by Olivier Amblet from Liquid Concept, which can be found here. It provides many other useful extensions for globalize plugin. Just install it into your vendor/plugins directory
NOTE
If you’re going to translate many models it would probably be good idea to put the code above into a partial and just pass an object to translate:
render :partial "translation_page", :locals => {:object => @product}
To inform user that the text hasn’t been yet translated you can use the following helper (also from Jeremy Voorhis’ slides mentioned above):
def translation_availability_for object_name, facet, message = nil
not_base_language do
message ||= content_tag 'p', '(Translation not available)'.t
object = instance_variable_get "@#{object_name}"
message if object && !object.send( facet ).blank? &&
object.send( "#{facet}_is_base?" )
end
end
Here’s the example of its usage:
<%= translation_availability_for :product, :description %>
<%=h @product.description %>
4.4 Translating dates
To translate dates you can use localize method or its shorter form loc. It takes format string as the paramater, exactly the same as strftime method.
I.e.
Product.find(:first).created_on.loc("%A %d-%B-%Y")
should give “Wednesday 14-June-2006” for English language and “Środa 14-Czerwiec-2006” for Polish.
countries table has field date_format, which you can use to specify custom format for different languages. Example for Polish language:
country = Country.find(:first, :conditions => "code = 'PL'")
country.update_attribute(:date_format, "%d/%m/%y")
Globalize uses cache to minimize database usage, so sometimes you need to clear it to make changes visible:
Locale.clear_cache
Locale.set "pl-PL"
Locale.country.date_format
Now you can just use
product.created_on.loc(Locale.country.date_format)
and you’ll get different date format for every language.
In a template use for example:
@product.send(column.name).localize("%d %B %Y") if column.name.to_s == 'created_on'
4.5 Translating error messages
Globalize provides modified version of error_messages_for helper. Unfortunatelly it doesn’t have translated error messages in the database yet. But you can easily add them by yourself. Here’s how to do it.
—-
I couldn’t find any way to do it on this page, so I came up with my own solution, using the rails console script. Fairly simple:
Locale.set 'pl-PL' # you might want to iterate throught LOCALES.each_value here
ActiveRecord::Errors.default_error_messages.each_value do |error_msg|
error_msg.translate
end
Benol
and if your base language isn’t English?
—-
4.6 Translating currency
WARNING
Current implementation of Currency class has some bugs. They should be fixed in the next revision.
To use it you need to modify
app/models/product.rb file. Add the following line:
composed_of :price, :class_name => "Globalize::Currency", :mapping => [ %w(price cents) ]
Now
product.price will return Globalize:Currency object. To display the actual value you’ve got 2 possibilities:
- .to_s method
- .format method, which can take optional parameters: if :code => true is specified, format using international 3-letter currency code; if :country is specified as well, use that country’s currency code for formatting
Text by: szymek (83.15.148.82)
revision 1 · 25.06.08 00:39 · by: Sven