# `PhoenixKitAI.TranslateWorker`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ai/blob/v0.4.0/lib/phoenix_kit_ai/translate_worker.ex#L1)

Generic Oban worker that translates one resource's fields into a single
target language, shared by every consumer module.

Resolves a `PhoenixKitAI.Translatable` adapter from the
job's `resource_type` (registered via `ai_translatables/0` and
discovered through `PhoenixKitAI.Translatables`), then runs:

    adapter.fetch/3-or-2 → adapter.source_fields/2 →
    Translation.translate_fields/6 → adapter.put_translation/4

broadcasting `{:ai_translation, event, payload}` at each lifecycle step
(see `PhoenixKitAI.Translations`) and writing one
`ai.translation_added` activity entry on success.

## Job args

    %{
      "resource_type" => "catalogue_item",
      "resource_uuid" => uuid,
      "endpoint_uuid" => uuid,
      "prompt_uuid"   => uuid,
      "source_lang"   => "en",
      "target_lang"   => "es",
      "actor_uuid"    => uuid_or_nil,
      "resource_scope" => scope_or_nil
    }

`resource_scope` is an optional, opaque, JSON-safe string partitioning a
resource's jobs (e.g. a version number). When present and the adapter exports
`fetch/3`, it's passed through so the adapter loads that exact slice; `nil`
(or absent) means the default slice via `fetch/2`.

## De-duplication

De-dup is **app-level** in `PhoenixKitAI.Translations.enqueue/1`
(one in-flight job per `(resource_type, resource_uuid, resource_scope,
target_lang)`), NOT
Oban's built-in `unique:`. Oban's uniqueness query references the
`:suspended` job state, which is absent from the `oban_job_state` enum on
hosts that upgraded the Oban *lib* ahead of its *migration* — there the
query raises `22P02` and kills every enqueue. The app guard queries only
the four always-present states (`available`/`scheduled`/`executing`/
`retryable`) and fails open. Same trade-off as the catalogue PDF worker.

---

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