From 557a7d736a873bf57c3e3e271669c0815fd6fe28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 30 Oct 2022 18:47:41 +0100 Subject: [PATCH 01/18] WIP Translation backends support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/application.ex | 3 +- .../api_spec/operations/status_operation.ex | 60 +++++++++++++++++++ .../controllers/status_controller.ex | 29 ++++++++- .../web/mastodon_api/views/status_view.ex | 8 +++ lib/pleroma/web/router.ex | 1 + 5 files changed, 99 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index de668052f..921384be9 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -182,7 +182,8 @@ defmodule Pleroma.Application do expiration: chat_message_id_idempotency_key_expiration(), limit: 500_000 ), - build_cachex("rel_me", limit: 2500) + build_cachex("rel_me", limit: 2500), + build_cachex("translations", default_ttl: :timer.hours(24), limit: 5_000) ] end diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index ef4e34044..961a2f402 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -409,6 +409,38 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do } end + def translate_operation do + %Operation{ + tags: ["Retrieve status information"], + summary: "Translate status", + description: "Translate status with an external API", + operationId: "StatusController.translate", + security: [%{"oAuth" => ["read:statuses"]}], + parameters: [id_param()], + requestBody: + request_body( + "Parameters", + %Schema{ + type: :object, + properties: %{ + target_language: %Schema{ + type: :string, + nullable: true, + description: "Translation target language." + } + } + }, + required: false + ), + responses: %{ + 200 => Operation.response("Translation", "application/json", translation()) + 400 => Operation.response("Error", "application/json", ApiError) + 404 => Operation.response("Error", "application/json", ApiError) + 503 => Operation.response("Error", "application/json", ApiError) + } + } + end + def favourites_operation do %Operation{ tags: ["Timelines"], @@ -793,4 +825,32 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do } } end + + defp translation do + %Schema{ + title: "StatusTranslation", + description: "Represents status translation with related information.", + type: :object, + required: [:content, :detected_source_language, :provider], + properties: %{ + content: %Schema{ + type: :string, + description: "Translated status content" + }, + detected_source_language: %Schema{ + type: :string, + description: "Detected source language" + }, + provider: %Schema{ + type: :string, + description: "Translation provider service name" + } + }, + example: %{ + "content" => "Software für die nächste Generation der sozialen Medien.", + "detected_source_language" => "en", + "provider" => "Deepl" + } + } + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 5aa7bddf0..4e1651596 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Object alias Pleroma.Repo alias Pleroma.ScheduledActivity + alias Pleroma.Translation alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility @@ -44,6 +45,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do ] ) + plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :translate) + plug( OAuthScopesPlug, %{scopes: ["write:statuses"]} @@ -85,7 +88,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do %{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark] ) - @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a + @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete translate)a plug( RateLimiter, @@ -554,6 +557,30 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end end + @doc "POST /api/v1/statuses/:id/translate" + def translate(%{body_params: params, assigns: %{user: user}} = conn, %{id: status_id}) do + with %Activity{object: object} <- Activity.get_by_id_with_object(status_id), + {:language, language} when is_binary(language) <- + {:language, Map.get(params, :target_language) || user.language}, + {:ok, result} <- + Translation.translate( + object.data["content"], + object.data["language"], + language + ) do + render(conn, "translation.json", result) + else + {:language, nil} -> + render_error(conn, :bad_request, "Language not specified") + + {:error, :not_found} -> + render_error(conn, :not_found, "Translation service not configured") + + {:error, error} when error in [:unexpected_response, :quota_exceeded, :too_many_requests] -> + render_error(conn, :service_unavailable, "Translation service not available") + end + end + @doc "GET /api/v1/favourites" def favourites( %{assigns: %{user: %User{} = user}, private: %{open_api_spex: %{params: params}}} = conn, diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index bae3fd1f8..026429d56 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -656,6 +656,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end + def render("translation.json", %{ + content: content, + detected_source_language: detected_source_language, + provider: provider + }) do + %{content: content, detected_source_language: detected_source_language, provider: provider} + end + def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do object = Object.normalize(activity, fetch: false) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8ba845364..5c949fc95 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -718,6 +718,7 @@ defmodule Pleroma.Web.Router do post("/statuses/:id/unbookmark", StatusController, :unbookmark) post("/statuses/:id/mute", StatusController, :mute_conversation) post("/statuses/:id/unmute", StatusController, :unmute_conversation) + post("/statuses/:id/translate", StatusController, :translate) post("/push/subscription", SubscriptionController, :create) get("/push/subscription", SubscriptionController, :show) From 90f91168f7ed9af6a4141fafa11417a6419a0c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 30 Oct 2022 18:52:26 +0100 Subject: [PATCH 02/18] Expose translation service availability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- config/description.exs | 13 ++++ lib/pleroma/translation.ex | 54 +++++++++++++ lib/pleroma/translation/deepl.ex | 75 +++++++++++++++++++ lib/pleroma/translation/libretranslate.ex | 66 ++++++++++++++++ lib/pleroma/translation/service.ex | 20 +++++ .../api_spec/operations/status_operation.ex | 6 +- .../web/mastodon_api/views/instance_view.ex | 3 +- 7 files changed, 233 insertions(+), 4 deletions(-) create mode 100644 lib/pleroma/translation.ex create mode 100644 lib/pleroma/translation/deepl.ex create mode 100644 lib/pleroma/translation/libretranslate.ex create mode 100644 lib/pleroma/translation/service.ex diff --git a/config/description.exs b/config/description.exs index 6c13bde31..0efea0882 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3523,5 +3523,18 @@ config :pleroma, :config_description, [ suggestion: [100_000] } ] + }, + %{ + group: :pleroma, + key: Pleroma.Translation, + type: :group, + description: "Translation providers", + children: [ + %{ + key: Pleroma.Translation, + type: :service, + suggestions: [Pleroma.Translation.DeepL, Pleroma.Translation.LibreTranslate] + } + ] } ] diff --git a/lib/pleroma/translation.ex b/lib/pleroma/translation.ex new file mode 100644 index 000000000..112f12cab --- /dev/null +++ b/lib/pleroma/translation.ex @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Translation do + @cache_ttl 86_400_000 + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + + def configured? do + service = get_service() + + !!service and service.configured? + end + + def translate(text, source_language, target_language) do + cache_key = get_cache_key(text, source_language, target_language) + + case @cachex.get(:translations_cache, cache_key) do + {:ok, nil} -> + service = get_service() + + result = + if !service or !service.configured? do + {:error, :not_found} + else + service.translate(text, source_language, target_language) + end + + store_result(result, cache_key) + + result + + {:ok, result} -> + {:ok, result} + + {:error, error} -> + {:error, error} + end + end + + defp get_service, do: Pleroma.Config.get([__MODULE__, :service]) + + defp get_cache_key(text, source_language, target_language) do + "#{source_language}/#{target_language}/#{content_hash(text)}" + end + + defp store_result({:ok, result}, cache_key) do + @cachex.put(:translations_cache, cache_key, result, ttl: @cache_ttl) + end + + defp store_result(_, _), do: nil + + defp content_hash(text), do: :crypto.hash(:sha256, text) |> Base.encode64() +end diff --git a/lib/pleroma/translation/deepl.ex b/lib/pleroma/translation/deepl.ex new file mode 100644 index 000000000..76fff4693 --- /dev/null +++ b/lib/pleroma/translation/deepl.ex @@ -0,0 +1,75 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Translation.DeepL do + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] + + alias Pleroma.Translation.Service + + @behaviour Service + + @impl Service + def configured? do + not_empty_string(get_plan()) and not_empty_string(get_api_key()) + end + + @impl Service + def translate(content, source_language, target_language) do + endpoint = endpoint_url() + + case Pleroma.HTTP.post( + endpoint <> + "?" <> + URI.encode_query(%{ + text: content, + source_lang: source_language |> String.upcase(), + target_lang: target_language, + tag_handling: "html" + }), + "", + [ + {"Content-Type", "application/x-www-form-urlencoded"}, + {"Authorization", "DeepL-Auth-Key #{get_api_key()}"} + ] + ) do + {:ok, %{status: 429}} -> + {:error, :too_many_requests} + + {:ok, %{status: 456}} -> + {:error, :quota_exceeded} + + {:ok, %{status: 200} = res} -> + %{ + "translations" => [ + %{"text" => content, "detected_source_language" => detected_source_language} + ] + } = Jason.decode!(res.body) + + {:ok, + %{ + content: content, + detected_source_language: detected_source_language, + provider: "DeepL" + }} + + _ -> + {:error, :internal_server_error} + end + end + + defp endpoint_url do + case get_plan() do + "free" -> "https://api-free.deepl.com/v2/translate" + _ -> "https://api.deepl.com/v2/translate" + end + end + + defp get_plan do + Pleroma.Config.get([__MODULE__, :plan]) + end + + defp get_api_key do + Pleroma.Config.get([__MODULE__, :api_key]) + end +end diff --git a/lib/pleroma/translation/libretranslate.ex b/lib/pleroma/translation/libretranslate.ex new file mode 100644 index 000000000..049053d43 --- /dev/null +++ b/lib/pleroma/translation/libretranslate.ex @@ -0,0 +1,66 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Translation.LibreTranslate do + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] + + alias Pleroma.Translation.Service + + @behaviour Service + + @impl Service + def configured?, do: not_empty_string(get_base_url()) + + @impl Service + def translate(content, source_language, target_language) do + endpoint = endpoint_url() + + case Pleroma.HTTP.post( + endpoint, + Jason.encode!(%{ + q: content, + source: source_language |> String.upcase(), + target: target_language, + format: "html", + api_key: get_api_key() + }), + [ + {"Content-Type", "application/json"} + ] + ) do + {:ok, %{status: 429}} -> + {:error, :too_many_requests} + + {:ok, %{status: 403}} -> + {:error, :quota_exceeded} + + {:ok, %{status: 200} = res} -> + %{ + "translatedText" => content + } = Jason.decode!(res.body) + + {:ok, + %{ + content: content, + detected_source_language: source_language, + provider: "LibreTranslate" + }} + + _ -> + {:error, :internal_server_error} + end + end + + defp endpoint_url do + get_base_url() <> "/translate" + end + + defp get_base_url do + Pleroma.Config.get([__MODULE__, :base_url]) + end + + defp get_api_key do + Pleroma.Config.get([__MODULE__, :api_key], "") + end +end diff --git a/lib/pleroma/translation/service.ex b/lib/pleroma/translation/service.ex new file mode 100644 index 000000000..55e995e92 --- /dev/null +++ b/lib/pleroma/translation/service.ex @@ -0,0 +1,20 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Translation.Service do + @callback configured?() :: boolean() + + @callback translate( + content :: String.t(), + source_language :: String.t(), + target_language :: String.t() + ) :: + {:ok, + %{ + content: String.t(), + detected_source_language: String.t(), + provider: String.t() + }} + | {:error, atom()} +end diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 961a2f402..00529bc47 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -433,9 +433,9 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do required: false ), responses: %{ - 200 => Operation.response("Translation", "application/json", translation()) - 400 => Operation.response("Error", "application/json", ApiError) - 404 => Operation.response("Error", "application/json", ApiError) + 200 => Operation.response("Translation", "application/json", translation()), + 400 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError), 503 => Operation.response("Error", "application/json", ApiError) } } diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index e4c6c81e1..f48e80fd6 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -202,7 +202,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do }, vapid: %{ public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) - } + }, + translation: %{enabled: Pleroma.Translation.configured?} }) end From 90f590788cffd154f9d2b40e5e644ad533883195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 30 Oct 2022 21:06:31 +0100 Subject: [PATCH 03/18] Add tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/translation.ex | 3 +- .../web/mastodon_api/views/instance_view.ex | 7 +++- test/pleroma/translation_test.ex | 29 ++++++++++++++ .../controllers/status_controller_test.exs | 40 +++++++++++++++++++ test/support/translation_mock.ex | 22 ++++++++++ 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 test/pleroma/translation_test.ex create mode 100644 test/support/translation_mock.ex diff --git a/lib/pleroma/translation.ex b/lib/pleroma/translation.ex index 112f12cab..7efec62a6 100644 --- a/lib/pleroma/translation.ex +++ b/lib/pleroma/translation.ex @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Translation do - @cache_ttl 86_400_000 @cachex Pleroma.Config.get([:cachex, :provider], Cachex) def configured? do @@ -45,7 +44,7 @@ defmodule Pleroma.Translation do end defp store_result({:ok, result}, cache_key) do - @cachex.put(:translations_cache, cache_key, result, ttl: @cache_ttl) + @cachex.put(:translations_cache, cache_key, result) end defp store_result(_, _), do: nil diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index f48e80fd6..63edd4b30 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -129,7 +129,10 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "profile_directory" end, "pleroma:get:main/ostatus", - "pleroma:group_actors" + "pleroma:group_actors", + if Pleroma.Translation.configured?() do + "translation" + end ] |> Enum.filter(& &1) end @@ -203,7 +206,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do vapid: %{ public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) }, - translation: %{enabled: Pleroma.Translation.configured?} + translation: %{enabled: Pleroma.Translation.configured?()} }) end diff --git a/test/pleroma/translation_test.ex b/test/pleroma/translation_test.ex new file mode 100644 index 000000000..2ae7856ee --- /dev/null +++ b/test/pleroma/translation_test.ex @@ -0,0 +1,29 @@ +defmodule Pleroma.TranslationTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Translation + # use Oban.Testing, repo: Pleroma.Repo + + setup do: clear_config([Pleroma.Translation, :service], TranslationMock) + + test "it translates text" do + assert {:ok, + %{ + content: "txet emos", + detected_source_language: _, + provider: _ + }} = Translation.translate("some text", "en", "uk") + end + + test "it stores translation result in cache" do + Translation.translate("some text", "en", "uk") + + assert {:ok, result} = + Cachex.get( + :translations_cache, + "en/uk/#{:crypto.hash(:sha256, "some text") |> Base.encode64()}" + ) + + assert result.content == "txet emos" + end +end diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index f95f15ec3..fd2f3e11c 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -2550,4 +2550,44 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do |> json_response_and_validate_schema(:not_found) end end + + describe "translating statuses" do + setup do: clear_config([Pleroma.Translation, :service], TranslationMock) + + test "it translates a status to user language" do + user = insert(:user, language: "fr") + %{conn: conn, user: user} = oauth_access(["read:statuses"], user: user) + another_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(another_user, %{ + status: "Cześć!", + visibility: "public", + language: "pl" + }) + + response = + conn + |> post("/api/v1/statuses/#{activity.id}/translate") + |> json_response_and_validate_schema(200) + + assert response == %{"content" => "!ćśezC", "detected_source_language" => "pl", "provider" => "TranslationMock"} + end + + test "it returns an error if no target language provided" do + %{conn: conn, user: user} = oauth_access(["read:statuses"]) + another_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(another_user, %{ + status: "Cześć!", + language: "pl" + }) + + response = + conn + |> post("/api/v1/statuses/#{activity.id}/translate") + |> json_response_and_validate_schema(400) + end + end end diff --git a/test/support/translation_mock.ex b/test/support/translation_mock.ex new file mode 100644 index 000000000..8da2116e8 --- /dev/null +++ b/test/support/translation_mock.ex @@ -0,0 +1,22 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule TranslationMock do + alias Pleroma.Translation.Service + + @behaviour Service + + @impl Service + def configured?, do: true + + @impl Service + def translate(content, source_language, _target_language) do + {:ok, + %{ + content: content |> String.reverse(), + detected_source_language: source_language, + provider: "TranslationMock" + }} + end +end From aa429f6e6a059e58af2550d87f8272dea92acc9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 30 Oct 2022 21:57:05 +0100 Subject: [PATCH 04/18] Do not translate non-public statuses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../controllers/status_controller.ex | 5 +++++ .../controllers/status_controller_test.exs | 22 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 4e1651596..239e15005 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -560,6 +560,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do @doc "POST /api/v1/statuses/:id/translate" def translate(%{body_params: params, assigns: %{user: user}} = conn, %{id: status_id}) do with %Activity{object: object} <- Activity.get_by_id_with_object(status_id), + {:visibility, visibility} when visibility in ["public", "unlisted"] <- + {:visibility, Visibility.get_visibility(object)}, {:language, language} when is_binary(language) <- {:language, Map.get(params, :target_language) || user.language}, {:ok, result} <- @@ -573,6 +575,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do {:language, nil} -> render_error(conn, :bad_request, "Language not specified") + {:visibility, _} -> + render_error(conn, :not_found, "Record not found") + {:error, :not_found} -> render_error(conn, :not_found, "Translation service not configured") diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index fd2f3e11c..f81864b6c 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -2571,7 +2571,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do |> post("/api/v1/statuses/#{activity.id}/translate") |> json_response_and_validate_schema(200) - assert response == %{"content" => "!ćśezC", "detected_source_language" => "pl", "provider" => "TranslationMock"} + assert response == %{ + "content" => "!ćśezC", + "detected_source_language" => "pl", + "provider" => "TranslationMock" + } end test "it returns an error if no target language provided" do @@ -2589,5 +2593,21 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do |> post("/api/v1/statuses/#{activity.id}/translate") |> json_response_and_validate_schema(400) end + + test "it doesn't translate non-public statuses" do + %{conn: conn, user: user} = oauth_access(["read:statuses"]) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Cześć!", + visibility: "private", + language: "pl" + }) + + response = + conn + |> post("/api/v1/statuses/#{activity.id}/translate") + |> json_response_and_validate_schema(404) + end end end From 066ec8fe955b9ff1e3cf15a76a8f2c4968015213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 30 Oct 2022 22:57:20 +0100 Subject: [PATCH 05/18] Update description.exs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- config/description.exs | 34 +++++++++++++++++++++-- lib/pleroma/translation/deepl.ex | 4 +-- lib/pleroma/translation/libretranslate.ex | 2 +- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/config/description.exs b/config/description.exs index 0efea0882..bdf2fc2f3 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3531,9 +3531,37 @@ config :pleroma, :config_description, [ description: "Translation providers", children: [ %{ - key: Pleroma.Translation, - type: :service, - suggestions: [Pleroma.Translation.DeepL, Pleroma.Translation.LibreTranslate] + key: :service, + type: :module, + suggestions: [Pleroma.Translation.Deepl, Pleroma.Translation.Libretranslate] + }, + %{ + group: {:subgroup, Pleroma.Translation.Deepl}, + key: :plan, + label: "DeepL plan", + type: {:dropdown, :atom}, + suggestions: [:free, :pro] + }, + %{ + group: {:subgroup, Pleroma.Translation.Deepl}, + key: :api_key, + label: "DeepL API Key", + type: :string, + suggestions: ["YOUR_API_KEY"] + }, + %{ + group: {:subgroup, Pleroma.Translation.Libretranslate}, + key: :base_url, + label: "LibreTranslate plan", + type: :string, + suggestions: ["https://libretranslate.com"] + }, + %{ + group: {:subgroup, Pleroma.Translation.Libretranslate}, + key: :api_key, + label: "LibreTranslate API Key", + type: :string, + suggestions: ["YOUR_API_KEY"] } ] } diff --git a/lib/pleroma/translation/deepl.ex b/lib/pleroma/translation/deepl.ex index 76fff4693..944dab8ec 100644 --- a/lib/pleroma/translation/deepl.ex +++ b/lib/pleroma/translation/deepl.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Translation.DeepL do +defmodule Pleroma.Translation.Deepl do import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] alias Pleroma.Translation.Service @@ -60,7 +60,7 @@ defmodule Pleroma.Translation.DeepL do defp endpoint_url do case get_plan() do - "free" -> "https://api-free.deepl.com/v2/translate" + :free -> "https://api-free.deepl.com/v2/translate" _ -> "https://api.deepl.com/v2/translate" end end diff --git a/lib/pleroma/translation/libretranslate.ex b/lib/pleroma/translation/libretranslate.ex index 049053d43..9c9b4b9b5 100644 --- a/lib/pleroma/translation/libretranslate.ex +++ b/lib/pleroma/translation/libretranslate.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Translation.LibreTranslate do +defmodule Pleroma.Translation.Libretranslate do import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] alias Pleroma.Translation.Service From 2b739faa7edc69781eab85da4f122bad05d0576d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 31 Oct 2022 21:58:10 +0100 Subject: [PATCH 06/18] Rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- config/description.exs | 17 ++++++++++------- lib/pleroma/{ => language}/translation.ex | 4 ++-- lib/pleroma/{ => language}/translation/deepl.ex | 10 +++++----- .../translation/libretranslate.ex | 10 +++++----- .../translation/provider.ex} | 2 +- .../controllers/status_controller.ex | 2 +- .../web/mastodon_api/views/instance_view.ex | 4 ++-- test/pleroma/{ => language}/translation_test.ex | 6 +++--- .../controllers/status_controller_test.exs | 2 +- test/support/translation_mock.ex | 8 ++++---- 10 files changed, 34 insertions(+), 31 deletions(-) rename lib/pleroma/{ => language}/translation.ex (92%) rename lib/pleroma/{ => language}/translation/deepl.ex (92%) rename lib/pleroma/{ => language}/translation/libretranslate.ex (90%) rename lib/pleroma/{translation/service.ex => language/translation/provider.ex} (92%) rename test/pleroma/{ => language}/translation_test.ex (79%) diff --git a/config/description.exs b/config/description.exs index bdf2fc2f3..cc09e2991 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3526,38 +3526,41 @@ config :pleroma, :config_description, [ }, %{ group: :pleroma, - key: Pleroma.Translation, + key: Pleroma.Language.Translation, type: :group, description: "Translation providers", children: [ %{ - key: :service, + key: :provider, type: :module, - suggestions: [Pleroma.Translation.Deepl, Pleroma.Translation.Libretranslate] + suggestions: [ + Pleroma.Language.Translation.Deepl, + Pleroma.Language.Translation.Libretranslate + ] }, %{ - group: {:subgroup, Pleroma.Translation.Deepl}, + group: {:subgroup, Pleroma.Language.Translation.Deepl}, key: :plan, label: "DeepL plan", type: {:dropdown, :atom}, suggestions: [:free, :pro] }, %{ - group: {:subgroup, Pleroma.Translation.Deepl}, + group: {:subgroup, Pleroma.Language.Translation.Deepl}, key: :api_key, label: "DeepL API Key", type: :string, suggestions: ["YOUR_API_KEY"] }, %{ - group: {:subgroup, Pleroma.Translation.Libretranslate}, + group: {:subgroup, Pleroma.Language.Translation.Libretranslate}, key: :base_url, label: "LibreTranslate plan", type: :string, suggestions: ["https://libretranslate.com"] }, %{ - group: {:subgroup, Pleroma.Translation.Libretranslate}, + group: {:subgroup, Pleroma.Language.Translation.Libretranslate}, key: :api_key, label: "LibreTranslate API Key", type: :string, diff --git a/lib/pleroma/translation.ex b/lib/pleroma/language/translation.ex similarity index 92% rename from lib/pleroma/translation.ex rename to lib/pleroma/language/translation.ex index 7efec62a6..c9cd9d2dd 100644 --- a/lib/pleroma/translation.ex +++ b/lib/pleroma/language/translation.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Translation do +defmodule Pleroma.Language.Translation do @cachex Pleroma.Config.get([:cachex, :provider], Cachex) def configured? do @@ -37,7 +37,7 @@ defmodule Pleroma.Translation do end end - defp get_service, do: Pleroma.Config.get([__MODULE__, :service]) + defp get_service, do: Pleroma.Config.get([__MODULE__, :provider]) defp get_cache_key(text, source_language, target_language) do "#{source_language}/#{target_language}/#{content_hash(text)}" diff --git a/lib/pleroma/translation/deepl.ex b/lib/pleroma/language/translation/deepl.ex similarity index 92% rename from lib/pleroma/translation/deepl.ex rename to lib/pleroma/language/translation/deepl.ex index 944dab8ec..81048378c 100644 --- a/lib/pleroma/translation/deepl.ex +++ b/lib/pleroma/language/translation/deepl.ex @@ -2,19 +2,19 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Translation.Deepl do +defmodule Pleroma.Language.Translation.Deepl do import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] - alias Pleroma.Translation.Service + alias Pleroma.Language.Translation.Provider - @behaviour Service + @behaviour Provider - @impl Service + @impl Provider def configured? do not_empty_string(get_plan()) and not_empty_string(get_api_key()) end - @impl Service + @impl Provider def translate(content, source_language, target_language) do endpoint = endpoint_url() diff --git a/lib/pleroma/translation/libretranslate.ex b/lib/pleroma/language/translation/libretranslate.ex similarity index 90% rename from lib/pleroma/translation/libretranslate.ex rename to lib/pleroma/language/translation/libretranslate.ex index 9c9b4b9b5..0c1fe17a0 100644 --- a/lib/pleroma/translation/libretranslate.ex +++ b/lib/pleroma/language/translation/libretranslate.ex @@ -2,17 +2,17 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Translation.Libretranslate do +defmodule Pleroma.Language.Translation.Libretranslate do import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] - alias Pleroma.Translation.Service + alias Pleroma.Language.Translation.Provider - @behaviour Service + @behaviour Provider - @impl Service + @impl Provider def configured?, do: not_empty_string(get_base_url()) - @impl Service + @impl Provider def translate(content, source_language, target_language) do endpoint = endpoint_url() diff --git a/lib/pleroma/translation/service.ex b/lib/pleroma/language/translation/provider.ex similarity index 92% rename from lib/pleroma/translation/service.ex rename to lib/pleroma/language/translation/provider.ex index 55e995e92..a88461a47 100644 --- a/lib/pleroma/translation/service.ex +++ b/lib/pleroma/language/translation/provider.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Translation.Service do +defmodule Pleroma.Language.Translation.Provider do @callback configured?() :: boolean() @callback translate( diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 239e15005..733f33e13 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -12,10 +12,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Activity alias Pleroma.Bookmark + alias Pleroma.Language.Translation alias Pleroma.Object alias Pleroma.Repo alias Pleroma.ScheduledActivity - alias Pleroma.Translation alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 63edd4b30..f98cf801f 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -130,7 +130,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do end, "pleroma:get:main/ostatus", "pleroma:group_actors", - if Pleroma.Translation.configured?() do + if Pleroma.Language.Translation.configured?() do "translation" end ] @@ -206,7 +206,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do vapid: %{ public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) }, - translation: %{enabled: Pleroma.Translation.configured?()} + translation: %{enabled: Pleroma.Language.Translation.configured?()} }) end diff --git a/test/pleroma/translation_test.ex b/test/pleroma/language/translation_test.ex similarity index 79% rename from test/pleroma/translation_test.ex rename to test/pleroma/language/translation_test.ex index 2ae7856ee..ecab3d20f 100644 --- a/test/pleroma/translation_test.ex +++ b/test/pleroma/language/translation_test.ex @@ -1,10 +1,10 @@ -defmodule Pleroma.TranslationTest do +defmodule Pleroma.Language.TranslationTest do use Pleroma.Web.ConnCase - alias Pleroma.Translation + alias Pleroma.Language.Translation # use Oban.Testing, repo: Pleroma.Repo - setup do: clear_config([Pleroma.Translation, :service], TranslationMock) + setup do: clear_config([Pleroma.Language.Translation, :provider], TranslationMock) test "it translates text" do assert {:ok, diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index f81864b6c..f05f4191d 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -2552,7 +2552,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do end describe "translating statuses" do - setup do: clear_config([Pleroma.Translation, :service], TranslationMock) + setup do: clear_config([Pleroma.Language.Translation, :provider], TranslationMock) test "it translates a status to user language" do user = insert(:user, language: "fr") diff --git a/test/support/translation_mock.ex b/test/support/translation_mock.ex index 8da2116e8..7e618c263 100644 --- a/test/support/translation_mock.ex +++ b/test/support/translation_mock.ex @@ -3,14 +3,14 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule TranslationMock do - alias Pleroma.Translation.Service + alias Pleroma.Language.Translation.Provider - @behaviour Service + @behaviour Provider - @impl Service + @impl Provider def configured?, do: true - @impl Service + @impl Provider def translate(content, source_language, _target_language) do {:ok, %{ From fedae008c8b4a017b56a76c9a3b18bc031e520c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 5 Nov 2022 21:41:58 +0100 Subject: [PATCH 07/18] Deepl: use :base_url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- config/description.exs | 10 +++---- lib/pleroma/language/translation/deepl.ex | 15 +++-------- .../language/translation/deepl_test.ex | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 test/pleroma/language/translation/deepl_test.ex diff --git a/config/description.exs b/config/description.exs index cc09e2991..b1730bab3 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3540,10 +3540,10 @@ config :pleroma, :config_description, [ }, %{ group: {:subgroup, Pleroma.Language.Translation.Deepl}, - key: :plan, - label: "DeepL plan", - type: {:dropdown, :atom}, - suggestions: [:free, :pro] + key: :base_url, + label: "DeepL base URL", + type: :string, + suggestions: ["https://api-free.deepl.com", "https://api.deepl.com"] }, %{ group: {:subgroup, Pleroma.Language.Translation.Deepl}, @@ -3555,7 +3555,7 @@ config :pleroma, :config_description, [ %{ group: {:subgroup, Pleroma.Language.Translation.Libretranslate}, key: :base_url, - label: "LibreTranslate plan", + label: "LibreTranslate instance URL", type: :string, suggestions: ["https://libretranslate.com"] }, diff --git a/lib/pleroma/language/translation/deepl.ex b/lib/pleroma/language/translation/deepl.ex index 81048378c..5a3474090 100644 --- a/lib/pleroma/language/translation/deepl.ex +++ b/lib/pleroma/language/translation/deepl.ex @@ -11,12 +11,12 @@ defmodule Pleroma.Language.Translation.Deepl do @impl Provider def configured? do - not_empty_string(get_plan()) and not_empty_string(get_api_key()) + is_atom(get_base_url()) and not_empty_string(get_api_key()) end @impl Provider def translate(content, source_language, target_language) do - endpoint = endpoint_url() + endpoint = get_base_url() case Pleroma.HTTP.post( endpoint <> @@ -58,15 +58,8 @@ defmodule Pleroma.Language.Translation.Deepl do end end - defp endpoint_url do - case get_plan() do - :free -> "https://api-free.deepl.com/v2/translate" - _ -> "https://api.deepl.com/v2/translate" - end - end - - defp get_plan do - Pleroma.Config.get([__MODULE__, :plan]) + defp get_base_url do + Pleroma.Config.get([__MODULE__, :base_url]) end defp get_api_key do diff --git a/test/pleroma/language/translation/deepl_test.ex b/test/pleroma/language/translation/deepl_test.ex new file mode 100644 index 000000000..0c29b84a4 --- /dev/null +++ b/test/pleroma/language/translation/deepl_test.ex @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Language.Translation.DeeplTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Language.Translation.Deepl + + test "it translates text" 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") + + {:ok, res} = + Deepl.translate( + "USUNĄĆ ŚLEDZIKA!Wklej to na swojego śledzika. Jeżeli uzbieramy 70% użytkowników nk...to usuną śledzika!!!", + "pl", + "en" + ) + + assert %{ + detected_source_language: "PL", + provider: "DeepL" + } = res + end +end From f0eb8e0b0c637104acaf95d8387a0d8f807e964f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 25 Apr 2024 23:50:11 +0200 Subject: [PATCH 08/18] Add tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- test/fixtures/tesla_mock/deepl-translation.json | 1 + test/support/http_request_mock.ex | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 test/fixtures/tesla_mock/deepl-translation.json diff --git a/test/fixtures/tesla_mock/deepl-translation.json b/test/fixtures/tesla_mock/deepl-translation.json new file mode 100644 index 000000000..fef7bb215 --- /dev/null +++ b/test/fixtures/tesla_mock/deepl-translation.json @@ -0,0 +1 @@ +{"translations":[{"detected_source_language":"PL","text":"REMOVE THE FOLLOWER!Paste this on your follower. If we get 70% of nk users...they will remove the follower!!!"}]} \ No newline at end of file diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index f4b6f1f9f..c5f29e0b8 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -1569,6 +1569,15 @@ defmodule HttpRequestMock do }} end + def post("https://api-free.deepl.com/v2/translate" <> _, _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/deepl-translation.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)}"} From 28f8bb00d8d6770732a7985aa03d326e601d3694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 8 Nov 2022 23:09:42 +0100 Subject: [PATCH 09/18] Add supported languages list to /api/v2/instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/language/translation.ex | 38 +++++++++++--- lib/pleroma/language/translation/deepl.ex | 49 ++++++++++++++++--- .../language/translation/libretranslate.ex | 32 ++++++++---- lib/pleroma/language/translation/provider.ex | 5 ++ .../web/mastodon_api/views/instance_view.ex | 24 ++++++++- .../tesla_mock/deepl-languages-list.json | 1 + .../language/translation/deepl_test.ex | 10 ++++ test/support/http_request_mock.ex | 9 ++++ test/support/translation_mock.ex | 12 ++++- 9 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/tesla_mock/deepl-languages-list.json diff --git a/lib/pleroma/language/translation.ex b/lib/pleroma/language/translation.ex index c9cd9d2dd..05ab898f3 100644 --- a/lib/pleroma/language/translation.ex +++ b/lib/pleroma/language/translation.ex @@ -6,9 +6,9 @@ defmodule Pleroma.Language.Translation do @cachex Pleroma.Config.get([:cachex, :provider], Cachex) def configured? do - service = get_service() + provider = get_provider() - !!service and service.configured? + !!provider and provider.configured? end def translate(text, source_language, target_language) do @@ -16,13 +16,13 @@ defmodule Pleroma.Language.Translation do case @cachex.get(:translations_cache, cache_key) do {:ok, nil} -> - service = get_service() + provider = get_provider() result = - if !service or !service.configured? do + if !configured?() do {:error, :not_found} else - service.translate(text, source_language, target_language) + provider.translate(text, source_language, target_language) end store_result(result, cache_key) @@ -37,7 +37,33 @@ defmodule Pleroma.Language.Translation do end end - defp get_service, do: Pleroma.Config.get([__MODULE__, :provider]) + 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 "#{source_language}/#{target_language}/#{content_hash(text)}" diff --git a/lib/pleroma/language/translation/deepl.ex b/lib/pleroma/language/translation/deepl.ex index 5a3474090..8ce1209cc 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 - is_atom(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_base_url() + endpoint = + base_url() + |> URI.merge("/v2/translate") + |> URI.to_string() case Pleroma.HTTP.post( endpoint <> @@ -30,7 +33,7 @@ defmodule Pleroma.Language.Translation.Deepl 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 @@ defmodule Pleroma.Language.Translation.Deepl do %{ content: content, detected_source_language: detected_source_language, - provider: "DeepL" + provider: @name }} _ -> @@ -58,11 +61,41 @@ defmodule Pleroma.Language.Translation.Deepl do end end - defp get_base_url do + @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 + + @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 0c1fe17a0..92bde8772 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 @@ defmodule Pleroma.Language.Translation.Libretranslate 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 a88461a47..a8b151fd7 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 f98cf801f..8c2462c80 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -206,10 +206,32 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do vapid: %{ public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) }, - translation: %{enabled: Pleroma.Language.Translation.configured?()} + translation: translation_config() }) end + defp translation_config do + enabled = Pleroma.Language.Translation.configured?() + + source_languages = + case Pleroma.Language.Translation.supported_languages(:source) do + {:ok, languages} -> languages + _ -> nil + end + + target_languages = + case Pleroma.Language.Translation.supported_languages(:target) do + {:ok, languages} -> languages + _ -> nil + end + + %{ + enabled: enabled, + source_languages: source_languages, + target_languages: target_languages + } + end + defp pleroma_configuration(instance) do %{ metadata: %{ 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 000000000..03d47d2ec --- /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.ex b/test/pleroma/language/translation/deepl_test.ex index 0c29b84a4..3a7265622 100644 --- a/test/pleroma/language/translation/deepl_test.ex +++ b/test/pleroma/language/translation/deepl_test.ex @@ -24,4 +24,14 @@ defmodule Pleroma.Language.Translation.DeeplTest 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 c5f29e0b8..771336b6f 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -1578,6 +1578,15 @@ defmodule HttpRequestMock 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 7e618c263..2047d6426 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 @@ defmodule TranslationMock 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 From 4696487f1f34f76735a24df628c8c15f3ba5ecfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 10 Nov 2022 22:15:49 +0100 Subject: [PATCH 10/18] Fix instance view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 8c2462c80..f81d33f8d 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -214,14 +214,18 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do enabled = Pleroma.Language.Translation.configured?() source_languages = - case Pleroma.Language.Translation.supported_languages(:source) do - {:ok, languages} -> languages + with true <- enabled, + {:ok, languages} <- Pleroma.Language.Translation.supported_languages(:source) do + languages + else _ -> nil end target_languages = - case Pleroma.Language.Translation.supported_languages(:target) do - {:ok, languages} -> languages + with true <- enabled, + {:ok, languages} <- Pleroma.Language.Translation.supported_languages(:target) do + languages + else _ -> nil end From 7fca35f4fd9ca396761f236119936978b63120a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 10 Dec 2022 21:21:38 +0100 Subject: [PATCH 11/18] InstanceView: Move supported languages to pleroma.metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../web/mastodon_api/views/instance_view.ex | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index f81d33f8d..b6b99c477 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -206,36 +206,10 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do vapid: %{ public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) }, - translation: translation_config() + translation: %{enabled: Pleroma.Language.Translation.configured?()} }) end - defp translation_config 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 - - %{ - enabled: enabled, - source_languages: source_languages, - target_languages: target_languages - } - end - defp pleroma_configuration(instance) do %{ metadata: %{ @@ -245,7 +219,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do fields_limits: fields_limits(), post_formats: Config.get([:instance, :allowed_post_formats]), birthday_required: Config.get([:instance, :birthday_required]), - birthday_min_age: Config.get([:instance, :birthday_min_age]) + birthday_min_age: Config.get([:instance, :birthday_min_age]), + translation: supported_languages() }, stats: %{mau: Pleroma.User.active_user_count()}, vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) @@ -271,4 +246,29 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView 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 end From 010c23e729e2d643938e6a8a55cd57ee2b5b3d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 14 Dec 2022 18:21:43 +0100 Subject: [PATCH 12/18] Include unspecified variants in target languages list for DeepL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/language/translation/deepl.ex | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/pleroma/language/translation/deepl.ex b/lib/pleroma/language/translation/deepl.ex index 8ce1209cc..0b56b35d7 100644 --- a/lib/pleroma/language/translation/deepl.ex +++ b/lib/pleroma/language/translation/deepl.ex @@ -80,6 +80,15 @@ defmodule Pleroma.Language.Translation.Deepl do languages = Jason.decode!(res.body) |> Enum.map(fn %{"language" => language} -> language |> String.downcase() end) + |> Enum.map(fn language -> + if String.contains?(language, "-") do + [language, language |> String.split("-") |> Enum.at(0)] + else + language + end + end) + |> List.flatten() + |> Enum.uniq() {:ok, languages} From f954f98fb7a1dd2503f5929de7589d43df9d5c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 13 May 2023 13:22:04 +0200 Subject: [PATCH 13/18] Implement /api/v1/instance/translation_languages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/language/translation.ex | 26 +++++++++++++++++++ lib/pleroma/language/translation/deepl.ex | 11 ++++++++ .../language/translation/libretranslate.ex | 11 ++++++++ lib/pleroma/language/translation/provider.ex | 2 ++ .../api_spec/operations/instance_operation.ex | 23 ++++++++++++++++ .../controllers/instance_controller.ex | 5 ++++ .../web/mastodon_api/views/instance_view.ex | 9 +++++++ lib/pleroma/web/router.ex | 1 + .../controllers/instance_controller_test.exs | 9 +++++++ test/support/translation_mock.ex | 11 +++++++- 10 files changed, 107 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/language/translation.ex b/lib/pleroma/language/translation.ex index 05ab898f3..e4916389d 100644 --- a/lib/pleroma/language/translation.ex +++ b/lib/pleroma/language/translation.ex @@ -63,6 +63,32 @@ defmodule Pleroma.Language.Translation do end end + def languages_matrix do + provider = get_provider() + + cache_key = "languages_matrix/#{provider.name()}" + + case @cachex.get(:translations_cache, cache_key) do + {:ok, nil} -> + result = + if !configured?() do + {:error, :not_found} + else + provider.languages_matrix() + 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 0b56b35d7..4f668fbba 100644 --- a/lib/pleroma/language/translation/deepl.ex +++ b/lib/pleroma/language/translation/deepl.ex @@ -97,6 +97,17 @@ defmodule Pleroma.Language.Translation.Deepl do end end + @impl Provider + def languages_matrix do + with {:ok, source_languages} <- supported_languages(:source), + {:ok, target_languages} <- supported_languages(:target) do + {:ok, + Map.new(source_languages, fn language -> {language, target_languages -- [language]} end)} + else + {:error, error} -> {:error, error} + end + end + @impl Provider def name, do: @name diff --git a/lib/pleroma/language/translation/libretranslate.ex b/lib/pleroma/language/translation/libretranslate.ex index 92bde8772..b793b166e 100644 --- a/lib/pleroma/language/translation/libretranslate.ex +++ b/lib/pleroma/language/translation/libretranslate.ex @@ -67,6 +67,17 @@ defmodule Pleroma.Language.Translation.Libretranslate do end end + @impl Provider + def languages_matrix do + with {:ok, source_languages} <- supported_languages(:source), + {:ok, target_languages} <- supported_languages(:target) do + {:ok, + Map.new(source_languages, fn language -> {language, target_languages -- [language]} end)} + else + {:error, error} -> {:error, error} + end + end + @impl Provider def name, do: @name diff --git a/lib/pleroma/language/translation/provider.ex b/lib/pleroma/language/translation/provider.ex index a8b151fd7..f12cba2cd 100644 --- a/lib/pleroma/language/translation/provider.ex +++ b/lib/pleroma/language/translation/provider.ex @@ -21,5 +21,7 @@ defmodule Pleroma.Language.Translation.Provider do @callback supported_languages(type :: :string | :target) :: {:ok, [String.t()]} | {:error, atom()} + @callback languages_matrix() :: {:ok, Map.t()} | {:error, atom()} + @callback name() :: String.t() end diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 708b74b12..30f4c2a97 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -46,6 +46,29 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } end + def translation_languages_operation do + %Operation{ + tags: ["Instance misc"], + summary: "Retrieve supported languages matrix", + operationId: "InstanceController.translation_languages", + responses: %{ + 200 => + Operation.response( + "Translation languages matrix", + "application/json", + %Schema{ + type: :object, + additionalProperties: %Schema{ + type: :array, + items: %Schema{type: :string}, + description: "Supported target languages for a source language" + } + } + ) + } + } + end + defp instance do %Schema{ type: :object, diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 3e664903a..bce64cf6e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -24,5 +24,10 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do @doc "GET /api/v1/instance/peers" def peers(conn, _params) do json(conn, Pleroma.Stats.get_peers()) +end + + @doc "GET /api/v1/instance/translation_languages" + def translation_languages(conn, _params) do + render(conn, "translation_languages.json") end end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index b6b99c477..096ed00ad 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -84,6 +84,15 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do } end + def render("translation_languages.json", _) do + with true <- Pleroma.Language.Translation.configured?(), + {:ok, languages} <- Pleroma.Language.Translation.languages_matrix() do + languages + else + _ -> %{} + end + end + def features do [ "pleroma_api", diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 5c949fc95..fcf6edfd7 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -760,6 +760,7 @@ defmodule Pleroma.Web.Router do get("/instance", InstanceController, :show) get("/instance/peers", InstanceController, :peers) + get("/instance/translation_languages", InstanceController, :translation_languages) get("/statuses", StatusController, :index) get("/statuses/:id", StatusController, :show) diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs index 2243b0d4a..8ffcff9f3 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -113,4 +113,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do assert get(conn, "/api/v2/instance") |> json_response_and_validate_schema(200) end + + test "translation languages matrix", %{conn: conn} do + clear_config([Pleroma.Language.Translation, :provider], TranslationMock) + + assert %{"en" => ["pl"], "pl" => ["en"]} = + conn + |> get("/api/v1/instance/translation_languages") + |> json_response_and_validate_schema(200) + end end diff --git a/test/support/translation_mock.ex b/test/support/translation_mock.ex index 2047d6426..95da738d1 100644 --- a/test/support/translation_mock.ex +++ b/test/support/translation_mock.ex @@ -24,7 +24,16 @@ defmodule TranslationMock do @impl Provider def supported_languages(_) do - ["en", "pl"] + {:ok, ["en", "pl"]} + end + + @impl Provider + def languages_matrix do + {:ok, + %{ + "en" => ["pl"], + "pl" => ["en"] + }} end @impl Provider From b53abd9d79eff1d7a650954a9585a145105b6e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 26 Apr 2024 00:00:30 +0200 Subject: [PATCH 14/18] changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/translate-posts.add | 1 + .../controllers/instance_controller.ex | 2 +- .../controllers/status_controller.ex | 8 +++++++- .../web/mastodon_api/views/instance_view.ex | 18 +++++++++--------- .../controllers/status_controller_test.exs | 18 ++++++++---------- 5 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 changelog.d/translate-posts.add diff --git a/changelog.d/translate-posts.add b/changelog.d/translate-posts.add new file mode 100644 index 000000000..e7a9317a1 --- /dev/null +++ b/changelog.d/translate-posts.add @@ -0,0 +1 @@ +Support translation providers (DeepL, LibreTranslate) \ No newline at end of file diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index bce64cf6e..ed015d574 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -24,7 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do @doc "GET /api/v1/instance/peers" def peers(conn, _params) do json(conn, Pleroma.Stats.get_peers()) -end + end @doc "GET /api/v1/instance/translation_languages" def translation_languages(conn, _params) do diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 733f33e13..ad1e78c30 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -558,7 +558,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end @doc "POST /api/v1/statuses/:id/translate" - def translate(%{body_params: params, assigns: %{user: user}} = conn, %{id: status_id}) do + def translate( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: params, params: %{id: status_id}}} + } = conn, + _ + ) do with %Activity{object: object} <- Activity.get_by_id_with_object(status_id), {:visibility, visibility} when visibility in ["public", "unlisted"] <- {:visibility, Visibility.get_visibility(object)}, diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 096ed00ad..e093b1a2f 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -75,15 +75,6 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do }) end - defp common_information(instance) do - %{ - title: Keyword.get(instance, :name), - version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", - languages: Keyword.get(instance, :languages, ["en"]), - rules: [] - } - end - def render("translation_languages.json", _) do with true <- Pleroma.Language.Translation.configured?(), {:ok, languages} <- Pleroma.Language.Translation.languages_matrix() do @@ -93,6 +84,15 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do end end + defp common_information(instance) do + %{ + title: Keyword.get(instance, :name), + version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", + languages: Keyword.get(instance, :languages, ["en"]), + rules: [] + } + end + def features do [ "pleroma_api", diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index f05f4191d..2a64cac5f 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -2556,7 +2556,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do test "it translates a status to user language" do user = insert(:user, language: "fr") - %{conn: conn, user: user} = oauth_access(["read:statuses"], user: user) + %{conn: conn} = oauth_access(["read:statuses"], user: user) another_user = insert(:user) {:ok, activity} = @@ -2579,7 +2579,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do end test "it returns an error if no target language provided" do - %{conn: conn, user: user} = oauth_access(["read:statuses"]) + %{conn: conn} = oauth_access(["read:statuses"]) another_user = insert(:user) {:ok, activity} = @@ -2588,10 +2588,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do language: "pl" }) - response = - conn - |> post("/api/v1/statuses/#{activity.id}/translate") - |> json_response_and_validate_schema(400) + assert conn + |> post("/api/v1/statuses/#{activity.id}/translate") + |> json_response_and_validate_schema(400) end test "it doesn't translate non-public statuses" do @@ -2604,10 +2603,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do language: "pl" }) - response = - conn - |> post("/api/v1/statuses/#{activity.id}/translate") - |> json_response_and_validate_schema(404) + assert conn + |> post("/api/v1/statuses/#{activity.id}/translate") + |> json_response_and_validate_schema(404) end end end From b430093caba7cf8cc8c4b9b9f1885146cd49a10f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 2 Aug 2024 09:41:48 +0200 Subject: [PATCH 15/18] Translation: Rename target language param MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/api_spec/operations/status_operation.ex | 2 +- lib/pleroma/web/mastodon_api/controllers/status_controller.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index d87d59ef6..b2dc606e1 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -435,7 +435,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do %Schema{ type: :object, properties: %{ - target_language: %Schema{ + lang: %Schema{ type: :string, nullable: true, description: "Translation target language." diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 9d40e0c30..cec35d7e2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -563,7 +563,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do {:visibility, visibility} when visibility in ["public", "unlisted"] <- {:visibility, Visibility.get_visibility(object)}, {:language, language} when is_binary(language) <- - {:language, Map.get(params, :target_language) || user.language}, + {:language, Map.get(params, :lang) || user.language}, {:ok, result} <- Translation.translate( object.data["content"], From d0dac30ac6e6808e772a5ec0b378e2b8294fc93a Mon Sep 17 00:00:00 2001 From: mkljczk Date: Sat, 22 Feb 2025 15:53:44 +0100 Subject: [PATCH 16/18] Merge downstream changes Signed-off-by: mkljczk --- lib/pleroma/application_requirements.ex | 18 ++++++++++++++- lib/pleroma/language/translation.ex | 22 +++++++++++++++++++ lib/pleroma/language/translation/deepl.ex | 2 ++ .../language/translation/libretranslate.ex | 4 +++- lib/pleroma/language/translation/provider.ex | 13 +++++++++++ .../{deepl_test.ex => deepl_test.exs} | 0 ...anslation_test.ex => translation_test.exs} | 1 - 7 files changed, 57 insertions(+), 3 deletions(-) rename test/pleroma/language/translation/{deepl_test.ex => deepl_test.exs} (100%) rename test/pleroma/language/{translation_test.ex => translation_test.exs} (94%) diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index a334d12ee..211b4882d 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -189,7 +189,23 @@ defmodule Pleroma.ApplicationRequirements do false end - if Enum.all?([preview_proxy_commands_status | filter_commands_statuses], & &1) do + 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, translation_commands_status | filter_commands_statuses], + & &1 + ) do :ok else {:error, diff --git a/lib/pleroma/language/translation.ex b/lib/pleroma/language/translation.ex index e4916389d..3706e76eb 100644 --- a/lib/pleroma/language/translation.ex +++ b/lib/pleroma/language/translation.ex @@ -11,6 +11,16 @@ defmodule Pleroma.Language.Translation 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) @@ -23,6 +33,7 @@ defmodule Pleroma.Language.Translation do {:error, :not_found} else provider.translate(text, source_language, target_language) + |> scrub_html() end store_result(result, cache_key) @@ -102,4 +113,15 @@ defmodule Pleroma.Language.Translation do defp store_result(_, _), do: nil defp content_hash(text), do: :crypto.hash(:sha256, text) |> Base.encode64() + + defp scrub_html({:ok, %{content: content} = result}) when is_binary(content) do + scrubbers = Pleroma.Config.get([:markup, :scrub_policy]) + + content + |> Pleroma.HTML.filter_tags(scrubbers) + + {:ok, %{result | content: content}} + end + + defp scrub_html(result), do: result end diff --git a/lib/pleroma/language/translation/deepl.ex b/lib/pleroma/language/translation/deepl.ex index 4f668fbba..e027035b4 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 b793b166e..fd727d1cf 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" @@ -44,7 +46,7 @@ defmodule Pleroma.Language.Translation.Libretranslate do %{ content: content, detected_source_language: source_language, - provider: "LibreTranslate" + provider: @name }} _ -> diff --git a/lib/pleroma/language/translation/provider.ex b/lib/pleroma/language/translation/provider.ex index f12cba2cd..533b5355a 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/test/pleroma/language/translation/deepl_test.ex b/test/pleroma/language/translation/deepl_test.exs similarity index 100% rename from test/pleroma/language/translation/deepl_test.ex rename to test/pleroma/language/translation/deepl_test.exs diff --git a/test/pleroma/language/translation_test.ex b/test/pleroma/language/translation_test.exs similarity index 94% rename from test/pleroma/language/translation_test.ex rename to test/pleroma/language/translation_test.exs index ecab3d20f..0be7a8d60 100644 --- a/test/pleroma/language/translation_test.ex +++ b/test/pleroma/language/translation_test.exs @@ -2,7 +2,6 @@ defmodule Pleroma.Language.TranslationTest do use Pleroma.Web.ConnCase alias Pleroma.Language.Translation - # use Oban.Testing, repo: Pleroma.Repo setup do: clear_config([Pleroma.Language.Translation, :provider], TranslationMock) From 22bbe55b55eeb3766f31c357043f97d9acd1d8dd Mon Sep 17 00:00:00 2001 From: mkljczk Date: Sat, 22 Feb 2025 16:03:05 +0100 Subject: [PATCH 17/18] fix Signed-off-by: mkljczk --- test/support/translation_mock.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/support/translation_mock.ex b/test/support/translation_mock.ex index 95da738d1..84ed8f696 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" From 25a3ee2256c8cf24575c8ed31eaa851d4c8dbea1 Mon Sep 17 00:00:00 2001 From: mkljczk Date: Wed, 19 Mar 2025 17:59:42 +0100 Subject: [PATCH 18/18] InstanceView: do not repeat information Signed-off-by: mkljczk --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index bd1ecc2f7..4b0480f66 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -157,9 +157,6 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "pleroma:bookmark_folders", if Pleroma.Language.LanguageDetector.configured?() do "pleroma:language_detection" - end, - if Pleroma.Language.Translation.configured?() do - "translation" end ] |> Enum.filter(& &1)