Having a comprehensive test suite for a localisation library is difficult. The amount of different languages, regions and scripts with different rules makes it impossible for a single person to create that test suite - I can only speak so many languages! If you speak a language that has a non-ASCII script, is RTL or has some extravagant pluralisation rules, please add tests for them in a PR or open an issue with input and expected output so I can add them.
A new take on internationalisation in Elixir.
# Set the locale
Idiom.put_locale("en-US")
t("landing.welcome")
# With natural language key
t("Hello Idiom!")
# With interpolation
t("Good morning, {{name}}. We hope you are having a great day.", %{name: "Tim"})
# With plural and interpolation
# `count` is a magic option that automatically is available as binding.
t("You need to buy {{count}} carrots", count: 1)
# With namespace
t("Create your account", namespace: "signup")
Idiom.put_namespace("signup")
t("Create your account")
# With explicit locale
t("Create your account", to: "fr")
# With fallback key
t(["Create your account", "Register"])
# With fallback locale
t("Create your account", to: "fr", fallback: "en")
To start off, add idiom
to the list of your dependencies:
def deps do
{:idiom, "~> 0.1"},
end
Additionally, in order to be able refresh translations in the background, add Idiom's Supervisor
to your application:
def start(_type, _args) do
children = [
Idiom,
]
# ...
end
There are a few things around Idiom that you can configure on an application level. The following fence shows all of Idiom's settings and their defaults.
config :idiom,
default_locale: "en",
default_fallback: "en",
default_namespace: "default",
data_dir: "priv/idiom",
backend: nil
In order to configure your backend, please have a look at its module documentation.
When calling t/3
, Idiom looks at the following settings to determine which locale to translate the key to, in order of priority:
- The explicit
to
option. When you callt("key", to: "fr")
, Idiom will always usefr
as a locale. - The locale set in the current process. You can call
Idiom.put_locale/1
to set it. Since this is just a wrapper around the process dictionary, it needs to be set for each process you are using Idiom in. - The
default_locale
setting. See the Configuration section for more details on how to set it.
For ease of presentation, whenever an example in this module documentation includes a translation file for context, it will be merged from the multiple files that
Idiom.Source.Local
actually expects. Instead of giving you the contents of allen/default.json
,en-US/default.json
,en-GB/default.json
and others, it will be represented here as one merged file, such as:
{ "en": {"default": { [Contents of what would usually be `en/default.json` ] }}, "en-US": {"default": { [Contents of what would usually be `en-US/default.json` ] }}, ... }
Locale codes can consist of multiple parts. Taking zh-Hant-HK
as an example, we have the language (zh
- Chinese), the script (Hant
, Tradtional) and the
region (HK
- Hong Kong). For different regions, there might only be differences for some specific keys, whereas all other keys share a translation. In
order to prevent needless repetition in your translation workflow, Idiom will always try to resolve translations in all of language, language and script, and
language, script and region variants, in order of specifity.
Taking the following file as an example (see also File format):
{
"en": {
"default": {
"Create your account": "Create your account"
}
},
"en-US": {
"default": {
"Take the elevator": "Take the elevator"
}
},
"en-GB": {
"default": {
"Take the elevator": "Take the lift"
}
}
}
The Create your account
message is the same for both American and British English, whereas the key Take the elevator
has different wording for each.
With Idiom's resolution hierarchy, you can use both en-US
and en-GB
to refer to the Create your account
key as well.
iex> t("Take the elevator", to: "en-US")
"Take the elevator"
iex> t("Take the elevator", to: "en-GB")
"Take the lift"
# Will first try to resolve the key in the `en-US` locale, then, since it does not exist, try `en`.
iex> t("Create your account", to: "en-US")
"Create your account"
iex> t("Create your account", to: "en-GB")
"Create your account"
For scenarios where multiple keys might apply, t/3
allows specifying a list of keys as well.
t(["Create your account", "Register"], to: "en-US")
This snippet will first try to resolve the Create your account
key, and fall back to resolving Register
when it does not exist.
For when a key might not be available in the set locale, you can set a fallback locale.
A fallback can be either a string or a list of strings. If you set the fallback as a list, Idiom will return the translation of the first locale for which
the key is available.
When you don't explicitly set a fallback
for t/3
, Idiom will try the default_fallback
(see Configuration).
When a key is available in neither the target or any of the fallback language, the key will be returned as-is.
# will return the translation for `en`
t("Key that is only available in `en` and `fr`", to: "es", fallback: "en")
# will return the translation for `fr`
t("Key that is only available in `en` and `fr`", to: "es", fallback: ["fr", "en"])
# will return the translation for `en`, which is set as `default_fallback`
t("Key that is only available in `en` and `fr`", to: "es")
# will return "Key that is not available in any locale"
t("Key that is not available in any locale", to: "es")
When both fallback keys and locales are provided, Idiom will first try to resolve all keys in each locale before jumping to the next one.
For example, the resolution order for t(["Create your account", "Register"], to: "es", fallback: ["fr", "de"])
will be:
Create your account
ines
Register
ines
Create your account
infr
Register
infr
Create your account
inde
Register
inde
Idiom allows grouping your keys into namespaces.
When calling t/3
, Idiom looks at the following settings to determine which namespace to resolve the key in, in order of priority:
- The
namespace
option, liket("Create your account", namespace: "signup")
- The namespace set in the current process. You can call
Idiom.put_namespace/1
to set it.
Since this is just a wrapper around the process dictionary, it needs to be set for each process you are using Idiom in. - The
default_namespace
setting. See the Configuration section for more details on how to set it.
Idiom supports interpolation in messages.
Interpolation can be added by adding an interpolation key to the message, enclosing it in {{}}
. Then, you can bind the key to any string by passing it as
key inside the second parameter of t/3
.
Taking the following file as an example (see also File format):
{
"en": {
"default": {
"Welcome, {{name}}": "Welcome, {{name}}",
"It is currently {{temperature}} degrees in {{city}}": "It is currently {{temperature}} degrees in {{city}}"
}
}
}
These messages can then be interpolated as such:
iex> t("Welcome, {{name}}", %{name: "Tim"})
"Welcome, Tim"
iex> t("It is currently {{temperature}} degrees in {{city}}", %{temperature: "31", city: "Hong Kong"})
"It is currently 31 degrees in Hong Kong"
Idiom supports the following key suffixes for pluralisation:
zero
one
two
few
many
other
Your keys, for English, might then look like this:
{
"carrot_one": "{{count}} carrot"
"carrot_other": "{{count}} carrots"
}
You can then pluralise your messages by passing count
to t/3
, such as:
iex> t("carrot", count: 1)
"1 carrot"
iex> t("carrot", count: 2)
"2 carrots"
As you can see in the above example, we are not passing an extra
%{count: x}
binding. This is because thecount
option acts as a magic binding that is automatically available for interpolation.
Idiom is backend-agnostic and allows configuring different providers which are grouped under Idiom.Backend
.
Phrase Strings is supported inside the Idiom.Backend.Phrase
module.
Lokalise is supported inside the Idiom.Backend.Lokalise
module.