PhoenixKitProjects.Web.Components.AITranslateBar (PhoenixKitProjects v0.5.0)

Copy Markdown View Source

AI-translation affordance for project / template / task forms.

Provides two surfaces:

  • <.ai_translate_button> — a single compact button rendered above the multilang tabs. Click toggles the modal below. Shows a "(N missing)" badge so the user knows there's something to do. Spinner badge while any job is in-flight.

  • <.ai_translate_modal> — a daisyUI dialog modal containing endpoint + prompt selectors, a "Generate Default Prompt" button when none is provisioned, in-flight status, and two action buttons:

    • "Translate Missing Only" — enqueues a job per missing lang
    • "Translate to Current Language" — enqueues a single job for the active tab's language (when not on the primary)

This is the publishing-style replacement for the earlier 40-button inline bar, which became unusable on apps with many enabled languages.

Host contract

Pass ai_translate: %{...}:

%{
  enabled: true,                  # boolean — gates render
  event: "translate_lang",        # phx-click event name
  toggle_event: "toggle_ai",      # opens/closes the modal
  select_endpoint_event: "...",   # endpoint dropdown change
  select_prompt_event: "...",     # prompt dropdown change
  select_scope_event: "...",      # scope radio change
  generate_prompt_event: "...",   # generate-default-prompt
  missing: ["es", "de"],          # langs still to translate
  all_langs: ["es", "de", "fr"],  # every non-primary enabled lang
  in_flight: ["es"],              # jobs running now
  modal_open: false,              # is the modal visible?
  endpoints: [{uuid, name}, ...], # AI endpoints list
  prompts: [{uuid, name}, ...],   # AI prompts list
  selected_endpoint_uuid: "...",  # current endpoint choice
  selected_prompt_uuid: "...",    # current prompt choice
  scope: :missing,                # :missing | :all | :current
  default_prompt_exists: true,    # hides the generate-button
  current_lang: "es",             # active multilang tab
  primary_lang: "en",             # source for translations + disables :current on primary
  primary_lang_name: "English"    # friendly label; falls back to upcased code, then a generic string
}

Action contract

The modal renders a single "Translate" button driven by scope:

  • :missing (default) — bulk: phx-value-lang="*". Worker only enqueues langs without translations.
  • :all — bulk with overwrite warning: phx-value-lang="**". Enqueues every non-primary lang (existing translations get overwritten on completion via the host's same-target merge).
  • :current — single lang: phx-value-lang=<current_lang>.

Host's handle_event(@ai_translate.event, %{"lang" => lang}, socket) branches on the value: "*" → missing-only path; "**" → all path; concrete code → single-lang path.

Summary

Functions

Compact trigger button. Shows a missing-count badge and a spinner badge while any translation is in flight.

Modal dialog with endpoint/prompt selectors + action buttons.

Functions

ai_translate_button(assigns)

Compact trigger button. Shows a missing-count badge and a spinner badge while any translation is in flight.

Hidden entirely when ai_translate.enabled != true, the toggle event is blank, or there's nothing to translate AND nothing in flight.

Attributes

  • ai_translate (:map) - Configuration map — see moduledoc for the full shape. Defaults to nil.
  • class (:string) - Defaults to "flex items-center gap-2 px-4 py-2 border-b border-base-200".

ai_translate_modal(assigns)

Modal dialog with endpoint/prompt selectors + action buttons.

Renders nothing when ai_translate.enabled != true — the gate matches ai_translate_button/1 so a host can always render both components and only the relevant one shows.