Füge Funktion zur Überprüfung identischer Übersetzungen hinzu und aktualisiere die Benutzeroberfläche

This commit is contained in:
Torsten Schulz (local)
2026-09-16 16:00:55 +02:00
parent 778eef512f
commit d1c68d78f1
3 changed files with 114 additions and 10 deletions

View File

@@ -17,6 +17,8 @@
#include <QDialogButtonBox>
#include <QInputDialog>
#include <QClipboard>
#include <QCheckBox>
#include <QCryptographicHash>
#include <QApplication>
#include <QCoreApplication>
#include <QTimer>
@@ -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<QTreeWidgetItem *> 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<QCheckBox *> 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);