# `MishkaGervaz.Messages`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.2/lib/mishka_gervaz/messages.ex#L1)

Provides Gettext macros with configurable backend support.

This module allows using Gettext macros while supporting a configurable
backend through the `:gettext_backend` option or application config.

## Configuration

### Option 1: Pass backend directly (recommended for gettext extraction)

    defmodule MyModule do
      use MishkaGervaz.Messages, backend: MishkaCmsCore.Gettext

      def my_function do
        dgettext("mishka_gervaz", "Load More")
      end
    end

### Option 2: Use application config (for runtime switching)

    config :mishka_gervaz, :gettext_backend, MyAppWeb.Gettext

Then:

    defmodule MyModule do
      use MishkaGervaz.Messages

      def my_function do
        dgettext("mishka_gervaz", "Load More")
      end
    end

## Translation Domain

All MishkaGervaz translations use the `mishka_gervaz` domain, except the messages a form field
type refuses a value with, which use the `errors` domain. Translation files should be placed at:

    priv/gettext/LOCALE/LC_MESSAGES/mishka_gervaz.po
    priv/gettext/LOCALE/LC_MESSAGES/errors.po

## Strings from the DSL

Every string a resource's `mishka_gervaz` DSL holds is translated when it is drawn, through
`translate_text/1`: labels, placeholders, prompts, `{label, value}` option labels, confirm
messages, empty and error states, notices, headers and footers. A function value is called as
it is and is not translated again. Mark a string with `dgettext_noop("mishka_gervaz", "...")` so
`mix gettext.extract` finds it.

## Work in another process

A new process has no Gettext locale of its own. Run work whose result holds words through
`start_async/4`, `assign_async/4` or `in_caller_locale/1` so it is translated in the locale of
the process that started it.

The same three carry the value of each process dictionary key registered with
`carry_process_keys/1`, so work that reads what its caller put in the process dictionary — the
tenant a page renders for, say — reads it in the other process too. None is registered by
default.

# `__using__`
*macro* 

Injects Gettext macros into the using module.

## Options

- `:backend` - The Gettext backend module to use. If not provided,
  falls back to the `:gettext_backend` config or `MishkaGervaz.Gettext`.

## Usage

    # With explicit backend (recommended for extraction)
    use MishkaGervaz.Messages, backend: MyApp.Gettext

    # With config-based backend
    use MishkaGervaz.Messages

# `assign_async`

```elixir
@spec assign_async(
  Phoenix.LiveView.Socket.t(),
  atom() | [atom()],
  (-&gt; {:ok, map()} | {:error, term()}),
  keyword()
) :: Phoenix.LiveView.Socket.t()
```

`Phoenix.LiveView.assign_async/4`, with `fun` run in the caller's Gettext locales and with its
values for the keys `carry_process_keys/1` registered (`in_caller_locale/1`).

# `carried_process_keys`

```elixir
@spec carried_process_keys() :: [term()]
```

The process dictionary keys `carry_process_keys/1` registered, in the order they were first
registered.

# `carry_process_keys`

```elixir
@spec carry_process_keys([term()]) :: :ok
```

Registers process dictionary keys whose values `in_caller_locale/1`, `start_async/4` and
`assign_async/4` carry into the work they start, beside the Gettext locales.

Call it when the application starts, with the keys its own functions read with `Process.get/1`.
Registering a key again changes nothing. A key the calling process holds no value for is not
carried, so the other process reads it as unset too.

## Examples

    MishkaGervaz.Messages.carry_process_keys([{MyApp.Tenant, :current}])

    Process.put({MyApp.Tenant, :current}, "acme")

    Task.async(MishkaGervaz.Messages.in_caller_locale(fn -> Process.get({MyApp.Tenant, :current}) end))
    |> Task.await()
    #=> "acme"

# `gettext_backend`

Gets the configured Gettext backend at runtime.

Returns the user-configured backend or `MishkaGervaz.Gettext` as default.

# `in_caller_locale`

```elixir
@spec in_caller_locale((-&gt; result) | (arg -&gt; result)) :: (-&gt; result) | (arg -&gt; result)
when result: term(), arg: term()
```

Wraps `fun`, of arity 0 or 1, to run in the Gettext locales of the process that calls this, with
the values it holds for the keys `carry_process_keys/1` registered.

Give the wrapped function to the work another process runs, such as `Task.async/1` or
`Task.async_stream/3`: a new process starts with no locale of its own, so words it translates are
otherwise in the default locale. The locale set with `Gettext.put_locale/1` and each one set with
`Gettext.put_locale/2` are carried. Both are read when `fun` is wrapped.

## Examples

    Gettext.put_locale(MyApp.Gettext, "fa")

    Task.async(MishkaGervaz.Messages.in_caller_locale(fn -> Gettext.get_locale(MyApp.Gettext) end))
    |> Task.await()
    #=> "fa"

# `start_async`

```elixir
@spec start_async(Phoenix.LiveView.Socket.t(), term(), (-&gt; term()), keyword()) ::
  Phoenix.LiveView.Socket.t()
```

`Phoenix.LiveView.start_async/4`, with `fun` run in the caller's Gettext locales and with its
values for the keys `carry_process_keys/1` registered (`in_caller_locale/1`).

Use it in place of `Phoenix.LiveView.start_async/4` wherever the result holds words a person
reads.

# `translate_text`

```elixir
@spec translate_text(term()) :: term()
```

Translates a string a resource declared in its `mishka_gervaz` DSL, in the locale of the process
that draws it.

A binary is looked up in the `mishka_gervaz` domain of `gettext_backend/0`: the translation when
the current locale has one, the string itself otherwise. `nil` and every other value are
returned unchanged, so a function or a number a DSL option held passes through. A string holding
`%{...}` is returned as it is.

A resource marks a string only its DSL holds with `dgettext_noop("mishka_gervaz", "...")`, which
returns the English string; MishkaGervaz translates it each time it is drawn.

    label dgettext_noop("mishka_gervaz", "Heading")

## Examples

    iex> MishkaGervaz.Messages.translate_text("No translation for this")
    "No translation for this"

    iex> MishkaGervaz.Messages.translate_text(nil)
    nil

---

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