Allow listing languages, setting source language #192
7 changed files with 97 additions and 28 deletions
|
@ -40,8 +40,9 @@ def languages do
|
|||
end
|
||||
|
||||
@impl Pleroma.Akkoma.Translator
|
||||
def translate(string, to_language) do
|
||||
with {:ok, %{status: 200} = response} <- do_request(api_key(), tier(), string, to_language),
|
||||
def translate(string, from_language, to_language) do
|
||||
with {:ok, %{status: 200} = response} <-
|
||||
do_request(api_key(), tier(), string, from_language, to_language),
|
||||
{:ok, body} <- Jason.decode(response.body) do
|
||||
%{"translations" => [%{"text" => translated, "detected_source_language" => detected}]} =
|
||||
body
|
||||
|
@ -57,7 +58,7 @@ def translate(string, to_language) do
|
|||
end
|
||||
end
|
||||
|
||||
defp do_request(api_key, tier, string, to_language) do
|
||||
defp do_request(api_key, tier, string, from_language, to_language) do
|
||||
HTTP.post(
|
||||
base_url(tier) <> "translate",
|
||||
URI.encode_query(
|
||||
|
@ -65,7 +66,8 @@ defp do_request(api_key, tier, string, to_language) do
|
|||
text: string,
|
||||
target_lang: to_language,
|
||||
tag_handling: "html"
|
||||
},
|
||||
}
|
||||
|> maybe_add_source(from_language),
|
||||
:rfc3986
|
||||
),
|
||||
[
|
||||
|
@ -75,6 +77,9 @@ defp do_request(api_key, tier, string, to_language) do
|
|||
)
|
||||
end
|
||||
|
||||
defp maybe_add_source(opts, nil), do: opts
|
||||
defp maybe_add_source(opts, lang), do: Map.put(opts, :source_lang, lang)
|
||||
|
||||
defp do_languages() do
|
||||
HTTP.get(
|
||||
base_url(tier()) <> "languages?type=target",
|
||||
|
|
|
@ -30,10 +30,17 @@ def languages do
|
|||
end
|
||||
|
||||
@impl Pleroma.Akkoma.Translator
|
||||
def translate(string, to_language) do
|
||||
with {:ok, %{status: 200} = response} <- do_request(string, to_language),
|
||||
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
|
||||
%{"translatedText" => translated, "detectedLanguage" => %{"language" => detected}} = body
|
||||
%{"translatedText" => translated} = body
|
||||
|
||||
detected =
|
||||
if Map.has_key?(body, "detectedLanguage") do
|
||||
get_in(body, ["detectedLanguage", "language"])
|
||||
else
|
||||
from_language
|
||||
end
|
||||
|
||||
{:ok, detected, translated}
|
||||
else
|
||||
|
@ -46,7 +53,7 @@ def translate(string, to_language) do
|
|||
end
|
||||
end
|
||||
|
||||
defp do_request(string, to_language) do
|
||||
defp do_request(string, from_language, to_language) do
|
||||
url = URI.parse(url())
|
||||
url = %{url | path: "/translate"}
|
||||
|
||||
|
@ -54,7 +61,7 @@ defp do_request(string, to_language) do
|
|||
to_string(url),
|
||||
Jason.encode!(%{
|
||||
q: string,
|
||||
source: "auto",
|
||||
source: if(is_nil(from_language), do: "auto", else: from_language),
|
||||
target: to_language,
|
||||
format: "html",
|
||||
api_key: api_key()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Pleroma.Akkoma.Translator do
|
||||
@callback translate(String.t(), String.t()) :: {:ok, String.t(), String.t()} | {:error, any()}
|
||||
@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()}]} | {:error, any()}
|
||||
end
|
||||
|
|
|
@ -413,7 +413,7 @@ def translate_operation do
|
|||
description: "View the translation of a given status",
|
||||
operationId: "StatusController.translation",
|
||||
security: [%{"oAuth" => ["read:statuses"]}],
|
||||
parameters: [id_param(), language_param()],
|
||||
parameters: [id_param(), language_param(), source_language_param()],
|
||||
responses: %{
|
||||
200 => Operation.response("Translation", "application/json", translation()),
|
||||
400 => Operation.response("Error", "application/json", ApiError),
|
||||
|
@ -572,6 +572,10 @@ defp language_param do
|
|||
Operation.parameter(:language, :path, :string, "ISO 639 language code", example: "en")
|
||||
end
|
||||
|
||||
defp source_language_param do
|
||||
Operation.parameter(:from, :query, :string, "ISO 639 language code", example: "en")
|
||||
end
|
||||
|
||||
defp status_response do
|
||||
Operation.response("Status", "application/json", Status)
|
||||
end
|
||||
|
|
|
@ -422,7 +422,7 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do
|
|||
end
|
||||
|
||||
@doc "GET /api/v1/statuses/:id/translations/:language"
|
||||
def translate(%{assigns: %{user: user}} = conn, %{id: id, language: language}) do
|
||||
def translate(%{assigns: %{user: user}} = conn, %{id: id, language: language} = params) do
|
||||
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)},
|
||||
|
@ -431,6 +431,7 @@ def translate(%{assigns: %{user: user}} = conn, %{id: id, language: language}) d
|
|||
fetch_or_translate(
|
||||
activity.id,
|
||||
activity.object.data["content"],
|
||||
Map.get(params, :from, nil),
|
||||
language,
|
||||
translation_module
|
||||
) do
|
||||
|
@ -449,16 +450,20 @@ def translate(%{assigns: %{user: user}} = conn, %{id: id, language: language}) d
|
|||
end
|
||||
end
|
||||
|
||||
defp fetch_or_translate(status_id, text, language, translation_module) do
|
||||
@cachex.fetch!(:translations_cache, "translations:#{status_id}:#{language}", fn _ ->
|
||||
value = translation_module.translate(text, language)
|
||||
defp fetch_or_translate(status_id, text, source_language, target_language, translation_module) do
|
||||
@cachex.fetch!(
|
||||
:translations_cache,
|
||||
"translations:#{status_id}:#{source_language}:#{target_language}",
|
||||
fn _ ->
|
||||
value = translation_module.translate(text, source_language, target_language)
|
||||
|
||||
with {:ok, _, _} <- value do
|
||||
value
|
||||
else
|
||||
_ -> {:ignore, value}
|
||||
end
|
||||
end)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
defp put_application(params, %{assigns: %{token: %Token{user: %User{} = user} = token}} = _conn) do
|
||||
|
|
|
@ -60,7 +60,7 @@ test "should work with the free tier" do
|
|||
}
|
||||
end)
|
||||
|
||||
assert {:ok, "ja", "I will crush you"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", "en")
|
||||
assert {:ok, "ja", "I will crush you"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", nil, "en")
|
||||
end
|
||||
|
||||
test "should work with the pro tier" do
|
||||
|
@ -85,7 +85,33 @@ test "should work with the pro tier" do
|
|||
}
|
||||
end)
|
||||
|
||||
assert {:ok, "ja", "I will crush you"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", "en")
|
||||
assert {:ok, "ja", "I will crush you"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", nil, "en")
|
||||
end
|
||||
|
||||
test "should assign source language if set" do
|
||||
clear_config([:deepl, :tier], :pro)
|
||||
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: :post, url: "https://api.deepl.com/v2/translate"} = env ->
|
||||
auth_header = Enum.find(env.headers, fn {k, _v} -> k == "authorization" end)
|
||||
assert {"authorization", "DeepL-Auth-Key deepl_api_key"} = auth_header
|
||||
assert String.contains?(env.body, "source_lang=ja")
|
||||
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
Jason.encode!(%{
|
||||
translations: [
|
||||
%{
|
||||
"text" => "I will crush you",
|
||||
"detected_source_language" => "ja"
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
end)
|
||||
|
||||
assert {:ok, "ja", "I will crush you"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", "ja", "en")
|
||||
end
|
||||
|
||||
test "should gracefully fail if the API errors" do
|
||||
|
@ -99,7 +125,8 @@ test "should gracefully fail if the API errors" do
|
|||
}
|
||||
end)
|
||||
|
||||
assert {:error, "DeepL request failed (code 403)"} = DeepL.translate("ギュギュ握りつぶしちゃうぞ", "en")
|
||||
assert {:error, "DeepL request failed (code 403)"} =
|
||||
DeepL.translate("ギュギュ握りつぶしちゃうぞ", nil, "en")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,7 +36,7 @@ test "should list supported languages" do
|
|||
test "should work without an API key" do
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: :post, url: "http://libre.translate/translate"} = env ->
|
||||
assert {:ok, %{"api_key" => nil}} = Jason.decode(env.body)
|
||||
assert {:ok, %{"api_key" => nil, "source" => "auto"}} = Jason.decode(env.body)
|
||||
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
|
@ -51,7 +51,8 @@ test "should work without an API key" do
|
|||
}
|
||||
end)
|
||||
|
||||
assert {:ok, "ja", "I will crush you"} = LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", "en")
|
||||
assert {:ok, "ja", "I will crush you"} =
|
||||
LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", nil, "en")
|
||||
end
|
||||
|
||||
test "should work with an API key" do
|
||||
|
@ -74,7 +75,8 @@ test "should work with an API key" do
|
|||
}
|
||||
end)
|
||||
|
||||
assert {:ok, "ja", "I will crush you"} = LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", "en")
|
||||
assert {:ok, "ja", "I will crush you"} =
|
||||
LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", nil, "en")
|
||||
end
|
||||
|
||||
test "should gracefully handle API key errors" do
|
||||
|
@ -92,7 +94,25 @@ test "should gracefully handle API key errors" do
|
|||
end)
|
||||
|
||||
assert {:error, "libre_translate: request failed (code 403)"} =
|
||||
LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", "en")
|
||||
LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", nil, "en")
|
||||
end
|
||||
|
||||
test "should set a source language if requested" do
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: :post, url: "http://libre.translate/translate"} = env ->
|
||||
assert {:ok, %{"api_key" => nil, "source" => "ja"}} = Jason.decode(env.body)
|
||||
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
Jason.encode!(%{
|
||||
translatedText: "I will crush you"
|
||||
})
|
||||
}
|
||||
end)
|
||||
|
||||
assert {:ok, "ja", "I will crush you"} =
|
||||
LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", "ja", "en")
|
||||
end
|
||||
|
||||
test "should gracefully handle an unsupported language" do
|
||||
|
@ -110,7 +130,7 @@ test "should gracefully handle an unsupported language" do
|
|||
end)
|
||||
|
||||
assert {:error, "libre_translate: request failed (code 400)"} =
|
||||
LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", "zoop")
|
||||
LibreTranslate.translate("ギュギュ握りつぶしちゃうぞ", nil, "zoop")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue