Manage translations embedded into translation structs.
Although it can be used with any struct Localize.Translate shines when paired with an
Ecto.Schema. It allows you to keep the translations into a field of the schema and avoids
requiring extra tables for translation storage and complex joins when retrieving translations
from the database.
The runtime API lives on Localize.Translate itself:
translate/1,translate/2return a translated copy of an entire struct, or a single field when the second argument is a field name.translate/3andtranslate!/3return a single translated field with an explicit locale or locale fallback chain.translatable?/2reports whether a field is declared as translatable.
For Ecto.Query support, Localize.Translate.QueryBuilder provides the translated/3
and translated_as/3 macros (requires Ecto.SQL).
Locale handling
Localize.Translate builds on :localize for
CLDR-aware locale handling. Three properties follow:
All locale inputs validate. Atoms (
:en), strings ("en"), and%Localize.LanguageTag{}structs are accepted anywhere a locale is expected — in:locales,translate/N, andQueryBuilder.translated/3. Each value is run throughLocalize.validate_locale/1and unwrapped to its:cldr_locale_idatom. Invalid locale names raise.Fallbacks walk the CLDR parent chain. A locale expands into a fallback chain by walking parents (e.g.
:"en-AU"→:"en-001"→:en, stopping before the:undroot). ForQueryBuilder, the chain is filtered to the locales declared in:localesso unsupported parents don't generate dead branches.The current locale is
Localize.get_locale/0.translate/1andtranslate/2(with a field name) default the locale fromLocalize.get_locale/0and walk its parents.
Mixed :locales lists like [:en, %Localize.LanguageTag{cldr_locale_id: :fr}] are
supported. After validation each entry is reduced to an atom, deduplicated, and
sorted.
When used, Localize.Translate accepts the following options:
:translates(required) - list of the fields that will be translated.:locales(optional) - the list of locales for which translations will be stored. Required if you use thetranslations/1macro to auto-generate the embedded schema. Locales are atoms (for example:en,:"en-AU").:default_locale(optional) - declares the locale of the base untranslated columns. The fields in the main schema are considered to be in this locale, and no separate translation embed is generated for it.:container(optional) - the name of the field that holds the translations. Defaults to:translations.
Structured translations
Structured translations are the preferred and recommended way of using Localize.Translate. To
use structured translations, use the translations/1 macro when defining a schema. The
translations/1 macro will define an embedded schema with the name being the parameter passed
to the translations/1 macro. Within the :translations field that is generated, an
embeds_one/2 call is added for each non-default locale in :locales. Here's an example
schema configuration:
defmodule MyApp.Article do
use Ecto.Schema
use Localize.Translate,
translates: [:title, :body],
locales: [:en, :es, :fr],
default_locale: :en
schema "articles" do
field :title, :string
field :body, :string
translations :translations
end
endWhen expanded by the Elixir compiler, the example above will look like the following code. It
is provided here only as an example to show what the translations/1 macro compiles to.
defmodule MyApp.Article do
use Ecto.Schema
schema "articles" do
field :title, :string
field :body, :string
embeds_one :translations, Translations, on_replace: :update, primary_key: false do
embeds_one :es, MyApp.Article.Translations.Fields
embeds_one :fr, MyApp.Article.Translations.Fields
end
end
end
defmodule MyApp.Article.Translations.Fields do
use Ecto.Schema
@primary_key false
embedded_schema do
field :title, :string
field :body, :string
end
endReflection
Any module that uses Localize.Translate will have an autogenerated __trans__ function that
can be used for runtime introspection of the translation metadata.
__trans__(:fields)- Returns the list of translatable fields.__trans__(:container)- Returns the name of the translation container.__trans__(:locales)- Returns the configured locales (ornilif not given).__trans__(:default_locale)- Returns the default locale. The fields in the main schema are considered to be in this locale.
Summary
Types
A struct field as an atom.
A locale identifier as an atom (for example :en, :"en-AU") or a string.
When translating or querying either a single locale or a list of locales can be provided.
A translatable struct that uses Localize.Translate.
Functions
Checks whether the given field is translatable or not.
Translates a whole struct into the current locale.
Translates a whole struct (or a single field) into the given locale.
Translates a single field into the given locale.
Translates a single field into the given locale, raising if no translation is available.
Defines the embedded translation container field on an Ecto.Schema.
Defines the embedded translation container field with explicit locales.
Types
@type field() :: atom()
A struct field as an atom.
A locale identifier as an atom (for example :en, :"en-AU") or a string.
When translating or querying either a single locale or a list of locales can be provided.
A list of locales acts as a fallback chain: each locale is tried in order until a translation is found.
@type translatable() :: struct()
A translatable struct that uses Localize.Translate.
Functions
@spec translatable?(module() | translatable(), String.t() | atom()) :: boolean()
Checks whether the given field is translatable or not.
Returns true if the given field is translatable. Raises if the given module or struct does not
use Localize.Translate.
Arguments
module_or_translatableis either a module that usesLocalize.Translateor a struct of that module.fieldis the field name as an atom or string.
Returns
trueif the given field is translatable.falseif the given field is not translatable.
Examples
Assuming the Article schema defined in Structured translations.
iex> Localize.Translate.translatable?(Article, :title)
true
iex> article = %Article{}
iex> Localize.Translate.translatable?(article, :not_existing)
false
iex> Localize.Translate.translatable?(Date, :day)
** (RuntimeError) Elixir.Date must use `Localize.Translate` in order to be translated
@spec translate(translatable()) :: translatable()
Translates a whole struct into the current locale.
Equivalent to translate(translatable, locale) with locale resolved from
Localize.get_locale/0 when the optional :localize dependency is loaded, falling back
to the schema's :default_locale otherwise.
Arguments
translatableis a struct that usesLocalize.Translate.
Returns
- The translated struct.
@spec translate(translatable(), locale_list() | field() | struct()) :: translatable() | any()
Translates a whole struct (or a single field) into the given locale.
Returns the struct with every translatable field — and every translatable association or
embed — replaced by the value for the requested locale, falling back to the default
value when the locale has no entry. If the second argument is the name of a translatable
field, returns that single translated field using the current locale (see translate/3
for an explicit locale).
Arguments
translatableis a struct that usesLocalize.Translate.locale_or_fieldis one of:an atom or string locale (e.g.
:es,"fr"),a list of locales acting as a fallback chain,
a
%Localize.LanguageTag{}— when the optional:localizedependency is loaded, expanded into a fallback chain by walking the CLDR parent chain, oran atom field name — translates a single field using the current locale (see
translate/3).
Returns
- The translated struct, or the translated value of a single field.
Examples
# Translate the entire article into Spanish
Localize.Translate.translate(article, :es)
# Fallback chain — Deutsch missing, Spanish wins
Localize.Translate.translate(article, [:de, :es])
@spec translate(translatable(), field(), locale_list() | struct()) :: any()
Translates a single field into the given locale.
Looks up the field's translation in the given locale (or first match in a fallback chain) and returns it. Returns the base value if no translation is available.
Arguments
translatableis a struct that usesLocalize.Translate.fieldis the name of the translatable field as an atom.localeis either a single locale or a list of locales acting as a fallback chain.
Returns
- The translated value, or the value from the base column when no translation exists for any locale in the chain.
Examples
# Spanish title
Localize.Translate.translate(article, :title, :es)
# Unknown locale falls back to the base column
Localize.Translate.translate(article, :title, :de)
# Fallback chain
Localize.Translate.translate(article, :title, [:de, :es])Raises if the field isn't declared as translatable:
Localize.Translate.translate(article, :fake_attr, :es)
** (RuntimeError) 'Article' module must declare 'fake_attr' as translatable
@spec translate!(translatable(), field(), locale_list() | struct()) :: any()
Translates a single field into the given locale, raising if no translation is available.
Strict variant of translate/3. Where translate/3 falls back to the base value when a
translation is missing, translate!/3 raises so callers can distinguish "no
translation" from "translation equals the default".
Arguments
translatableis a struct that usesLocalize.Translate.fieldis the name of the translatable field as an atom.localeis either a single locale or a list of locales acting as a fallback chain.
Returns
The translated value.
Raises a
RuntimeErrorif no translation exists for any locale in the chain.
Examples
Localize.Translate.translate!(article, :title, :de)
** (RuntimeError) translation doesn't exist for field ':title' in locale :de
Defines the embedded translation container field on an Ecto.Schema.
Within an Ecto.Schema block, expands into an embeds_one/3 declaration for the named
container field plus auto-generated Translations and Translations.Fields embedded
schemas — one embeds_one per non-default locale, each holding the translatable string
fields declared in :translates.
Arguments
field_nameis the name of the container field on the parent schema, given as an atom (commonly:translations).translation_module(optional) is the alias name of the generated translation schema. Defaults toTranslations. Pass an alias to use a different module name under the parent.locales_or_options(optional) is either an explicit list of locale atoms, or a keyword list of options. When omitted, the locales configured via the:localesoption onuse Localize.Translateare used.
Options
:build_field_schema- whentrue(the default), generates the innerTranslations.Fieldsmodule. Set tofalseif you want to define that module yourself.
Examples
defmodule MyApp.Article do
use Ecto.Schema
use Localize.Translate,
translates: [:title, :body],
locales: [:en, :es, :fr],
default_locale: :en
schema "articles" do
field :title, :string
field :body, :string
translations :translations
end
end
Defines the embedded translation container field with explicit locales.
Four-arity form of translations/3. Use this when you want to override the configured
:locales option for a single schema, or to pass an options keyword list alongside an
explicit locale list.
Arguments
field_nameis the name of the container field on the parent schema, as an atom.translation_moduleis the alias name of the generated translation schema.localesis the explicit list of locale atoms for which translation embeds are generated. The schema's:default_localeis excluded.optionsis a keyword list of options, the same as accepted bytranslations/3.