From d1c68d78f1f0a3257af602fd3ea084da1a433910 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 16 Sep 2026 16:00:55 +0200 Subject: [PATCH] =?UTF-8?q?F=C3=BCge=20Funktion=20zur=20=C3=9Cberpr=C3=BCf?= =?UTF-8?q?ung=20identischer=20=C3=9Cbersetzungen=20hinzu=20und=20aktualis?= =?UTF-8?q?iere=20die=20Benutzeroberfl=C3=A4che?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mainwindow.cpp | 118 ++++++++++++++++++++++++++++++++++++++++++++----- mainwindow.h | 5 +++ mainwindow.ui | 1 + 3 files changed, 114 insertions(+), 10 deletions(-) diff --git a/mainwindow.cpp b/mainwindow.cpp index 57f7739..5aa91e3 100755 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -454,7 +456,7 @@ void MainWindow::on_nextUntranslatedButton_clicked() { for (int j = 0; j < topLevelItem->childCount(); ++j) { QTreeWidgetItem *childItem = topLevelItem->child(j); // Prüfe, ob Übersetzung (Spalte 3) leer ist - if (childItem->text(3).isEmpty()) { + if (isOpenTranslation(childItem)) { ui->originalTextEdit->setText(childItem->text(2)); // Originaltext aus Spalte 2 ui->translationEdit->clear(); ui->treeWidget->scrollToItem(childItem); @@ -622,9 +624,10 @@ bool MainWindow::parseAndEditFile(QFile &backupFile, QTreeWidgetItem *fileItem, auto oldText = match.captured(4); auto newText = match.captured(6); auto replacedText = findNewText(fileItem, lineNumber, oldText, speaker); - QString toReplace = QString("\"%1\"").arg(newText).trimmed(); - QString replacement = QString("\"%1\"").arg(replacedText.isEmpty() ? newText : replacedText); - auto replacedBlock = block.replace(toReplace, replacement); + QString replacedBlock = block; + const int translatedTextOffset = match.capturedStart(6) - match.capturedStart(0); + replacedBlock.replace(translatedTextOffset, match.capturedLength(6), + replacedText.isEmpty() ? newText : replacedText); content.replace(match.capturedStart(0), match.capturedLength(0), replacedBlock); // The replacement can have a different length. Continue after the new // block, otherwise later translations in this file may be skipped. @@ -638,9 +641,10 @@ bool MainWindow::parseAndEditFile(QFile &backupFile, QTreeWidgetItem *fileItem, auto oldText = match.captured(3); auto newText = match.captured(4); auto replacedText = findNewText(fileItem, lineNumber, oldText, ""); - QString toReplace = QString("\"%1\"").arg(newText); - QString replacement = QString("\"%1\"").arg(replacedText.isEmpty() ? newText : replacedText); - auto replacedBlock = block.replace(toReplace, replacement); + QString replacedBlock = block; + const int translatedTextOffset = match.capturedStart(4) - match.capturedStart(0); + replacedBlock.replace(translatedTextOffset, match.capturedLength(4), + replacedText.isEmpty() ? newText : replacedText); content.replace(match.capturedStart(0), match.capturedLength(0), replacedBlock); offset = match.capturedStart(0) + replacedBlock.size(); changed = true; @@ -850,6 +854,66 @@ void MainWindow::on_retryTranslationButton_clicked() on_autoTranslateButton_clicked(); } +void MainWindow::on_reviewIdenticalTranslationsButton_clicked() +{ + QVector candidates; + for (int fileIndex = 0; fileIndex < ui->treeWidget->topLevelItemCount(); ++fileIndex) { + QTreeWidgetItem *fileItem = ui->treeWidget->topLevelItem(fileIndex); + for (int itemIndex = 0; itemIndex < fileItem->childCount(); ++itemIndex) { + QTreeWidgetItem *item = fileItem->child(itemIndex); + if (!item->text(2).isEmpty() && item->text(2).trimmed() == item->text(3).trimmed() + && !isIdenticalTranslationApproved(item)) { + candidates.append(item); + } + } + } + if (candidates.isEmpty()) { + QMessageBox::information(this, tr("Gleiche Texte prüfen"), tr("Es gibt keine unbestätigten, originalgleichen Übersetzungen.")); + return; + } + + QDialog dialog(this); + dialog.setWindowTitle(tr("Originalgleiche Texte prüfen")); + dialog.resize(760, 620); + auto *layout = new QVBoxLayout(&dialog); + auto *description = new QLabel(tr("Markiere nur Texte, die absichtlich unverändert bleiben sollen. Nicht markierte Texte bleiben offen und werden bei der nächsten Sammelübersetzung erneut angefragt."), &dialog); + description->setWordWrap(true); + layout->addWidget(description); + auto *scrollArea = new QScrollArea(&dialog); + scrollArea->setWidgetResizable(true); + auto *content = new QWidget(scrollArea); + auto *contentLayout = new QVBoxLayout(content); + QVector checks; + checks.reserve(candidates.size()); + for (QTreeWidgetItem *item : std::as_const(candidates)) { + const QString text = tr("%1 — Zeile %2: %3").arg(item->parent()->text(0), item->text(0), item->text(2)); + auto *check = new QCheckBox(text, content); + contentLayout->addWidget(check); + checks.append(check); + } + contentLayout->addStretch(); + scrollArea->setWidget(content); + layout->addWidget(scrollArea); + auto *buttons = new QDialogButtonBox(QDialogButtonBox::Apply | QDialogButtonBox::Cancel, &dialog); + buttons->button(QDialogButtonBox::Apply)->setText(tr("Ausgewählte bestätigen")); + connect(buttons->button(QDialogButtonBox::Apply), &QPushButton::clicked, &dialog, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + layout->addWidget(buttons); + + if (dialog.exec() != QDialog::Accepted) { + return; + } + QJsonObject approvals = configuration.value("approved-identical-translations").toObject(); + for (int index = 0; index < candidates.size(); ++index) { + if (checks[index]->isChecked()) { + approvals[identicalTranslationApprovalKey(candidates[index])] = true; + } + } + configuration["approved-identical-translations"] = approvals; + saveConfiguration(); + countAndShowUntranslated(); +} + void MainWindow::on_bulkAutoTranslateButton_clicked() { bulkUsesLibreTranslate = isLibreTranslateProvider(); @@ -867,7 +931,7 @@ void MainWindow::on_bulkAutoTranslateButton_clicked() QTreeWidgetItem *fileItem = ui->treeWidget->topLevelItem(fileIndex); for (int itemIndex = 0; itemIndex < fileItem->childCount(); ++itemIndex) { QTreeWidgetItem *item = fileItem->child(itemIndex); - if (!item->text(2).isEmpty() && item->text(3).isEmpty()) { + if (isOpenTranslation(item)) { BulkTranslationItem translation{item, item->text(2), QString(), {}}; bulkTranslations.append(std::move(translation)); } @@ -1158,7 +1222,12 @@ void MainWindow::showBulkTranslationDialog() if (savedBulkFiles.contains(item->parent())) { continue; } - item->setText(3, edits[index]->toPlainText()); + const QString translation = edits[index]->toPlainText(); + // An unchanged source string is not an automatic translation. Leave + // it open so it can be retried with a working language pair. + item->setText(3, translation.trimmed() == bulkTranslations[index].originalText.trimmed() + ? QString() + : translation); if (QTreeWidgetItem *parent = item->parent()) { parent->setData(0, Qt::UserRole, true); } @@ -1301,7 +1370,7 @@ void MainWindow::countAndShowUntranslated() { int untranslatedInSectionCount = 0; for (int i = 0; i < parentItem->childCount(); ++i) { QTreeWidgetItem *childItem = parentItem->child(i); - if (!childItem->text(2).isEmpty() && childItem->text(3).isEmpty()) { + if (isOpenTranslation(childItem)) { untranslatedCount++; untranslatedInSectionCount++; } @@ -1317,6 +1386,30 @@ void MainWindow::countAndShowUntranslated() { ui->untranslatedLabel->setText(QString("%1").arg(untranslatedCount)); } +bool MainWindow::isOpenTranslation(const QTreeWidgetItem *item) const +{ + if (!item || item->text(2).isEmpty()) { + return false; + } + const QString original = item->text(2).trimmed(); + const QString translation = item->text(3).trimmed(); + return translation.isEmpty() || (translation == original && !isIdenticalTranslationApproved(item)); +} + +QString MainWindow::identicalTranslationApprovalKey(const QTreeWidgetItem *item) const +{ + const QTreeWidgetItem *fileItem = item ? item->parent() : nullptr; + const QString filePath = fileItem ? fileItem->data(0, Qt::UserRole + 1).toString() : QString(); + const QString rawKey = filePath + '\n' + (item ? item->text(0) : QString()) + '\n' + (item ? item->text(2) : QString()); + return QString::fromLatin1(QCryptographicHash::hash(rawKey.toUtf8(), QCryptographicHash::Sha256).toHex()); +} + +bool MainWindow::isIdenticalTranslationApproved(const QTreeWidgetItem *item) const +{ + return configuration.value("approved-identical-translations").toObject() + .value(identicalTranslationApprovalKey(item)).toBool(); +} + void MainWindow::on_searchButton_clicked() { bool ok; QString query = QInputDialog::getText(this, "Search", "Search for:", QLineEdit::Normal, "", &ok); @@ -1359,6 +1452,11 @@ void MainWindow::setConfigValue(const QString &key, const QString &value) { return; } configuration[key] = value; + saveConfiguration(); +} + +void MainWindow::saveConfiguration() +{ QString configPath = QDir::homePath() + "/.renpytranslate.conf"; QJsonDocument doc(configuration); QFile configFile(configPath); diff --git a/mainwindow.h b/mainwindow.h index 5fb43d3..3dfc16d 100755 --- a/mainwindow.h +++ b/mainwindow.h @@ -42,6 +42,7 @@ private slots: void on_deeplTranslateFrom_currentTextChanged(const QString &sourceLanguage); void on_autoTranslateButton_clicked(); void on_retryTranslationButton_clicked(); + void on_reviewIdenticalTranslationsButton_clicked(); void on_bulkAutoTranslateButton_clicked(); void on_checkDeeplUsageButton_clicked(); void on_translationProviderCombo_currentIndexChanged(int index); @@ -96,6 +97,10 @@ private: void showBulkTranslationDialog(); void onDeeplTranslationAuthenticationError(QNetworkReply *reply, QAuthenticator *authenticator); void countAndShowUntranslated(); + bool isOpenTranslation(const QTreeWidgetItem *item) const; + bool isIdenticalTranslationApproved(const QTreeWidgetItem *item) const; + QString identicalTranslationApprovalKey(const QTreeWidgetItem *item) const; + void saveConfiguration(); void searchNext(); QVector parseTextBlock(const QString &block); void setConfigValue(const QString &key, const QString &value); diff --git a/mainwindow.ui b/mainwindow.ui index 8a8f24d..d6d4e2f 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -84,6 +84,7 @@ Aktuellen Text übersetzen Alle offenen Texte übersetzen + Prüft Texte, deren Übersetzung absichtlich mit dem Original identisch sein soll.Gleiche Texte prüfen Qt::Horizontal4020