Merge branch 'translate-posts' into 'develop'

Support translation providers (DeepL, LibreTranslate)

See merge request pleroma/pleroma!4102
This commit is contained in:
lain 2025-03-20 09:11:57 +00:00
commit 81960dccf2
23 changed files with 815 additions and 6 deletions

View file

@ -0,0 +1 @@
Support translation providers (DeepL, LibreTranslate)

View file

@ -3522,5 +3522,49 @@ config :pleroma, :config_description, [
suggestions: ["/usr/share/fasttext/lid.176.bin"]
}
]
},
%{
group: :pleroma,
key: Pleroma.Language.Translation,
type: :group,
description: "Translation providers",
children: [
%{
key: :provider,
type: :module,
suggestions: [
Pleroma.Language.Translation.Deepl,
Pleroma.Language.Translation.Libretranslate
]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Deepl},
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},
key: :api_key,
label: "DeepL API Key",
type: :string,
suggestions: ["YOUR_API_KEY"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Libretranslate},
key: :base_url,
label: "LibreTranslate instance URL",
type: :string,
suggestions: ["https://libretranslate.com"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Libretranslate},
key: :api_key,
label: "LibreTranslate API Key",
type: :string,
suggestions: ["YOUR_API_KEY"]
}
]
}
]

View file

@ -172,7 +172,8 @@ defmodule Pleroma.Application do
limit: 500_000
),
build_cachex("rel_me", limit: 2500),
build_cachex("host_meta", default_ttl: :timer.minutes(120), limit: 5000)
build_cachex("host_meta", default_ttl: :timer.minutes(120), limit: 5_000),
build_cachex("translations", default_ttl: :timer.hours(24), limit: 5_000)
]
end

View file

@ -202,11 +202,24 @@ defmodule Pleroma.ApplicationRequirements do
false
end
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,
language_detector_commands_status
| filter_commands_statuses
language_detector_commands_status,
translation_commands_status | filter_commands_statuses
],
& &1
) do

View file

@ -0,0 +1,127 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.Translation do
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def configured? do
provider = get_provider()
!!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)
case @cachex.get(:translations_cache, cache_key) do
{:ok, nil} ->
provider = get_provider()
result =
if !configured?() do
{:error, :not_found}
else
provider.translate(text, source_language, target_language)
|> scrub_html()
end
store_result(result, cache_key)
result
{:ok, result} ->
{:ok, result}
{:error, error} ->
{:error, error}
end
end
def supported_languages(type) when type in [:source, :target] do
provider = get_provider()
cache_key = "#{type}_languages/#{provider.name()}"
case @cachex.get(:translations_cache, cache_key) do
{:ok, nil} ->
result =
if !configured?() do
{:error, :not_found}
else
provider.supported_languages(type)
end
store_result(result, cache_key)
result
{:ok, result} ->
{:ok, result}
{:error, error} ->
{:error, error}
end
end
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
"#{source_language}/#{target_language}/#{content_hash(text)}"
end
defp store_result({:ok, result}, cache_key) do
@cachex.put(:translations_cache, cache_key, result)
end
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

View file

@ -0,0 +1,123 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.Translation.Deepl do
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
alias Pleroma.Language.Translation.Provider
use Provider
@behaviour Provider
@name "DeepL"
@impl Provider
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 =
base_url()
|> URI.merge("/v2/translate")
|> URI.to_string()
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 #{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: @name
}}
_ ->
{:error, :internal_server_error}
end
end
@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)
|> 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}
_ ->
{:error, :internal_server_error}
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
defp base_url do
Pleroma.Config.get([__MODULE__, :base_url])
end
defp api_key do
Pleroma.Config.get([__MODULE__, :api_key])
end
end

View file

@ -0,0 +1,93 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.Translation.Libretranslate do
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
alias Pleroma.Language.Translation.Provider
use Provider
@behaviour Provider
@name "LibreTranslate"
@impl Provider
def configured?, do: not_empty_string(base_url()) and not_empty_string(api_key())
@impl Provider
def translate(content, source_language, target_language) do
case Pleroma.HTTP.post(
base_url() <> "/translate",
Jason.encode!(%{
q: content,
source: source_language |> String.upcase(),
target: target_language,
format: "html",
api_key: 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: @name
}}
_ ->
{:error, :internal_server_error}
end
end
@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
@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
defp base_url do
Pleroma.Config.get([__MODULE__, :base_url])
end
defp api_key do
Pleroma.Config.get([__MODULE__, :api_key], "")
end
end

View file

@ -0,0 +1,40 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# 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(
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()}
@callback supported_languages(type :: :string | :target) ::
{:ok, [String.t()]} | {:error, atom()}
@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

View file

@ -57,6 +57,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,

View file

@ -427,6 +427,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: %{
lang: %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"],
@ -819,4 +851,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

View file

@ -30,4 +30,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do
def rules(conn, _params) do
render(conn, "rules.json")
end
@doc "GET /api/v1/instance/translation_languages"
def translation_languages(conn, _params) do
render(conn, "translation_languages.json")
end
end

View file

@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.BookmarkFolder
alias Pleroma.Language.Translation
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
@ -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,
@ -549,6 +552,41 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
end
end
@doc "POST /api/v1/statuses/:id/translate"
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)},
{:language, language} when is_binary(language) <-
{:language, Map.get(params, :lang) || 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")
{:visibility, _} ->
render_error(conn, :not_found, "Record not found")
{: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,

View file

@ -90,6 +90,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
defp common_information(instance) do
%{
languages: Keyword.get(instance, :languages, ["en"]),
@ -246,7 +255,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
},
vapid: %{
public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
}
},
translation: %{enabled: Pleroma.Language.Translation.configured?()}
})
end
@ -259,7 +269,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)
@ -285,4 +296,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

View file

@ -681,6 +681,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)

View file

@ -740,6 +740,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)
@ -787,6 +788,7 @@ defmodule Pleroma.Web.Router do
get("/instance", InstanceController, :show)
get("/instance/peers", InstanceController, :peers)
get("/instance/rules", InstanceController, :rules)
get("/instance/translation_languages", InstanceController, :translation_languages)
get("/statuses", StatusController, :index)
get("/statuses/:id", StatusController, :show)

View file

@ -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}]

View file

@ -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!!!"}]}

View file

@ -0,0 +1,37 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# 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
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

View file

@ -0,0 +1,28 @@
defmodule Pleroma.Language.TranslationTest do
use Pleroma.Web.ConnCase
alias Pleroma.Language.Translation
setup do: clear_config([Pleroma.Language.Translation, :provider], 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

View file

@ -152,4 +152,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
}
] = result["rules"]
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

View file

@ -2483,4 +2483,62 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|> json_response_and_validate_schema(:not_found)
end
end
describe "translating statuses" do
setup do: clear_config([Pleroma.Language.Translation, :provider], TranslationMock)
test "it translates a status to user language" do
user = insert(:user, language: "fr")
%{conn: conn} = 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} = oauth_access(["read:statuses"])
another_user = insert(:user)
{:ok, activity} =
CommonAPI.post(another_user, %{
status: "Cześć!",
language: "pl"
})
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
%{conn: conn, user: user} = oauth_access(["read:statuses"])
{:ok, activity} =
CommonAPI.post(user, %{
status: "Cześć!",
visibility: "private",
language: "pl"
})
assert conn
|> post("/api/v1/statuses/#{activity.id}/translate")
|> json_response_and_validate_schema(404)
end
end
end

View file

@ -1706,6 +1706,24 @@ 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("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)}"}

View file

@ -0,0 +1,43 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule TranslationMock do
alias Pleroma.Language.Translation.Provider
use Provider
@behaviour Provider
@name "TranslationMock"
@impl Provider
def configured?, do: true
@impl Provider
def translate(content, source_language, _target_language) do
{:ok,
%{
content: content |> String.reverse(),
detected_source_language: source_language,
provider: @name
}}
end
@impl Provider
def supported_languages(_) do
{:ok, ["en", "pl"]}
end
@impl Provider
def languages_matrix do
{:ok,
%{
"en" => ["pl"],
"pl" => ["en"]
}}
end
@impl Provider
def name, do: @name
end