diff --git a/lib/outlook/translators.ex b/lib/outlook/translators.ex index 15abfeb..098760b 100644 --- a/lib/outlook/translators.ex +++ b/lib/outlook/translators.ex @@ -6,99 +6,93 @@ defmodule Outlook.Translators do import Ecto.Query, warn: false alias Outlook.Repo - alias Outlook.Translators.DeeplAccount + alias Outlook.InternalTree.TranslationUnit + alias Outlook.Translators.{DeeplAccount,Deepl} + alias OutlookWeb.HtmlDocComponent - @doc """ - Returns the list of deepl_accounts. - - ## Examples - - iex> list_deepl_accounts() - [%DeeplAccount{}, ...] - - """ def list_deepl_accounts do Repo.all(DeeplAccount) end - @doc """ - Gets a single deepl_account. - - Raises `Ecto.NoResultsError` if the Deepl account does not exist. - - ## Examples - - iex> get_deepl_account!(123) - %DeeplAccount{} - - iex> get_deepl_account!(456) - ** (Ecto.NoResultsError) - - """ def get_deepl_account!(id), do: Repo.get!(DeeplAccount, id) - @doc """ - Creates a deepl_account. + def get_deepl_auth_key(user_id) do + query = + from DeeplAccount, + where: [user_id: ^user_id], + select: [:auth_key] + Repo.one!(query) + |> Map.get(:auth_key) + end - ## Examples - - iex> create_deepl_account(%{field: value}) - {:ok, %DeeplAccount{}} - - iex> create_deepl_account(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ def create_deepl_account(attrs \\ %{}) do %DeeplAccount{} |> DeeplAccount.changeset(attrs) |> Repo.insert() end - @doc """ - Updates a deepl_account. - - ## Examples - - iex> update_deepl_account(deepl_account, %{field: new_value}) - {:ok, %DeeplAccount{}} - - iex> update_deepl_account(deepl_account, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ def update_deepl_account(%DeeplAccount{} = deepl_account, attrs) do deepl_account |> DeeplAccount.changeset(attrs) |> Repo.update() end - @doc """ - Deletes a deepl_account. - - ## Examples - - iex> delete_deepl_account(deepl_account) - {:ok, %DeeplAccount{}} - - iex> delete_deepl_account(deepl_account) - {:error, %Ecto.Changeset{}} - - """ def delete_deepl_account(%DeeplAccount{} = deepl_account) do Repo.delete(deepl_account) end - @doc """ - Returns an `%Ecto.Changeset{}` for tracking deepl_account changes. - - ## Examples - - iex> change_deepl_account(deepl_account) - %Ecto.Changeset{data: %DeeplAccount{}} - - """ def change_deepl_account(%DeeplAccount{} = deepl_account, attrs \\ %{}) do DeeplAccount.changeset(deepl_account, attrs) end + + + def translate(translation, current_user) do + %{lang: target_lang, + article: %{content: article_tree, language: source_lang} + } = translation + article_as_html = prepare_article(article_tree) + auth_key = get_deepl_auth_key(current_user.id) + args = [ + self(), + article_as_html, + %{ + source_lang: source_lang, + target_lang: target_lang, + auth_key: auth_key + } + ] + Task.start_link(Deepl, :translate, args) + end + + defp prepare_article(tree) do + # Logger.info "so far." + HtmlDocComponent.render_doc(%{tree: tree}) + |> Phoenix.HTML.Safe.to_iodata() + |> IO.iodata_to_binary() + end + + def process_translation_result(result, tunit_ids) do + # TODO: update :our_character_count + process_translation(result.translation, tunit_ids) + end + + def process_translation(translation, tunit_ids) do + tunit_map = translation + |> Floki.parse_fragment! + |> Floki.find("span.tunit") + |> Enum.map(fn {_,atts,cont} -> + %TranslationUnit{ + uuid: Enum.find(atts, fn {k,_} -> k == "uuid" end) |> Tuple.to_list |> Enum.at(1), + content: Floki.raw_html(cont), + status: :untranslated + } + end) + |> Enum.map(fn tunit -> {tunit.uuid, tunit} end) + |> Enum.into(%{}) + + case Enum.sort(Map.keys(tunit_map)) == Enum.sort(tunit_ids) do + true -> {:ok, tunit_map} + false -> {:error, "keys don't equal the originals"} + end + end end diff --git a/lib/outlook/translators/deepl.ex b/lib/outlook/translators/deepl.ex index 986a9f6..b7dd8fb 100644 --- a/lib/outlook/translators/deepl.ex +++ b/lib/outlook/translators/deepl.ex @@ -1,10 +1,12 @@ defmodule Outlook.Translators.Deepl do - def test(pid) do - for n <- 0..100 do - send(pid, {:progress, %{progress: n}}) - Process.sleep 50 - end + def translate(pid, article, options) do + send(pid, {:progress, %{progress: 0}}) + credentials = start_translation(article, options) + status = check_status(pid, credentials) + translation = get_translation(credentials) + send(pid, {:translation, %{translation: translation, billed_characters: status.billed_characters}}) + Process.sleep 1000 send(pid, {:progress, %{progress: nil}}) end @@ -13,10 +15,11 @@ defmodule Outlook.Translators.Deepl do @doc """ Upload the content to translate and return document_id and document_key as Map. """ - def start_translation %{auth_key: auth_key} = _credentials, content, target_lang do + def start_translation content, options do form = get_multipart_form( [ - {"target_lang", target_lang}, + {"source_lang", options.source_lang}, + {"target_lang", options.target_lang}, {"file", content, {"form-data", [{:name, "file"}, {:filename, "datei.html"}]}, []} ] ) @@ -25,16 +28,16 @@ defmodule Outlook.Translators.Deepl do :post, "https://api-free.deepl.com/v2/document", form, - get_multipart_headers(auth_key) + get_multipart_headers(options.auth_key) ) Jason.decode!(response_raw.body, keys: :atoms) - |> Map.put(:auth_key, auth_key) + |> Map.put(:auth_key, options.auth_key) end @doc """ - Upload the content to translate and return the estimated time until done. + Check the status until translation is "done". """ - def check_status credentials do + def check_status pid, credentials do response_raw = HTTPoison.request!( :post, "https://api-free.deepl.com/v2/document/#{credentials.document_id}", @@ -45,8 +48,12 @@ defmodule Outlook.Translators.Deepl do case response do %{status: "translating"} -> - Process.sleep(String.to_integer(response.seconds_remaining) * 1000) - check_status(credentials) + steps = response.seconds_remaining * 5 + for n <- 0..steps do + send(pid, {:progress, %{progress: 100 * n / steps}}) + Process.sleep 200 + end + check_status(pid, credentials) %{status: "done"} -> response end diff --git a/lib/outlook_web/live/translation_live/form_component.ex b/lib/outlook_web/live/translation_live/form_component.ex index c261adf..e8cde72 100644 --- a/lib/outlook_web/live/translation_live/form_component.ex +++ b/lib/outlook_web/live/translation_live/form_component.ex @@ -63,9 +63,21 @@ defmodule OutlookWeb.TranslationLive.FormComponent do {:ok, socket |> assign(deepl_progress: progress)} end + def update(%{deepl_translation: translation}, socket) do + tunit_keys = Map.keys(socket.assigns.translation_content) + case Outlook.Translators.process_translation_result(translation, tunit_keys) do + {:ok, new_translation_content} -> + {:ok, socket + |> assign(translation_content: new_translation_content) + |> update_current_tunit()} + {:error, message} -> + {:ok, socket |> put_flash(:error, message)} + end + end + @impl true def handle_event("translate-deepl", _, socket) do - Task.start_link(Outlook.Translators.Deepl, :test, [self()]) + Outlook.Translators.translate(socket.assigns.translation, socket.assigns.current_user) {:noreply, socket} end @@ -98,11 +110,21 @@ defmodule OutlookWeb.TranslationLive.FormComponent do |> assign(:current_tunit, socket.assigns.translation_content[uuid])} end + @doc "updating on browser events" def handle_event("update_current_tunit", %{"content" => content}, socket) do tunit = %TranslationUnit{socket.assigns.current_tunit | content: content} {:noreply, socket |> assign(:current_tunit, tunit)} end + + # updating after Deepl translation + defp update_current_tunit(socket) when is_struct(socket.assigns.current_tunit) do + assign(socket, + :current_tunit, + socket.assigns.translation_content[socket.assigns.current_tunit.uuid]) + end + defp update_current_tunit(socket), do: socket + defp update_translation_with_current_tunit(socket) do translation_content = if socket.assigns.current_tunit do socket.assigns.translation_content diff --git a/lib/outlook_web/live/translation_live/new_edit.ex b/lib/outlook_web/live/translation_live/new_edit.ex index 082c63d..8961456 100644 --- a/lib/outlook_web/live/translation_live/new_edit.ex +++ b/lib/outlook_web/live/translation_live/new_edit.ex @@ -17,6 +17,7 @@ defmodule OutlookWeb.TranslationLive.NewEdit do action={@live_action} translation={@translation} translation_content={@translation_content} + current_user={@current_user} navigate={~p"/translations"} /> """ @@ -56,4 +57,9 @@ defmodule OutlookWeb.TranslationLive.NewEdit do send_update(self(), FormComponent, progress: payload.progress, id: @form_cmpnt_id) {:noreply, socket} end + + def handle_info({:translation, payload}, socket) do + send_update(self(), FormComponent, deepl_translation: payload, id: @form_cmpnt_id) + {:noreply, socket} + end end