10.2. A Rails Application in Only One Language: German

In a Rails application aimed only at German users, it is unfortunately not enough to just translate all the views into German. The approach is in many respects similar to a multi-lingual Rails application (see Section 10.3, “Multilingual Rails Application”). Correspondingly, there will be a certain amount of repetition. I am going to show you the steps you need to watch out for by using a simple application as example. Let's go through all the changes using the example of this bibliography application:
$ rails new bibliography
  [...]
$ cd bibliography
$ rails generate scaffold book title number_of_pages:integer 'price:decimal{7,2}'
  [...]
$ rake db:migrate
  [...]
$
To get examples for validation errors, please insert the following validations in the app/models/book.rb:
class Book < ActiveRecord::Base
  attr_accessible :number_of_pages, :price, :title

  validates :title,
            :presence => true,
            :uniqueness => true,
            :length => { :within => 2..255 }

  validates :price,
            :presence => true,
            :numericality => { :greater_than => 0 }
end
Please search the configuration file config/application.rb for the value config.i18n.default_locale and set it to :de for German. In the same context, we then also insert two directories in the line above for the translations of the models and the views. This directory structure is not a technical requirement, but makes it easier to keep track of things if your application is quite big:
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', 'models', '*', '*.yml').to_s]
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', 'views', '*', '*.yml').to_s]
config.i18n.default_locale = :de
You then still need to create the corresponding directories:
$ mkdir -p config/locales/models/book
$ mkdir -p config/locales/views/book
$ 
Now you need to generate a language configuration file for German or simply download a ready-made one by Sven Fuchs from his Github repository at https://github.com/svenfuchs/rails-i18n:
$ cd config/locales
$ curl -O https://raw.github.com/svenfuchs/rails-i18n/master/rails/locale/de.yml
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  4940  100  4940    0     0   4296      0  0:00:01  0:00:01 --:--:--  4862
$

Note

If you know how Bundler works, you can also insert the line gem 'rails-i18n' into the file Gemfile and then execute bundle. This gives you all language files from the repository.
In the file config/locales/de.yml, you have all required formats and generic wordings for German that you need for a normal Rails application (for example, days of the week, currency symbols, etc). Have a look at it with your favorite editor to get a first impression.
Next, we need to tell Rails that a model 'book' is not called 'book' in German, but 'Buch'. The same applies to all attributes. So we create the file config/locales/models/book/de.yml with the following structure. As side effect, we get the methods Model.model_name.human and Model.human_attribute_name(attribute), with which we can insert the model and attribute names in the view.
# ruby encoding: utf-8

de:
  activerecord:
    models:
      book: 'Buch'
    attributes:
      book:
        title: 'Titel'
        number_of_pages: 'Seitenanzahl'
        price: 'Preis'
In the file config/locales/views/book/de.yml we insert a few values for the scaffold views:
# ruby encoding: utf-8

de:
  views:
    show: Anzeigen
    edit: Editieren
    destroy: Löschen
    are_you_sure: Sind Sie sicher?
    back: Zurück
    edit: Editieren
    book:
      index:
        title: Bücherliste
        new: Neues Buch
      edit:
        title: Buch editieren
      new:
        title: Neues Buch
      flash_messages:
        book_was_successfully_created: 'Das Buch wurde erfolgreich angelegt.'
        book_was_successfully_updated: 'Das Buch wurde erfolgreich aktualisiert.'
Now we still need to integrate a "few" changes into the views. We use the I18n.t helper that can also be abbreviated with t in the view. I18n.t reads out the corresponding item from the YAML file. In the case of a purely monolingual German application, we could also write the German text directly into the view, but with this method we can more easily switch to multilingual use if required. You can find more information on I18n.t in the section called “I18n.t”.
app/views/books/_form.html.erb
<%= form_for(@book) do |f| %>
  <% if @book.errors.any? %>
    <div id="error_explanation">
      <h2><%= t 'activerecord.errors.template.header', :model => Book.model_name.human, :count => @book.errors.count %></h2>
      <ul>
      <% @book.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :number_of_pages %><br />
    <%= f.number_field :number_of_pages %>
  </div>
  <div class="field">
    <%= f.label :price %><br />
    <%= f.text_field :price %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
app/views/books/edit.html.erb
<h1><%= t 'views.book.edit.title' %></h1>

<%= render 'form' %>

<%= link_to I18n.t('views.show'), @book %> |
<%= link_to I18n.t('views.back'), books_path %>
app/views/books/index.html.erb
<h1><%= t 'views.book.index.title' %></h1>

<table>
  <tr>
    <th><%= Book.human_attribute_name(:title) %></th>
    <th><%= Book.human_attribute_name(:number_of_pages) %></th>
    <th><%= Book.human_attribute_name(:price) %></th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @books.each do |book| %>
  <tr>
    <td><%= book.title %></td>
    <td><%= number_with_delimiter(book.number_of_pages) %></td>
    <td><%= number_to_currency(book.price) %></td>
    <td><%= link_to I18n.t('views.show'), book %></td>
    <td><%= link_to I18n.t('views.edit'), edit_book_path(book) %></td>
    <td><%= link_to I18n.t('views.destroy'), book, confirm: I18n.t('views.are_you_sure'), method: :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to I18n.t('views.book.index.new'), new_book_path %>
app/views/books/new.html.erb
<h1><%= t 'views.book.new.title' %></h1>

<%= render 'form' %>

<%= link_to I18n.t('views.back'), books_path %>
app/views/books/show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <b><%= Book.human_attribute_name(:title) %>:</b>
  <%= @book.title %>
</p>

<p>
  <b><%= Book.human_attribute_name(:number_of_pages) %>:</b>
  <%= number_with_delimiter(@book.number_of_pages) %>
</p>

<p>
  <b><%= Book.human_attribute_name(:price) %>:</b>
  <%= number_to_currency(@book.price) %>
</p>


<%= link_to I18n.t('views.edit'), edit_book_path(@book) %> |
<%= link_to I18n.t('views.back'), books_path %>

Note

In the show and index view, I have integrated the helpers number_with_delimiter and number_to_currency so the numbers are represented more attractively for the end user.
Right at the end, we still need to adapt a few flash messages in the controller app/controllers/books_controller.rb:
class BooksController < ApplicationController
  # GET /books
  # GET /books.json
  def index
    @books = Book.all

    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @books }
    end
  end

  # GET /books/1
  # GET /books/1.json
  def show
    @book = Book.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.json { render json: @book }
    end
  end

  # GET /books/new
  # GET /books/new.json
  def new
    @book = Book.new

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @book }
    end
  end

  # GET /books/1/edit
  def edit
    @book = Book.find(params[:id])
  end

  # POST /books
  # POST /books.json
  def create
    @book = Book.new(params[:book])

    respond_to do |format|
      if @book.save
        format.html { redirect_to @book, notice: I18n.t('views.book.flash_messages.book_was_successfully_created') }
        format.json { render json: @book, status: :created, location: @book }
      else
        format.html { render action: "new" }
        format.json { render json: @book.errors, status: :unprocessable_entity }
      end
    end
  end

  # PUT /books/1
  # PUT /books/1.json
  def update
    @book = Book.find(params[:id])

    respond_to do |format|
      if @book.update_attributes(params[:book])
        format.html { redirect_to @book, notice: I18n.t('views.book.flash_messages.book_was_successfully_updated') }
        format.json { head :no_content }
      else
        format.html { render action: "edit" }
        format.json { render json: @book.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /books/1
  # DELETE /books/1.json
  def destroy
    @book = Book.find(params[:id])
    @book.destroy

    respond_to do |format|
      format.html { redirect_to books_url }
      format.json { head :no_content }
    end
  end
end
Now you can use the views generated by the scaffold generator entirely in German. The structure of the YAML files shown here can of course be adapted to your own preferences. The texts in the views and the controller are displayed with I18n.t. At this point you could of course also integrate the German text directly if the application is purely in German.

Paths in German

Our bibliography is completely in German, but the URLs are still in English. If we want to make all books available at the URL http://0.0.0.0:3000/buecher instead of the URL http://0.0.0.0:3000/books then we need to add the following entry to the config/routes.rb:
Bibliography::Application.routes.draw do
  scope(:path_names => { :new => "neu", :edit => "bearbeiten" }) do
    resources :books, :path => "buecher"
  end
end
As a result, we then have the following new paths:
$ rake routes
    books GET    /buecher(.:format)                books#index
          POST   /buecher(.:format)                books#create
 new_book GET    /buecher/neu(.:format)            books#new
edit_book GET    /buecher/:id/bearbeiten(.:format) books#edit
     book GET    /buecher/:id(.:format)            books#show
          PUT    /buecher/:id(.:format)            books#update
          DELETE /buecher/:id(.:format)            books#destroy
$
The brilliant thing with Rails routes is that you do not need to do anything else. The rest is managed transparently by the routes.

Updates about this book will be published on my Twitter feed.