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. Beyond dedup, lifting them out makes the
merge_blank_fields_only/2 policy directly unit-testable — the
user-edits-win contract is load-bearing for the form UX (a
translation that lands mid-edit must not silently clobber what the
user typed).
Summary
Functions
Assigns the AI-translate bar's initial mount state onto socket.
Does the resource have at least one non-blank translatable field
for lang?
Merges the AI's translated new_lang_map into the existing
current_lang_map, with user-typed values winning over AI
output.
Merges AI output into the form's current lang map according to the job's scope.
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.
Does the resource have at least one non-blank translatable field
for lang?
Merges the AI's translated new_lang_map into the existing
current_lang_map, with user-typed values winning over AI
output.
A field is updated by the AI only when the current value is
blank (nil, "", or whitespace-only). If the user switched to
the target language during the Oban job and typed something in
e.g. name, the AI's translated name will NOT overwrite it.
This is the policy fix from PR #12's final codex review — an
unconditional Map.merge/2 would silently clobber edits the user
made between dispatching the translation and the job completing.
Merges AI output into the form's current lang map according to the job's scope.
overwrite? == true(the "all" scope) — AI output wins via plainMap.merge/2, mirroring the worker's persisted merge so the open form reflects exactly what was written to the DB. Without this, an "overwrite all" translation would update the DB but leave the open form showing the old values, and a subsequent save would silently revert the overwrite.overwrite? == false(missing-only / single-lang) — defers tomerge_blank_fields_only/2so edits the user made while the job ran are preserved.
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.