Localize.Translate (Localize Translate v0.1.0)

Copy Markdown View Source

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/2 return a translated copy of an entire struct, or a single field when the second argument is a field name.

  • translate/3 and translate!/3 return a single translated field with an explicit locale or locale fallback chain.

  • translatable?/2 reports 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, and QueryBuilder.translated/3. Each value is run through Localize.validate_locale/1 and unwrapped to its :cldr_locale_id atom. 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 :und root). For QueryBuilder, the chain is filtered to the locales declared in :locales so unsupported parents don't generate dead branches.

  • The current locale is Localize.get_locale/0. translate/1 and translate/2 (with a field name) default the locale from Localize.get_locale/0 and 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 the translations/1 macro 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
end

When 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
end

Reflection

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 (or nil if 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

field()

@type field() :: atom()

A struct field as an atom.

locale()

@type locale() :: atom() | String.t()

A locale identifier as an atom (for example :en, :"en-AU") or a string.

locale_list()

@type locale_list() :: locale() | [locale(), ...]

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.

translatable()

@type translatable() :: struct()

A translatable struct that uses Localize.Translate.

Functions

translatable?(module_or_translatable, field)

@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_translatable is either a module that uses Localize.Translate or a struct of that module.

  • field is the field name as an atom or string.

Returns

  • true if the given field is translatable.

  • false if 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

translate(translatable)

@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

Returns

  • The translated struct.

translate(translatable, locale_or_field)

@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

  • translatable is a struct that uses Localize.Translate.

  • locale_or_field is 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 :localize dependency is loaded, expanded into a fallback chain by walking the CLDR parent chain, or

    • an 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])

translate(translatable, field, locale)

@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

  • translatable is a struct that uses Localize.Translate.

  • field is the name of the translatable field as an atom.

  • locale is 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

translate!(translatable, field, locale)

@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

  • translatable is a struct that uses Localize.Translate.

  • field is the name of the translatable field as an atom.

  • locale is either a single locale or a list of locales acting as a fallback chain.

Returns

  • The translated value.

  • Raises a RuntimeError if 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

translations(field_name, translation_module \\ nil, locales_or_options \\ [])

(macro)

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_name is 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 to Translations. 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 :locales option on use Localize.Translate are used.

Options

  • :build_field_schema - when true (the default), generates the inner Translations.Fields module. Set to false if 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

translations(field_name, translation_module, locales, options)

(macro)

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_name is the name of the container field on the parent schema, as an atom.

  • translation_module is the alias name of the generated translation schema.

  • locales is the explicit list of locale atoms for which translation embeds are generated. The schema's :default_locale is excluded.

  • options is a keyword list of options, the same as accepted by translations/3.