Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
machinetranslationdialog.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
5#include "ui_machinetranslationdialog.h"
6
7#include "messagemodel.h"
8#include "auto-translation/machinetranslator.h"
9#include "auto-translation/translationsettings.h"
10#include "globals.h"
11
12#include <QtCore/qsettings.h>
13#include <QtWidgets/qmessagebox.h>
14
15#include <array>
16
17using namespace Qt::Literals::StringLiterals;
18
19namespace {
20
21struct ApiTypeInfo
22{
24 const QLatin1String displayName;
25 const QLatin1String defaultUrl;
26 bool showApiKeyField;
27};
28
29// Central registry of all supported API types and their metadata
30constexpr ApiTypeInfo s_apiTypes[] = {
31 { TranslationApiType::Ollama, "Ollama"_L1, "http://localhost:11434"_L1, false },
32 { TranslationApiType::OpenAICompatible, "OpenAI Compatible"_L1, "http://localhost:8080"_L1,
33 true },
34};
35
36const ApiTypeInfo *findApiTypeInfo(TranslationApiType type)
37{
38 for (const auto &info : s_apiTypes) {
39 if (info.type == type)
40 return &info;
41 }
42 return nullptr;
43}
44
45} // namespace
46
47QT_BEGIN_NAMESPACE
48
49static constexpr std::array<const char *, 3> toolBoxTexts {
50 QT_TRANSLATE_NOOP("MachineTranslationDialog", "Configuration"),
51 QT_TRANSLATE_NOOP("MachineTranslationDialog", "Selection"),
52 QT_TRANSLATE_NOOP("MachineTranslationDialog", "Progress")
53};
54
55MachineTranslationDialog::MachineTranslationDialog(QWidget *parent)
56 : QDialog(parent),
57 m_ui(std::make_unique<Ui::MachineTranslationDialog>()),
58 m_translator(std::make_unique<MachineTranslator>())
59{
60 m_ui->setupUi(this);
61
62 updateToolBoxTexts();
63 connect(m_ui->toolBox, &QToolBox::currentChanged, this, &MachineTranslationDialog::updateToolBoxTexts);
64
65 m_ui->statusLabel->setWordWrap(true);
66 m_ui->statusLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
67 connect(m_ui->translateButton, &QPushButton::clicked, this,
68 &MachineTranslationDialog::translateSelection);
69 connect(m_ui->filesComboBox, &QComboBox::currentIndexChanged, this, [this] {
70 m_ui->filterComboBox->setCurrentIndex(0);
71 updateStatus();
72 });
73 connect(m_ui->groupListWidget, &QListWidget::itemSelectionChanged, this,
74 [this] { updateStatus(); });
75 connect(m_translator.get(), &MachineTranslator::batchTranslated, this,
76 &MachineTranslationDialog::onBatchTranslated);
77 connect(m_translator.get(), &MachineTranslator::translationFailed, this,
78 &MachineTranslationDialog::onTranslationFailed);
79 connect(m_ui->doneButton, &QPushButton::clicked, this, [this] {
80 if (discardTranslations())
81 accept();
82 });
83 connect(m_ui->cancelButton, &QPushButton::clicked, this, [this] {
84 if (discardTranslations())
85 reject();
86 });
87 connect(m_ui->applyButton, &QPushButton::clicked, this,
88 &MachineTranslationDialog::applyTranslations);
89
90 connect(m_ui->stopButton, &QPushButton::clicked, this, &MachineTranslationDialog::stop);
91 connect(m_ui->connectButton, &QPushButton::clicked, this,
92 &MachineTranslationDialog::connectToServer);
93 connect(m_translator.get(), &MachineTranslator::modelsReceived, this,
94 &MachineTranslationDialog::onModelsReceived);
95 connect(m_ui->modelComboBox, &QComboBox::currentTextChanged, this, [](const QString &text) {
96 if (!text.isEmpty())
97 QSettings().setValue(settingPath(selectedModelSettingsKey), text);
98 });
99 connect(this, &QDialog::finished, m_translator.get(), &MachineTranslator::stop);
100 connect(m_ui->filterComboBox, &QComboBox::currentIndexChanged, this,
101 &MachineTranslationDialog::onFilterChanged);
102 connect(m_ui->serverText, &QLineEdit::textChanged, this,
103 &MachineTranslationDialog::onServerUrlChanged);
104 connect(m_ui->apiKeyEdit, &QLineEdit::textChanged, this, [this] {
105 if (m_connectionState == ConnectionState::Connected)
106 setConnectionState(ConnectionState::Modified);
107 });
108 connect(m_ui->apiTypeComboBox, &QComboBox::currentIndexChanged, this,
109 &MachineTranslationDialog::onApiTypeChanged);
110 connect(m_ui->logButton, &QPushButton::clicked, this, [this] {
111 if (m_ui->logButton->isChecked())
112 connect(m_translator.get(), &MachineTranslator::debugLog, this,
113 &MachineTranslationDialog::onNewDebugMessage);
114 else
115 disconnect(m_translator.get(), &MachineTranslator::debugLog, this,
116 &MachineTranslationDialog::onNewDebugMessage);
117 });
118 connect(m_ui->nextButton, &QPushButton::clicked, this,
119 [this] { m_ui->toolBox->setCurrentIndex(1); });
120
121 setConnectionState(ConnectionState::NotConnected);
122
123 // Populate API type combo box from registry
124 m_ui->apiTypeComboBox->clear();
125 for (const auto &info : s_apiTypes) {
126 m_ui->apiTypeComboBox->addItem(QString::fromLatin1(info.displayName),
127 QVariant::fromValue(info.type));
128 }
129
130 // Restore saved API type selection
131 QSettings config;
132 const int savedApiType = config.value(settingPath(selectedApiTypeSettingsKey),
133 static_cast<int>(TranslationApiType::Ollama))
134 .toInt();
135 for (int i = 0; i < m_ui->apiTypeComboBox->count(); ++i) {
136 if (m_ui->apiTypeComboBox->itemData(i).toInt() == savedApiType) {
137 m_ui->apiTypeComboBox->setCurrentIndex(i);
138 break;
139 }
140 }
141
142 // Advanced settings
143 loadAdvancedSettings();
144 validateAdvancedSettings();
145
146 connect(m_ui->advancedSettingsToggle, &QPushButton::toggled, this,
147 &MachineTranslationDialog::toggleAdvancedSettings);
148 connect(m_ui->applySettingsButton, &QPushButton::clicked, this,
149 &MachineTranslationDialog::saveAdvancedSettings);
150 connect(m_ui->resetSettingsButton, &QPushButton::clicked, this,
151 &MachineTranslationDialog::resetAdvancedSettings);
152
153 // Validate when relevant settings change
154 connect(m_ui->maxRetriesSpinBox, &QSpinBox::valueChanged, this,
155 &MachineTranslationDialog::validateAdvancedSettings);
156 connect(m_ui->maxJsonFormatTriesSpinBox, &QSpinBox::valueChanged, this,
157 &MachineTranslationDialog::validateAdvancedSettings);
158
159 // Collapse advanced settings when leaving Configuration tab or closing dialog
160 connect(m_ui->toolBox, &QToolBox::currentChanged, this,
161 [this](int) { m_ui->advancedSettingsToggle->setChecked(false); });
162 connect(this, &QDialog::finished, this,
163 [this] { m_ui->advancedSettingsToggle->setChecked(false); });
164}
165
166void MachineTranslationDialog::updateToolBoxTexts()
167{
168 const int count = m_ui->toolBox->count();
169 Q_ASSERT(unsigned(count) == toolBoxTexts.size());
170 const int index = m_ui->toolBox->currentIndex();
171
172 for (int i = 0; i < count; ++i) {
173 const QString baseText = MachineTranslationDialog::tr(toolBoxTexts[i]);
174 m_ui->toolBox->setItemText(i, (i == index ? "- "_L1 : "+ "_L1) + baseText);
175 }
176}
177
179{
180 m_dataModel = dm;
181 refresh(true);
182}
183
184void MachineTranslationDialog::refresh(bool init)
185{
186 if (init) {
187 m_ui->toolBox->setCurrentIndex(0);
188 m_ui->filesComboBox->clear();
189 m_ui->filesComboBox->addItems(m_dataModel->srcFileNames());
190 m_ui->filesComboBox->setCurrentIndex(0);
191 m_ui->translationLog->setText(tr("Translation Log"));
192 m_ui->translateButton->setEnabled(true);
193 m_ui->stopButton->setEnabled(false);
194 connectToServer();
195 }
196 m_sentTexts = 0;
197 m_failedTranslations = 0;
198 m_receivedTranslations.clear();
199 m_ongoingTranslations.clear();
200 m_ui->applyButton->setEnabled(false);
201 m_ui->progressBar->setVisible(false);
202 m_translator->start();
203}
204
205void MachineTranslationDialog::logProgress(const QList<QStringList> &table)
206{
207 const qsizetype receivedCount = m_receivedTranslations.size();
208 m_ui->statusLabel->setText(
209 tr("Translation status: %1/%2 source texts translated, %3/%2 failed.")
210 .arg(receivedCount)
211 .arg(m_sentTexts)
212 .arg(m_failedTranslations));
213 const int progress =
214 m_sentTexts > 0 ? (receivedCount + m_failedTranslations) * 100 / m_sentTexts : 0;
215 m_ui->progressBar->setValue(progress);
216 if (!table.empty()) {
217 QString html = "<hr/><table cellpadding=\"4\""
218 "style=\""
219 "width:100%; "
220 "margin-left:10px; "
221 "\">"_L1;
222 for (const QStringList &row : table) {
223 html += "<tr>"_L1;
224 for (const QString &col : row)
225 html += "<td>%1</td>"_L1.arg(col);
226 html += "</tr>"_L1;
227 }
228 html += "</table>"_L1;
229 m_ui->translationLog->append(html);
230 }
231
232 if (receivedCount + m_failedTranslations == m_sentTexts) {
233 m_ui->translationLog->append(
234 tr("<hr/><b>Translation completed: %1/%2 translated, %3/%2 failed.</b>")
235 .arg(receivedCount)
236 .arg(m_sentTexts)
237 .arg(m_failedTranslations));
238 m_ui->translateButton->setEnabled(true);
239 m_ui->stopButton->setEnabled(false);
240 m_ui->applyButton->setEnabled(true);
241 m_ui->progressBar->setVisible(false);
242 } else {
243 m_ui->translateButton->setEnabled(false);
244 m_ui->stopButton->setEnabled(true);
245 m_ui->progressBar->setVisible(true);
246 }
247}
248
249void MachineTranslationDialog::logInfo(const QString &info)
250{
251 m_ui->translationLog->append("<hr/>"_L1);
252 m_ui->translationLog->append(info);
253}
254
255void MachineTranslationDialog::logWarning(const QString &warning)
256{
257 m_ui->translationLog->append(
258 "<span style=\"color:orange;\">%1</span>"_L1.arg(warning.toHtmlEscaped()));
259}
260
261void MachineTranslationDialog::logError(const QString &error)
262{
263 m_ui->translationLog->append("<hr/>"_L1);
264 m_ui->translationLog->append(
265 "<span style=\"color:red; font-weight: bold; \">%1</span>"_L1.arg(error));
266}
267
268bool MachineTranslationDialog::discardTranslations()
269{
270 return (m_receivedTranslations.empty()
271 || QMessageBox::warning(
272 this, tr("Qt Linguist"),
273 tr("%n translated item(s) will be discarded. Continue?", 0,
274 m_receivedTranslations.size()),
275 QMessageBox::Yes | QMessageBox::No)
276 == QMessageBox::Yes);
277}
278
279void MachineTranslationDialog::stop()
280{
281 m_translator->stop();
282 m_ui->stopButton->setEnabled(false);
283 m_ui->translateButton->setEnabled(true);
284 m_sentTexts = 0;
285 m_failedTranslations = 0;
286 m_ongoingTranslations.clear();
287 m_translator->start();
288 m_ui->applyButton->setEnabled(!m_receivedTranslations.empty());
289 m_ui->progressBar->setVisible(false);
290 logError(tr("Translation Stopped."));
291}
292
293void MachineTranslationDialog::translateSelection()
294{
295 m_ui->toolBox->setCurrentIndex(2);
296 const QString model = m_ui->modelComboBox->currentText();
297 const int id = m_ui->filesComboBox->currentIndex();
298 if (model.isEmpty()) {
299 logError(tr("Please verify the service URL is valid "
300 "and a translation model is selected."));
301 return;
302 }
303 if (id < 0) {
304 logError(tr("Please select a file for translation."));
305 return;
306 }
307 if (!discardTranslations())
308 return;
309 refresh(false);
310
311 const int filter = m_ui->filterComboBox->currentIndex();
312 const DataModel *dm = m_dataModel->model(id);
313 Messages messages;
314 if (filter == 0) {
315 QMutexLocker lock(&m_mutex);
318 if (tm->translation().isEmpty()) {
319 messages.items.append(tm);
320 m_ongoingTranslations[tm] =
321 MultiDataIndex{ it.translationType(), id, it.group(), it.message() };
322 }
323 }
324 for (DataModelIterator it(IDBASED, dm); it.isValid(); ++it) {
326 if (tm->translation().isEmpty()) {
327 messages.items.append(tm);
328 m_ongoingTranslations[tm] =
329 MultiDataIndex{ it.translationType(), id, it.group(), it.message() };
330 }
331 }
332 } else {
333 const QList<QListWidgetItem *> selectedItems = m_ui->groupListWidget->selectedItems();
334
335 if (selectedItems.isEmpty()) {
336 logError(tr("Please select at least one context/label to translate."));
337 return;
338 }
339
340 QMutexLocker lock(&m_mutex);
341 const auto type = (filter == 1) ? TEXTBASED : IDBASED;
342 for (QListWidgetItem *item : selectedItems) {
343 const int groupIdx = item->data(Qt::UserRole).toInt();
344 const GroupItem *g = dm->groupItem(groupIdx, type);
345 for (int i = 0; i < g->messageCount(); i++) {
346 const TranslatorMessage *tm = &g->messageItem(i)->message();
347 if (tm->translation().isEmpty()) {
348 messages.items.append(tm);
349 m_ongoingTranslations[tm] = MultiDataIndex{ type, id, groupIdx, i };
350 }
351 }
352 }
353 }
354
355 if (messages.items.isEmpty()) {
356 logInfo(tr("No items to translate. All selected messages already have translations."));
357 return;
358 }
359
360 messages.srcLang = QLocale::languageToString(dm->sourceLanguage());
361 messages.tgtLang = QLocale::languageToString(dm->language());
362 messages.pluralFormsCount = dm->numerusForms().size();
363 m_sentTexts += messages.items.size();
364 m_translator->activateTranslationModel(model);
365 m_translator->translate(messages, m_ui->contextEdit->toPlainText().trimmed());
366 logInfo(tr("Translation Started"));
367 logProgress({});
368}
369
370void MachineTranslationDialog::onBatchTranslated(
371 QHash<const TranslatorMessage *, QStringList> translations)
372{
373 QList<QStringList> log;
374 QList<QString> warnings;
375 log.reserve(translations.size());
376 QMutexLocker lock(&m_mutex);
377
378 const int id = m_ui->filesComboBox->currentIndex();
379 const int expectedForms = m_dataModel->model(id)->numerusForms().size();
380
381 for (const auto &[msg, translationList] : translations.asKeyValueRange()) {
382 const QString displayTranslation = translationList.size() == 1
383 ? translationList.first().simplified()
384 : translationList.join(" | "_L1);
385 log.append({ msg->sourceText().simplified(), displayTranslation });
386 if (msg->isPlural() && translationList.size() != expectedForms)
387 warnings.append(tr("Plural count expected %1, got %2 for \"%3\".")
388 .arg(expectedForms)
389 .arg(translationList.size())
390 .arg(msg->sourceText()));
391 m_receivedTranslations.append(
392 std::make_pair(m_ongoingTranslations.take(msg), translationList));
393 }
394 logInfo(tr("Translation Batch:"));
395 logProgress(log);
396 for (const QString &warning : std::as_const(warnings))
397 logWarning(warning);
398}
399
400void MachineTranslationDialog::onNewDebugMessage(const QByteArray &message, bool fromLlm)
401{
402 const QString color = isDarkMode() ? "yellow"_L1 : "orange"_L1;
403 const QString from = fromLlm ? "LLM:"_L1 : "Qt Linguist:"_L1;
404 const QString log =
405 "<p style=\"color:red; font-weight:bold; margin:0;\">%1</p>"
406 "<p style=\"color:%2; font-weight:normal; font-size:small; margin:0;\">%3</p>"
407 "<hr/>"_L1.arg(from, color, QString::fromUtf8(message).toHtmlEscaped());
408 m_ui->translationLog->append(log);
409}
410
411void MachineTranslationDialog::onFilterChanged(int id)
412{
413 m_ui->groupLabel->setEnabled(id != 0);
414 m_ui->groupListWidget->setEnabled(id != 0);
415 m_ui->groupListWidget->clear();
416 int modelId = m_ui->filesComboBox->currentIndex();
417 if (modelId < 0)
418 return;
419
420 QList<QPair<QString, int>> groupsWithIndices;
421 if (id == 1) {
422 for (int i = 0; i < m_dataModel->model(modelId)->contextCount(); i++)
423 groupsWithIndices.append(
424 { m_dataModel->model(modelId)->groupItem(i, TEXTBASED)->group(), i });
425 } else if (id == 2) {
426 for (int i = 0; i < m_dataModel->model(modelId)->labelCount(); i++)
427 groupsWithIndices.append(
428 { m_dataModel->model(modelId)->groupItem(i, IDBASED)->group(), i });
429 }
430
431 std::sort(groupsWithIndices.begin(), groupsWithIndices.end(),
432 [](const QPair<QString, int> &a, const QPair<QString, int> &b) {
433 return a.first.compare(b.first, Qt::CaseInsensitive) < 0;
434 });
435
436 for (const auto &group : groupsWithIndices) {
437 QListWidgetItem *item = new QListWidgetItem(group.first);
438 item->setData(Qt::UserRole, group.second);
439 m_ui->groupListWidget->addItem(item);
440 }
441}
442
443void MachineTranslationDialog::applyTranslations()
444{
445 QMutexLocker lock(&m_mutex);
446 for (const auto &[item, translations] : std::as_const(m_receivedTranslations))
447 m_dataModel->setTranslations(item, translations);
448 refresh(false);
449 logInfo(tr("Translations Applied."));
450 updateStatus();
451}
452
453void MachineTranslationDialog::onTranslationFailed(QList<const TranslatorMessage *> failed)
454{
455 QList<QStringList> log;
456 log.reserve(failed.size() + 1);
457
458 QMutexLocker lock(&m_mutex);
459 m_failedTranslations += failed.size();
460 for (const TranslatorMessage *m : std::as_const(failed)) {
461 log << QStringList{ m->sourceText().simplified() };
462 m_ongoingTranslations.remove(m);
463 }
464 logError(tr("Failed Translation(s):"));
465 logProgress(log);
466}
467
468void MachineTranslationDialog::updateStatus()
469{
470 const int model = m_ui->filesComboBox->currentIndex();
471 const int filter = m_ui->filterComboBox->currentIndex();
472
473 QList<QListWidgetItem *> selectedItems;
474 if (filter > 0)
475 selectedItems = m_ui->groupListWidget->selectedItems();
476
477 if (model < 0 || filter < 0 || (filter > 0 && selectedItems.isEmpty())) {
478 //: No selected items
479 m_ui->selectionLabel->setText(tr("Selection status: -"));
480 } else if (filter == 0) {
481 int count = 0;
482 for (DataModelIterator it(IDBASED, m_dataModel->model(model)); it.isValid(); ++it)
483 if (it.current()->translation().isEmpty())
484 count++;
485 for (DataModelIterator it(TEXTBASED, m_dataModel->model(model)); it.isValid(); ++it)
486 if (it.current()->translation().isEmpty())
487 count++;
488
489 m_ui->selectionLabel->setText(tr("Selected %n item(s).", 0, count));
490 } else if (!selectedItems.isEmpty()) {
491 const auto type = (filter == 1) ? TEXTBASED : IDBASED;
492 int count = 0;
493 for (QListWidgetItem *item : std::as_const(selectedItems)) {
494 const int groupIdx = item->data(Qt::UserRole).toInt();
495 const GroupItem *g = m_dataModel->model(model)->groupItem(groupIdx, type);
496 for (int i = 0; i < g->messageCount(); i++)
497 if (g->messageItem(i)->message().translation().isEmpty())
498 count++;
499 }
500 m_ui->selectionLabel->setText(
501 tr("Selected %n item(s) in %1 group(s).", 0, count).arg(selectedItems.size()));
502 }
503}
504
505void MachineTranslationDialog::connectToServer()
506{
507 if (m_ui->serverText->text().isEmpty()) {
508 setConnectionState(ConnectionState::NotConnected);
509 return;
510 }
511 setConnectionState(ConnectionState::Connecting);
512 m_translator->setUrl(m_ui->serverText->text());
513 m_translator->setApiKey(m_ui->apiKeyEdit->text());
514 m_translator->requestModels();
515}
516
517void MachineTranslationDialog::onApiTypeChanged(int index)
518{
519 const QVariant data = m_ui->apiTypeComboBox->itemData(index);
520 if (!data.isValid())
521 return;
522
523 const auto apiType = static_cast<TranslationApiType>(data.toInt());
524 const ApiTypeInfo *info = findApiTypeInfo(apiType);
525 if (!info)
526 return;
527
528 QSettings config;
529 config.setValue(settingPath(selectedApiTypeSettingsKey), static_cast<int>(apiType));
530
531 m_translator->setApiType(apiType);
532 m_ui->serverText->setText(QString::fromLatin1(info->defaultUrl));
533 m_ui->modelComboBox->clear();
534
535 // Show/hide API key field based on API type
536 m_ui->apiKeyLabel->setVisible(info->showApiKeyField);
537 m_ui->apiKeyEdit->setVisible(info->showApiKeyField);
538
539 connectToServer();
540}
541
542void MachineTranslationDialog::onServerUrlChanged()
543{
544 if (m_connectionState == ConnectionState::Connected
545 && m_ui->serverText->text() != m_lastConnectedUrl) {
546 setConnectionState(ConnectionState::Modified);
547 }
548}
549
550void MachineTranslationDialog::onModelsReceived(const QStringList &models)
551{
552 if (models.isEmpty()) {
553 setConnectionState(ConnectionState::Failed);
554 } else {
555 m_lastConnectedUrl = m_ui->serverText->text();
556 setConnectionState(ConnectionState::Connected);
557
558 QSettings config;
559 QString savedModel = config.value(settingPath(selectedModelSettingsKey)).toString();
560 m_ui->modelComboBox->clear();
561 m_ui->modelComboBox->addItems(models);
562
563 // Restore saved selection if found
564 if (!savedModel.isEmpty()) {
565 int index = m_ui->modelComboBox->findText(savedModel);
566 if (index >= 0)
567 m_ui->modelComboBox->setCurrentIndex(index);
568 }
569 }
570}
571
572void MachineTranslationDialog::setConnectionState(ConnectionState state)
573{
574 m_connectionState = state;
575 updateConnectionIndicator();
576}
577
578void MachineTranslationDialog::updateConnectionIndicator()
579{
580 QString statusText;
581 QString styleSheet;
582
583 switch (m_connectionState) {
584 case ConnectionState::NotConnected:
585 statusText = tr("Not connected - click \"Connect\" to fetch models");
586 styleSheet = "QLabel { color: gray; }"_L1;
587 break;
588 case ConnectionState::Connecting:
589 statusText = tr("Connecting...");
590 styleSheet = "QLabel { color: orange; }"_L1;
591 break;
592 case ConnectionState::Connected:
593 statusText = tr("Connected");
594 styleSheet = "QLabel { color: green;}"_L1;
595 break;
596 case ConnectionState::Failed:
597 statusText = tr("Connection failed - verify server URL and click \"Connect\"");
598 styleSheet = "QLabel { color: red; }"_L1;
599 break;
600 case ConnectionState::Modified:
601 statusText = tr("URL modified - click \"Connect\" to apply");
602 styleSheet = "QLabel { color: orange; }"_L1;
603 break;
604 }
605
606 m_ui->connectionStatusLabel->setText(statusText);
607 m_ui->connectionStatusLabel->setStyleSheet(styleSheet);
608}
609
610void MachineTranslationDialog::toggleAdvancedSettings(bool checked)
611{
612 m_ui->advancedSettingsWidget->setVisible(checked);
613 m_ui->advancedSettingsToggle->setText(checked ? tr("Advanced Settings \xe2\x96\xbc")
614 : tr("Advanced Settings \xe2\x96\xb6"));
615 if (checked) {
616 loadAdvancedSettings();
617 validateAdvancedSettings();
618 }
619}
620
621void MachineTranslationDialog::loadAdvancedSettings()
622{
623 m_ui->maxRetriesSpinBox->setValue(TranslationSettings::maxRetries());
624 m_ui->maxConcurrentBatchesSpinBox->setValue(TranslationSettings::maxConcurrentBatches());
625 m_ui->transferTimeoutSpinBox->setValue(TranslationSettings::transferTimeoutMs() / 1000);
626 m_ui->maxBatchSizeSpinBox->setValue(TranslationSettings::maxBatchSize());
627 m_ui->temperatureSpinBox->setValue(TranslationSettings::temperature());
628 m_ui->maxJsonFormatTriesSpinBox->setValue(TranslationSettings::maxJsonFormatTries());
629 m_ui->ollamaWakeUpTimeoutSpinBox->setValue(TranslationSettings::ollamaWakeUpTimeoutMs() / 1000);
630}
631
632void MachineTranslationDialog::saveAdvancedSettings()
633{
634 TranslationSettings::setMaxRetries(m_ui->maxRetriesSpinBox->value());
635 TranslationSettings::setMaxConcurrentBatches(m_ui->maxConcurrentBatchesSpinBox->value());
636 TranslationSettings::setTransferTimeoutMs(m_ui->transferTimeoutSpinBox->value() * 1000);
637 TranslationSettings::setMaxBatchSize(m_ui->maxBatchSizeSpinBox->value());
638 TranslationSettings::setTemperature(m_ui->temperatureSpinBox->value());
639 TranslationSettings::setMaxJsonFormatTries(m_ui->maxJsonFormatTriesSpinBox->value());
640 TranslationSettings::setOllamaWakeUpTimeoutMs(m_ui->ollamaWakeUpTimeoutSpinBox->value() * 1000);
641}
642
643void MachineTranslationDialog::resetAdvancedSettings()
644{
646 loadAdvancedSettings();
647 validateAdvancedSettings();
648}
649
650void MachineTranslationDialog::validateAdvancedSettings()
651{
652 const int maxRetries = m_ui->maxRetriesSpinBox->value();
653 const int maxJsonFormatTries = m_ui->maxJsonFormatTriesSpinBox->value();
654
655 QStringList warnings;
656
657 // Warn if JSON format tries is too low
658 if (maxJsonFormatTries < 3) {
659 //: "Maximum JSON Format Tries" should use the same translation as
660 //: the label "Maximum JSON Format Tries" in Advanced Settings
661 warnings << tr("\xe2\x9a\xa0 Maximum JSON Format Tries: Low value may cause unnecessary "
662 "format switching due to temporary errors. Recommended: 3 or higher.");
663 }
664
665 // Warn if max retries doesn't cover all format stages
666 // 3 format stages: JsonObject, JsonSchema, None
667 if (maxRetries < maxJsonFormatTries * 3) {
668 //: "Maximum Retries" and "Maximum JSON Format Tries" should use the same
669 //: translations as the labels "Maximum Retries" and "Maximum JSON Format Tries"
670 //: in Advanced Settings
671 warnings << tr("\xe2\x9a\xa0 Maximum Retries: Should be at least 3\xc3\x97 'Maximum JSON "
672 "Format Tries' for full fallback coverage");
673 }
674
675 m_ui->settingsWarningLabel->setText(warnings.join(u'\n'));
676}
677
679
680QT_END_NAMESPACE
bool isValid() const
DataModelIterator(TranslationType type, const DataModel *model=0, int groupNo=0, int messageNo=0)
MessageItem * current() const
GroupItem * groupItem(int index, TranslationType type) const
int labelCount() const
int contextCount() const
void setDataModel(MultiDataModel *dm)
void stop() noexcept
const TranslatorMessage & message() const
DataModel * model(int i)
static void setMaxJsonFormatTries(int value)
static void setMaxBatchSize(int value)
static void setTemperature(double value)
static void setMaxConcurrentBatches(int value)
static void setTransferTimeoutMs(int value)
static void setMaxRetries(int value)
static void setOllamaWakeUpTimeoutMs(int value)
TranslationApiType
@ IDBASED
@ TEXTBASED