Füge Unterstützung für LibreTranslate hinzu und verbessere die Benutzeroberfläche
This commit is contained in:
206
mainwindow.cpp
206
mainwindow.cpp
@@ -22,6 +22,7 @@
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QLocale>
|
||||
#include <QMenu>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QScrollArea>
|
||||
@@ -154,6 +155,13 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
ui->deeplApiKey->setText(configuration["deepl-key"].toString());
|
||||
loadDeeplTranslationPossibilities();
|
||||
}
|
||||
if (configuration.contains("libretranslate-url")) {
|
||||
ui->libreTranslateUrl->setText(configuration["libretranslate-url"].toString());
|
||||
}
|
||||
if (configuration.contains("translation-provider")) {
|
||||
ui->translationProviderCombo->setCurrentText(configuration["translation-provider"].toString());
|
||||
}
|
||||
on_translationProviderCombo_currentIndexChanged(ui->translationProviderCombo->currentIndex());
|
||||
if (configuration.contains("last-dir") && (QDir()).exists(configuration["last-dir"].toString())) {
|
||||
ui->projectDir->setText(configuration["last-dir"].toString());
|
||||
crawlProject();
|
||||
@@ -164,6 +172,67 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
noConfigChange = false;
|
||||
}
|
||||
|
||||
bool MainWindow::isLibreTranslateProvider() const
|
||||
{
|
||||
return ui->translationProviderCombo->currentIndex() == 1;
|
||||
}
|
||||
|
||||
QString MainWindow::libreTranslateUrl(const QString &path) const
|
||||
{
|
||||
return ui->libreTranslateUrl->text().trimmed().replace(QRegularExpression("/+$/"), "") + path;
|
||||
}
|
||||
|
||||
void MainWindow::on_translationProviderCombo_currentIndexChanged(int)
|
||||
{
|
||||
const bool libreTranslate = isLibreTranslateProvider();
|
||||
ui->label_9->setVisible(!libreTranslate);
|
||||
ui->deeplApiKey->setVisible(!libreTranslate);
|
||||
ui->checkDeeplUsageButton->setVisible(!libreTranslate);
|
||||
ui->libreTranslateUrlLabel->setVisible(libreTranslate);
|
||||
ui->libreTranslateUrl->setVisible(libreTranslate);
|
||||
setConfigValue("translation-provider", ui->translationProviderCombo->currentText());
|
||||
if (libreTranslate) {
|
||||
loadLibreTranslateLanguages();
|
||||
} else {
|
||||
translationsMap.clear();
|
||||
loadDeeplTranslationPossibilities();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_libreTranslateUrl_editingFinished()
|
||||
{
|
||||
setConfigValue("libretranslate-url", ui->libreTranslateUrl->text().trimmed());
|
||||
if (isLibreTranslateProvider()) {
|
||||
loadLibreTranslateLanguages();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::loadLibreTranslateLanguages()
|
||||
{
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, &QNetworkAccessManager::finished, this, [this, manager](QNetworkReply *reply) {
|
||||
if (!isLibreTranslateProvider()) {
|
||||
// The provider changed while this request was in flight.
|
||||
} else if (reply->error() != QNetworkReply::NoError) {
|
||||
ui->statusbar->showMessage(tr("LibreTranslate nicht erreichbar: %1").arg(reply->errorString()), 6000);
|
||||
} else {
|
||||
translationsMap.clear();
|
||||
const QJsonArray languages = QJsonDocument::fromJson(reply->readAll()).array();
|
||||
for (const QJsonValue &value : languages) {
|
||||
const QJsonObject language = value.toObject();
|
||||
const QString source = language.value("code").toString();
|
||||
for (const QJsonValue &target : language.value("targets").toArray()) {
|
||||
translationsMap[source].push_back(target.toString());
|
||||
}
|
||||
}
|
||||
renderDeeplSources();
|
||||
}
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
});
|
||||
manager->get(QNetworkRequest(QUrl(libreTranslateUrl("/languages"))));
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete ui;
|
||||
@@ -559,6 +628,9 @@ void MainWindow::renderDeeplSources()
|
||||
}
|
||||
|
||||
void MainWindow::onDeeplTranslationPossibilitiesLoaded(QNetworkReply *reply) {
|
||||
if (isLibreTranslateProvider()) {
|
||||
return;
|
||||
}
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray responseData = reply->readAll();
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
||||
@@ -638,7 +710,8 @@ void MainWindow::on_deeplTranslateFrom_currentTextChanged(const QString &sourceL
|
||||
}
|
||||
|
||||
void MainWindow::on_autoTranslateButton_clicked() {
|
||||
if (ui->deeplApiKey->text().isEmpty()) {
|
||||
const bool libreTranslate = isLibreTranslateProvider();
|
||||
if (!libreTranslate && ui->deeplApiKey->text().isEmpty()) {
|
||||
QMessageBox::information(this, "Deepl error", "For auto translation, you need to add a deepl API key.");
|
||||
return;
|
||||
}
|
||||
@@ -648,21 +721,29 @@ void MainWindow::on_autoTranslateButton_clicked() {
|
||||
QJsonObject jsonData;
|
||||
// Protect Mustache-/Platzhalter vor DeepL-Übersetzung
|
||||
QString protectedText = protectPlaceholdersForDeepl(originalText);
|
||||
jsonData["text"] = QJsonArray::fromStringList({protectedText});
|
||||
jsonData["source_lang"] = sourceLang;
|
||||
jsonData["target_lang"] = targetLang;
|
||||
jsonData["formality"] = "less";
|
||||
// Use XML tag handling so our <x id="N"/> placeholders are preserved
|
||||
jsonData["tag_handling"] = "xml";
|
||||
jsonData["tag_handling_version"] = "v2";
|
||||
QUrl url("https://api-free.deepl.com/v2/translate");
|
||||
if (libreTranslate) {
|
||||
jsonData["q"] = protectedText;
|
||||
jsonData["source"] = sourceLang;
|
||||
jsonData["target"] = targetLang;
|
||||
jsonData["format"] = "text";
|
||||
} else {
|
||||
jsonData["text"] = QJsonArray::fromStringList({protectedText});
|
||||
jsonData["source_lang"] = sourceLang;
|
||||
jsonData["target_lang"] = targetLang;
|
||||
jsonData["formality"] = "less";
|
||||
jsonData["tag_handling"] = "xml";
|
||||
jsonData["tag_handling_version"] = "v2";
|
||||
}
|
||||
QUrl url(libreTranslate ? libreTranslateUrl("/translate") : QStringLiteral("https://api-free.deepl.com/v2/translate"));
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "DeepL-Auth-Key " + ui->deeplApiKey->text().toUtf8());
|
||||
if (!libreTranslate) {
|
||||
request.setRawHeader("Authorization", "DeepL-Auth-Key " + ui->deeplApiKey->text().toUtf8());
|
||||
}
|
||||
static int attempt = 1;
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, &QNetworkAccessManager::finished, this,
|
||||
[this, manager, jsonData, request](QNetworkReply *reply) mutable {
|
||||
[this, manager, jsonData, request, libreTranslate](QNetworkReply *reply) mutable {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qDebug() << "Translation request failed:" << reply->errorString();
|
||||
qDebug() << "Attempt:" << attempt;
|
||||
@@ -675,7 +756,7 @@ void MainWindow::on_autoTranslateButton_clicked() {
|
||||
}
|
||||
} else {
|
||||
attempt = 1;
|
||||
translationRequestFinished(reply);
|
||||
translationRequestFinished(reply, libreTranslate);
|
||||
}
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
@@ -686,7 +767,8 @@ void MainWindow::on_autoTranslateButton_clicked() {
|
||||
|
||||
void MainWindow::on_bulkAutoTranslateButton_clicked()
|
||||
{
|
||||
if (ui->deeplApiKey->text().isEmpty()) {
|
||||
bulkUsesLibreTranslate = isLibreTranslateProvider();
|
||||
if (!bulkUsesLibreTranslate && ui->deeplApiKey->text().isEmpty()) {
|
||||
QMessageBox::information(this, tr("DeepL error"), tr("Für die automatische Übersetzung wird ein DeepL-API-Key benötigt."));
|
||||
return;
|
||||
}
|
||||
@@ -718,6 +800,54 @@ void MainWindow::on_bulkAutoTranslateButton_clicked()
|
||||
requestNextBulkTranslationBatch();
|
||||
}
|
||||
|
||||
void MainWindow::on_checkDeeplUsageButton_clicked()
|
||||
{
|
||||
if (ui->deeplApiKey->text().isEmpty()) {
|
||||
QMessageBox::information(this, tr("DeepL error"), tr("Bitte hinterlege zuerst einen DeepL-API-Key."));
|
||||
return;
|
||||
}
|
||||
|
||||
ui->checkDeeplUsageButton->setEnabled(false);
|
||||
ui->statusbar->showMessage(tr("DeepL-Kontingent wird geprüft …"));
|
||||
QNetworkRequest request(QUrl("https://api-free.deepl.com/v2/usage"));
|
||||
request.setRawHeader("Authorization", "DeepL-Auth-Key " + ui->deeplApiKey->text().toUtf8());
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, &QNetworkAccessManager::finished, this, [this, manager](QNetworkReply *reply) {
|
||||
ui->checkDeeplUsageButton->setEnabled(true);
|
||||
ui->statusbar->clearMessage();
|
||||
const QByteArray responseBody = reply->readAll();
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QString details;
|
||||
const QJsonDocument response = QJsonDocument::fromJson(responseBody);
|
||||
if (response.isObject()) {
|
||||
details = response.object().value("message").toString();
|
||||
}
|
||||
QMessageBox::critical(this, tr("DeepL error"),
|
||||
tr("Das DeepL-Kontingent konnte nicht abgefragt werden: %1")
|
||||
.arg(details.isEmpty() ? reply->errorString() : details));
|
||||
} else {
|
||||
const QJsonObject usage = QJsonDocument::fromJson(responseBody).object();
|
||||
const qint64 usedCharacters = usage.value("character_count").toVariant().toLongLong();
|
||||
const qint64 characterLimit = usage.value("character_limit").toVariant().toLongLong();
|
||||
if (characterLimit <= 0) {
|
||||
QMessageBox::critical(this, tr("DeepL error"), tr("DeepL hat keine gültigen Kontingentdaten geliefert."));
|
||||
} else {
|
||||
const qint64 remainingCharacters = characterLimit - usedCharacters;
|
||||
const double usedPercentage = 100.0 * usedCharacters / characterLimit;
|
||||
QMessageBox::information(this, tr("DeepL-Kontingent"),
|
||||
tr("Verbraucht: %1 von %2 Zeichen (%3 %).\nVerfügbar: %4 Zeichen.")
|
||||
.arg(QLocale().toString(usedCharacters),
|
||||
QLocale().toString(characterLimit),
|
||||
QLocale().toString(usedPercentage, 'f', 1),
|
||||
QLocale().toString(remainingCharacters)));
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
});
|
||||
manager->get(request);
|
||||
}
|
||||
|
||||
void MainWindow::requestNextBulkTranslationBatch()
|
||||
{
|
||||
if (nextBulkTranslationIndex >= bulkTranslations.size()) {
|
||||
@@ -744,16 +874,25 @@ void MainWindow::requestNextBulkTranslationBatch()
|
||||
}
|
||||
|
||||
QJsonObject payload;
|
||||
payload["text"] = QJsonArray::fromStringList(texts);
|
||||
payload["source_lang"] = ui->deeplTranslateFrom->currentText();
|
||||
payload["target_lang"] = ui->deeplTranslateTo->currentText();
|
||||
payload["formality"] = "less";
|
||||
payload["tag_handling"] = "xml";
|
||||
payload["tag_handling_version"] = "v2";
|
||||
if (bulkUsesLibreTranslate) {
|
||||
payload["q"] = QJsonArray::fromStringList(texts);
|
||||
payload["source"] = ui->deeplTranslateFrom->currentText();
|
||||
payload["target"] = ui->deeplTranslateTo->currentText();
|
||||
payload["format"] = "text";
|
||||
} else {
|
||||
payload["text"] = QJsonArray::fromStringList(texts);
|
||||
payload["source_lang"] = ui->deeplTranslateFrom->currentText();
|
||||
payload["target_lang"] = ui->deeplTranslateTo->currentText();
|
||||
payload["formality"] = "less";
|
||||
payload["tag_handling"] = "xml";
|
||||
payload["tag_handling_version"] = "v2";
|
||||
}
|
||||
|
||||
QNetworkRequest request(QUrl("https://api-free.deepl.com/v2/translate"));
|
||||
QNetworkRequest request(QUrl(bulkUsesLibreTranslate ? libreTranslateUrl("/translate") : QStringLiteral("https://api-free.deepl.com/v2/translate")));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "DeepL-Auth-Key " + ui->deeplApiKey->text().toUtf8());
|
||||
if (!bulkUsesLibreTranslate) {
|
||||
request.setRawHeader("Authorization", "DeepL-Auth-Key " + ui->deeplApiKey->text().toUtf8());
|
||||
}
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, &QNetworkAccessManager::finished, this,
|
||||
[this, manager, firstItem, itemCount = texts.size()](QNetworkReply *reply) {
|
||||
@@ -769,13 +908,21 @@ void MainWindow::handleBulkTranslationReply(QNetworkReply *reply, int firstItem,
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
ui->bulkAutoTranslateButton->setEnabled(true);
|
||||
ui->statusbar->clearMessage();
|
||||
QMessageBox::critical(this, tr("DeepL error"), tr("Die Sammelübersetzung ist fehlgeschlagen: %1").arg(reply->errorString()));
|
||||
const int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
const QString errorMessage = httpStatus == 456
|
||||
? tr("Das DeepL-Zeichenkontingent für diesen API-Key ist ausgeschöpft. "
|
||||
"Bei API Free wird es im nächsten Abrechnungszeitraum zurückgesetzt; bei API Pro prüfe bitte das Cost-Control-Limit.")
|
||||
: tr("Die Sammelübersetzung ist fehlgeschlagen: %1").arg(reply->errorString());
|
||||
QMessageBox::critical(this, tr("DeepL error"), errorMessage);
|
||||
bulkTranslations.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonDocument response = QJsonDocument::fromJson(reply->readAll());
|
||||
const QJsonArray translations = response.object().value("translations").toArray();
|
||||
const QJsonObject responseObject = response.object();
|
||||
const QJsonArray translations = bulkUsesLibreTranslate
|
||||
? responseObject.value("translatedText").toArray()
|
||||
: responseObject.value("translations").toArray();
|
||||
if (translations.size() != itemCount) {
|
||||
ui->bulkAutoTranslateButton->setEnabled(true);
|
||||
ui->statusbar->clearMessage();
|
||||
@@ -785,7 +932,9 @@ void MainWindow::handleBulkTranslationReply(QNetworkReply *reply, int firstItem,
|
||||
}
|
||||
|
||||
for (int index = 0; index < translations.size(); ++index) {
|
||||
QString translatedText = translations.at(index).toObject().value("text").toString();
|
||||
QString translatedText = bulkUsesLibreTranslate
|
||||
? translations.at(index).toString()
|
||||
: translations.at(index).toObject().value("text").toString();
|
||||
BulkTranslationItem &item = bulkTranslations[firstItem + index];
|
||||
item.translatedText = restoreBulkPlaceholders(translatedText, item.placeholders);
|
||||
item.translatedText.replace('"', '\'');
|
||||
@@ -915,16 +1064,17 @@ void MainWindow::showBulkTranslationDialog()
|
||||
bulkTranslations.clear();
|
||||
}
|
||||
|
||||
void MainWindow::translationRequestFinished(QNetworkReply *reply) {
|
||||
void MainWindow::translationRequestFinished(QNetworkReply *reply, bool isLibreTranslate) {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray responseData = reply->readAll();
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
||||
if (!jsonResponse.isNull() && jsonResponse.isObject()) {
|
||||
QJsonObject jsonObject = jsonResponse.object();
|
||||
QJsonArray translationsArray = jsonObject["translations"].toArray();
|
||||
if (!translationsArray.isEmpty()) {
|
||||
QJsonObject translationObject = translationsArray[0].toObject();
|
||||
QString translatedText = translationObject["text"].toString();
|
||||
if (isLibreTranslate || !translationsArray.isEmpty()) {
|
||||
QString translatedText = isLibreTranslate
|
||||
? jsonObject["translatedText"].toString()
|
||||
: translationsArray[0].toObject()["text"].toString();
|
||||
// Restore any placeholders that were protected before the request
|
||||
QString restored = restorePlaceholdersFromDeepl(translatedText);
|
||||
restored.replace("\"", "'");
|
||||
|
||||
Reference in New Issue
Block a user