Files
renpy-translator/mainwindow.cpp
2024-02-26 18:23:30 +01:00

475 lines
17 KiB
C++

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <QDateTime>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
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 <key> 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<void(QTreeWidgetItem *)> 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));
}