Füge Unterstützung für die Massenübersetzung hinzu und verbessere die Platzhalterbehandlung
This commit is contained in:
3
.gitignore
vendored
Normal file → Executable file
3
.gitignore
vendored
Normal file → Executable file
@@ -73,3 +73,6 @@ CMakeLists.txt.user*
|
||||
*.exe
|
||||
CMakeLists.txt.usr
|
||||
|
||||
# CMake build directories
|
||||
build/
|
||||
cmake-build-*/
|
||||
7
CMakeLists.txt
Normal file → Executable file
7
CMakeLists.txt
Normal file → Executable file
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(RenpyTranslationHelper VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
@@ -9,6 +9,11 @@ set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# C++ ABI-Kompatibilität mit Qt6 erzwingen (alte ABI verwenden)
|
||||
# Qt6 wurde mit einer älteren GCC-Version kompiliert, die die alte ABI verwendet
|
||||
# Dies muss VOR find_package(QT) gesetzt werden
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_USE_CXX11_ABI=0")
|
||||
|
||||
#target_include_directories(RenpyTranslationHelper PRIVATE /usr/include/qt6)
|
||||
|
||||
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets Network)
|
||||
|
||||
0
RenpyTranslationHelper_en_GB.ts
Normal file → Executable file
0
RenpyTranslationHelper_en_GB.ts
Normal file → Executable file
345
mainwindow.cpp
Normal file → Executable file
345
mainwindow.cpp
Normal file → Executable file
@@ -18,6 +18,115 @@
|
||||
#include <QInputDialog>
|
||||
#include <QClipboard>
|
||||
#include <QTimer>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QScrollArea>
|
||||
#include <QSet>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace {
|
||||
|
||||
class OriginalWordReplacementEdit final : public QPlainTextEdit
|
||||
{
|
||||
public:
|
||||
explicit OriginalWordReplacementEdit(const QString &originalText, QWidget *parent = nullptr)
|
||||
: QPlainTextEdit(parent), m_originalText(originalText)
|
||||
{
|
||||
setMinimumHeight(72);
|
||||
setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
||||
}
|
||||
|
||||
protected:
|
||||
void contextMenuEvent(QContextMenuEvent *event) override
|
||||
{
|
||||
QTextCursor wordCursor = cursorForPosition(event->pos());
|
||||
wordCursor.select(QTextCursor::WordUnderCursor);
|
||||
if (!wordCursor.hasSelection()) {
|
||||
QPlainTextEdit::contextMenuEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const int wordStart = wordCursor.selectionStart();
|
||||
const int wordEnd = wordCursor.selectionEnd();
|
||||
QMenu *menu = createStandardContextMenu();
|
||||
QMenu *replacementMenu = menu->addMenu(tr("Durch Wort aus Original ersetzen"));
|
||||
QSet<QString> seenWords;
|
||||
const QRegularExpression wordExpression(QStringLiteral("[\\p{L}\\p{N}_'-]+"));
|
||||
QRegularExpressionMatchIterator matches = wordExpression.globalMatch(m_originalText);
|
||||
while (matches.hasNext()) {
|
||||
const QString originalWord = matches.next().captured(0);
|
||||
if (seenWords.contains(originalWord)) {
|
||||
continue;
|
||||
}
|
||||
seenWords.insert(originalWord);
|
||||
QAction *action = replacementMenu->addAction(originalWord);
|
||||
connect(action, &QAction::triggered, this, [this, wordStart, wordEnd, originalWord] {
|
||||
QTextCursor replacementCursor = textCursor();
|
||||
replacementCursor.setPosition(wordStart);
|
||||
replacementCursor.setPosition(wordEnd, QTextCursor::KeepAnchor);
|
||||
replacementCursor.insertText(originalWord);
|
||||
setTextCursor(replacementCursor);
|
||||
});
|
||||
}
|
||||
if (replacementMenu->isEmpty()) {
|
||||
replacementMenu->setEnabled(false);
|
||||
}
|
||||
menu->exec(event->globalPos());
|
||||
delete menu;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_originalText;
|
||||
};
|
||||
|
||||
QString protectBulkPlaceholders(const QString &input, QVector<QString> &placeholders)
|
||||
{
|
||||
placeholders.clear();
|
||||
const QRegularExpression expression(QStringLiteral("\\{[^}]+\\}"));
|
||||
QRegularExpressionMatchIterator matches = expression.globalMatch(input);
|
||||
if (!matches.hasNext()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
QString protectedText;
|
||||
int previousEnd = 0;
|
||||
while (matches.hasNext()) {
|
||||
const QRegularExpressionMatch match = matches.next();
|
||||
protectedText += input.mid(previousEnd, match.capturedStart() - previousEnd);
|
||||
placeholders.append(match.captured(0));
|
||||
protectedText += QStringLiteral("<x id=\"%1\"/>").arg(placeholders.size() - 1);
|
||||
previousEnd = match.capturedEnd();
|
||||
}
|
||||
return protectedText + input.mid(previousEnd);
|
||||
}
|
||||
|
||||
QString restoreBulkPlaceholders(const QString &input, const QVector<QString> &placeholders)
|
||||
{
|
||||
const QRegularExpression expression(QStringLiteral("<x\\s+id=\"(\\d+)\"\\s*/>"));
|
||||
QRegularExpressionMatchIterator matches = expression.globalMatch(input);
|
||||
if (!matches.hasNext()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
QString restoredText;
|
||||
int previousEnd = 0;
|
||||
while (matches.hasNext()) {
|
||||
const QRegularExpressionMatch match = matches.next();
|
||||
restoredText += input.mid(previousEnd, match.capturedStart() - previousEnd);
|
||||
bool validId = false;
|
||||
const int id = match.captured(1).toInt(&validId);
|
||||
restoredText += validId && id >= 0 && id < placeholders.size()
|
||||
? placeholders.at(id)
|
||||
: match.captured(0);
|
||||
previousEnd = match.capturedEnd();
|
||||
}
|
||||
return restoredText + input.mid(previousEnd);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
@@ -516,8 +625,6 @@ void MainWindow::on_deeplTranslateFrom_currentTextChanged(const QString &sourceL
|
||||
}
|
||||
}
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
void MainWindow::on_autoTranslateButton_clicked() {
|
||||
if (ui->deeplApiKey->text().isEmpty()) {
|
||||
QMessageBox::information(this, "Deepl error", "For auto translation, you need to add a deepl API key.");
|
||||
@@ -527,9 +634,15 @@ void MainWindow::on_autoTranslateButton_clicked() {
|
||||
QString sourceLang = ui->deeplTranslateFrom->currentText();
|
||||
QString targetLang = ui->deeplTranslateTo->currentText();
|
||||
QJsonObject jsonData;
|
||||
jsonData["text"] = QJsonArray::fromStringList({originalText});
|
||||
// Protect Mustache-/Platzhalter vor DeepL-Übersetzung
|
||||
QString protectedText = protectPlaceholdersForDeepl(originalText);
|
||||
jsonData["text"] = QJsonArray::fromStringList({protectedText});
|
||||
jsonData["source_lang"] = sourceLang;
|
||||
jsonData["target_lang"] = targetLang;
|
||||
jsonData["formality"] = "less";
|
||||
// Use XML tag handling so our <x id="N"/> placeholders are preserved
|
||||
jsonData["tag_handling"] = "xml";
|
||||
jsonData["tag_handling_version"] = "v2";
|
||||
QUrl url("https://api-free.deepl.com/v2/translate");
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
@@ -559,6 +672,176 @@ void MainWindow::on_autoTranslateButton_clicked() {
|
||||
manager->post(request, QJsonDocument(jsonData).toJson());
|
||||
}
|
||||
|
||||
void MainWindow::on_bulkAutoTranslateButton_clicked()
|
||||
{
|
||||
if (ui->deeplApiKey->text().isEmpty()) {
|
||||
QMessageBox::information(this, tr("DeepL error"), tr("Für die automatische Übersetzung wird ein DeepL-API-Key benötigt."));
|
||||
return;
|
||||
}
|
||||
if (ui->deeplTranslateFrom->currentText().isEmpty() || ui->deeplTranslateTo->currentText().isEmpty()) {
|
||||
QMessageBox::information(this, tr("DeepL error"), tr("Bitte wähle Quell- und Zielsprache aus."));
|
||||
return;
|
||||
}
|
||||
|
||||
bulkTranslations.clear();
|
||||
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(3).isEmpty()) {
|
||||
BulkTranslationItem translation{item, item->text(2), QString(), {}};
|
||||
bulkTranslations.append(std::move(translation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bulkTranslations.isEmpty()) {
|
||||
QMessageBox::information(this, tr("Information"), tr("Es gibt keine offenen Texte zum Übersetzen."));
|
||||
return;
|
||||
}
|
||||
|
||||
nextBulkTranslationIndex = 0;
|
||||
ui->bulkAutoTranslateButton->setEnabled(false);
|
||||
ui->statusbar->showMessage(tr("Übersetze %1 offene Texte …").arg(bulkTranslations.size()));
|
||||
requestNextBulkTranslationBatch();
|
||||
}
|
||||
|
||||
void MainWindow::requestNextBulkTranslationBatch()
|
||||
{
|
||||
if (nextBulkTranslationIndex >= bulkTranslations.size()) {
|
||||
ui->bulkAutoTranslateButton->setEnabled(true);
|
||||
ui->statusbar->clearMessage();
|
||||
showBulkTranslationDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int maximumTextsPerRequest = 50;
|
||||
constexpr int maximumCharactersPerRequest = 100000;
|
||||
const int firstItem = nextBulkTranslationIndex;
|
||||
QStringList texts;
|
||||
int characterCount = 0;
|
||||
while (nextBulkTranslationIndex < bulkTranslations.size() && texts.size() < maximumTextsPerRequest) {
|
||||
BulkTranslationItem &item = bulkTranslations[nextBulkTranslationIndex];
|
||||
const QString protectedText = protectBulkPlaceholders(item.originalText, item.placeholders);
|
||||
if (!texts.isEmpty() && characterCount + protectedText.size() > maximumCharactersPerRequest) {
|
||||
break;
|
||||
}
|
||||
texts.append(protectedText);
|
||||
characterCount += protectedText.size();
|
||||
++nextBulkTranslationIndex;
|
||||
}
|
||||
|
||||
QJsonObject payload;
|
||||
payload["text"] = QJsonArray::fromStringList(texts);
|
||||
payload["source_lang"] = ui->deeplTranslateFrom->currentText();
|
||||
payload["target_lang"] = ui->deeplTranslateTo->currentText();
|
||||
payload["formality"] = "less";
|
||||
payload["tag_handling"] = "xml";
|
||||
payload["tag_handling_version"] = "v2";
|
||||
|
||||
QNetworkRequest request(QUrl("https://api-free.deepl.com/v2/translate"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "DeepL-Auth-Key " + ui->deeplApiKey->text().toUtf8());
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||
connect(manager, &QNetworkAccessManager::finished, this,
|
||||
[this, manager, firstItem, itemCount = texts.size()](QNetworkReply *reply) {
|
||||
handleBulkTranslationReply(reply, firstItem, itemCount);
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
});
|
||||
manager->post(request, QJsonDocument(payload).toJson());
|
||||
}
|
||||
|
||||
void MainWindow::handleBulkTranslationReply(QNetworkReply *reply, int firstItem, int itemCount)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
ui->bulkAutoTranslateButton->setEnabled(true);
|
||||
ui->statusbar->clearMessage();
|
||||
QMessageBox::critical(this, tr("DeepL error"), tr("Die Sammelübersetzung ist fehlgeschlagen: %1").arg(reply->errorString()));
|
||||
bulkTranslations.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonDocument response = QJsonDocument::fromJson(reply->readAll());
|
||||
const QJsonArray translations = response.object().value("translations").toArray();
|
||||
if (translations.size() != itemCount) {
|
||||
ui->bulkAutoTranslateButton->setEnabled(true);
|
||||
ui->statusbar->clearMessage();
|
||||
QMessageBox::critical(this, tr("DeepL error"), tr("DeepL hat eine unvollständige Antwort für die Sammelübersetzung geliefert."));
|
||||
bulkTranslations.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int index = 0; index < translations.size(); ++index) {
|
||||
QString translatedText = translations.at(index).toObject().value("text").toString();
|
||||
BulkTranslationItem &item = bulkTranslations[firstItem + index];
|
||||
item.translatedText = restoreBulkPlaceholders(translatedText, item.placeholders);
|
||||
item.translatedText.replace('"', '\'');
|
||||
}
|
||||
ui->statusbar->showMessage(tr("Übersetzt: %1 von %2 …").arg(nextBulkTranslationIndex).arg(bulkTranslations.size()));
|
||||
requestNextBulkTranslationBatch();
|
||||
}
|
||||
|
||||
void MainWindow::showBulkTranslationDialog()
|
||||
{
|
||||
QDialog dialog(this);
|
||||
dialog.setWindowTitle(tr("Automatische Übersetzungen prüfen"));
|
||||
dialog.resize(900, 700);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(&dialog);
|
||||
QLabel *description = new QLabel(tr("Prüfe und bearbeite die Vorschläge vor dem Übernehmen. Mit Rechtsklick auf ein Wort in einer Übersetzung kannst du es durch ein Wort aus dem Original ersetzen."), &dialog);
|
||||
description->setWordWrap(true);
|
||||
layout->addWidget(description);
|
||||
|
||||
QScrollArea *scrollArea = new QScrollArea(&dialog);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
QWidget *content = new QWidget(scrollArea);
|
||||
QVBoxLayout *contentLayout = new QVBoxLayout(content);
|
||||
QVector<OriginalWordReplacementEdit *> edits;
|
||||
edits.reserve(bulkTranslations.size());
|
||||
for (const BulkTranslationItem &item : std::as_const(bulkTranslations)) {
|
||||
QLabel *originalLabel = new QLabel(item.originalText, content);
|
||||
originalLabel->setWordWrap(true);
|
||||
originalLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
originalLabel->setStyleSheet(QStringLiteral("font-weight: 600; padding-top: 10px;"));
|
||||
contentLayout->addWidget(originalLabel);
|
||||
|
||||
auto *translationEdit = new OriginalWordReplacementEdit(item.originalText, content);
|
||||
translationEdit->setPlainText(item.translatedText);
|
||||
translationEdit->setPlaceholderText(tr("Übersetzung"));
|
||||
contentLayout->addWidget(translationEdit);
|
||||
edits.append(translationEdit);
|
||||
}
|
||||
contentLayout->addStretch();
|
||||
scrollArea->setWidget(content);
|
||||
layout->addWidget(scrollArea);
|
||||
|
||||
auto applyTranslations = [this, &edits] {
|
||||
for (int index = 0; index < bulkTranslations.size(); ++index) {
|
||||
QTreeWidgetItem *item = bulkTranslations[index].item;
|
||||
item->setText(3, edits[index]->toPlainText());
|
||||
if (QTreeWidgetItem *parent = item->parent()) {
|
||||
parent->setData(0, Qt::UserRole, true);
|
||||
}
|
||||
}
|
||||
countAndShowUntranslated();
|
||||
};
|
||||
|
||||
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Apply | QDialogButtonBox::Cancel, &dialog);
|
||||
buttons->button(QDialogButtonBox::Save)->setText(tr("Zwischenspeichern"));
|
||||
buttons->button(QDialogButtonBox::Apply)->setText(tr("Übernehmen und schließen"));
|
||||
connect(buttons->button(QDialogButtonBox::Save), &QPushButton::clicked, &dialog, applyTranslations);
|
||||
connect(buttons->button(QDialogButtonBox::Apply), &QPushButton::clicked, &dialog, [applyTranslations, &dialog] {
|
||||
applyTranslations();
|
||||
dialog.accept();
|
||||
});
|
||||
connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
dialog.exec();
|
||||
bulkTranslations.clear();
|
||||
}
|
||||
|
||||
void MainWindow::translationRequestFinished(QNetworkReply *reply) {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray responseData = reply->readAll();
|
||||
@@ -569,8 +852,10 @@ void MainWindow::translationRequestFinished(QNetworkReply *reply) {
|
||||
if (!translationsArray.isEmpty()) {
|
||||
QJsonObject translationObject = translationsArray[0].toObject();
|
||||
QString translatedText = translationObject["text"].toString();
|
||||
translatedText.replace("\"", "'");
|
||||
ui->translationEdit->setText(translatedText);
|
||||
// Restore any placeholders that were protected before the request
|
||||
QString restored = restorePlaceholdersFromDeepl(translatedText);
|
||||
restored.replace("\"", "'");
|
||||
ui->translationEdit->setText(restored);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -579,6 +864,55 @@ void MainWindow::translationRequestFinished(QNetworkReply *reply) {
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
QString MainWindow::protectPlaceholdersForDeepl(const QString &input) {
|
||||
deeplPlaceholderMap.clear();
|
||||
QRegularExpression rx(R"(\{[^}]+\})");
|
||||
QRegularExpressionMatchIterator it = rx.globalMatch(input);
|
||||
if (!it.hasNext()) return input;
|
||||
QString result;
|
||||
int lastPos = 0;
|
||||
int idx = 0;
|
||||
while (it.hasNext()) {
|
||||
QRegularExpressionMatch m = it.next();
|
||||
int start = m.capturedStart();
|
||||
int len = m.capturedLength();
|
||||
result += input.mid(lastPos, start - lastPos);
|
||||
deeplPlaceholderMap.append(m.captured(0));
|
||||
result += QString("<x id=\"%1\"/>").arg(idx);
|
||||
idx++;
|
||||
lastPos = start + len;
|
||||
}
|
||||
result += input.mid(lastPos);
|
||||
return result;
|
||||
}
|
||||
|
||||
QString MainWindow::restorePlaceholdersFromDeepl(const QString &input) {
|
||||
if (deeplPlaceholderMap.isEmpty()) return input;
|
||||
QRegularExpression rx(R"(<x\s+id=\"(\d+)\"\s*/>)");
|
||||
QRegularExpressionMatchIterator it = rx.globalMatch(input);
|
||||
if (!it.hasNext()) return input;
|
||||
QString result;
|
||||
int lastPos = 0;
|
||||
while (it.hasNext()) {
|
||||
QRegularExpressionMatch m = it.next();
|
||||
int start = m.capturedStart();
|
||||
int len = m.capturedLength();
|
||||
result += input.mid(lastPos, start - lastPos);
|
||||
bool ok;
|
||||
int id = m.captured(1).toInt(&ok);
|
||||
if (ok && id >= 0 && id < deeplPlaceholderMap.size()) {
|
||||
result += deeplPlaceholderMap[id];
|
||||
} else {
|
||||
result += m.captured(0); // fallback: leave tag as-is
|
||||
}
|
||||
lastPos = start + len;
|
||||
}
|
||||
result += input.mid(lastPos);
|
||||
// clear mapping after restore to avoid stale values
|
||||
deeplPlaceholderMap.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
void MainWindow::countAndShowUntranslated() {
|
||||
int untranslatedCount = 0;
|
||||
std::function<int(QTreeWidgetItem *)> countUntranslated = [&](QTreeWidgetItem *parentItem) {
|
||||
@@ -663,4 +997,3 @@ void MainWindow::on_deeplApiKey_editingFinished()
|
||||
void MainWindow::on_copyButton_clicked() {
|
||||
QGuiApplication::clipboard()->setText(ui->originalTextEdit->text());
|
||||
}
|
||||
|
||||
|
||||
16
mainwindow.h
Normal file → Executable file
16
mainwindow.h
Normal file → Executable file
@@ -41,6 +41,7 @@ private slots:
|
||||
void onDeeplTranslationPossibilitiesLoaded(QNetworkReply *reply);
|
||||
void on_deeplTranslateFrom_currentTextChanged(const QString &sourceLanguage);
|
||||
void on_autoTranslateButton_clicked();
|
||||
void on_bulkAutoTranslateButton_clicked();
|
||||
void on_searchButton_clicked();
|
||||
void on_searchNextButton_clicked();
|
||||
void on_deeplApiKey_editingFinished();
|
||||
@@ -58,6 +59,12 @@ private:
|
||||
QString oldText;
|
||||
QString newText;
|
||||
};
|
||||
struct BulkTranslationItem {
|
||||
QTreeWidgetItem *item;
|
||||
QString originalText;
|
||||
QString translatedText;
|
||||
QVector<QString> placeholders;
|
||||
};
|
||||
std::unique_ptr<QNetworkAccessManager> networkManager;
|
||||
Ui::MainWindow *ui;
|
||||
QJsonObject configuration;
|
||||
@@ -76,10 +83,19 @@ private:
|
||||
void loadDeeplTranslationPossibilities();
|
||||
void renderDeeplSources();
|
||||
void translationRequestFinished(QNetworkReply *reply);
|
||||
void requestNextBulkTranslationBatch();
|
||||
void handleBulkTranslationReply(QNetworkReply *reply, int firstItem, int itemCount);
|
||||
void showBulkTranslationDialog();
|
||||
void onDeeplTranslationAuthenticationError(QNetworkReply *reply, QAuthenticator *authenticator);
|
||||
void countAndShowUntranslated();
|
||||
void searchNext();
|
||||
QVector<TranslationItem> parseTextBlock(const QString &block);
|
||||
void setConfigValue(const QString &key, const QString &value);
|
||||
// DeepL placeholder protection: maps tag id -> original placeholder text
|
||||
QVector<QString> deeplPlaceholderMap;
|
||||
QString protectPlaceholdersForDeepl(const QString &input);
|
||||
QString restorePlaceholdersFromDeepl(const QString &input);
|
||||
QVector<BulkTranslationItem> bulkTranslations;
|
||||
int nextBulkTranslationIndex{0};
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
10
mainwindow.ui
Normal file → Executable file
10
mainwindow.ui
Normal file → Executable file
@@ -198,6 +198,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="bulkAutoTranslateButton">
|
||||
<property name="toolTip">
|
||||
<string>Translate all untranslated texts and review the suggestions before applying them.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Translate all open texts</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
Reference in New Issue
Block a user