From b147d2b19d4ddbcd28ad6498ca09a9e062a0d358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Mon, 15 Dec 2025 23:58:08 +0100 Subject: [PATCH] Add /api/v1/instance/translation_languages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- config/description.exs | 2 +- lib/pleroma/akkoma/translator.ex | 20 +++++++----- .../akkoma/translators/argos_translate.ex | 8 ++--- lib/pleroma/akkoma/translators/deepl.ex | 8 ++--- .../akkoma/translators/libre_translate.ex | 8 ++--- lib/pleroma/akkoma/translators/provider.ex | 9 ++++++ .../controllers/translation_controller.ex | 17 ++-------- .../api_spec/operations/instance_operation.ex | 23 ++++++++++++++ .../controllers/instance_controller.ex | 14 +++++++++ .../web/mastodon_api/views/instance_view.ex | 12 +++++++ lib/pleroma/web/router.ex | 1 + .../controllers/instance_controller_test.exs | 31 +++++++++++++++++++ 12 files changed, 118 insertions(+), 35 deletions(-) create mode 100644 lib/pleroma/akkoma/translators/provider.ex diff --git a/config/description.exs b/config/description.exs index 3b8338419..c3ebd046b 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3495,7 +3495,7 @@ config :pleroma, :config_description, [ key: :module, type: :module, description: "Translation module.", - suggestions: {:list_behaviour_implementations, Pleroma.Akkoma.Translator} + suggestions: {:list_behaviour_implementations, Pleroma.Akkoma.Translator.Provider} } ] }, diff --git a/lib/pleroma/akkoma/translator.ex b/lib/pleroma/akkoma/translator.ex index 91a23299c..9bcaf119d 100644 --- a/lib/pleroma/akkoma/translator.ex +++ b/lib/pleroma/akkoma/translator.ex @@ -1,9 +1,15 @@ defmodule Pleroma.Akkoma.Translator do - @callback translate(String.t(), String.t() | nil, String.t()) :: - {:ok, String.t(), String.t()} | {:error, any()} - @callback languages() :: - {:ok, [%{name: String.t(), code: String.t()}], - [%{name: String.t(), code: String.t()}]} - | {:error, any()} - @callback name() :: String.t() + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + + def languages do + module = Pleroma.Config.get([:translator, :module]) + + @cachex.fetch!(:translations_cache, "languages:#{module}}", fn _ -> + with {:ok, source_languages, dest_languages} <- module.languages() do + {:commit, {:ok, source_languages, dest_languages}} + else + {:error, err} -> {:ignore, {:error, err}} + end + end) + end end diff --git a/lib/pleroma/akkoma/translators/argos_translate.ex b/lib/pleroma/akkoma/translators/argos_translate.ex index 92bddbe0f..a05763eaf 100644 --- a/lib/pleroma/akkoma/translators/argos_translate.ex +++ b/lib/pleroma/akkoma/translators/argos_translate.ex @@ -1,5 +1,5 @@ defmodule Pleroma.Akkoma.Translators.ArgosTranslate do - @behaviour Pleroma.Akkoma.Translator + @behaviour Pleroma.Akkoma.Translator.Provider alias Pleroma.Config @@ -23,7 +23,7 @@ defmodule Pleroma.Akkoma.Translators.ArgosTranslate do end end - @impl Pleroma.Akkoma.Translator + @impl Pleroma.Akkoma.Translator.Provider def languages do with {response, 0} <- safe_languages() do langs = @@ -83,7 +83,7 @@ defmodule Pleroma.Akkoma.Translators.ArgosTranslate do defp htmlify_response(string, _), do: string - @impl Pleroma.Akkoma.Translator + @impl Pleroma.Akkoma.Translator.Provider def translate(string, nil, to_language) do # Akkoma's Pleroma-fe expects us to detect the source language automatically. # Argos-translate doesn't have that option (yet?) @@ -107,6 +107,6 @@ defmodule Pleroma.Akkoma.Translators.ArgosTranslate do end end - @impl Pleroma.Akkoma.Translator + @impl Pleroma.Akkoma.Translator.Provider def name, do: "Argos Translate" end diff --git a/lib/pleroma/akkoma/translators/deepl.ex b/lib/pleroma/akkoma/translators/deepl.ex index 4c82f8f97..436612a4a 100644 --- a/lib/pleroma/akkoma/translators/deepl.ex +++ b/lib/pleroma/akkoma/translators/deepl.ex @@ -1,5 +1,5 @@ defmodule Pleroma.Akkoma.Translators.DeepL do - @behaviour Pleroma.Akkoma.Translator + @behaviour Pleroma.Akkoma.Translator.Provider alias Pleroma.HTTP alias Pleroma.Config @@ -21,7 +21,7 @@ defmodule Pleroma.Akkoma.Translators.DeepL do Config.get([:deepl, :tier]) end - @impl Pleroma.Akkoma.Translator + @impl Pleroma.Akkoma.Translator.Provider def languages do with {:ok, %{status: 200} = source_response} <- do_languages("source"), {:ok, %{status: 200} = dest_response} <- do_languages("target"), @@ -48,7 +48,7 @@ defmodule Pleroma.Akkoma.Translators.DeepL do end end - @impl Pleroma.Akkoma.Translator + @impl Pleroma.Akkoma.Translator.Provider def translate(string, from_language, to_language) do with {:ok, %{status: 200} = response} <- do_request(api_key(), tier(), string, from_language, to_language), @@ -98,6 +98,6 @@ defmodule Pleroma.Akkoma.Translators.DeepL do ) end - @impl Pleroma.Akkoma.Translator + @impl Pleroma.Akkoma.Translator.Provider def name, do: "DeepL" end diff --git a/lib/pleroma/akkoma/translators/libre_translate.ex b/lib/pleroma/akkoma/translators/libre_translate.ex index 97bea0048..9614dbff9 100644 --- a/lib/pleroma/akkoma/translators/libre_translate.ex +++ b/lib/pleroma/akkoma/translators/libre_translate.ex @@ -1,5 +1,5 @@ defmodule Pleroma.Akkoma.Translators.LibreTranslate do - @behaviour Pleroma.Akkoma.Translator + @behaviour Pleroma.Akkoma.Translator.Provider alias Pleroma.Config alias Pleroma.HTTP @@ -13,7 +13,7 @@ defmodule Pleroma.Akkoma.Translators.LibreTranslate do Config.get([:libre_translate, :url]) end - @impl Pleroma.Akkoma.Translator + @impl Pleroma.Akkoma.Translator.Provider def languages do with {:ok, %{status: 200} = response} <- do_languages(), {:ok, body} <- Jason.decode(response.body) do @@ -30,7 +30,7 @@ defmodule Pleroma.Akkoma.Translators.LibreTranslate do end end - @impl Pleroma.Akkoma.Translator + @impl Pleroma.Akkoma.Translator.Provider def translate(string, from_language, to_language) do with {:ok, %{status: 200} = response} <- do_request(string, from_language, to_language), {:ok, body} <- Jason.decode(response.body) do @@ -80,6 +80,6 @@ defmodule Pleroma.Akkoma.Translators.LibreTranslate do HTTP.get(to_string(url)) end - @impl Pleroma.Akkoma.Translator + @impl Pleroma.Akkoma.Translator.Provider def name, do: "LibreTranslate" end diff --git a/lib/pleroma/akkoma/translators/provider.ex b/lib/pleroma/akkoma/translators/provider.ex new file mode 100644 index 000000000..dc430e9d1 --- /dev/null +++ b/lib/pleroma/akkoma/translators/provider.ex @@ -0,0 +1,9 @@ +defmodule Pleroma.Akkoma.Translator.Provider do + @callback translate(String.t(), String.t() | nil, String.t()) :: + {:ok, String.t(), String.t()} | {:error, any()} + @callback languages() :: + {:ok, [%{name: String.t(), code: String.t()}], + [%{name: String.t(), code: String.t()}]} + | {:error, any()} + @callback name() :: String.t() +end diff --git a/lib/pleroma/web/akkoma_api/controllers/translation_controller.ex b/lib/pleroma/web/akkoma_api/controllers/translation_controller.ex index 23b53a80b..2e0fd3878 100644 --- a/lib/pleroma/web/akkoma_api/controllers/translation_controller.ex +++ b/lib/pleroma/web/akkoma_api/controllers/translation_controller.ex @@ -1,12 +1,11 @@ defmodule Pleroma.Web.AkkomaAPI.TranslationController do use Pleroma.Web, :controller + alias Pleroma.Akkoma.Translator alias Pleroma.Web.Plugs.OAuthScopesPlug require Logger - @cachex Pleroma.Config.get([:cachex, :provider], Cachex) - @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} plug( OAuthScopesPlug, @@ -24,7 +23,7 @@ defmodule Pleroma.Web.AkkomaAPI.TranslationController do @doc "GET /api/v1/akkoma/translation/languages" def languages(conn, _params) do with {:enabled, true} <- {:enabled, Pleroma.Config.get([:translator, :enabled])}, - {:ok, source_languages, dest_languages} <- get_languages() do + {:ok, source_languages, dest_languages} <- Translator.languages() do conn |> json(%{source: source_languages, target: dest_languages}) else @@ -36,16 +35,4 @@ defmodule Pleroma.Web.AkkomaAPI.TranslationController do {:error, e} end end - - defp get_languages do - module = Pleroma.Config.get([:translator, :module]) - - @cachex.fetch!(:translations_cache, "languages:#{module}}", fn _ -> - with {:ok, source_languages, dest_languages} <- module.languages() do - {:commit, {:ok, source_languages, dest_languages}} - else - {:error, err} -> {:ignore, {:error, err}} - end - end) - end end diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index c72fee197..301e8e572 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -34,6 +34,29 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } end + def translation_languages_operation do + %Operation{ + tags: ["Instance"], + 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 5376e4594..c717e7426 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -20,4 +20,18 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do 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 + with {:ok, source_languages, destination_languages} <- Pleroma.Akkoma.Translator.languages() do + conn + |> render("translation_languages.json", %{ + source_languages: source_languages, + destination_languages: destination_languages + }) + else + {:enabled, false} -> json(conn, %{}) + e -> {:error, e} + end + 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 2a5104edf..5bc82e486 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -55,6 +55,18 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do } end + def render("translation_languages.json", %{ + source_languages: source_languages, + destination_languages: destination_languages + }) do + source_language_codes = Enum.map(source_languages, fn lang -> lang.code end) + dest_language_codes = Enum.map(destination_languages, fn lang -> lang.code end) + + Map.new(source_language_codes, fn language -> + {language, dest_language_codes -- [language]} + end) + end + def features do [ "pleroma_api", diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 3f17652f3..97b9f7b25 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -668,6 +668,7 @@ defmodule Pleroma.Web.Router do post("/accounts", AccountController, :create) get("/instance", InstanceController, :show) + get("/instance/translation_languages", InstanceController, :translation_languages) get("/instance/peers", InstanceController, :peers) get("/statuses", StatusController, :index) 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 966ff86e3..77da6eb93 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -108,4 +108,35 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do assert ["peer1.com", "peer2.com"] == Enum.sort(result) end + + test "get translation languages", %{conn: conn} do + Tesla.Mock.mock_global(fn + %{method: :get, url: "https://api-free.deepl.com/v2/languages?type=source"} -> + %Tesla.Env{ + status: 200, + body: + Jason.encode!([ + %{language: "en", name: "English"} + ]) + } + + %{method: :get, url: "https://api-free.deepl.com/v2/languages?type=target"} -> + %Tesla.Env{ + status: 200, + body: + Jason.encode!([ + %{language: "ja", name: "Japanese"} + ]) + } + end) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> get("/api/v1/instance/translation_languages") + + response = json_response_and_validate_schema(conn, 200) + + assert %{"en" => ["ja"]} = response + end end