diff --git a/config/description.exs b/config/description.exs
index 33d959aab4..73ca4809b3 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -3544,7 +3544,8 @@
type: :module,
suggestions: [
Pleroma.Language.Translation.Deepl,
- Pleroma.Language.Translation.Libretranslate
+ Pleroma.Language.Translation.Libretranslate,
+ Pleroma.Language.Translation.TranslateLocally
]
},
%{
@@ -3586,6 +3587,14 @@
label: "LibreTranslate API Key",
type: :string,
suggestions: ["YOUR_API_KEY"]
+ },
+ %{
+ group: {:subgroup, Pleroma.Language.Translation.TranslateLocally},
+ key: :intermediate_language,
+ label:
+ "translateLocally intermediate language (used when direct source->target model is not available)",
+ type: :string,
+ suggestions: ["en"]
}
]
},
diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index 295ba0e1af..ee4188fbce 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -200,10 +200,24 @@ defp check_system_commands!(:ok) do
false
end
+ translation_commands_status =
+ if Pleroma.Language.Translation.missing_dependencies() == [] do
+ true
+ else
+ Logger.error(
+ "The following dependencies required by the currently enabled " <>
+ "translation provider are not installed: " <>
+ inspect(Pleroma.Language.Translation.missing_dependencies())
+ )
+
+ false
+ end
+
if Enum.all?(
[
preview_proxy_commands_status,
- language_detector_commands_status
+ language_detector_commands_status,
+ translation_commands_status
| filter_commands_statuses
],
& &1
diff --git a/lib/pleroma/language/translation.ex b/lib/pleroma/language/translation.ex
index e4916389dd..be796802bc 100644
--- a/lib/pleroma/language/translation.ex
+++ b/lib/pleroma/language/translation.ex
@@ -11,6 +11,16 @@ def configured? do
!!provider and provider.configured?
end
+ def missing_dependencies do
+ provider = get_provider()
+
+ if provider do
+ provider.missing_dependencies()
+ else
+ []
+ end
+ end
+
def translate(text, source_language, target_language) do
cache_key = get_cache_key(text, source_language, target_language)
diff --git a/lib/pleroma/language/translation/deepl.ex b/lib/pleroma/language/translation/deepl.ex
index 4f668fbba5..e027035b4d 100644
--- a/lib/pleroma/language/translation/deepl.ex
+++ b/lib/pleroma/language/translation/deepl.ex
@@ -7,6 +7,8 @@ defmodule Pleroma.Language.Translation.Deepl do
alias Pleroma.Language.Translation.Provider
+ use Provider
+
@behaviour Provider
@name "DeepL"
diff --git a/lib/pleroma/language/translation/libretranslate.ex b/lib/pleroma/language/translation/libretranslate.ex
index b793b166e0..69ecf23b0b 100644
--- a/lib/pleroma/language/translation/libretranslate.ex
+++ b/lib/pleroma/language/translation/libretranslate.ex
@@ -7,6 +7,8 @@ defmodule Pleroma.Language.Translation.Libretranslate do
alias Pleroma.Language.Translation.Provider
+ use Provider
+
@behaviour Provider
@name "LibreTranslate"
diff --git a/lib/pleroma/language/translation/provider.ex b/lib/pleroma/language/translation/provider.ex
index f12cba2cde..533b5355aa 100644
--- a/lib/pleroma/language/translation/provider.ex
+++ b/lib/pleroma/language/translation/provider.ex
@@ -3,6 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.Translation.Provider do
+ alias Pleroma.Language.Translation.Provider
+
+ @callback missing_dependencies() :: [String.t()]
+
@callback configured?() :: boolean()
@callback translate(
@@ -24,4 +28,13 @@ defmodule Pleroma.Language.Translation.Provider do
@callback languages_matrix() :: {:ok, Map.t()} | {:error, atom()}
@callback name() :: String.t()
+
+ defmacro __using__(_opts) do
+ quote do
+ @impl Provider
+ def missing_dependencies, do: []
+
+ defoverridable missing_dependencies: 0
+ end
+ end
end
diff --git a/lib/pleroma/language/translation/translate_locally.ex b/lib/pleroma/language/translation/translate_locally.ex
new file mode 100644
index 0000000000..7eaa95e7b2
--- /dev/null
+++ b/lib/pleroma/language/translation/translate_locally.ex
@@ -0,0 +1,129 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Language.Translation.TranslateLocally do
+ alias Pleroma.Language.Translation.Provider
+
+ use Provider
+
+ @behaviour Provider
+
+ @name "translateLocally"
+
+ @impl Provider
+ def missing_dependencies do
+ if Pleroma.Utils.command_available?("translateLocally") do
+ []
+ else
+ ["translateLocally"]
+ end
+ end
+
+ @impl Provider
+ def configured?, do: is_map(models())
+
+ @impl Provider
+ def translate(content, source_language, target_language) do
+ model =
+ models()
+ |> Map.get(source_language, %{})
+ |> Map.get(target_language)
+
+ models =
+ if model do
+ [model]
+ else
+ [
+ models()
+ |> Map.get(source_language, %{})
+ |> Map.get(intermediary_language()),
+ models()
+ |> Map.get(intermediary_language(), %{})
+ |> Map.get(target_language)
+ ]
+ end
+
+ translated_content =
+ Enum.reduce(models, content, fn model, content ->
+ text_path = Path.join(System.tmp_dir!(), "translateLocally-#{Ecto.UUID.generate()}")
+
+ File.write(text_path, content)
+
+ translated_content =
+ case System.cmd("translateLocally", ["-m", model, "-i", text_path, "--html"]) do
+ {content, _} -> content
+ _ -> nil
+ end
+
+ File.rm(text_path)
+
+ translated_content
+ end)
+
+ {:ok,
+ %{
+ content: translated_content,
+ detected_source_language: source_language,
+ provider: @name
+ }}
+ end
+
+ @impl Provider
+ def supported_languages(:source) do
+ languages =
+ languages_matrix()
+ |> elem(1)
+ |> Map.keys()
+
+ {:ok, languages}
+ end
+
+ @impl Provider
+ def supported_languages(:target) do
+ languages =
+ languages_matrix()
+ |> elem(1)
+ |> Map.values()
+ |> List.flatten()
+ |> Enum.uniq()
+
+ {:ok, languages}
+ end
+
+ @impl Provider
+ def languages_matrix do
+ languages =
+ models()
+ |> Map.to_list()
+ |> Enum.map(fn {key, value} -> {key, Map.keys(value)} end)
+ |> Enum.into(%{})
+
+ matrix =
+ if intermediary_language() do
+ languages
+ |> Map.to_list()
+ |> Enum.map(fn {key, value} ->
+ with_intermediary =
+ (((value ++ languages[intermediary_language()])
+ |> Enum.uniq()) --
+ [key])
+ |> Enum.sort()
+
+ {key, with_intermediary}
+ end)
+ |> Enum.into(%{})
+ else
+ languages
+ end
+
+ {:ok, matrix}
+ end
+
+ @impl Provider
+ def name, do: @name
+
+ defp models, do: Pleroma.Config.get([__MODULE__, :models])
+
+ defp intermediary_language, do: Pleroma.Config.get([__MODULE__, :intermediary_language])
+end
diff --git a/test/pleroma/language/translation/translate_locally_test.exs b/test/pleroma/language/translation/translate_locally_test.exs
new file mode 100644
index 0000000000..51cbd11bd8
--- /dev/null
+++ b/test/pleroma/language/translation/translate_locally_test.exs
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Language.Translation.TranslateLocallyTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Language.Translation.TranslateLocally
+
+ @example_models %{
+ "de" => %{
+ "en" => "de-en-base"
+ },
+ "en" => %{
+ "de" => "en-de-base",
+ "pl" => "en-pl-tiny"
+ },
+ "cs" => %{
+ "en" => "cs-en-base"
+ },
+ "pl" => %{
+ "en" => "pl-en-tiny"
+ }
+ }
+
+ test "it returns languages list" do
+ clear_config([Pleroma.Language.Translation.TranslateLocally, :models], @example_models)
+
+ assert {:ok, languages} = TranslateLocally.supported_languages(:source)
+ assert ["cs", "de", "en", "pl"] = languages |> Enum.sort()
+ end
+
+ describe "it returns languages matrix" do
+ test "without intermediary language" do
+ clear_config([Pleroma.Language.Translation.TranslateLocally, :models], @example_models)
+
+ assert {:ok,
+ %{
+ "cs" => ["en"],
+ "de" => ["en"],
+ "en" => ["de", "pl"],
+ "pl" => ["en"]
+ }} = TranslateLocally.languages_matrix()
+ end
+
+ test "with intermediary language" do
+ clear_config([Pleroma.Language.Translation.TranslateLocally, :models], @example_models)
+ clear_config([Pleroma.Language.Translation.TranslateLocally, :intermediary_language], "en")
+
+ assert {:ok,
+ %{
+ "cs" => ["de", "en", "pl"],
+ "de" => ["en", "pl"],
+ "en" => ["de", "pl"],
+ "pl" => ["de", "en"]
+ }} = TranslateLocally.languages_matrix()
+ end
+ end
+end
diff --git a/test/support/translation_mock.ex b/test/support/translation_mock.ex
index 95da738d10..84ed8f696e 100644
--- a/test/support/translation_mock.ex
+++ b/test/support/translation_mock.ex
@@ -5,6 +5,8 @@
defmodule TranslationMock do
alias Pleroma.Language.Translation.Provider
+ use Provider
+
@behaviour Provider
@name "TranslationMock"