Public API for AI-driven translation of Project, Task, and
Assignment resources. Wraps Oban enqueuing on
PhoenixKitProjects.Workers.TranslateResourceWorker and exposes
helpers hosts use to drive the language switcher's ai_translate
attr.
Design notes
- Worker does the AI call, this module just enqueues. Host LVs
call
enqueue/1from ahandle_eventclause and subscribe to the projects PubSub topic for status updates. - One job per
(resource, target_lang)pair. Uniqueness is enforced at the Oban worker level; this module just emits the enqueue call. A second enqueue for an in-flight pair returns{:ok, %{conflict?: true}}. enqueue_all_missing/1loops over the resource's missing languages and enqueues one job per language. Each runs independently; status events arrive in the order Oban completes them, not the order the languages were enqueued.- No "translate all resources in a project" bulk action. That's
a level above this module — a host can iterate projects and
call
enqueue_all_missing/1per resource if desired.
Status flow
- Host calls
enqueue/1→{:ok, %{conflict?: false}}and a:translation_startedPubSub event fires from the worker. - Host's
handle_info({:projects, :translation_started, ...})addstarget_langto itsin_flightset, re-renders the switcher with the updatedai_translate.in_flightlist → spinner replaces sparkle. - Worker completes →
:translation_completedevent. Host removestarget_langfromin_flightAND frommissing(the language now has a translation), re-renders. - On failure:
:translation_failedevent with:reason. Host removes fromin_flight(leaves inmissing) and surfaces a flash.
Summary
Functions
Is AI-driven translation usable right now?
Is the default projects translation prompt already provisioned?
Enqueue a translation job for a single resource + target language.
Enqueue one translation job per missing target language.
Generates the default projects translation prompt in the AI prompts
catalog. Returns {:ok, prompt} or {:error, changeset}. No-op
surface when PhoenixKitAI is unavailable — returns
{:error, :ai_not_installed}.
Resolves the default AI endpoint UUID from Settings, or nil.
Resolves the default AI prompt UUID. Prefers the explicit setting;
falls back to the prompt matching @translation_prompt_slug if one
exists. Returns nil when nothing is wired up.
List configured AI endpoints as {uuid, name} tuples. Empty when AI
is unavailable.
List configured AI prompts as {uuid, name} tuples. Empty when AI
is unavailable.
Types
@type resource_type() :: String.t()
Functions
@spec ai_translation_available?() :: boolean()
Is AI-driven translation usable right now?
Returns true when the optional PhoenixKitAI plugin is loaded, the
module is enabled at runtime, and at least one AI endpoint is
configured. Hosts use this to gate the AI translate UI surface.
@spec default_translation_prompt_exists?() :: boolean()
Is the default projects translation prompt already provisioned?
@spec enqueue(enqueue_params()) :: {:ok, %{conflict?: boolean()}} | {:error, term()}
Enqueue a translation job for a single resource + target language.
Returns {:ok, %{conflict?: false}} when the job is freshly
enqueued, or {:ok, %{conflict?: true}} when an identical job is
already in flight (Oban's unique constraint catches the dup).
Validates required keys and returns {:error, {:invalid, [keys]}}
on a malformed input map — saves the host LV from cryptic Oban
exceptions at job perform time.
@spec enqueue_all_missing(enqueue_params(), [String.t()]) :: {:ok, %{ enqueued: non_neg_integer(), conflicts: non_neg_integer(), errors: [{String.t(), term()}], in_flight: [String.t()] }} | {:error, term()}
Enqueue one translation job per missing target language.
missing_langs is host-supplied — typically computed as
enabled_languages -- ([primary_language] ++ Map.keys(resource.translations)).
Returns {:ok, %{enqueued: N, conflicts: M, errors: [...], in_flight: [...]}}:
enqueued— number of jobs newly inserted into Oban.conflicts— number of duplicates (job for that lang already running).errors— list of{lang, reason}for langs whose enqueue raised or returned{:error, _}. Callers should surface this as a partial failure.in_flight— exactly the langs whose enqueue succeeded (newly or by conflict). This is what a host should add to its UI spinner set: failed langs MUST NOT spin because no worker broadcast will arrive to clear them.
Generates the default projects translation prompt in the AI prompts
catalog. Returns {:ok, prompt} or {:error, changeset}. No-op
surface when PhoenixKitAI is unavailable — returns
{:error, :ai_not_installed}.
@spec get_default_ai_endpoint_uuid() :: String.t() | nil
Resolves the default AI endpoint UUID from Settings, or nil.
@spec get_default_ai_prompt_uuid() :: String.t() | nil
Resolves the default AI prompt UUID. Prefers the explicit setting;
falls back to the prompt matching @translation_prompt_slug if one
exists. Returns nil when nothing is wired up.
List configured AI endpoints as {uuid, name} tuples. Empty when AI
is unavailable.
List configured AI prompts as {uuid, name} tuples. Empty when AI
is unavailable.