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
  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 becomes 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
$

Buy the new Rails 5.1 version of this book.

If you know how Bundler works, you can also insert the line gem 'rails-i18n' into the file Gemfile and then execute bundle install. 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.
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:
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>
  <thead>
    <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>
  </thead>

  <tbody>
    <% @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, method: :delete, data: { confirm: I18n.t('views.are_you_sure')} %></td>
      </tr>
    <% end %>
  </tbody>
</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>
  <strong><%= Book.human_attribute_name(:title) %>:</strong>
  <%= @book.title %>
</p>

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

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

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

Buy the new Rails 5.1 version of this book.

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 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
  before_action :set_book, only: [:show, :edit, :update, :destroy]

  # GET /books
  # GET /books.json
  def index
    @books = Book.all
  end

  # GET /books/1
  # GET /books/1.json
  def show
  end

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

  # GET /books/1/edit
  def edit
  end

  # POST /books
  # POST /books.json
  def create
    @book = Book.new(book_params)

    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 action: 'show', status: :created, location: @book }
      else
        format.html { render action: 'new' }
        format.json { render json: @book.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /books/1
  # PATCH/PUT /books/1.json
  def update
    respond_to do |format|
      if @book.update(book_params)
        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.destroy
    respond_to do |format|
      format.html { redirect_to books_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_book
      @book = Book.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def book_params
      params.require(:book).permit(:title, :number_of_pages, :price)
    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
  resources :books, path: 'buecher', path_names: { new: 'neu', edit: 'editieren' }
end
As a result, we then have the following new paths:
$ rake routes
   Prefix Verb   URI Pattern                      Controller#Action
    books GET    /buecher(.:format)               books#index
          POST   /buecher(.:format)               books#create
 new_book GET    /buecher/neu(.:format)           books#new
edit_book GET    /buecher/:id/editieren(.:format) books#edit
     book GET    /buecher/:id(.:format)           books#show
          PATCH  /buecher/:id(.:format)           books#update
          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 routing engine.

Thank you for your support and the visibility by linking to this website on Twitter and Facebook. That helps a lot!