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:
@@ -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
|
||||
|
||||
|
@@ -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:
|
@@ -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".
|
||||
"\ <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\">".
|
||||
"\ <button name=\"exportxml\" class=\"editbtn\" type=button onClick=\"Export(this.form)\">Export X[HT]ML</button>".
|
||||
"\ <b>PAD: </b><span id=\"padid\">$linkpad</span>\n".
|
||||
|
@@ -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()"> <input name=new type=checkbox value="ON" onClick="CheckClean()">Update selected pad/review <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 & Paste the x[ht]ml file here:</h2>
|
||||
|
42
www/send.js
42
www/send.js
@@ -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="";
|
||||
|
Reference in New Issue
Block a user