PhoenixKitProjects.Web.AITranslateFormHelpers (PhoenixKitProjects v0.5.1)

Copy Markdown View Source

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

assign_ai_translate_mount_state(socket)

@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.

bump_translation_completed(socket, lang)

@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.

bump_translation_started(socket, started_count)

@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's 1; 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.

has_any_translation?(translations, lang, translatable_fields)

@spec has_any_translation?(map(), String.t(), [atom() | String.t()]) :: boolean()

Does the resource have at least one non-blank translatable field for lang?

missing_languages(language_tabs, primary_language, translations, translatable_fields)

@spec missing_languages([map()], String.t(), map() | nil, [atom() | String.t()]) :: [
  String.t()
]

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.