Renpy translation helper
This commit is contained in:
75
.gitignore
vendored
Normal file
75
.gitignore
vendored
Normal file
@@ -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
|
||||||
|
|
||||||
80
CMakeLists.txt
Normal file
80
CMakeLists.txt
Normal file
@@ -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()
|
||||||
3
RenpyTranslationHelper_en_GB.ts
Normal file
3
RenpyTranslationHelper_en_GB.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="en_GB"></TS>
|
||||||
23
main.cpp
Normal file
23
main.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QTranslator>
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
474
mainwindow.cpp
Normal file
474
mainwindow.cpp
Normal file
@@ -0,0 +1,474 @@
|
|||||||
|
#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));
|
||||||
|
}
|
||||||
|
|
||||||
59
mainwindow.h
Normal file
59
mainwindow.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef MAINWINDOW_H
|
||||||
|
#define MAINWINDOW_H
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
|
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<QString, QString> fileContentsMap;
|
||||||
|
std::map<QString, std::vector<QString> > 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
|
||||||
356
mainwindow.ui
Normal file
356
mainwindow.ui
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>1260</width>
|
||||||
|
<height>719</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Renpy Translation Helper</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<widget class="QWidget" name="">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>10</x>
|
||||||
|
<y>10</y>
|
||||||
|
<width>1233</width>
|
||||||
|
<height>681</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Project Directory</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="projectDir">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>500</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="selectProjectDirButton">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>27</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="reloadTranslationsButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Reload</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Language</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="languageCombo">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="reloadFilesButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Reload</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeWidget" name="treeWidget">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>1231</width>
|
||||||
|
<height>400</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>File</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Original</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Translation</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>All items</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="nextUntranslatedButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Jump to next untranslated item</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>137</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Deepl Translate from</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="deeplTranslateFrom"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>to</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="deeplTranslateTo"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="autoTranslateButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Auto translate</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Original Text:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="originalTextEdit">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Translation:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="translationEdit"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="setTranslationButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Set translation</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="setTranslationAndJumpNextButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Set translation and jump to next untranslated</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="setAndNextAndAutoTranslate">
|
||||||
|
<property name="text">
|
||||||
|
<string>Set translation, jump to next untranslated and auto translate</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enter action:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="enterActionCombo">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>400</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Set translation, jump to next untranslated and auto translate</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Set translation and jump to next untranslated</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Set translation</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>Untranslated:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="untranslatedLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>0</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="saveButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save translations</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="cleanupAndSaveButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Delete backups and save translations</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="cleanupButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Delete backups</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
Reference in New Issue
Block a user