PhoenixKitProjects.Workers.TranslateResourceWorker (PhoenixKitProjects v0.5.0)

Copy Markdown View Source

Oban worker that translates a Project / Task / Assignment's translatable fields from a source language to a single target language, then writes the result into the resource's translations JSONB map.

Templates use the same Project schema as regular projects (with is_template: true), so resource_type of "project" or "template" both route through Project. The distinction shows up only in the activity-log entry and the user-facing flash text.

Job arg shape

%{
  "resource_type" => "project" | "template" | "task" | "assignment",
  "resource_uuid" => uuid,
  "endpoint_uuid" => uuid,        # PhoenixKitAI endpoint
  "prompt_uuid" => uuid,          # PhoenixKitAI prompt template
  "source_lang" => "en",
  "target_lang" => "es",
  "actor_uuid" => uuid_or_nil
}

Storage

Writes to resource.translations[target_lang] via the resource's existing changeset, preserving any pre-existing translations on other languages. The shape matches what the multilang form writes (e.g. %{"et" => %{"name" => "...", "description" => "..."}}) so the form's edit round-trip works unchanged after a translation finishes.

Broadcasts

  • {:projects, :translation_started, %{...}} before the AI call
  • {:projects, :translation_completed, %{...}} after a successful write
  • {:projects, :translation_failed, %{...}} on any failure path

Payload always includes resource_type, resource_uuid, target_lang. :translation_failed adds reason (a normalised atom or {atom, term} tuple from PhoenixKit.Modules.AI.Translation).

Uniqueness

One job in flight per (resource_uuid, target_lang) pair so a double-click in the UI doesn't burn AI tokens twice. Other target languages on the same resource can run concurrently.

period: :infinity is deliberate: Oban's unique default is a 60-second window, which would let a second enqueue slip through for a translation that runs longer than a minute (entirely possible for a multi-field AI call). With :infinity the dedup is bounded by the in-flight states instead of time — a duplicate is rejected as long as the original is still available/scheduled/executing/ retryable, and a fresh job is allowed only once the previous one reaches a terminal state (so re-translating later still works).

Auto-completion

Mirrors publishing's TranslatePostWorker shape (queue, retry policy, activity-log action) but writes a much smaller surface (no version model, no per-language status, no cache invalidation). Each module owns its own broadcast topic + activity-log action; the actual AI orchestration lives in PhoenixKit.Modules.AI.Translation.