From 963c46e909231dd0e71c242d82195df024ce575e Mon Sep 17 00:00:00 2001 From: Torsten Schulz Date: Mon, 26 Feb 2024 18:23:30 +0100 Subject: [PATCH] Renpy translation helper --- .gitignore | 75 +++++ CMakeLists.txt | 80 ++++++ RenpyTranslationHelper_en_GB.ts | 3 + main.cpp | 23 ++ mainwindow.cpp | 474 ++++++++++++++++++++++++++++++++ mainwindow.h | 59 ++++ mainwindow.ui | 356 ++++++++++++++++++++++++ 7 files changed, 1070 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 RenpyTranslationHelper_en_GB.ts create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8edc8f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe +CMakeLists.txt.usr + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..55cf361 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,80 @@ +cmake_minimum_required(VERSION 3.5) + +project(RenpyTranslationHelper VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools) + +set(TS_FILES RenpyTranslationHelper_en_GB.ts) + +set(PROJECT_SOURCES + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui + ${TS_FILES} +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(RenpyTranslationHelper + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ) +# Define target properties for Android with Qt 6 as: +# set_property(TARGET RenpyTranslationHelper APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR +# ${CMAKE_CURRENT_SOURCE_DIR}/android) +# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation + + qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) +else() + if(ANDROID) + add_library(RenpyTranslationHelper SHARED + ${PROJECT_SOURCES} + ) +# Define properties for Android with Qt 5 after find_package() calls as: +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") + else() + add_executable(RenpyTranslationHelper + ${PROJECT_SOURCES} + ) + endif() + + qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) +endif() + +target_link_libraries(RenpyTranslationHelper PRIVATE + Qt${QT_VERSION_MAJOR}::Widgets + Qt5Network +) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +if(${QT_VERSION} VERSION_LESS 6.1.0) + set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.RenpyTranslationHelper) +endif() +set_target_properties(RenpyTranslationHelper PROPERTIES + ${BUNDLE_ID_OPTION} + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +include(GNUInstallDirs) +install(TARGETS RenpyTranslationHelper + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(RenpyTranslationHelper) +endif() diff --git a/RenpyTranslationHelper_en_GB.ts b/RenpyTranslationHelper_en_GB.ts new file mode 100644 index 0000000..76f52a4 --- /dev/null +++ b/RenpyTranslationHelper_en_GB.ts @@ -0,0 +1,3 @@ + + + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..c2d5bae --- /dev/null +++ b/main.cpp @@ -0,0 +1,23 @@ +#include "mainwindow.h" + +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + QTranslator translator; + const QStringList uiLanguages = QLocale::system().uiLanguages(); + for (const QString &locale : uiLanguages) { + const QString baseName = "RenpyTranslationHelper_" + QLocale(locale).name(); + if (translator.load(":/i18n/" + baseName)) { + a.installTranslator(&translator); + break; + } + } + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..191b09d --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,474 @@ +#include "mainwindow.h" +#include "./ui_mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + loadDeeplTranslationPossibilities(); + ui->setupUi(this); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + + +void MainWindow::on_selectProjectDirButton_clicked() +{ + QString selectedDir = QFileDialog::getExistingDirectory(this, tr("Verzeichnis auswählen"), QDir::homePath()); + if (!selectedDir.isEmpty()) { + ui->projectDir->setText(selectedDir); + crawlProject(); + } +} + +void MainWindow::crawlProject() { + QString projectDir = ui->projectDir->text(); + QDir dir(projectDir); + if (!dir.exists()) { + QMessageBox::critical(this, tr("Error"), tr("The project directory does not exist!")); + return; + } + QString tlDirPath = QDir::cleanPath(projectDir + QDir::separator() + "game" + QDir::separator() + "tl"); + if (!QDir(tlDirPath).exists()) { + QMessageBox::information(this, tr("Information"), tr("No translations available.")); + return; + } + QStringList languageDirs = QDir(tlDirPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot); + if (languageDirs.isEmpty()) { + QMessageBox::information(this, tr("Information"), tr("No translations available.")); + return; + } + ui->languageCombo->clear(); + ui->languageCombo->addItems(languageDirs); + ui->languageCombo->setCurrentIndex(-1); +} + +void MainWindow::on_reloadButton_clicked() +{ + crawlProject(); +} + + +void MainWindow::on_languageCombo_currentTextChanged(const QString &selectedLanguage) +{ + fileContentsMap.clear(); + ui->treeWidget->clear(); + if (ui->languageCombo->currentIndex() == -1) { + return; + } + 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); + while (it.hasNext()) { + QString filePath = it.next(); + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + continue; + QTextStream in(&file); + QString fileContent = in.readAll(); + file.close(); + fileContentsMap[filePath] = fileContent; + populateTreeWidgetFromMap(); + } +} + +void MainWindow::populateTreeWidgetFromMap() { + ui->treeWidget->clear(); + for (const auto &pair : fileContentsMap) { + QTreeWidgetItem *fileItem = new QTreeWidgetItem(ui->treeWidget); + QString fileName = QFileInfo(pair.first).fileName(); + fileItem->setText(0, fileName); + fileItem->setData(0, Qt::UserRole, false); + QStringList blocks = pair.second.split("\n # "); + for (const QString &block : blocks) { + if (block.trimmed().isEmpty()) { + continue; + } + if (block.contains("\n old") && block.contains("\n new")) { + QStringList lines = block.trimmed().split('\n'); + if (lines.size() >= 3) { + QString lineNumber = lines[0].section(':', 1, 1).trimmed(); + QString originalText = lines[1].section('"', 1, 1).trimmed(); + QString newText = lines[2].section('"', 1, 1).trimmed(); + QTreeWidgetItem *blockItem = new QTreeWidgetItem(fileItem); + blockItem->setText(0, lineNumber); + blockItem->setText(1, originalText); + blockItem->setText(2, newText); + } + } + } + } + ui->treeWidget->resizeColumnToContents(0); + int firstColumnWidth = ui->treeWidget->columnWidth(0); + int remainingWidth = ui->treeWidget->viewport()->width() - firstColumnWidth; + int columnWidth = remainingWidth / 2; + ui->treeWidget->setColumnWidth(1, columnWidth); + ui->treeWidget->setColumnWidth(2, columnWidth); + countAndShowUntranslated(); +} + +void MainWindow::on_reloadTranslationsButton_clicked() +{ + crawlProject(); +} + + +void MainWindow::on_reloadFilesButton_clicked() +{ + populateTreeWidgetFromMap(); +} + +void MainWindow::on_nextUntranslatedButton_clicked() +{ + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) { + QTreeWidgetItem *topLevelItem = ui->treeWidget->topLevelItem(i); + for (int j = 0; j < topLevelItem->childCount(); ++j) { + QTreeWidgetItem *childItem = topLevelItem->child(j); + if (childItem->text(2).isEmpty()) { + ui->originalTextEdit->setText(childItem->text(1)); + ui->translationEdit->clear(); + ui->treeWidget->scrollToItem(childItem); + ui->treeWidget->setCurrentItem(childItem); + return; + } + } + } + QMessageBox::information(this, tr("Information"), tr("No untranslated item found.")); +} + +void MainWindow::on_treeWidget_itemSelectionChanged() +{ + QTreeWidgetItem *selectedItem = ui->treeWidget->currentItem(); + if (selectedItem) { + if (selectedItem->treeWidget()->indexOfTopLevelItem(selectedItem) < 0) { + ui->originalTextEdit->setText(selectedItem->text(1)); + ui->translationEdit->setText(selectedItem->text(2)); + } + } +} + + +void MainWindow::on_setTranslationButton_clicked() +{ + QTreeWidgetItem *selectedItem = ui->treeWidget->currentItem(); + if (selectedItem) { + if (selectedItem->treeWidget()->indexOfTopLevelItem(selectedItem) < 0) { + selectedItem->setText(2, ui->translationEdit->text()); + QTreeWidgetItem *parentItem = selectedItem->parent(); + if (parentItem) { + parentItem->setData(0, Qt::UserRole, true); + } + } + } + countAndShowUntranslated(); +} + + +void MainWindow::on_setTranslationAndJumpNextButton_clicked() +{ + on_setTranslationButton_clicked(); + on_nextUntranslatedButton_clicked(); +} + + +void MainWindow::on_setAndNextAndAutoTranslate_clicked() +{ + on_setTranslationAndJumpNextButton_clicked(); + on_autoTranslateButton_clicked(); +} + + +void MainWindow::on_translationEdit_returnPressed() +{ + qDebug() << ui->enterActionCombo->currentIndex(); + switch (ui->enterActionCombo->currentIndex()) { + case 0: + on_setAndNextAndAutoTranslate_clicked(); + break; + case 1: + on_setTranslationAndJumpNextButton_clicked(); + break; + case 2: + on_setTranslationButton_clicked(); + break; + } +} + + +void MainWindow::on_saveButton_clicked() +{ + QStringList failedFiles; + saveFiles(failedFiles); + if (!failedFiles.isEmpty()) { + QString errorMessage = "Failed to save the following files:\n\n"; + errorMessage += failedFiles.join("\n"); + QMessageBox::critical(this, tr("Error"), errorMessage); + } else { + QMessageBox::information(this, "Information", "The translations were saved succesfully."); + } +} + +void MainWindow::saveFiles(QStringList &failedFiles) { + auto projectDir = ui->projectDir->text(); + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) { + QTreeWidgetItem *fileItem = ui->treeWidget->topLevelItem(i); + if (fileItem->data(0, Qt::UserRole).toBool()) { + QString fileName = fileItem->text(0); + QString filePath = QString("%1/game/tl/%2/%3").arg(projectDir, ui->languageCombo->currentText(), fileName); + QString backupFileName = QString("%1/game/tl/%2/%3.bak.%4") + .arg(projectDir, ui->languageCombo->currentText(), fileName, QDateTime::currentDateTime().toString("yyyyMMddHHmmss")); + if (!createBackup(filePath, backupFileName)) { + failedFiles << filePath + ": Failed to create backup"; + continue; + } + if (!editAndSaveFile(fileItem, backupFileName, filePath)) { + failedFiles << filePath + ": Failed to edit and save file"; + } + fileItem->setData(0, Qt::UserRole, false); + } + } +} + +bool MainWindow::createBackup(const QString &filePath, const QString &backupFileName) { + if (!QFile::rename(filePath, backupFileName)) { + qDebug() << "Failed to create backup file: " << backupFileName; + qDebug() << "Original name: " << filePath; + return false; + } + return true; +} + +bool MainWindow::editAndSaveFile(QTreeWidgetItem *fileItem, const QString &backupFileName, const QString &fileName) { + QFile backupFile(backupFileName); + if (!backupFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "Failed to open backup file for reading: " << backupFileName; + return false; + } + QStringList lines; + bool changed = false; + if (!parseAndEditFile(backupFile, fileItem, lines, changed)) { + qDebug() << "Failed to parse and edit file: " << backupFileName; + return false; + } + backupFile.close(); + if (changed) { + if (!saveToFile(fileName, lines)) { + qDebug() << "Failed to save file: " << backupFileName; + return false; + } + } + return true; +} + +bool MainWindow::parseAndEditFile(QFile &backupFile, QTreeWidgetItem *fileItem, QStringList &lines, bool &changed) { + QTextStream backupStream(&backupFile); + QString fileName = fileItem->text(0); + while (!backupStream.atEnd()) { + QString line = backupStream.readLine().trimmed(); // Trim the line before processing + if (line.startsWith("#") && line.contains(fileName)) { + QString originalIdentifierLine(line.trimmed()); + QString lineNumber = line.section(':', 1, 1).trimmed(); + QString nextLine = backupStream.readLine(); + + if (nextLine.trimmed().startsWith("old")) { + QString originalOldText = nextLine.section('"', 1, 1).trimmed(); + QString originalNewText = backupStream.readLine(); + QString newText = findNewText(fileItem, lineNumber, originalOldText); + if (newText.isEmpty()) { + newText = originalNewText.trimmed(); + } + lines.append(QString(" %1").arg(originalIdentifierLine)); + lines.append(QString(" old \"%1\"").arg(originalOldText)); + if (newText.isEmpty()) { + lines.append(" new \"\""); + } else { + lines.append(QString(" new \"%1\"").arg(newText)); + } + changed = true; + } else { + lines.append(line); + lines.append(nextLine); + } + } else { + lines.append(line); + } + } + return true; +} + + +QString MainWindow::findNewText(QTreeWidgetItem *fileItem, const QString &lineNumber, const QString &originalText) { + for (int i = 0; i < fileItem->childCount(); ++i) { + QTreeWidgetItem *childItem = fileItem->child(i); + if (childItem->text(0) == lineNumber && childItem->text(1) == originalText) { + return childItem->text(2); + } + } + return QString(); +} + +void MainWindow::loadDeeplTranslationPossibilities() { + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + connect(manager, &QNetworkAccessManager::finished, this, &MainWindow::onDeeplTranslationPossibilitiesLoaded); + QNetworkRequest request(QUrl("https://api-free.deepl.com/v2/glossary-language-pairs")); + request.setRawHeader("Authorization", "DeepL-Auth-Key " + deeplAuthKey.toUtf8()); // Replace with your DeepL API key + manager->get(request); +} + +void MainWindow::renderDeeplSources() +{ + ui->deeplTranslateFrom->clear(); + for (const auto& pair : translationsMap) { + ui->deeplTranslateFrom->addItem(pair.first); + } +} + +void MainWindow::onDeeplTranslationPossibilitiesLoaded(QNetworkReply *reply) { + if (reply->error() == QNetworkReply::NoError) { + QByteArray responseData = reply->readAll(); + QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData); + if (jsonResponse.isObject()) { + QJsonObject jsonResponseObject = jsonResponse.object(); + if (jsonResponseObject["supported_languages"].isArray()) { + QJsonArray languagePairsArray = jsonResponseObject["supported_languages"].toArray(); + for (const QJsonValue &value : languagePairsArray) { + QJsonObject languagePair = value.toObject(); + QString sourceLang = languagePair["source_lang"].toString(); + QString targetLang = languagePair["target_lang"].toString(); + translationsMap[sourceLang].push_back(targetLang); + } + renderDeeplSources(); + } else { + qDebug() << "Invalid JSON format: Data not found"; + qDebug() << responseData; + } + } else { + qDebug() << "Invalid JSON format: Response is not an object"; + } + } else { + qDebug() << "Network error:" << reply->errorString(); + } + reply->deleteLater(); +} + +bool MainWindow::saveToFile(const QString &backupFileName, const QStringList &lines) { + QFile backupFile(backupFileName); + if (!backupFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + qDebug() << "Failed to open backup file for writing: " << backupFileName; + return false; + } + + QTextStream out(&backupFile); + out << lines.join("\n") << Qt::endl; + backupFile.close(); + return true; +} + +void MainWindow::on_cleanupButton_clicked() +{ + QString projectDir = ui->projectDir->text(); + QString language = ui->languageCombo->currentText(); + QString tlDirPath = QDir::cleanPath(projectDir + QDir::separator() + "game" + QDir::separator() + "tl" + QDir::separator() + language); + QDir tlDir(tlDirPath); + QStringList filters; + filters << "*.bak.*"; + tlDir.setNameFilters(filters); + QStringList fileList = tlDir.entryList(QDir::Files); + for (int i = 0; i < fileList.size(); ++i) { + const QString &fileName = fileList.at(i); + if (!tlDir.remove(fileName)) { + qDebug() << "Failed to remove" << fileName; + } + } +} + + +void MainWindow::on_cleanupAndSaveButton_clicked() +{ + on_cleanupButton_clicked(); + on_saveButton_clicked(); +} + + +void MainWindow::on_deeplTranslateFrom_currentTextChanged(const QString &sourceLanguage) +{ + ui->deeplTranslateTo->clear(); + auto it = translationsMap.find(sourceLanguage); + if (it != translationsMap.end()) { + for (const auto& targetLang : it->second) { + ui->deeplTranslateTo->addItem(targetLang); + } + } +} + +void MainWindow::on_autoTranslateButton_clicked() { + QString originalText = ui->originalTextEdit->text(); + QString sourceLang = ui->deeplTranslateFrom->currentText(); + QString targetLang = ui->deeplTranslateTo->currentText(); + QJsonObject jsonData; + jsonData["text"] = QJsonArray::fromStringList({originalText}); + jsonData["source_lang"] = sourceLang; + jsonData["target_lang"] = targetLang; + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + connect(manager, &QNetworkAccessManager::finished, this, &MainWindow::translationRequestFinished); + QUrl url("https://api-free.deepl.com/v2/translate"); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "DeepL-Auth-Key " + deeplAuthKey.toUtf8()); + manager->post(request, QJsonDocument(jsonData).toJson()); +} + +void MainWindow::translationRequestFinished(QNetworkReply *reply) { + 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(); + ui->translationEdit->setText(translatedText); + } + } + } else { + qDebug() << "Translation request failed:" << reply->errorString(); + } + reply->deleteLater(); +} + +void MainWindow::countAndShowUntranslated() +{ + int untranslatedCount = 0; + std::function countUntranslated = [&](QTreeWidgetItem *parentItem) { + for (int i = 0; i < parentItem->childCount(); ++i) { + QTreeWidgetItem *childItem = parentItem->child(i); + if (!childItem->text(1).isEmpty() && childItem->text(2).isEmpty()) { + untranslatedCount++; + } + countUntranslated(childItem); + } + }; + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) { + QTreeWidgetItem *topLevelItem = ui->treeWidget->topLevelItem(i); + countUntranslated(topLevelItem); + } + ui->untranslatedLabel->setText(QString("%1").arg(untranslatedCount)); +} + diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..5c1991e --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,59 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class QTreeWidgetItem; +class QFile; +class QNetworkReply; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void on_selectProjectDirButton_clicked(); + void on_reloadButton_clicked(); + void on_languageCombo_currentTextChanged(const QString &selectedLanguage); + void on_reloadTranslationsButton_clicked(); + void on_reloadFilesButton_clicked(); + void on_nextUntranslatedButton_clicked(); + void on_treeWidget_itemSelectionChanged(); + void on_setTranslationButton_clicked(); + void on_setTranslationAndJumpNextButton_clicked(); + void on_setAndNextAndAutoTranslate_clicked(); + void on_translationEdit_returnPressed(); + void on_saveButton_clicked(); + void on_cleanupButton_clicked(); + void on_cleanupAndSaveButton_clicked(); + void onDeeplTranslationPossibilitiesLoaded(QNetworkReply *reply); + void on_deeplTranslateFrom_currentTextChanged(const QString &sourceLanguage); + void on_autoTranslateButton_clicked(); + +private: + Ui::MainWindow *ui; + std::map fileContentsMap; + std::map > translationsMap; + const QString deeplAuthKey = "5f6bc5cc-1e5d-4c69-9ef0-eb3cc2c1ece5:fx"; + void crawlProject(); + void populateTreeWidgetFromMap(); + void saveFiles(QStringList &failedFiles); + bool createBackup(const QString &filePath, const QString &backupFileName); + bool editAndSaveFile(QTreeWidgetItem *fileItem, const QString &backupFileName, const QString &fileName); + bool saveToFile(const QString &backupFileName, const QStringList &lines); + bool parseAndEditFile(QFile &backupFile, QTreeWidgetItem *fileItem, QStringList &lines, bool &changed); + QString findNewText(QTreeWidgetItem *fileItem, const QString &lineNumber, const QString &originalText); + void loadDeeplTranslationPossibilities(); + void renderDeeplSources(); + void translationRequestFinished(QNetworkReply *reply); + void countAndShowUntranslated(); +}; +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..a689b60 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,356 @@ + + + MainWindow + + + + 0 + 0 + 1260 + 719 + + + + Renpy Translation Helper + + + + + + 10 + 10 + 1233 + 681 + + + + + + + + + Project Directory + + + + + + + + 500 + 0 + + + + + + + + + 27 + 16777215 + + + + ... + + + + + + + Reload + + + + + + + Language + + + + + + + + 200 + 0 + + + + + + + + Reload + + + + + + + + + + 1231 + 400 + + + + + File + + + + + Original + + + + + Translation + + + + + All items + + + + + + + + + + + + + + + + Jump to next untranslated item + + + + + + + + 137 + 16777215 + + + + Deepl Translate from + + + + + + + + + + + 20 + 16777215 + + + + to + + + + + + + + + + Auto translate + + + + + + + + + + + Original Text: + + + + + + + false + + + + + + + + + + + Translation: + + + + + + + + + + + + + + Set translation + + + + + + + Set translation and jump to next untranslated + + + + + + + Set translation, jump to next untranslated and auto translate + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Enter action: + + + + + + + + 400 + 0 + + + + + Set translation, jump to next untranslated and auto translate + + + + + Set translation and jump to next untranslated + + + + + Set translation + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Untranslated: + + + + + + + 0 + + + + + + + + + + + Save translations + + + + + + + Delete backups and save translations + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Delete backups + + + + + + + + + + + + +