mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2024-09-27 05:50:14 +00:00
Expose translation service availability
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
557a7d736a
commit
90f91168f7
7 changed files with 233 additions and 4 deletions
|
@ -3523,5 +3523,18 @@ config :pleroma, :config_description, [
|
||||||
suggestion: [100_000]
|
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]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
54
lib/pleroma/translation.ex
Normal file
54
lib/pleroma/translation.ex
Normal 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
|
75
lib/pleroma/translation/deepl.ex
Normal file
75
lib/pleroma/translation/deepl.ex
Normal 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
|
66
lib/pleroma/translation/libretranslate.ex
Normal file
66
lib/pleroma/translation/libretranslate.ex
Normal 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
|
20
lib/pleroma/translation/service.ex
Normal file
20
lib/pleroma/translation/service.ex
Normal 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
|
|
@ -433,9 +433,9 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
||||||
required: false
|
required: false
|
||||||
),
|
),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Translation", "application/json", translation())
|
200 => Operation.response("Translation", "application/json", translation()),
|
||||||
400 => Operation.response("Error", "application/json", ApiError)
|
400 => Operation.response("Error", "application/json", ApiError),
|
||||||
404 => Operation.response("Error", "application/json", ApiError)
|
404 => Operation.response("Error", "application/json", ApiError),
|
||||||
503 => Operation.response("Error", "application/json", ApiError)
|
503 => Operation.response("Error", "application/json", ApiError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,7 +202,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
||||||
},
|
},
|
||||||
vapid: %{
|
vapid: %{
|
||||||
public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||||
}
|
},
|
||||||
|
translation: %{enabled: Pleroma.Translation.configured?}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue