Post Format

Multilingual Websites with Ruby on Rails (2)

Part 2 – Variables, Validations, Model Classes

Web pages often contains sentences in which a single word is changing. For example, “You are logged in as xxxx.” For the translation, we can obviously assemble the sentence from the components. There is often a problem, because the order of the words is different in different languages .

A better solution is the so-called interpolation, as Ruby itself also offers. The string contains a marked variable, which will be replaced at run time. The Yaml file specification does not use the Ruby syntax. Instead, you mark the variable with double curly brackets.

# Definition
  logmsg: "You are logged in as {{name}}."
 
# call
I18n.translate(:logmsg, :name => 'Administrator')
# or short form
t :logmsg, :name => 'Administrator'

We need to make sure that the parameters of the definition and the call are matching. The translate()-method is quite tolerant: Superfluous parameters are ignored.

:!: Attention: the person who translates need to know that the variable names in the brackets must not be translated.

Another small trap lurks in the variable names: They may not have the name of one of the other parameters of the translate() method. Not allowed are therefore “default” and “scope”.

Texts of Validations

Even if our application is monolingual in another language than English, we get surprised in one area: The automatically generated error messages from the validation of the model classes. Very convenient, but unfortunately only in English.

Also, the messages contains the class name and attribute name from the ruby code directly. Classes and attribute names are chosen to serve the needs of the programmers. For users, other words are sometimes more appropriate.

You see the composition of such an error message in this picture:

Composion of the translation of an error message from the model validation in Rails

To display the validation message with texts in our local language, we need only the translations with the right keys in our translation Yaml file. The template sentences are defined in the area (scope) activerecord.messages. Since these are are equal for all applications, there exists already standard translations for many languages.

Model Translation

We define the names of the fields and objects in the Yaml file with the key activerecord.models.classname or activerecord.attributs.classname.attributname. Rails recognizes these keys and uses the translations correctly in the automatic validation notifications.

activerecord:
  models:
    term: "Begriff"
  attributes:
    term:
      name: "Name"
      topic: "Sachgebiet"

The central translation of class names and field names is also useful for the views. So we don’ t need to translate the the labels of input fields a second or third time.

For the view this does not always make sense. The user interface should not stick too narrow to the model. The important thing is the user.

Validations with own Messages

A typical use for a specific message is to check of a phone number. The methods for the specification of the validations have a parameter “message”.

So the translation should be no problem: we should just call t()-method, right? Unfortunately it is not that easy:

# funktioniert so nicht! Does not work!
validates_format_of( :fax,{
    :with => /\A[0-9 \+\-\/\(\)]+\Z/i,
    :message => t('activerecord.errors.messages.bad_phone'),
    :allow_blank => true})

Depending on the version and the deployment, Rails either did not even start or it does not delivers the required translation. What’s wrong?

The language is set for each request of the web user. But the declarative validations are defined as part of the model class, there is no request at this time! It therefore makes no sense to call a translate method in this stage.

But how do we solve this problem? Typical of Rails, the solution is already built in. But if you don’t know, you can go in round circles.

We can put a symbol rather than a string as the message parameter. Now the symbol will be evaluated at the time when the error message will be displayed.

The symbol gets evaluated in these specific contexts (scope) one after another:

  • activerecord.errors.models.[model_name].attributes.[attribute_name]
  • activerecord.errors.models.[model_name]
  • activerecord.errors.messages

Therefore we can simply write our validation like this:

validates_format_of( :fax, {
     :with => /\A[0-9\+\-\/\(\)]+\Z/i,
     :message => :bad_phone,
     :allow_blank => true } )

Sources:

Description (Guide) of the Internationalization API: guides.rubyonrails.org/i18n.html

Language files for standard translations: github.com/svenfuchs/rails-i18n/rails/locale

Author: Karsten Meier

Weil ich gerne Probleme löse bin ich Informatiker geworden. Für meine Kunden berate und konzeptioniere ich und entwickle mit fast allem, was einen Prozessor hat. Sie finden mich auch auf Twitter

One comment

  1. Hi,

    vielen Dank für den Tip mit den eigenen Fehlermeldungen bei der Validierung (kannte ich noch nicht).

    Du solltest vielleicht noch erwähnen, dass man die Übersetzungen in separaten Dateien verteilen kann.

    Und das die Verwendung der “activerecord.models.xxx” so XXX.human_name (wobei XXX das Model darstellen) und “activerecord.attributes.xxx.yyy” so XXX.human_attribute_name “yyy” verwendet werden kann.

    Viel Spass
    Martin

Leave a Reply