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
- Implement a
Translatableadapter and register it viaai_translatables/0on itsPhoenixKit.Module. - From a form LV:
subscribe/1, then on a button click callenqueue/1(orenqueue_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.
Summary
Functions
Is AI translation usable right now? PhoenixKitAI loaded + enabled +
at least one enabled endpoint configured. Hosts gate the UI on this.
Default endpoint UUID: the ai_translation_endpoint_uuid setting, else the first enabled endpoint, else nil.
Is the shared default translation prompt already provisioned?
Default prompt UUID: the ai_translation_prompt_uuid setting, else the shared phoenixkit-translate-content prompt, else nil.
Enqueue one translation job for a single (resource, target_lang).
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).
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.
Configured AI endpoints as [{uuid, name}]. Empty when unavailable.
Configured AI prompts as [{uuid, name}]. Empty when unavailable.
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 to the global translation topic.
Subscribe to a single resource's translation topic.
The global AI-translation status topic.
Per-resource AI-translation status topic.
Types
Functions
@spec available?() :: boolean()
Is AI translation usable right now? PhoenixKitAI loaded + enabled +
at least one enabled endpoint configured. Hosts gate the UI on this.
@spec default_endpoint_uuid() :: String.t() | nil
Default endpoint UUID: the ai_translation_endpoint_uuid setting, else the first enabled endpoint, else nil.
@spec default_prompt_exists?() :: boolean()
Is the shared default translation prompt already provisioned?
@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 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.
@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).
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.
Configured AI endpoints as [{uuid, name}]. Empty when unavailable.
Configured AI prompts as [{uuid, name}]. Empty when unavailable.
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).
@spec subscribe() :: :ok | {:error, term()}
Subscribe to the global translation topic.
Subscribe to a single resource's translation topic.
@spec topic() :: String.t()
The global AI-translation status topic.
Per-resource AI-translation status topic.