PhoenixKitAI.Translatable adapter for shop products.
Resource identity
resource_type is "shop_product"; resource_uuid is the product uuid.
Fields
%{"title", "description", "body", "seo_title", "seo_description"} from
the source language (Translations.get/3), non-empty only. "body" maps
to the schema's body_html (the shared prompt vocabulary uses body).
The slug is NEVER sourced from or trusted to the AI — it is regenerated
locally from the translated title, and only when the target language has
no slug yet, so re-translations can't change published URLs (which is why
no slug-history/redirect machinery is needed).
Concurrency
All languages share ONE product row's JSONB maps, so put_translation/4
re-reads the row under FOR UPDATE and merges against the latest
committed state (the publishing group-adapter pattern) — concurrent
per-language jobs serialize on the row lock and never drop a sibling
language. update_product/2 / update_product_translation/3 are
deliberately NOT used here: they write a stale in-memory struct without a
lock — the exact lost-update race this adapter must prevent.
Slug uniqueness within the language is checked app-side (suffix on collision); there is no DB unique constraint on the JSONB slug map (core migration v47 dropped it), so the check is best-effort across rows.
Prompt
The seo fields are not in the shared translation prompt's vocabulary, so
this adapter ships its own prompt (ensure_prompt/0, slug
phoenixkit-shop-product-translation). Host forms must pass its uuid
per job — the global ai_translation_prompt_uuid setting stays untouched.
Requires the optional phoenix_kit_ai plugin: ensure_prompt/0 returns
{:error, :ai_not_installed} when it is absent, and the whole adapter is
only reached through duck-typed discovery, which never runs without it.
Summary
Functions
Idempotently creates this adapter's translation prompt and returns its uuid — host forms pass it per job instead of the shared default prompt.
The resource-type key this adapter registers under.
Functions
Idempotently creates this adapter's translation prompt and returns its uuid — host forms pass it per job instead of the shared default prompt.
The resource-type key this adapter registers under.