Expose translation service availability

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2022-10-30 18:52:26 +01:00
parent 557a7d736a
commit 90f91168f7
7 changed files with 233 additions and 4 deletions

View file

@ -3523,5 +3523,18 @@ config :pleroma, :config_description, [
suggestion: [100_000]
}
]
},
%{
group: :pleroma,
key: Pleroma.Translation,
type: :group,
description: "Translation providers",
children: [
%{
key: Pleroma.Translation,
type: :service,
suggestions: [Pleroma.Translation.DeepL, Pleroma.Translation.LibreTranslate]
}
]
}
]

View file

@ -0,0 +1,54 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Translation do
@cache_ttl 86_400_000
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def configured? do
service = get_service()
!!service and service.configured?
end
def translate(text, source_language, target_language) do
cache_key = get_cache_key(text, source_language, target_language)
case @cachex.get(:translations_cache, cache_key) do
{:ok, nil} ->
service = get_service()
result =
if !service or !service.configured? do
{:error, :not_found}
else
service.translate(text, source_language, target_language)
end
store_result(result, cache_key)
result
{:ok, result} ->
{:ok, result}
{:error, error} ->
{:error, error}
end
end
defp get_service, do: Pleroma.Config.get([__MODULE__, :service])
defp get_cache_key(text, source_language, target_language) do
"#{source_language}/#{target_language}/#{content_hash(text)}"
end
defp store_result({:ok, result}, cache_key) do
@cachex.put(:translations_cache, cache_key, result, ttl: @cache_ttl)
end
defp store_result(_, _), do: nil
defp content_hash(text), do: :crypto.hash(:sha256, text) |> Base.encode64()
end

View file

@ -0,0 +1,75 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Translation.DeepL do
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
alias Pleroma.Translation.Service
@behaviour Service
@impl Service
def configured? do
not_empty_string(get_plan()) and not_empty_string(get_api_key())
end
@impl Service
def translate(content, source_language, target_language) do
endpoint = endpoint_url()
case Pleroma.HTTP.post(
endpoint <>
"?" <>
URI.encode_query(%{
text: content,
source_lang: source_language |> String.upcase(),
target_lang: target_language,
tag_handling: "html"
}),
"",
[
{"Content-Type", "application/x-www-form-urlencoded"},
{"Authorization", "DeepL-Auth-Key #{get_api_key()}"}
]
) do
{:ok, %{status: 429}} ->
{:error, :too_many_requests}
{:ok, %{status: 456}} ->
{:error, :quota_exceeded}
{:ok, %{status: 200} = res} ->
%{
"translations" => [
%{"text" => content, "detected_source_language" => detected_source_language}
]
} = Jason.decode!(res.body)
{:ok,
%{
content: content,
detected_source_language: detected_source_language,
provider: "DeepL"
}}
_ ->
{:error, :internal_server_error}
end
end
defp endpoint_url do
case get_plan() do
"free" -> "https://api-free.deepl.com/v2/translate"
_ -> "https://api.deepl.com/v2/translate"
end
end
defp get_plan do
Pleroma.Config.get([__MODULE__, :plan])
end
defp get_api_key do
Pleroma.Config.get([__MODULE__, :api_key])
end
end

View file

@ -0,0 +1,66 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Translation.LibreTranslate do
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
alias Pleroma.Translation.Service
@behaviour Service
@impl Service
def configured?, do: not_empty_string(get_base_url())
@impl Service
def translate(content, source_language, target_language) do
endpoint = endpoint_url()
case Pleroma.HTTP.post(
endpoint,
Jason.encode!(%{
q: content,
source: source_language |> String.upcase(),
target: target_language,
format: "html",
api_key: get_api_key()
}),
[
{"Content-Type", "application/json"}
]
) do
{:ok, %{status: 429}} ->
{:error, :too_many_requests}
{:ok, %{status: 403}} ->
{:error, :quota_exceeded}
{:ok, %{status: 200} = res} ->
%{
"translatedText" => content
} = Jason.decode!(res.body)
{:ok,
%{
content: content,
detected_source_language: source_language,
provider: "LibreTranslate"
}}
_ ->
{:error, :internal_server_error}
end
end
defp endpoint_url do
get_base_url() <> "/translate"
end
defp get_base_url do
Pleroma.Config.get([__MODULE__, :base_url])
end
defp get_api_key do
Pleroma.Config.get([__MODULE__, :api_key], "")
end
end

View file

@ -0,0 +1,20 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Translation.Service do
@callback configured?() :: boolean()
@callback translate(
content :: String.t(),
source_language :: String.t(),
target_language :: String.t()
) ::
{:ok,
%{
content: String.t(),
detected_source_language: String.t(),
provider: String.t()
}}
| {:error, atom()}
end

View file

@ -433,9 +433,9 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
required: false
),
responses: %{
200 => Operation.response("Translation", "application/json", translation())
400 => Operation.response("Error", "application/json", ApiError)
404 => Operation.response("Error", "application/json", ApiError)
200 => Operation.response("Translation", "application/json", translation()),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError),
503 => Operation.response("Error", "application/json", ApiError)
}
}

View file

@ -202,7 +202,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
},
vapid: %{
public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
}
},
translation: %{enabled: Pleroma.Translation.configured?}
})
end