Add translation module for Argos Translate
Argos Translate is a Python module for translation and can be used as a command line tool. This is also the engine for LibreTranslate, for which we already have a module. Here we can use the engine irectly from our server without doing requests to a third party or having to install our own LibreTranslate webservice. One thing that's currently still missing from ArgosTranslate is auto-detection of languages.
This commit is contained in:
parent
dcac8adb3d
commit
f027db9654
4 changed files with 160 additions and 0 deletions
|
@ -882,6 +882,11 @@
|
||||||
url: "http://127.0.0.1:5000",
|
url: "http://127.0.0.1:5000",
|
||||||
api_key: nil
|
api_key: nil
|
||||||
|
|
||||||
|
config :pleroma, :argos_translate,
|
||||||
|
command_argos_translate: "argos-translate",
|
||||||
|
command_argospm: "argospm",
|
||||||
|
default_language: "en"
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -1140,3 +1140,9 @@ Translations are available at `/api/v1/statuses/:id/translations/:language`, whe
|
||||||
|
|
||||||
- `:url` - URL of LibreTranslate instance
|
- `:url` - URL of LibreTranslate instance
|
||||||
- `:api_key` - API key for LibreTranslate
|
- `:api_key` - API key for LibreTranslate
|
||||||
|
|
||||||
|
### `:argos_translate`
|
||||||
|
|
||||||
|
- `:command_argos_translate` - command for `argos-translate`. Can be the command if it's in your PATH, or the full path to the file (default: `argos-translate`).
|
||||||
|
- `:command_argospm` - command for `argospm`. Can be the command if it's in your PATH, or the full path to the file (default: `argospm`).
|
||||||
|
- `:default_language` - When no language is provided to translate from, this language will be used. Must be a two letter langage code from a language you have installed (default: `en`).
|
||||||
|
|
83
lib/pleroma/akkoma/translators/argos_translate.ex
Normal file
83
lib/pleroma/akkoma/translators/argos_translate.ex
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
defmodule Pleroma.Akkoma.Translators.ArgosTranslate do
|
||||||
|
@behaviour Pleroma.Akkoma.Translator
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
defp argos_translate do
|
||||||
|
Config.get([:argos_translate, :command_argos_translate])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp argospm do
|
||||||
|
Config.get([:argos_translate, :command_argospm])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp default_language do
|
||||||
|
Config.get([:argos_translate, :default_language])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp safe_languages() do
|
||||||
|
try do
|
||||||
|
System.cmd(argospm(), ["list"], stderr_to_stdout: true, parallelism: true)
|
||||||
|
rescue
|
||||||
|
_ -> {"Command #{argospm()} not found", 1}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Pleroma.Akkoma.Translator
|
||||||
|
def languages do
|
||||||
|
with {response, 0} <- safe_languages() do
|
||||||
|
langs =
|
||||||
|
response
|
||||||
|
|> String.split("\n", trim: true)
|
||||||
|
|> Enum.map(fn
|
||||||
|
"translate-" <> l -> String.split(l, "_")
|
||||||
|
_ -> ""
|
||||||
|
end)
|
||||||
|
|
||||||
|
source_langs =
|
||||||
|
langs
|
||||||
|
|> Enum.map(fn [l, _] -> %{code: l, name: l} end)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
dest_langs =
|
||||||
|
langs
|
||||||
|
|> Enum.map(fn [_, l] -> %{code: l, name: l} end)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
{:ok, source_langs, dest_langs}
|
||||||
|
else
|
||||||
|
{response, _} -> {:error, "ArgosTranslate failed to fetch languages (#{response})"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp safe_translate(string, from_language, to_language) do
|
||||||
|
try do
|
||||||
|
System.cmd(
|
||||||
|
argos_translate(),
|
||||||
|
["--from-lang", from_language, "--to-lang", to_language, string],
|
||||||
|
stderr_to_stdout: true,
|
||||||
|
parallelism: true
|
||||||
|
)
|
||||||
|
rescue
|
||||||
|
_ -> {"Command #{argos_translate()} not found", 1}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Pleroma.Akkoma.Translator
|
||||||
|
def translate(string, from_language, to_language) do
|
||||||
|
# Akkoma's Pleroma-fe expects us to detect the source language automatically.
|
||||||
|
# Argos-translate doesn't have that option (yet?)
|
||||||
|
# see <https://github.com/argosopentech/argos-translate/issues/9>
|
||||||
|
# For now we choose a default source language from settings.
|
||||||
|
# Afterwards people get the option to overwrite the source language from a dropdown.
|
||||||
|
from_language = from_language || default_language()
|
||||||
|
to_language = to_language || default_language()
|
||||||
|
|
||||||
|
with {translated, 0} <-
|
||||||
|
safe_translate(string, from_language, to_language) do
|
||||||
|
{:ok, from_language, translated}
|
||||||
|
else
|
||||||
|
{response, _} -> {:error, "ArgosTranslate failed to translate (#{response})"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
66
test/pleroma/akkoma/translators/argos_translate_test.exs
Normal file
66
test/pleroma/akkoma/translators/argos_translate_test.exs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
defmodule Pleroma.Akkoma.Translators.ArgosTranslateTest do
|
||||||
|
alias Pleroma.Akkoma.Translators.ArgosTranslate
|
||||||
|
|
||||||
|
import Mock
|
||||||
|
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
|
setup do
|
||||||
|
clear_config([:argos_translate, :command_argos_translate], "argos-translate_test")
|
||||||
|
clear_config([:argos_translate, :command_argospm], "argospm_test")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it lists available languages" do
|
||||||
|
languages =
|
||||||
|
with_mock System, [:passthrough],
|
||||||
|
cmd: fn "argospm_test", ["list"], _ ->
|
||||||
|
{"translate-nl_en\ntranslate-en_nl\ntranslate-ja_en\n", 0}
|
||||||
|
end do
|
||||||
|
ArgosTranslate.languages()
|
||||||
|
end
|
||||||
|
|
||||||
|
assert {:ok, source_langs, dest_langs} = languages
|
||||||
|
|
||||||
|
assert [%{code: "en", name: "en"}, %{code: "ja", name: "ja"}, %{code: "nl", name: "nl"}] =
|
||||||
|
source_langs |> Enum.sort()
|
||||||
|
|
||||||
|
assert [%{code: "en", name: "en"}, %{code: "nl", name: "nl"}] = dest_langs |> Enum.sort()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it translates from default language when no language is set" do
|
||||||
|
translation_response =
|
||||||
|
with_mock System, [:passthrough],
|
||||||
|
cmd: fn "argos-translate_test", ["--from-lang", "en", "--to-lang", "fr", "blabla"], _ ->
|
||||||
|
{"yadayada", 0}
|
||||||
|
end do
|
||||||
|
ArgosTranslate.translate("blabla", nil, "fr")
|
||||||
|
end
|
||||||
|
|
||||||
|
assert {:ok, "en", "yadayada"} = translation_response
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it translates from the provided language" do
|
||||||
|
translation_response =
|
||||||
|
with_mock System, [:passthrough],
|
||||||
|
cmd: fn "argos-translate_test", ["--from-lang", "nl", "--to-lang", "en", "blabla"], _ ->
|
||||||
|
{"yadayada", 0}
|
||||||
|
end do
|
||||||
|
ArgosTranslate.translate("blabla", "nl", "en")
|
||||||
|
end
|
||||||
|
|
||||||
|
assert {:ok, "nl", "yadayada"} = translation_response
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns a proper error when the executable can't be found" do
|
||||||
|
non_existing_command = "sfqsfgqsefd"
|
||||||
|
clear_config([:argos_translate, :command_argos_translate], non_existing_command)
|
||||||
|
clear_config([:argos_translate, :command_argospm], non_existing_command)
|
||||||
|
|
||||||
|
assert nil == System.find_executable(non_existing_command)
|
||||||
|
|
||||||
|
assert {:error, "ArgosTranslate failed to fetch languages" <> _} = ArgosTranslate.languages()
|
||||||
|
|
||||||
|
assert {:error, "ArgosTranslate failed to translate" <> _} =
|
||||||
|
ArgosTranslate.translate("blabla", "nl", "en")
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue