Linguist

Test Version on Hex.pm Hex Docs Hex.pm downloads License Latest commit GitHub top language

Linguist is a simple Elixir internationalization library.

Installation

Add :linguist to your mix.exs dependencies:

def deps do
  [
    {:linguist, "~> 0.6"}
  ]
end

Update your dependencies:

$ mix deps.get

Usage

Define a module that uses Linguist.Vocabulary, and add each locale with the locale/2 macro. Translations can be given as a keyword list, or as a path to an .exs file that evaluates to a keyword list, or to a .yml/.yaml file. They are compiled into functions on the module.

defmodule I18n do
  use Linguist.Vocabulary

  locale "en", [
    flash: [
      notice: [
        hello: "hello %{first} %{last}",
        bye: "bye now, %{name}!"
      ]
    ],
    users: [
      title: "Users",
      profiles: [
        title: "Profiles"
      ]
    ]
  ]

  locale "fr", Path.join([__DIR__, "fr.exs"])
  locale "es", Path.join([__DIR__, "es.yml"])
end

fr.exs:

[
  flash: [
    notice: [
      hello: "salut %{first} %{last}"
    ]
  ]
]

es.yml:

flash:
  notice:
    hello: "hola %{first} %{last}"

Look up translations by locale and dot-separated path, passing any interpolation bindings:

iex> I18n.t!("en", "flash.notice.hello", first: "chris", last: "mccord")
"hello chris mccord"

iex> I18n.t!("fr", "flash.notice.hello", first: "chris", last: "mccord")
"salut chris mccord"

iex> I18n.t("es", "flash.notice.hello", first: "Alex", last: "Smith")
{:ok, "hola Alex Smith"}

iex> I18n.t("en", "users.missing")
{:error, :no_translation}

iex> I18n.locales()
["es", "fr", "en"]

t/3 returns {:ok, translation} or {:error, :no_translation}, and t!/3 returns the translation or raises Linguist.NoTranslationError. Both raise KeyError if a binding used by the translation is missing. Use %%{ to include a literal %{ in a translation.

Loading YAML files at runtime

Linguist.MemorizedVocabulary loads YAML files at runtime rather than at compile time, and stores the translations in an ETS table:

iex> Linguist.MemorizedVocabulary.locale("es", "priv/locales/es.yml")
true

iex> Linguist.MemorizedVocabulary.t("es", "flash.notice.hello", first: "Alex", last: "Smith")
{:ok, "hola Alex Smith"}

The ETS table is owned by the process that loads the first locale, so load all the locales from a long-lived process, such as during application start. Locale names are normalized, so "ES" is looked up as "es", and "fr-fr" as "fr-FR".

Pluralization

When the bindings include the pluralization key, the translation is looked up under the plural category for that number, following the CLDR plural rules for the locale, such as one or other:

locale "en", [
  apple: [
    one: "%{count} apple",
    other: "%{count} apples"
  ]
]
iex> I18n.t!("en", "apple", count: 1)
"1 apple"

iex> I18n.t!("en", "apple", count: 2)
"2 apples"

Configuration

The pluralization key is required, and should be an atom:

config :linguist, pluralization_key: :count

With this config, translations are pluralized based on the count binding passed to t/3 or t!/3.

Pluralization uses ex_cldr, which only knows the en locale by default. Add every other locale you pluralize to the Linguist.Cldr backend:

config :linguist, Linguist.Cldr, locales: ["en", "es", "fr"]