Add English and Russian ChatGUI interface translations

This commit is contained in:
evgeny committed 2026-10-05 16:55:43 +02:00
1 parent 972c6b7f0a
commit 27d93ecaf9
50 files changed
+4978 -491

No files matched your search

+20 -19
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "accountlist.h"
#include "accountdelegate.h"
#include "memberlistmodel.h"
@@ -57,10 +58,10 @@ QString AccountList::relativeTimeStr(qint64 timestamp) {
if (timestamp <= 0) return "";
qint64 diff = QDateTime::currentSecsSinceEpoch() - timestamp;
if (diff < 0) diff = 0;
if (diff < 60) return QString("%1s").arg(diff);
if (diff < 3600) return QString("%1m").arg(diff / 60);
if (diff < 86400) return QString("%1h").arg(diff / 3600);
return QString("%1d").arg(diff / 86400);
if (diff < 60) return QCoreApplication::translate("ChatGui", "%1s").arg(diff);
if (diff < 3600) return QCoreApplication::translate("ChatGui", "%1m").arg(diff / 60);
if (diff < 86400) return QCoreApplication::translate("ChatGui", "%1h").arg(diff / 3600);
return QCoreApplication::translate("ChatGui", "%1d").arg(diff / 86400);
}
AccountList::AccountList(DbManager* db, QWidget *parent)
@@ -75,7 +76,7 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
m_header = new QLabel("Members", this);
m_header = new QLabel(QCoreApplication::translate("ChatGui", "Members"), this);
m_header->setAlignment(Qt::AlignCenter);
m_header->setStyleSheet("font-weight: bold; padding: 8px; background: palette(window);");
layout->addWidget(m_header);
@@ -104,7 +105,7 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
detailLayout->setContentsMargins(0, 0, 0, 0);
detailLayout->setSpacing(0);
m_detailHeader = new QLabel("Select a member", m_detailPanel);
m_detailHeader = new QLabel(QCoreApplication::translate("ChatGui", "Select a member"), m_detailPanel);
m_detailHeader->setWordWrap(true);
m_detailHeader->setStyleSheet("font-weight: bold; padding: 4px 8px; background: palette(window);");
detailLayout->addWidget(m_detailHeader);
@@ -177,11 +178,11 @@ void AccountList::setChannel(const QString& channelId) {
m_detailTimer->stop();
m_linkTimer->stop();
m_detailNodeId = 0;
m_detailHeader->setText("Select a member");
m_detailHeader->setText(QCoreApplication::translate("ChatGui", "Select a member"));
m_detailText->clear();
m_header->setText(isOwner ? QString::fromUtf8("Members \xF0\x9F\x94\x91") : QStringLiteral("Members"));
m_header->setToolTip(isOwner ? QStringLiteral("Channel owner (has private key)") : QString());
m_header->setText(isOwner ? QCoreApplication::translate("ChatGui", "Members 🔑") : QCoreApplication::translate("ChatGui", "Members"));
m_header->setToolTip(isOwner ? QCoreApplication::translate("ChatGui", "Channel owner (has private key)") : QString());
}
void AccountList::refresh() { m_model->refresh(); }
@@ -371,9 +372,9 @@ void AccountList::updateDetailDisplay() {
hdr.append(QString("<span style='color:%1;'>●</span> %2 %3")
.arg(dotColor, m_detailName.toHtmlEscaped(), nodeHex));
if (m_detailLastSeen > 0)
hdr.append(QString("seen %1").arg(relativeTimeStr(m_detailLastSeen)));
hdr.append(QCoreApplication::translate("ChatGui", "seen %1").arg(relativeTimeStr(m_detailLastSeen)));
if (m_detailCreated > 0)
hdr.append(QString("created %1").arg(relativeTimeStr(m_detailCreated)));
hdr.append(QCoreApplication::translate("ChatGui", "created %1").arg(relativeTimeStr(m_detailCreated)));
m_detailHeader->setText(hdr.join(" "));
/* ── Body ── */
@@ -385,7 +386,7 @@ void AccountList::updateDetailDisplay() {
};
if (!m_detailAddrs.isEmpty()) {
lines.append("── Addresses ──");
lines.append(QCoreApplication::translate("ChatGui", "── Addresses ──"));
for (const auto& a : m_detailAddrs) {
lines.append(QString(" [sock%1] %2 %3 %4:%5 %6")
.arg(a.socket_id)
@@ -398,7 +399,7 @@ void AccountList::updateDetailDisplay() {
}
if (isSelf) {
lines.append("── Listen ──");
lines.append(QCoreApplication::translate("ChatGui", "── Listen ──"));
for (const auto& s : m_snapSocks) {
const DetailAddress* a = nullptr;
for (const auto& da : m_detailAddrs) {
@@ -413,7 +414,7 @@ void AccountList::updateDetailDisplay() {
}
} else {
if (!m_snapLinks.isEmpty()) {
lines.append("── Connections ──");
lines.append(QCoreApplication::translate("ChatGui", "── Connections ──"));
for (const auto& lk : m_snapLinks) {
bool up = (lk.linkState == 3 && lk.linkStatus == 1);
QString c = up ? "#4CAF50" : "#E53935";
@@ -482,15 +483,15 @@ void AccountList::onMemberContextMenu(const QPoint& pos) {
QString name = m_model->data(index, Qt::DisplayRole).toString();
QMenu menu(m_listView);
QAction* pmAction = menu.addAction(QString::fromUtf8("Написать сообщение"));
QAction* pmAction = menu.addAction(QCoreApplication::translate("ChatGui", "Send message"));
if (nodeId == m_db->myNodeId()) pmAction->setEnabled(false);
QAction* callAction = menu.addAction(QString::fromUtf8("Позвонить"));
QAction* callAction = menu.addAction(QCoreApplication::translate("ChatGui", "Call peer"));
if (nodeId == m_db->myNodeId() || !m_model->isOnlineAt(index.row())) callAction->setEnabled(false);
QAction* connectAction = menu.addAction("Connect");
QAction* connectAction = menu.addAction(QCoreApplication::translate("ChatGui", "Connect"));
if (nodeId == m_db->myNodeId()) connectAction->setEnabled(false);
QAction* renameAction = menu.addAction("Rename");
QAction* renameAction = menu.addAction(QCoreApplication::translate("ChatGui", "Rename"));
if (nodeId == m_db->myNodeId()) renameAction->setEnabled(false);
QAction* editAction = menu.addAction("Edit");
QAction* editAction = menu.addAction(QCoreApplication::translate("ChatGui", "Edit"));
QAction* chosen = menu.exec(m_listView->viewport()->mapToGlobal(pos));
if (chosen == pmAction) {
+34 -33
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "audiodevicesettingspage.h"
#include "sound_manager.h"
#include "audiorecorder.h"
@@ -33,14 +34,14 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
formLayout->setContentsMargins(0, 0, 0, 0);
formLayout->setSpacing(10);
auto* outLabel = new QLabel("Playback device:", this);
auto* outLabel = new QLabel(QCoreApplication::translate("ChatGui", "Playback device:"), this);
m_outputCombo = new QComboBox(this);
m_outputCombo->setStyleSheet(
"QComboBox { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
formLayout->addRow(outLabel, m_outputCombo);
auto* inLabel = new QLabel("Recording device:", this);
auto* inLabel = new QLabel(QCoreApplication::translate("ChatGui", "Recording device:"), this);
m_inputCombo = new QComboBox(this);
m_inputCombo->setStyleSheet(m_outputCombo->styleSheet());
formLayout->addRow(inLabel, m_inputCombo);
@@ -54,7 +55,7 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
layout->addWidget(sep1);
/* --- Microphone test --- */
auto* micLabel = new QLabel("Microphone test", this);
auto* micLabel = new QLabel(QCoreApplication::translate("ChatGui", "Microphone test"), this);
micLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(micLabel);
@@ -67,7 +68,7 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
m_micLevelBar->setFixedHeight(16);
micRow->addWidget(m_micLevelBar, 1);
m_testMicBtn = new QPushButton("Test Microphone", this);
m_testMicBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Test Microphone"), this);
m_testMicBtn->setCheckable(true);
m_testMicBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
@@ -84,11 +85,11 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
layout->addWidget(sep2);
/* --- Speaker test --- */
auto* spkLabel = new QLabel("Speaker test", this);
auto* spkLabel = new QLabel(QCoreApplication::translate("ChatGui", "Speaker test"), this);
spkLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(spkLabel);
m_testSpeakerBtn = new QPushButton("Test Speaker (440Hz)", this);
m_testSpeakerBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Test Speaker (440Hz)"), this);
m_testSpeakerBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
@@ -103,14 +104,14 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
layout->addWidget(sep3);
/* --- Codec quality --- */
auto* codecLabel = new QLabel("Voice codec quality", this);
auto* codecLabel = new QLabel(QCoreApplication::translate("ChatGui", "Voice codec quality"), this);
codecLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(codecLabel);
m_codecPresetCombo = new QComboBox(this);
m_codecPresetCombo->addItem("Low (16 kbps, fast)", 0);
m_codecPresetCombo->addItem("Standard (32 kbps)", 1);
m_codecPresetCombo->addItem("High (64 kbps, best)", 2);
m_codecPresetCombo->addItem(QCoreApplication::translate("ChatGui", "Low (16 kbps, fast)"), 0);
m_codecPresetCombo->addItem(QCoreApplication::translate("ChatGui", "Standard (32 kbps)"), 1);
m_codecPresetCombo->addItem(QCoreApplication::translate("ChatGui", "High (64 kbps, best)"), 2);
m_codecPresetCombo->setStyleSheet(m_outputCombo->styleSheet());
layout->addWidget(m_codecPresetCombo);
@@ -121,7 +122,7 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
layout->addWidget(sep5);
/* --- Radio (PTT) Opus --- */
auto* radioLabel = new QLabel("Radio (PTT) — Opus", this);
auto* radioLabel = new QLabel(QCoreApplication::translate("ChatGui", "Radio (PTT) — Opus"), this);
radioLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(radioLabel);
@@ -130,20 +131,20 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
radioForm->setSpacing(10);
m_radioOpusModeCombo = new QComboBox(this);
m_radioOpusModeCombo->addItem("Voice (VOIP)", 0);
m_radioOpusModeCombo->addItem("Music (AUDIO)", 1);
m_radioOpusModeCombo->addItem(QCoreApplication::translate("ChatGui", "Voice (VOIP)"), 0);
m_radioOpusModeCombo->addItem(QCoreApplication::translate("ChatGui", "Music (AUDIO)"), 1);
m_radioOpusModeCombo->setStyleSheet(m_outputCombo->styleSheet());
radioForm->addRow(new QLabel("Mode:", this), m_radioOpusModeCombo);
radioForm->addRow(new QLabel(QCoreApplication::translate("ChatGui", "Mode:"), this), m_radioOpusModeCombo);
m_radioOpusBitrateCombo = new QComboBox(this);
for (int br : {16, 32, 64, 96, 128})
m_radioOpusBitrateCombo->addItem(QString::number(br) + " kbps", br);
m_radioOpusBitrateCombo->addItem(QString::number(br) + QCoreApplication::translate("ChatGui", " kbps"), br);
m_radioOpusBitrateCombo->setStyleSheet(m_outputCombo->styleSheet());
radioForm->addRow(new QLabel("Bitrate:", this), m_radioOpusBitrateCombo);
radioForm->addRow(new QLabel(QCoreApplication::translate("ChatGui", "Bitrate:"), this), m_radioOpusBitrateCombo);
layout->addLayout(radioForm);
m_radioCaptureStereoCb = new QCheckBox("Capture stereo (playback is always stereo)", this);
m_radioCaptureStereoCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Capture stereo (playback is always stereo)"), this);
m_radioCaptureStereoCb->setStyleSheet("font-size: 12px;");
layout->addWidget(m_radioCaptureStereoCb);
@@ -159,9 +160,9 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* r
m_jitterReserveSpin->setDecimals(1);
m_jitterReserveSpin->setSingleStep(0.1);
m_jitterReserveSpin->setValue(1.0);
m_jitterReserveSpin->setSuffix(" s");
m_jitterReserveSpin->setToolTip("Maximum adaptive jitter reserve for calls. Applies to the next call.");
callForm->addRow("Call: maximum jitter reserve", m_jitterReserveSpin);
m_jitterReserveSpin->setSuffix(QCoreApplication::translate("ChatGui", " s"));
m_jitterReserveSpin->setToolTip(QCoreApplication::translate("ChatGui", "Maximum adaptive jitter reserve for calls. Applies to the next call."));
callForm->addRow(QCoreApplication::translate("ChatGui", "Call: maximum jitter reserve"), m_jitterReserveSpin);
layout->addLayout(callForm);
buildCompressorUI(layout);
@@ -183,19 +184,19 @@ void AudioDeviceSettingsPage::loadFromDb() {
/* Output devices */
m_outputCombo->clear();
m_outputCombo->addItem("Default (auto)", QString());
m_outputCombo->addItem(QCoreApplication::translate("ChatGui", "Default (auto)"), QString());
for (int i = 0; i < playbackDevs.size(); i++) {
QString label = playbackDevs[i].name;
if (playbackDevs[i].isDefault) label += " (default)";
if (playbackDevs[i].isDefault) label += QCoreApplication::translate("ChatGui", " (default)");
m_outputCombo->addItem(label, playbackDevs[i].id);
}
/* Input devices */
m_inputCombo->clear();
m_inputCombo->addItem("Default (auto)", QString());
m_inputCombo->addItem(QCoreApplication::translate("ChatGui", "Default (auto)"), QString());
for (int i = 0; i < captureDevs.size(); i++) {
QString label = captureDevs[i].name;
if (captureDevs[i].isDefault) label += " (default)";
if (captureDevs[i].isDefault) label += QCoreApplication::translate("ChatGui", " (default)");
m_inputCombo->addItem(label, captureDevs[i].id);
}
@@ -366,11 +367,11 @@ void AudioDeviceSettingsPage::onTestMicToggled() {
m_testRecorder->startRecording(true);
m_micTestActive = true;
m_levelTimer->start();
m_testMicBtn->setText("Stop Test");
m_testMicBtn->setText(QCoreApplication::translate("ChatGui", "Stop Test"));
m_micLevelBar->setValue(0);
} else {
stopMicTest();
m_testMicBtn->setText("Test Microphone");
m_testMicBtn->setText(QCoreApplication::translate("ChatGui", "Test Microphone"));
}
}
@@ -415,15 +416,15 @@ void AudioDeviceSettingsPage::onTestSpeaker() {
}
void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
auto* compLabel = new QLabel("Voice Compressor", this);
auto* compLabel = new QLabel(QCoreApplication::translate("ChatGui", "Voice Compressor"), this);
compLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(compLabel);
m_compressorEnabledCb = new QCheckBox("Enable compressor (leveler/normalizer)", this);
m_compressorEnabledCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Enable compressor (leveler/normalizer)"), this);
m_compressorEnabledCb->setStyleSheet("font-size: 12px;");
layout->addWidget(m_compressorEnabledCb);
m_aecEnabledCb = new QCheckBox("Enable echo cancellation (AEC) in calls", this);
m_aecEnabledCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Enable echo cancellation (AEC) in calls"), this);
m_aecEnabledCb->setStyleSheet("font-size: 12px;");
layout->addWidget(m_aecEnabledCb);
@@ -432,7 +433,7 @@ void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
auto* row = new QHBoxLayout();
row->setSpacing(6);
auto* textLabel = new QLabel(name, this);
textLabel->setFixedWidth(180);
textLabel->setMinimumWidth(180);
textLabel->setStyleSheet("font-size: 12px;");
row->addWidget(textLabel);
slider = new QSlider(Qt::Horizontal, this);
@@ -450,9 +451,9 @@ void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
layout->addLayout(row);
};
makeSliderRow("Max gain:", m_compressorMaxGainSlider, m_compressorMaxGainLabel,
makeSliderRow(QCoreApplication::translate("ChatGui", "Max gain:"), m_compressorMaxGainSlider, m_compressorMaxGainLabel,
1, 50, 1);
makeSliderRow("Rise rate (x/sec):", m_compressorRiseRateSlider, m_compressorRiseRateLabel,
makeSliderRow(QCoreApplication::translate("ChatGui", "Rise rate (x/sec):"), m_compressorRiseRateSlider, m_compressorRiseRateLabel,
2, 10, 1);
auto connectSlider = [this](QSlider* slider) {
@@ -464,7 +465,7 @@ void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
void AudioDeviceSettingsPage::updateCompressorLabels() {
if (m_compressorMaxGainLabel)
m_compressorMaxGainLabel->setText(QString::number(m_compressorMaxGainSlider->value()) + " dB");
m_compressorMaxGainLabel->setText(QString::number(m_compressorMaxGainSlider->value()) + QCoreApplication::translate("ChatGui", " dB"));
if (m_compressorRiseRateLabel) {
int v = m_compressorRiseRateSlider->value();
m_compressorRiseRateLabel->setText(QString::number(v) + "x");
+25 -24
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "callwindow.h"
#include "../transport/gui_bridge.h"
#include "call_audio_engine.h"
@@ -136,7 +137,7 @@ static QWidget* wrapWithCaption(QWidget* btn, const QString& caption, QLabel** c
CallWindow::CallWindow(QWidget* parent)
: QWidget(parent, Qt::Window) {
setWindowTitle(QStringLiteral("Звонок"));
setWindowTitle(QCoreApplication::translate("ChatGui", "Call"));
setFixedSize(360, 480);
setAttribute(Qt::WA_DeleteOnClose, false);
@@ -171,10 +172,10 @@ CallWindow::CallWindow(QWidget* parent)
m_statsLabel->setStyleSheet("color: gray; font-size: 11px;");
m_statsLabel->hide();
m_acceptBtn = makeRoundBtn(makeRoundIcon(QColor("#27ae60"), Glyph::Phone, 96), QStringLiteral("Принять"), this);
m_declineBtn = makeRoundBtn(makeRoundIcon(QColor("#e74c3c"), Glyph::PhoneDown, 96), QStringLiteral("Отклонить"), this);
m_hangupBtn = makeRoundBtn(makeRoundIcon(QColor("#e74c3c"), Glyph::PhoneDown, 96), QStringLiteral("Завершить"), this);
m_muteBtn = makeRoundBtn(makeRoundIcon(QColor("#7f8c8d"), Glyph::Mic, 96), QStringLiteral("Mute"), this);
m_acceptBtn = makeRoundBtn(makeRoundIcon(QColor("#27ae60"), Glyph::Phone, 96), QCoreApplication::translate("ChatGui", "Accept"), this);
m_declineBtn = makeRoundBtn(makeRoundIcon(QColor("#e74c3c"), Glyph::PhoneDown, 96), QCoreApplication::translate("ChatGui", "Decline"), this);
m_hangupBtn = makeRoundBtn(makeRoundIcon(QColor("#e74c3c"), Glyph::PhoneDown, 96), QCoreApplication::translate("ChatGui", "End call"), this);
m_muteBtn = makeRoundBtn(makeRoundIcon(QColor("#7f8c8d"), Glyph::Mic, 96), QCoreApplication::translate("ChatGui", "Mute"), this);
m_muteBtn->setCheckable(true);
m_deviceCombo = new QComboBox(this);
@@ -194,10 +195,10 @@ CallWindow::CallWindow(QWidget* parent)
lay->addWidget(m_statsLabel);
lay->addStretch(1);
m_acceptCell = wrapWithCaption(m_acceptBtn, QStringLiteral("Принять"));
m_declineCell = wrapWithCaption(m_declineBtn, QStringLiteral("Отклонить"));
m_hangupCell = wrapWithCaption(m_hangupBtn, QStringLiteral("Завершить"));
m_muteCell = wrapWithCaption(m_muteBtn, QStringLiteral("Mute"), &m_muteCaption);
m_acceptCell = wrapWithCaption(m_acceptBtn, QCoreApplication::translate("ChatGui", "Accept"));
m_declineCell = wrapWithCaption(m_declineBtn, QCoreApplication::translate("ChatGui", "Decline"));
m_hangupCell = wrapWithCaption(m_hangupBtn, QCoreApplication::translate("ChatGui", "End call"));
m_muteCell = wrapWithCaption(m_muteBtn, QCoreApplication::translate("ChatGui", "Mute"), &m_muteCaption);
QHBoxLayout* btn = new QHBoxLayout();
btn->setSpacing(10);
@@ -237,29 +238,29 @@ void CallWindow::setBgpPath(const QString& path) {
}
void CallWindow::setConnectionType(uint8_t type, quint64 nextHop) {
const QString names[] = { QStringLiteral("нет подключения"), QStringLiteral("прямое"),
QStringLiteral("reverse"), QStringLiteral("indirect") };
m_connectionLabel->setText(QStringLiteral("Подключение: %1").arg(names[type <= 3 ? type : 0]));
m_connectionLabel->setToolTip(type == 3 ? QStringLiteral("Посредник: 0x%1").arg(nextHop, 16, 16, QChar('0')) : QString());
const QString names[] = { QCoreApplication::translate("ChatGui", "not connected"), QCoreApplication::translate("ChatGui", "direct"),
QCoreApplication::translate("ChatGui", "reverse"), QCoreApplication::translate("ChatGui", "indirect") };
m_connectionLabel->setText(QCoreApplication::translate("ChatGui", "Connection: %1").arg(names[type <= 3 ? type : 0]));
m_connectionLabel->setToolTip(type == 3 ? QCoreApplication::translate("ChatGui", "Relay: 0x%1").arg(nextHop, 16, 16, QChar('0')) : QString());
}
void CallWindow::setState(State s) {
m_state = s;
switch (s) {
case Outgoing:
m_statusLabel->setText(QStringLiteral("Пытаемся связаться…"));
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Connecting…"));
m_durationLabel->hide();
break;
case Ringing:
m_statusLabel->setText(QStringLiteral("Звоним…"));
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Calling…"));
m_durationLabel->hide();
break;
case Incoming:
m_statusLabel->setText(QStringLiteral("Входящий звонок…"));
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Incoming call…"));
m_durationLabel->hide();
break;
case Active:
m_statusLabel->setText(QStringLiteral("Разговор"));
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "In call"));
m_durationLabel->setText(QStringLiteral("00:00"));
m_durationLabel->show();
m_startMs = QDateTime::currentMSecsSinceEpoch();
@@ -269,7 +270,7 @@ void CallWindow::setState(State s) {
break;
case Ended:
setConnectionType(0, 0);
m_statusLabel->setText(QStringLiteral("Завершён"));
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Ended"));
m_durationTimer->stop();
break;
}
@@ -279,11 +280,11 @@ void CallWindow::setState(State s) {
void CallWindow::populateDeviceCombo() {
m_deviceCombo->blockSignals(true);
m_deviceCombo->clear();
m_deviceCombo->addItem(QStringLiteral("Default (auto)"), QString());
m_deviceCombo->addItem(QCoreApplication::translate("ChatGui", "Default (auto)"), QString());
const auto devs = SoundManager::enumeratePlaybackDevices();
for (int i = 0; i < devs.size(); i++) {
QString label = devs[i].name;
if (devs[i].isDefault) label += QStringLiteral(" (default)");
if (devs[i].isDefault) label += QCoreApplication::translate("ChatGui", " (default)");
m_deviceCombo->addItem(label, devs[i].id);
}
QString cur = CallAudioEngine::instance()->playbackDevice();
@@ -328,7 +329,7 @@ void CallWindow::onMuteToggled(bool checked) {
m_muteBtn->setIcon(makeRoundIcon(checked ? QColor("#e74c3c") : QColor("#7f8c8d"),
checked ? Glyph::MicOff : Glyph::Mic, 96));
if (m_muteCaption)
m_muteCaption->setText(checked ? QStringLiteral("Вкл. микрофон") : QStringLiteral("Mute"));
m_muteCaption->setText(checked ? QCoreApplication::translate("ChatGui", "Unmute") : QCoreApplication::translate("ChatGui", "Mute"));
}
void CallWindow::onDeviceChanged(int index) {
@@ -354,9 +355,9 @@ void CallWindow::updateDuration() {
void CallWindow::setAudioStats(int rtt, int buffer, int tempo, int dropped, int underruns,
int minimum, int maximum, int reserve) {
m_statsLabel->setText(QStringLiteral("Буфер: %1 мс · Запас: %2 мс\n"
"За 10 с: минимум %3 · максимум %4 мс\n"
"Скорость: %5× · RTT: %6 мс\nПотери буфера: %7 · Недостачи: %8")
m_statsLabel->setText(QCoreApplication::translate("ChatGui",
"Buffer: %1 ms · Reserve: %2 ms\nOver 10 s: minimum %3 · maximum %4 ms\nSpeed: %5× · RTT: %6 "
"ms\nBuffer drops: %7 · Underruns: %8")
.arg(buffer).arg(reserve).arg(minimum).arg(maximum)
.arg(tempo / 100.0, 0, 'f', 2).arg(rtt).arg(dropped).arg(underruns));
m_statsLabel->show();
+8 -7
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "channellist.h"
#include "channeldelegate.h"
#include "../db/db_manager.h"
@@ -146,7 +147,7 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
auto *header = new QLabel("Channels", this);
auto *header = new QLabel(QCoreApplication::translate("ChatGui", "Channels"), this);
header->setAlignment(Qt::AlignCenter);
header->setStyleSheet("font-weight: bold; padding: 8px; background: palette(window);");
layout->addWidget(header);
@@ -167,7 +168,7 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
auto *gearBtn = new QPushButton(QString::fromUtf8("\xE2\x9A\x99"), footer);
gearBtn->setFixedSize(28, 28);
gearBtn->setToolTip(QString::fromUtf8("Настройки"));
gearBtn->setToolTip(QCoreApplication::translate("ChatGui", "Settings"));
gearBtn->setStyleSheet(
"QPushButton { border: none; font-size: 18px; background: transparent; color: #666; }"
"QPushButton:hover { color: #3390EC; background: rgba(51,144,236,20); border-radius: 14px; }");
@@ -177,7 +178,7 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
auto *monitorBtn = new QPushButton(QString::fromUtf8("\xF0\x9F\x94\x8D"), footer); /* 🔍 */
monitorBtn->setFixedSize(28, 28);
monitorBtn->setToolTip(QString::fromUtf8("Connection Monitor"));
monitorBtn->setToolTip(QCoreApplication::translate("ChatGui", "Connection Monitor"));
monitorBtn->setStyleSheet(
"QPushButton { border: none; font-size: 16px; background: transparent; color: #666; }"
"QPushButton:hover { color: #3390EC; background: rgba(51,144,236,20); border-radius: 14px; }");
@@ -186,9 +187,9 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
fl->addStretch();
auto *createBtn = new QPushButton("GRP", footer);
auto *createBtn = new QPushButton(QCoreApplication::translate("ChatGui", "GRP"), footer);
createBtn->setFixedSize(36, 28);
createBtn->setToolTip(QString::fromUtf8("Создать группу"));
createBtn->setToolTip(QCoreApplication::translate("ChatGui", "Create group"));
createBtn->setStyleSheet(
"QPushButton { border: none; border-radius: 6px; font-size: 11px; font-weight: bold;"
" color: white; background: #27AE60; }"
@@ -198,7 +199,7 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
auto *plusBtn = new QPushButton("+", footer);
plusBtn->setFixedSize(28, 28);
plusBtn->setToolTip(QString::fromUtf8("Присоединиться"));
plusBtn->setToolTip(QCoreApplication::translate("ChatGui", "Join"));
plusBtn->setStyleSheet(
"QPushButton { border: none; border-radius: 14px; font-size: 18px; font-weight: bold;"
" color: white; background: #3390EC; }"
@@ -294,7 +295,7 @@ void ChannelList::loadChannels() {
auto dms = m_db->getDmConversations();
if (!dms.isEmpty()) {
auto *section = new QStandardItem();
section->setData(QStringLiteral("Личные"), ChannelNameRole);
section->setData(QCoreApplication::translate("ChatGui", "Direct messages"), ChannelNameRole);
section->setData(true, ChannelIsSectionRole);
section->setFlags(Qt::NoItemFlags);
m_model->appendRow(section);
+15 -12
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "channelsettingsdialog.h"
#include "../db/db_manager.h"
#include "../transport/gui_bridge.h"
@@ -16,7 +17,7 @@ ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager
, m_channelId(channelId)
, m_db(db)
{
setWindowTitle(QStringLiteral("Channel Settings"));
setWindowTitle(QCoreApplication::translate("ChatGui", "Channel Settings"));
setMinimumWidth(340);
setModal(true);
@@ -24,7 +25,7 @@ ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager
layout->setContentsMargins(20, 16, 20, 16);
layout->setSpacing(12);
auto* titleLabel = new QLabel(QStringLiteral("Settings for channel"), this);
auto* titleLabel = new QLabel(QCoreApplication::translate("ChatGui", "Settings for channel"), this);
titleLabel->setStyleSheet("font-weight: bold; font-size: 14px;");
layout->addWidget(titleLabel);
@@ -35,16 +36,18 @@ ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager
layout->addSpacing(8);
m_autoPlayVoice = new QCheckBox(QStringLiteral("Auto-play voice messages on receive"), this);
m_autoPlayVoice->setToolTip(QStringLiteral("Voice messages will play automatically when received. Multiple messages queue up and play sequentially without interrupting current playback."));
m_autoPlayVoice = new QCheckBox(QCoreApplication::translate("ChatGui", "Auto-play voice messages on receive"), this);
m_autoPlayVoice->setToolTip(QCoreApplication::translate("ChatGui",
"Voice messages will play automatically when received. Multiple messages queue up and play "
"sequentially without interrupting current playback."));
layout->addWidget(m_autoPlayVoice);
m_soundOnMessage = new QCheckBox(QStringLiteral("Play sound on incoming message"), this);
m_soundOnMessage->setToolTip(QStringLiteral("Play a notification sound when a message arrives in this channel."));
m_soundOnMessage = new QCheckBox(QCoreApplication::translate("ChatGui", "Play sound on incoming message"), this);
m_soundOnMessage->setToolTip(QCoreApplication::translate("ChatGui", "Play a notification sound when a message arrives in this channel."));
layout->addWidget(m_soundOnMessage);
m_pttMode = new QCheckBox(QStringLiteral("Push-To-Talk (PTT) mode"), this);
m_pttMode->setToolTip(QStringLiteral("Record voice messages with push-to-talk. (Coming soon)"));
m_pttMode = new QCheckBox(QCoreApplication::translate("ChatGui", "Push-To-Talk (PTT) mode"), this);
m_pttMode->setToolTip(QCoreApplication::translate("ChatGui", "Record voice messages with push-to-talk. (Coming soon)"));
m_pttMode->setEnabled(false);
layout->addWidget(m_pttMode);
@@ -54,8 +57,8 @@ ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager
btnLayout->setSpacing(8);
btnLayout->addStretch();
auto* cancelBtn = new QPushButton(QStringLiteral("Cancel"), this);
cancelBtn->setFixedWidth(90);
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), this);
cancelBtn->setMinimumWidth(90);
cancelBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
@@ -63,8 +66,8 @@ ChannelSettingsDialog::ChannelSettingsDialog(const QString& channelId, DbManager
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
btnLayout->addWidget(cancelBtn);
auto* saveBtn = new QPushButton(QStringLiteral("Save"), this);
saveBtn->setFixedWidth(90);
auto* saveBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Save"), this);
saveBtn->setMinimumWidth(90);
saveBtn->setDefault(true);
saveBtn->setStyleSheet(
"QPushButton { border: none; border-radius: 4px;"
+2 -1
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "chatview.h"
#include "messagedelegate.h"
#include "emoji.h"
@@ -44,7 +45,7 @@ ChatView::ChatView(QWidget *parent)
" background: rgba(90,90,90,220);"
"}"
);
m_scrollDownBtn->setToolTip(tr("Scroll to bottom"));
m_scrollDownBtn->setToolTip(QCoreApplication::translate("ChatGui", "Scroll to bottom"));
m_scrollDownBtn->setVisible(false);
connect(m_scrollDownBtn, &QPushButton::clicked, this, [this]() {
+4 -3
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
// connmonitorwindow.cpp — ETCP connection monitor (standalone, non-modal window)
#include "connmonitorwindow.h"
#include "../transport/gui_bridge.h"
@@ -31,7 +32,7 @@ static void connMetricsCb(const uint8_t* data, int len) {
ConnMonitorWindow::ConnMonitorWindow(QWidget* parent)
: QDialog(parent)
{
setWindowTitle("Connection Monitor");
setWindowTitle(QCoreApplication::translate("ChatGui", "Connection Monitor"));
resize(900, 550);
setAttribute(Qt::WA_DeleteOnClose);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@@ -40,7 +41,7 @@ ConnMonitorWindow::ConnMonitorWindow(QWidget* parent)
mainLayout->setSpacing(4);
mainLayout->setContentsMargins(8, 8, 8, 8);
auto* title = new QLabel("Active ETCP Connections (10 fps)", this);
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Active ETCP Connections (10 fps)"), this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
mainLayout->addWidget(title);
@@ -63,7 +64,7 @@ ConnMonitorWindow::ConnMonitorWindow(QWidget* parent)
m_details->setStyleSheet(
"QPlainTextEdit { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 8px; background: palette(base); }");
m_details->setPlaceholderText("Select a connection to view metrics");
m_details->setPlaceholderText(QCoreApplication::translate("ChatGui", "Select a connection to view metrics"));
splitter->addWidget(m_connList);
splitter->addWidget(m_details);
+6 -5
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "creategroupdialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
@@ -8,16 +9,16 @@ CreateGroupDialog::CreateGroupDialog(QWidget* parent)
: QDialog(parent)
, m_nameEdit(new QLineEdit(this))
{
setWindowTitle("Создать группу");
setWindowTitle(QCoreApplication::translate("ChatGui", "Create group"));
setMinimumWidth(350);
setModal(true);
auto* layout = new QVBoxLayout(this);
auto* nameLabel = new QLabel("Название группы:", this);
auto* nameLabel = new QLabel(QCoreApplication::translate("ChatGui", "Group name:"), this);
layout->addWidget(nameLabel);
m_nameEdit->setPlaceholderText("Введите название...");
m_nameEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Enter a name..."));
m_nameEdit->setMaxLength(128);
layout->addWidget(m_nameEdit);
@@ -26,11 +27,11 @@ CreateGroupDialog::CreateGroupDialog(QWidget* parent)
auto* btnLayout = new QHBoxLayout();
btnLayout->addStretch();
auto* cancelBtn = new QPushButton("Отмена", this);
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), this);
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
btnLayout->addWidget(cancelBtn);
auto* createBtn = new QPushButton("Создать", this);
auto* createBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Create"), this);
createBtn->setDefault(true);
connect(createBtn, &QPushButton::clicked, this, [this]() {
if (m_nameEdit->text().trimmed().isEmpty()) {
+36 -35
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
// databasesettingspage.cpp — Database maintenance page in settings
#include "databasesettingspage.h"
#include "../db/db_manager.h"
@@ -17,7 +18,7 @@ DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
layout->setSpacing(10);
layout->setContentsMargins(0, 0, 0, 0);
auto* title = new QLabel("Database Maintenance", this);
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Database Maintenance"), this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
layout->addWidget(title);
@@ -35,29 +36,29 @@ DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
QFileInfo fi(dbPath);
if (fi.exists()) {
qint64 sz = fi.size();
if (sz < 1024) dbSize = QString("%1 B").arg(sz);
else if (sz < 1024 * 1024) dbSize = QString("%1 KB").arg(sz / 1024);
else dbSize = QString("%1 MB").arg(sz / (1024.0 * 1024.0), 0, 'f', 2);
if (sz < 1024) dbSize = QCoreApplication::translate("ChatGui", "%1 B").arg(sz);
else if (sz < 1024 * 1024) dbSize = QCoreApplication::translate("ChatGui", "%1 KB").arg(sz / 1024);
else dbSize = QCoreApplication::translate("ChatGui", "%1 MB").arg(sz / (1024.0 * 1024.0), 0, 'f', 2);
}
QFileInfo walFi(dbPath + "-wal");
if (walFi.exists()) {
qint64 wsz = walFi.size();
if (wsz < 1024) walSize = QString("%1 B").arg(wsz);
else if (wsz < 1024 * 1024) walSize = QString("%1 KB").arg(wsz / 1024);
else walSize = QString("%1 MB").arg(wsz / (1024.0 * 1024.0), 0, 'f', 2);
if (wsz < 1024) walSize = QCoreApplication::translate("ChatGui", "%1 B").arg(wsz);
else if (wsz < 1024 * 1024) walSize = QCoreApplication::translate("ChatGui", "%1 KB").arg(wsz / 1024);
else walSize = QCoreApplication::translate("ChatGui", "%1 MB").arg(wsz / (1024.0 * 1024.0), 0, 'f', 2);
} else {
walSize = "n/a";
walSize = QCoreApplication::translate("ChatGui", "n/a");
}
}
}
m_filePathLabel = new QLabel(dbPath, this);
m_filePathLabel->setStyleSheet("font-size: 11px; color: gray;");
m_filePathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
infoLayout->addRow("File:", m_filePathLabel);
infoLayout->addRow(QCoreApplication::translate("ChatGui", "File:"), m_filePathLabel);
m_fileSizeLabel = new QLabel(dbSize, this);
m_fileSizeLabel->setStyleSheet("font-size: 11px;");
infoLayout->addRow("Size:", m_fileSizeLabel);
infoLayout->addRow(QCoreApplication::translate("ChatGui", "Size:"), m_fileSizeLabel);
m_walSizeLabel = new QLabel(walSize, this);
m_walSizeLabel->setStyleSheet("font-size: 11px;");
@@ -76,34 +77,34 @@ DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
btnLayout->setSpacing(6);
auto* quickRow = new QHBoxLayout();
m_quickBtn = new QPushButton("Quick Check", this);
m_quickBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Quick Check"), this);
m_quickBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 5px 14px; font-size: 12px; background: palette(button); }"
"QPushButton:hover { background: palette(light); }");
connect(m_quickBtn, &QPushButton::clicked, this, &DatabaseSettingsPage::onQuickCheck);
quickRow->addWidget(m_quickBtn);
auto* quickDesc = new QLabel("Scans structure without verifying indexes (fast)", this);
auto* quickDesc = new QLabel(QCoreApplication::translate("ChatGui", "Scans structure without verifying indexes (fast)"), this);
quickDesc->setStyleSheet("color: gray; font-size: 11px;");
quickRow->addWidget(quickDesc, 1);
btnLayout->addLayout(quickRow);
auto* fullRow = new QHBoxLayout();
m_fullBtn = new QPushButton("Full Check", this);
m_fullBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Full Check"), this);
m_fullBtn->setStyleSheet(m_quickBtn->styleSheet());
connect(m_fullBtn, &QPushButton::clicked, this, &DatabaseSettingsPage::onFullCheck);
fullRow->addWidget(m_fullBtn);
auto* fullDesc = new QLabel("Verifies all data structures and indexes (thorough)", this);
auto* fullDesc = new QLabel(QCoreApplication::translate("ChatGui", "Verifies all data structures and indexes (thorough)"), this);
fullDesc->setStyleSheet("color: gray; font-size: 11px;");
fullRow->addWidget(fullDesc, 1);
btnLayout->addLayout(fullRow);
auto* optRow = new QHBoxLayout();
m_optimizeBtn = new QPushButton("Optimize", this);
m_optimizeBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Optimize"), this);
m_optimizeBtn->setStyleSheet(m_quickBtn->styleSheet());
connect(m_optimizeBtn, &QPushButton::clicked, this, &DatabaseSettingsPage::onOptimize);
optRow->addWidget(m_optimizeBtn);
auto* optDesc = new QLabel("Update query planner statistics for better performance", this);
auto* optDesc = new QLabel(QCoreApplication::translate("ChatGui", "Update query planner statistics for better performance"), this);
optDesc->setStyleSheet("color: gray; font-size: 11px;");
optRow->addWidget(optDesc, 1);
btnLayout->addLayout(optRow);
@@ -123,13 +124,13 @@ DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
"QPlainTextEdit { font-family: monospace; font-size: 11px;"
" background: palette(base); border: 1px solid palette(mid); border-radius: 4px;"
" padding: 4px; }");
m_resultText->setPlaceholderText("Run a check to see results...");
m_resultText->setPlaceholderText(QCoreApplication::translate("ChatGui", "Run a check to see results..."));
layout->addWidget(m_resultText);
layout->addStretch();
if (!m_db || !m_db->m_db) {
m_statusLabel->setText("Database not connected");
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Database not connected"));
m_statusLabel->setStyleSheet("font-size: 12px; font-weight: bold; color: red;");
m_quickBtn->setEnabled(false);
m_fullBtn->setEnabled(false);
@@ -140,7 +141,7 @@ DatabaseSettingsPage::DatabaseSettingsPage(DbManager* db, QWidget* parent)
void DatabaseSettingsPage::refreshStatus() {
if (!m_db || !m_db->m_db) {
m_statusLabel->setText("Database not connected");
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Database not connected"));
m_statusLabel->setStyleSheet("font-size: 12px; font-weight: bold; color: red;");
m_quickBtn->setEnabled(false);
m_fullBtn->setEnabled(false);
@@ -157,16 +158,16 @@ void DatabaseSettingsPage::refreshStatus() {
QFileInfo fi(dbPath);
if (fi.exists()) {
qint64 sz = fi.size();
if (sz < 1024) m_fileSizeLabel->setText(QString("%1 B").arg(sz));
else if (sz < 1024 * 1024) m_fileSizeLabel->setText(QString("%1 KB").arg(sz / 1024));
else m_fileSizeLabel->setText(QString("%1 MB").arg(sz / (1024.0 * 1024.0), 0, 'f', 2));
if (sz < 1024) m_fileSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 B").arg(sz));
else if (sz < 1024 * 1024) m_fileSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 KB").arg(sz / 1024));
else m_fileSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 MB").arg(sz / (1024.0 * 1024.0), 0, 'f', 2));
QFileInfo walFi(dbPath + "-wal");
if (walFi.exists()) {
qint64 wsz = walFi.size();
if (wsz < 1024) m_walSizeLabel->setText(QString("%1 B").arg(wsz));
else if (wsz < 1024 * 1024) m_walSizeLabel->setText(QString("%1 KB").arg(wsz / 1024));
else m_walSizeLabel->setText(QString("%1 MB").arg(wsz / (1024.0 * 1024.0), 0, 'f', 2));
} else { m_walSizeLabel->setText("n/a"); }
if (wsz < 1024) m_walSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 B").arg(wsz));
else if (wsz < 1024 * 1024) m_walSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 KB").arg(wsz / 1024));
else m_walSizeLabel->setText(QCoreApplication::translate("ChatGui", "%1 MB").arg(wsz / (1024.0 * 1024.0), 0, 'f', 2));
} else { m_walSizeLabel->setText(QCoreApplication::translate("ChatGui", "n/a")); }
}
}
}
@@ -176,47 +177,47 @@ void DatabaseSettingsPage::setBusy(bool busy) {
m_fullBtn->setEnabled(!busy);
m_optimizeBtn->setEnabled(!busy);
if (busy) {
m_statusLabel->setText("Checking...");
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Checking..."));
m_statusLabel->setStyleSheet("font-size: 12px; margin-top: 4px; color: gray;");
}
}
void DatabaseSettingsPage::onQuickCheck() {
if (!m_db || !m_db->m_db) {
m_statusLabel->setText("Database not open");
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Database not open"));
m_statusLabel->setStyleSheet("font-size: 12px; color: red;");
return;
}
setBusy(true);
QTimer::singleShot(50, this, [this]() {
IntegrityResult r = m_db->integrityCheckQuick();
showResult("Quick Check", r);
showResult(QCoreApplication::translate("ChatGui", "Quick Check"), r);
});
}
void DatabaseSettingsPage::onFullCheck() {
if (!m_db || !m_db->m_db) {
m_statusLabel->setText("Database not open");
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Database not open"));
m_statusLabel->setStyleSheet("font-size: 12px; color: red;");
return;
}
setBusy(true);
QTimer::singleShot(50, this, [this]() {
IntegrityResult r = m_db->integrityCheckFull();
showResult("Full Check", r);
showResult(QCoreApplication::translate("ChatGui", "Full Check"), r);
});
}
void DatabaseSettingsPage::onOptimize() {
if (!m_db || !m_db->m_db) {
m_statusLabel->setText("Database not open");
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "Database not open"));
m_statusLabel->setStyleSheet("font-size: 12px; color: red;");
return;
}
setBusy(true);
QTimer::singleShot(50, this, [this]() {
IntegrityResult r = m_db->integrityOptimize();
showResult("Optimize", r);
showResult(QCoreApplication::translate("ChatGui", "Optimize"), r);
});
}
@@ -226,11 +227,11 @@ void DatabaseSettingsPage::showResult(const QString& checkName, const IntegrityR
m_optimizeBtn->setEnabled(true);
if (result.ok) {
m_statusLabel->setText(QString("%1: OK (%2 ms)")
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "%1: OK (%2 ms)")
.arg(checkName).arg(result.elapsedMs));
m_statusLabel->setStyleSheet("font-size: 12px; font-weight: bold; color: green;");
} else {
m_statusLabel->setText(QString("%1: FAILED (%2 ms)")
m_statusLabel->setText(QCoreApplication::translate("ChatGui", "%1: FAILED (%2 ms)")
.arg(checkName).arg(result.elapsedMs));
m_statusLabel->setStyleSheet("font-size: 12px; font-weight: bold; color: red;");
}
+19 -16
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "dminvitedialog.h"
#include "debug_ui.h"
#include "../db/db_manager.h"
@@ -18,28 +19,29 @@ extern "C" {
DmInviteDialog::DmInviteDialog(DbManager* db, quint64 peer, const QString& name, const QString& source, QWidget* parent)
: QDialog(parent), m_peer(peer), m_name(name), m_source(source) {
setWindowTitle(QStringLiteral("Пригласить в группу"));
setWindowTitle(QCoreApplication::translate("ChatGui", "Invite to group"));
setMinimumWidth(400);
auto* layout = new QVBoxLayout(this);
layout->addWidget(new QLabel(QStringLiteral("Получатель: %1").arg(name), this));
layout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Recipient: %1").arg(name), this));
m_groups = new QComboBox(this);
for (const auto& group : db->getChannels()) {
if (db->isChannelMember(group.channelId, db->myNodeId())) m_groups->addItem(group.name, group.channelId);
}
m_groups->setCurrentIndex(-1);
m_groups->setPlaceholderText(QStringLiteral("Выберите группу"));
m_groups->setPlaceholderText(QCoreApplication::translate("ChatGui", "Select a group"));
layout->addWidget(m_groups);
auto* expiry = new QLabel(QStringLiteral("Ссылка действует %1 минут с момента создания.\n"
"При поздней доставке PM потребуется новое приглашение.").arg(JOIN_KEY_TTL_SECONDS / 60), this);
auto* expiry = new QLabel(QCoreApplication::translate("ChatGui",
"The link expires %1 minutes after creation.\nIf the direct message arrives late, a new "
"invitation will be needed.").arg(JOIN_KEY_TTL_SECONDS / 60), this);
expiry->setWordWrap(true);
layout->addWidget(expiry);
m_status = new QLabel(this);
m_status->setWordWrap(true);
if (m_groups->count() == 0) m_status->setText(QStringLiteral("Нет групп для приглашения."));
if (m_groups->count() == 0) m_status->setText(QCoreApplication::translate("ChatGui", "No groups available to invite to."));
layout->addWidget(m_status);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
buttons->button(QDialogButtonBox::Cancel)->setText(QStringLiteral("Отмена"));
m_send = buttons->addButton(QStringLiteral("Пригласить"), QDialogButtonBox::AcceptRole);
buttons->button(QDialogButtonBox::Cancel)->setText(QCoreApplication::translate("ChatGui", "Cancel"));
m_send = buttons->addButton(QCoreApplication::translate("ChatGui", "Invite"), QDialogButtonBox::AcceptRole);
m_send->setEnabled(false);
layout->addWidget(buttons);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
@@ -70,10 +72,11 @@ void DmInviteDialog::sendInvite() {
if (m_pending || m_groups->currentIndex() < 0) return;
if (!gui_bridge_is_uasync_ready() || !gui_bridge_get_inst()) {
GUI_ERROR("invite PM: core unavailable");
m_status->setText(QStringLiteral("Ядро не запущено.")); return;
m_status->setText(QCoreApplication::translate("ChatGui", "The core is not running.")); return;
}
auto* req = (struct dm_invite_req*)u_calloc(1, sizeof(struct dm_invite_req));
if (!req) { GUI_ERROR("invite PM: request allocation failed"); m_status->setText(QStringLiteral("Недостаточно памяти.")); return; }
if (!req) { GUI_ERROR("invite PM: request allocation failed"); m_status->setText(QCoreApplication::translate("ChatGui",
"Not enough memory.")); return; }
static uint64_t nextRequestId = 0;
req->inst = gui_bridge_get_inst(); req->request_id = m_requestId = ++nextRequestId; req->peer_node_id = m_peer;
req->channel_id = m_groups->currentData().toString().toULongLong();
@@ -81,7 +84,7 @@ void DmInviteDialog::sendInvite() {
snprintf(req->peer_name, sizeof(req->peer_name), "%s", m_name.toUtf8().constData());
m_pending = true;
m_groups->setEnabled(false); m_send->setEnabled(false);
m_status->setText(QStringLiteral("Создание и отправка приглашения…"));
m_status->setText(QCoreApplication::translate("ChatGui", "Creating and sending invitation…"));
gui_bridge_post_uasync_fn(dm_invite_trampoline, req);
}
@@ -93,11 +96,11 @@ void DmInviteDialog::onResult(const uint8_t* data, int len) {
if (!m_pending || request != m_requestId || peer != m_peer) return;
m_pending = false;
if (result == DM_INVITE_OK) { m_convId = QString::number(conv); accept(); return; }
const QString error = result == DM_INVITE_START_ERROR ? QStringLiteral("Не удалось создать личную беседу.")
: result == DM_INVITE_SEND_ERROR ? QStringLiteral("Не удалось отправить приглашение.")
: result == DM_INVITE_BUSY ? QStringLiteral("Другое приглашение ещё создаётся. Повторите попытку.")
: result == DM_INVITE_CANCELLED ? QStringLiteral("Создание приглашения отменено.")
: QStringLiteral("Не удалось создать приглашение. Проверьте подключение и повторите попытку.");
const QString error = result == DM_INVITE_START_ERROR ? QCoreApplication::translate("ChatGui", "Failed to create a direct conversation.")
: result == DM_INVITE_SEND_ERROR ? QCoreApplication::translate("ChatGui", "Failed to send the invitation.")
: result == DM_INVITE_BUSY ? QCoreApplication::translate("ChatGui", "Another invitation is still being created. Try again.")
: result == DM_INVITE_CANCELLED ? QCoreApplication::translate("ChatGui", "Invitation creation cancelled.")
: QCoreApplication::translate("ChatGui", "Failed to create an invitation. Check the connection and try again.");
GUI_ERROR("invite PM dialog: failed request=%llu result=%d", (unsigned long long)request, result);
m_status->setText(error); m_groups->setEnabled(true); m_send->setEnabled(m_groups->currentIndex() >= 0);
}
+5 -4
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "emoji.h"
QHash<QString, QString> g_animatedEmojiMap;
@@ -133,10 +134,10 @@ static QVector<EmojiData> animations() {
QVector<EmojiCategory> builtinEmojiSet() {
return {
{"Smileys & People", smileys()},
{"Gestures", gestures()},
{"Hearts & Symbols", hearts()},
{"Animations", animations()},
{QCoreApplication::translate("ChatGui", "Smileys & People"), smileys()},
{QCoreApplication::translate("ChatGui", "Gestures"), gestures()},
{QCoreApplication::translate("ChatGui", "Hearts & Symbols"), hearts()},
{QCoreApplication::translate("ChatGui", "Animations"), animations()},
};
}
+15
View File
@@ -0,0 +1,15 @@
/* Выбор языка интерфейса независимо от регионального формата дат и чисел. */
#pragma once
#include <QString>
#include <QStringList>
#include <QLocale>
inline QString resolveGuiLanguage(const QString& preference, const QStringList& systemLanguages = QLocale::system().uiLanguages()) {
if (preference == QStringLiteral("en") || preference == QStringLiteral("ru")) return preference;
for (const QString& language : systemLanguages) {
const QString code = language.section('-', 0, 0).section('_', 0, 0).toLower();
if (code == QStringLiteral("en") || code == QStringLiteral("ru")) return code;
}
return QStringLiteral("en");
}
+5 -4
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "imageviewer_window.h"
#include "../../lib/debug_config.h"
#include <QPainter>
@@ -34,14 +35,14 @@ static QPointF wheelPos(const QWheelEvent* e) {
ImageViewerWindow::ImageViewerWindow(QWidget *parent)
: QWidget(parent)
{
setWindowTitle(QStringLiteral("Фото"));
setWindowTitle(QCoreApplication::translate("ChatGui", "Photo"));
setAttribute(Qt::WA_DeleteOnClose, false);
setMinimumSize(320, 240);
resize(800, 600);
setMouseTracking(true);
m_closeBtn = new QPushButton(QStringLiteral("✕"), this);
m_closeBtn->setToolTip(QStringLiteral("Закрыть"));
m_closeBtn->setToolTip(QCoreApplication::translate("ChatGui", "Close"));
m_closeBtn->setCursor(Qt::ArrowCursor);
m_closeBtn->setStyleSheet(
"QPushButton { background: rgba(0,0,0,0.5); color: white; border: none; "
@@ -56,7 +57,7 @@ ImageViewerWindow::ImageViewerWindow(QWidget *parent)
m_closeFade = new QPropertyAnimation(m_closeEffect, "opacity", this);
m_closeFade->setEasingCurve(QEasingCurve::InOutQuad);
m_hintLabel = new QLabel(QStringLiteral("Ctrl+колесо — зум"), this);
m_hintLabel = new QLabel(QCoreApplication::translate("ChatGui", "Ctrl+wheel — zoom"), this);
m_hintLabel->setAlignment(Qt::AlignCenter);
m_hintLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
m_hintLabel->setStyleSheet(
@@ -126,7 +127,7 @@ void ImageViewerWindow::paintEvent(QPaintEvent*) {
if (m_image.isNull()) {
p.setPen(QColor(150, 150, 150));
p.drawText(rect(), Qt::AlignCenter, QStringLiteral("Не удалось загрузить фото"));
p.drawText(rect(), Qt::AlignCenter, QCoreApplication::translate("ChatGui", "Failed to load photo"));
return;
}
+18 -17
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "inputbar.h"
#include "emojipanel.h"
#include "audiorecorder.h"
@@ -61,13 +62,13 @@ static QWidget *wrapBottomBtn(QPushButton *btn, int pad) {
}
QString InputBar::formatFileSize(qint64 bytes) {
if (bytes < 1024) return QString("%1B").arg(bytes);
if (bytes < 1024) return QCoreApplication::translate("ChatGui", "%1B").arg(bytes);
double kb = bytes / 1024.0;
if (kb < 1024.0) return QString("%1KB").arg(kb, 0, 'f', kb < 10.0 ? 1 : 0);
if (kb < 1024.0) return QCoreApplication::translate("ChatGui", "%1KB").arg(kb, 0, 'f', kb < 10.0 ? 1 : 0);
double mb = kb / 1024.0;
if (mb < 1024.0) return QString("%1MB").arg(mb, 0, 'f', mb < 10.0 ? 1 : 0);
if (mb < 1024.0) return QCoreApplication::translate("ChatGui", "%1MB").arg(mb, 0, 'f', mb < 10.0 ? 1 : 0);
double gb = mb / 1024.0;
return QString("%1GB").arg(gb, 0, 'f', 1);
return QCoreApplication::translate("ChatGui", "%1GB").arg(gb, 0, 'f', 1);
}
InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *parent)
@@ -108,7 +109,7 @@ InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *par
layout->setSpacing(4);
m_attachBtn = makeIconBtn(QIcon(":/icons/attach.png"),
QString::fromUtf8("Прикрепить"), this);
QCoreApplication::translate("ChatGui", "Attach"), this);
connect(m_attachBtn, &QPushButton::clicked, this, &InputBar::onAttachClicked);
layout->addWidget(wrapBottomBtn(m_attachBtn, 3), 0, Qt::AlignBottom);
@@ -126,15 +127,15 @@ InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *par
layout->addWidget(m_input, 1);
m_emojiBtn = makeIconBtn(QIcon(":/icons/emoji.png"),
QString::fromUtf8("Смайлики"), this);
QCoreApplication::translate("ChatGui", "Emoji"), this);
layout->addWidget(wrapBottomBtn(m_emojiBtn, 3), 0, Qt::AlignBottom);
m_pttBtn = makeIconBtn(QIcon(":/icons/mic.png"),
QString::fromUtf8("Голосовое сообщение (удерживайте)"), this);
QCoreApplication::translate("ChatGui", "Voice message (hold)"), this);
layout->addWidget(wrapBottomBtn(m_pttBtn, 3), 0, Qt::AlignBottom);
auto *sendBtn = makeIconBtn(QIcon(":/icons/send.png"),
QString::fromUtf8("Отправить"), this);
QCoreApplication::translate("ChatGui", "Send"), this);
sendBtn->setStyleSheet(sendBtn->styleSheet() +
"QPushButton { border: 1px solid palette(highlight); }");
layout->addWidget(sendBtn, 0, Qt::AlignBottom);
@@ -331,7 +332,7 @@ void InputBar::onPttReleased() {
void InputBar::onAttachClicked() {
if (m_channelIdForRecord.isEmpty()) return;
QString srcPath = QFileDialog::getOpenFileName(this, QString::fromUtf8("Прикрепить файл"));
QString srcPath = QFileDialog::getOpenFileName(this, QCoreApplication::translate("ChatGui", "Attach file"));
if (srcPath.isEmpty()) return;
QFileInfo fi(srcPath);
@@ -360,10 +361,10 @@ void InputBar::onAttachClicked() {
bool asVideo = false;
if (!asImage && QStringList{"mp4", "mov", "mkv", "avi", "webm", "m4v"}.contains(fi.suffix().toLower())) {
QMessageBox box(this);
box.setWindowTitle(QString::fromUtf8("Отправить видео"));
box.setText(QString::fromUtf8("Файл содержит видео. Как отправить?"));
QPushButton *fileBtn = box.addButton(QString::fromUtf8("Как файл"), QMessageBox::AcceptRole);
QPushButton *videoBtn = box.addButton(QString::fromUtf8("Как видео"), QMessageBox::DestructiveRole);
box.setWindowTitle(QCoreApplication::translate("ChatGui", "Send video"));
box.setText(QCoreApplication::translate("ChatGui", "The file contains video. How would you like to send it?"));
QPushButton *fileBtn = box.addButton(QCoreApplication::translate("ChatGui", "As a file"), QMessageBox::AcceptRole);
QPushButton *videoBtn = box.addButton(QCoreApplication::translate("ChatGui", "As a video"), QMessageBox::DestructiveRole);
box.exec();
asVideo = (box.clickedButton() == videoBtn);
if (!box.clickedButton() || (!asVideo && box.clickedButton() != fileBtn)) {
@@ -421,8 +422,8 @@ void InputBar::updatePreparation(const QString& target, const QByteArray& job, i
void InputBar::showPreparation() {
QString status;
if (!m_preparations.value(m_channelIdForRecord).isEmpty()) status = QString::fromUtf8("Подготовка медиа…");
else if (m_preparationErrors.contains(m_channelIdForRecord)) status = QString::fromUtf8("Не удалось подготовить вложение");
if (!m_preparations.value(m_channelIdForRecord).isEmpty()) status = QCoreApplication::translate("ChatGui", "Preparing media…");
else if (m_preparationErrors.contains(m_channelIdForRecord)) status = QCoreApplication::translate("ChatGui", "Failed to prepare attachment");
m_preparationLabel->setText(status);
m_preparationLabel->setVisible(!status.isEmpty());
}
@@ -442,7 +443,7 @@ void InputBar::showRecordOverlay() {
m_recordOverlay->setGeometry(m_input->geometry().adjusted(4, 4, -4, -4));
m_recordOverlay->show();
m_recordOverlay->raise();
m_recordLabel->setText(QString::fromUtf8("\xF0\x9F\x94\xB4 Recording 0:00"));
m_recordLabel->setText(QCoreApplication::translate("ChatGui", "🔴 Recording 0:00"));
if (m_levelBar) m_levelBar->setValue(0);
}
}
@@ -454,7 +455,7 @@ void InputBar::hideRecordOverlay() {
void InputBar::updateRecordingUI(int ms) {
if (m_recordLabel) {
int secs = ms / 1000;
m_recordLabel->setText(QString::fromUtf8("\xF0\x9F\x94\xB4 Recording %1:%2")
m_recordLabel->setText(QCoreApplication::translate("ChatGui", "🔴 Recording %1:%2")
.arg(secs / 60).arg(secs % 60, 2, 10, QChar('0')));
}
if (m_levelBar && m_recorder) {
+15 -14
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "invite_link.h"
#include "debug_ui.h"
#include <QByteArray>
@@ -61,13 +62,13 @@ QString encodeInviteLink(uint64_t channelId, const QByteArray& pubkey,
InviteData decodeInviteLink(const QString& link) {
InviteData d;
if (!link.startsWith(QLatin1String("utun://"))) {
d.error = QStringLiteral("invalid prefix (expected utun://)");
d.error = QCoreApplication::translate("ChatGui", "invalid prefix (expected utun://)");
GUI_ERROR("decodeInviteLink: invalid prefix");
return d;
}
QByteArray blob = QByteArray::fromBase64(link.mid(7).toLatin1());
if (blob.isEmpty()) {
d.error = QStringLiteral("base64 decode failed");
d.error = QCoreApplication::translate("ChatGui", "base64 decode failed");
GUI_ERROR("decodeInviteLink: base64 decode failed");
return d;
}
@@ -75,7 +76,7 @@ InviteData decodeInviteLink(const QString& link) {
int len = blob.size();
if (len < 11) {
d.error = QStringLiteral("blob too short (%1 bytes)").arg(len);
d.error = QCoreApplication::translate("ChatGui", "blob too short (%1 bytes)").arg(len);
GUI_ERROR("decodeInviteLink: blob too short (%d bytes)", len);
return d;
}
@@ -83,7 +84,7 @@ InviteData decodeInviteLink(const QString& link) {
uint8_t ver = data[off++];
if (ver != INVITE_LINK_VERSION && ver != 0x02 && ver != 0x01) {
d.error = QStringLiteral("unsupported version 0x%1").arg(ver, 2, 16, QLatin1Char('0'));
d.error = QCoreApplication::translate("ChatGui", "unsupported version 0x%1").arg(ver, 2, 16, QLatin1Char('0'));
GUI_ERROR("decodeInviteLink: unsupported version %d", ver);
return d;
}
@@ -91,13 +92,13 @@ InviteData decodeInviteLink(const QString& link) {
/* v2/v3: password field before channel_id */
if (ver == 0x02 || ver == INVITE_LINK_VERSION) {
if (off >= len) {
d.error = QStringLiteral("truncated at pass_len");
d.error = QCoreApplication::translate("ChatGui", "truncated at pass_len");
return d;
}
uint8_t plen = data[off++];
if (plen > 0) {
if (off + plen > len) {
d.error = QStringLiteral("truncated at password");
d.error = QCoreApplication::translate("ChatGui", "truncated at password");
return d;
}
d.password = QString::fromUtf8(reinterpret_cast<const char*>(data + off), plen);
@@ -107,7 +108,7 @@ InviteData decodeInviteLink(const QString& link) {
/* channel_id: uint64 big-endian */
if (off + 8 > len) {
d.error = QStringLiteral("truncated at channel_id");
d.error = QCoreApplication::translate("ChatGui", "truncated at channel_id");
GUI_ERROR("decodeInviteLink: truncated at channel_id");
return d;
}
@@ -118,7 +119,7 @@ InviteData decodeInviteLink(const QString& link) {
/* join_key: uint64 big-endian */
if (off + 8 > len) {
d.error = QStringLiteral("truncated at join_key");
d.error = QCoreApplication::translate("ChatGui", "truncated at join_key");
GUI_ERROR("decodeInviteLink: truncated at join_key");
return d;
}
@@ -129,15 +130,15 @@ InviteData decodeInviteLink(const QString& link) {
/* v3: reality-блок (node-global параметры) */
if (ver == INVITE_LINK_VERSION) {
if (off >= len) { d.error = QStringLiteral("truncated at reality"); return d; }
if (off >= len) { d.error = QCoreApplication::translate("ChatGui", "truncated at reality"); return d; }
if (data[off++] != 0) {
if (off + 8 + 32 + 3 + 1 > len) { d.error = QStringLiteral("truncated at reality params"); return d; }
if (off + 8 + 32 + 3 + 1 > len) { d.error = QCoreApplication::translate("ChatGui", "truncated at reality params"); return d; }
d.reality.shortId = QByteArray(reinterpret_cast<const char*>(data + off), 8); off += 8;
d.reality.serverPubkey = QByteArray(reinterpret_cast<const char*>(data + off), 32); off += 32;
d.reality.version = QByteArray(reinterpret_cast<const char*>(data + off), 3); off += 3;
uint8_t snlen = data[off++];
if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1;
if (off + snlen > len) { d.error = QStringLiteral("truncated at reality server_name"); return d; }
if (off + snlen > len) { d.error = QCoreApplication::translate("ChatGui", "truncated at reality server_name"); return d; }
d.reality.serverName = QString::fromUtf8(reinterpret_cast<const char*>(data + off), snlen);
off += snlen;
d.reality.has = true;
@@ -149,12 +150,12 @@ InviteData decodeInviteLink(const QString& link) {
uint8_t header = data[off++];
int cnt = (header & 0x03) + 1;
if (cnt < 1 || cnt > 4) {
d.error = QStringLiteral("invalid addr count %1").arg(cnt);
d.error = QCoreApplication::translate("ChatGui", "invalid addr count %1").arg(cnt);
GUI_ERROR("decodeInviteLink: invalid addr count %d", cnt);
return d;
}
if (off + 32 > len) {
d.error = QStringLiteral("truncated at pubkey");
d.error = QCoreApplication::translate("ChatGui", "truncated at pubkey");
GUI_ERROR("decodeInviteLink: truncated at pubkey");
return d;
}
@@ -168,7 +169,7 @@ InviteData decodeInviteLink(const QString& link) {
bool isV6 = header & (1 << (2 + j));
int ipLen = isV6 ? 16 : 4;
if (off + 2 + ipLen + 2 > len) {
d.error = QStringLiteral("truncated at addr %1 of block").arg(j);
d.error = QCoreApplication::translate("ChatGui", "truncated at addr %1 of block").arg(j);
GUI_ERROR("decodeInviteLink: truncated at addr %d of block", j);
return d;
}
+21 -20
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "inviteby.h"
#include "invite_link.h"
#include "debug_ui.h"
@@ -21,14 +22,14 @@ extern "C" {
InviteByLinkDialog::InviteByLinkDialog(const QString& channelId, UtunNode* node, QWidget* parent)
: QDialog(parent), m_channelId(channelId), m_node(node)
{
setWindowTitle(QString::fromUtf8("Пригласить в канал %1").arg(channelId));
setWindowTitle(QCoreApplication::translate("ChatGui", "Invite to channel %1").arg(channelId));
setMinimumWidth(420);
setModal(true);
auto *layout = new QVBoxLayout(this);
layout->setSpacing(12);
auto *title = new QLabel(QString::fromUtf8("Вставьте utun:// ссылку приглашаемого узла:"), this);
auto *title = new QLabel(QCoreApplication::translate("ChatGui", "Paste the invited node's utun:// link:"), this);
title->setStyleSheet("font-weight: bold; font-size: 12px;");
layout->addWidget(title);
@@ -46,7 +47,7 @@ InviteByLinkDialog::InviteByLinkDialog(const QString& channelId, UtunNode* node,
auto *btnLayout = new QHBoxLayout();
btnLayout->addStretch();
m_inviteSendBtn = new QPushButton(QString::fromUtf8("Пригласить"), this);
m_inviteSendBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Invite"), this);
m_inviteSendBtn->setStyleSheet(
"QPushButton { border: none; border-radius: 4px;"
" padding: 5px 14px; font-size: 12px; font-weight: bold;"
@@ -83,14 +84,14 @@ void InviteByLinkDialog::onInviteLinkTextChanged() {
}
if (!link.startsWith(QLatin1String("utun://"))) {
m_inviteStatusLabel->setStyleSheet("color: gray; font-size: 11px; padding: 2px 0;");
m_inviteStatusLabel->setText(QString::fromUtf8("Ожидается utun:// ссылка..."));
m_inviteStatusLabel->setText(QCoreApplication::translate("ChatGui", "Expected a utun:// link..."));
return;
}
InviteData d = decodeInviteLink(link);
if (!d.error.isEmpty()) {
m_inviteStatusLabel->setStyleSheet("color: gray; font-size: 11px; padding: 2px 0;");
m_inviteStatusLabel->setText(QString::fromUtf8("Неверная ссылка: ") + d.error);
m_inviteStatusLabel->setText(QCoreApplication::translate("ChatGui", "Invalid link: ") + d.error);
return;
}
@@ -112,9 +113,9 @@ void InviteByLinkDialog::onInviteLinkTextChanged() {
QString::fromLatin1(d.addrs[i].address.left(4).toHex())).arg(d.addrs[i].port);
}
}
if (d.addrs.size() > 3) addrInfo += QString(" +%1 more").arg(d.addrs.size() - 3);
if (d.addrs.size() > 3) addrInfo += QCoreApplication::translate("ChatGui", " +%1 more").arg(d.addrs.size() - 3);
QString diag = QString::fromUtf8("Ссылка: узел 0x%1 ключ %2, %3 адр. (%4)\nПриглашение в канал: %5")
QString diag = QCoreApplication::translate("ChatGui", "Link: node 0x%1 key %2, %3 addresses (%4)\nInvitation to channel: %5")
.arg(targetNodeId, 16, 16, QChar('0'))
.arg(pkShort)
.arg(d.addrs.size())
@@ -123,7 +124,7 @@ void InviteByLinkDialog::onInviteLinkTextChanged() {
if (myNodeId != 0 && myNodeId == targetNodeId) {
m_inviteStatusLabel->setStyleSheet("color: #E67E22; font-size: 11px; padding: 2px 0;");
m_inviteStatusLabel->setText(diag + QString::fromUtf8(" ⚠ Это ваша собственная ссылка"));
m_inviteStatusLabel->setText(diag + QCoreApplication::translate("ChatGui", " ⚠ This is your own link"));
} else {
m_inviteStatusLabel->setStyleSheet("color: gray; font-size: 11px; padding: 2px 0;");
m_inviteStatusLabel->setText(diag);
@@ -142,21 +143,21 @@ void InviteByLinkDialog::onSendInvite() {
if (m_inviteSending) return;
QString link = m_inviteLinkEdit->text().trimmed();
if (link.isEmpty()) {
setInviteStatus(QString::fromUtf8("Вставьте utun:// ссылку узла."), true);
setInviteStatus(QCoreApplication::translate("ChatGui", "Paste the node's utun:// link."), true);
return;
}
InviteData d = decodeInviteLink(link);
if (!d.error.isEmpty()) {
setInviteStatus(QString::fromUtf8("Неверная ссылка: ") + d.error, true);
setInviteStatus(QCoreApplication::translate("ChatGui", "Invalid link: ") + d.error, true);
return;
}
if (d.pubkey.size() != 32) {
setInviteStatus(QString::fromUtf8("Pubkey не 32 байта."), true);
setInviteStatus(QCoreApplication::translate("ChatGui", "The public key must be 32 bytes."), true);
return;
}
if (d.addrs.isEmpty()) {
setInviteStatus(QString::fromUtf8("Нет адресов."), true);
setInviteStatus(QCoreApplication::translate("ChatGui", "No addresses."), true);
return;
}
@@ -166,7 +167,7 @@ void InviteByLinkDialog::onSendInvite() {
uint64_t myNodeId = m_node ? m_node->nodeIdHex().toULongLong(nullptr, 16) : 0;
if (myNodeId != 0 && myNodeId == targetNodeId) {
GUI_WARN("invite_by_link: self-invite rejected — link contains own pubkey, node=0x%016llx", (unsigned long long)myNodeId);
setInviteStatus(QString::fromUtf8("Ошибка: это ваша собственная ссылка (нельзя пригласить себя)"), true);
setInviteStatus(QCoreApplication::translate("ChatGui", "Error: this is your own link (you cannot invite yourself)"), true);
return;
}
}
@@ -186,27 +187,27 @@ void InviteByLinkDialog::onSendInvite() {
(const uint8_t*)d.pubkey.constData(),
(const uint8_t*)addrsBuf.constData(), d.addrs.size(), addrsBuf.size());
setInviteStatus(QString::fromUtf8("Приглашение отправлено, ожидание ответа..."));
setInviteStatus(QCoreApplication::translate("ChatGui", "Invitation sent, waiting for a response..."));
}
void InviteByLinkDialog::onConnectResult(uint64_t nodeId, uint64_t channelId, int result) {
if (!m_inviteSending || nodeId != m_inviteTargetNodeId) return;
if (result == 0) {
setInviteStatus(QString::fromUtf8("Узел присоединился к каналу."), false);
setInviteStatus(QCoreApplication::translate("ChatGui", "The node has joined the channel."), false);
QTimer::singleShot(400, this, &QDialog::accept);
} else {
m_inviteSending = false;
m_inviteSendBtn->setEnabled(true);
m_inviteSendBtn->setFocus();
const char* errMsg = "Неизвестная ошибка.";
QString errMsg = QCoreApplication::translate("ChatGui", "Unknown error.");
switch (result) {
case -1: errMsg = "Канал не найден."; break;
case 2: errMsg = "Таймаут — узел не ответил."; break;
case -7: errMsg = "Узел не инициализирован."; break;
case -1: errMsg = QCoreApplication::translate("ChatGui", "Channel not found."); break;
case 2: errMsg = QCoreApplication::translate("ChatGui", "Timed out — the node did not respond."); break;
case -7: errMsg = QCoreApplication::translate("ChatGui", "Node not initialized."); break;
default: break;
}
setInviteStatus(QString::fromUtf8("Ошибка: %1").arg(QString::fromUtf8(errMsg)), true);
setInviteStatus(QCoreApplication::translate("ChatGui", "Error: %1").arg(errMsg), true);
}
}
+12 -10
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "invitedialog.h"
#include "qrcode_utils.h"
#include "../db/db_manager.h"
@@ -26,17 +27,18 @@ ShareInviteDialog::ShareInviteDialog(const QString& channelId, DbManager* db, QW
, m_channelId(channelId)
, m_db(db)
{
setWindowTitle(QString::fromUtf8("Пригласительная ссылка"));
setWindowTitle(QCoreApplication::translate("ChatGui", "Invite link"));
setMinimumSize(420, 560);
setModal(true);
auto *layout = new QVBoxLayout(this);
layout->setSpacing(12);
auto* expiry = new QLabel(QStringLiteral("Ссылка действует %1 минут с момента создания.").arg(JOIN_KEY_TTL_SECONDS / 60), this);
auto* expiry = new QLabel(QCoreApplication::translate("ChatGui",
"The link expires %1 minutes after creation.").arg(JOIN_KEY_TTL_SECONDS / 60), this);
expiry->setWordWrap(true);
layout->addWidget(expiry);
auto *nodeLabel = new QLabel(QString::fromUtf8("Узел входа:"), this);
auto *nodeLabel = new QLabel(QCoreApplication::translate("ChatGui", "Entry node:"), this);
nodeLabel->setStyleSheet("font-size: 12px; color: palette(window-text);");
m_nodeCombo = new QComboBox(this);
@@ -52,10 +54,10 @@ ShareInviteDialog::ShareInviteDialog(const QString& channelId, DbManager* db, QW
m_linkEdit = new QLineEdit(this);
m_linkEdit->setReadOnly(true);
m_linkEdit->setPlaceholderText(QString::fromUtf8("Генерация ссылки..."));
m_linkEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Generating link..."));
m_linkEdit->setStyleSheet("padding: 6px; font-family: monospace;");
auto *copyBtn = new QPushButton(QString::fromUtf8("Копировать"), this);
auto *copyBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Copy"), this);
connect(copyBtn, &QPushButton::clicked, this, [this]() {
QClipboard* cb = QApplication::clipboard();
cb->setText(m_linkEdit->text(), QClipboard::Clipboard);
@@ -125,7 +127,7 @@ void ShareInviteDialog::requestLink(uint64_t targetNodeId) {
req->request_id = m_requestId = ++nextRequestId;
req->target_node_id = targetNodeId;
m_linkEdit->setText(QString());
m_linkEdit->setPlaceholderText(QString::fromUtf8("Генерация ссылки..."));
m_linkEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Generating link..."));
m_qrLabel->clear();
gui_bridge_post_uasync_fn(chat_invite_build_link_trampoline, req);
}
@@ -151,7 +153,7 @@ void ShareInviteDialog::onCandidates(const char* chId, int chIdLen, const uint8_
if (autoId == 0) {
m_linkEdit->setText(QString());
m_linkEdit->setPlaceholderText(QString::fromUtf8("Не удалось определить узел (см. лог)"));
m_linkEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Failed to identify the node (see log)"));
return;
}
@@ -167,7 +169,7 @@ void ShareInviteDialog::onCandidates(const char* chId, int chIdLen, const uint8_
m_nodeCombo->clear();
/* пункт 0 — узел, найденный авто-выбором */
m_nodeCombo->addItem(nodeLabel(autoId) + QString::fromUtf8(" (auto)"),
m_nodeCombo->addItem(nodeLabel(autoId) + QCoreApplication::translate("ChatGui", " (auto)"),
QVariant::fromValue((qulonglong)autoId));
/* остальные узлы (пропуская авто), свой помечаем (self) */
@@ -179,7 +181,7 @@ void ShareInviteDialog::onCandidates(const char* chId, int chIdLen, const uint8_
if (nid == autoId) continue;
QString label = nodeLabel(nid);
if (m_db && nid == m_db->myNodeId())
label += QString::fromUtf8(" (self)");
label += QCoreApplication::translate("ChatGui", " (self)");
m_nodeCombo->addItem(label, QVariant::fromValue((qulonglong)nid));
}
@@ -193,7 +195,7 @@ void ShareInviteDialog::onLinkReady(const char* chId, int chIdLen, uint64_t requ
if (cid != m_channelId) return;
if (!link || link[0] == '\0') {
m_linkEdit->setText(QString());
m_linkEdit->setPlaceholderText(QString::fromUtf8("Не удалось собрать ссылку (см. лог)"));
m_linkEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Failed to build the link (see log)"));
m_qrLabel->clear();
return;
}
+23 -21
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "joindialog.h"
#include "invite_link.h"
#include "debug_ui.h"
@@ -23,7 +24,7 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
, m_node(node)
, m_db(db)
{
setWindowTitle("Join Channel");
setWindowTitle(QCoreApplication::translate("ChatGui", "Join Channel"));
setMinimumWidth(420);
setModal(true);
@@ -31,7 +32,7 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
layout->setSpacing(12);
layout->setContentsMargins(20, 16, 20, 16);
auto *titleLabel = new QLabel("Paste invite link:", this);
auto *titleLabel = new QLabel(QCoreApplication::translate("ChatGui", "Paste invite link:"), this);
titleLabel->setStyleSheet("font-weight: bold; font-size: 13px;");
layout->addWidget(titleLabel);
@@ -41,8 +42,9 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
"QLineEdit { border: 1px solid palette(mid); border-radius: 6px;"
" padding: 8px 12px; font-size: 13px; background: palette(base); }");
layout->addWidget(m_linkEdit);
auto* expiry = new QLabel(QStringLiteral("Приглашение действует 10 минут с момента создания.\n"
"Если срок истёк, попросите отправителя создать новую ссылку."), this);
auto* expiry = new QLabel(QCoreApplication::translate("ChatGui",
"The invitation expires 10 minutes after creation.\nIf it has expired, ask the sender to "
"create a new link."), this);
expiry->setWordWrap(true);
layout->addWidget(expiry);
@@ -57,8 +59,8 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
btnLayout->setSpacing(8);
btnLayout->addStretch();
m_cancelBtn = new QPushButton("Cancel", this);
m_cancelBtn->setFixedWidth(90);
m_cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), this);
m_cancelBtn->setMinimumWidth(90);
m_cancelBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
@@ -66,8 +68,8 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
connect(m_cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
btnLayout->addWidget(m_cancelBtn);
m_connectBtn = new QPushButton("Connect", this);
m_connectBtn->setFixedWidth(90);
m_connectBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Connect"), this);
m_connectBtn->setMinimumWidth(90);
m_connectBtn->setDefault(true);
m_connectBtn->setStyleSheet(
"QPushButton { border: none; border-radius: 4px;"
@@ -113,7 +115,7 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
: QString("?%1").arg(a.proto);
addrList.append(QString("%1:%2 (%3)").arg(addrStr).arg(a.port).arg(protoName));
}
setStatus(QString("Node: 0x%1 | %2")
setStatus(QCoreApplication::translate("ChatGui", "Node: 0x%1 %2")
.arg(nodeId, 16, 16, QLatin1Char('0'))
.arg(addrList.join(", ")));
});
@@ -149,22 +151,22 @@ void JoinDialog::onConnectClicked() {
if (m_connecting) return;
QString link = m_linkEdit->text().trimmed();
if (link.isEmpty()) {
setStatus("Enter an invite link.", true);
setStatus(QCoreApplication::translate("ChatGui", "Enter an invite link."), true);
return;
}
setStatus("Parsing link...");
setStatus(QCoreApplication::translate("ChatGui", "Parsing link..."));
InviteData d = decodeInviteLink(link);
if (!d.error.isEmpty()) {
setStatus("Invalid invite link: " + d.error, true);
setStatus(QCoreApplication::translate("ChatGui", "Invalid invite link: ") + d.error, true);
return;
}
if (d.pubkey.size() != 32) {
setStatus(QString("Invalid invite link: pubkey is %1 bytes (need 32)").arg(d.pubkey.size()), true);
setStatus(QCoreApplication::translate("ChatGui", "Invalid invite link: pubkey is %1 bytes (need 32)").arg(d.pubkey.size()), true);
return;
}
if (d.addrs.isEmpty()) {
setStatus("No peer addresses in the invite link.", true);
setStatus(QCoreApplication::translate("ChatGui", "No peer addresses in the invite link."), true);
return;
}
@@ -190,7 +192,7 @@ void JoinDialog::onConnectClicked() {
m_connectBtn->setEnabled(false);
m_linkEdit->setReadOnly(true);
setStatus("Connecting to peer...");
setStatus(QCoreApplication::translate("ChatGui", "Connecting to peer..."));
QByteArray addrsBuf = serializeInviteAddrs(d);
@@ -206,7 +208,7 @@ void JoinDialog::onConnectResult(uint64_t nodeId, uint64_t channelId, int result
if (!m_connecting || nodeId != m_pendingNodeId || channelId != m_pendingChannelId) return;
if (result == 0) {
setStatus("Connected!");
setStatus(QCoreApplication::translate("ChatGui", "Connected!"));
QString cid = QString::number(m_pendingChannelId);
emit joined(cid);
accept();
@@ -216,13 +218,13 @@ void JoinDialog::onConnectResult(uint64_t nodeId, uint64_t channelId, int result
m_linkEdit->setReadOnly(false);
m_connectBtn->setFocus();
const char* errMsg = "Unknown error.";
QString errMsg = QCoreApplication::translate("ChatGui", "Unknown error.");
switch (result) {
case -1: errMsg = "Channel not found."; break;
case 2: errMsg = "Connection timed out."; break;
case -7: errMsg = "Node not initialized."; break;
case -1: errMsg = QCoreApplication::translate("ChatGui", "Channel not found."); break;
case 2: errMsg = QCoreApplication::translate("ChatGui", "Connection timed out."); break;
case -7: errMsg = QCoreApplication::translate("ChatGui", "Node not initialized."); break;
default: break;
}
setStatus(QString("Connection failed: %1").arg(errMsg), true);
setStatus(QCoreApplication::translate("ChatGui", "Connection failed: %1").arg(errMsg), true);
}
}
+30 -11
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include <QApplication>
#include <QIcon>
#include <QPalette>
@@ -6,6 +7,8 @@
#include <QFile>
#include <QLockFile>
#include <QMessageBox>
#include <QTranslator>
#include "gui_language.h"
#include <cstdio>
#ifdef _WIN32
#include <windows.h>
@@ -75,13 +78,11 @@ int main(int argc, char *argv[]) {
app.setDesktopFileName("org.utun.vibechat");
app.setWindowIcon(QIcon(":/icons/app_icon.png"));
app.setQuitOnLastWindowClosed(false);
debug_config_init();
/* single-instance: лог/БД/сокеты конфликтуют при втором запуске */
/* Блокировка до любых изменений конфига/БД; предупреждение после загрузки языка. */
QLockFile lock(QCoreApplication::applicationDirPath() + "/vibechat.lock");
if (!lock.tryLock()) {
QMessageBox::warning(nullptr, "vibechat", "vibechat is already running.");
return 1;
}
const bool locked = lock.tryLock();
app.setStyle(QStyleFactory::create("Fusion"));
QPalette p;
@@ -105,10 +106,31 @@ int main(int argc, char *argv[]) {
QString cfgDir = QCoreApplication::applicationDirPath();
QString cfgPath = cfgDir + "/vibechat.cfg";
/* миграция: старый конфиг chatgui.cfg → vibechat.cfg */
if (!QFile::exists(cfgPath) && QFile::exists(cfgDir + "/chatgui.cfg"))
if (locked && !QFile::exists(cfgPath) && QFile::exists(cfgDir + "/chatgui.cfg"))
QFile::rename(cfgDir + "/chatgui.cfg", cfgPath);
NodeConfig cfg(cfgPath);
if (cfg.exists() && !cfg.load()) {
qCritical("Cannot open/read config file: %s", qPrintable(cfgPath));
return 1;
}
QTranslator guiTranslator, qtTranslator;
const QString language = resolveGuiLanguage(cfg.language());
if (!guiTranslator.load(":/translations/chatgui_" + language + ".qm") || !app.installTranslator(&guiTranslator)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI language: failed to load embedded catalog for %s", qPrintable(language));
return 1;
}
if (language == "ru") {
if (!qtTranslator.load(QStringLiteral(":/translations/qtbase_ru.qm")) || !app.installTranslator(&qtTranslator)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI language: failed to load embedded Qt Russian catalog");
return 1;
}
}
if (!locked) {
QMessageBox::warning(nullptr, "vibechat", QCoreApplication::translate("ChatGui", "vibechat is already running."));
return 1;
}
bool needSave = false;
if (!cfg.exists()) {
@@ -117,10 +139,6 @@ int main(int argc, char *argv[]) {
return 1;
}
} else {
if (!cfg.load()) {
qCritical("Cannot open/read config file: %s", qPrintable(cfgPath));
return 1;
}
if (cfg.hasErrors()) {
for (const auto& err : cfg.errors())
fprintf(stderr, "CONFIG ERROR: %s\n", qPrintable(err));
@@ -154,12 +172,13 @@ int main(int argc, char *argv[]) {
/* ранняя инициализация лога в GUI-потоке: SoundManager::init() вызывается в
* конструкторе MainWindow ДО старта utun-потока, поэтому без этого его диагностика
* (ma_engine_init и т.п.) в лог-файл не попадёт. */
debug_config_init();
debug_enable_function_name(0);
debug_enable_file_output(debugFile.toUtf8().constData(), 0);
QString lvl = cfg.debugLevel().trimmed();
if (lvl.isEmpty()) lvl = QStringLiteral("info");
debug_apply_global_level(lvl.toUtf8().constData());
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "GUI language: preference=%s system=%s selected=%s",
qPrintable(cfg.language()), qPrintable(QLocale::system().uiLanguages().join(',')), qPrintable(language));
DbManager db;
MainWindow w(nullptr, &db, cfgPath, dbPath, debugFile,
+28 -15
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "mainwindow.h"
#include "channellist.h"
#include "messagelist.h"
@@ -437,7 +438,7 @@ void MainWindow::setupBridgeCallbacks() {
uint64_t from; memcpy(&from, data + 1 + chLen, 8);
s_mainWindow->m_trayIcon->showMessage(
"vibechat",
QStringLiteral("You are now a channel co-owner.\nReceived admin key for channel %1 from 0x%2")
QCoreApplication::translate("ChatGui", "You are now a channel co-owner.\nReceived admin key for channel %1 from 0x%2")
.arg(chId).arg(QString::number(from, 16)),
QSystemTrayIcon::Information, 5000);
if (s_mainWindow->m_currentChannelId == chId)
@@ -604,7 +605,7 @@ void MainWindow::onCallIncoming(const QString& chId, quint64 caller, quint64 cal
SoundManager::instance()->playLoop("call_incoming");
if (!isVisible() || isMinimized()) {
m_trayIcon->showMessage("vibechat", QStringLiteral("Incoming call from %1").arg(name),
m_trayIcon->showMessage("vibechat", QCoreApplication::translate("ChatGui", "Incoming call from %1").arg(name),
QSystemTrayIcon::Information, 5000);
}
@@ -687,7 +688,9 @@ void MainWindow::onDmConvUpdated(const QString& convId) {
void MainWindow::onInviteToPmRequested(const QString& source, quint64 peer, const QString& name) {
if (!m_db || !m_db->isOpen() || !m_node || !m_node->isRunning()) {
GUI_ERROR("invite PM: node unavailable");
QMessageBox::warning(this, QStringLiteral("Приглашение"), QStringLiteral("Ядро не запущено.")); return;
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
"Invitation"), QCoreApplication::translate("ChatGui",
"The core is not running.")); return;
}
if (!peer || peer == m_db->myNodeId()) { GUI_ERROR("invite PM: invalid recipient"); return; }
DmInviteDialog dialog(m_db, peer, name, source, this);
@@ -702,11 +705,15 @@ void MainWindow::onMessageLinkActivated(const QString& link) {
const QUrl url(link, QUrl::StrictMode);
if (!url.isValid() || url.host().isEmpty() || (url.scheme() != "http" && url.scheme() != "https")) {
GUI_ERROR("message link: invalid web address bytes=%d", (int)link.size());
QMessageBox::warning(this, tr("Ссылка"), tr("Некорректная ссылка.")); return;
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
"Link"), QCoreApplication::translate("ChatGui",
"Invalid link.")); return;
}
if (!QDesktopServices::openUrl(url)) {
GUI_ERROR("message link: external open failed scheme=%s host=%s", qPrintable(url.scheme()), qPrintable(url.host()));
QMessageBox::warning(this, tr("Ссылка"), tr("Не удалось открыть ссылку. Проверьте наличие браузера.")); return;
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
"Link"), QCoreApplication::translate("ChatGui",
"Failed to open the link. Check that a browser is installed.")); return;
}
GUI_INFO("message link: external open requested scheme=%s host=%s", qPrintable(url.scheme()), qPrintable(url.host()));
return;
@@ -714,11 +721,15 @@ void MainWindow::onMessageLinkActivated(const QString& link) {
const auto invite = decodeInviteLink(link);
if (!invite.error.isEmpty()) {
GUI_ERROR("invite link: invalid error=%s", qPrintable(invite.error));
QMessageBox::warning(this, QStringLiteral("Приглашение"), QStringLiteral("Некорректная ссылка: ") + invite.error); return;
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
"Invitation"), QCoreApplication::translate("ChatGui",
"Invalid link: ") + invite.error); return;
}
if (!m_db || !m_db->isOpen() || !m_node || !m_node->isRunning()) {
GUI_ERROR("invite link: node unavailable");
QMessageBox::warning(this, QStringLiteral("Приглашение"), QStringLiteral("Ядро не запущено.")); return;
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
"Invitation"), QCoreApplication::translate("ChatGui",
"The core is not running.")); return;
}
const QString channelId = QString::number(invite.channelId);
for (const auto& channel : m_db->getChannels()) {
@@ -751,19 +762,19 @@ void MainWindow::setupTray() {
m_trayIcon->setToolTip("vibechat");
m_trayMenu = new QMenu(this);
QAction *settingsAction = m_trayMenu->addAction("Settings");
QAction *settingsAction = m_trayMenu->addAction(QCoreApplication::translate("ChatGui", "Settings"));
connect(settingsAction, &QAction::triggered, this, &MainWindow::showSettings);
m_trayMenu->addSeparator();
QAction *showAction = m_trayMenu->addAction("Show");
QAction *showAction = m_trayMenu->addAction(QCoreApplication::translate("ChatGui", "Show"));
connect(showAction, &QAction::triggered, this, [this]() {
show();
raise();
activateWindow();
});
QAction *quitAction = m_trayMenu->addAction("Quit");
QAction *quitAction = m_trayMenu->addAction(QCoreApplication::translate("ChatGui", "Quit"));
connect(quitAction, &QAction::triggered, this, &MainWindow::requestShutdown);
m_trayIcon->setContextMenu(m_trayMenu);
@@ -778,7 +789,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "MainWindow: closeEvent — hiding to tray");
saveWindowGeometry();
hide();
m_trayIcon->showMessage("vibechat", "Application minimized to tray.",
m_trayIcon->showMessage("vibechat", QCoreApplication::translate("ChatGui", "Application minimized to tray."),
QSystemTrayIcon::Information, 2000);
event->ignore();
}
@@ -814,7 +825,7 @@ void MainWindow::showShareInviteDialog(const QString& channelId) {
void MainWindow::showInviteByLinkDialog(const QString& channelId) {
if (!m_node || !m_node->isRunning()) {
QMessageBox::warning(this, "Error", "Node is not running.");
QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Error"), QCoreApplication::translate("ChatGui", "Node is not running."));
return;
}
InviteByLinkDialog dlg(channelId, m_node, this);
@@ -823,7 +834,7 @@ void MainWindow::showInviteByLinkDialog(const QString& channelId) {
void MainWindow::showJoinDialog() {
if (!m_node || !m_node->isRunning()) {
QMessageBox::warning(this, "Error", "Node is not running.");
QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Error"), QCoreApplication::translate("ChatGui", "Node is not running."));
return;
}
@@ -953,11 +964,13 @@ void MainWindow::reloadChannels() {
void MainWindow::onCreateGroupRequested() {
if (!m_node || !m_node->isRunning()) {
QMessageBox::warning(this, "Error", "Node is not running.");
QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Error"), QCoreApplication::translate("ChatGui", "Node is not running."));
return;
}
if (!gui_bridge_is_uasync_ready()) {
QMessageBox::warning(this, "Error", "uTun thread is not ready yet. Please wait and try again.");
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
"Error"), QCoreApplication::translate("ChatGui",
"uTun thread is not ready yet. Please wait and try again."));
return;
}
+5 -2
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "memberlistmodel.h"
#include "../transport/gui_bridge.h"
#include <QPainter>
@@ -47,9 +48,11 @@ QVariant MemberListModel::data(const QModelIndex& index, int role) const {
return sub;
}
if (role == Qt::ToolTipRole)
return QString(it->online ? "online" : "offline") + " | 0x" +
return (it->online ? QCoreApplication::translate("ChatGui",
"online") : QCoreApplication::translate("ChatGui",
"offline")) + " | 0x" +
QString("%1").arg(it->nodeId, 16, 16, QChar('0')) +
((it->flags & CHAT_MEMBER_FLAG_RADIO) ? tr(" | Radio enabled") : QString());
((it->flags & CHAT_MEMBER_FLAG_RADIO) ? QCoreApplication::translate("ChatGui", " · Radio enabled") : QString());
if (role == Qt::UserRole + 2) return it->isSelf;
if (role == Qt::UserRole + 3) return it->flags;
if (role == Qt::UserRole + 4) return (bool)it->storage;
+20 -19
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "memberpropsdialog.h"
#include "../db/db_manager.h"
#include "../transport/gui_bridge.h"
@@ -25,7 +26,7 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
, m_flags(flags)
, m_transferBtn(nullptr)
{
setWindowTitle("Member Properties");
setWindowTitle(QCoreApplication::translate("ChatGui", "Member Properties"));
setMinimumWidth(340);
setModal(true);
@@ -80,52 +81,52 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
layout->setContentsMargins(20, 16, 20, 16);
auto* title = new QLabel(
QString("Node: 0x%1").arg(nodeId, 16, 16, QChar('0')) +
QCoreApplication::translate("ChatGui", "Node: 0x%1").arg(nodeId, 16, 16, QChar('0')) +
(memberName.isEmpty() ? "" : QString(" (%1)").arg(memberName)),
this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
layout->addWidget(title);
m_nickEdit = new QLineEdit(this);
m_nickEdit->setPlaceholderText("Local nickname...");
m_nickEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Local nickname..."));
m_nickEdit->setText(curNick);
m_nickEdit->setEnabled(!isOwn);
layout->addWidget(m_nickEdit);
m_adminCb = new QCheckBox("Admin", this);
m_adminCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Admin"), this);
m_adminCb->setChecked(isAdmin);
m_adminCb->setEnabled(haveKey);
layout->addWidget(m_adminCb);
m_superCb = new QCheckBox("Supernode", this);
m_superCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Supernode"), this);
m_superCb->setChecked(isSuper);
m_superCb->setEnabled(haveKey);
layout->addWidget(m_superCb);
m_storageCb = new QCheckBox("Storage (priority attachment keeper)", this);
m_storageCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Storage (priority attachment keeper)"), this);
m_storageCb->setChecked(curStorage);
m_storageCb->setEnabled(haveKey);
layout->addWidget(m_storageCb);
m_verifiedCb = new QCheckBox("Verified", this);
m_verifiedCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Verified"), this);
m_verifiedCb->setChecked(curVerified);
m_verifiedCb->setEnabled(haveKey);
layout->addWidget(m_verifiedCb);
m_deletedCb = new QCheckBox("Deleted", this);
m_deletedCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Deleted"), this);
m_deletedCb->setChecked(curDeleted);
m_deletedCb->setEnabled(haveKey);
layout->addWidget(m_deletedCb);
if (!haveKey) {
auto* hint = new QLabel("(Admin/Super require channel private key)", this);
auto* hint = new QLabel(QCoreApplication::translate("ChatGui", "(Admin/Super require channel private key)"), this);
hint->setStyleSheet("color: gray; font-size: 11px;");
layout->addWidget(hint);
}
m_transferBtn = new QPushButton("Transfer admin rights", this);
m_transferBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Transfer admin rights"), this);
m_transferBtn->setEnabled(haveKey && !isOwn);
m_transferBtn->setToolTip(QStringLiteral("Send the channel private key to this member (encrypted + signed)"));
m_transferBtn->setToolTip(QCoreApplication::translate("ChatGui", "Send the channel private key to this member (encrypted + signed)"));
m_transferBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
@@ -139,8 +140,8 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
auto* btnLayout = new QHBoxLayout();
btnLayout->addStretch();
auto* cancelBtn = new QPushButton("Cancel", this);
cancelBtn->setFixedWidth(90);
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), this);
cancelBtn->setMinimumWidth(90);
cancelBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
@@ -148,8 +149,8 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
btnLayout->addWidget(cancelBtn);
auto* saveBtn = new QPushButton("Save", this);
saveBtn->setFixedWidth(90);
auto* saveBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Save"), this);
saveBtn->setMinimumWidth(90);
saveBtn->setDefault(true);
saveBtn->setStyleSheet(
"QPushButton { border: none; border-radius: 4px;"
@@ -214,10 +215,10 @@ void MemberPropsDialog::onTransferAdmin() {
QString name = m_memberName.isEmpty() ? QString("0x%1").arg(m_nodeId, 16, 16, QChar('0')) : m_memberName;
QMessageBox::StandardButton answer = QMessageBox::question(
this,
QStringLiteral("Transfer admin rights"),
QStringLiteral("Send the channel private key to \"%1\"?\n\n"
"The key is transferred encrypted and signed. The recipient will become a co-owner "
"and will be able to manage members.").arg(name),
QCoreApplication::translate("ChatGui", "Transfer admin rights"),
QCoreApplication::translate("ChatGui",
"Send the channel private key to \"%1\"?\n\nThe key is transferred encrypted and signed. The "
"recipient will become a co-owner and will be able to manage members.").arg(name),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
+11 -8
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "messagedelegate.h"
#include "message_links.h"
#include "chatview.h"
@@ -334,20 +335,20 @@ static int measureTextHeight(const QFont &font, const QString &text, int width,
}
static QString formatFileSize(qint64 bytes) {
if (bytes < 1024) return QString("%1B").arg(bytes);
if (bytes < 1024) return QCoreApplication::translate("ChatGui", "%1B").arg(bytes);
double kb = bytes / 1024.0;
if (kb < 1024.0) return QString("%1KB").arg(kb, 0, 'f', kb < 10.0 ? 1 : 0);
if (kb < 1024.0) return QCoreApplication::translate("ChatGui", "%1KB").arg(kb, 0, 'f', kb < 10.0 ? 1 : 0);
double mb = kb / 1024.0;
if (mb < 1024.0) return QString("%1MB").arg(mb, 0, 'f', mb < 10.0 ? 1 : 0);
if (mb < 1024.0) return QCoreApplication::translate("ChatGui", "%1MB").arg(mb, 0, 'f', mb < 10.0 ? 1 : 0);
double gb = mb / 1024.0;
return QString("%1GB").arg(gb, 0, 'f', 1);
return QCoreApplication::translate("ChatGui", "%1GB").arg(gb, 0, 'f', 1);
}
static QString formatBitrate(qint64 bytes, qint64 durMs) {
if (bytes <= 0 || durMs <= 0) return QString();
qint64 kbps = bytes * 8 / durMs;
if (kbps >= 1000) return QString("%1Mbps").arg(kbps / 1000.0, 0, 'f', 1);
return QString("%1kbps").arg(kbps);
if (kbps >= 1000) return QCoreApplication::translate("ChatGui", "%1Mbps").arg(kbps / 1000.0, 0, 'f', 1);
return QCoreApplication::translate("ChatGui", "%1kbps").arg(kbps);
}
static int calcMaxLineWidth(const QFont &font, const QString &text, int availW) {
@@ -1308,7 +1309,9 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
painter->setPen(QColor(0x80, 0x80, 0x80));
qint64 fsz = index.data(MsgFileSizeRole).toLongLong();
QString sizeStr = formatFileSize(fsz);
QString stateStr = (dlState == 2) ? QString::fromUtf8("\xE2\x9C\x85") : (dlState == 1) ? "Downloading..." : "Download";
QString stateStr = (dlState == 2) ? QString::fromUtf8("\xE2\x9C\x85") : (dlState == 1) ? QCoreApplication::translate("ChatGui",
"Downloading...") : QCoreApplication::translate("ChatGui",
"Download");
QString deliveryState = index.data(MsgDeliveryStateRole).toString();
if (!deliveryState.isEmpty()) stateStr = deliveryState;
QString info = sizeStr + " \xC2\xB7 " + stateStr;
@@ -1582,7 +1585,7 @@ void MessageDelegate::drawStatusBar(QPainter *p, const Layout &L,
QString statusText;
if (index.data(MsgEditedRole).toBool()) {
statusText = QString::fromUtf8("изменено ");
statusText = QCoreApplication::translate("ChatGui", "edited ");
}
statusText += index.data(MsgTimeRole).toString();
p->drawText(QRect(r.left(), r.top(), r.width(), r.height()),
+24 -21
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "messagelist.h"
#include "debug_ui.h"
#include "chatview.h"
@@ -353,7 +354,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
hl->addWidget(m_peerStatus);
m_channelTitle = new QLabel(header);
m_channelTitle->setStyleSheet("font-weight: bold; font-size: 14px;");
m_channelTitle->setText(QStringLiteral("Канал"));
m_channelTitle->setText(QCoreApplication::translate("ChatGui", "Channel"));
hl->addWidget(m_channelTitle, 1);
m_talkingLabel = new QLabel(header);
@@ -362,8 +363,8 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
hl->addWidget(m_talkingLabel);
m_inviteBtn = new QPushButton(QStringLiteral("+"), header);
m_inviteBtn->setAccessibleName(QStringLiteral("Пригласить в группу"));
m_inviteBtn->setToolTip(QStringLiteral("Пригласить в группу"));
m_inviteBtn->setAccessibleName(QCoreApplication::translate("ChatGui", "Invite to group"));
m_inviteBtn->setToolTip(QCoreApplication::translate("ChatGui", "Invite to group"));
m_inviteBtn->setFixedSize(34, 30);
m_inviteBtn->setCursor(Qt::PointingHandCursor);
m_inviteBtn->hide();
@@ -380,7 +381,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
});
m_callBtn = new QPushButton(QStringLiteral("☎"), header);
m_callBtn->setAccessibleName(QStringLiteral("Позвонить"));
m_callBtn->setAccessibleName(QCoreApplication::translate("ChatGui", "Call peer"));
m_callBtn->setFixedSize(34, 30);
m_callBtn->setCursor(Qt::PointingHandCursor);
m_callBtn->hide();
@@ -394,11 +395,11 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
m_radioBtn = new QPushButton(header);
m_radioBtn->setIcon(makeRadioIcon(header->palette().color(QPalette::ButtonText)));
m_radioBtn->setIconSize(QSize(24, 24));
m_radioBtn->setAccessibleName(QStringLiteral("Рация"));
m_radioBtn->setAccessibleName(QCoreApplication::translate("ChatGui", "Radio"));
m_radioBtn->setCheckable(true);
m_radioBtn->setFixedSize(34, 30);
m_radioBtn->setCursor(Qt::PointingHandCursor);
m_radioBtn->setToolTip(QStringLiteral("Рация: включить/выключить прослушивание канала"));
m_radioBtn->setToolTip(QCoreApplication::translate("ChatGui", "Radio: toggle channel listening"));
m_radioBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 6px; background: palette(base); font-size: 15px; }"
"QPushButton:checked { background: #27ae60; border-color: #27ae60; }");
@@ -1023,17 +1024,17 @@ void MessageList::onMessageContextMenu(const QPoint& pos) {
bool downloaded = !filePath.isEmpty() && QFile::exists(filePath);
QMenu menu(m_view);
QAction* saveAct = menu.addAction(QString::fromUtf8("Сохранить в файл"));
QAction* saveAct = menu.addAction(QCoreApplication::translate("ChatGui", "Save to file"));
saveAct->setEnabled(downloaded);
QAction* dlAct = nullptr;
if (!downloaded && !isDm) dlAct = menu.addAction(QString::fromUtf8("Скачать"));
if (!downloaded && !isDm) dlAct = menu.addAction(QCoreApplication::translate("ChatGui", "Download"));
QAction* chosen = menu.exec(m_view->viewport()->mapToGlobal(pos));
if (chosen == saveAct) {
QString base = displayName;
if (base.isEmpty()) base = QFileInfo(filePath).fileName();
QString dest = QFileDialog::getSaveFileName(this, QString::fromUtf8("Сохранить файл"), base);
QString dest = QFileDialog::getSaveFileName(this, QCoreApplication::translate("ChatGui", "Save file"), base);
if (dest.isEmpty()) return;
std::thread([filePath, dest]() {
if (!QFile::copy(filePath, dest))
@@ -1063,9 +1064,9 @@ void MessageList::onMessageContextMenu(const QPoint& pos) {
void MessageList::clearDmCallRoute() {
m_callPeer = 0; m_callGroup = 0;
m_callBtn->setEnabled(false);
m_callBtn->setToolTip(QStringLiteral("Собеседник недоступен"));
m_callBtn->setToolTip(QCoreApplication::translate("ChatGui", "Peer unavailable"));
m_peerStatus->setStyleSheet("background: #E53935; border-radius: 6px;");
m_peerStatus->setToolTip(QStringLiteral("Не в сети"));
m_peerStatus->setToolTip(QCoreApplication::translate("ChatGui", "Offline"));
}
void MessageList::refreshDmCallRoute() {
@@ -1095,8 +1096,8 @@ void MessageList::onDmCallRoute(const uint8_t* data, int len) {
m_callPeer = peer; m_callGroup = group;
m_callBtn->setEnabled(peer && group);
m_peerStatus->setStyleSheet(group ? "background: #4CAF50; border-radius: 6px;" : "background: #E53935; border-radius: 6px;");
m_peerStatus->setToolTip(group ? QStringLiteral("В сети") : QStringLiteral("Не в сети"));
m_callBtn->setToolTip(group ? QStringLiteral("Позвонить") : QStringLiteral("Собеседник недоступен"));
m_peerStatus->setToolTip(group ? QCoreApplication::translate("ChatGui", "Online") : QCoreApplication::translate("ChatGui", "Offline"));
m_callBtn->setToolTip(group ? QCoreApplication::translate("ChatGui", "Call peer") : QCoreApplication::translate("ChatGui", "Peer unavailable"));
}
bool MessageList::isDmChannel(const QString& channelId) {
@@ -1155,7 +1156,7 @@ void MessageList::onDmMessages(const uint8_t* data, int len) {
const QString inviteLink = invite ? text : QString();
if (invite) {
const QString groupName = message.value("tags").toObject().value("group_name").toString();
text = tr("Приглашение в группу «%1»").arg(groupName) + '\n' + inviteLink;
text = QCoreApplication::translate("ChatGui", "Invitation to group “%1”").arg(groupName) + '\n' + inviteLink;
GUI_DEBUG("invite PM: rendered conv=%s seq=%lld group_name_bytes=%d link_bytes=%d", qPrintable(m_currentChannelId),
(long long)seq, (int)groupName.toUtf8().size(), (int)inviteLink.size());
}
@@ -1163,12 +1164,14 @@ void MessageList::onDmMessages(const uint8_t* data, int len) {
QString state = message.value("media_status").toString();
QString status;
if (file) {
if (state == "delivered") status = QString::fromUtf8("Доставлено");
else if (state == "expired") status = QString::fromUtf8("Срок хранения истёк");
else if (dir == 1) status = QString::fromUtf8("Ожидает доставки");
else if (state == "ready") status = QString::fromUtf8("Получено");
else status = QString::fromUtf8("Загрузка…");
} else if (dir == 1) status = message.value("pending").toBool() ? QString::fromUtf8("Ожидает доставки") : QString::fromUtf8("Доставлено");
if (state == "delivered") status = QCoreApplication::translate("ChatGui", "Delivered");
else if (state == "expired") status = QCoreApplication::translate("ChatGui", "Storage period expired");
else if (dir == 1) status = QCoreApplication::translate("ChatGui", "Awaiting delivery");
else if (state == "ready") status = QCoreApplication::translate("ChatGui", "Received");
else status = QCoreApplication::translate("ChatGui", "Downloading…");
} else if (dir == 1) status = message.value("pending").toBool() ? QCoreApplication::translate("ChatGui",
"Awaiting delivery") : QCoreApplication::translate("ChatGui",
"Delivered");
AccountRow acc = m_db ? m_db->getAccount(author) : AccountRow();
QString name = authorName(author);
@@ -1228,7 +1231,7 @@ void MessageList::onRadioTalk(quint64 groupId, quint64 srcNodeId, bool on) {
if (on) {
QString name = authorName(srcNodeId);
if (name.isEmpty()) name = QStringLiteral("0x%1").arg(srcNodeId, 0, 16);
m_talkingLabel->setText(QString::fromUtf8("\xF0\x9F\x8E\x99 %1 говорит").arg(name));
m_talkingLabel->setText(QCoreApplication::translate("ChatGui", "🎙 %1 is speaking").arg(name));
m_talkingLabel->show();
} else {
m_talkingLabel->hide();
+24 -20
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
// networksettingspage.cpp — Network configuration form (список listen-сокетов)
#include "networksettingspage.h"
#include "../transport/config_updater.h"
@@ -13,7 +14,7 @@
#include <QFile>
#include <QTextStream>
static const QString AUTO_IP_LABEL = "Auto (0.0.0.0)";
static QString autoIpLabel() { return QCoreApplication::translate("ChatGui", "Auto (0.0.0.0)"); }
NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
: QWidget(parent)
@@ -22,11 +23,11 @@ NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
layout->setSpacing(12);
layout->setContentsMargins(0, 0, 0, 0);
auto* title = new QLabel("Listen Sockets", this);
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Listen Sockets"), this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
layout->addWidget(title);
m_autoSocketsCheck = new QCheckBox("Auto sockets (UDP+TCP from interfaces)", this);
m_autoSocketsCheck = new QCheckBox(QCoreApplication::translate("ChatGui", "Auto sockets (UDP+TCP from interfaces)"), this);
layout->addWidget(m_autoSocketsCheck);
m_serverList = new QListWidget(this);
@@ -34,8 +35,8 @@ NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
layout->addWidget(m_serverList);
auto* btnRow = new QHBoxLayout();
m_addBtn = new QPushButton("Add", this);
m_removeBtn = new QPushButton("Remove", this);
m_addBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Add"), this);
m_removeBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Remove"), this);
btnRow->addWidget(m_addBtn);
btnRow->addWidget(m_removeBtn);
btnRow->addStretch();
@@ -44,41 +45,44 @@ NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
// --- форма правки выбранного сокета ---
auto* formLayout = new QGridLayout();
formLayout->addWidget(new QLabel("Name:", this), 0, 0);
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Name:"), this), 0, 0);
m_nameEdit = new QLineEdit(this);
m_nameEdit->setMinimumWidth(180);
formLayout->addWidget(m_nameEdit, 0, 1);
formLayout->addWidget(new QLabel("IP Address:", this), 1, 0);
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "IP Address:"), this), 1, 0);
m_ipCombo = new QComboBox(this);
m_ipCombo->setEditable(true);
m_ipCombo->setMinimumWidth(180);
formLayout->addWidget(m_ipCombo, 1, 1);
formLayout->addWidget(new QLabel("Port:", this), 2, 0);
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Port:"), this), 2, 0);
m_portSpin = new QSpinBox(this);
m_portSpin->setRange(1, 65535);
m_portSpin->setValue(1333);
formLayout->addWidget(m_portSpin, 2, 1);
formLayout->addWidget(new QLabel("Protocol:", this), 3, 0);
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Protocol:"), this), 3, 0);
m_protoCombo = new QComboBox(this);
m_protoCombo->addItems({"UDP", "TCP"});
formLayout->addWidget(m_protoCombo, 3, 1);
formLayout->addWidget(new QLabel("Type:", this), 4, 0);
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Type:"), this), 4, 0);
m_typeCombo = new QComboBox(this);
m_typeCombo->addItems({"public", "nat", "private", "local"});
m_typeCombo->addItem(QCoreApplication::translate("ChatGui", "Public"), QStringLiteral("public"));
m_typeCombo->addItem(QStringLiteral("NAT"), QStringLiteral("nat"));
m_typeCombo->addItem(QCoreApplication::translate("ChatGui", "Private"), QStringLiteral("private"));
m_typeCombo->addItem(QCoreApplication::translate("ChatGui", "Local"), QStringLiteral("local"));
formLayout->addWidget(m_typeCombo, 4, 1);
formLayout->addWidget(new QLabel("Interface:", this), 5, 0);
formLayout->addWidget(new QLabel(QCoreApplication::translate("ChatGui", "Interface:"), this), 5, 0);
m_interfaceCombo = new QComboBox(this);
m_interfaceCombo->setMinimumWidth(180);
formLayout->addWidget(m_interfaceCombo, 5, 1);
layout->addLayout(formLayout);
auto* note = new QLabel("Changes take effect after restart.", this);
auto* note = new QLabel(QCoreApplication::translate("ChatGui", "Changes take effect after restart."), this);
note->setStyleSheet("color: gray; font-size: 11px;");
layout->addWidget(note);
layout->addStretch();
@@ -111,7 +115,7 @@ static void splitAddr(const QString& addr, QString& ip, int& port) {
}
void NetworkSettingsPage::populateInterfaces() {
m_interfaceCombo->addItem("Auto (any)");
m_interfaceCombo->addItem(QCoreApplication::translate("ChatGui", "Auto (any)"));
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
if (iface.flags() & QNetworkInterface::IsLoopBack) continue;
if (!(iface.flags() & QNetworkInterface::IsUp)) continue;
@@ -120,7 +124,7 @@ void NetworkSettingsPage::populateInterfaces() {
}
void NetworkSettingsPage::populateAddresses() {
m_ipCombo->addItem(AUTO_IP_LABEL);
m_ipCombo->addItem(autoIpLabel());
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
if (iface.flags() & QNetworkInterface::IsLoopBack) continue;
if (!(iface.flags() & QNetworkInterface::IsUp)) continue;
@@ -166,11 +170,11 @@ void NetworkSettingsPage::loadFormFromSelected() {
m_nameEdit->setText(s.name);
QString ip; int port = 0;
splitAddr(s.addr, ip, port);
if (ip == "0.0.0.0") ip = AUTO_IP_LABEL;
if (ip == "0.0.0.0") ip = autoIpLabel();
m_ipCombo->setCurrentText(ip);
m_portSpin->setValue(port > 0 ? port : 1333);
m_protoCombo->setCurrentIndex(s.transport == "tcp" ? 1 : 0);
int typeIdx = m_typeCombo->findText(s.type);
int typeIdx = m_typeCombo->findData(s.type);
m_typeCombo->setCurrentIndex(typeIdx >= 0 ? typeIdx : 0);
int ifIdx = m_interfaceCombo->findText(s.netif);
m_interfaceCombo->setCurrentIndex(ifIdx >= 0 ? ifIdx : 0);
@@ -185,13 +189,13 @@ void NetworkSettingsPage::updateSelectedServerFromForm() {
s.name = m_nameEdit->text().trimmed();
QString ip = m_ipCombo->currentText().trimmed();
if (ip == AUTO_IP_LABEL) ip = "0.0.0.0";
if (ip == autoIpLabel()) ip = "0.0.0.0";
s.addr = ip + ":" + QString::number(m_portSpin->value());
s.transport = (m_protoCombo->currentIndex() == 1) ? "tcp" : "udp";
s.type = m_typeCombo->currentText().trimmed();
s.type = m_typeCombo->currentData().toString();
QString iface = m_interfaceCombo->currentText();
s.netif = iface.startsWith("Auto") ? QString() : iface;
s.netif = m_interfaceCombo->currentIndex() == 0 ? QString() : iface;
}
void NetworkSettingsPage::updateSelectedListItemText() {
+21 -15
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
// nodespage.cpp — Nodes table + dump in settings
#include "nodespage.h"
#include "../db/db_manager.h"
@@ -59,12 +60,15 @@ NodesPage::NodesPage(DbManager* db, QWidget* parent)
layout->setSpacing(8);
layout->setContentsMargins(0, 0, 0, 0);
auto* title = new QLabel("Known Nodes", this);
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Known Nodes"), this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
layout->addWidget(title);
m_table = new QTableWidget(0, 5, this);
m_table->setHorizontalHeaderLabels({"Node ID", "Name", "x25519", "ed25519", "Addrs"});
m_table->setHorizontalHeaderLabels({QCoreApplication::translate("ChatGui",
"Node ID"), QCoreApplication::translate("ChatGui",
"Name"), "x25519", "ed25519", QCoreApplication::translate("ChatGui",
"Addrs")});
m_table->horizontalHeader()->setStretchLastSection(true);
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
@@ -78,7 +82,7 @@ NodesPage::NodesPage(DbManager* db, QWidget* parent)
" font-weight: bold; font-size: 9px; }");
layout->addWidget(m_table, 2);
auto* dumpLabel = new QLabel("Details", this);
auto* dumpLabel = new QLabel(QCoreApplication::translate("ChatGui", "Details"), this);
dumpLabel->setStyleSheet("font-weight: bold; font-size: 12px; margin-top: 8px;");
layout->addWidget(dumpLabel);
@@ -88,13 +92,13 @@ NodesPage::NodesPage(DbManager* db, QWidget* parent)
m_dumpText->setStyleSheet(
"QPlainTextEdit { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 8px; background: palette(base); }");
m_dumpText->setPlaceholderText("Click Refresh to load nodes");
m_dumpText->setPlaceholderText(QCoreApplication::translate("ChatGui", "Click Refresh to load nodes"));
layout->addWidget(m_dumpText, 3);
auto* btnLayout = new QHBoxLayout();
btnLayout->addStretch();
m_refreshBtn = new QPushButton("Refresh", this);
m_refreshBtn->setFixedWidth(90);
m_refreshBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Refresh"), this);
m_refreshBtn->setMinimumWidth(90);
m_refreshBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
@@ -109,7 +113,7 @@ void NodesPage::onRefreshClicked() { refreshNodes(); }
void NodesPage::refreshNodes() {
if (!m_db || !m_db->m_db) {
m_table->setRowCount(0);
m_dumpText->setPlainText(QString("Database not connected (m_db=%1, m_db->m_db=%2)")
m_dumpText->setPlainText(QCoreApplication::translate("ChatGui", "Database not connected (m_db=%1, m_db->m_db=%2)")
.arg((quintptr)m_db, 0, 16).arg(m_db ? (quintptr)m_db->m_db : 0, 0, 16));
qWarning("NodesPage::refreshNodes: DB not ready m_db=%p m_db->m_db=%p", (void*)m_db, m_db ? (void*)m_db->m_db : nullptr);
return;
@@ -167,7 +171,7 @@ void NodesPage::refreshNodes() {
"SELECT node_id, name, x25519_pubkey, ed25519_pubkey,"
" last_seen_at, created_at FROM nodes ORDER BY node_id",
-1, &st, nullptr);
if (!st) { m_dumpText->setPlainText("Query error"); return; }
if (!st) { m_dumpText->setPlainText(QCoreApplication::translate("ChatGui", "Query error")); return; }
int nodeCount = 0;
while (sqlite3_step(st) == SQLITE_ROW) {
@@ -181,15 +185,17 @@ void NodesPage::refreshNodes() {
qint64 last_seen = sqlite3_column_int64(st, 4);
qint64 created = sqlite3_column_int64(st, 5);
dump += QString("Node: %1").arg(hex16(nid));
dump += QCoreApplication::translate("ChatGui", "Node: %1").arg(hex16(nid));
if (!name.isEmpty()) dump += QString(" (%1)").arg(name);
dump += QString(" id=0x%1\n").arg(nid, 16, 16, QChar('0'));
dump += QString(" x25519: %1\n").arg(x25.isEmpty() ? "-" : shortenKey(x25));
dump += QString(" ed25519: %1\n").arg(ed.isEmpty() ? "-" : shortenKey(ed));
if (last_seen > 0)
dump += QString(" last_seen: %1\n").arg(QDateTime::fromSecsSinceEpoch(last_seen).toString("yyyy-MM-dd hh:mm:ss"));
dump += QCoreApplication::translate("ChatGui",
" last seen: %1\n").arg(QDateTime::fromSecsSinceEpoch(last_seen).toString("yyyy-MM-dd hh:mm:ss"));
if (created > 0)
dump += QString(" created: %1\n").arg(QDateTime::fromSecsSinceEpoch(created).toString("yyyy-MM-dd hh:mm:ss"));
dump += QCoreApplication::translate("ChatGui",
" created: %1\n").arg(QDateTime::fromSecsSinceEpoch(created).toString("yyyy-MM-dd hh:mm:ss"));
sqlite3_stmt* na = nullptr;
sqlite3_prepare_v2(d,
@@ -200,7 +206,7 @@ void NodesPage::refreshNodes() {
sqlite3_bind_int64(na, 1, (sqlite3_int64)nid);
int ac = 0;
while (sqlite3_step(na) == SQLITE_ROW) {
if (ac == 0) dump += " addresses:\n";
if (ac == 0) dump += QCoreApplication::translate("ChatGui", " addresses:\n");
int family = sqlite3_column_int(na, 0);
int proto = sqlite3_column_int(na, 1);
const void* addr = sqlite3_column_blob(na, 2);
@@ -219,14 +225,14 @@ void NodesPage::refreshNodes() {
}
ac++;
}
if (ac == 0) dump += " addresses: none\n";
if (ac == 0) dump += QCoreApplication::translate("ChatGui", " addresses: none\n");
sqlite3_finalize(na);
} else {
dump += " addresses: none\n";
dump += QCoreApplication::translate("ChatGui", " addresses: none\n");
}
}
sqlite3_finalize(st);
if (nodeCount == 0) dump = "No nodes found.";
if (nodeCount == 0) dump = QCoreApplication::translate("ChatGui", "No nodes found.");
m_dumpText->setPlainText(dump);
}
+3 -2
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "ptt_hotkey_manager.h"
#include "radio_audio_engine.h"
#include "../db/db_manager.h"
@@ -196,7 +197,7 @@ QString PttHotkeyManager::keyName(int key) const {
if (name) return QString::fromLatin1(name);
}
#endif
return QString("Key %1").arg(key);
return QCoreApplication::translate("ChatGui", "Key %1").arg(key);
}
QString PttHotkeyManager::describe(const PttKeyChord& chord) const {
@@ -204,7 +205,7 @@ QString PttHotkeyManager::describe(const PttKeyChord& chord) const {
std::sort(keys.begin(), keys.end());
QStringList names;
for (int key : keys) names << keyName(key);
return names.isEmpty() ? QStringLiteral("Не задано") : names.join(" + ");
return names.isEmpty() ? QCoreApplication::translate("ChatGui", "Not set") : names.join(" + ");
}
bool PttHotkeyManager::usesPortal() {
+9 -7
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "ptt_portal.h"
#include "../../lib/debug_config.h"
@@ -50,7 +51,7 @@ void PttPortal::setStatus(const QString& text) { m_status = text; emit statusCha
void PttPortal::initialize() {
if (busy() || m_state == State::Ready || m_state == State::Unbound) return;
m_state = State::Checking;
setStatus("Подключение глобальных хоткеев…");
setStatus(QCoreApplication::translate("ChatGui", "Connecting global shortcuts…"));
auto message = QDBusMessage::createMethodCall(kService, kPath, "org.freedesktop.DBus.Properties", "Get");
message.setArguments({kInterface, "version"});
auto* watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(message), this);
@@ -108,7 +109,7 @@ void PttPortal::response(uint code, const QVariantMap& results, const QDBusMessa
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "PTT portal: response state=%d code=%u", int(state), code);
if (code != 0) {
fail(QString("request %1: response=%2%3").arg(int(state)).arg(code).arg(code == 1 ? " (cancelled)" : ""));
if (code == 1) setStatus("Назначение отменено. Нажмите кнопку, чтобы повторить.");
if (code == 1) setStatus(QCoreApplication::translate("ChatGui", "Assignment cancelled. Click the button to try again."));
return;
}
if (state == State::Creating) {
@@ -127,11 +128,11 @@ void PttPortal::response(uint code, const QVariantMap& results, const QDBusMessa
m_configureAfterBind = known.size() == 3 && m_configureRequested;
m_state = State::Unbound;
if (!known.isEmpty() || m_configureRequested) bind();
else setStatus("Назначьте сочетание в системных настройках.");
else setStatus(QCoreApplication::translate("ChatGui", "Assign a shortcut in system settings."));
} else if (state == State::Binding) {
m_state = State::Ready;
updateDescriptions(qdbus_cast<PttPortalShortcuts>(results.value("shortcuts")));
setStatus("Глобальные хоткеи подключены.");
setStatus(QCoreApplication::translate("ChatGui", "Global shortcuts connected."));
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "PTT portal: bindings ready session=%s", qPrintable(m_session));
// Первое BindShortcuts само открывает окно назначения, повторное Configure — отдельный вызов.
m_configureRequested = false;
@@ -143,9 +144,10 @@ bool PttPortal::isPttId(const QString& id) { return id == "ptt1" || id == "ptt2"
void PttPortal::bind() {
PttPortalShortcuts shortcuts;
for (int i = 1; i <= 3; i++) shortcuts << PttPortalShortcut{QString("ptt%1").arg(i), {{"description", QString("Рация: передача %1").arg(i)}}};
for (int i = 1; i <= 3; i++) shortcuts << PttPortalShortcut{QString("ptt%1").arg(i), {{"description", QCoreApplication::translate("ChatGui",
"Radio: transmit %1").arg(i)}}};
m_state = State::Binding;
setStatus("Назначение глобальных хоткеев…");
setStatus(QCoreApplication::translate("ChatGui", "Assigning global shortcuts…"));
request("BindShortcuts", {QVariant::fromValue(QDBusObjectPath(m_session)), QVariant::fromValue(shortcuts), m_parentWindow}, {});
}
@@ -230,6 +232,6 @@ void PttPortal::fail(const QString& detail) {
m_configureRequested = m_configureAfterBind = false;
m_state = State::Failed;
m_descriptions = {"", "", ""}; emit descriptionsChanged();
setStatus("Глобальные хоткеи недоступны. Повторите назначение; подробности в логе radio.");
setStatus(QCoreApplication::translate("ChatGui", "Global shortcuts unavailable. Try assigning again; see the radio log for details."));
}
void PttPortal::sessionClosed() { fail("portal session closed"); }
+7 -6
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "radio_audio_engine.h"
#include "sound_manager.h"
#include "../transport/audio_diagnostics.h"
@@ -82,7 +83,7 @@ void RadioAudioEngine::loadSettings(const QString& configPath) {
/* Проверяем значения и атомарно сохраняем INI до отправки команды в ядро. */
bool RadioAudioEngine::applySettings(const QMap<QString, int>& values) {
if (!m_settingsReady || m_settingsBusy || !gui_bridge_is_uasync_ready() || !gui_bridge_get_inst()) {
m_settingsError = QStringLiteral("Настройки рации ещё не готовы");
m_settingsError = QCoreApplication::translate("ChatGui", "Radio settings are not ready yet");
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "RadioAudio: cannot apply settings ready=%d busy=%d", m_settingsReady, m_settingsBusy);
emit stateChanged();
return false;
@@ -92,7 +93,7 @@ bool RadioAudioEngine::applySettings(const QMap<QString, int>& values) {
size_t index = 0;
while (index < next.size() && it.key() != QLatin1String(settingKeys[index])) ++index;
if (index == next.size() || it.value() < settingMin[index] || it.value() > settingMax[index]) {
m_settingsError = QStringLiteral("Некорректная настройка рации: %1").arg(it.key());
m_settingsError = QCoreApplication::translate("ChatGui", "Invalid radio setting: %1").arg(it.key());
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioAudio: invalid setting %s=%d", qPrintable(it.key()), it.value());
emit stateChanged();
return false;
@@ -103,7 +104,7 @@ bool RadioAudioEngine::applySettings(const QMap<QString, int>& values) {
if (next != m_settings && !m_configPath.isEmpty()) {
QFile input(m_configPath);
if (!input.open(QIODevice::ReadOnly)) {
m_settingsError = QStringLiteral("Не удалось прочитать конфигурацию: %1").arg(input.errorString());
m_settingsError = QCoreApplication::translate("ChatGui", "Failed to read configuration: %1").arg(input.errorString());
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioAudio: read config failed: %s", qPrintable(input.errorString()));
emit stateChanged();
return false;
@@ -116,7 +117,7 @@ bool RadioAudioEngine::applySettings(const QMap<QString, int>& values) {
if (!data.endsWith('\n')) data.append('\n');
QSaveFile output(m_configPath);
if (!output.open(QIODevice::WriteOnly) || output.write(data) != data.size() || !output.commit()) {
m_settingsError = QStringLiteral("Не удалось сохранить конфигурацию: %1").arg(output.errorString());
m_settingsError = QCoreApplication::translate("ChatGui", "Failed to save configuration: %1").arg(output.errorString());
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioAudio: save config failed: %s", qPrintable(output.errorString()));
emit stateChanged();
return false;
@@ -131,7 +132,7 @@ void RadioAudioEngine::postSettings(const std::array<int, 6>& values, bool apply
auto* inst = gui_bridge_get_inst();
void* memory = inst && gui_bridge_is_uasync_ready() ? u_malloc(sizeof(SettingsRequest)) : nullptr;
if (!memory) {
m_settingsError = QStringLiteral("Не удалось отправить настройки рации в ядро");
m_settingsError = QCoreApplication::translate("ChatGui", "Failed to send radio settings to the core");
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioAudio: settings request unavailable inst=%d uasync=%d",
inst != nullptr, gui_bridge_is_uasync_ready());
emit stateChanged();
@@ -164,7 +165,7 @@ void RadioAudioEngine::postSettings(const std::array<int, 6>& values, bool apply
engine->m_settings = snapshot;
engine->m_settingsReady = true;
engine->m_settingsBusy = false;
if (!success) engine->m_settingsError = QStringLiteral("Ядро отклонило настройки рации");
if (!success) engine->m_settingsError = QCoreApplication::translate("ChatGui", "The core rejected radio settings");
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "RadioAudio: settings applied=%d vad=%d threshold=%d hangover=%dms TX_AGC=%d "
"TX_gain=%ddB VAD_gain=%ddB", success, snapshot[0], snapshot[1], snapshot[2], snapshot[3], snapshot[4], snapshot[5]);
if ((changed || apply) && engine->m_active) engine->reconfigureCapture();
+20 -19
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "radiopanel.h"
#include "radio_audio_engine.h"
#include "sound_manager.h"
@@ -24,8 +25,8 @@ RadioPanel::RadioPanel(QWidget* parent) : QWidget(parent) {
row->setSpacing(8);
m_input = new QToolButton(this);
m_output = new QToolButton(this);
m_input->setText(QStringLiteral("Микрофон"));
m_output->setText(QStringLiteral("Выход звука"));
m_input->setText(QCoreApplication::translate("ChatGui", "Microphone"));
m_output->setText(QCoreApplication::translate("ChatGui", "Audio output"));
m_input->setObjectName(QStringLiteral("radioInputButton"));
m_output->setObjectName(QStringLiteral("radioOutputButton"));
for (auto* button : {m_input, m_output}) {
@@ -43,15 +44,15 @@ RadioPanel::RadioPanel(QWidget* parent) : QWidget(parent) {
row->addWidget(m_status, 1);
m_vad = new QPushButton(QStringLiteral("VAD"), this);
m_vad->setObjectName(QStringLiteral("radioVadButton"));
m_vad->setAccessibleName(QStringLiteral("Автоматическая передача по голосу"));
m_vad->setAccessibleName(QCoreApplication::translate("ChatGui", "Automatic voice transmission"));
m_vad->setCheckable(true);
m_vad->setFixedSize(52, 34);
m_vad->setStyleSheet("QPushButton:checked { background: #3390EC; color: white; }");
row->addWidget(m_vad);
m_ptt = new QPushButton(QStringLiteral("PTT"), this);
m_ptt->setObjectName(QStringLiteral("radioPttButton"));
m_ptt->setAccessibleName(QStringLiteral("Держите и говорите"));
m_ptt->setToolTip(QStringLiteral("Держите кнопку или глобальный хоткей для передачи"));
m_ptt->setAccessibleName(QCoreApplication::translate("ChatGui", "Hold to talk"));
m_ptt->setToolTip(QCoreApplication::translate("ChatGui", "Hold the button or a global shortcut to transmit"));
m_ptt->setFixedSize(64, 34);
m_ptt->setStyleSheet("QPushButton[transmitting=\"true\"] { background: #27ae60; color: white; }");
row->addWidget(m_ptt);
@@ -82,16 +83,16 @@ void RadioPanel::updateState() {
m_ptt->update();
}
QString state;
if (!engine->settingsError().isEmpty()) state = QStringLiteral("Ошибка настройки");
else if (engine->settingsBusy()) state = QStringLiteral("Применение настроек…");
else if (!engine->captureAvailable()) state = QStringLiteral("Микрофон недоступен");
else if (engine->vadEnabled() && !engine->vadAvailable()) state = QStringLiteral("VAD недоступен");
else if (transmitting) state = QStringLiteral("Передача");
else state = engine->vadEnabled() ? QStringLiteral("VAD слушает") : QStringLiteral("Приём");
if (!engine->settingsError().isEmpty()) state = QCoreApplication::translate("ChatGui", "Settings error");
else if (engine->settingsBusy()) state = QCoreApplication::translate("ChatGui", "Applying settings…");
else if (!engine->captureAvailable()) state = QCoreApplication::translate("ChatGui", "Microphone unavailable");
else if (engine->vadEnabled() && !engine->vadAvailable()) state = QCoreApplication::translate("ChatGui", "VAD unavailable");
else if (transmitting) state = QCoreApplication::translate("ChatGui", "Transmitting");
else state = engine->vadEnabled() ? QCoreApplication::translate("ChatGui", "VAD listening") : QCoreApplication::translate("ChatGui", "Receiving");
m_status->setText(state);
m_status->setToolTip(engine->settingsError().isEmpty() ? state : engine->settingsError());
m_vad->setToolTip(engine->vadEnabled() ? QStringLiteral("Выключить автоматическую передачу по голосу")
: QStringLiteral("Включить автоматическую передачу по голосу"));
m_vad->setToolTip(engine->vadEnabled() ? QCoreApplication::translate("ChatGui", "Disable automatic voice transmission")
: QCoreApplication::translate("ChatGui", "Enable automatic voice transmission"));
}
/* Список перечитывается при открытии меню: подключённые устройства видны без перезапуска. */
@@ -107,7 +108,7 @@ void RadioPanel::populateDevices(QToolButton* button, bool capture) {
group->addAction(action);
connect(action, &QAction::triggered, this, [this, id, capture]() { selectDevice(id, capture); });
};
add(QStringLiteral("По умолчанию (авто)"), QString());
add(QCoreApplication::translate("ChatGui", "Default (auto)"), QString());
auto devices = capture ? SoundManager::enumerateCaptureDevices() : SoundManager::enumeratePlaybackDevices();
for (const auto& device : devices) add(device.name, device.id);
}
@@ -116,7 +117,7 @@ void RadioPanel::selectDevice(const QString& id, bool capture) {
auto* inst = gui_bridge_get_inst();
if (!inst || !gui_bridge_is_uasync_ready()) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: cannot save device: core unavailable");
m_status->setText(QStringLiteral("Ядро недоступно"));
m_status->setText(QCoreApplication::translate("ChatGui", "Core unavailable"));
return;
}
const char* key = capture ? "audio_input_id" : "audio_output_id";
@@ -124,14 +125,14 @@ void RadioPanel::selectDevice(const QString& id, bool capture) {
auto* request = static_cast<save_ui_state_arg*>(u_calloc(1, sizeof(save_ui_state_arg)));
if (!request) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: device settings allocation failed");
m_status->setText(QStringLiteral("Не удалось выбрать устройство"));
m_status->setText(QCoreApplication::translate("ChatGui", "Failed to select device"));
return;
}
size_t keyLen = strlen(key), valueLen = value.size();
if (keyLen + valueLen + 2 > sizeof(request->data)) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: device ID too long bytes=%zu", valueLen);
u_free(request);
m_status->setText(QStringLiteral("Не удалось выбрать устройство"));
m_status->setText(QCoreApplication::translate("ChatGui", "Failed to select device"));
return;
}
request->inst = inst;
@@ -148,9 +149,9 @@ void RadioPanel::updateDevices() {
for (auto* button : {m_input, m_output}) {
bool capture = button == m_input;
QString id = capture ? manager->captureDevice() : manager->playbackDevice();
QString name = QStringLiteral("По умолчанию (авто)");
QString name = QCoreApplication::translate("ChatGui", "Default (auto)");
if (!id.isEmpty()) {
name = QStringLiteral("Выбранное устройство недоступно; используется доступное");
name = QCoreApplication::translate("ChatGui", "Selected device unavailable; using an available device");
auto devices = capture ? manager->enumerateCaptureDevices() : manager->enumeratePlaybackDevices();
for (const auto& device : devices) if (device.id == id) { name = device.name; break; }
}
+37 -25
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "radiosettingspage.h"
#include "ptt_hotkey_manager.h"
#include "radio_audio_engine.h"
@@ -17,7 +18,7 @@
RadioSettingsPage::RadioSettingsPage(DbManager* db, QWidget* parent) : QWidget(parent), m_db(db) {
auto* layout = new QVBoxLayout(this);
auto* title = new QLabel("Рация (PTT)", this);
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Radio (PTT)"), this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
layout->addWidget(title);
m_audioSettings = new QWidget(this);
@@ -30,16 +31,20 @@ RadioSettingsPage::RadioSettingsPage(DbManager* db, QWidget* parent) : QWidget(p
form->addRow(label, spin);
return spin;
};
m_threshold = add(QStringLiteral("Порог речи VAD:"), 10, 95, QStringLiteral(" %"));
m_threshold->setToolTip(QStringLiteral("Порог вероятности речи: меньше — чувствительнее. Это не порог громкости."));
m_hangover = add(QStringLiteral("Тишина до окончания передачи:"), 50, 2000, QStringLiteral(" мс"));
m_compressor = new QCheckBox(QStringLiteral("Компрессор передачи и VAD"), m_audioSettings);
m_threshold = add(QCoreApplication::translate("ChatGui", "VAD speech threshold:"), 10, 95, QStringLiteral(" %"));
m_threshold->setToolTip(QCoreApplication::translate("ChatGui",
"Speech probability threshold: lower is more sensitive. This is not a volume threshold."));
m_hangover = add(QCoreApplication::translate("ChatGui",
"Silence before ending transmission:"), 50, 2000, QCoreApplication::translate("ChatGui",
" ms"));
m_compressor = new QCheckBox(QCoreApplication::translate("ChatGui", "Transmission and VAD compressor"), m_audioSettings);
form->addRow(m_compressor);
m_txGain = add(QStringLiteral("Max gain передачи:"), 1, 60, QStringLiteral(" dB"));
m_vadGain = add(QStringLiteral("Max gain VAD:"), 1, 60, QStringLiteral(" dB"));
auto* explanation = new QLabel(QStringLiteral("VAD включается кнопкой на панели рации. Буфер начала речи — 0,6 с. "
"Компрессор VAD имеет отдельный max gain; остальные параметры общие с компрессором передачи. "
"Скорость нарастания усиления задаётся в разделе Audio."), m_audioSettings);
m_txGain = add(QCoreApplication::translate("ChatGui", "Transmission max gain:"), 1, 60, QCoreApplication::translate("ChatGui", " dB"));
m_vadGain = add(QCoreApplication::translate("ChatGui", "VAD max gain:"), 1, 60, QCoreApplication::translate("ChatGui", " dB"));
auto* explanation = new QLabel(QCoreApplication::translate("ChatGui",
"Enable VAD using the button on the radio panel. Speech prebuffer is 0.6 s. The VAD "
"compressor has a separate maximum gain; other parameters are shared with the transmission "
"compressor. Set the gain rise rate in Audio."), m_audioSettings);
explanation->setWordWrap(true);
form->addRow(explanation);
layout->addWidget(m_audioSettings);
@@ -50,12 +55,13 @@ RadioSettingsPage::RadioSettingsPage(DbManager* db, QWidget* parent) : QWidget(p
});
loadAudioSettings();
if (PttHotkeyManager::usesPortal()) {
auto* hint = new QLabel("Глобальные хоткеи работают из любых приложений. "
"Назначьте обычную клавишу с Ctrl, Shift, Alt или Win в системном окне. "
"Изменения сохраняются системой сразу. Во время настройки передача с клавиатуры отключена.", this);
auto* hint = new QLabel(QCoreApplication::translate("ChatGui",
"Global shortcuts work from any application. Assign a regular key with Ctrl, Shift, Alt or "
"Win in the system dialog. Changes are saved by the system immediately. Keyboard "
"transmission is disabled during setup."), this);
hint->setWordWrap(true);
layout->addWidget(hint);
auto* button = new QPushButton("Задать PTT хоткеи…", this);
auto* button = new QPushButton(QCoreApplication::translate("ChatGui", "Set PTT shortcuts…"), this);
layout->addWidget(button);
m_buttons << button;
for (int i = 0; i < 3; i++) {
@@ -75,23 +81,27 @@ RadioSettingsPage::RadioSettingsPage(DbManager* db, QWidget* parent) : QWidget(p
return;
}
auto* hint = new QLabel(
"Удерживайте сочетание для передачи из любого приложения — даже когда ChatGUI не в фокусе. "
"Рация канала должна быть включена. Для записи зажмите нужные клавиши вместе и отпустите их. "
"Левые и правые Ctrl, Shift, Alt, Win различаются. Fn доступна, только если клавиатура передаёт её ОС.", this);
QCoreApplication::translate("ChatGui",
"Hold a shortcut to transmit from any application, even when ChatGUI is not focused. "
"Channel radio must be enabled. To record a shortcut, hold the desired keys together and "
"release them. Left and right Ctrl, Shift, Alt and Win are distinct. Fn is available only "
"if the keyboard reports it to the OS."), this);
hint->setWordWrap(true);
hint->setStyleSheet("color: gray; font-size: 11px;");
layout->addWidget(hint);
if (!PttHotkeyManager::isSupported()) {
auto* warn = new QLabel("Глобальные хоткеи доступны на Windows и Linux/X11. На этой платформе запись недоступна.", this);
auto* warn = new QLabel(QCoreApplication::translate("ChatGui",
"Global shortcuts are available on Windows and Linux/X11. Shortcut recording is "
"unavailable on this platform."), this);
warn->setWordWrap(true);
warn->setStyleSheet("color: #e74c3c; font-size: 12px;");
layout->addWidget(warn);
}
for (int i = 0; i < 3; i++) {
auto* row = new QHBoxLayout();
auto* button = new QPushButton(QString("Задать PTT хоткей %1").arg(i + 1), this);
auto* button = new QPushButton(QCoreApplication::translate("ChatGui", "Set PTT shortcut %1").arg(i + 1), this);
button->setEnabled(PttHotkeyManager::isSupported());
auto* clear = new QPushButton("Очистить", this);
auto* clear = new QPushButton(QCoreApplication::translate("ChatGui", "Clear"), this);
row->addWidget(button); row->addWidget(clear);
layout->addLayout(row);
auto* label = new QLabel(this);
@@ -127,13 +137,13 @@ RadioSettingsPage::~RadioSettingsPage() {
void RadioSettingsPage::startCapture(int index) {
finishCapture(false);
if (!PttHotkeyManager::instance()->beginCapture()) {
m_labels[index]->setText("Не удалось начать запись клавиш. Подробности в логе radio.");
m_labels[index]->setText(QCoreApplication::translate("ChatGui", "Failed to start key recording. See the radio log for details."));
return;
}
m_captureIndex = index;
m_capture.candidate.clear();
m_buttons[index]->setText("Отменить запись");
m_labels[index]->setText("Нажмите одну или несколько клавиш вместе…");
m_buttons[index]->setText(QCoreApplication::translate("ChatGui", "Cancel recording"));
m_labels[index]->setText(QCoreApplication::translate("ChatGui", "Press one or more keys together…"));
qApp->installEventFilter(this);
}
@@ -147,7 +157,7 @@ void RadioSettingsPage::finishCapture(bool save) {
m_captureIndex = -1;
qApp->removeEventFilter(this);
PttHotkeyManager::instance()->endCapture();
m_buttons[index]->setText(QString("Задать PTT хоткей %1").arg(index + 1));
m_buttons[index]->setText(QCoreApplication::translate("ChatGui", "Set PTT shortcut %1").arg(index + 1));
updateLabel(index);
}
@@ -223,7 +233,9 @@ void RadioSettingsPage::updatePortalLabels() {
const auto descriptions = manager->portalDescriptions();
for (int i = 0; i < m_labels.size(); i++) {
QString text = descriptions.value(i);
m_labels[i]->setText(QString("PTT хоткей %1: %2").arg(i + 1).arg(text.isEmpty() ? "Не задано" : text));
m_labels[i]->setText(QCoreApplication::translate("ChatGui",
"PTT shortcut %1: %2").arg(i + 1).arg(text.isEmpty() ? QCoreApplication::translate("ChatGui",
"Not set") : text));
}
m_portalStatus->setText(manager->portalStatus());
m_buttons[0]->setEnabled(!manager->portalBusy());
+6 -5
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "renamedialog.h"
#include "../db/db_manager.h"
#include <QVBoxLayout>
@@ -13,7 +14,7 @@ RenameDialog::RenameDialog(DbManager* db, const QString& channelId,
, m_channelId(channelId)
, m_nodeId(nodeId)
{
setWindowTitle("Rename");
setWindowTitle(QCoreApplication::translate("ChatGui", "Rename"));
setMinimumWidth(300);
setModal(true);
@@ -50,8 +51,8 @@ RenameDialog::RenameDialog(DbManager* db, const QString& channelId,
auto* btnLayout = new QHBoxLayout();
btnLayout->addStretch();
auto* cancelBtn = new QPushButton("Cancel", this);
cancelBtn->setFixedWidth(90);
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), this);
cancelBtn->setMinimumWidth(90);
cancelBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
@@ -59,8 +60,8 @@ RenameDialog::RenameDialog(DbManager* db, const QString& channelId,
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
btnLayout->addWidget(cancelBtn);
auto* saveBtn = new QPushButton("Save", this);
saveBtn->setFixedWidth(90);
auto* saveBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Save"), this);
saveBtn->setMinimumWidth(90);
saveBtn->setDefault(true);
saveBtn->setStyleSheet(
"QPushButton { border: none; border-radius: 4px;"
+59 -29
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
// settingsdialog.cpp — Settings dialog with category list
#include "settingsdialog.h"
#include "networksettingspage.h"
@@ -14,6 +15,9 @@
#include "../../lib/mem.h"
#include "../db/db_manager.h"
#include "../transport/utun_node.h"
#include "../transport/node_config.h"
#include "../transport/config_updater.h"
#include "../../lib/debug_config.h"
extern "C" {
#include "chat/chat_core.h"
}
@@ -33,7 +37,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
: QDialog(parent)
, m_configPath(configPath)
{
setWindowTitle("Settings");
setWindowTitle(QCoreApplication::translate("ChatGui", "Settings"));
setMinimumSize(760, 460);
setWindowModality(Qt::WindowModal);
@@ -49,16 +53,16 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
" background: palette(window); font-size: 13px; }"
"QListWidget::item { padding: 10px 14px; }"
"QListWidget::item:selected { background: #3390EC; color: white; }");
m_categoryList->addItem("Profile");
m_categoryList->addItem("Storage");
m_categoryList->addItem("Network");
m_categoryList->addItem("Connections");
m_categoryList->addItem("Database");
m_categoryList->addItem("Status");
m_categoryList->addItem("Nodes");
m_categoryList->addItem("Sound");
m_categoryList->addItem("Audio");
m_categoryList->addItem("Рация");
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Profile"));
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Storage"));
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Network"));
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Connections"));
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Database"));
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Status"));
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Nodes"));
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Sound"));
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Audio"));
m_categoryList->addItem(QCoreApplication::translate("ChatGui", "Radio"));
mainLayout->addWidget(m_categoryList);
// --- Right panel: stacked pages ---
@@ -73,35 +77,50 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
auto* profilePage = new QWidget(m_pages);
auto* profLayout = new QFormLayout(profilePage);
profLayout->setContentsMargins(0, 12, 0, 0);
auto* nameLabel = new QLabel("Display name:", profilePage);
auto* nameLabel = new QLabel(QCoreApplication::translate("ChatGui", "Display name:"), profilePage);
m_nicknameEdit = new QLineEdit(profilePage);
m_nicknameEdit->setPlaceholderText("Your name (shown to others)");
m_nicknameEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Your name (shown to others)"));
m_nicknameEdit->setMaxLength(32);
m_nicknameEdit->setStyleSheet(
"QLineEdit { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
profLayout->addRow(nameLabel, m_nicknameEdit);
auto* sortLabel = new QLabel("Channel sorting:", profilePage);
m_languageCombo = new QComboBox(profilePage);
m_languageCombo->setObjectName(QStringLiteral("languageCombo"));
m_languageCombo->addItem(QCoreApplication::translate("ChatGui", "System language"), QStringLiteral("system"));
m_languageCombo->addItem(QStringLiteral("English"), QStringLiteral("en"));
m_languageCombo->addItem(QStringLiteral("Русский"), QStringLiteral("ru"));
NodeConfig languageConfig(m_configPath);
if (!languageConfig.load()) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "GUI language: cannot read preferences from %s", qPrintable(m_configPath));
}
m_languageCombo->setCurrentIndex(m_languageCombo->findData(languageConfig.language()));
profLayout->addRow(QCoreApplication::translate("ChatGui", "Interface language:"), m_languageCombo);
auto* languageNote = new QLabel(QCoreApplication::translate("ChatGui", "Language changes take effect after restart."), profilePage);
languageNote->setWordWrap(true);
profLayout->addRow(languageNote);
auto* sortLabel = new QLabel(QCoreApplication::translate("ChatGui", "Channel sorting:"), profilePage);
m_sortCombo = new QComboBox(profilePage);
m_sortCombo->addItem("Alphabetical (A-Z)");
m_sortCombo->addItem("Recent Activity");
m_sortCombo->addItem(QCoreApplication::translate("ChatGui", "Alphabetical (A-Z)"));
m_sortCombo->addItem(QCoreApplication::translate("ChatGui", "Recent Activity"));
m_sortCombo->setStyleSheet(
"QComboBox { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
profLayout->addRow(sortLabel, m_sortCombo);
auto* joinLabel = new QLabel("Join requests:", profilePage);
auto* joinLabel = new QLabel(QCoreApplication::translate("ChatGui", "Join requests:"), profilePage);
m_joinPolicyCombo = new QComboBox(profilePage);
m_joinPolicyCombo->addItem("Auto-join");
m_joinPolicyCombo->addItem("Ask before joining");
m_joinPolicyCombo->addItem("Deny all");
m_joinPolicyCombo->addItem(QCoreApplication::translate("ChatGui", "Auto-join"));
m_joinPolicyCombo->addItem(QCoreApplication::translate("ChatGui", "Ask before joining"));
m_joinPolicyCombo->addItem(QCoreApplication::translate("ChatGui", "Deny all"));
m_joinPolicyCombo->setStyleSheet(
"QComboBox { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
profLayout->addRow(joinLabel, m_joinPolicyCombo);
m_autoConnectCheck = new QCheckBox("Auto-connect to group nodes", profilePage);
m_autoConnectCheck = new QCheckBox(QCoreApplication::translate("ChatGui", "Auto-connect to group nodes"), profilePage);
m_autoConnectCheck->setStyleSheet("font-size: 13px;");
profLayout->addRow(m_autoConnectCheck);
m_pages->addWidget(profilePage);
@@ -120,11 +139,11 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
auto* connPage = new QWidget(m_pages);
auto* connLayout = new QVBoxLayout(connPage);
connLayout->setAlignment(Qt::AlignCenter);
auto* hintLabel = new QLabel("Live ETCP connection monitor\nwith per-link BBR statistics", connPage);
auto* hintLabel = new QLabel(QCoreApplication::translate("ChatGui", "Live ETCP connection monitor\nwith per-link BBR statistics"), connPage);
hintLabel->setAlignment(Qt::AlignCenter);
hintLabel->setStyleSheet("font-size: 12px; color: palette(text); padding: 20px;");
connLayout->addWidget(hintLabel);
auto* openBtn = new QPushButton("Open Connection Monitor", connPage);
auto* openBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Open Connection Monitor"), connPage);
openBtn->setStyleSheet("QPushButton { padding: 10px 20px; font-size: 13px; }");
connect(openBtn, &QPushButton::clicked, this, &SettingsDialog::onOpenConnMonitor);
connLayout->addWidget(openBtn);
@@ -160,8 +179,8 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
btnLayout->setSpacing(8);
btnLayout->addStretch();
auto* cancelBtn = new QPushButton("Cancel", rightPanel);
cancelBtn->setFixedWidth(90);
auto* cancelBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Cancel"), rightPanel);
cancelBtn->setMinimumWidth(90);
cancelBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
@@ -169,8 +188,8 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
btnLayout->addWidget(cancelBtn);
m_saveBtn = new QPushButton("Save", rightPanel);
m_saveBtn->setFixedWidth(90);
m_saveBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Save"), rightPanel);
m_saveBtn->setMinimumWidth(90);
m_saveBtn->setDefault(true);
m_saveBtn->setStyleSheet(
"QPushButton { border: none; border-radius: 4px;"
@@ -299,8 +318,10 @@ void SettingsDialog::onSave() {
m_audioDevicePage->applyAndSave();
m_storagePage->applyAndSave();
if (!m_radioPage->apply()) {
QMessageBox::warning(this, QStringLiteral("Настройки рации"), QStringLiteral("Не удалось применить настройки рации. "
"Проверьте доступность ядра и файла конфигурации; подробности в логе radio."));
QMessageBox::warning(this, QCoreApplication::translate("ChatGui",
"Radio settings"), QCoreApplication::translate("ChatGui",
"Failed to apply radio settings. Check that the core and configuration file are available; "
"see the radio log for details."));
return;
}
@@ -357,6 +378,15 @@ void SettingsDialog::onSave() {
postChatSetting(m_configPath, "group_autoconnect", v);
}
}
const QString language = m_languageCombo->currentData().toString();
if (!config_update_option(m_configPath, "gui", "language", language)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI language: failed to save preference=%s path=%s",
qPrintable(language), qPrintable(m_configPath));
QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Settings"),
QCoreApplication::translate("ChatGui", "Failed to save the interface language. Check the configuration file."));
return;
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "GUI language: saved preference=%s; applies after restart", qPrintable(language));
accept();
}
+1
View File
@@ -43,6 +43,7 @@ private:
QPushButton* m_saveBtn;
QLineEdit* m_nicknameEdit;
QComboBox* m_sortCombo;
QComboBox* m_languageCombo;
QComboBox* m_joinPolicyCombo;
QCheckBox* m_autoConnectCheck;
QString m_oldNickname;
+13 -12
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "soundsettingspage.h"
#include "sound_manager.h"
#include "../db/db_manager.h"
@@ -22,12 +23,12 @@ SoundSettingsPage::SoundSettingsPage(DbManager* db, QWidget* parent)
layout->setContentsMargins(0, 12, 0, 0);
layout->setSpacing(14);
m_enabledCheck = new QCheckBox("Enable sounds", this);
m_enabledCheck = new QCheckBox(QCoreApplication::translate("ChatGui", "Enable sounds"), this);
m_enabledCheck->setStyleSheet("font-size: 13px;");
layout->addWidget(m_enabledCheck);
auto* volLayout = new QHBoxLayout();
auto* volLabel = new QLabel("Volume:", this);
auto* volLabel = new QLabel(QCoreApplication::translate("ChatGui", "Volume:"), this);
volLabel->setStyleSheet("font-size: 13px;");
volLayout->addWidget(volLabel);
@@ -51,25 +52,25 @@ SoundSettingsPage::SoundSettingsPage(DbManager* db, QWidget* parent)
sep->setStyleSheet("QFrame { color: palette(mid); }");
layout->addWidget(sep);
auto* header = new QLabel("Events", this);
auto* header = new QLabel(QCoreApplication::translate("ChatGui", "Events"), this);
header->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(header);
m_events[0] = {"msg_incoming", "Incoming message", nullptr, nullptr, nullptr};
m_events[1] = {"msg_outgoing", "Outgoing message", nullptr, nullptr, nullptr};
m_events[2] = {"connect", "Peer connected", nullptr, nullptr, nullptr};
m_events[3] = {"disconnect", "Peer disconnected", nullptr, nullptr, nullptr};
m_events[4] = {"call_incoming", "Incoming call", nullptr, nullptr, nullptr};
m_events[5] = {"call_connecting", "Call: connecting", nullptr, nullptr, nullptr};
m_events[6] = {"call_ringing", "Call: ringback", nullptr, nullptr, nullptr};
m_events[0] = {"msg_incoming", QT_TRANSLATE_NOOP("ChatGui", "Incoming message"), nullptr, nullptr, nullptr};
m_events[1] = {"msg_outgoing", QT_TRANSLATE_NOOP("ChatGui", "Outgoing message"), nullptr, nullptr, nullptr};
m_events[2] = {"connect", QT_TRANSLATE_NOOP("ChatGui", "Peer connected"), nullptr, nullptr, nullptr};
m_events[3] = {"disconnect", QT_TRANSLATE_NOOP("ChatGui", "Peer disconnected"), nullptr, nullptr, nullptr};
m_events[4] = {"call_incoming", QT_TRANSLATE_NOOP("ChatGui", "Incoming call"), nullptr, nullptr, nullptr};
m_events[5] = {"call_connecting", QT_TRANSLATE_NOOP("ChatGui", "Call: connecting"), nullptr, nullptr, nullptr};
m_events[6] = {"call_ringing", QT_TRANSLATE_NOOP("ChatGui", "Call: ringback"), nullptr, nullptr, nullptr};
for (int i = 0; i < 7; i++) {
auto& e = m_events[i];
auto* row = new QHBoxLayout();
row->setSpacing(8);
auto* lbl = new QLabel(e.label, this);
lbl->setFixedWidth(140);
auto* lbl = new QLabel(QCoreApplication::translate("ChatGui", e.label), this);
lbl->setMinimumWidth(140);
lbl->setStyleSheet("font-size: 13px;");
row->addWidget(lbl);
+7 -6
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
// statuspage.cpp — Status page (NTP + connections) in settings
#include "statuspage.h"
#include "../transport/utun_node.h"
@@ -50,7 +51,7 @@ StatusPage::StatusPage(UtunNode* node, QWidget* parent)
layout->addWidget(m_clockFrame);
/* ── System Status section ── */
auto* title = new QLabel("System Status", this);
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "System Status"), this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
layout->addWidget(title);
@@ -60,13 +61,13 @@ StatusPage::StatusPage(UtunNode* node, QWidget* parent)
m_textEdit->setStyleSheet(
"QPlainTextEdit { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 8px; background: palette(base); }");
m_textEdit->setPlaceholderText("Click Refresh to load status");
m_textEdit->setPlaceholderText(QCoreApplication::translate("ChatGui", "Click Refresh to load status"));
layout->addWidget(m_textEdit, 1);
auto* btnLayout = new QHBoxLayout();
btnLayout->addStretch();
m_refreshBtn = new QPushButton("Refresh", this);
m_refreshBtn->setFixedWidth(90);
m_refreshBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Refresh"), this);
m_refreshBtn->setMinimumWidth(90);
m_refreshBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
@@ -104,7 +105,7 @@ void StatusPage::updateClock() {
if (synced)
m_clockLabel->setText(QString("%1 [NTP ✓]").arg(t));
else
m_clockLabel->setText(QString("%1 [LOCAL]").arg(t));
m_clockLabel->setText(QCoreApplication::translate("ChatGui", "%1 [LOCAL]").arg(t));
int msToNext = 1000 - (corrected.time().msec());
m_clockTimer->start(msToNext);
@@ -112,7 +113,7 @@ void StatusPage::updateClock() {
void StatusPage::refreshStatus() {
updateClock();
m_textEdit->setPlainText("Loading...");
m_textEdit->setPlainText(QCoreApplication::translate("ChatGui", "Loading..."));
gui_bridge_post_uasync_fn(chat_core_collect_status_trampoline, gui_bridge_get_inst());
}
+7 -6
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "storagesettingspage.h"
#include "../db/db_manager.h"
#include "../transport/gui_bridge.h"
@@ -46,7 +47,7 @@ StorageSettingsPage::StorageSettingsPage(DbManager* db, QWidget* parent)
layout->setContentsMargins(0, 12, 0, 0);
layout->setSpacing(14);
auto* title = new QLabel("Media Storage", this);
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Media Storage"), this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
layout->addWidget(title);
@@ -54,27 +55,27 @@ StorageSettingsPage::StorageSettingsPage(DbManager* db, QWidget* parent)
form->setSpacing(10);
form->setContentsMargins(0, 8, 0, 0);
m_autoloadCheck = new QCheckBox("Autoload media", this);
m_autoloadCheck = new QCheckBox(QCoreApplication::translate("ChatGui", "Autoload media"), this);
m_autoloadCheck->setStyleSheet("font-size: 13px;");
form->addRow(m_autoloadCheck);
m_autoloadMaxSize = new QSpinBox(this);
m_autoloadMaxSize->setRange(1, 500);
m_autoloadMaxSize->setSuffix(" MB");
m_autoloadMaxSize->setSuffix(QCoreApplication::translate("ChatGui", " MB"));
m_autoloadMaxSize->setFixedWidth(110);
m_autoloadMaxSize->setStyleSheet(
"QSpinBox { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 5px 8px; font-size: 13px; background: palette(base); }");
form->addRow("Autoload max size:", m_autoloadMaxSize);
form->addRow(QCoreApplication::translate("ChatGui", "Autoload max size:"), m_autoloadMaxSize);
m_maxStorageSize = new QSpinBox(this);
m_maxStorageSize->setRange(1, 100);
m_maxStorageSize->setSuffix(" GB");
m_maxStorageSize->setSuffix(QCoreApplication::translate("ChatGui", " GB"));
m_maxStorageSize->setFixedWidth(110);
m_maxStorageSize->setStyleSheet(
"QSpinBox { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 5px 8px; font-size: 13px; background: palette(base); }");
form->addRow("Max storage size:", m_maxStorageSize);
form->addRow(QCoreApplication::translate("ChatGui", "Max storage size:"), m_maxStorageSize);
layout->addLayout(form);
+9 -8
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "videoplayer_engine.h"
#include "sound_manager.h"
#include "../../lib/debug_config.h"
@@ -74,10 +75,10 @@ void Worker::run() {
};
if (avformat_open_input(&fmt, path.toUtf8().constData(), nullptr, nullptr) != 0) {
worker_fail(this, QString("cannot open: %1").arg(path)); return;
worker_fail(this, QCoreApplication::translate("ChatGui", "cannot open: %1").arg(path)); return;
}
if (avformat_find_stream_info(fmt, nullptr) < 0) {
worker_fail(this, QString("no stream info: %1").arg(path)); cleanup(); return;
worker_fail(this, QCoreApplication::translate("ChatGui", "no stream info: %1").arg(path)); cleanup(); return;
}
for (unsigned i = 0; i < fmt->nb_streams; i++) {
@@ -85,7 +86,7 @@ void Worker::run() {
if (p->codec_type == AVMEDIA_TYPE_VIDEO && vidx < 0) vidx = (int)i;
else if (p->codec_type == AVMEDIA_TYPE_AUDIO && aidx < 0) aidx = (int)i;
}
if (vidx < 0) { worker_fail(this, QString("no video stream: %1").arg(path)); cleanup(); return; }
if (vidx < 0) { worker_fail(this, QCoreApplication::translate("ChatGui", "no video stream: %1").arg(path)); cleanup(); return; }
AVStream* vst = fmt->streams[vidx];
int vw = vst->codecpar->width, vh = vst->codecpar->height;
@@ -131,7 +132,7 @@ void Worker::run() {
if (aidx >= 0) {
aframe = av_frame_alloc();
pkt = av_packet_alloc();
if (!aframe || !pkt) { worker_fail(this, "alloc failed"); cleanup(); return; }
if (!aframe || !pkt) { worker_fail(this, QCoreApplication::translate("ChatGui", "alloc failed")); cleanup(); return; }
while (av_read_frame(fmt, pkt) >= 0) {
if (abort->loadRelaxed()) { av_packet_unref(pkt); cleanup(); return; }
@@ -175,11 +176,11 @@ void Worker::run() {
vdec = avcodec_alloc_context3(vc);
if (!vc || !vdec || avcodec_parameters_to_context(vdec, vst->codecpar) < 0 ||
avcodec_open2(vdec, vc, nullptr) < 0) {
worker_fail(this, "video decoder open failed"); cleanup(); return;
worker_fail(this, QCoreApplication::translate("ChatGui", "video decoder open failed")); cleanup(); return;
}
frame = av_frame_alloc();
pkt = av_packet_alloc();
if (!frame || !pkt) { worker_fail(this, "alloc failed"); cleanup(); return; }
if (!frame || !pkt) { worker_fail(this, QCoreApplication::translate("ChatGui", "alloc failed")); cleanup(); return; }
/* YUVJ* (deprecated full-range) → обычный формат + явный range, чтобы
* не ловить warning swscaler и не искажать цвета */
AVPixelFormat srcFmt = vdec->pix_fmt;
@@ -192,7 +193,7 @@ void Worker::run() {
}
sws = sws_getContext(vw, vh, srcFmt, vw, vh, AV_PIX_FMT_RGB24,
SWS_BILINEAR, nullptr, nullptr, nullptr);
if (!sws) { worker_fail(this, "sws init failed"); cleanup(); return; }
if (!sws) { worker_fail(this, QCoreApplication::translate("ChatGui", "sws init failed")); cleanup(); return; }
int *invTbl = nullptr, *tbl = nullptr, sr = 0, dr = 0, br = 0, ct = 0, st = 0;
if (sws_getColorspaceDetails(sws, &invTbl, &sr, &tbl, &dr, &br, &ct, &st) >= 0)
sws_setColorspaceDetails(sws, invTbl, srcRange, tbl, 1 /* RGB full-range */, br, ct, st);
@@ -252,7 +253,7 @@ void Worker::run() {
void Worker::run() {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "VideoEngine: built without FFmpeg");
emit error(QStringLiteral("видео недоступно (сборка без FFmpeg)"));
emit error(QCoreApplication::translate("ChatGui", "video unavailable (built without FFmpeg)"));
}
#endif /* HAVE_FFMPEG */
+6 -5
View File
@@ -1,3 +1,4 @@
#include <QCoreApplication>
#include "videoplayer_window.h"
#include "videoplayer_engine.h"
#include "../../lib/debug_config.h"
@@ -76,7 +77,7 @@ public:
VideoPlayerWindow::VideoPlayerWindow(QWidget* parent)
: QWidget(parent, Qt::Window | Qt::FramelessWindowHint)
{
setWindowTitle(QStringLiteral("Video"));
setWindowTitle(QCoreApplication::translate("ChatGui", "Video"));
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
@@ -99,7 +100,7 @@ VideoPlayerWindow::VideoPlayerWindow(QWidget* parent)
"QSlider::handle:horizontal { width: 12px; margin: -4px 0; background: white; border-radius: 6px; }");
m_playBtn = new QPushButton(QStringLiteral("⏸"), m_controls);
m_playBtn->setToolTip(QStringLiteral("Пауза"));
m_playBtn->setToolTip(QCoreApplication::translate("ChatGui", "Pause"));
m_playBtn->setFixedWidth(40);
m_slider = new QSlider(Qt::Horizontal, m_controls);
m_slider->setStyle(new SeekSliderStyle());
@@ -115,7 +116,7 @@ VideoPlayerWindow::VideoPlayerWindow(QWidget* parent)
/* крестик закрытия — отдельно, справа вверху */
m_closeBtn = new QPushButton(QStringLiteral("✕"), this);
m_closeBtn->setToolTip(QStringLiteral("Закрыть"));
m_closeBtn->setToolTip(QCoreApplication::translate("ChatGui", "Close"));
m_closeBtn->setCursor(Qt::ArrowCursor);
m_closeBtn->setStyleSheet(
"QPushButton { background: rgba(0,0,0,0.5); color: white; border: none; "
@@ -193,10 +194,10 @@ void VideoPlayerWindow::onStateChanged(int state) {
VideoPlayerEngine::State s = (VideoPlayerEngine::State)state;
if (s == VideoPlayerEngine::Playing) {
m_playBtn->setText(QStringLiteral("⏸"));
m_playBtn->setToolTip(QStringLiteral("Пауза"));
m_playBtn->setToolTip(QCoreApplication::translate("ChatGui", "Pause"));
} else if (s == VideoPlayerEngine::Paused || s == VideoPlayerEngine::Finished) {
m_playBtn->setText(QStringLiteral("▶"));
m_playBtn->setToolTip(QStringLiteral("Играть"));
m_playBtn->setToolTip(QCoreApplication::translate("ChatGui", "Play"));
showControls();
}
}