Add Libretranslate support

Special characters do not fully work yet when being inserted into the HTML.
This commit adds a new `cmd`: `autotranslate`, which passed the `content` to the Libretranslate instance that has been added to the `docker-compose.yml`.
This instance is only in the internal network, thus API keys do not need to be enabled.
This commit is contained in:
2025-03-20 12:56:42 +01:00
parent d53cfc861d
commit 6e7bdaccdb
5 changed files with 127 additions and 4 deletions

View File

@@ -6,7 +6,7 @@ FROM bitnami/apache:2.4
# Install dependencies
USER 0
RUN install_packages libxml2-utils libdbd-sqlite3-perl wget
RUN install_packages libxml2-utils libdbd-sqlite3-perl wget libwww-perl libjson-perl
USER 1001

View File

@@ -5,16 +5,41 @@
version: "3"
services:
libretranslate:
image: libretranslate/libretranslate:latest
container_name: libretranslate
restart: always
tty: true
environment:
- TZ=UTC
- LT_UPDATE_MODELS=true
- LT_LOAD_ONLY=en,es,fr,de,it,pt
volumes:
- libretranslate_models:/home/libretranslate/.local:rw
networks:
- internal_network
webpreview:
container_name: webpreview
build: .
image: webpreview
restart: always
depends_on:
- libretranslate
volumes:
- /srv/webpreview/db:/app/cgi/db
networks:
- internal_network
# Reverse Proxy
ports:
- "7050:8080"
labels:
proxy.host: "webpreview.fsfe.org"
proxy.port: "7050"
networks:
internal_network:
driver: bridge
volumes:
libretranslate_models:

View File

@@ -2778,9 +2778,45 @@ sub FilenameFromURL {
### Automatic Translations ###
use LWP::UserAgent;
use HTTP::Request;
use JSON;
sub translate {
my ($text, $lang) = @_;
$text.=" (translated)";
my $url = "http://libretranslate:5000/translate";
my $api_key = "";
my $req_data = {
q => $text,
source => "auto",
target => $lang,
format => "text",
api_key => $api_key,
};
my $json_data = encode_json($req_data);
my $ua = LWP::UserAgent->new;
my $request = HTTP::Request->new(POST => $url, [
"Content-Type" => "application/json"
], $json_data);
my $response = $ua->request($request);
if ($response->is_success) {
my $json_response = $response->decoded_content;
my $parsed_response = decode_json($json_response);
my $translated_text = $parsed_response->{translatedText};
return $translated_text;
} else {
warn "Translation error: ", $response->status_line, "\n";
warn "Response body: ", $response->decoded_content, "\n";
return $text;
}
return $text;
}
@@ -3039,6 +3075,13 @@ if($in{cmd} eq "padid") {
exit(0);
}
if ($in{cmd} eq "autotranslate") {
print "Content-Type: text/plain\n\n";
print translate($in{content}, $in{lang});
DebugClose();
exit(0);
}
if($in{cmd} eq "transtags") {
print "Content-Type: text/plain\n\n";
# Send only random requests
@@ -3726,7 +3769,20 @@ if(length($padid)>0 && $padid ne "--") {
$linkpad="<a href=\"$homepage?pad=$padid\">$padid</a>";
}
$newform="\n<form name=\"form1\" action=$script method=post onSubmit=\"return(ComposeHTML(this))\">\n".
$newform="\n<label for=\"autotranslate_language\">Choose a language:</label>\n".
"<select id=\"autotranslate_language\" name=\"language\">\n".
"<option value=\"en\">English</option>\n".
"<option value=\"es\">Spanish</option>\n".
"<option value=\"fr\">French</option>\n".
"<option value=\"de\">German</option>\n".
"<option value=\"it\">Italian</option>\n".
"<option value=\"pt\">Portuguese</option>\n".
"</select>\n".
"\n<h2 class=\"translation-info\" hidden>Translating page, please be patient...</h2><br>\n".
"\&nbsp;<button id=\"autotranslate\" name=\"autotranslate\" class=\"btngray\" type=button value=\"Automatically translate\" onClick=\"Autotranslate()\">Automatically translate</button>";
$newform.="\n<form name=\"form1\" action=$script method=post onSubmit=\"return(ComposeHTML(this))\">\n".
"<input name=\"regenerate\" type=submit value=\"Regenerate X[HT]ML\">".
"\&nbsp;<button name=\"exportxml\" class=\"editbtn\" type=button onClick=\"Export(this.form)\">Export X[HT]ML</button>".
"\&nbsp;<b>PAD: </b><span id=\"padid\">$linkpad</span>\n".

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html>
<!--
SPDX-FileCopyrightText: 2020 Luca Bonissi <lucabon@fsfe.org>
SPDX-FileCopyrightText: 2025 Sofía Aritz <sofiaritz@fsfe.org>
SPDX-License-Identifier: AGPL-3.0-or-later
-->
@@ -10,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<title>FSFE - X[HT]ML file preview</title>
</head>
<body onLoad="CheckPreview()">
<h2 class="translation-info" hidden>Translating page, please be patient...</h2>
<form name="form1" action="/cgi/fsfe.pl" onSubmit="FixAction()" method="POST">
You can specify the <b>PAD ID/filename: </b><input name="pad" value="" size=15 onChange="CheckClean()"> / <b>Review: </b><input name="rev" size=2 value="" onChange="CheckClean()">&nbsp;&nbsp; <input name=new type=checkbox value="ON" onClick="CheckClean()">Update selected pad/review&nbsp;&nbsp;<input type=button value="View/Update PAD" onClick="ViewPAD()"><input type=hidden name="ispad" value=""><input type=hidden name="filename" value=""><input type=hidden name="diff"><input type=hidden name=ro value="0"><br>
<h2 style="margin-top: 0.1em; padding-bottom: 0; margin-bottom: 0">Copy &amp; Paste the x[ht]ml file here:</h2>

View File

@@ -664,6 +664,46 @@ function Proofread(btn)
}
}
async function Autotranslate()
{
async function TranslatePart(text, lang)
{
console.debug(`== Requesting translation in ${lang}`)
let res = await fetch(window.location.href, {
method: "POST",
headers: {
"Content-Type": "text/plain"
},
body: `cmd=autotranslate&homepage=${homepage}&xmlreq=1&lang=${lang}&content=${encodeURIComponent(text)}`
})
text = await res.text()
console.debug(text)
console.debug("== Finished translation\n")
return text
}
async function TranslateHTML(element, lang)
{
if (!element) return;
for (const node of element.childNodes) {
if (node.id === "tags") continue;
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim()) {
node.nodeValue = await TranslatePart(node.nodeValue.trim(), lang);
} else if (node.nodeType === Node.ELEMENT_NODE) {
await TranslateHTML(node, lang);
}
}
}
Array.from(document.getElementsByClassName("translation-info")).forEach((e) => e.hidden = false)
await TranslateHTML(document.getElementById("allmain"), document.getElementById("autotranslate_language").value)
Array.from(document.getElementsByClassName("translation-info")).forEach((e) => e.hidden = true)
}
function SetFilename()
{
var fname,i;
@@ -718,7 +758,7 @@ function ReviewDIFF(frm)
ReadOnly();
lang=frm.lang.value;
CloseChoose();
setTimeout('GoDIFF('+padid+',"'+frm.oldrev.value+'","'+frm.newrev.value+'")',100);
setTimeout(() => GoDIFF(padid,frm.oldrev.value,frm.newrev.value), 100);
}
var nextoper="";