diff --git a/lib/pleroma/language/translation.ex b/lib/pleroma/language/translation.ex index c812385258..05ab898f3e 100644 --- a/lib/pleroma/language/translation.ex +++ b/lib/pleroma/language/translation.ex @@ -37,6 +37,32 @@ def translate(text, source_language, target_language) do end end + def supported_languages(type) when type in [:source, :target] do + provider = get_provider() + + cache_key = "#{type}_languages/#{provider.name()}" + + case @cachex.get(:translations_cache, cache_key) do + {:ok, nil} -> + result = + if !configured?() do + {:error, :not_found} + else + provider.supported_languages(type) + end + + store_result(result, cache_key) + + result + + {:ok, result} -> + {:ok, result} + + {:error, error} -> + {:error, error} + end + end + defp get_provider, do: Pleroma.Config.get([__MODULE__, :provider]) defp get_cache_key(text, source_language, target_language) do diff --git a/lib/pleroma/language/translation/deepl.ex b/lib/pleroma/language/translation/deepl.ex index 325ef0861a..8ce1209cc3 100644 --- a/lib/pleroma/language/translation/deepl.ex +++ b/lib/pleroma/language/translation/deepl.ex @@ -9,14 +9,17 @@ defmodule Pleroma.Language.Translation.Deepl do @behaviour Provider + @name "DeepL" + @impl Provider - def configured? do - not_empty_string(get_base_url()) and not_empty_string(get_api_key()) - end + def configured?, do: not_empty_string(base_url()) and not_empty_string(api_key()) @impl Provider def translate(content, source_language, target_language) do - endpoint = get_endpoint() + endpoint = + base_url() + |> URI.merge("/v2/translate") + |> URI.to_string() case Pleroma.HTTP.post( endpoint <> @@ -30,7 +33,7 @@ def translate(content, source_language, target_language) do "", [ {"Content-Type", "application/x-www-form-urlencoded"}, - {"Authorization", "DeepL-Auth-Key #{get_api_key()}"} + {"Authorization", "DeepL-Auth-Key #{api_key()}"} ] ) do {:ok, %{status: 429}} -> @@ -50,7 +53,7 @@ def translate(content, source_language, target_language) do %{ content: content, detected_source_language: detected_source_language, - provider: "DeepL" + provider: @name }} _ -> @@ -58,17 +61,41 @@ def translate(content, source_language, target_language) do end end - defp get_endpoint do - get_base_url() - |> URI.merge("/v2/translate") - |> URI.to_string() + @impl Provider + def supported_languages(type) when type in [:source, :target] do + endpoint = + base_url() + |> URI.merge("/v2/languages") + |> URI.to_string() + + case Pleroma.HTTP.post( + endpoint <> "?" <> URI.encode_query(%{type: type}), + "", + [ + {"Content-Type", "application/x-www-form-urlencoded"}, + {"Authorization", "DeepL-Auth-Key #{api_key()}"} + ] + ) do + {:ok, %{status: 200} = res} -> + languages = + Jason.decode!(res.body) + |> Enum.map(fn %{"language" => language} -> language |> String.downcase() end) + + {:ok, languages} + + _ -> + {:error, :internal_server_error} + end end - defp get_base_url do + @impl Provider + def name, do: @name + + defp base_url do Pleroma.Config.get([__MODULE__, :base_url]) end - defp get_api_key do + defp api_key do Pleroma.Config.get([__MODULE__, :api_key]) end end diff --git a/lib/pleroma/language/translation/libretranslate.ex b/lib/pleroma/language/translation/libretranslate.ex index 0c1fe17a04..92bde87726 100644 --- a/lib/pleroma/language/translation/libretranslate.ex +++ b/lib/pleroma/language/translation/libretranslate.ex @@ -9,21 +9,21 @@ defmodule Pleroma.Language.Translation.Libretranslate do @behaviour Provider + @name "LibreTranslate" + @impl Provider - def configured?, do: not_empty_string(get_base_url()) + def configured?, do: not_empty_string(base_url()) and not_empty_string(api_key()) @impl Provider def translate(content, source_language, target_language) do - endpoint = endpoint_url() - case Pleroma.HTTP.post( - endpoint, + base_url() <> "/translate", Jason.encode!(%{ q: content, source: source_language |> String.upcase(), target: target_language, format: "html", - api_key: get_api_key() + api_key: api_key() }), [ {"Content-Type", "application/json"} @@ -52,15 +52,29 @@ def translate(content, source_language, target_language) do end end - defp endpoint_url do - get_base_url() <> "/translate" + @impl Provider + def supported_languages(_) do + case Pleroma.HTTP.get(base_url() <> "/languages") do + {:ok, %{status: 200} = res} -> + languages = + Jason.decode!(res.body) + |> Enum.map(fn %{"code" => code} -> code end) + + {:ok, languages} + + _ -> + {:error, :internal_server_error} + end end - defp get_base_url do + @impl Provider + def name, do: @name + + defp base_url do Pleroma.Config.get([__MODULE__, :base_url]) end - defp get_api_key do + defp api_key do Pleroma.Config.get([__MODULE__, :api_key], "") end end diff --git a/lib/pleroma/language/translation/provider.ex b/lib/pleroma/language/translation/provider.ex index a88461a47f..a8b151fd7a 100644 --- a/lib/pleroma/language/translation/provider.ex +++ b/lib/pleroma/language/translation/provider.ex @@ -17,4 +17,9 @@ defmodule Pleroma.Language.Translation.Provider do provider: String.t() }} | {:error, atom()} + + @callback supported_languages(type :: :string | :target) :: + {:ok, [String.t()]} | {:error, atom()} + + @callback name() :: String.t() end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index eb20c669b6..3c9697905b 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -253,7 +253,8 @@ defp pleroma_configuration(instance) do privileged_staff: Config.get([:instance, :privileged_staff]), birthday_required: Config.get([:instance, :birthday_required]), birthday_min_age: Config.get([:instance, :birthday_min_age]), - migration_cooldown_period: Config.get([:instance, :migration_cooldown_period]) + migration_cooldown_period: Config.get([:instance, :migration_cooldown_period]), + translation: supported_languages() }, stats: %{mau: Pleroma.User.active_user_count()}, vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key), @@ -282,6 +283,31 @@ defp pleroma_configuration2(instance) do }) end + defp supported_languages do + enabled = Pleroma.Language.Translation.configured?() + + source_languages = + with true <- enabled, + {:ok, languages} <- Pleroma.Language.Translation.supported_languages(:source) do + languages + else + _ -> nil + end + + target_languages = + with true <- enabled, + {:ok, languages} <- Pleroma.Language.Translation.supported_languages(:target) do + languages + else + _ -> nil + end + + %{ + source_languages: source_languages, + target_languages: target_languages + } + end + defp contact_account(nil), do: nil defp contact_account("@" <> username) do diff --git a/test/fixtures/tesla_mock/deepl-languages-list.json b/test/fixtures/tesla_mock/deepl-languages-list.json new file mode 100644 index 0000000000..03d47d2ec9 --- /dev/null +++ b/test/fixtures/tesla_mock/deepl-languages-list.json @@ -0,0 +1 @@ +[{"language":"BG","name":"Bulgarian","supports_formality":false},{"language":"CS","name":"Czech","supports_formality":false},{"language":"DA","name":"Danish","supports_formality":false},{"language":"DE","name":"German","supports_formality":true},{"language":"EL","name":"Greek","supports_formality":false},{"language":"EN-GB","name":"English (British)","supports_formality":false},{"language":"EN-US","name":"English (American)","supports_formality":false},{"language":"ES","name":"Spanish","supports_formality":true},{"language":"ET","name":"Estonian","supports_formality":false},{"language":"FI","name":"Finnish","supports_formality":false},{"language":"FR","name":"French","supports_formality":true},{"language":"HU","name":"Hungarian","supports_formality":false},{"language":"ID","name":"Indonesian","supports_formality":false},{"language":"IT","name":"Italian","supports_formality":true},{"language":"JA","name":"Japanese","supports_formality":false},{"language":"LT","name":"Lithuanian","supports_formality":false},{"language":"LV","name":"Latvian","supports_formality":false},{"language":"NL","name":"Dutch","supports_formality":true},{"language":"PL","name":"Polish","supports_formality":true},{"language":"PT-BR","name":"Portuguese (Brazilian)","supports_formality":true},{"language":"PT-PT","name":"Portuguese (European)","supports_formality":true},{"language":"RO","name":"Romanian","supports_formality":false},{"language":"RU","name":"Russian","supports_formality":true},{"language":"SK","name":"Slovak","supports_formality":false},{"language":"SL","name":"Slovenian","supports_formality":false},{"language":"SV","name":"Swedish","supports_formality":false},{"language":"TR","name":"Turkish","supports_formality":false},{"language":"UK","name":"Ukrainian","supports_formality":false},{"language":"ZH","name":"Chinese (simplified)","supports_formality":false}] \ No newline at end of file diff --git a/test/pleroma/language/translation/deepl_test.exs b/test/pleroma/language/translation/deepl_test.exs index 0c29b84a4e..3a7265622f 100644 --- a/test/pleroma/language/translation/deepl_test.exs +++ b/test/pleroma/language/translation/deepl_test.exs @@ -24,4 +24,14 @@ test "it translates text" do provider: "DeepL" } = res end + + test "it returns languages list" do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + clear_config([Pleroma.Language.Translation.Deepl, :base_url], "https://api-free.deepl.com") + clear_config([Pleroma.Language.Translation.Deepl, :api_key], "API_KEY") + + assert {:ok, [language | _languages]} = Deepl.supported_languages(:target) + + assert is_binary(language) + end end diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 6f29a820d7..0eaaad2e24 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -1597,6 +1597,15 @@ def post("https://api-free.deepl.com/v2/translate" <> _, _, _, _) do }} end + def post("https://api-free.deepl.com/v2/languages" <> _, _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/deepl-languages-list.json"), + headers: [{"content-type", "application/json"}] + }} + end + def post(url, query, body, headers) do {:error, "Mock response not implemented for POST #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"} diff --git a/test/support/translation_mock.ex b/test/support/translation_mock.ex index 7e618c2639..2047d64261 100644 --- a/test/support/translation_mock.ex +++ b/test/support/translation_mock.ex @@ -7,6 +7,8 @@ defmodule TranslationMock do @behaviour Provider + @name "TranslationMock" + @impl Provider def configured?, do: true @@ -16,7 +18,15 @@ def translate(content, source_language, _target_language) do %{ content: content |> String.reverse(), detected_source_language: source_language, - provider: "TranslationMock" + provider: @name }} end + + @impl Provider + def supported_languages(_) do + ["en", "pl"] + end + + @impl Provider + def name, do: @name end