Easy Internationalization for Your Rails App with BDD

John Ivanoff
John Ivanoff
Share

My company has a web application in US format that we have modified for use by our UK affiliate. However our current need to amend it further and add some functions is leading us down the path of a total re-write. But I believe Rails has saved the day. I am going to show you how easy it is to “internationalize” with Rails. This will be the first in a series with further tests upcoming to create, read, use and delete (CRUD). We will start by creating a Rails application. I like to use Cucumber and factory_girl for testing, so we won’t need test-unit.

$ rails new international --skip-test-unit
...
$ cd international
international $
Open up the folder that has our new app in it and edit the Gemfile. We need to add the testing Gems to it.
group :test do
  gem 'cucumber-rails', '1.2.1'
  gem 'rspec-rails', '2.8.1'
  gem 'database<em>cleaner', '0.7.1'
 gem 'factory</em>girl', '2.4.0'
end
Save the file. Now run bundler to install the new gems.
international $ bundle install
Fetching source index for http://rubygems.org/
Using rake (0.9.2.2)
Using multi<em>json (1.0.4)
Using activesupport (3.1.3)
Using builder (3.0.0)
Using i18n (0.6.0)
Using activemodel (3.1.3)
Using erubis (2.7.0)
Using rack (1.3.6)
Using rack-cache (1.1)
Using rack-mount (0.8.3)
Using rack-test (0.6.1)
Using hike (1.2.1)
Using tilt (1.3.3)
Using sprockets (2.0.3)
Using actionpack (3.1.3)
Using mime-types (1.17.2)
Using polyglot (0.3.3)
Using treetop (1.4.10)
Using mail (2.3.0)
Using actionmailer (3.1.3)
Using arel (2.2.1)
Using tzinfo (0.3.31)
Using activerecord (3.1.3)
Using activeresource (3.1.3)
Using bundler (1.0.21)
Installing nokogiri (1.5.0) with native extensions
Installing ffi (1.0.11) with native extensions
Installing childprocess (0.3.0)
Installing rubyzip (0.9.5)
Installing selenium-webdriver (2.17.0)
Installing xpath (0.1.4)
Installing capybara (1.1.2)
Using coffee-script-source (1.2.0)
Using execjs (1.2.13)
Using coffee-script (2.2.0)
Using rack-ssl (1.3.2)
Using json (1.6.5)
Using rdoc (3.12)
Using thor (0.14.6)
Using railties (3.1.3)
Using coffee-rails (3.1.1)
Installing diff-lcs (1.1.3)
Installing gherkin (2.7.3) with native extensions
Installing term-ansicolor (1.0.7)
Installing cucumber (1.1.4)
Installing cucumber-rails (1.2.1)
Installing database</em>cleaner (0.7.1)
Installing factory_girl (2.4.0)
Using jquery-rails (1.0.19)
Using rails (3.1.3)
Installing rspec-core (2.8.0)
Installing rspec-expectations (2.8.0)
Installing rspec-mocks (2.8.0)
Installing rspec (2.8.0)
Installing rspec-rails (2.8.1)
Using sass (3.1.12)
Using sass-rails (3.1.5)
Using sqlite3 (1.3.5)
Using uglifier (1.2.2)
Your bundle is complete! Use <code>bundle show [gemname]</code> to see where a bundled gem is installed.
With the new gems loaded we can install Cucumber.
international $ rails generate cucumber:install
create config/cucumber.yml
create script/cucumber
 chmod script/cucumber
create features/step_definitions
create features/support
create features/support/env.rb
 exist lib/tasks
create lib/tasks/cucumber.rake
  gsub config/database.yml
  gsub config/database.yml
 force config/database.yml
In order for cucumber to work, we need to create the database, even though we have no models. The following command will do this:
international $ rake db:migrate db:test:prepare
Let’s see if it’s working. Go ahead and run Cucumber:
international $ cucumber
Using the default profile...
0 scenarios
0 steps
0m0.000s
It works. Now we can create our first test. Create a new file in the features folder called manage_locations.feature
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location.
    Given there is a location named "location 1"
    When I am on the locations page
    Then I should see "location 1"
Save the file and let’s run the test.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/manage</em>locations.feature:7
      Undefined step: "there is a location named "location 1"" (Cucumber::Undefined)
      features/manage<em>locations.feature:7:in <code>Given there is a location named "location 1"'
 When I am on the locations page # features/manage_locations.feature:8
 Undefined step: "I am on the locations page" (Cucumber::Undefined)
 features/manage_locations.feature:8:in</code>When I am on the locations page'
 Then I should see "location 1" # features/manage</em>locations.feature:9
      Undefined step: "I should see "location 1"" (Cucumber::Undefined)
      features/manage_locations.feature:9:in `Then I should see "location 1"'
  1 scenario (1 undefined)
  3 steps (3 undefined)
  0m1.745s
You can implement step definitions for undefined steps with these snippets:
Given /^there is a location named "([^"]<em>)"$/ do |arg1|
 pending # express the regexp above with the code you wish you had
end
When /^I am on the locations page$/ do
 pending # express the regexp above with the code you wish you had
end
Then /^I should see "([^"]</em>)"$/ do |arg1|
 pending # express the regexp above with the code you wish you had
end
Just like Cucumber says, we’ll implement the steps using the template it gave us. Create a new file in the /features/step_definitions folder called location_steps.rb Copy and paste those snippets into the file we just created.
Given /^there is a location named "([^"]<em>)"$/ do |arg1|
 pending # express the regexp above with the code you wish you had
end
When /^I am on the locations page$/ do
 pending # express the regexp above with the code you wish you had
end
Then /^I should see "([^"]</em>)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end
Let’s save the file and rerun the test.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 TODO (Cucumber::Pending)
 ./features/step</em>definitions/location<em>steps.rb:2:in <code>/^there is a location named "([^"]*)"$/'
 features/manage_locations.feature:7:in</code>Given there is a location named "location 1"'
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 Then I should see "location 1" # features/step</em>definitions/location_steps.rb:9
  1 scenario (1 pending)
  3 steps (2 skipped, 1 pending)
  0m1.249s
As expected it’s not passing, but there’s a bit less noise. We need to create a location model so we can have locations. Using the generator,  create one and, for now, it will just have a name attribute.
international $ rails g model location name:string
invoke active<em>record
create db/migrate/20120118214355</em>create_locations.rb
create   app/models/location.rb
With the model created, time to run the migration and prep the test database.
international $ rake db:migrate db:test:prepare
==  CreateLocations: migrating ================================================
-- create_table(:locations)
   -> 0.0015s
==  CreateLocations: migrated (0.0016s) =======================================
Now we can create a location. I like to use factory_girl. It makes it easy to create test data. (Learn more) It takes a bit of time to set up, but I think it’s worth it. In the /features/support folder create a file called factories.rb and add the following.
require 'factory_girl'
FactoryGirl.define do
  factory :location do |f|
    f.name 'test location'
  end
end
This will create a location with the name “test location” in the database for testing purposes. You can always pass in a name to the factory, so it doesn’t have to be “test’ location.” We’ll see an example of that shortly. In the location_steps.rb file, modify the first step so it looks like this:
Given /^there is a location named "([^"]*)"$/ do |name|
  Factory(:location, :name => name)
end
Save the file and rerun the test
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 TODO (Cucumber::Pending)
 ./features/step</em>definitions/location<em>steps.rb:6:in <code>/^I am on the locations page$/'
 features/manage_locations.feature:8:in</code>When I am on the locations page'
 Then I should see "location 1" # features/step</em>definitions/location_steps.rb:9
  1 scenario (1 pending)
  3 steps (1 skipped, 1 pending, 1 passed)
  0m1.485s
Excellent… the first step is passing. Next, we’ll go to the locations page but we’ll need to make a route for that. Open the routes.rb file in the config folder and add
match 'locations/' => 'locations#index', :as => :locations
That will get us to the locations index page. We can check the route by typing in the command line:
international $ rake routes
locations /locations(.:format) locations#index
in the location_steps.rb file let’s tell is where to go by adding:
When /^I am on the locations page$/ do
  visit(locations_path)
end
Save the file and rerun the test.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
uninitialized constant LocationController (ActionController::RoutingError)
 ./features/step</em>definitions/location<em>steps.rb:6:in <code>/^I am on the locations page$/'
 features/manage_locations.feature:8:in</code>When I am on the locations page'
 Then I should see "location 1" # features/step</em>definitions/location<em>steps.rb:9
 Failing Scenarios:
 cucumber features/manage</em>locations.feature:6 # Scenario: List a location.
  1 scenario (1 failed)
  3 steps (1 failed, 1 skipped, 1 passed)
  0m1.501s
It tells us there is no LocationsController. OK, create one and just worry about the index page, for now.
international $ rails g controller locations index
 create app/controllers/locations<em>controller.rb
 route get "locations/index"
 invoke erb
 create app/views/locations
 create app/views/locations/index.html.erb
 invoke helper
 create app/helpers/locations</em>helper.rb
 invoke assets
 invoke   coffee
 create     app/assets/javascripts/locations.js.coffee
 invoke   scss
 create     app/assets/stylesheets/locations.css.scss
Let’s go in the routes file (config/routes.rb) and delete the route that was created when we genereated the controller. Remove this line.
get "locations/index"
Save the file,. Now that we have a controller, let’s rerun the test.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 Then I should see "location 1" # features/step</em>definitions/location<em>steps.rb:9
 TODO (Cucumber::Pending)
 ./features/step</em>definitions/location_steps.rb:10:in <code>/^I should see "([^"]*)"$/'
 features/manage_locations.feature:9:in</code>Then I should see "location 1"'
  1 scenario (1 pending)
  3 steps (1 pending, 2 passed)
  0m1.858s
Perfect. We just have to display the names of the locations. In /app/views/locations folder open the index.html.erb file. We’ll add this code that will loop through the names:
<ul>
  <% @locations.each do |location| %>
    <li>
      <%= location.name %>
    </li>
  <% end %>
</ul>
Let’s see what happens if we run the test now.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 You have a nil object when you didn't expect it!
 You might have expected an instance of Array.
 The error occurred while evaluating nil.each (ActionView::Template::Error)
 ......
 features/manage</em>locations.feature:8:in `When I am on the locations page'
    Then I should see "location 1" # features/step<em>definitions/location</em>steps.rb:9
Failing Scenarios:
cucumber features/manage_locations.feature:6 # Scenario: List a location.
1 scenario (1 failed)
3 steps (1 failed, 1 skipped, 1 passed)
0m1.599s
Nil object? We didn’t get anything from the database. Let’s fix that by opening up the locations_controller.rb file in /app/controllers Change the index method:
def index
  @locations = Location.all

  respond_to do |format|
    format.html # index.html.erb
    format.json { render json: @locations }
  end
end
In the location_step.rb we need to change the following:
Then /^I should see "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end
to:
Then /^(?:|I )should see "([^"]*)"$/ do |text|
  if page.respond_to? :should
    page.should have_content(text)
  else
    assert page.has_content?(text)
  end
end
Save the files and rerun the test.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 Then I should see "location 1" # features/step</em>definitions/location_steps.rb:9
  1 scenario (1 passed)
  3 steps (3 passed)
  0m1.679s
Okay, all green! The first test is passing! Who needs a break?

¿Habla español?

Let’s get into the internationalization. We need to make an i18n module. In the /config/initializers/ folder create a new file called i18n.rb In this file we’ll add
#encoding: utf-8

I18n.default_locale = :en

LANGUAGES = [
  ['English', 'en'],
  ["Español".html_safe, 'es']
]
This sets the default language to English and creates a list of languages that we will support along with their locales. The locale will be specified in the URL,  telling us which language to use.  We’ll scope this in the routes and add a root URL. Don’t forget to remove /public/index.html! Open the routes.rb file and add these lines:
scope '(:locale)' do
  match 'locations/' => 'locations#index', :as => :locations
  root :to => 'locations#index'
end
We have nested our routes inside the scope of :locale, and since it’s in parentheses, it’s optional. http://localhost:3000/es will use the default locale, English. http://localhost:3000/en and http://localhost:3000/es
will use the same controller and method, but will use the specified locale in the URL Now we need to set the locale based on the param in the URL, if it’s provided. We’ll do this in the Application Controller with a before filter.Open the application_colbtroller.rb file in the /app/controllers folder. Add this line:
class ApplicationController < ActionController::Base
  before_filter :set</em>i18n<em>locale_from_params
  protect_from_forgery

protected
  def set_i18n_locale_from_params
    if params[:locale]
      if I18n.available_locales.include?(params[:locale].to_sym)
        I18n.locale = params[:locale]
      else
        flash.now[:notice] = "#{params[:locale]} translation not available"
        logger.error
        flash.now[:notice]
      end
    end
  end
  def default_url_options
    { locale: I18n.locale }
  end
end
So here it is checking for a locale parameter. If there is one, it checks to see if that locale is in our list of languages back in the /config/initializers/i18n.rb file. If it is in the list, we set the locale to the param. If it is not in the list, we will show a message saying that locale is not available. The default_url_options sets the default locale when one is not provided. Since we’ve made a lot of changes at this point, let’s save all of the files and rerun the test.
international $ cucumber

Using the default profile...
Feature: Manage locations
In order to manage locations
As a user
I want to create and edit my locations.

Scenario: List a location. # features/manage<em>locations.feature:6
Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
Then I should see "location 1" # features/step</em>definitions/location_steps.rb:9

1 scenario (1 passed)
3 steps (3 passed)
0m1.583s
Still all green. Whew. Let’s get this i18n cooking. On our locations index page, add a “Locations” heading Open the managing_locations.feature file and add
Scenario: List a location.
Given there is a location named "location 1"
When I am on the locations page
Then I should see "Locations"
And I should see "location 1"
Save the files and rerun the test.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 Then I should see "Locations" # features/step</em>definitions/location<em>steps.rb:9
 expected there to be content "Locations" in "Internationalnnnn location 1n n" (RSpec::Expectations::ExpectationNotMetError)
 ./features/step</em>definitions/location<em>steps.rb:11:in <code>/^(?:|I )should see "([^"]*)"$/'
 features/manage_locations.feature:9:in</code>Then I should see "Locations"'
 And I should see "location 1" # features/step</em>definitions/location<em>steps.rb:9
 Failing Scenarios:
 cucumber features/manage</em>locations.feature:6 # Scenario: List a location.
  1 scenario (1 failed)
  4 steps (1 failed, 1 skipped, 2 passed)
  0m1.593s
Fails as expected. Open the index.html.ern file in /app/views/locations and add
<h1>Locations</h1>
Save the file and rerun the test.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 Then I should see "Locations" # features/step</em>definitions/location<em>steps.rb:9
 And I should see "location 1" # features/step</em>definitions/location_steps.rb:9
  1 scenario (1 passed)
  4 steps (4 passed)
  0m1.604s
We’re back to green. Now copy the same test but instead of “Location” in the heading we should see “Locaciones” Change your managing_locations.feature to:
Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location.
    Given there is a location named "location 1"
    When I am on the locations page
    Then I should see "Locations"
    And I should see "location 1"
  Scenario: List a location.
    Given there is a location named "location 1"
    When I am on the locations page
    Then I should see "Locaciones"
    And I should see "location 1"
Save the file and rerun the test
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 Then I should see "Locations" # features/step</em>definitions/location<em>steps.rb:9
 And I should see "location 1" # features/step</em>definitions/location<em>steps.rb:9
 Scenario: List a location. # features/manage</em>locations.feature:12
    Given there is a location named "location 1" # features/step<em>definitions/location</em>steps.rb:1
    When I am on the locations page # features/step<em>definitions/location</em>steps.rb:5
    Then I should see "Locaciones" # features/step<em>definitions/location</em>steps.rb:9
     expected there to be content "Locaciones" in "InternationalnnLocationsnnn location 1n n" (RSpec::Expectations::ExpectationNotMetError)
     ./features/step<em>definitions/location</em>steps.rb:11:in <code>/^(?:|I )should see "([^"]*)"$/'
features/manage_locations.feature:15:in</code>Then I should see "Locaciones"'
    And I should see "location 1" # features/step<em>definitions/location</em>steps.rb:9
  Failing Scenarios:
  cucumber features/manage_locations.feature:12 # Scenario: List a location.
  2 scenarios (1 failed, 1 passed)
  8 steps (1 failed, 1 skipped, 6 passed)
  0m1.643s
It failed where we expected. Here’s where all the coding we did earlier pays off. It’s time to translate. For the words we need to translate, we will call I18n.translate which has an alias I18n.t. There is also a helper provided named t. The parameter to the translate function is a unique dot-qualified name. We can choose any name you like, but if we use the t helper function provided, names that start with a dot will first be expanded using the name of the template. Let’s do that. In the index.html.erb, change as follows
<h1>Locations</h1>
to
<h1><%= t('.title_html') %></h1>
Save the file. We need to create a en.yml file in /config/locales folder.
en:

locations:
    index:
      title_html:    "Locations
We also need to create a es.yml file in /config/locales folder
es:

locations:
    index:
      title_html:    "Locaciones"
These files hold the translations. Also notice the structure of the YAML resembles the file structure? (/locations/index). This will be handy down the road. Save the files and rerun the test…again.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 Then I should see "Locations" # features/step</em>definitions/location<em>steps.rb:9
 And I should see "location 1" # features/step</em>definitions/location<em>steps.rb:9
 Scenario: List a location. # features/manage</em>locations.feature:12
    Given there is a location named "location 1" # features/step<em>definitions/location</em>steps.rb:1
    When I am on the locations page # features/step<em>definitions/location</em>steps.rb:5
    Then I should see "Locaciones" # features/step<em>definitions/location</em>steps.rb:9
     expected there to be content "Locaciones" in "InternationalnnLocationsnnn location 1n n" (RSpec::Expectations::ExpectationNotMetError)
     ./features/step<em>definitions/location</em>steps.rb:11:in <code>/^(?:|I )should see "([^"]*)"$/'
 features/manage_locations.feature:15:in</code>Then I should see "Locaciones"'
    And I should see "location 1" # features/step<em>definitions/location</em>steps.rb:9
  Failing Scenarios:
  cucumber features/manage_locations.feature:12 # Scenario: List a location.
  2 scenarios (1 failed, 1 passed)
  8 steps (1 failed, 1 skipped, 6 passed)
  0m1.643s
WHAT? Red? It seems that in the second test we never said to use es as the locale so it is defaulting to en
. Let’s fix that as follows:
Scenario: List a location.
  Given there is a location named "location 1"
  And I am on the es site
  When I am on the locations page
  Then I should see "Locaciones"
  And I should see "location 1"
Save the files and rerun the test.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 Then I should see "Locations" # features/step</em>definitions/location<em>steps.rb:9
 And I should see "location 1" # features/step</em>definitions/location<em>steps.rb:9
 Scenario: List a location. # features/manage</em>locations.feature:12
    Given there is a location named "location 1" # features/step<em>definitions/location</em>steps.rb:1
    And I am on the es site # features/manage<em>locations.feature:14
 Undefined step: "I am on the es site" (Cucumber::Undefined)
 features/manage</em>locations.feature:14:in `And I am on the es site'
    When I am on the locations page # features/step<em>definitions/location</em>steps.rb:5
    Then I should see "Locaciones" # features/step<em>definitions/location</em>steps.rb:9
    And I should see "location 1" # features/step<em>definitions/location</em>steps.rb:9
  2 scenarios (1 undefined, 1 passed)
  9 steps (3 skipped, 1 undefined, 5 passed)
  0m1.723s
  You can implement step definitions for undefined steps with these snippets:
  Given /^I am on the es site$/ do
    pending # express the regexp above with the code you wish you had
  end
Alright, then! It tells us what to do. Open the location_steps.rb and add this to it.
Given /^I am on the (.+) site$/ do |language|
  I18n.locale = language
end
Save the file and rerun the test.
international $ cucumber
  Using the default profile...
  Feature: Manage locations
  In order to manage locations
  As a user
  I want to create and edit my locations.
  Scenario: List a location. # features/manage<em>locations.feature:6
 Given there is a location named "location 1" # features/step</em>definitions/location<em>steps.rb:1
 When I am on the locations page # features/step</em>definitions/location<em>steps.rb:5
 Then I should see "Locations" # features/step</em>definitions/location<em>steps.rb:9
 And I should see "location 1" # features/step</em>definitions/location<em>steps.rb:9
 Scenario: List a location. # features/manage</em>locations.feature:12
    Given there is a location named "location 1" # features/step<em>definitions/location</em>steps.rb:1
    And I am on the es site # features/step<em>definitions/location</em>steps.rb:17
    When I am on the locations page # features/step<em>definitions/location</em>steps.rb:5
    Then I should see "Locaciones" # features/step<em>definitions/location</em>steps.rb:9
    And I should see "location 1" # features/step<em>definitions/location</em>steps.rb:9
  2 scenarios (2 passed)
  9 steps (9 passed)
  0m1.636s
SCORE! You now have a very simple multilingual site. I hope you found useful for using BDD to drive internationalization of your Rails app.

Frequently Asked Questions (FAQs) on Internationalization for Your Rails App with BDD

What is the importance of internationalization in Rails applications?

Internationalization, often abbreviated as i18n, is a crucial aspect of modern web applications. It allows your Rails application to be used by people from different geographical locations, who speak different languages. This is achieved by translating the application’s user interface into various languages. By doing this, you can reach a wider audience, improve user experience, and increase the potential market for your application.

How does Behavior Driven Development (BDD) enhance the process of internationalization?

BDD is a software development approach that encourages collaboration among developers, QA and non-technical participants in a software project. It enhances the process of internationalization by ensuring that the application behaves as expected in different locales. BDD uses human-readable descriptions of software user requirements as the basis for software tests. This means that you can write tests to check that your application behaves correctly when it’s translated into different languages.

What are some of the tools used for internationalization in Rails?

Rails provides a built-in framework for internationalization, which includes key features like translation and localization. However, there are also several gems that can help with this process. One of the most popular is the ‘i18n’ gem. This gem provides an easy-to-use and extensible framework for translating your application to different languages. It also supports features like pluralization and number formatting.

How can I test my Rails application for different locales using BDD?

BDD encourages the use of automated tests to verify the behavior of your application. You can use testing frameworks like RSpec and Cucumber to write tests for your application. These tests can be written to check that your application behaves correctly when it’s translated into different languages. For example, you can write a test to check that a form error message is correctly translated when the user’s locale is set to French.

How can I handle pluralization in different languages?

Pluralization rules can vary significantly between different languages. Fortunately, the ‘i18n’ gem provides support for this. You can define different pluralization rules for different languages in your translation files. Then, you can use the ‘pluralize’ helper method in your views to correctly pluralize words based on the user’s locale.

How can I handle number formatting in different locales?

Number formatting can also vary between different locales. For example, some locales use a comma as the decimal separator, while others use a period. The ‘i18n’ gem provides a ‘number_to_currency’ helper method that you can use to format numbers correctly based on the user’s locale.

How can I handle date and time formatting in different locales?

Date and time formatting can vary significantly between different locales. The ‘i18n’ gem provides a ‘l’ (short for localize) helper method that you can use to format dates and times correctly based on the user’s locale.

How can I handle translations for dynamic content?

For dynamic content, you can use interpolation in your translation files. This allows you to insert variables into your translations. For example, you could have a translation like “Hello, %{name}!”, where ‘%{name}’ is replaced with the user’s name.

How can I handle fallbacks for missing translations?

The ‘i18n’ gem provides a mechanism for handling missing translations. You can set a default locale in your application configuration, and if a translation is missing for the user’s locale, the gem will fall back to the default locale.

How can I contribute to the ‘i18n’ gem?

The ‘i18n’ gem is open source, and contributions are welcome. You can contribute by reporting bugs, suggesting new features, or submitting pull requests. You can find the source code and issue tracker on the gem’s GitHub page.