Verbessere die Benutzeroberfläche und die Leistung beim Laden von Übersetzungen mit Fortschrittsanzeigen und Fehlerbehandlung

This commit is contained in:
Torsten Schulz (local)
2026-09-16 14:49:16 +02:00
parent 3a0f28bdd1
commit 778eef512f

View File

@@ -17,6 +17,8 @@
#include <QDialogButtonBox>
#include <QInputDialog>
#include <QClipboard>
#include <QApplication>
#include <QCoreApplication>
#include <QTimer>
#include <QDialog>
#include <QDialogButtonBox>
@@ -26,8 +28,10 @@
#include <QMenu>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QProgressDialog>
#include <QScrollArea>
#include <QSet>
#include <QSignalBlocker>
#include <QScrollBar>
#include <QVBoxLayout>
@@ -168,10 +172,27 @@ MainWindow::MainWindow(QWidget *parent)
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();
if (configuration.contains("last-language") && ui->languageCombo->findText(configuration["last-language"].toString()) >= 0) {
ui->languageCombo->setCurrentText(configuration["last-language"].toString());
}
// Let the main window become visible before scanning potentially large projects.
QTimer::singleShot(0, this, [this] {
const QString savedLanguage = configuration.value("last-language").toString();
QProgressDialog startupProgress(tr("Projekt wird vorbereitet …"), QString(), 0, 2, this);
startupProgress.setWindowTitle(tr("Bitte warten"));
startupProgress.setCancelButton(nullptr);
startupProgress.setMinimumDuration(0);
startupProgress.setWindowModality(Qt::ApplicationModal);
startupProgress.show();
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
crawlProject();
startupProgress.setValue(1);
startupProgress.setLabelText(tr("Letzte Sprache und Übersetzungsdateien werden geladen …"));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
if (!savedLanguage.isEmpty() && ui->languageCombo->findText(savedLanguage) >= 0) {
const QSignalBlocker blockLanguageSelection(ui->languageCombo);
ui->languageCombo->setCurrentText(savedLanguage);
on_languageCombo_currentTextChanged(savedLanguage);
}
startupProgress.setValue(2);
});
}
noConfigChange = false;
}
@@ -270,6 +291,7 @@ void MainWindow::crawlProject() {
QMessageBox::information(this, tr("Information"), tr("No translations available."));
return;
}
const QSignalBlocker blockLanguageSelection(ui->languageCombo);
ui->languageCombo->clear();
ui->languageCombo->addItems(languageDirs);
ui->languageCombo->setCurrentIndex(-1);
@@ -286,17 +308,39 @@ void MainWindow::on_languageCombo_currentTextChanged(const QString &selectedLang
QString projectDir = ui->projectDir->text();
QString languageDirPath = QDir::cleanPath(projectDir + QDir::separator() + "game" + QDir::separator() + "tl" + QDir::separator() + selectedLanguage);
QDirIterator it(languageDirPath, QStringList() << "*.rpy", QDir::Files, QDirIterator::Subdirectories);
QStringList filePaths;
while (it.hasNext()) {
QString filePath = it.next();
filePaths.append(it.next());
}
QProgressDialog progress(tr("Übersetzungsdateien werden geladen …"), QString(), 0, filePaths.size(), this);
progress.setWindowTitle(tr("Bitte warten"));
progress.setCancelButton(nullptr);
progress.setMinimumDuration(0);
progress.setWindowModality(Qt::ApplicationModal);
progress.show();
QApplication::setOverrideCursor(Qt::WaitCursor);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
for (int fileIndex = 0; fileIndex < filePaths.size(); ++fileIndex) {
const QString &filePath = filePaths.at(fileIndex);
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
progress.setValue(fileIndex + 1);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
continue;
}
QTextStream in(&file);
QString fileContent = in.readAll();
file.close();
fileContentsMap[filePath] = fileContent;
populateTreeWidgetFromMap();
progress.setValue(fileIndex + 1);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
progress.setLabelText(tr("Übersetzungsliste wird vorbereitet …"));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
populateTreeWidgetFromMap();
QApplication::restoreOverrideCursor();
}
void MainWindow::populateTreeWidgetFromMap() {
@@ -344,22 +388,23 @@ QVector<MainWindow::TranslationItem> MainWindow::parseTextBlock(const QString& i
lineNumber = match.captured(1).toInt();
}
} else if (line.startsWith(" # ") || line.startsWith(" old ")) {
originalText = line.mid(line.indexOf("\"") + 1).trimmed();
originalText.chop(1);
const int firstQuote = line.indexOf('"');
const int lastQuote = line.lastIndexOf('"');
originalText = firstQuote >= 0 && lastQuote > firstQuote
? line.mid(firstQuote + 1, lastQuote - firstQuote - 1)
: QString();
identifier = "";
} else if ((line.startsWith(" ") && !line.startsWith(" # ")) || line.startsWith(" new ")) {
translatedText = line.mid(4).trimmed();
if (translatedText.startsWith("new \"")) {
translatedText = translatedText.mid(5).trimmed();
} else if (translatedText.startsWith("\"")) {
translatedText = translatedText.mid(1).trimmed();
} else {
int firstQuoteIndex = translatedText.indexOf("\"");
int lastQuoteIndex = translatedText.lastIndexOf("\"");
identifier = translatedText.mid(0, firstQuoteIndex).trimmed();
translatedText = translatedText.mid(firstQuoteIndex + 1, lastQuoteIndex - firstQuoteIndex).trimmed();
const QString translationLine = line.startsWith(" ") ? line.mid(4) : line;
const int firstQuote = translationLine.indexOf('"');
const int lastQuote = translationLine.lastIndexOf('"');
if (firstQuote < 0 || lastQuote <= firstQuote) {
continue;
}
translatedText.chop(1);
identifier = translationLine.startsWith("new ") || translationLine.startsWith('"')
? QString()
: translationLine.left(firstQuote).trimmed();
translatedText = translationLine.mid(firstQuote + 1, lastQuote - firstQuote - 1);
blocks.append(TranslationItem(lineNumber, identifier, originalText, translatedText));
originalText.clear();
translatedText.clear();
@@ -382,7 +427,25 @@ void MainWindow::on_reloadTranslationsButton_clicked()
void MainWindow::on_reloadFilesButton_clicked()
{
populateTreeWidgetFromMap();
if (ui->languageCombo->currentIndex() < 0) {
return;
}
bool hasUnsavedChanges = false;
for (int index = 0; index < ui->treeWidget->topLevelItemCount(); ++index) {
if (ui->treeWidget->topLevelItem(index)->data(0, Qt::UserRole).toBool()) {
hasUnsavedChanges = true;
break;
}
}
if (hasUnsavedChanges && QMessageBox::warning(
this,
tr("Ungespeicherte Änderungen"),
tr("Durch das Neuladen werden ungespeicherte Änderungen verworfen. Fortfahren?"),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel) != QMessageBox::Yes) {
return;
}
on_languageCombo_currentTextChanged(ui->languageCombo->currentText());
}
void MainWindow::on_nextUntranslatedButton_clicked() {
@@ -563,7 +626,9 @@ bool MainWindow::parseAndEditFile(QFile &backupFile, QTreeWidgetItem *fileItem,
QString replacement = QString("\"%1\"").arg(replacedText.isEmpty() ? newText : replacedText);
auto replacedBlock = block.replace(toReplace, replacement);
content.replace(match.capturedStart(0), match.capturedLength(0), replacedBlock);
offset = match.capturedEnd(0);
// The replacement can have a different length. Continue after the new
// block, otherwise later translations in this file may be skipped.
offset = match.capturedStart(0) + replacedBlock.size();
changed = true;
}
offset = 0;
@@ -577,7 +642,7 @@ bool MainWindow::parseAndEditFile(QFile &backupFile, QTreeWidgetItem *fileItem,
QString replacement = QString("\"%1\"").arg(replacedText.isEmpty() ? newText : replacedText);
auto replacedBlock = block.replace(toReplace, replacement);
content.replace(match.capturedStart(0), match.capturedLength(0), replacedBlock);
offset = match.capturedEnd(0);
offset = match.capturedStart(0) + replacedBlock.size();
changed = true;
}
lines.append(content.split("\n"));
@@ -988,6 +1053,7 @@ void MainWindow::showBulkTranslationDialog()
const bool dialogUsesLibreTranslate = bulkUsesLibreTranslate;
const QString dialogSourceLanguage = ui->deeplTranslateFrom->currentText();
const QString dialogTargetLanguage = ui->deeplTranslateTo->currentText();
QSet<QTreeWidgetItem *> savedBulkFiles;
edits.reserve(bulkTranslations.size());
QTreeWidgetItem *currentFile = nullptr;
QVBoxLayout *fileLayout = nullptr;
@@ -1086,9 +1152,12 @@ void MainWindow::showBulkTranslationDialog()
scrollArea->setWidget(content);
layout->addWidget(scrollArea);
auto applyTranslations = [this, &edits] {
auto applyTranslations = [this, &edits, &savedBulkFiles] {
for (int index = 0; index < bulkTranslations.size(); ++index) {
QTreeWidgetItem *item = bulkTranslations[index].item;
if (savedBulkFiles.contains(item->parent())) {
continue;
}
item->setText(3, edits[index]->toPlainText());
if (QTreeWidgetItem *parent = item->parent()) {
parent->setData(0, Qt::UserRole, true);
@@ -1100,7 +1169,7 @@ void MainWindow::showBulkTranslationDialog()
int currentFileSection = 0;
QSet<QTreeWidgetItem *> promptedFiles;
connect(scrollArea->verticalScrollBar(), &QScrollBar::valueChanged, &dialog,
[this, &fileSections, &currentFileSection, &promptedFiles, applyTranslations](int scrollPosition) {
[this, &fileSections, &currentFileSection, &promptedFiles, &savedBulkFiles, applyTranslations](int scrollPosition) {
int visibleFileSection = currentFileSection;
for (int index = 0; index < fileSections.size(); ++index) {
if (fileSections[index].first->y() <= scrollPosition + 12) {
@@ -1113,7 +1182,8 @@ void MainWindow::showBulkTranslationDialog()
return;
}
QTreeWidgetItem *completedFile = fileSections[currentFileSection].second;
const int completedFileSection = currentFileSection;
QTreeWidgetItem *completedFile = fileSections[completedFileSection].second;
currentFileSection = visibleFileSection;
if (promptedFiles.contains(completedFile)) {
return;
@@ -1131,6 +1201,9 @@ void MainWindow::showBulkTranslationDialog()
QString errorMessage;
if (!saveFile(completedFile, errorMessage)) {
QMessageBox::critical(this, tr("Error"), errorMessage);
} else {
savedBulkFiles.insert(completedFile);
fileSections[completedFileSection].first->hide();
}
}
});