# `PhoenixKit.Modules.AI.Translations`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.130/lib/modules/ai/translations.ex#L1)

Core orchestration for AI-driven translation — the shared layer every
feature module enqueues against instead of re-implementing its own
context + worker.

Pairs with:

- `PhoenixKit.Modules.AI.Translation` — the engine (the AI call + parse).
- `PhoenixKit.Modules.AI.Translatable` — the per-module adapter behaviour.
- `PhoenixKit.Modules.AI.TranslateWorker` — the generic Oban worker this
  module enqueues.

## What a consumer does

1. Implement a `Translatable` adapter and register it via
   `ai_translatables/0` on its `PhoenixKit.Module`.
2. From a form LV: `subscribe/1`, then on a button click call
   `enqueue/1` (or `enqueue_all_missing/2`), and react to the
   `{:ai_translation, event, payload}` messages.

Everything else — the AI call, parsing, persistence (via the adapter),
retry policy, broadcasts, and the unified audit log — is shared.

## Status messages

The worker broadcasts `{:ai_translation, event, payload}` where `event`
is `:translation_started | :translation_completed | :translation_failed`.
`payload` always has `:resource_type`, `:resource_uuid`, `:source_lang`,
`:target_lang`. `:translation_completed` adds `:fields` (the translated
`%{field => text}` map, possibly empty); `:translation_failed` adds
`:reason`.

# `enqueue_params`

```elixir
@type enqueue_params() :: %{
  :resource_type =&gt; String.t(),
  :resource_uuid =&gt; String.t(),
  :endpoint_uuid =&gt; String.t(),
  :prompt_uuid =&gt; String.t(),
  :source_lang =&gt; String.t(),
  :target_lang =&gt; String.t(),
  optional(:actor_uuid) =&gt; String.t() | nil
}
```

# `available?`

```elixir
@spec available?() :: boolean()
```

Is AI translation usable right now? `PhoenixKitAI` loaded + enabled +
at least one enabled endpoint configured. Hosts gate the UI on this.

# `default_endpoint_uuid`

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

Default endpoint UUID: the `ai_translation_endpoint_uuid` setting, else the first enabled endpoint, else nil.

# `default_prompt_exists?`

```elixir
@spec default_prompt_exists?() :: boolean()
```

Is the shared default translation prompt already provisioned?

# `default_prompt_uuid`

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

Default prompt UUID: the `ai_translation_prompt_uuid` setting, else the shared `phoenixkit-translate-content` prompt, else nil.

# `enqueue`

```elixir
@spec enqueue(map()) :: {:ok, %{conflict?: boolean()}} | {:error, term()}
```

Enqueue one translation job for a single `(resource, target_lang)`.

Returns `{:ok, %{conflict?: boolean}}` (`conflict?: true` when an
identical job is already in flight) or `{:error, reason}` on a malformed
input map.

# `enqueue_all_missing`

```elixir
@spec enqueue_all_missing(map(), [String.t()]) ::
  {:ok,
   %{
     enqueued: non_neg_integer(),
     conflicts: non_neg_integer(),
     errors: [{String.t(), term()}],
     in_flight: [String.t()]
   }}
  | {:error, term()}
```

Enqueue one job per missing target language. `base_params` is
`enqueue_params` minus `:target_lang`. Returns
`{:ok, %{enqueued, conflicts, errors, in_flight}}` — `in_flight` is the
langs a host should mark spinning (newly enqueued + conflicts; never the
errored ones, since no broadcast will arrive to clear them).

# `ensure_default_prompt`

```elixir
@spec ensure_default_prompt() :: {:ok, struct()} | {:error, term()}
```

Idempotently provision the shared translation prompt. Returns
`{:ok, prompt}` (existing or freshly created) or `{:error, reason}`.
`{:error, :ai_not_installed}` when the plugin is unavailable.

# `list_endpoints`

```elixir
@spec list_endpoints() :: [{String.t(), String.t()}]
```

Configured AI endpoints as `[{uuid, name}]`. Empty when unavailable.

# `list_prompts`

```elixir
@spec list_prompts() :: [{String.t(), String.t()}]
```

Configured AI prompts as `[{uuid, name}]`. Empty when unavailable.

# `missing_languages`

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

Given the enabled base language codes, the primary code, and the langs
that already have a translation, return the still-missing base codes
(primary excluded — it's the source, never a target).

# `subscribe`

```elixir
@spec subscribe() :: :ok | {:error, term()}
```

Subscribe to the global translation topic.

# `subscribe`

```elixir
@spec subscribe(String.t(), String.t()) :: :ok | {:error, term()}
```

Subscribe to a single resource's translation topic.

# `topic`

```elixir
@spec topic() :: String.t()
```

The global AI-translation status topic.

# `topic`

```elixir
@spec topic(String.t(), String.t()) :: String.t()
```

Per-resource AI-translation status topic.

---

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