# `VerifiedTranslations`
[🔗](https://github.com/treathealth/verified_translations/blob/v0.1.1/lib/verified_translations.ex#L1)

Compile-time verified translations for Phoenix.

Like `Phoenix.VerifiedRoutes` does for paths, the `~t` sigil verifies at
compile time that every message is translated to every configured locale,
and fails compilation otherwise:

    use VerifiedTranslations

    ~t"Welcome back"
    ~t"Hello #{name}"       # source "Hello %{name}", binding name
    ~t"Hello #{@user.name}" # source "Hello %{user_name}"

Each message lives in its own JSON file holding all locales, so parallel
branches, worktrees and coding agents never conflict on translation files:

    // priv/translations/default/hello-name-<128-bit hash>.json
    {
      "msgid": "Hello %{name}",
      "fi": "Hei %{name}",
      "sv": "Hej %{name}"
    }

Translations are inlined into the calling module at compile time, so there
is no runtime lookup table and no Gettext backend to recompile. Editing a
JSON file recompiles only the modules that use that message.

## Configuration

    config :verified_translations,
      locales: ["en", "fi", "sv"],
      source_locale: "en",            # language of the text in ~t
      path: "priv/translations",      # relative to the project root
      create_missing: true            # write stub files for new messages

`use VerifiedTranslations` accepts the same keys (plus `:domain`, default
`"default"`) to override the config per module.

## Locale

The current locale is per process, like Gettext's:

    VerifiedTranslations.put_locale("fi")
    VerifiedTranslations.with_locale("sv", fn -> ~t"Hello #{name}" end)

Unknown or unset locales render the source text.

# `get_locale`

```elixir
@spec get_locale(module() | nil) :: String.t() | nil
```

Returns the locale of the current process.

Falls back to Gettext's locale (of `gettext_backend` if given) when
`put_locale/1` was not called, so apps that already set the Gettext locale
keep working. Returns `nil` without Gettext.

# `known_locales`

```elixir
@spec known_locales() :: [String.t()]
```

Configured locales, including the source locale.

# `put_locale`

```elixir
@spec put_locale(String.t()) :: :ok
```

Sets the locale of the current process.

When Gettext is available, also sets Gettext's global locale (and the
configured `:gettext_backend`'s locale) so `gettext`/`ngettext` calls stay
in the same language.

# `sigil_t`
*macro* 

Translates the text to the current locale, verified at compile time.

Interpolated variables become bindings named after their access path:
`#{name}` and `#{@name}` are `%{name}`, `#{@user.name}` is `%{user_name}`.

# `tn`
*macro* 

Translates a plural message, choosing the CLDR plural category of `count`
for the current locale.

    tn(length(files), one: "%{count} file", other: "%{count} files")
    tn(@n, one: "#{@user.name} has one item", other: "#{@user.name} has %{count} items")

The forms must be exactly the source locale's CLDR categories (`one` and
`other` for English). `%{count}` is the first argument; other values are
interpolated like in `~t`. The message file holds one object of forms per
locale, verified to have exactly that locale's categories:

    {"kind": "plural", "msgid": "%{count} files",
     "fi": {"one": "%{count} tiedosto", "other": "%{count} tiedostoa"}}

`~t"#{count} file(s)"N` (gettext_sigils compatible) is the same with one
source text for every category.

Ordinals use CLDR's ordinal categories and are stored as `"kind": "ordinal"`:

    tn(place, ordinal: true, one: "%{count}st", two: "%{count}nd", few: "%{count}rd", other: "%{count}th")

# `with_locale`

```elixir
@spec with_locale(String.t(), (-&gt; result)) :: result when result: term()
```

Runs `fun` with `locale` as the current locale, restoring the previous one.

---

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