When I wrote a Phoenix app for the
first time, I quickly asked myself how to organize my translation
files. By default, all translations goes into
priv/gettext/[LOCALE]/LC_MESSAGES/default.po
and it can easily
become a big mess. So I started to search for a way to split my
translations by domains. It’s not a thing that is explained in the
(really good) Phoenix
guides.
Under the hood there’s gettext
It took me some time to figure out how to do it the right way and that’s mainly because I was searching through the Phoenix documentation when the real answer was available in the underlying lib that handle translations gettext and its Elixir bridge.
Gettext is used for translation since forever in free software world. It’s a battle tested library that has everything you need when it comes to translations. It can do simple translations, handle plural translations and domain-based translations.
Coming from Ruby and Rails world I’m really used to the ecosystem specific solution (a.k.a i18n gem and YAML files) to handle translation.
To be honest, when I started using Rails, I was wondering why the community wasn’t using gettext since it was the most standard i18n library I was aware of. It was used everywhere.
When I saw that Phoenix was using gettext I got a mixed feeling of “OMG back to this old lib” and “Yeah this good old gettext!”.
After using it a little bit I quickly told to myself “why do we reinvent the wheel when there’s something that good out there?”.
The format is easy to learn and there’s a bunch of tools available to translate strings.
Basic usage of gettext in Phoenix
gettext "Title"
searches for a key (msgid) named Title
in the
default namespace.
So in your .eex
file you’ll get something like:
<tr>
<th colspan="2"><%= gettext "Status" %></th>
<th><%= gettext "Title" %></th>
<th><%= gettext "Brand" %></th>
<th><%= gettext "Description" %></th>
</tr>
where gettext will search in the default namespace (default.po) for
msgid Status
, Title
, Brand
, and Description
. If no translation
is found for the current language then the string will be used as is.
To handle translation you have files with two extensions. The first
one is .pot
(e.g. priv/gettext/default.pot
), one per domain, which
is generated by scanning the app code and lists all keys. You then
have one .po
file per locale / domain (e.g.
priv/gettext/fr/LC_MESSAGES/default.po
), this is where you’ll
actually put the translations.
Custom domain
I’m pretty ashamed of having searched so much to find out how to use
custom domains / files for translations when the answer was pretty
much in the dgettext
function.
Gettext.dgettext(Api.Gettext, "additionals", "In progress")
The first argument is the backend used for translation. In a typical
Phoenix app it’ll be YourApp.Gettext
.
The second argument is the domain is which gettext will search. In our
example it means that the translations will be in
priv/gettext/additionals.pot
and
priv/gettext/[locale]/LC_MESSAGES/additionals.po
files. Yes the
domain is determined by the file name.
The third and last argument is the msgid
or in other words the key
and untranslated text.
Final words
I think that if your app becomes big enough, it’s a good strategy to divide your translations in multiple contextualized files to avoid collisions and ease translation process.
Hope you’ll find this useful.
Share on
Twitter Facebook LinkedInHave comments or want to discuss this topic?
Send an email to ~bounga/public-inbox@lists.sr.ht