diff --git a/lib/adapter.ex b/lib/adapter.ex index c8b0823..f7e2884 100644 --- a/lib/adapter.ex +++ b/lib/adapter.ex @@ -1,6 +1,15 @@ defmodule Kanta.DeepL.Adapter do @moduledoc """ DeepL API adapter + + We use XML tag handling to avoid translating the content of curly brackets. + These curly brackets are used by gettext for variable substitution. + They look like this: + + ``` + price = Money.new(1000, "EUR") + gettext("Price: %{price}", price: price) + ``` """ use Tesla @@ -13,29 +22,34 @@ defmodule Kanta.DeepL.Adapter do plug(Tesla.Middleware.JSON) - def request_translation(source_lang, target_lang, text) do - post("/v2/translate", %{ - source_lang: source_lang, - target_lang: target_lang, - text: [text] - }) - |> case do - {:ok, %Tesla.Env{body: %{"translations" => translations}}} -> {:ok, translations} - {_, %Tesla.Env{body: body, status: status}} -> {:error, status, body} - error -> {:error, error} - end + def request_translation(source_lang, target_lang, text, context_name) do + request_translations(source_lang, target_lang, [text], context_name) end - def request_translations(source_lang, target_lang, texts) do + def request_translations(source_lang, target_lang, texts, context_name) do + texts = Enum.map(texts, &replace_curly_brackets_with_xml_tags/1) + post("/v2/translate", %{ source_lang: source_lang, target_lang: target_lang, + context: context_name, + tag_handling: "xml", + ignore_tags: ["gettext_variable"], text: texts }) |> case do - {:ok, %Tesla.Env{body: %{"translations" => translations}}} -> {:ok, translations} - {_, %Tesla.Env{body: body, status: status}} -> {:error, status, body} - error -> {:error, error} + {:ok, %Tesla.Env{body: %{"translations" => translations}}} -> + translations + |> Enum.map(fn translation -> + Map.update!(translation, "text", &replace_xml_tags_with_curly_brackets/1) + end) + |> then(&{:ok, &1}) + + {_, %Tesla.Env{body: body, status: status}} -> + {:error, status, body} + + error -> + {:error, error} end end @@ -54,4 +68,12 @@ defmodule Kanta.DeepL.Adapter do {_, config} -> Keyword.get(config, :api_key) end end + + defp replace_curly_brackets_with_xml_tags(text) do + Regex.replace(~r/%{(.*)}/, text, fn _, x -> "#{x}" end) + end + + defp replace_xml_tags_with_curly_brackets(text) do + Regex.replace(~r/(.*)<\/gettext_variable>/, text, fn _, x -> "%{#{x}}" end) + end end diff --git a/lib/form_component.ex b/lib/form_component.ex index 107c60e..aff0fb8 100644 --- a/lib/form_component.ex +++ b/lib/form_component.ex @@ -108,11 +108,8 @@ defmodule Kanta.DeepL.Plugin.FormComponent do %{"translated_text" => translated}, %{assigns: %{message: %Message{message_type: :singular}}} = socket ) do - locale = socket.assigns.locale translation = socket.assigns.translation - Translations.update_singular_translation(translation, %{"translated_text" => translated}) - {:noreply, socket} end @@ -121,11 +118,8 @@ defmodule Kanta.DeepL.Plugin.FormComponent do %{"translated_text" => translated}, %{assigns: %{message: %Message{message_type: :plural}}} = socket ) do - locale = socket.assigns.locale translation = socket.assigns.translation - Translations.update_plural_translation(translation, %{"translated_text" => translated}) - {:noreply, socket} end @@ -140,7 +134,8 @@ defmodule Kanta.DeepL.Plugin.FormComponent do case Adapter.request_translation( source_locale, String.upcase(translate_locale.iso639_code), - source_text + source_text, + if(message.context, do: message.context.name) ) do {:ok, translations} -> %{"text" => translated_text} = List.first(translations) diff --git a/lib/translate_all_component.ex b/lib/translate_all_component.ex index 797e074..030bc57 100644 --- a/lib/translate_all_component.ex +++ b/lib/translate_all_component.ex @@ -24,14 +24,14 @@ defmodule Kanta.DeepL.Plugin.TranslateAllComponent do singular_messages = Translations.list_singular_translations( filter: %{locale_id: socket.assigns.locale_id, translated_text: :is_null}, - preloads: [:message], + preloads: [message: :context], skip_pagination: true ) plural_messages = Translations.list_plural_translations( filter: %{locale_id: socket.assigns.locale_id, translated_text: :is_null}, - preloads: [:message], + preloads: [message: :context], skip_pagination: true ) @@ -50,18 +50,23 @@ defmodule Kanta.DeepL.Plugin.TranslateAllComponent do {:noreply, socket} end - defp translate_all(messages, locale_code, update_fn) do - messages - |> Enum.chunk_every(@deepl_limit) - |> Enum.map(&translate_batch(&1, locale_code, update_fn)) + defp translate_all(translations, locale_code, update_fn) do + translations + |> Enum.group_by(&if &1.message.context, do: &1.message.context.name) + |> Enum.reduce([], fn {context_name, same_context_translations}, acc -> + same_context_translations + |> Enum.chunk_every(@deepl_limit) + |> Enum.map(&translate_batch(&1, locale_code, update_fn, context_name)) + |> then(&Kernel.++(acc, &1)) + end) end - defp translate_batch(batch, locale_code, update_fn) do + defp translate_batch(batch, locale_code, update_fn, context_name) do source_lang = nil target_lang = String.upcase(locale_code) texts = Enum.map(batch, & &1.message.msgid) - case Adapter.request_translations(source_lang, target_lang, texts) do + case Adapter.request_translations(source_lang, target_lang, texts, context_name) do {:ok, translations} -> batch |> Enum.zip(translations)