Shared form-LV helpers for the AI translate bar wiring on project, template, and task forms.
Extracted because the three form LVs each held an identical copy of
these helpers. The merge policy now lives directly in the form LVs
as plain Map.merge/2 — explicit user-click means AI value always
wins, and the form UI is locked while a translation is in flight,
so there's no user-edits-during-job race to mitigate.
Summary
Functions
Assigns the AI-translate bar's initial mount state onto socket.
Bump the progress-UI state when a translation lifecycle event lands
(:translation_completed or :translation_failed). Both terminal
outcomes advance the bar — the UX is "this job finished, the lang
is no longer spinning", and failure is communicated separately via
the flash, not by holding the progress count back.
Bump the progress-UI state when a new dispatch starts.
Does the resource have at least one non-blank translatable field
for lang?
Computes the missing list for the language switcher's
ai_translate.missing slot.
Functions
@spec assign_ai_translate_mount_state(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
Assigns the AI-translate bar's initial mount state onto socket.
The DB/plugin-backed lookups (default endpoint/prompt UUIDs, the
endpoint + prompt lists, default-prompt existence) only run on the
connected mount. mount/3 fires twice — once for the dead HTTP
render and again on the WS upgrade — and these values are only needed
once the modal is interactive, so the dead render gets empty defaults
and we avoid five duplicate Settings/plugin round-trips per mount.
@spec bump_translation_completed(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Bump the progress-UI state when a translation lifecycle event lands
(:translation_completed or :translation_failed). Both terminal
outcomes advance the bar — the UX is "this job finished, the lang
is no longer spinning", and failure is communicated separately via
the flash, not by holding the progress count back.
Takes the lang so the helper can detect duplicate / stale terminal
events (e.g. the same :translation_completed arriving twice on a
PubSub reconnect, or a stale event from a previous session's
in-flight set still in transit). When lang is no longer in
:ai_translate_in_flight, treat as a no-op: don't double-bump
progress past total and don't flip status back to :in_progress
for a session that already reached :completed.
Also removes lang from the in-flight list — caller passes the
socket BEFORE removal, this helper consolidates the removal +
progress bump so the two stay in sync.
Flips status to :completed only when the in-flight list goes
empty as a result of this bump.
@spec bump_translation_started(Phoenix.LiveView.Socket.t(), non_neg_integer()) :: Phoenix.LiveView.Socket.t()
Bump the progress-UI state when a new dispatch starts.
started_count— number of langs the host just enqueued. For single-lang it's1; for bulk*/**it's the count of successfully-enqueued langs.
When the previous session was nil or :completed, this RESETS
the bar to a fresh :in_progress session sized for the new
dispatch. When the previous session was still :in_progress,
this ADDS to the running total — so a user that clicks "translate
FR" while SQ is still translating sees the bar grow to 2/2 instead
of restarting.
Does the resource have at least one non-blank translatable field
for lang?
Computes the missing list for the language switcher's
ai_translate.missing slot.
A language is "missing" when it's in the host's enabled-language list, isn't the primary language, and doesn't have any non-blank translatable field for that language code yet.
The non-blank rule matters: %{"es" => %{}} and
%{"es" => %{"name" => ""}} both still count as missing — the
user hasn't actually translated anything yet, just opened the tab.
Treating an empty map as "translated" would hide the sparkle the
user is looking for.