Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions lib/machine_translations/provider/deepl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,108 @@ defmodule Accent.MachineTranslations.Provider.Deepl do
def enabled?(%{config: %{"key" => key}}), do: not is_nil(key)
def enabled?(_), do: false

defp normalize_lang(nil), do: nil
defp normalize_lang(slug) when is_binary(slug) do
slug
|> String.upcase()
|> String.split("-", parts: 2)
|> List.first()
end

# e.g. supported_glossary_langs="DE,EN,FR,IT,ES,PT"
defp parse_langs_list(nil), do: MapSet.new()
defp parse_langs_list(str) when is_binary(str) do
str
|> String.split(~r/[\s,]+/, trim: true)
|> Enum.map(&String.upcase/1)
|> MapSet.new()
end

def translate(provider, contents, source_arg, target_arg) do
with {:ok, {source, target}} <-
Accent.MachineTranslations.map_source_and_target(source_arg, target_arg, @supported_languages),
# Normalize for DeepL
source_lang <- source |> normalize_lang(),
target_lang <- target |> normalize_lang(),
params <-
%{
text: contents,
source_lang: source_lang,
target_lang: target_lang
}
|> maybe_put_glossary(provider.config, source_lang, target_lang),
{:ok, %{body: %{"translations" => translations}}} <-
Tesla.post(client(provider.config["key"]), "translate", params) do
{:ok, Enum.map(translations, &%TranslatedText{text: &1["text"]})}
else
{:ok, %{status: status, body: body}} when status > 201 -> {:error, body}
error -> error
end
end

defp glossary_map_from_env(config) do
raw = config["glossary_map"] || System.get_env("DEEPL_GLOSSARY_MAP")

parse_glossary_map(raw)
end

# Parse the glossary map from the environment. Can be in format
# JSON: {"<source_lang>-><target_lang>": "<glossary_id>"} => {"DE->EN": "abc", "DE->IT": "def"}
# Or String: "[source_lang]->[target_lang]:[glossary_id],..." => "DE->EN:abc,DE->IT:def"
defp parse_glossary_map(nil), do: %{}
defp parse_glossary_map(raw) when is_binary(raw) do
case Jason.decode(raw) do
{:ok, %{} = map} ->
map
|> Enum.reduce(%{}, fn {pair, id}, acc ->
case String.split(pair, "->", parts: 2) do
[src, tgt] ->
src_n = normalize_lang(src)
tgt_n = normalize_lang(tgt)
Map.put(acc, {src_n, tgt_n}, id)
_ ->
acc
end
end)

_non_json ->
raw
|> String.split(~r/[\s,]+/, trim: true)
|> Enum.reduce(%{}, fn entry, acc ->
case String.split(entry, ":", parts: 2) do
[pair, id] ->
case String.split(pair, "->", parts: 2) do
[src, tgt] ->
src_n = normalize_lang(src)
tgt_n = normalize_lang(tgt)
Map.put(acc, {src_n, tgt_n}, id)
_ -> acc
end
_ -> acc
end
end)
end
end

defp maybe_put_glossary(params, config, source_lang, target_lang) do
cond do
is_nil(source_lang) or is_nil(target_lang) -> params
source_lang == target_lang -> params
true ->
id =
glossary_map_from_env(config)
|> Map.get({source_lang, target_lang})

if is_binary(id) and id != "" do
Map.put(params, :glossary_id, id)
else
params
end
end
end

defp maybe_put_glossary(params, _), do: params

def translate(provider, contents, source_arg, target_arg) do
with {:ok, {source, target}} <-
Accent.MachineTranslations.map_source_and_target(source_arg, target_arg, @supported_languages),
Expand Down
1 change: 1 addition & 0 deletions webapp/app/components/inline-machine-translate/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title={{@languageSlug}}
@onClick={{perform this.submitTask @languageSlug}}
@loading={{this.isSubmitting}}
@disabled={{this.isMainLanguage}}
local-class='button'
class='button button--iconOnly button--link button--filled button--white'
>
Expand Down
22 changes: 20 additions & 2 deletions webapp/app/components/inline-machine-translate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,36 @@ export default class ImprovePrompt extends Component<Args> {
return this.submitTask.isRunning;
}

get isMainLanguage() {
// Split region as it's not important for translations
return (
this.args.languageSlug.split('-')[0] ===
this.args.project.revisions
.filter((r) => r.isMaster)[0]
.language.slug.split('-')[0]
);
}

submitTask = dropTask(async (targetLanguageSlug: string) => {
this.args.onUpdatingText();

const sourceLanguageSlug: string = this.args.project.revisions.filter(
(r) => r.isMaster,
)[0].language.slug;

const variables = {
projectId: this.args.project.id,
text: this.args.text,
targetLanguageSlug
targetLanguageSlug,
sourceLanguageSlug:
sourceLanguageSlug === targetLanguageSlug
? undefined
: sourceLanguageSlug,
};

const {data} = await this.apollo.client.query({
query: projectTranslateTextQuery,
variables
variables,
});

if (data.viewer.project.translatedText?.text) {
Expand Down