PhoenixKit.Modules.AI.Translatable adapter for publishing posts — the
per-module hook into core's generic AI-translation pipeline
(PhoenixKit.Modules.AI.{Translations,TranslateWorker}).
Replaces the bespoke Workers.TranslatePostWorker, which translated every
language sequentially in a single Oban job. Translations.enqueue_all_missing/2
dispatches one job per target language, so they run in parallel (bounded
by the Oban queue) — wall-clock drops from the sum of all languages to ~the
slowest single one.
Resource identity
resource_type is "publishing_post"; resource_uuid is the post's uuid
(core validates it as a real UUID). Translations target the post's active
version — the version the editor normally works on.
Fields
source_fields/2 returns %{"title", "content"} read in the source
language. put_translation/4 creates the target-language content row (via
add_language_to_post when absent) and writes the translated title/content,
generating the per-language url_slug locally from the translated title
via SlugHelpers.slugify/1. That honors the configured slug style and avoids
trusting an AI-returned slug (which a reasoning model can mangle).
Concurrency
Each target language is a distinct phoenix_kit_publishing_contents row
(unique on version_uuid + language), so concurrent per-language jobs never
touch the same row — no merge/lock dance is needed (unlike JSONB-on-one-row
consumers).
Events
pubsub_topics/1 returns the post's translations topic, so core's
{:ai_translation, …} lifecycle events reach the editor LiveView (already
subscribed to that topic). Per-language content creation also emits
publishing's own :translation_created via add_language_to_post.
Summary
Functions
The resource_type string this adapter serves.
Types
@type t() :: %PhoenixKitPublishing.AITranslatable{ group_slug: String.t(), post_slug: String.t() | nil, post_uuid: String.t(), version: pos_integer() | nil }
Functions
@spec resource_type() :: String.t()
The resource_type string this adapter serves.