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