gettext v0.11.0 Gettext
The Gettext
module provides a
gettext-based API for working with
internationalized applications.
Using Gettext
To use Gettext
, a module that calls use Gettext
has to be defined:
defmodule MyApp.Gettext do
use Gettext, otp_app: :my_app
end
This automatically defines some macros in the MyApp.Gettext
module.
Here are some examples:
import MyApp.Gettext
# Simple translation
gettext "Here is the string to translate"
# Plural translation
ngettext "Here is the string to translate",
"Here are the strings to translate",
3
# Domain-based translation
dgettext "errors", "Here is the error message to translate"
Translations are looked up from .po
files. In the following sections we will
explore exactly what are those files before we explore the “Gettext API” in
detail.
Translations
Translations are stored inside PO (Portable Object) files, with a .po
extension. For example, this is a snippet from a PO file:
# This is a comment
msgid "Hello world!"
msgstr "Ciao mondo!"
PO files containing translations for an application must be stored in a
directory (by default it’s priv/gettext
) that has the following struture:
gettext directory
└─ locale
└─ LC_MESSAGES
├─ domain_1.po
├─ domain_2.po
└─ domain_3.po
Here, locale
is the locale of the translations (for example, en_US
),
LC_MESSAGES
is a fixed directory, and domain_i.po
are PO files containing
domain-scoped translations. For more information on domains, check out the
“Domains” section below.
A concrete example of such a directory structure could look like this:
priv/gettext
└─ en_US
| └─ LC_MESSAGES
| ├─ default.po
| └─ errors.po
└─ it
└─ LC_MESSAGES
├─ default.po
└─ errors.po
By default, Gettext expects translations to be stored under the priv/gettext
directory of an application. This behaviour can be changed by specifying a
:priv
option when using Gettext
:
# Look for translations in my_app/priv/translations instead of
# my_app/priv/gettext
use Gettext, otp_app: :my_app, priv: "priv/translations"
The translations directory specified by the :priv
option should be a directory
inside priv/
, otherwise some things (like mix compile.gettext
) won’t work
as expected.
Locale
At runtime, all gettext-related functions and macros that do not explicitely
take a locale as an argument read the locale from Gettext.get_locale/1
. The
locale can be set with Gettext.put_locale/2
. Locales are expressed as
strings (like "en"
or "fr"
); they can be arbitrary strings as long as they
match a directory name.
Gettext stores the locale per-process (in the process dictionary) and per
Gettext module. This means that Gettext.put_locale/2
must be called in every
new process in order to have the right locale available in that process. Pay
attention to this behaviour, since not setting the locale with
Gettext.put_locale/2
will not result in any errors when Gettext.get_locale/1
is called; the default locale will be returned instead.
Default locale
The default Gettext locale is "en"
. The value of the default locale for a
given Gettext module can be set in the configuration for the :otp_app
of
that Gettext module. For example, in the config/config.exs
file of the
my_app
application:
config :my_app, MyApp.Gettext, default_locale: "fr"
Gettext API
There are two ways to use gettext:
- using macros from your own gettext module, like
MyApp.Gettext
- using functions from the
Gettext
module
These two approaches are different and each one has its own use case.
Using macros
Each module that calls use Gettext
is usually referred to as a “Gettext
backend”, as it implements the Gettext.Backend
behaviour. When a module
calls use Gettext
, the following macros are automatically
defined inside it:
gettext/2
dgettext/3
ngettext/4
dngettext/5
Supposing the caller module is MyApp.Gettext
, the macros mentioned above
behave as follows:
gettext(msgid, bindings \\ %{})
- likeGettext.gettext(MyApp.Gettext, msgid, bindings)
dgettext(domain, msgid, bindings \\ %{})
- likeGettext.dgettext(MyApp.Gettext, domain, msgid, bindings)
ngettext(msgid, msgid_plural, n, bindings \\ %{})
- likeGettext.ngettext(MyApp.Gettext, msgid, msgid_plural, n, bindings)
dngettext(domain, msgid, msgid_plural, n, bindings \\ %{})
- likeGettext.dngettext(MyApp.Gettext, domain, msgid, msgid_plural, n, bindings)
Using macros is preferred as gettext is able to automatically sync the
translations in your code with PO files. This, however, imposes a constraint:
arguments passed to any of these macros have to be strings at compile
time. This means that they have to be string literals or somthing that
expands to a string literal at compile time (e.g., a module attribute like
@my_string "foo"
).
These are all valid uses of the gettext macros:
Gettext.put_locale MyApp.Gettext, "it"
MyApp.Gettext.gettext "Hello world"
#=> "Ciao mondo"
@msgid "Hello world"
MyApp.Gettext.gettext @msgid
#=> "Ciao mondo"
The gettext
/dgettext
/ngettext
/dngettext
macros raise an
ArgumentError
exception if they receive a msgid
or a msgid_plural
that
doesn’t expand to a string at compile time:
msgid = "Hello world"
MyApp.Gettext.gettext msgid
#=> ** (ArgumentError) msgid must be a string literal
Using compile-time strings isn’t always possible. For this reason,
the Gettext
module provides a set of functions as well.
Using functions
If compile-time strings cannot be used, the solution is to use the functions
in the Gettext
module instead of the macros described above. These functions
perfectly mirror the macro API, but they all expect a module name as the first
argument. This module has to be a module which calls use Gettext
. For example:
defmodule MyApp.Gettext do
use Gettext, otp_app: :my_app
end
Gettext.put_locale MyApp.Gettext, "pt_BR"
msgid = "Hello world"
Gettext.gettext(MyApp.Gettext, msgid)
#=> "Olá mundo"
While using functions from the Gettext
module yields the same results as
using macros (with the added benefit of dynamic arguments), all the
compile-time features mentioned in the previous section are lost.
Domains
The dgettext
and dngettext
functions/macros also accept a domain as one
of the arguments. The domain of a translation is determined by the name of the
PO file that contains that translation. For example, the domain of
translations in the it/LC_MESSAGES/errors.po
file is "errors"
, so those
translations would need to be retrieved with dgettext
or dngettext
:
MyApp.Gettext.dgettext "errors", "Error!"
#=> "Errore!"
When gettext
or ngettext
are used, the "default"
domain is used.
Interpolation
All *gettext
functions and macros provided by gettext support interpolation.
Interpolation keys can be placed in msgid
s or msgid_plural
s with by
enclosing them in %{
and }
, like this:
"This is an %{interpolated} string"
Interpolation bindings can be passed as an argument to all of the *gettext
functions/macros. For example, given the following PO file for the "it"
locale:
msgid "Hello, %{name}!"
msgstr "Ciao, %{name}!"
interpolation can be done like follows:
Gettext.put_locale MyApp.Gettext, "it"
MyApp.Gettext.gettext "Hello, %{name}!", name: "Meg"
#=> "Ciao, Meg!"
Interpolation keys that are in a string but not in the provided bindings
result in a Gettext.Error
exception:
MyApp.Gettext.gettext "Hello, %{name}!"
#=> ** (Gettext.Error) missing interpolation keys: name
Keys that are in the interpolation bindings but that don’t occur in the string are ignored. Interpolations in gettext are often expanded at compile time, ensuring a low performance cost when running them at runtime.
Pluralization
Pluralization in gettext for Elixir works very similar to how pluralization
works in GNU gettext. The *ngettext
functions/macros accept a msgid
, a
msgid_plural
and a count of elements; the right translation is chosen based
on the pluralization rule for the given locale.
For example, given the following snippet of PO file for the "it"
locale:
msgid "One error"
msgid_plural "%{count} errors"
msgstr[0] "Un errore"
msgstr[1] "%{count} errori"
the ngettext
macro can be used like this:
Gettext.put_locale MyApp.Gettext, "it"
MyApp.Gettext.ngettext "One error", "%{count} errors", 3
#=> "3 errori"
The %{count}
interpolation key is a special key since it gets replaced by
the number of elements argument passed to *ngettext
, like if the count: 3
key-value pair were in the interpolation bindings. Hence, never pass the
count
key in the bindings:
# `count: 4` is ignored here
MyApp.Gettext.ngettext "One error", "%{count} errors", 3, count: 4
#=> "3 errori"
You can specify a “pluralizer” module via the :plural_forms
option in the
configuration for the :gettext
application.
# config/config.exs
config :gettext, plural_forms: MyApp.Plural
To learn more about pluralization rules, plural forms and what they mean to
Gettext check the documentation for Gettext.Plural
.
Missing translations
When a translation is missing in the specified locale (both with functions as well as with macros), the argument is returned:
- in case of calls to
gettext
/dgettext
, themsgid
argument is returned as is; - in case of calls to
ngettext
/dngettext
, themsgid
argument is returned in case of a singular value and themsgid_plural
is returned in case of a plural value (following the English pluralization rule).
For example:
Gettext.put_locale MyApp.Gettext, "foo"
MyApp.Gettext.gettext "Hey there"
#=> "Hey there"
MyApp.Gettext.ngettext "One error", "%{count} errors", 3
#=> "3 errors"
Empty translations
When a msgstr
is empty (""
), the translation is considered missing and the
behaviour described above for missing translation is applied. A plural
translation is considered to have an empty msgstr
if at least one
translation in the msgstr
is empty.
Compile-time features
As mentioned above, using the gettext macros (as opposed to functions) allows
gettext to operate on those translations at compile-time. This can be used
to extract translations from the source code into POT files automatically
(instead of having to manually add translations to POT files when they’re added
to the source code). The gettext.extract
does exactly this: whenever there
are new translations in the source code, running gettext.extract
syncs the
existing POT files with the changed code base. Read the documentation for
Mix.Tasks.Gettext.Extract
for more information on the extraction process.
POT files are just template files and the translations in them do not actually contain translated strings. A POT file looks like this:
# The msgstr is empty
msgid "hello, world"
msgstr ""
Whenever a POT file changes, it’s likely that developers (or translators) will
want to update the corresponding PO files for each locale. To do that, gettext
provides the gettext.merge
Mix task. For example, running:
mix gettext.merge priv/gettext --locale pt_BR
will update all the PO files in priv/gettext/pt_BR/LC_MESSAGES
with the new
version of the POT files in priv/gettext
. Read more about the merging
process in the documentation for Mix.Tasks.Gettext.Merge
.
Finally, gettext is able to recompile modules that call use Gettext
whenever
PO files change. To enable this feature, the :gettext
compiler needs to be
added to the list of Mix compilers. In mix.exs
:
def project do
[compilers: [:gettext] ++ Mix.compilers]
end
Options
The following is a comprehensive list of options that can be passed to use
Gettext
.
defmodule MyApp.Gettext do
use Gettext, # options
end
:otp_app
(required) - an atom representing an OTP application. Translations will be searched in directories inside this application’s directory (priv/gettext
by default, see the:priv
option).:priv
- a string representing a directory where translations will be searched. The directory is relative to the directory of the application specified by the:otp_app
option. It is recommended to always have this directory inside"priv"
, otherwise some features like the “mix compile.gettext” won’t work as expected.:plural_forms
- a module which will act as a “pluralizer”. For more information, look at the documentation forGettext.Plural
.
Configuration
You can configure :gettext
like any other application:
# config/config.exs
config :gettext,
...
The following is a list of the configuration options that :gettext
supports:
:fuzzy_threshold
- the default threshold for the Jaro distance measuring the similarity of translations. Look at the documentation for themix gettext.merge
task (Mix.Tasks.Gettext.Merge
) for more information on fuzzy translations.:excluded_refs_from_purging
- a regex that is matched against translation references. Gettext will preserve all translations in all POT files that have a matching reference. You can use this pattern to prevent Gettext from removing translations that you have extracted using another tool.
Backend configuration
A Gettext backend such as MyApp.Gettext
can be configured under the
MyApp.Gettext
key of the application specified in the :otp_app
option for
that backend. For example, the following backend:
defmodule MyApp.Gettext do
use Gettext, otp_app: :my_app
end
can be configured like this:
# config/config.exs
config :my_app, MyApp.Gettext,
default_locale: "fr"
The following is a list of the configuration options supported by each backend:
:default_locale
- a binary which specifies the default locale to use for the given backend.
Summary
Functions
Returns the translation of the given string in the given domain
Returns the pluralized translation of the given string in the given domain
Gets the locale for the current process and the given backend
Returns the translation of the given string in the "default"
domain
Returns all the locales for which PO files exist for the given backend
Returns the pluralized translation of the given string in the "default"
domain
Sets the locale for the current process and the given backend
Runs fun
with the gettext locale set to locale
for the given backend
Types
Functions
Specs
dgettext(module, binary, binary, bindings) :: binary
Returns the translation of the given string in the given domain.
The string is translated by the backend
module.
The translated string is interpolated based on the bindings
argument. For
more information on how interpolation works, refer to the documentation of the
Gettext
module.
If the translation for the given msgid
is not found, the msgid
(interpolated if necessary) is returned.
Examples
defmodule MyApp.Gettext do
use Gettext, otp_app: :my_app
end
Gettext.put_locale(MyApp.Gettext, "it")
Gettext.dgettext(MyApp.Gettext, "errors", "Invalid")
#=> "Non valido"
Gettext.dgettext(MyApp.Gettext, "errors", "%{name} is not a valid name", name: "Meg")
#=> "Meg non è un nome valido"
Gettext.dgettext(MyApp.Gettext, "alerts", "nonexisting")
#=> "nonexisting"
Specs
dngettext(module, binary, binary, binary, non_neg_integer, bindings) :: binary
Returns the pluralized translation of the given string in the given domain.
The string is translated and pluralized by the backend
module.
The translated string is interpolated based on the bindings
argument. For
more information on how interpolation works, refer to the documentation of the
Gettext
module.
If the translation for the given msgid
and msgid_plural
is not found, the
msgid
or msgid_plural
(based on n
being singular or plural) is returned
(interpolated if necessary).
Examples
defmodule MyApp.Gettext do
use Gettext, otp_app: :my_app
end
Gettext.dngettext(MyApp.Gettext, "errors", "Error", "%{count} errors", 3)
#=> "3 errori"
Gettext.dngettext(MyApp.Gettext, "errors", "Error", "%{count} errors", 1)
#=> "Errore"
Gets the locale for the current process and the given backend.
This function returns the value of the locale for the current process and the
given backend
. If there is no locale for the current process and the given
backend, the default locale is set as the locale for the current process and
the given backend and then returned. For more information on the default
locale and how it can be set, refer to the documentation of the Gettext
module.
Examples
Gettext.get_locale(MyApp.Gettext)
#=> "en"
Specs
gettext(module, binary, bindings) :: binary
Returns the translation of the given string in the "default"
domain.
Works exactly like:
Gettext.dgettext(backend, "default", msgid, bindings)
Returns all the locales for which PO files exist for the given backend
.
If the translations directory for the given backend doesn’t exist, then an empty list is returned.
Examples
With the following backend:
defmodule MyApp.Gettext do
use Gettext, otp_app: :my_app
end
and the following translations directory:
my_app/priv/gettext
├─ en
├─ it
└─ pt_BR
then:
Gettext.known_locales(MyApp.Gettext)
#=> ["en", "it", "pt_BR"]
Specs
ngettext(module, binary, binary, non_neg_integer, bindings) :: binary
Returns the pluralized translation of the given string in the "default"
domain.
Works exactly like:
Gettext.dngettext(backend, "default", msgid, msgid_plural, n, bindings)
Sets the locale for the current process and the given backend
.
The locale is stored in the process dictionary. locale
must be a string; if
it’s not, an ArgumentError
exception is raised.
Examples
Gettext.put_locale(MyApp.Gettext, "pt_BR")
#=> nil
Gettext.get_locale(MyApp.Gettext)
#=> "pt_BR"
Runs fun
with the gettext locale set to locale
for the given backend
.
This function just sets the Gettext locale for backend
to locale
before
running fun
and sets it back to its previous value afterwards. Note that
put_locale/2
is used to set the locale, which is thus set only for the
current process (keep this in mind if you plan on spawning processes inside
fun
).
The value returned by this function is the return value of fun
.
Examples
Gettext.put_locale(MyApp.Gettext, "fr")
MyApp.Gettext.gettext("Hello world")
#=> "Bonjour monde"
Gettext.with_locale MyApp.Gettext, "it", fn ->
MyApp.Gettext.gettext("Hello world")
end
#=> "Ciao mondo"
MyApp.Gettext.gettext("Hello world")
#=> "Bonjour monde"