# `Localize.Translate`
[🔗](https://github.com/elixir-localize/localize_translate/blob/v0.1.0/lib/localize/translate.ex#L1)

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`](https://hex.pm/packages/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.

# `field`

```elixir
@type field() :: atom()
```

A struct field as an atom.

# `locale`

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

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

# `locale_list`

```elixir
@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`

```elixir
@type translatable() :: struct()
```

A translatable struct that uses `Localize.Translate`.

# `translatable?`

```elixir
@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](#module-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`

```elixir
@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

* `translatable` is a struct that uses `Localize.Translate`.

### Returns

* The translated struct.

# `translate`

```elixir
@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`

```elixir
@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!`

```elixir
@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`
*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`
*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`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
