From f2169352f1de3564d029c84ee012acaf8444db16 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 28 Aug 2022 20:38:06 +0100 Subject: [PATCH 1/9] add deepl config --- config/config.exs | 5 +++++ config/description.exs | 20 ++++++++++++++++++++ mix.exs | 3 +++ mix.lock | 1 + 4 files changed, 29 insertions(+) diff --git a/config/config.exs b/config/config.exs index 5ae7a33a2..b5eaba5e1 100644 --- a/config/config.exs +++ b/config/config.exs @@ -843,6 +843,11 @@ config :pleroma, Pleroma.Search.Elasticsearch.Cluster, } } +config :pleroma, :deepl, + # either :free or :pro + tier: :free, + api_key: "" + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/config/description.exs b/config/description.exs index 61ef8f449..4703748f8 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3349,5 +3349,25 @@ config :pleroma, :config_description, [ ] } ] + }, + %{ + group: :pleroma, + key: :deepl, + type: :group, + description: "DeepL settings.", + children: [ + %{ + key: :tier, + type: :atom, + description: "API Tier", + suggestion: [:free, :pro] + }, + %{ + key: :api_key, + type: :string, + description: "API key for DeepL", + suggestion: [nil] + } + ] } ] diff --git a/mix.exs b/mix.exs index ef038ce74..b90dfeea5 100644 --- a/mix.exs +++ b/mix.exs @@ -161,6 +161,9 @@ defmodule Pleroma.Mixfile do {:ueberauth, "~> 0.4"}, {:linkify, git: "https://akkoma.dev/AkkomaGang/linkify.git", branch: "bugfix/line-ending-buffer"}, + {:deep_lex, + git: "https://akkoma.dev/AkkomaGang/deep_lex.git", + ref: "fd72e36419848138ef257b52867e7504a2a7808b"}, {:http_signatures, "~> 0.1.1"}, {:telemetry, "~> 0.3"}, {:poolboy, "~> 1.5"}, diff --git a/mix.lock b/mix.lock index 7eeb5c138..c4f363441 100644 --- a/mix.lock +++ b/mix.lock @@ -22,6 +22,7 @@ "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "deep_lex": {:git, "https://akkoma.dev/AkkomaGang/deep_lex.git", "fd72e36419848138ef257b52867e7504a2a7808b", [ref: "fd72e36419848138ef257b52867e7504a2a7808b"]}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark": {:hex, :earmark, "1.4.26", "f0e3c3d5c278a6d448ad8c27ab0ecdec9c57a7710553138c56af220a6330a4fd", [:mix], [{:earmark_parser, "~> 1.4.26", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "e1231882b56bece0692af33f0959f06c9cd580c2dc2ecb1dc9f16f2750fa78c5"}, "earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"}, -- 2.34.1 From 55fc13481755613ebc1976d571f15ab5e2140955 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 28 Aug 2022 21:40:00 +0100 Subject: [PATCH 2/9] add deepl translation operation --- config/config.exs | 1 + config/description.exs | 6 ++ .../api_spec/operations/status_operation.ex | 55 +++++++++++++++++++ .../controllers/status_controller.ex | 24 +++++++- lib/pleroma/web/router.ex | 1 + 5 files changed, 86 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index b5eaba5e1..6a1769294 100644 --- a/config/config.exs +++ b/config/config.exs @@ -844,6 +844,7 @@ config :pleroma, Pleroma.Search.Elasticsearch.Cluster, } config :pleroma, :deepl, + enabled: false, # either :free or :pro tier: :free, api_key: "" diff --git a/config/description.exs b/config/description.exs index 4703748f8..47f80532d 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3356,6 +3356,12 @@ config :pleroma, :config_description, [ type: :group, description: "DeepL settings.", children: [ + %{ + key: :enabled, + type: :boolean, + description: "Is translation enabled?", + suggestion: [true, false] + }, %{ key: :tier, type: :atom, diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index a5da8b58e..5d18dccf6 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -406,6 +406,22 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do } end + def translate_operation do + %Operation{ + tags: ["Retrieve status translation"], + summary: "Translate status", + description: "View the translation of a given status", + operationId: "StatusController.translation", + security: [%{"oAuth" => ["read:statuses"]}], + parameters: [id_param(), language_param()], + responses: %{ + 200 => Operation.response("Translation", "application/json", translation()), + 400 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + def array_of_statuses do %Schema{type: :array, items: Status, example: [Status.schema().example]} end @@ -552,6 +568,10 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do ) end + defp language_param do + Operation.parameter(:language, :path, :string, "ISO 639 language code", example: "en") + end + defp status_response do Operation.response("Status", "application/json", Status) end @@ -573,4 +593,39 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do } } end + + defp translation_result do + %Schema{ + title: "StatusTranslation", + description: "The translation of a status.", + type: :object, + required: [:detected_source_language, :text], + properties: %{ + detected_source_language: %Schema{ + type: :string, + description: "The detected source language of the status." + }, + text: %Schema{ + type: :string, + description: "The translated text of the status." + } + }, + example: %{ + "detected_source_language" => "en", + "text" => "Hear, Feel, Think" + } + } + end + + defp translation do + %Schema{ + title: "StatusTranslation", + description: "The translation of a status.", + type: :object, + required: [:translations], + properties: %{ + translations: %Schema{type: :array, items: translation_result()} + } + } + 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 9ab30742b..c497b4e91 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Bookmark alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.Config alias Pleroma.ScheduledActivity alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -37,7 +38,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do when action in [ :index, :show, - :context + :context, + :translate ] ) @@ -418,6 +420,26 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do ) end + @doc "GET /api/v1/statuses/:id/translations/:language" + def translate(%{assigns: %{user: user}} = conn, %{id: id, language: language}) do + with {:enabled, true} <- {:enabled, Config.get([:deepl, :enabled])}, + %Activity{} = activity <- IO.inspect(Activity.get_by_id_with_object(id)), + {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, + api_key <- Config.get([:deepl, :api_key]), + tier <- Config.get([:deepl, :tier]), + {:ok, translation} <- DeepLex.translate(api_key, tier, activity.object.data["content"], language) do + json(conn, translation) + else + {:enabled, false} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => "DeepL is not enabled"}) + + {:visible, false} -> + {:error, :not_found} + end + end + defp put_application(params, %{assigns: %{token: %Token{user: %User{} = user} = token}} = _conn) do if user.disclose_client do %{client_name: client_name, website: website} = Repo.preload(token, :app).app diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 647d99278..aff7b67db 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -553,6 +553,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) + get("/statuses/:id/translations/:language", StatusController, :translate) post("/push/subscription", SubscriptionController, :create) get("/push/subscription", SubscriptionController, :show) -- 2.34.1 From 83d881dcb1f5d9d664349b8fe367f7261c2b442c Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 28 Aug 2022 22:37:23 +0100 Subject: [PATCH 3/9] add libretranslate as well --- config/config.exs | 9 +++- lib/pleroma/translators/deepl.ex | 43 +++++++++++++++++++ lib/pleroma/translators/libre_translate.ex | 38 ++++++++++++++++ lib/pleroma/translators/translator.ex | 3 ++ .../api_spec/operations/status_operation.ex | 27 ++---------- .../controllers/status_controller.ex | 15 ++++--- mix.exs | 3 -- .../controllers/status_controller_test.exs | 28 ++++++++++++ 8 files changed, 132 insertions(+), 34 deletions(-) create mode 100644 lib/pleroma/translators/deepl.ex create mode 100644 lib/pleroma/translators/libre_translate.ex create mode 100644 lib/pleroma/translators/translator.ex diff --git a/config/config.exs b/config/config.exs index 6a1769294..330e572fe 100644 --- a/config/config.exs +++ b/config/config.exs @@ -843,12 +843,19 @@ config :pleroma, Pleroma.Search.Elasticsearch.Cluster, } } -config :pleroma, :deepl, +config :pleroma, :translator, enabled: false, + module: Akkoma.Translators.DeepL + +config :pleroma, :deepl, # either :free or :pro tier: :free, api_key: "" +config :pleroma, :libre_translate, + url: "http://127.0.0.1:5000", + api_key: nil + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/lib/pleroma/translators/deepl.ex b/lib/pleroma/translators/deepl.ex new file mode 100644 index 000000000..d63742208 --- /dev/null +++ b/lib/pleroma/translators/deepl.ex @@ -0,0 +1,43 @@ +defmodule Akkoma.Translators.DeepL do + @behaviour Akkoma.Translator + + use Tesla + alias Pleroma.Config + + plug(Tesla.Middleware.EncodeFormUrlencoded) + plug(Tesla.Middleware.DecodeJson) + + defp base_url(:free) do + "https://api-free.deepl.com/v2/" + end + + defp base_url(:pro) do + "https://api.deepl.com/v2/" + end + + defp api_key do + Config.get([:deepl, :api_key]) + end + + defp tier do + Config.get([:deepl, :tier]) + end + + @impl Akkoma.Translator + def translate(string, to_language) do + with {:ok, response} <- do_request(api_key(), tier(), string, to_language) do + %{"translations" => [%{"text" => translated, "detected_source_language" => detected}]} = response.body + {:ok, detected, translated} + else + {:error, reason} -> {:error, reason} + end + end + + defp do_request(api_key, tier, string, to_language) do + post(base_url(tier) <> "translate", %{ + auth_key: api_key, + text: string, + target_lang: to_language + }) + end +end diff --git a/lib/pleroma/translators/libre_translate.ex b/lib/pleroma/translators/libre_translate.ex new file mode 100644 index 000000000..34956f26a --- /dev/null +++ b/lib/pleroma/translators/libre_translate.ex @@ -0,0 +1,38 @@ +defmodule Akkoma.Translators.LibreTranslate do + @behaviour Akkoma.Translator + + use Tesla + alias Pleroma.Config + + plug(Tesla.Middleware.JSON) + + defp api_key do + Config.get([:libre_translate, :api_key]) + end + + defp url do + Config.get([:libre_translate, :url]) + end + + @impl Akkoma.Translator + def translate(string, to_language) do + with {:ok, response} <- do_request(string, to_language) do + %{"translatedText" => translated, "detectedLanguage" => %{"language" => detected}} = response.body + {:ok, detected, translated} + else + {:error, reason} -> {:error, reason} + end + end + + defp do_request(string, to_language) do + url = URI.parse(url()) + url = %{url | path: "/translate"} + + post(url, %{ + q: string, + source: "auto", + target: to_language, + api_key: api_key() + }) + end +end diff --git a/lib/pleroma/translators/translator.ex b/lib/pleroma/translators/translator.ex new file mode 100644 index 000000000..e18f921b5 --- /dev/null +++ b/lib/pleroma/translators/translator.ex @@ -0,0 +1,3 @@ +defmodule Akkoma.Translator do + @callback translate(String.t(), String.t()) :: {:ok, String.t(), String.t()} | {:error, any()} +end diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 5d18dccf6..e27207683 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -593,28 +593,6 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do } } end - - defp translation_result do - %Schema{ - title: "StatusTranslation", - description: "The translation of a status.", - type: :object, - required: [:detected_source_language, :text], - properties: %{ - detected_source_language: %Schema{ - type: :string, - description: "The detected source language of the status." - }, - text: %Schema{ - type: :string, - description: "The translated text of the status." - } - }, - example: %{ - "detected_source_language" => "en", - "text" => "Hear, Feel, Think" - } - } end defp translation do @@ -622,9 +600,10 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do title: "StatusTranslation", description: "The translation of a status.", type: :object, - required: [:translations], + required: [:detected_language, :text], properties: %{ - translations: %Schema{type: :array, items: translation_result()} + detected_language: %Schema{type: :string, description: "The detected language of the text"}, + text: %Schema{type: :string, description: "The translated text"} } } end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index c497b4e91..1e24593ab 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -422,13 +422,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do @doc "GET /api/v1/statuses/:id/translations/:language" def translate(%{assigns: %{user: user}} = conn, %{id: id, language: language}) do - with {:enabled, true} <- {:enabled, Config.get([:deepl, :enabled])}, - %Activity{} = activity <- IO.inspect(Activity.get_by_id_with_object(id)), + with {:enabled, true} <- {:enabled, Config.get([:translator, :enabled])}, + %Activity{} = activity <- Activity.get_by_id_with_object(id), {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, - api_key <- Config.get([:deepl, :api_key]), - tier <- Config.get([:deepl, :tier]), - {:ok, translation} <- DeepLex.translate(api_key, tier, activity.object.data["content"], language) do - json(conn, translation) + translation_module <- Config.get([:translator, :module]), + {:ok, detected, translation} <- + translation_module.translate(activity.object.data["content"], language) do + json(conn, %{detected_lanugage: detected, text: translation}) else {:enabled, false} -> conn @@ -437,6 +437,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do {:visible, false} -> {:error, :not_found} + + _e -> + {:error, :internal_server_error} end end diff --git a/mix.exs b/mix.exs index b90dfeea5..ef038ce74 100644 --- a/mix.exs +++ b/mix.exs @@ -161,9 +161,6 @@ defmodule Pleroma.Mixfile do {:ueberauth, "~> 0.4"}, {:linkify, git: "https://akkoma.dev/AkkomaGang/linkify.git", branch: "bugfix/line-ending-buffer"}, - {:deep_lex, - git: "https://akkoma.dev/AkkomaGang/deep_lex.git", - ref: "fd72e36419848138ef257b52867e7504a2a7808b"}, {:http_signatures, "~> 0.1.1"}, {:telemetry, "~> 0.3"}, {:poolboy, "~> 1.5"}, 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 ea168f6c5..405105aca 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -2071,4 +2071,32 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do |> json_response_and_validate_schema(422) end end + + describe "translating statuses" do + setup do: oauth_access(["read:statuses"]) + + test "translating a status with deepl", %{conn: conn} do + Tesla.Mock.mock(fn + %{method: :post, url: "http://api-free.deepl.com/translate"} -> + {:ok, + %{ + status: 200, + body: + ~s({"data": {"translations": [{"translatedText": "Tell me, for whom do you fight?"}]}}) + }} + end) + + user = insert(:user) + {:ok, quoted_status} = CommonAPI.post(user, %{status: "何のために闘う?"}) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> get("/api/v1/statuses/#{quoted_status.id}/translations/en") + + response = json_response_and_validate_schema(conn, 200) + + assert response["text"] == "Tell me, for whom do you fight?" + end + end end -- 2.34.1 From b3220cc8fca1273bf8128303cdd23e32770592d6 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 28 Aug 2022 22:40:07 +0100 Subject: [PATCH 4/9] mix format --- lib/pleroma/translators/deepl.ex | 4 +++- lib/pleroma/translators/libre_translate.ex | 4 +++- lib/pleroma/web/api_spec/operations/status_operation.ex | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/translators/deepl.ex b/lib/pleroma/translators/deepl.ex index d63742208..830ad1555 100644 --- a/lib/pleroma/translators/deepl.ex +++ b/lib/pleroma/translators/deepl.ex @@ -26,7 +26,9 @@ defmodule Akkoma.Translators.DeepL do @impl Akkoma.Translator def translate(string, to_language) do with {:ok, response} <- do_request(api_key(), tier(), string, to_language) do - %{"translations" => [%{"text" => translated, "detected_source_language" => detected}]} = response.body + %{"translations" => [%{"text" => translated, "detected_source_language" => detected}]} = + response.body + {:ok, detected, translated} else {:error, reason} -> {:error, reason} diff --git a/lib/pleroma/translators/libre_translate.ex b/lib/pleroma/translators/libre_translate.ex index 34956f26a..e8b741741 100644 --- a/lib/pleroma/translators/libre_translate.ex +++ b/lib/pleroma/translators/libre_translate.ex @@ -17,7 +17,9 @@ defmodule Akkoma.Translators.LibreTranslate do @impl Akkoma.Translator def translate(string, to_language) do with {:ok, response} <- do_request(string, to_language) do - %{"translatedText" => translated, "detectedLanguage" => %{"language" => detected}} = response.body + %{"translatedText" => translated, "detectedLanguage" => %{"language" => detected}} = + response.body + {:ok, detected, translated} else {:error, reason} -> {:error, reason} diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index e27207683..04a7bf5db 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -593,7 +593,6 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do } } end - end defp translation do %Schema{ @@ -602,7 +601,10 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do type: :object, required: [:detected_language, :text], properties: %{ - detected_language: %Schema{type: :string, description: "The detected language of the text"}, + detected_language: %Schema{ + type: :string, + description: "The detected language of the text" + }, text: %Schema{type: :string, description: "The translated text"} } } -- 2.34.1 From 7fec63688ce8dc8a8bb5dda254add2e88f8db795 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 29 Aug 2022 17:48:58 +0100 Subject: [PATCH 5/9] add tests for translation, API docs --- config/description.exs | 43 ++++++++++- lib/pleroma/akkoma/translators/deepl.ex | 58 ++++++++++++++ .../akkoma/translators/libre_translate.ex | 51 +++++++++++++ .../{ => akkoma}/translators/translator.ex | 2 +- lib/pleroma/translators/deepl.ex | 45 ----------- lib/pleroma/translators/libre_translate.ex | 40 ---------- .../controllers/status_controller.ex | 8 +- test/pleroma/translators/deepl_test.exs | 75 +++++++++++++++++++ .../translators/libre_translate_text.exs | 73 ++++++++++++++++++ .../controllers/status_controller_test.exs | 66 +++++++++++++--- 10 files changed, 356 insertions(+), 105 deletions(-) create mode 100644 lib/pleroma/akkoma/translators/deepl.ex create mode 100644 lib/pleroma/akkoma/translators/libre_translate.ex rename lib/pleroma/{ => akkoma}/translators/translator.ex (72%) delete mode 100644 lib/pleroma/translators/deepl.ex delete mode 100644 lib/pleroma/translators/libre_translate.ex create mode 100644 test/pleroma/translators/deepl_test.exs create mode 100644 test/pleroma/translators/libre_translate_text.exs diff --git a/config/description.exs b/config/description.exs index 47f80532d..019bca485 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3352,9 +3352,9 @@ config :pleroma, :config_description, [ }, %{ group: :pleroma, - key: :deepl, + key: :translator, type: :group, - description: "DeepL settings.", + description: "Translation Settings", children: [ %{ key: :enabled, @@ -3362,16 +3362,51 @@ config :pleroma, :config_description, [ description: "Is translation enabled?", suggestion: [true, false] }, + %{ + key: :module, + type: :module, + description: "Translation module.", + suggestions: {:list_behaviour_implementations, Pleroma.Akkoma.Translator} + } + ] + }, + %{ + group: :pleroma, + key: :deepl, + label: "DeepL", + type: :group, + description: "DeepL Settings.", + children: [ %{ key: :tier, - type: :atom, + type: {:dropdown, :atom}, description: "API Tier", - suggestion: [:free, :pro] + suggestions: [:free, :pro] }, %{ key: :api_key, type: :string, description: "API key for DeepL", + suggestions: [nil] + } + ] + }, + %{ + group: :pleroma, + key: :libre_translate, + type: :group, + description: "LibreTranslate Settings.", + children: [ + %{ + key: :url, + type: :string, + description: "URL for libretranslate", + suggestion: [nil] + }, + %{ + key: :api_key, + type: :string, + description: "API key for libretranslate", suggestion: [nil] } ] diff --git a/lib/pleroma/akkoma/translators/deepl.ex b/lib/pleroma/akkoma/translators/deepl.ex new file mode 100644 index 000000000..b782e7cd6 --- /dev/null +++ b/lib/pleroma/akkoma/translators/deepl.ex @@ -0,0 +1,58 @@ +defmodule Pleroma.Akkoma.Translators.DeepL do + @behaviour Pleroma.Akkoma.Translator + + alias Pleroma.HTTP + alias Pleroma.Config + require Logger + + defp base_url(:free) do + "https://api-free.deepl.com/v2/" + end + + defp base_url(:pro) do + "https://api.deepl.com/v2/" + end + + defp api_key do + Config.get([:deepl, :api_key]) + end + + defp tier do + Config.get([:deepl, :tier]) + end + + @impl Pleroma.Akkoma.Translator + def translate(string, to_language) do + with {:ok, %{status: 200} = response} <- do_request(api_key(), tier(), string, to_language), + {:ok, body} <- Jason.decode(response.body) do + %{"translations" => [%{"text" => translated, "detected_source_language" => detected}]} = + body + + {:ok, detected, translated} + else + {:ok, %{status: 403} = response} -> + Logger.warning("DeepL: Request rejected, please check your API key: #{inspect(response)}") + {:error, "DeepL request failed"} + + {:error, reason} -> + {:error, reason} + end + end + + defp do_request(api_key, tier, string, to_language) do + HTTP.post( + base_url(tier) <> "translate", + URI.encode_query( + %{ + text: string, + target_lang: to_language + }, + :rfc3986 + ), + [ + {"authorization", "DeepL-Auth-Key #{api_key}"}, + {"content-type", "application/x-www-form-urlencoded"} + ] + ) + end +end diff --git a/lib/pleroma/akkoma/translators/libre_translate.ex b/lib/pleroma/akkoma/translators/libre_translate.ex new file mode 100644 index 000000000..86ce0036b --- /dev/null +++ b/lib/pleroma/akkoma/translators/libre_translate.ex @@ -0,0 +1,51 @@ +defmodule Pleroma.Akkoma.Translators.LibreTranslate do + @behaviour Pleroma.Akkoma.Translator + + alias Pleroma.Config + alias Pleroma.HTTP + require Logger + + defp api_key do + Config.get([:libre_translate, :api_key]) + end + + defp url do + Config.get([:libre_translate, :url]) + end + + @impl Pleroma.Akkoma.Translator + def translate(string, to_language) do + with {:ok, %{status: 200} = response} <- do_request(string, to_language), + {:ok, body} <- Jason.decode(response.body) do + %{"translatedText" => translated, "detectedLanguage" => %{"language" => detected}} = body + + {:ok, detected, translated} + else + {:ok, %{status: 403} = response} -> + Logger.warning("DeepL: Request rejected, please check your API key: #{inspect(response)}") + {:error, "libre_translate: request failed"} + + {:error, reason} -> + {:error, reason} + end + end + + defp do_request(string, to_language) do + url = URI.parse(url()) + url = %{url | path: "/translate"} + + HTTP.post( + to_string(url), + Jason.encode!(%{ + q: string, + source: "auto", + target: to_language, + format: "html", + api_key: api_key() + }), + [ + {"content-type", "application/json"} + ] + ) + end +end diff --git a/lib/pleroma/translators/translator.ex b/lib/pleroma/akkoma/translators/translator.ex similarity index 72% rename from lib/pleroma/translators/translator.ex rename to lib/pleroma/akkoma/translators/translator.ex index e18f921b5..0276ed6c2 100644 --- a/lib/pleroma/translators/translator.ex +++ b/lib/pleroma/akkoma/translators/translator.ex @@ -1,3 +1,3 @@ -defmodule Akkoma.Translator do +defmodule Pleroma.Akkoma.Translator do @callback translate(String.t(), String.t()) :: {:ok, String.t(), String.t()} | {:error, any()} end diff --git a/lib/pleroma/translators/deepl.ex b/lib/pleroma/translators/deepl.ex deleted file mode 100644 index 830ad1555..000000000 --- a/lib/pleroma/translators/deepl.ex +++ /dev/null @@ -1,45 +0,0 @@ -defmodule Akkoma.Translators.DeepL do - @behaviour Akkoma.Translator - - use Tesla - alias Pleroma.Config - - plug(Tesla.Middleware.EncodeFormUrlencoded) - plug(Tesla.Middleware.DecodeJson) - - defp base_url(:free) do - "https://api-free.deepl.com/v2/" - end - - defp base_url(:pro) do - "https://api.deepl.com/v2/" - end - - defp api_key do - Config.get([:deepl, :api_key]) - end - - defp tier do - Config.get([:deepl, :tier]) - end - - @impl Akkoma.Translator - def translate(string, to_language) do - with {:ok, response} <- do_request(api_key(), tier(), string, to_language) do - %{"translations" => [%{"text" => translated, "detected_source_language" => detected}]} = - response.body - - {:ok, detected, translated} - else - {:error, reason} -> {:error, reason} - end - end - - defp do_request(api_key, tier, string, to_language) do - post(base_url(tier) <> "translate", %{ - auth_key: api_key, - text: string, - target_lang: to_language - }) - end -end diff --git a/lib/pleroma/translators/libre_translate.ex b/lib/pleroma/translators/libre_translate.ex deleted file mode 100644 index e8b741741..000000000 --- a/lib/pleroma/translators/libre_translate.ex +++ /dev/null @@ -1,40 +0,0 @@ -defmodule Akkoma.Translators.LibreTranslate do - @behaviour Akkoma.Translator - - use Tesla - alias Pleroma.Config - - plug(Tesla.Middleware.JSON) - - defp api_key do - Config.get([:libre_translate, :api_key]) - end - - defp url do - Config.get([:libre_translate, :url]) - end - - @impl Akkoma.Translator - def translate(string, to_language) do - with {:ok, response} <- do_request(string, to_language) do - %{"translatedText" => translated, "detectedLanguage" => %{"language" => detected}} = - response.body - - {:ok, detected, translated} - else - {:error, reason} -> {:error, reason} - end - end - - defp do_request(string, to_language) do - url = URI.parse(url()) - url = %{url | path: "/translate"} - - post(url, %{ - q: string, - source: "auto", - target: to_language, - api_key: api_key() - }) - 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 1e24593ab..43fb63c38 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -428,18 +428,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do translation_module <- Config.get([:translator, :module]), {:ok, detected, translation} <- translation_module.translate(activity.object.data["content"], language) do - json(conn, %{detected_lanugage: detected, text: translation}) + json(conn, %{detected_language: detected, text: translation}) else {:enabled, false} -> conn |> put_status(:bad_request) - |> json(%{"error" => "DeepL is not enabled"}) + |> json(%{"error" => "Translation is not enabled"}) {:visible, false} -> {:error, :not_found} - _e -> - {:error, :internal_server_error} + e -> + e end end diff --git a/test/pleroma/translators/deepl_test.exs b/test/pleroma/translators/deepl_test.exs new file mode 100644 index 000000000..3e1f1e175 --- /dev/null +++ b/test/pleroma/translators/deepl_test.exs @@ -0,0 +1,75 @@ +defmodule Pleroma.Akkoma.Translators.DeepLTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Akkoma.Translators.DeepL + + describe "translating with deepl" do + setup do + clear_config([:deepl, :api_key], "deepl_api_key") + end + + test "should work with the free tier" do + clear_config([:deepl, :tier], :free) + + Tesla.Mock.mock(fn + %{method: :post, url: "https://api-free.deepl.com/v2/translate"} = env -> + auth_header = Enum.find(env.headers, fn {k, _v} -> k == "authorization" end) + assert {"authorization", "DeepL-Auth-Key deepl_api_key"} = auth_header + + %Tesla.Env{ + status: 200, + body: + Jason.encode!(%{ + translations: [ + %{ + "text" => "I will crush you", + "detected_source_language" => "ja" + } + ] + }) + } + end) + + assert {:ok, "ja", "I will crush you"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", "en") + end + + test "should work with the pro tier" do + clear_config([:deepl, :tier], :pro) + + Tesla.Mock.mock(fn + %{method: :post, url: "https://api.deepl.com/v2/translate"} = env -> + auth_header = Enum.find(env.headers, fn {k, _v} -> k == "authorization" end) + assert {"authorization", "DeepL-Auth-Key deepl_api_key"} = auth_header + + %Tesla.Env{ + status: 200, + body: + Jason.encode!(%{ + translations: [ + %{ + "text" => "I will crush you", + "detected_source_language" => "ja" + } + ] + }) + } + end) + + assert {:ok, "ja", "I will crush you"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", "en") + end + + test "should gracefully fail if the API errors" do + clear_config([:deepl, :tier], :free) + + Tesla.Mock.mock(fn + %{method: :post, url: "https://api-free.deepl.com/v2/translate"} -> + %Tesla.Env{ + status: 403, + body: "" + } + end) + + assert {:error, "DeepL request failed"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", "en") + end + end +end diff --git a/test/pleroma/translators/libre_translate_text.exs b/test/pleroma/translators/libre_translate_text.exs new file mode 100644 index 000000000..798b45113 --- /dev/null +++ b/test/pleroma/translators/libre_translate_text.exs @@ -0,0 +1,73 @@ +defmodule Pleroma.Akkoma.Translators.LibreTranslateTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Akkoma.Translators.LibreTranslate + + describe "translating with libre translate" do + setup do + clear_config([:libre_translate, :url], "http://libre.translate/translate") + end + + test "should work without an API key" do + Tesla.Mock.mock(fn + %{method: :post, url: "http://libre.translate/translate"} = env -> + assert {:ok, %{"api_key" => nil}} = Jason.decode(env.body) + + %Tesla.Env{ + status: 200, + body: + Jason.encode!(%{ + detectedLanguage: %{ + confidence: 83, + language: "ja" + }, + translatedText: "I will crush you" + }) + } + end) + + assert {:ok, "ja", "I will crush you"} = LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", "en") + end + + test "should work with an API key" do + clear_config([:libre_translate, :api_key], "libre_translate_api_key") + + Tesla.Mock.mock(fn + %{method: :post, url: "http://libre.translate/translate"} = env -> + assert {:ok, %{"api_key" => "libre_translate_api_key"}} = Jason.decode(env.body) + + %Tesla.Env{ + status: 200, + body: + Jason.encode!(%{ + detectedLanguage: %{ + confidence: 83, + language: "ja" + }, + translatedText: "I will crush you" + }) + } + end) + + assert {:ok, "ja", "I will crush you"} = LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", "en") + end + + test "should gracefully handle API key errors" do + clear_config([:libre_translate, :api_key], "") + + Tesla.Mock.mock(fn + %{method: :post, url: "http://libre.translate/translate"} -> + %Tesla.Env{ + status: 403, + body: + Jason.encode!(%{ + error: "Please contact the server operator to obtain an API key" + }) + } + end) + + assert {:error, "libre_translate: request failed"} = + LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", "en") + end + 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 405105aca..771164be3 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -2073,30 +2073,74 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do end describe "translating statuses" do - setup do: oauth_access(["read:statuses"]) + setup do + clear_config([:translator, :enabled], true) + clear_config([:translator, :module], Pleroma.Akkoma.Translators.DeepL) + clear_config([:deepl, :api_key], "deepl_api_key") + oauth_access(["read:statuses"]) + end + + test "should return text and detected language", %{conn: conn} do + clear_config([:deepl, :tier], :free) - test "translating a status with deepl", %{conn: conn} do Tesla.Mock.mock(fn - %{method: :post, url: "http://api-free.deepl.com/translate"} -> - {:ok, - %{ - status: 200, - body: - ~s({"data": {"translations": [{"translatedText": "Tell me, for whom do you fight?"}]}}) - }} + %{method: :post, url: "https://api-free.deepl.com/v2/translate"} -> + %Tesla.Env{ + status: 200, + body: + Jason.encode!(%{ + translations: [ + %{ + "text" => "Tell me, for whom do you fight?", + "detected_source_language" => "ja" + } + ] + }) + } end) user = insert(:user) - {:ok, quoted_status} = CommonAPI.post(user, %{status: "何のために闘う?"}) + {:ok, to_translate} = CommonAPI.post(user, %{status: "何のために闘う?"}) conn = conn |> put_req_header("content-type", "application/json") - |> get("/api/v1/statuses/#{quoted_status.id}/translations/en") + |> get("/api/v1/statuses/#{to_translate.id}/translations/en") response = json_response_and_validate_schema(conn, 200) assert response["text"] == "Tell me, for whom do you fight?" + assert response["detected_language"] == "ja" + end + + test "should not allow translating of statuses you cannot see", %{conn: conn} do + clear_config([:deepl, :tier], :free) + + Tesla.Mock.mock(fn + %{method: :post, url: "https://api-free.deepl.com/v2/translate"} -> + %Tesla.Env{ + status: 200, + body: + Jason.encode!(%{ + translations: [ + %{ + "text" => "Tell me, for whom do you fight?", + "detected_source_language" => "ja" + } + ] + }) + } + end) + + user = insert(:user) + {:ok, to_translate} = CommonAPI.post(user, %{status: "何のために闘う?", visibility: "private"}) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> get("/api/v1/statuses/#{to_translate.id}/translations/en") + + json_response_and_validate_schema(conn, 404) end end end -- 2.34.1 From cf774bf64d971cff78e3a101604a01ce90ee00b9 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 29 Aug 2022 17:59:54 +0100 Subject: [PATCH 6/9] add documentation and extra tests --- docs/docs/configuration/cheatsheet.md | 25 +++++++++++++++++++ lib/pleroma/akkoma/translators/deepl.ex | 6 ++--- .../akkoma/translators/libre_translate.ex | 6 ++--- test/pleroma/translators/deepl_test.exs | 2 +- ...late_text.exs => libre_translate_test.exs} | 20 ++++++++++++++- 5 files changed, 51 insertions(+), 8 deletions(-) rename test/pleroma/translators/{libre_translate_text.exs => libre_translate_test.exs} (77%) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index a29db208c..90041d3d6 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -1159,3 +1159,28 @@ Each job has these settings: * `:max_running` - max concurrently runnings jobs * `:max_waiting` - max waiting jobs + +### Translation Settings + +Settings to automatically translate statuses for end users. Currently supported +translation services are DeepL and LibreTranslate. + +Translations are available at `/api/v1/statuses/:id/translations/:language`, where +`language` is the target language code (e.g `en`) + +### `:translator` + +- `:enabled` - enables translation +- `:module` - Sets module to be used + - Either `Pleroma.Akkoma.Translators.DeepL` or `Pleroma.Akkoma.Translators.LibreTranslate` + +### `:deepl` + +- `:api_key` - API key for DeepL +- `:tier` - API tier + - either `:free` or `:pro` + +### `:libre_translate` + +- `:url` - URL of LibreTranslate instance +- `:api_key` - API key for LibreTranslate \ No newline at end of file diff --git a/lib/pleroma/akkoma/translators/deepl.ex b/lib/pleroma/akkoma/translators/deepl.ex index b782e7cd6..0a4a7fe10 100644 --- a/lib/pleroma/akkoma/translators/deepl.ex +++ b/lib/pleroma/akkoma/translators/deepl.ex @@ -30,9 +30,9 @@ defmodule Pleroma.Akkoma.Translators.DeepL do {:ok, detected, translated} else - {:ok, %{status: 403} = response} -> - Logger.warning("DeepL: Request rejected, please check your API key: #{inspect(response)}") - {:error, "DeepL request failed"} + {:ok, %{status: status} = response} -> + Logger.warning("DeepL: Request rejected: #{inspect(response)}") + {:error, "DeepL request failed (code #{status})"} {:error, reason} -> {:error, reason} diff --git a/lib/pleroma/akkoma/translators/libre_translate.ex b/lib/pleroma/akkoma/translators/libre_translate.ex index 86ce0036b..615d04192 100644 --- a/lib/pleroma/akkoma/translators/libre_translate.ex +++ b/lib/pleroma/akkoma/translators/libre_translate.ex @@ -21,9 +21,9 @@ defmodule Pleroma.Akkoma.Translators.LibreTranslate do {:ok, detected, translated} else - {:ok, %{status: 403} = response} -> - Logger.warning("DeepL: Request rejected, please check your API key: #{inspect(response)}") - {:error, "libre_translate: request failed"} + {:ok, %{status: status} = response} -> + Logger.warning("libre_translate: request failed, #{inspect(response)}") + {:error, "libre_translate: request failed (code #{status})"} {:error, reason} -> {:error, reason} diff --git a/test/pleroma/translators/deepl_test.exs b/test/pleroma/translators/deepl_test.exs index 3e1f1e175..286d21d3e 100644 --- a/test/pleroma/translators/deepl_test.exs +++ b/test/pleroma/translators/deepl_test.exs @@ -69,7 +69,7 @@ defmodule Pleroma.Akkoma.Translators.DeepLTest do } end) - assert {:error, "DeepL request failed"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", "en") + assert {:error, "DeepL request failed (code 403)"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", "en") end end end diff --git a/test/pleroma/translators/libre_translate_text.exs b/test/pleroma/translators/libre_translate_test.exs similarity index 77% rename from test/pleroma/translators/libre_translate_text.exs rename to test/pleroma/translators/libre_translate_test.exs index 798b45113..9ed2c5323 100644 --- a/test/pleroma/translators/libre_translate_text.exs +++ b/test/pleroma/translators/libre_translate_test.exs @@ -66,8 +66,26 @@ defmodule Pleroma.Akkoma.Translators.LibreTranslateTest do } end) - assert {:error, "libre_translate: request failed"} = + assert {:error, "libre_translate: request failed (code 403)"} = LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", "en") end + + test "should gracefully handle an unsupported language" do + clear_config([:libre_translate, :api_key], "") + + Tesla.Mock.mock(fn + %{method: :post, url: "http://libre.translate/translate"} -> + %Tesla.Env{ + status: 400, + body: + Jason.encode!(%{ + error: "zoop is not supported" + }) + } + end) + + assert {:error, "libre_translate: request failed (code 400)"} = + LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", "zoop") + end end end -- 2.34.1 From 9af5fe78b185d274e91c9319a15e2a9627526fa5 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 29 Aug 2022 18:42:46 +0100 Subject: [PATCH 7/9] fix search options in description --- config/description.exs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/config/description.exs b/config/description.exs index 019bca485..a17897b98 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3226,13 +3226,14 @@ config :pleroma, :config_description, [ group: :pleroma, key: Pleroma.Search, type: :group, + label: "Search", description: "General search settings.", children: [ %{ key: :module, - type: :keyword, + type: :module, description: "Selected search module.", - suggestion: [Pleroma.Search.DatabaseSearch, Pleroma.Search.Meilisearch] + suggestions: {:list_behaviour_implementations, Pleroma.Search.SearchBackend} } ] }, @@ -3257,7 +3258,7 @@ config :pleroma, :config_description, [ }, %{ key: :initial_indexing_chunk_size, - type: :int, + type: :integer, description: "Amount of posts in a batch when running the initial indexing operation. Should probably not be more than 100000" <> " since there's a limit on maximum insert size", @@ -3268,6 +3269,7 @@ config :pleroma, :config_description, [ %{ group: :pleroma, key: Pleroma.Search.Elasticsearch.Cluster, + label: "Elasticsearch", type: :group, description: "Elasticsearch settings.", children: [ @@ -3334,13 +3336,13 @@ config :pleroma, :config_description, [ }, %{ key: :bulk_page_size, - type: :int, + type: :integer, description: "Size for bulk put requests, mostly used on building the index", suggestion: [5000] }, %{ key: :bulk_wait_interval, - type: :int, + type: :integer, description: "Time to wait between bulk put requests (in ms)", suggestion: [15_000] } -- 2.34.1 From 9e1300ccc1236cc2fa47a28f79a2e09f05cfb610 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 29 Aug 2022 20:27:49 +0100 Subject: [PATCH 8/9] add caching for translations --- CHANGELOG.md | 1 + lib/pleroma/application.ex | 3 ++- .../controllers/status_controller.ex | 20 ++++++++++++++++++- .../web/mastodon_api/views/instance_view.ex | 3 +++ .../controllers/status_controller_test.exs | 4 ++-- 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a71255ff..05cb69c40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - support for setting instance languages in metadata - support for reusing oauth tokens, and not requiring new authorizations - the ability to obfuscate domains in your MRF descriptions +- automatic translation of statuses via DeepL or LibreTranslate ### Changed - MFM parsing is now done on the backend by a modified version of ilja's parser -> https://akkoma.dev/AkkomaGang/mfm-parser diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index e11e5495a..b809f7733 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -154,7 +154,8 @@ defmodule Pleroma.Application do build_cachex("web_resp", limit: 2500), build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10), build_cachex("failed_proxy_url", limit: 2500), - build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000) + build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000), + build_cachex("translations", default_ttl: :timer.hours(24 * 30), limit: 2500) ] end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 43fb63c38..d9b93ca5e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -31,6 +31,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do plug(:skip_public_check when action in [:index, :show]) @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) plug( OAuthScopesPlug, @@ -427,7 +428,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, translation_module <- Config.get([:translator, :module]), {:ok, detected, translation} <- - translation_module.translate(activity.object.data["content"], language) do + fetch_or_translate( + activity.id, + activity.object.data["content"], + language, + translation_module + ) do json(conn, %{detected_language: detected, text: translation}) else {:enabled, false} -> @@ -443,6 +449,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end end + defp fetch_or_translate(status_id, text, language, translation_module) do + @cachex.fetch!(:user_cache, "translations:#{status_id}:#{language}", fn _ -> + value = translation_module.translate(text, language) + + with {:ok, _, _} <- value do + value + else + _ -> {:ignore, value} + end + end) + end + defp put_application(params, %{assigns: %{token: %Token{user: %User{} = user} = token}} = _conn) do if user.disclose_client do %{client_name: client_name, website: website} = Repo.preload(token, :app).app diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 7ae357e23..436519439 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -81,6 +81,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do if Config.get([:instance, :profile_directory]) do "profile_directory" end, + if Config.get([:translator, :enabled], false) do + "akkoma:machine_translation" + end, "custom_emoji_reactions" ] |> Enum.filter(& &1) 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 771164be3..e38f5fe58 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -2083,7 +2083,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do test "should return text and detected language", %{conn: conn} do clear_config([:deepl, :tier], :free) - Tesla.Mock.mock(fn + Tesla.Mock.mock_global(fn %{method: :post, url: "https://api-free.deepl.com/v2/translate"} -> %Tesla.Env{ status: 200, @@ -2116,7 +2116,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do test "should not allow translating of statuses you cannot see", %{conn: conn} do clear_config([:deepl, :tier], :free) - Tesla.Mock.mock(fn + Tesla.Mock.mock_global(fn %{method: :post, url: "https://api-free.deepl.com/v2/translate"} -> %Tesla.Env{ status: 200, -- 2.34.1 From 9739460edde77f9a8401d9ae922c1934637712c7 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 29 Aug 2022 20:31:58 +0100 Subject: [PATCH 9/9] remove unused dep --- mix.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/mix.lock b/mix.lock index c4f363441..7eeb5c138 100644 --- a/mix.lock +++ b/mix.lock @@ -22,7 +22,6 @@ "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, - "deep_lex": {:git, "https://akkoma.dev/AkkomaGang/deep_lex.git", "fd72e36419848138ef257b52867e7504a2a7808b", [ref: "fd72e36419848138ef257b52867e7504a2a7808b"]}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark": {:hex, :earmark, "1.4.26", "f0e3c3d5c278a6d448ad8c27ab0ecdec9c57a7710553138c56af220a6330a4fd", [:mix], [{:earmark_parser, "~> 1.4.26", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "e1231882b56bece0692af33f0959f06c9cd580c2dc2ecb1dc9f16f2750fa78c5"}, "earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"}, -- 2.34.1