Rails GetText Syntax Overview
In this article, we will give you a quick overview of how to translate your Rails application with the GetText syntax. There is only a few (short-named) methods to remember, we will make you love underscores!
TL;DR
Singular | _("text") |
Plural | n_("singular text", "plural text", number) |
Singular with context | p_("context", "text") |
Plural with context | np_("context", "singular text", "plural text", number) |
Simple Texts
_
is the base method you will use everywhere.
In a controller :
class PostsController < ActionController::Base
def create
...
flash[:notice] = _('Post successfully saved')
redirect_to posts_path
end
end
In a view :
<h1><%= _('Create a New Post') %></h1>
<%= f.label :title, _('Title') %>
In a model validation :
class Post < ApplicationRecord
validates :title, :presence => {
:message => lambda { _('Title is mandatory.') }
}
end
Note that the call was wrapped into a lambda because otherwhise it would have been statically evaluated.
Interpolations
Interpolations can be easily performed using the standard %
operator.
Find some examples below.
_('Hi %{name}, do you like violence?') % { name: "Steve" }
_('%{city1} is bigger than %{city2}') % { city1: "NYC", city2: "BXL" }
Plural Forms
If the translatable string is depending on the number of items, use _n
.
It takes three arguments : the singular form, the plural form, and the
cardinality.
For example :
n_("There is a mouse in our house.", "There are some mice in our house.", @mice.size)
And you can of course combine with interpolations :
n_("There is a mouse.", "There are %{num} mice.", @mice.size) % { num: @mice.size }
Translation Contexts
Sometimes you would need to scope a given translation to a given part of you application. For instance in order to be able to translate a given string differently depending on the context.
There are two dedicated methods for that : p_
(singular with context)
and np_
(plural with context). They take the same arguments as _
and
n_
(respectively) with an extra string argument defining the context
added before.
Here is an example :
p_("Menu", "Open file")
p_("Menu", "Close file")
With plurals and interpolations :
np_("Menu", "Open a file", "Open %{n} files", 4) % { n: 4 }
If you are wondering why it is p for defining a context, that is because it stands for prefix.
Other Syntaxes
Some other special usages of GetText
can also be used but won't be detailed here because they are very specific
(and not so useful) : s_
, ns_
, N_
and Nn_
.
Dealing with YAML files and their Directory Structure
This article is about the way Translation.io deals with YAML files. Whether you prefer YAML over GetText or you use GetText but dealing with a few YAML keys is inevitable, this page is for you!
Since YAML doesn't guarantee that only a language is contained in a single file, we unfortunately are not able to mirror your source YAML structure.
Init & sync
Initializing your project implies :
- All .yml files containing keys of your source language will be kept.
- All .yml files containing keys of your target languages will be grouped
into one named
translation.LANGUAGE.yml
. - All localization keys will be separated into localization-specific files.
These files are named
localization.LANGUAGE.yml
.
Let's see this in details.
translation.xx.yml
files contain all the "textual" YAML keys from your
project. Once your translators translated them in Translation.io, you can
sync and they will be updated. You cannot edit them directly: they will be
overridden at each sync.
localization.xx.yml
files contain all the non-textual (date formats, booleans,
variables) keys that your translators will not understand. We populate them
with the correct values for the target languages when initializing the project.
These files are not synced with Translation.io. So you can update them
manually if you need to. You can also
configure other keys to be treated as localization.
Adding new keys
When a new key is needed:
- Add it to one of your source files.
- The next sync will send this new key to Translation.io and the key will be
added to all target
translation.xx.yml
files. - After the key is translated in Translation.io, one more sync and your application translations will be up-to-date.
Example
Let's say your project is in en and is translated to fr and es. And you have these files at the moment :
- config/locales/en.yml
- config/locales/stuff.en.yml
- config/locales/en/other.en.yml
- config/locales/fr.yml
- config/locales/stuff.fr.yml
- config/locales/es.yml
- config/locales/stuff.es.yml
After an init, you will have these files :
- config/locales/en.yml
- config/locales/stuff.en.yml
- config/locales/en/other.en.yml
- config/locales/translation.fr.yml
- config/locales/translation.es.yml
- config/locales/localization.fr.yml
- config/locales/localization.es.yml
Same file struture for source language and two files per target language for localization.
Why GetText is Better than Rails I18n
We have a strong opinion about how an application in general, and more especially a Rails application, should be internationalized. A lot of people use the default Rails Internationalization API (I18n) but we don't really think this is an ideal solution for big projects.
Our reasons.
1. It's painful to "make up" keys.
The welcome title of the main page may be as simple as t("home.title")
but you can end up with very verbose things like
t("on_call_schedules.contact_form.on_call_schedule_comment_placeholder")
.
2. Not trivial to search text in code.
When you want to change a text, you have to look for its key in the codebase, then find the text itself in the corresponding YAML file. It doesn't sound very practical to us.
With GetText, editing a text is just a matter of finding it and updating it, right in place. No need to switch between many files.
3. Difficult coevolution between keys and associated text.
You've found a nice, short and descriptive key name for a given text. A few weeks later the customer asks for a significant change in the source language of that text. In such a situation you also need to change the key according to its new signification. Sometimes you won't, or you will forget. Then you will probably end up with a key name not representative of its content.
4. Difficult to know if all the keys are really used in the application.
All the translation keys are located at the same place : in YAML file(s).
They are also organized as a tree. When the key is used in the code, it is
formatted with a dot syntax like my.key
. When you are in a YAML file, it's
not easy to perform a "find in project" with your editor to see if it is still
used or not. Because you have to convert the path in the tree to dot syntax
before.
With the GetText paradigm, unused strings will be automatically detected at
POT file generation. And with Translation.io you can
safely remove all unused strings with the rake translation:sync_and_purge
task.
5. YAML can contain values that are not translations.
Rails YAML files contain not only translation strings but also localization values (integers, arrays, booleans) in the same place and that's bad. For example : date formats, number separators, default currency or measure units, etc.
A translator is supposed to translate, not localize. That's not his role to choose how you want your dates or numbers to be displayed, right? Moreover, this special keys often contain special constructions (e.g., with percent signs or spaces) that he might break.
We think localization is part of the configuration of the app and it should not reach the translator UI at all. That's why these localization keys are detected and separated on a dedicated YAML file with Translation.io.
6. GetText is a standard in the Open-Source community.
GNU GetText has been around since 1995 and is the de-facto translation solution for many projects. So why not using it in Rails applications?
7. GetText syntax is nice.
GetText syntax looks optimal to us: short method names because used everywhere in the codebase and easy to remember.
However, YAML has some advantages too.
1. Pre-translated YAML files for Gems.
It's easier to ship the translatable strings of a Gem as a YAML file. No dependency at all because Rails ships with YAML. But if you don't code a gem, this argument is weak.
2. Allows to copywrite text in the source language.
Certainly the biggest argument in favor of YAML. You can use YAML keys in your code as placeholders for text and then allow some people (clients? marketing team?) to edit the text in all languages, including the source one, without changing the source code.
We allow this feature in Translation.io. It's called Copywriting
Conclusion
We propose two ways for translating your applications and both ways can be used at the same time without any issue. But we strongly encourage you to use GetText as your main workflow since your translation process will be way smoother.
The only exception is when you need to delegate the source edition of the text to an external person or team. In that specific case, use a well-named YAML key and use our copywriting feature.
Coming from FastGettext
If you are currently using FastGettext and/or gettext_i18n_rails to translate your Rails application and you would like to switch to Translation.io, this article will help you with the process of updating your codebase to make it work.
If you are not sure what you are doing, consider working in branch :-)
The Gemfile
The Gemfile
should only contain the translation
gem for all environments :
gem 'translation'
Make sure to remove any references to other gems like :
gem 'gettext_i18n_rails'
gem 'gettext', :require => false
gem 'ruby_parser', :require => false
gem 'gettext'
You don't have to include gettext
neither, translation
already depends
on its right version and will therefore include it for you.
Initializer
You probably have a fast_gettext.rb
or a gettext.rb
initializer file
containing this kind of configuration :
FastGettext.add_text_domain 'app', :path => 'config/gettext_locales',
:type => :po
FastGettext.default_available_locales = ['en', 'fr', 'de', 'nl']
FastGettext.default_text_domain = 'app'
You won't of course need it anymore.
When you create a project on
Translation.io, you will be
asked to create an initializer named translation.rb
:
TranslationIO.configure do |config|
config.api_key = 'xxxxxxxxxxxxxxxxxxxxxxxx'
config.source_locale = 'en'
config.target_locales = ['fr', 'de', 'nl']
# Uncomment this if you don't want to use gettext
# config.disable_gettext = true
# Uncomment this if you already use gettext or fast_gettext
# config.locales_path = File.join('path', 'to', 'gettext_locale')
end
There you have to correclty set config.target_locales
to the former
target locales.
It is very likely that your existing POT/PO files are not in
config/locales/gettext
. In that case, just move them there (keeping
the right structure) or set config.locales_path
to the correct base
location used by FastGettext
(usually Rails.root.join('locale')
).
Translation.io only supports
one unique textdomain
named app. If you were using multiple
textdomains, you will have to merge them. If your domain was not
named app you will have to rename all the PO/POT files to app.xx.
Controllers and before filter
You probably have a before_action
somewhere in your controller
hierarchy to set the current locale (I18n.locale
) according to the
URL, the session, a cookie, etc.
translation
ships with a
generic implementation
that can be called directly from all ActionController::Base
descendants :
before_action :set_locale
But you can of course continue to use your own method if you need custom behaviour by defining it. For instance :
before_action :set_locale
def set_locale
I18n.locale = current_user.locale || I18n.default_locale
end
That's it
After performing this set of changes, you should be ready to translate your rails applications with Translation.io. Don't hesitate to contact us if you think something is missing or not clear enough.