UI: wrap all text for translation (#24961)

* rough multiple language demo

* more wrappings

* stash

* add some bad translations

* updates

* map from french to spanish still has same problem of needing to call setText on everything

* add files

* restart UI

* use return code

* relative path

* more translations

* don't loop restart

* Toggle and prime translations

* try on device

* try QComboBox with readable style

* stash

* not yet scrollable

* stash

* dynamic translations (doesn't work for dynamic widget strings yet)

* clean up multiple option selector

* store languages in json

* try transparent

* Try transparent popup

* see how this looks

* tweaks

* clean up

* clean up

* clean up 2 and missing tr

* wrap more strings

* missing updater

* fixes

* add basic test to ensure all strings wrapped

* try in CI

* clean up

* test name

* fix test

* always install qt dev tools

* fix deps

* fast test

* add section so it prints multiple errors

* debug

* debug

get rid of those

* make any difference?

* comment

* oh...

* run with offscreen platform

* try out section

* clean up

* fix missing wrappings (it works!)

* move down

* space

* clear relevant params, set TICI=1
pull/24856/head^2
Shane Smiskol 3 years ago committed by GitHub
parent bef5350fa3
commit 879a7c3201
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/selfdrive_tests.yaml
  2. 3
      selfdrive/ui/SConscript
  3. 8
      selfdrive/ui/installer/installer.cc
  4. 4
      selfdrive/ui/qt/home.cc
  5. 16
      selfdrive/ui/qt/maps/map_settings.cc
  6. 2
      selfdrive/ui/qt/offroad/driverview.cc
  7. 32
      selfdrive/ui/qt/offroad/networking.cc
  8. 14
      selfdrive/ui/qt/offroad/onboarding.cc
  9. 110
      selfdrive/ui/qt/offroad/settings.cc
  10. 14
      selfdrive/ui/qt/onroad.cc
  11. 18
      selfdrive/ui/qt/setup/reset.cc
  12. 42
      selfdrive/ui/qt/setup/setup.cc
  13. 16
      selfdrive/ui/qt/setup/updater.cc
  14. 16
      selfdrive/ui/qt/sidebar.cc
  15. 4
      selfdrive/ui/qt/text.cc
  16. 8
      selfdrive/ui/qt/util.cc
  17. 8
      selfdrive/ui/qt/widgets/drive_stats.cc
  18. 2
      selfdrive/ui/qt/widgets/drive_stats.h
  19. 10
      selfdrive/ui/qt/widgets/input.cc
  20. 6
      selfdrive/ui/qt/widgets/offroad_alerts.cc
  21. 28
      selfdrive/ui/qt/widgets/prime.cc
  22. 18
      selfdrive/ui/qt/widgets/ssh_keys.cc
  23. 2
      selfdrive/ui/qt/widgets/ssh_keys.h
  24. 1
      selfdrive/ui/tests/.gitignore
  25. 18
      selfdrive/ui/tests/create_test_translations.sh
  26. 19
      selfdrive/ui/tests/test_runner.cc
  27. 51
      selfdrive/ui/tests/test_translations.cc
  28. 1
      tools/ubuntu_setup.sh

@ -305,6 +305,8 @@ jobs:
$UNIT_TEST selfdrive/hardware/tici && \ $UNIT_TEST selfdrive/hardware/tici && \
$UNIT_TEST selfdrive/modeld && \ $UNIT_TEST selfdrive/modeld && \
$UNIT_TEST tools/lib/tests && \ $UNIT_TEST tools/lib/tests && \
./selfdrive/ui/tests/create_test_translations.sh && \
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
./common/tests/test_util && \ ./common/tests/test_util && \
./common/tests/test_swaglog && \ ./common/tests/test_swaglog && \
./selfdrive/boardd/tests/test_boardd_usbprotocol && \ ./selfdrive/boardd/tests/test_boardd_usbprotocol && \

@ -58,6 +58,9 @@ qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc",
"qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc",
"qt/offroad/onboarding.cc", "qt/offroad/driverview.cc"] "qt/offroad/onboarding.cc", "qt/offroad/driverview.cc"]
qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs) qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs)
if GetOption('test'):
qt_src.remove("main.cc") # replaced by test_runner
qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs)
# setup and factory resetter # setup and factory resetter

@ -53,7 +53,7 @@ Installer::Installer(QWidget *parent) : QWidget(parent) {
layout->setContentsMargins(150, 290, 150, 150); layout->setContentsMargins(150, 290, 150, 150);
layout->setSpacing(0); layout->setSpacing(0);
QLabel *title = new QLabel("Installing..."); QLabel *title = new QLabel(tr("Installing..."));
title->setStyleSheet("font-size: 90px; font-weight: 600;"); title->setStyleSheet("font-size: 90px; font-weight: 600;");
layout->addWidget(title, 0, Qt::AlignTop); layout->addWidget(title, 0, Qt::AlignTop);
@ -141,9 +141,9 @@ void Installer::cachedFetch(const QString &cache) {
void Installer::readProgress() { void Installer::readProgress() {
const QVector<QPair<QString, int>> stages = { const QVector<QPair<QString, int>> stages = {
// prefix, weight in percentage // prefix, weight in percentage
{"Receiving objects: ", 91}, {tr("Receiving objects: "), 91},
{"Resolving deltas: ", 2}, {tr("Resolving deltas: "), 2},
{"Updating files: ", 7}, {tr("Updating files: "), 7},
}; };
auto line = QString(proc.readAllStandardError()); auto line = QString(proc.readAllStandardError());

@ -111,7 +111,7 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
date = new QLabel(); date = new QLabel();
header_layout->addWidget(date, 1, Qt::AlignHCenter | Qt::AlignLeft); header_layout->addWidget(date, 1, Qt::AlignHCenter | Qt::AlignLeft);
update_notif = new QPushButton("UPDATE"); update_notif = new QPushButton(tr("UPDATE"));
update_notif->setVisible(false); update_notif->setVisible(false);
update_notif->setStyleSheet("background-color: #364DEF;"); update_notif->setStyleSheet("background-color: #364DEF;");
QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); });
@ -202,6 +202,6 @@ void OffroadHome::refresh() {
update_notif->setVisible(updateAvailable); update_notif->setVisible(updateAvailable);
alert_notif->setVisible(alerts); alert_notif->setVisible(alerts);
if (alerts) { if (alerts) {
alert_notif->setText(QString::number(alerts) + (alerts > 1 ? " ALERTS" : " ALERT")); alert_notif->setText(QString::number(alerts) + (alerts > 1 ? tr(" ALERTS") : tr(" ALERT")));
} }
} }

@ -59,11 +59,11 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
current_widget = new QWidget(this); current_widget = new QWidget(this);
QVBoxLayout *current_layout = new QVBoxLayout(current_widget); QVBoxLayout *current_layout = new QVBoxLayout(current_widget);
QLabel *title = new QLabel("Current Destination"); QLabel *title = new QLabel(tr("Current Destination"));
title->setStyleSheet("font-size: 55px"); title->setStyleSheet("font-size: 55px");
current_layout->addWidget(title); current_layout->addWidget(title);
current_route = new ButtonControl("", "CLEAR"); current_route = new ButtonControl("", tr("CLEAR"));
current_route->setStyleSheet("padding-left: 40px;"); current_route->setStyleSheet("padding-left: 40px;");
current_layout->addWidget(current_route); current_layout->addWidget(current_route);
QObject::connect(current_route, &ButtonControl::clicked, [=]() { QObject::connect(current_route, &ButtonControl::clicked, [=]() {
@ -78,7 +78,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
main_layout->addWidget(current_widget); main_layout->addWidget(current_widget);
// Recents // Recents
QLabel *recents_title = new QLabel("Recent Destinations"); QLabel *recents_title = new QLabel(tr("Recent Destinations"));
recents_title->setStyleSheet("font-size: 55px"); recents_title->setStyleSheet("font-size: 55px");
main_layout->addWidget(recents_title); main_layout->addWidget(recents_title);
main_layout->addSpacing(20); main_layout->addSpacing(20);
@ -92,7 +92,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
QWidget * no_prime_widget = new QWidget; QWidget * no_prime_widget = new QWidget;
{ {
QVBoxLayout *no_prime_layout = new QVBoxLayout(no_prime_widget); QVBoxLayout *no_prime_layout = new QVBoxLayout(no_prime_widget);
QLabel *signup_header = new QLabel("Try the Navigation Beta"); QLabel *signup_header = new QLabel(tr("Try the Navigation Beta"));
signup_header->setStyleSheet(R"(font-size: 75px; color: white; font-weight:600;)"); signup_header->setStyleSheet(R"(font-size: 75px; color: white; font-weight:600;)");
signup_header->setAlignment(Qt::AlignCenter); signup_header->setAlignment(Qt::AlignCenter);
@ -104,7 +104,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
screenshot->setPixmap(pm.scaledToWidth(1080, Qt::SmoothTransformation)); screenshot->setPixmap(pm.scaledToWidth(1080, Qt::SmoothTransformation));
no_prime_layout->addWidget(screenshot, 0, Qt::AlignHCenter); no_prime_layout->addWidget(screenshot, 0, Qt::AlignHCenter);
QLabel *signup = new QLabel("Get turn-by-turn directions displayed and more with a comma \nprime subscription. Sign up now: https://connect.comma.ai"); QLabel *signup = new QLabel(tr("Get turn-by-turn directions displayed and more with a comma \nprime subscription. Sign up now: https://connect.comma.ai"));
signup->setStyleSheet(R"(font-size: 45px; color: white; font-weight:300;)"); signup->setStyleSheet(R"(font-size: 45px; color: white; font-weight:300;)");
signup->setAlignment(Qt::AlignCenter); signup->setAlignment(Qt::AlignCenter);
@ -161,12 +161,12 @@ void MapPanel::showEvent(QShowEvent *event) {
void MapPanel::clear() { void MapPanel::clear() {
home_button->setIcon(QPixmap("../assets/navigation/home_inactive.png")); home_button->setIcon(QPixmap("../assets/navigation/home_inactive.png"));
home_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); home_address->setStyleSheet(R"(font-size: 50px; color: grey;)");
home_address->setText("No home\nlocation set"); home_address->setText(tr("No home\nlocation set"));
home_button->disconnect(); home_button->disconnect();
work_button->setIcon(QPixmap("../assets/navigation/work_inactive.png")); work_button->setIcon(QPixmap("../assets/navigation/work_inactive.png"));
work_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); work_address->setStyleSheet(R"(font-size: 50px; color: grey;)");
work_address->setText("No work\nlocation set"); work_address->setText(tr("No work\nlocation set"));
work_button->disconnect(); work_button->disconnect();
clearLayout(recent_layout); clearLayout(recent_layout);
@ -279,7 +279,7 @@ void MapPanel::parseResponse(const QString &response, bool success) {
} }
if (!has_recents) { if (!has_recents) {
QLabel *no_recents = new QLabel("no recent destinations"); QLabel *no_recents = new QLabel(tr("no recent destinations"));
no_recents->setStyleSheet(R"(font-size: 50px; color: #9c9c9c)"); no_recents->setStyleSheet(R"(font-size: 50px; color: #9c9c9c)");
recent_layout->addWidget(no_recents); recent_layout->addWidget(no_recents);
} }

@ -53,7 +53,7 @@ void DriverViewScene::paintEvent(QPaintEvent* event) {
p.setPen(Qt::white); p.setPen(Qt::white);
p.setRenderHint(QPainter::TextAntialiasing); p.setRenderHint(QPainter::TextAntialiasing);
configFont(p, "Inter", 100, "Bold"); configFont(p, "Inter", 100, "Bold");
p.drawText(geometry(), Qt::AlignCenter, "camera starting"); p.drawText(geometry(), Qt::AlignCenter, tr("camera starting"));
return; return;
} }

@ -27,7 +27,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) {
QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen); QVBoxLayout* vlayout = new QVBoxLayout(wifiScreen);
vlayout->setContentsMargins(20, 20, 20, 20); vlayout->setContentsMargins(20, 20, 20, 20);
if (show_advanced) { if (show_advanced) {
QPushButton* advancedSettings = new QPushButton("Advanced"); QPushButton* advancedSettings = new QPushButton(tr("Advanced"));
advancedSettings->setObjectName("advanced_btn"); advancedSettings->setObjectName("advanced_btn");
advancedSettings->setStyleSheet("margin-right: 30px;"); advancedSettings->setStyleSheet("margin-right: 30px;");
advancedSettings->setFixedSize(400, 100); advancedSettings->setFixedSize(400, 100);
@ -84,7 +84,7 @@ void Networking::connectToNetwork(const Network &n) {
} else if (n.security_type == SecurityType::OPEN) { } else if (n.security_type == SecurityType::OPEN) {
wifi->connect(n); wifi->connect(n);
} else if (n.security_type == SecurityType::WPA) { } else if (n.security_type == SecurityType::WPA) {
QString pass = InputDialog::getText("Enter password", this, "for \"" + n.ssid + "\"", true, 8); QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"") + n.ssid + "\"", true, 8);
if (!pass.isEmpty()) { if (!pass.isEmpty()) {
wifi->connect(n, pass); wifi->connect(n, pass);
} }
@ -94,7 +94,7 @@ void Networking::connectToNetwork(const Network &n) {
void Networking::wrongPassword(const QString &ssid) { void Networking::wrongPassword(const QString &ssid) {
if (wifi->seenNetworks.contains(ssid)) { if (wifi->seenNetworks.contains(ssid)) {
const Network &n = wifi->seenNetworks.value(ssid); const Network &n = wifi->seenNetworks.value(ssid);
QString pass = InputDialog::getText("Wrong password", this, "for \"" + n.ssid +"\"", true, 8); QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"") + n.ssid +"\"", true, 8);
if (!pass.isEmpty()) { if (!pass.isEmpty()) {
wifi->connect(n, pass); wifi->connect(n, pass);
} }
@ -118,7 +118,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
main_layout->setSpacing(20); main_layout->setSpacing(20);
// Back button // Back button
QPushButton* back = new QPushButton("Back"); QPushButton* back = new QPushButton(tr("Back"));
back->setObjectName("back_btn"); back->setObjectName("back_btn");
back->setFixedSize(400, 100); back->setFixedSize(400, 100);
connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); connect(back, &QPushButton::clicked, [=]() { emit backPress(); });
@ -126,14 +126,14 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
ListWidget *list = new ListWidget(this); ListWidget *list = new ListWidget(this);
// Enable tethering layout // Enable tethering layout
tetheringToggle = new ToggleControl("Enable Tethering", "", "", wifi->isTetheringEnabled()); tetheringToggle = new ToggleControl(tr("Enable Tethering"), "", "", wifi->isTetheringEnabled());
list->addItem(tetheringToggle); list->addItem(tetheringToggle);
QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering); QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering);
// Change tethering password // Change tethering password
ButtonControl *editPasswordButton = new ButtonControl("Tethering Password", "EDIT"); ButtonControl *editPasswordButton = new ButtonControl(tr("Tethering Password"), tr("EDIT"));
connect(editPasswordButton, &ButtonControl::clicked, [=]() { connect(editPasswordButton, &ButtonControl::clicked, [=]() {
QString pass = InputDialog::getText("Enter new tethering password", this, "", true, 8, wifi->getTetheringPassword()); QString pass = InputDialog::getText(tr("Enter new tethering password"), this, "", true, 8, wifi->getTetheringPassword());
if (!pass.isEmpty()) { if (!pass.isEmpty()) {
wifi->changeTetheringPassword(pass); wifi->changeTetheringPassword(pass);
} }
@ -141,7 +141,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
list->addItem(editPasswordButton); list->addItem(editPasswordButton);
// IP address // IP address
ipLabel = new LabelControl("IP Address", wifi->ipv4_address); ipLabel = new LabelControl(tr("IP Address"), wifi->ipv4_address);
list->addItem(ipLabel); list->addItem(ipLabel);
// SSH keys // SSH keys
@ -150,7 +150,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
// Roaming toggle // Roaming toggle
const bool roamingEnabled = params.getBool("GsmRoaming"); const bool roamingEnabled = params.getBool("GsmRoaming");
ToggleControl *roamingToggle = new ToggleControl("Enable Roaming", "", "", roamingEnabled); ToggleControl *roamingToggle = new ToggleControl(tr("Enable Roaming"), "", "", roamingEnabled);
QObject::connect(roamingToggle, &SshToggle::toggleFlipped, [=](bool state) { QObject::connect(roamingToggle, &SshToggle::toggleFlipped, [=](bool state) {
params.putBool("GsmRoaming", state); params.putBool("GsmRoaming", state);
wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn"))); wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn")));
@ -158,11 +158,11 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
list->addItem(roamingToggle); list->addItem(roamingToggle);
// APN settings // APN settings
ButtonControl *editApnButton = new ButtonControl("APN Setting", "EDIT"); ButtonControl *editApnButton = new ButtonControl(tr("APN Setting"), tr("EDIT"));
connect(editApnButton, &ButtonControl::clicked, [=]() { connect(editApnButton, &ButtonControl::clicked, [=]() {
const bool roamingEnabled = params.getBool("GsmRoaming"); const bool roamingEnabled = params.getBool("GsmRoaming");
const QString cur_apn = QString::fromStdString(params.get("GsmApn")); const QString cur_apn = QString::fromStdString(params.get("GsmApn"));
QString apn = InputDialog::getText("Enter APN", this, "leave blank for automatic configuration", false, -1, cur_apn).trimmed(); QString apn = InputDialog::getText(tr("Enter APN"), this, tr("leave blank for automatic configuration"), false, -1, cur_apn).trimmed();
if (apn.isEmpty()) { if (apn.isEmpty()) {
params.remove("GsmApn"); params.remove("GsmApn");
@ -207,7 +207,7 @@ WifiUI::WifiUI(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi)
checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(49, Qt::SmoothTransformation); checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(49, Qt::SmoothTransformation);
circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(49, Qt::SmoothTransformation); circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(49, Qt::SmoothTransformation);
QLabel *scanning = new QLabel("Scanning for networks..."); QLabel *scanning = new QLabel(tr("Scanning for networks..."));
scanning->setStyleSheet("font-size: 65px;"); scanning->setStyleSheet("font-size: 65px;");
main_layout->addWidget(scanning, 0, Qt::AlignCenter); main_layout->addWidget(scanning, 0, Qt::AlignCenter);
@ -260,7 +260,7 @@ void WifiUI::refresh() {
clearLayout(main_layout); clearLayout(main_layout);
if (wifi->seenNetworks.size() == 0) { if (wifi->seenNetworks.size() == 0) {
QLabel *scanning = new QLabel("Scanning for networks..."); QLabel *scanning = new QLabel(tr("Scanning for networks..."));
scanning->setStyleSheet("font-size: 65px;"); scanning->setStyleSheet("font-size: 65px;");
main_layout->addWidget(scanning, 0, Qt::AlignCenter); main_layout->addWidget(scanning, 0, Qt::AlignCenter);
return; return;
@ -286,17 +286,17 @@ void WifiUI::refresh() {
hlayout->addWidget(ssidLabel, network.connected == ConnectedType::CONNECTING ? 0 : 1); hlayout->addWidget(ssidLabel, network.connected == ConnectedType::CONNECTING ? 0 : 1);
if (network.connected == ConnectedType::CONNECTING) { if (network.connected == ConnectedType::CONNECTING) {
QPushButton *connecting = new QPushButton("CONNECTING..."); QPushButton *connecting = new QPushButton(tr("CONNECTING..."));
connecting->setObjectName("connecting"); connecting->setObjectName("connecting");
hlayout->addWidget(connecting, 2, Qt::AlignLeft); hlayout->addWidget(connecting, 2, Qt::AlignLeft);
} }
// Forget button // Forget button
if (wifi->isKnownConnection(network.ssid) && !wifi->isTetheringEnabled()) { if (wifi->isKnownConnection(network.ssid) && !wifi->isTetheringEnabled()) {
QPushButton *forgetBtn = new QPushButton("FORGET"); QPushButton *forgetBtn = new QPushButton(tr("FORGET"));
forgetBtn->setObjectName("forgetBtn"); forgetBtn->setObjectName("forgetBtn");
QObject::connect(forgetBtn, &QPushButton::clicked, [=]() { QObject::connect(forgetBtn, &QPushButton::clicked, [=]() {
if (ConfirmationDialog::confirm("Forget Wi-Fi Network \"" + QString::fromUtf8(network.ssid) + "\"?", this)) { if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"") + QString::fromUtf8(network.ssid) + "\"?", this)) {
wifi->forgetConnection(network.ssid); wifi->forgetConnection(network.ssid);
} }
}); });

@ -76,7 +76,7 @@ void TermsPage::showEvent(QShowEvent *event) {
main_layout->setContentsMargins(45, 35, 45, 45); main_layout->setContentsMargins(45, 35, 45, 45);
main_layout->setSpacing(0); main_layout->setSpacing(0);
QLabel *title = new QLabel("Terms & Conditions"); QLabel *title = new QLabel(tr("Terms & Conditions"));
title->setStyleSheet("font-size: 90px; font-weight: 600;"); title->setStyleSheet("font-size: 90px; font-weight: 600;");
main_layout->addWidget(title); main_layout->addWidget(title);
@ -104,11 +104,11 @@ void TermsPage::showEvent(QShowEvent *event) {
buttons->setSpacing(45); buttons->setSpacing(45);
main_layout->addLayout(buttons); main_layout->addLayout(buttons);
QPushButton *decline_btn = new QPushButton("Decline"); QPushButton *decline_btn = new QPushButton(tr("Decline"));
buttons->addWidget(decline_btn); buttons->addWidget(decline_btn);
QObject::connect(decline_btn, &QPushButton::clicked, this, &TermsPage::declinedTerms); QObject::connect(decline_btn, &QPushButton::clicked, this, &TermsPage::declinedTerms);
accept_btn = new QPushButton("Scroll to accept"); accept_btn = new QPushButton(tr("Scroll to accept"));
accept_btn->setEnabled(false); accept_btn->setEnabled(false);
accept_btn->setStyleSheet(R"( accept_btn->setStyleSheet(R"(
QPushButton { QPushButton {
@ -123,7 +123,7 @@ void TermsPage::showEvent(QShowEvent *event) {
} }
void TermsPage::enableAccept() { void TermsPage::enableAccept() {
accept_btn->setText("Agree"); accept_btn->setText(tr("Agree"));
accept_btn->setEnabled(true); accept_btn->setEnabled(true);
} }
@ -137,7 +137,7 @@ void DeclinePage::showEvent(QShowEvent *event) {
main_layout->setSpacing(40); main_layout->setSpacing(40);
QLabel *text = new QLabel(this); QLabel *text = new QLabel(this);
text->setText("You must accept the Terms and Conditions in order to use openpilot."); text->setText(tr("You must accept the Terms and Conditions in order to use openpilot."));
text->setStyleSheet(R"(font-size: 80px; font-weight: 300; margin: 200px;)"); text->setStyleSheet(R"(font-size: 80px; font-weight: 300; margin: 200px;)");
text->setWordWrap(true); text->setWordWrap(true);
main_layout->addWidget(text, 0, Qt::AlignCenter); main_layout->addWidget(text, 0, Qt::AlignCenter);
@ -146,12 +146,12 @@ void DeclinePage::showEvent(QShowEvent *event) {
buttons->setSpacing(45); buttons->setSpacing(45);
main_layout->addLayout(buttons); main_layout->addLayout(buttons);
QPushButton *back_btn = new QPushButton("Back"); QPushButton *back_btn = new QPushButton(tr("Back"));
buttons->addWidget(back_btn); buttons->addWidget(back_btn);
QObject::connect(back_btn, &QPushButton::clicked, this, &DeclinePage::getBack); QObject::connect(back_btn, &QPushButton::clicked, this, &DeclinePage::getBack);
QPushButton *uninstall_btn = new QPushButton(QString("Decline, uninstall %1").arg(getBrand())); QPushButton *uninstall_btn = new QPushButton(QString(tr("Decline, uninstall %1")).arg(getBrand()));
uninstall_btn->setStyleSheet("background-color: #B73D3D"); uninstall_btn->setStyleSheet("background-color: #B73D3D");
buttons->addWidget(uninstall_btn); buttons->addWidget(uninstall_btn);
QObject::connect(uninstall_btn, &QPushButton::clicked, [=]() { QObject::connect(uninstall_btn, &QPushButton::clicked, [=]() {

@ -29,45 +29,45 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
std::vector<std::tuple<QString, QString, QString, QString>> toggles{ std::vector<std::tuple<QString, QString, QString, QString>> toggles{
{ {
"OpenpilotEnabledToggle", "OpenpilotEnabledToggle",
"Enable openpilot", tr("Enable openpilot"),
"Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off.", tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."),
"../assets/offroad/icon_openpilot.png", "../assets/offroad/icon_openpilot.png",
}, },
{ {
"IsLdwEnabled", "IsLdwEnabled",
"Enable Lane Departure Warnings", tr("Enable Lane Departure Warnings"),
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h).", tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."),
"../assets/offroad/icon_warning.png", "../assets/offroad/icon_warning.png",
}, },
{ {
"IsRHD", "IsRHD",
"Enable Right-Hand Drive", tr("Enable Right-Hand Drive"),
"Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat.", tr("Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat."),
"../assets/offroad/icon_openpilot_mirrored.png", "../assets/offroad/icon_openpilot_mirrored.png",
}, },
{ {
"IsMetric", "IsMetric",
"Use Metric System", tr("Use Metric System"),
"Display speed in km/h instead of mph.", tr("Display speed in km/h instead of mph."),
"../assets/offroad/icon_metric.png", "../assets/offroad/icon_metric.png",
}, },
{ {
"RecordFront", "RecordFront",
"Record and Upload Driver Camera", tr("Record and Upload Driver Camera"),
"Upload data from the driver facing camera and help improve the driver monitoring algorithm.", tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
"../assets/offroad/icon_monitoring.png", "../assets/offroad/icon_monitoring.png",
}, },
{ {
"DisengageOnAccelerator", "DisengageOnAccelerator",
"Disengage On Accelerator Pedal", tr("Disengage On Accelerator Pedal"),
"When enabled, pressing the accelerator pedal will disengage openpilot.", tr("When enabled, pressing the accelerator pedal will disengage openpilot."),
"../assets/offroad/icon_disengage_on_accelerator.svg", "../assets/offroad/icon_disengage_on_accelerator.svg",
}, },
#ifdef ENABLE_MAPS #ifdef ENABLE_MAPS
{ {
"NavSettingTime24h", "NavSettingTime24h",
"Show ETA in 24h format", tr("Show ETA in 24h format"),
"Use 24h format instead of am/pm", tr("Use 24h format instead of am/pm"),
"../assets/offroad/icon_metric.png", "../assets/offroad/icon_metric.png",
}, },
#endif #endif
@ -79,8 +79,8 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
if (params.getBool("DisableRadar_Allow")) { if (params.getBool("DisableRadar_Allow")) {
toggles.push_back({ toggles.push_back({
"DisableRadar", "DisableRadar",
"openpilot Longitudinal Control", tr("openpilot Longitudinal Control"),
"openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB!", tr("openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB!"),
"../assets/offroad/icon_speed_limit.png", "../assets/offroad/icon_speed_limit.png",
}); });
} }
@ -95,29 +95,29 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
setSpacing(50); setSpacing(50);
addItem(new LabelControl("Dongle ID", getDongleId().value_or("N/A"))); addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A"))));
addItem(new LabelControl("Serial", params.get("HardwareSerial").c_str())); addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str()));
// offroad-only buttons // offroad-only buttons
auto dcamBtn = new ButtonControl("Driver Camera", "PREVIEW", auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"),
"Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)"); tr("Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)"));
connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); }); connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); });
addItem(dcamBtn); addItem(dcamBtn);
auto resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", " "); auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), "");
connect(resetCalibBtn, &ButtonControl::showDescription, this, &DevicePanel::updateCalibDescription); connect(resetCalibBtn, &ButtonControl::showDescription, this, &DevicePanel::updateCalibDescription);
connect(resetCalibBtn, &ButtonControl::clicked, [&]() { connect(resetCalibBtn, &ButtonControl::clicked, [&]() {
if (ConfirmationDialog::confirm("Are you sure you want to reset calibration?", this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), this)) {
params.remove("CalibrationParams"); params.remove("CalibrationParams");
} }
}); });
addItem(resetCalibBtn); addItem(resetCalibBtn);
if (!params.getBool("Passive")) { if (!params.getBool("Passive")) {
auto retrainingBtn = new ButtonControl("Review Training Guide", "REVIEW", "Review the rules, features, and limitations of openpilot"); auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot"));
connect(retrainingBtn, &ButtonControl::clicked, [=]() { connect(retrainingBtn, &ButtonControl::clicked, [=]() {
if (ConfirmationDialog::confirm("Are you sure you want to review the training guide?", this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), this)) {
emit reviewTrainingGuide(); emit reviewTrainingGuide();
} }
}); });
@ -125,7 +125,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
} }
if (Hardware::TICI()) { if (Hardware::TICI()) {
auto regulatoryBtn = new ButtonControl("Regulatory", "VIEW", ""); auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), "");
connect(regulatoryBtn, &ButtonControl::clicked, [=]() { connect(regulatoryBtn, &ButtonControl::clicked, [=]() {
const std::string txt = util::read_file("../assets/offroad/fcc.html"); const std::string txt = util::read_file("../assets/offroad/fcc.html");
RichTextDialog::alert(QString::fromStdString(txt), this); RichTextDialog::alert(QString::fromStdString(txt), this);
@ -143,12 +143,12 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
QHBoxLayout *power_layout = new QHBoxLayout(); QHBoxLayout *power_layout = new QHBoxLayout();
power_layout->setSpacing(30); power_layout->setSpacing(30);
QPushButton *reboot_btn = new QPushButton("Reboot"); QPushButton *reboot_btn = new QPushButton(tr("Reboot"));
reboot_btn->setObjectName("reboot_btn"); reboot_btn->setObjectName("reboot_btn");
power_layout->addWidget(reboot_btn); power_layout->addWidget(reboot_btn);
QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot); QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot);
QPushButton *poweroff_btn = new QPushButton("Power Off"); QPushButton *poweroff_btn = new QPushButton(tr("Power Off"));
poweroff_btn->setObjectName("poweroff_btn"); poweroff_btn->setObjectName("poweroff_btn");
power_layout->addWidget(poweroff_btn); power_layout->addWidget(poweroff_btn);
QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff); QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff);
@ -168,8 +168,8 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
void DevicePanel::updateCalibDescription() { void DevicePanel::updateCalibDescription() {
QString desc = QString desc =
"openpilot requires the device to be mounted within 4° left or right and " tr("openpilot requires the device to be mounted within 4° left or right and "
"within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required."; "within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.");
std::string calib_bytes = Params().get("CalibrationParams"); std::string calib_bytes = Params().get("CalibrationParams");
if (!calib_bytes.empty()) { if (!calib_bytes.empty()) {
try { try {
@ -179,9 +179,9 @@ void DevicePanel::updateCalibDescription() {
if (calib.getCalStatus() != 0) { if (calib.getCalStatus() != 0) {
double pitch = calib.getRpyCalib()[1] * (180 / M_PI); double pitch = calib.getRpyCalib()[1] * (180 / M_PI);
double yaw = calib.getRpyCalib()[2] * (180 / M_PI); double yaw = calib.getRpyCalib()[2] * (180 / M_PI);
desc += QString(" Your device is pointed %1° %2 and %3° %4.") desc += QString(tr(" Your device is pointed %1° %2 and %3° %4."))
.arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "down" : "up", .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? tr("down") : tr("up"),
QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "left" : "right"); QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? tr("left") : tr("right"));
} }
} catch (kj::Exception) { } catch (kj::Exception) {
qInfo() << "invalid CalibrationParams"; qInfo() << "invalid CalibrationParams";
@ -192,51 +192,51 @@ void DevicePanel::updateCalibDescription() {
void DevicePanel::reboot() { void DevicePanel::reboot() {
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), this)) {
// Check engaged again in case it changed while the dialog was open // Check engaged again in case it changed while the dialog was open
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
Params().putBool("DoReboot", true); Params().putBool("DoReboot", true);
} }
} }
} else { } else {
ConfirmationDialog::alert("Disengage to Reboot", this); ConfirmationDialog::alert(tr("Disengage to Reboot"), this);
} }
} }
void DevicePanel::poweroff() { void DevicePanel::poweroff() {
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), this)) {
// Check engaged again in case it changed while the dialog was open // Check engaged again in case it changed while the dialog was open
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
Params().putBool("DoShutdown", true); Params().putBool("DoShutdown", true);
} }
} }
} else { } else {
ConfirmationDialog::alert("Disengage to Power Off", this); ConfirmationDialog::alert(tr("Disengage to Power Off"), this);
} }
} }
SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
gitBranchLbl = new LabelControl("Git Branch"); gitBranchLbl = new LabelControl(tr("Git Branch"));
gitCommitLbl = new LabelControl("Git Commit"); gitCommitLbl = new LabelControl(tr("Git Commit"));
osVersionLbl = new LabelControl("OS Version"); osVersionLbl = new LabelControl(tr("OS Version"));
versionLbl = new LabelControl("Version", "", QString::fromStdString(params.get("ReleaseNotes")).trimmed()); versionLbl = new LabelControl(tr("Version"), "", QString::fromStdString(params.get("ReleaseNotes")).trimmed());
lastUpdateLbl = new LabelControl("Last Update Check", "", "The last time openpilot successfully checked for an update. The updater only runs while the car is off."); lastUpdateLbl = new LabelControl(tr("Last Update Check"), "", tr("The last time openpilot successfully checked for an update. The updater only runs while the car is off."));
updateBtn = new ButtonControl("Check for Update", ""); updateBtn = new ButtonControl(tr("Check for Update"), "");
connect(updateBtn, &ButtonControl::clicked, [=]() { connect(updateBtn, &ButtonControl::clicked, [=]() {
if (params.getBool("IsOffroad")) { if (params.getBool("IsOffroad")) {
fs_watch->addPath(QString::fromStdString(params.getParamPath("LastUpdateTime"))); fs_watch->addPath(QString::fromStdString(params.getParamPath("LastUpdateTime")));
fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdateFailedCount"))); fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdateFailedCount")));
updateBtn->setText("CHECKING"); updateBtn->setText(tr("CHECKING"));
updateBtn->setEnabled(false); updateBtn->setEnabled(false);
} }
std::system("pkill -1 -f selfdrive.updated"); std::system("pkill -1 -f selfdrive.updated");
}); });
auto uninstallBtn = new ButtonControl("Uninstall " + getBrand(), "UNINSTALL"); auto uninstallBtn = new ButtonControl(tr("Uninstall ") + getBrand(), tr("UNINSTALL"));
connect(uninstallBtn, &ButtonControl::clicked, [&]() { connect(uninstallBtn, &ButtonControl::clicked, [&]() {
if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), this)) {
params.putBool("DoUninstall", true); params.putBool("DoUninstall", true);
} }
}); });
@ -250,8 +250,8 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
fs_watch = new QFileSystemWatcher(this); fs_watch = new QFileSystemWatcher(this);
QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) { QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) {
if (path.contains("UpdateFailedCount") && std::atoi(params.get("UpdateFailedCount").c_str()) > 0) { if (path.contains("UpdateFailedCount") && std::atoi(params.get("UpdateFailedCount").c_str()) > 0) {
lastUpdateLbl->setText("failed to fetch update"); lastUpdateLbl->setText(tr("failed to fetch update"));
updateBtn->setText("CHECK"); updateBtn->setText(tr("CHECK"));
updateBtn->setEnabled(true); updateBtn->setEnabled(true);
} else if (path.contains("LastUpdateTime")) { } else if (path.contains("LastUpdateTime")) {
updateLabels(); updateLabels();
@ -272,7 +272,7 @@ void SoftwarePanel::updateLabels() {
versionLbl->setText(getBrandVersion()); versionLbl->setText(getBrandVersion());
lastUpdateLbl->setText(lastUpdate); lastUpdateLbl->setText(lastUpdate);
updateBtn->setText("CHECK"); updateBtn->setText(tr("CHECK"));
updateBtn->setEnabled(true); updateBtn->setEnabled(true);
gitBranchLbl->setText(QString::fromStdString(params.get("GitBranch"))); gitBranchLbl->setText(QString::fromStdString(params.get("GitBranch")));
gitCommitLbl->setText(QString::fromStdString(params.get("GitCommit")).left(10)); gitCommitLbl->setText(QString::fromStdString(params.get("GitCommit")).left(10));
@ -301,7 +301,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
)"); )");
// close button // close button
QPushButton *close_btn = new QPushButton("×"); QPushButton *close_btn = new QPushButton(tr("×"));
close_btn->setStyleSheet(R"( close_btn->setStyleSheet(R"(
QPushButton { QPushButton {
font-size: 140px; font-size: 140px;
@ -327,15 +327,15 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView);
QList<QPair<QString, QWidget *>> panels = { QList<QPair<QString, QWidget *>> panels = {
{"Device", device}, {tr("Device"), device},
{"Network", network_panel(this)}, {tr("Network"), network_panel(this)},
{"Toggles", new TogglesPanel(this)}, {tr("Toggles"), new TogglesPanel(this)},
{"Software", new SoftwarePanel(this)}, {tr("Software"), new SoftwarePanel(this)},
}; };
#ifdef ENABLE_MAPS #ifdef ENABLE_MAPS
auto map_panel = new MapPanel(this); auto map_panel = new MapPanel(this);
panels.push_back({"Navigation", map_panel}); panels.push_back({tr("Navigation"), map_panel});
QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings); QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings);
#endif #endif
@ -367,7 +367,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
nav_btns->addButton(btn); nav_btns->addButton(btn);
sidebar_layout->addWidget(btn, 0, Qt::AlignRight); sidebar_layout->addWidget(btn, 0, Qt::AlignRight);
const int lr_margin = name != "Network" ? 50 : 0; // Network panel handles its own margins const int lr_margin = name != tr("Network") ? 50 : 0; // Network panel handles its own margins
panel->setContentsMargins(lr_margin, 25, lr_margin, 25); panel->setContentsMargins(lr_margin, 25, lr_margin, 25);
ScrollView *panel_frame = new ScrollView(panel, this); ScrollView *panel_frame = new ScrollView(panel, this);

@ -199,7 +199,7 @@ void NvgWindow::updateState(const UIState &s) {
setProperty("is_metric", s.scene.is_metric); setProperty("is_metric", s.scene.is_metric);
setProperty("speed", cur_speed); setProperty("speed", cur_speed);
setProperty("setSpeed", set_speed); setProperty("setSpeed", set_speed);
setProperty("speedUnit", s.scene.is_metric ? "km/h" : "mph"); setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph"));
setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE);
setProperty("status", s.status); setProperty("status", s.status);
@ -266,10 +266,10 @@ void NvgWindow::drawHud(QPainter &p) {
p.setPen(QColor(0xa6, 0xa6, 0xa6, 0xff)); p.setPen(QColor(0xa6, 0xa6, 0xa6, 0xff));
} }
configFont(p, "Inter", 40, "SemiBold"); configFont(p, "Inter", 40, "SemiBold");
QRect max_rect = getTextRect(p, Qt::AlignCenter, "MAX"); QRect max_rect = getTextRect(p, Qt::AlignCenter, tr("MAX"));
max_rect.moveCenter({set_speed_rect.center().x(), 0}); max_rect.moveCenter({set_speed_rect.center().x(), 0});
max_rect.moveTop(set_speed_rect.top() + 27); max_rect.moveTop(set_speed_rect.top() + 27);
p.drawText(max_rect, Qt::AlignCenter, "MAX"); p.drawText(max_rect, Qt::AlignCenter, tr("MAX"));
// Draw set speed // Draw set speed
if (is_cruise_set) { if (is_cruise_set) {
@ -313,16 +313,16 @@ void NvgWindow::drawHud(QPainter &p) {
// "SPEED" // "SPEED"
configFont(p, "Inter", 28, "SemiBold"); configFont(p, "Inter", 28, "SemiBold");
QRect text_speed_rect = getTextRect(p, Qt::AlignCenter, "SPEED"); QRect text_speed_rect = getTextRect(p, Qt::AlignCenter, tr("SPEED"));
text_speed_rect.moveCenter({sign_rect.center().x(), 0}); text_speed_rect.moveCenter({sign_rect.center().x(), 0});
text_speed_rect.moveTop(sign_rect_outer.top() + 22); text_speed_rect.moveTop(sign_rect_outer.top() + 22);
p.drawText(text_speed_rect, Qt::AlignCenter, "SPEED"); p.drawText(text_speed_rect, Qt::AlignCenter, tr("SPEED"));
// "LIMIT" // "LIMIT"
QRect text_limit_rect = getTextRect(p, Qt::AlignCenter, "LIMIT"); QRect text_limit_rect = getTextRect(p, Qt::AlignCenter, tr("LIMIT"));
text_limit_rect.moveCenter({sign_rect.center().x(), 0}); text_limit_rect.moveCenter({sign_rect.center().x(), 0});
text_limit_rect.moveTop(sign_rect_outer.top() + 51); text_limit_rect.moveTop(sign_rect_outer.top() + 51);
p.drawText(text_limit_rect, Qt::AlignCenter, "LIMIT"); p.drawText(text_limit_rect, Qt::AlignCenter, tr("LIMIT"));
// Speed limit value // Speed limit value
configFont(p, "Inter", 70, "Bold"); configFont(p, "Inter", 70, "Bold");

@ -26,16 +26,16 @@ void Reset::doReset() {
if (rm == 0 || fmt == 0) { if (rm == 0 || fmt == 0) {
std::system("sudo reboot"); std::system("sudo reboot");
} }
body->setText("Reset failed. Reboot to try again."); body->setText(tr("Reset failed. Reboot to try again."));
rebootBtn->show(); rebootBtn->show();
} }
void Reset::confirm() { void Reset::confirm() {
const QString confirm_txt = "Are you sure you want to reset your device?"; const QString confirm_txt = tr("Are you sure you want to reset your device?");
if (body->text() != confirm_txt) { if (body->text() != confirm_txt) {
body->setText(confirm_txt); body->setText(confirm_txt);
} else { } else {
body->setText("Resetting device..."); body->setText(tr("Resetting device..."));
rejectBtn->hide(); rejectBtn->hide();
rebootBtn->hide(); rebootBtn->hide();
confirmBtn->hide(); confirmBtn->hide();
@ -50,13 +50,13 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
main_layout->setContentsMargins(45, 220, 45, 45); main_layout->setContentsMargins(45, 220, 45, 45);
main_layout->setSpacing(0); main_layout->setSpacing(0);
QLabel *title = new QLabel("System Reset"); QLabel *title = new QLabel(tr("System Reset"));
title->setStyleSheet("font-size: 90px; font-weight: 600;"); title->setStyleSheet("font-size: 90px; font-weight: 600;");
main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
main_layout->addSpacing(60); main_layout->addSpacing(60);
body = new QLabel("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot."); body = new QLabel(tr("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot."));
body->setWordWrap(true); body->setWordWrap(true);
body->setStyleSheet("font-size: 80px; font-weight: light;"); body->setStyleSheet("font-size: 80px; font-weight: light;");
main_layout->addWidget(body, 1, Qt::AlignTop | Qt::AlignLeft); main_layout->addWidget(body, 1, Qt::AlignTop | Qt::AlignLeft);
@ -65,11 +65,11 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
main_layout->addLayout(blayout); main_layout->addLayout(blayout);
blayout->setSpacing(50); blayout->setSpacing(50);
rejectBtn = new QPushButton("Cancel"); rejectBtn = new QPushButton(tr("Cancel"));
blayout->addWidget(rejectBtn); blayout->addWidget(rejectBtn);
QObject::connect(rejectBtn, &QPushButton::clicked, QCoreApplication::instance(), &QCoreApplication::quit); QObject::connect(rejectBtn, &QPushButton::clicked, QCoreApplication::instance(), &QCoreApplication::quit);
rebootBtn = new QPushButton("Reboot"); rebootBtn = new QPushButton(tr("Reboot"));
blayout->addWidget(rebootBtn); blayout->addWidget(rebootBtn);
#ifdef __aarch64__ #ifdef __aarch64__
QObject::connect(rebootBtn, &QPushButton::clicked, [=]{ QObject::connect(rebootBtn, &QPushButton::clicked, [=]{
@ -77,7 +77,7 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
}); });
#endif #endif
confirmBtn = new QPushButton("Confirm"); confirmBtn = new QPushButton(tr("Confirm"));
confirmBtn->setStyleSheet("background-color: #465BEA;"); confirmBtn->setStyleSheet("background-color: #465BEA;");
blayout->addWidget(confirmBtn); blayout->addWidget(confirmBtn);
QObject::connect(confirmBtn, &QPushButton::clicked, this, &Reset::confirm); QObject::connect(confirmBtn, &QPushButton::clicked, this, &Reset::confirm);
@ -85,7 +85,7 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) {
rejectBtn->setVisible(!recover); rejectBtn->setVisible(!recover);
rebootBtn->setVisible(recover); rebootBtn->setVisible(recover);
if (recover) { if (recover) {
body->setText("Unable to mount data partition. Press confirm to reset your device."); body->setText(tr("Unable to mount data partition. Press confirm to reset your device."));
} }
setStyleSheet(R"( setStyleSheet(R"(

@ -70,13 +70,13 @@ QWidget * Setup::low_voltage() {
inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft); inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft);
inner_layout->addSpacing(80); inner_layout->addSpacing(80);
QLabel *title = new QLabel("WARNING: Low Voltage"); QLabel *title = new QLabel(tr("WARNING: Low Voltage"));
title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;"); title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;");
inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
inner_layout->addSpacing(25); inner_layout->addSpacing(25);
QLabel *body = new QLabel("Power your device in a car with a harness or proceed at your own risk."); QLabel *body = new QLabel(tr("Power your device in a car with a harness or proceed at your own risk."));
body->setWordWrap(true); body->setWordWrap(true);
body->setAlignment(Qt::AlignTop | Qt::AlignLeft); body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
body->setStyleSheet("font-size: 80px; font-weight: 300;"); body->setStyleSheet("font-size: 80px; font-weight: 300;");
@ -89,14 +89,14 @@ QWidget * Setup::low_voltage() {
blayout->setSpacing(50); blayout->setSpacing(50);
main_layout->addLayout(blayout, 0); main_layout->addLayout(blayout, 0);
QPushButton *poweroff = new QPushButton("Power off"); QPushButton *poweroff = new QPushButton(tr("Power off"));
poweroff->setObjectName("navBtn"); poweroff->setObjectName("navBtn");
blayout->addWidget(poweroff); blayout->addWidget(poweroff);
QObject::connect(poweroff, &QPushButton::clicked, this, [=]() { QObject::connect(poweroff, &QPushButton::clicked, this, [=]() {
Hardware::poweroff(); Hardware::poweroff();
}); });
QPushButton *cont = new QPushButton("Continue"); QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn"); cont->setObjectName("navBtn");
blayout->addWidget(cont); blayout->addWidget(cont);
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage); QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
@ -114,12 +114,12 @@ QWidget * Setup::getting_started() {
vlayout->setContentsMargins(165, 280, 100, 0); vlayout->setContentsMargins(165, 280, 100, 0);
main_layout->addLayout(vlayout); main_layout->addLayout(vlayout);
QLabel *title = new QLabel("Getting Started"); QLabel *title = new QLabel(tr("Getting Started"));
title->setStyleSheet("font-size: 90px; font-weight: 500;"); title->setStyleSheet("font-size: 90px; font-weight: 500;");
vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
vlayout->addSpacing(90); vlayout->addSpacing(90);
QLabel *desc = new QLabel("Before we get on the road, let’s finish installation and cover some details."); QLabel *desc = new QLabel(tr("Before we get on the road, let’s finish installation and cover some details."));
desc->setWordWrap(true); desc->setWordWrap(true);
desc->setStyleSheet("font-size: 80px; font-weight: 300;"); desc->setStyleSheet("font-size: 80px; font-weight: 300;");
vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft); vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft);
@ -144,7 +144,7 @@ QWidget * Setup::network_setup() {
main_layout->setContentsMargins(55, 50, 55, 50); main_layout->setContentsMargins(55, 50, 55, 50);
// title // title
QLabel *title = new QLabel("Connect to Wi-Fi"); QLabel *title = new QLabel(tr("Connect to Wi-Fi"));
title->setStyleSheet("font-size: 90px; font-weight: 500;"); title->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
@ -162,7 +162,7 @@ QWidget * Setup::network_setup() {
main_layout->addLayout(blayout); main_layout->addLayout(blayout);
blayout->setSpacing(50); blayout->setSpacing(50);
QPushButton *back = new QPushButton("Back"); QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn"); back->setObjectName("navBtn");
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
blayout->addWidget(back); blayout->addWidget(back);
@ -179,9 +179,9 @@ QWidget * Setup::network_setup() {
cont->setEnabled(success); cont->setEnabled(success);
if (success) { if (success) {
const bool cell = networking->wifi->currentNetworkType() == NetworkType::CELL; const bool cell = networking->wifi->currentNetworkType() == NetworkType::CELL;
cont->setText(cell ? "Continue without Wi-Fi" : "Continue"); cont->setText(cell ? tr("Continue without Wi-Fi") : tr("Continue"));
} else { } else {
cont->setText("Waiting for internet"); cont->setText(tr("Waiting for internet"));
} }
repaint(); repaint();
}); });
@ -235,7 +235,7 @@ QWidget * Setup::software_selection() {
main_layout->setSpacing(0); main_layout->setSpacing(0);
// title // title
QLabel *title = new QLabel("Choose Software to Install"); QLabel *title = new QLabel(tr("Choose Software to Install"));
title->setStyleSheet("font-size: 90px; font-weight: 500;"); title->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
@ -245,12 +245,12 @@ QWidget * Setup::software_selection() {
QButtonGroup *group = new QButtonGroup(widget); QButtonGroup *group = new QButtonGroup(widget);
group->setExclusive(true); group->setExclusive(true);
QWidget *dashcam = radio_button("Dashcam", group); QWidget *dashcam = radio_button(tr("Dashcam"), group);
main_layout->addWidget(dashcam); main_layout->addWidget(dashcam);
main_layout->addSpacing(30); main_layout->addSpacing(30);
QWidget *custom = radio_button("Custom Software", group); QWidget *custom = radio_button(tr("Custom Software"), group);
main_layout->addWidget(custom); main_layout->addWidget(custom);
main_layout->addStretch(); main_layout->addStretch();
@ -260,12 +260,12 @@ QWidget * Setup::software_selection() {
main_layout->addLayout(blayout); main_layout->addLayout(blayout);
blayout->setSpacing(50); blayout->setSpacing(50);
QPushButton *back = new QPushButton("Back"); QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn"); back->setObjectName("navBtn");
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
blayout->addWidget(back); blayout->addWidget(back);
QPushButton *cont = new QPushButton("Continue"); QPushButton *cont = new QPushButton(tr("Continue"));
cont->setObjectName("navBtn"); cont->setObjectName("navBtn");
cont->setEnabled(false); cont->setEnabled(false);
cont->setProperty("primary", true); cont->setProperty("primary", true);
@ -278,7 +278,7 @@ QWidget * Setup::software_selection() {
}); });
QString url = DASHCAM_URL; QString url = DASHCAM_URL;
if (group->checkedButton() != dashcam) { if (group->checkedButton() != dashcam) {
url = InputDialog::getText("Enter URL", this, "for Custom Software"); url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
} }
if (!url.isEmpty()) { if (!url.isEmpty()) {
QTimer::singleShot(1000, this, [=]() { QTimer::singleShot(1000, this, [=]() {
@ -300,7 +300,7 @@ QWidget * Setup::software_selection() {
QWidget * Setup::downloading() { QWidget * Setup::downloading() {
QWidget *widget = new QWidget(); QWidget *widget = new QWidget();
QVBoxLayout *main_layout = new QVBoxLayout(widget); QVBoxLayout *main_layout = new QVBoxLayout(widget);
QLabel *txt = new QLabel("Downloading..."); QLabel *txt = new QLabel(tr("Downloading..."));
txt->setStyleSheet("font-size: 90px; font-weight: 500;"); txt->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(txt, 0, Qt::AlignCenter); main_layout->addWidget(txt, 0, Qt::AlignCenter);
return widget; return widget;
@ -312,13 +312,13 @@ QWidget * Setup::download_failed() {
main_layout->setContentsMargins(55, 225, 55, 55); main_layout->setContentsMargins(55, 225, 55, 55);
main_layout->setSpacing(0); main_layout->setSpacing(0);
QLabel *title = new QLabel("Download Failed"); QLabel *title = new QLabel(tr("Download Failed"));
title->setStyleSheet("font-size: 90px; font-weight: 500;"); title->setStyleSheet("font-size: 90px; font-weight: 500;");
main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
main_layout->addSpacing(67); main_layout->addSpacing(67);
QLabel *body = new QLabel("Ensure the entered URL is valid, and the device’s internet connection is good."); QLabel *body = new QLabel(tr("Ensure the entered URL is valid, and the device’s internet connection is good."));
body->setWordWrap(true); body->setWordWrap(true);
body->setAlignment(Qt::AlignTop | Qt::AlignLeft); body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;"); body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;");
@ -331,14 +331,14 @@ QWidget * Setup::download_failed() {
blayout->setSpacing(50); blayout->setSpacing(50);
main_layout->addLayout(blayout, 0); main_layout->addLayout(blayout, 0);
QPushButton *reboot = new QPushButton("Reboot device"); QPushButton *reboot = new QPushButton(tr("Reboot device"));
reboot->setObjectName("navBtn"); reboot->setObjectName("navBtn");
blayout->addWidget(reboot); blayout->addWidget(reboot);
QObject::connect(reboot, &QPushButton::clicked, this, [=]() { QObject::connect(reboot, &QPushButton::clicked, this, [=]() {
Hardware::reboot(); Hardware::reboot();
}); });
QPushButton *restart = new QPushButton("Start over"); QPushButton *restart = new QPushButton(tr("Start over"));
restart->setObjectName("navBtn"); restart->setObjectName("navBtn");
restart->setProperty("primary", true); restart->setProperty("primary", true);
blayout->addWidget(restart); blayout->addWidget(restart);

@ -20,13 +20,13 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
QVBoxLayout *layout = new QVBoxLayout(prompt); QVBoxLayout *layout = new QVBoxLayout(prompt);
layout->setContentsMargins(100, 250, 100, 100); layout->setContentsMargins(100, 250, 100, 100);
QLabel *title = new QLabel("Update Required"); QLabel *title = new QLabel(tr("Update Required"));
title->setStyleSheet("font-size: 80px; font-weight: bold;"); title->setStyleSheet("font-size: 80px; font-weight: bold;");
layout->addWidget(title); layout->addWidget(title);
layout->addSpacing(75); layout->addSpacing(75);
QLabel *desc = new QLabel("An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB."); QLabel *desc = new QLabel(tr("An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB."));
desc->setWordWrap(true); desc->setWordWrap(true);
desc->setStyleSheet("font-size: 65px;"); desc->setStyleSheet("font-size: 65px;");
layout->addWidget(desc); layout->addWidget(desc);
@ -37,14 +37,14 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
hlayout->setSpacing(30); hlayout->setSpacing(30);
layout->addLayout(hlayout); layout->addLayout(hlayout);
QPushButton *connect = new QPushButton("Connect to Wi-Fi"); QPushButton *connect = new QPushButton(tr("Connect to Wi-Fi"));
connect->setObjectName("navBtn"); connect->setObjectName("navBtn");
QObject::connect(connect, &QPushButton::clicked, [=]() { QObject::connect(connect, &QPushButton::clicked, [=]() {
setCurrentWidget(wifi); setCurrentWidget(wifi);
}); });
hlayout->addWidget(connect); hlayout->addWidget(connect);
QPushButton *install = new QPushButton("Install"); QPushButton *install = new QPushButton(tr("Install"));
install->setObjectName("navBtn"); install->setObjectName("navBtn");
install->setStyleSheet("background-color: #465BEA;"); install->setStyleSheet("background-color: #465BEA;");
QObject::connect(install, &QPushButton::clicked, this, &Updater::installUpdate); QObject::connect(install, &QPushButton::clicked, this, &Updater::installUpdate);
@ -61,7 +61,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
networking->setStyleSheet("Networking { background-color: #292929; border-radius: 13px; }"); networking->setStyleSheet("Networking { background-color: #292929; border-radius: 13px; }");
layout->addWidget(networking, 1); layout->addWidget(networking, 1);
QPushButton *back = new QPushButton("Back"); QPushButton *back = new QPushButton(tr("Back"));
back->setObjectName("navBtn"); back->setObjectName("navBtn");
back->setStyleSheet("padding-left: 60px; padding-right: 60px;"); back->setStyleSheet("padding-left: 60px; padding-right: 60px;");
QObject::connect(back, &QPushButton::clicked, [=]() { QObject::connect(back, &QPushButton::clicked, [=]() {
@ -77,7 +77,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
layout->setContentsMargins(150, 330, 150, 150); layout->setContentsMargins(150, 330, 150, 150);
layout->setSpacing(0); layout->setSpacing(0);
text = new QLabel("Loading..."); text = new QLabel(tr("Loading..."));
text->setStyleSheet("font-size: 90px; font-weight: 600;"); text->setStyleSheet("font-size: 90px; font-weight: 600;");
layout->addWidget(text, 0, Qt::AlignTop); layout->addWidget(text, 0, Qt::AlignTop);
@ -91,7 +91,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
layout->addStretch(); layout->addStretch();
reboot = new QPushButton("Reboot"); reboot = new QPushButton(tr("Reboot"));
reboot->setObjectName("navBtn"); reboot->setObjectName("navBtn");
reboot->setStyleSheet("padding-left: 60px; padding-right: 60px;"); reboot->setStyleSheet("padding-left: 60px; padding-right: 60px;");
QObject::connect(reboot, &QPushButton::clicked, [=]() { QObject::connect(reboot, &QPushButton::clicked, [=]() {
@ -161,7 +161,7 @@ void Updater::updateFinished(int exitCode, QProcess::ExitStatus exitStatus) {
if (exitCode == 0) { if (exitCode == 0) {
Hardware::reboot(); Hardware::reboot();
} else { } else {
text->setText("Update failed"); text->setText(tr("Update failed"));
reboot->show(); reboot->show();
} }
} }

@ -64,26 +64,26 @@ void Sidebar::updateState(const UIState &s) {
ItemStatus connectStatus; ItemStatus connectStatus;
auto last_ping = deviceState.getLastAthenaPingTime(); auto last_ping = deviceState.getLastAthenaPingTime();
if (last_ping == 0) { if (last_ping == 0) {
connectStatus = ItemStatus{{"CONNECT", "OFFLINE"}, warning_color}; connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color};
} else { } else {
connectStatus = nanos_since_boot() - last_ping < 80e9 ? ItemStatus{{"CONNECT", "ONLINE"}, good_color} : ItemStatus{{"CONNECT", "ERROR"}, danger_color}; connectStatus = nanos_since_boot() - last_ping < 80e9 ? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, good_color} : ItemStatus{{tr("CONNECT"), tr("ERROR")}, danger_color};
} }
setProperty("connectStatus", QVariant::fromValue(connectStatus)); setProperty("connectStatus", QVariant::fromValue(connectStatus));
ItemStatus tempStatus = {{"TEMP", "HIGH"}, danger_color}; ItemStatus tempStatus = {{tr("TEMP"), tr("HIGH")}, danger_color};
auto ts = deviceState.getThermalStatus(); auto ts = deviceState.getThermalStatus();
if (ts == cereal::DeviceState::ThermalStatus::GREEN) { if (ts == cereal::DeviceState::ThermalStatus::GREEN) {
tempStatus = {{"TEMP", "GOOD"}, good_color}; tempStatus = {{tr("TEMP"), tr("GOOD")}, good_color};
} else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) {
tempStatus = {{"TEMP", "OK"}, warning_color}; tempStatus = {{tr("TEMP"), tr("OK")}, warning_color};
} }
setProperty("tempStatus", QVariant::fromValue(tempStatus)); setProperty("tempStatus", QVariant::fromValue(tempStatus));
ItemStatus pandaStatus = {{"VEHICLE", "ONLINE"}, good_color}; ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, good_color};
if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) {
pandaStatus = {{"NO", "PANDA"}, danger_color}; pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color};
} else if (s.scene.started && !sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK()) { } else if (s.scene.started && !sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK()) {
pandaStatus = {{"GPS", "SEARCH"}, warning_color}; pandaStatus = {{tr("GPS"), tr("SEARCH")}, warning_color};
} }
setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); setProperty("pandaStatus", QVariant::fromValue(pandaStatus));
} }

@ -33,12 +33,12 @@ int main(int argc, char *argv[]) {
QPushButton *btn = new QPushButton(); QPushButton *btn = new QPushButton();
#ifdef __aarch64__ #ifdef __aarch64__
btn->setText("Reboot"); btn->setText(QObject::tr("Reboot"));
QObject::connect(btn, &QPushButton::clicked, [=]() { QObject::connect(btn, &QPushButton::clicked, [=]() {
Hardware::reboot(); Hardware::reboot();
}); });
#else #else
btn->setText("Exit"); btn->setText(QObject::tr("Exit"));
QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit); QObject::connect(btn, &QPushButton::clicked, &a, &QApplication::quit);
#endif #endif
main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom); main_layout->addWidget(btn, 0, 0, Qt::AlignRight | Qt::AlignBottom);

@ -15,7 +15,7 @@ QString getVersion() {
} }
QString getBrand() { QString getBrand() {
return Params().getBool("Passive") ? "dashcam" : "openpilot"; return Params().getBool("Passive") ? QObject::tr("dashcam") : QObject::tr("openpilot");
} }
QString getBrandVersion() { QString getBrandVersion() {
@ -63,13 +63,13 @@ QString timeAgo(const QDateTime &date) {
s = "now"; s = "now";
} else if (diff < 60 * 60) { } else if (diff < 60 * 60) {
int minutes = diff / 60; int minutes = diff / 60;
s = QString("%1 minute%2 ago").arg(minutes).arg(minutes > 1 ? "s" : ""); s = QString(QObject::tr("%1 minute%2 ago")).arg(minutes).arg(minutes > 1 ? "s" : "");
} else if (diff < 60 * 60 * 24) { } else if (diff < 60 * 60 * 24) {
int hours = diff / (60 * 60); int hours = diff / (60 * 60);
s = QString("%1 hour%2 ago").arg(hours).arg(hours > 1 ? "s" : ""); s = QString(QObject::tr("%1 hour%2 ago")).arg(hours).arg(hours > 1 ? "s" : "");
} else if (diff < 3600 * 24 * 7) { } else if (diff < 3600 * 24 * 7) {
int days = diff / (60 * 60 * 24); int days = diff / (60 * 60 * 24);
s = QString("%1 day%2 ago").arg(days).arg(days > 1 ? "s" : ""); s = QString(QObject::tr("%1 day%2 ago")).arg(days).arg(days > 1 ? "s" : "");
} else { } else {
s = date.date().toString(); s = date.date().toString();
} }

@ -34,16 +34,16 @@ DriveStats::DriveStats(QWidget* parent) : QFrame(parent) {
grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft); grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft);
grid_layout->addWidget(labels.hours = newLabel("0", "number"), row, 2, Qt::AlignLeft); grid_layout->addWidget(labels.hours = newLabel("0", "number"), row, 2, Qt::AlignLeft);
grid_layout->addWidget(newLabel("Drives", "unit"), row + 1, 0, Qt::AlignLeft); grid_layout->addWidget(newLabel((tr("Drives")), "unit"), row + 1, 0, Qt::AlignLeft);
grid_layout->addWidget(labels.distance_unit = newLabel(getDistanceUnit(), "unit"), row + 1, 1, Qt::AlignLeft); grid_layout->addWidget(labels.distance_unit = newLabel(getDistanceUnit(), "unit"), row + 1, 1, Qt::AlignLeft);
grid_layout->addWidget(newLabel("Hours ", "unit"), row + 1, 2, Qt::AlignLeft); grid_layout->addWidget(newLabel(tr("Hours"), "unit"), row + 1, 2, Qt::AlignLeft);
main_layout->addLayout(grid_layout); main_layout->addLayout(grid_layout);
}; };
add_stats_layouts("ALL TIME", all_); add_stats_layouts(tr("ALL TIME"), all_);
main_layout->addStretch(); main_layout->addStretch();
add_stats_layouts("PAST WEEK", week_); add_stats_layouts(tr("PAST WEEK"), week_);
if (auto dongleId = getDongleId()) { if (auto dongleId = getDongleId()) {
QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats"; QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats";

@ -12,7 +12,7 @@ public:
private: private:
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
void updateStats(); void updateStats();
inline QString getDistanceUnit() const { return metric_ ? "KM" : "Miles"; } inline QString getDistanceUnit() const { return metric_ ? tr("KM") : tr("Miles"); }
bool metric_; bool metric_;
QJsonDocument stats_; QJsonDocument stats_;

@ -67,7 +67,7 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s
vlayout->addWidget(sublabel, 1, Qt::AlignTop | Qt::AlignLeft); vlayout->addWidget(sublabel, 1, Qt::AlignTop | Qt::AlignLeft);
} }
QPushButton* cancel_btn = new QPushButton("Cancel"); QPushButton* cancel_btn = new QPushButton(tr("Cancel"));
cancel_btn->setFixedSize(386, 125); cancel_btn->setFixedSize(386, 125);
cancel_btn->setStyleSheet(R"( cancel_btn->setStyleSheet(R"(
font-size: 48px; font-size: 48px;
@ -164,7 +164,7 @@ void InputDialog::handleEnter() {
done(QDialog::Accepted); done(QDialog::Accepted);
emitText(line->text()); emitText(line->text());
} else { } else {
setMessage("Need at least "+QString::number(minLength)+" characters!", false); setMessage(tr("Need at least ") + QString::number(minLength) + tr(" characters!"), false);
} }
} }
@ -217,12 +217,12 @@ ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString
} }
bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) { bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) {
ConfirmationDialog d = ConfirmationDialog(prompt_text, "Ok", "", parent); ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), "", parent);
return d.exec(); return d.exec();
} }
bool ConfirmationDialog::confirm(const QString &prompt_text, QWidget *parent) { bool ConfirmationDialog::confirm(const QString &prompt_text, QWidget *parent) {
ConfirmationDialog d = ConfirmationDialog(prompt_text, "Ok", "Cancel", parent); ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), tr("Cancel"), parent);
return d.exec(); return d.exec();
} }
@ -254,6 +254,6 @@ RichTextDialog::RichTextDialog(const QString &prompt_text, const QString &btn_te
} }
bool RichTextDialog::alert(const QString &prompt_text, QWidget *parent) { bool RichTextDialog::alert(const QString &prompt_text, QWidget *parent) {
auto d = RichTextDialog(prompt_text, "Ok", parent); auto d = RichTextDialog(prompt_text, tr("Ok"), parent);
return d.exec(); return d.exec();
} }

@ -22,12 +22,12 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent
QHBoxLayout *footer_layout = new QHBoxLayout(); QHBoxLayout *footer_layout = new QHBoxLayout();
main_layout->addLayout(footer_layout); main_layout->addLayout(footer_layout);
QPushButton *dismiss_btn = new QPushButton("Close"); QPushButton *dismiss_btn = new QPushButton(tr("Close"));
dismiss_btn->setFixedSize(400, 125); dismiss_btn->setFixedSize(400, 125);
footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft); footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft);
QObject::connect(dismiss_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss); QObject::connect(dismiss_btn, &QPushButton::clicked, this, &AbstractAlert::dismiss);
snooze_btn = new QPushButton("Snooze Update"); snooze_btn = new QPushButton(tr("Snooze Update"));
snooze_btn->setVisible(false); snooze_btn->setVisible(false);
snooze_btn->setFixedSize(550, 125); snooze_btn->setFixedSize(550, 125);
footer_layout->addWidget(snooze_btn, 0, Qt::AlignBottom | Qt::AlignRight); footer_layout->addWidget(snooze_btn, 0, Qt::AlignBottom | Qt::AlignRight);
@ -38,7 +38,7 @@ AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent
snooze_btn->setStyleSheet(R"(color: white; background-color: #4F4F4F;)"); snooze_btn->setStyleSheet(R"(color: white; background-color: #4F4F4F;)");
if (hasRebootBtn) { if (hasRebootBtn) {
QPushButton *rebootBtn = new QPushButton("Reboot and Update"); QPushButton *rebootBtn = new QPushButton(tr("Reboot and Update"));
rebootBtn->setFixedSize(600, 125); rebootBtn->setFixedSize(600, 125);
footer_layout->addWidget(rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight); footer_layout->addWidget(rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight);
QObject::connect(rebootBtn, &QPushButton::clicked, [=]() { Hardware::reboot(); }); QObject::connect(rebootBtn, &QPushButton::clicked, [=]() { Hardware::reboot(); });

@ -83,18 +83,18 @@ PairingPopup::PairingPopup(QWidget *parent) : QDialogBase(parent) {
vlayout->addSpacing(30); vlayout->addSpacing(30);
QLabel *title = new QLabel("Pair your device to your comma account", this); QLabel *title = new QLabel(tr("Pair your device to your comma account"), this);
title->setStyleSheet("font-size: 75px; color: black;"); title->setStyleSheet("font-size: 75px; color: black;");
title->setWordWrap(true); title->setWordWrap(true);
vlayout->addWidget(title); vlayout->addWidget(title);
QLabel *instructions = new QLabel(R"( QLabel *instructions = new QLabel(tr(R"(
<ol type='1' style='margin-left: 15px;'> <ol type='1' style='margin-left: 15px;'>
<li style='margin-bottom: 50px;'>Go to https://connect.comma.ai on your phone</li> <li style='margin-bottom: 50px;'>Go to https://connect.comma.ai on your phone</li>
<li style='margin-bottom: 50px;'>Click "add new device" and scan the QR code on the right</li> <li style='margin-bottom: 50px;'>Click "add new device" and scan the QR code on the right</li>
<li style='margin-bottom: 50px;'>Bookmark connect.comma.ai to your home screen to use it like an app</li> <li style='margin-bottom: 50px;'>Bookmark connect.comma.ai to your home screen to use it like an app</li>
</ol> </ol>
)", this); )"), this);
instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;"); instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;");
instructions->setWordWrap(true); instructions->setWordWrap(true);
vlayout->addWidget(instructions); vlayout->addWidget(instructions);
@ -120,19 +120,19 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
primeLayout->setMargin(0); primeLayout->setMargin(0);
primeWidget->setContentsMargins(60, 50, 60, 50); primeWidget->setContentsMargins(60, 50, 60, 50);
QLabel* subscribed = new QLabel("✓ SUBSCRIBED"); QLabel* subscribed = new QLabel(tr("✓ SUBSCRIBED"));
subscribed->setStyleSheet("font-size: 41px; font-weight: bold; color: #86FF4E;"); subscribed->setStyleSheet("font-size: 41px; font-weight: bold; color: #86FF4E;");
primeLayout->addWidget(subscribed, 0, Qt::AlignTop); primeLayout->addWidget(subscribed, 0, Qt::AlignTop);
primeLayout->addSpacing(60); primeLayout->addSpacing(60);
QLabel* commaPrime = new QLabel("comma prime"); QLabel* commaPrime = new QLabel(tr("comma prime"));
commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;"); commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;");
primeLayout->addWidget(commaPrime, 0, Qt::AlignTop); primeLayout->addWidget(commaPrime, 0, Qt::AlignTop);
primeLayout->addSpacing(20); primeLayout->addSpacing(20);
QLabel* connectUrl = new QLabel("CONNECT.COMMA.AI"); QLabel* connectUrl = new QLabel(tr("CONNECT.COMMA.AI"));
connectUrl->setStyleSheet("font-size: 41px; font-family: Inter SemiBold; color: #A0A0A0;"); connectUrl->setStyleSheet("font-size: 41px; font-family: Inter SemiBold; color: #A0A0A0;");
primeLayout->addWidget(connectUrl, 0, Qt::AlignTop); primeLayout->addWidget(connectUrl, 0, Qt::AlignTop);
@ -145,7 +145,7 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
pointsLayout->setMargin(0); pointsLayout->setMargin(0);
pointsWidget->setContentsMargins(60, 50, 60, 50); pointsWidget->setContentsMargins(60, 50, 60, 50);
QLabel* commaPoints = new QLabel("COMMA POINTS"); QLabel* commaPoints = new QLabel(tr("COMMA POINTS"));
commaPoints->setStyleSheet("font-size: 41px; font-family: Inter SemiBold;"); commaPoints->setStyleSheet("font-size: 41px; font-family: Inter SemiBold;");
pointsLayout->addWidget(commaPoints, 0, Qt::AlignTop); pointsLayout->addWidget(commaPoints, 0, Qt::AlignTop);
@ -181,24 +181,24 @@ PrimeAdWidget::PrimeAdWidget(QWidget* parent) : QFrame(parent) {
main_layout->setContentsMargins(80, 90, 80, 60); main_layout->setContentsMargins(80, 90, 80, 60);
main_layout->setSpacing(0); main_layout->setSpacing(0);
QLabel *upgrade = new QLabel("Upgrade Now"); QLabel *upgrade = new QLabel(tr("Upgrade Now"));
upgrade->setStyleSheet("font-size: 75px; font-weight: bold;"); upgrade->setStyleSheet("font-size: 75px; font-weight: bold;");
main_layout->addWidget(upgrade, 0, Qt::AlignTop); main_layout->addWidget(upgrade, 0, Qt::AlignTop);
main_layout->addSpacing(50); main_layout->addSpacing(50);
QLabel *description = new QLabel("Become a comma prime member at connect.comma.ai"); QLabel *description = new QLabel(tr("Become a comma prime member at connect.comma.ai"));
description->setStyleSheet("font-size: 60px; font-weight: light; color: white;"); description->setStyleSheet("font-size: 60px; font-weight: light; color: white;");
description->setWordWrap(true); description->setWordWrap(true);
main_layout->addWidget(description, 0, Qt::AlignTop); main_layout->addWidget(description, 0, Qt::AlignTop);
main_layout->addStretch(); main_layout->addStretch();
QLabel *features = new QLabel("PRIME FEATURES:"); QLabel *features = new QLabel(tr("PRIME FEATURES:"));
features->setStyleSheet("font-size: 41px; font-weight: bold; color: #E5E5E5;"); features->setStyleSheet("font-size: 41px; font-weight: bold; color: #E5E5E5;");
main_layout->addWidget(features, 0, Qt::AlignBottom); main_layout->addWidget(features, 0, Qt::AlignBottom);
main_layout->addSpacing(30); main_layout->addSpacing(30);
QVector<QString> bullets = {"Remote access", "1 year of storage", "Developer perks"}; QVector<QString> bullets = {tr("Remote access"), tr("1 year of storage"), tr("Developer perks")};
for (auto &b: bullets) { for (auto &b: bullets) {
const QString check = "<b><font color='#465BEA'>✓</font></b> "; const QString check = "<b><font color='#465BEA'>✓</font></b> ";
QLabel *l = new QLabel(check + b); QLabel *l = new QLabel(check + b);
@ -227,20 +227,20 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
finishRegistationLayout->setContentsMargins(30, 75, 30, 45); finishRegistationLayout->setContentsMargins(30, 75, 30, 45);
finishRegistationLayout->setSpacing(0); finishRegistationLayout->setSpacing(0);
QLabel* registrationTitle = new QLabel("Finish Setup"); QLabel* registrationTitle = new QLabel(tr("Finish Setup"));
registrationTitle->setStyleSheet("font-size: 75px; font-weight: bold; margin-left: 55px;"); registrationTitle->setStyleSheet("font-size: 75px; font-weight: bold; margin-left: 55px;");
finishRegistationLayout->addWidget(registrationTitle); finishRegistationLayout->addWidget(registrationTitle);
finishRegistationLayout->addSpacing(30); finishRegistationLayout->addSpacing(30);
QLabel* registrationDescription = new QLabel("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."); QLabel* registrationDescription = new QLabel(tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
registrationDescription->setWordWrap(true); registrationDescription->setWordWrap(true);
registrationDescription->setStyleSheet("font-size: 55px; font-weight: light; margin-left: 55px;"); registrationDescription->setStyleSheet("font-size: 55px; font-weight: light; margin-left: 55px;");
finishRegistationLayout->addWidget(registrationDescription); finishRegistationLayout->addWidget(registrationDescription);
finishRegistationLayout->addStretch(); finishRegistationLayout->addStretch();
QPushButton* pair = new QPushButton("Pair device"); QPushButton* pair = new QPushButton(tr("Pair device"));
pair->setFixedHeight(220); pair->setFixedHeight(220);
pair->setStyleSheet(R"( pair->setStyleSheet(R"(
QPushButton { QPushButton {

@ -4,16 +4,16 @@
#include "selfdrive/ui/qt/api.h" #include "selfdrive/ui/qt/api.h"
#include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/input.h"
SshControl::SshControl() : ButtonControl("SSH Keys", "", "Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username.") { SshControl::SshControl() : ButtonControl(tr("SSH Keys"), "", tr("Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username.")) {
username_label.setAlignment(Qt::AlignRight | Qt::AlignVCenter); username_label.setAlignment(Qt::AlignRight | Qt::AlignVCenter);
username_label.setStyleSheet("color: #aaaaaa"); username_label.setStyleSheet("color: #aaaaaa");
hlayout->insertWidget(1, &username_label); hlayout->insertWidget(1, &username_label);
QObject::connect(this, &ButtonControl::clicked, [=]() { QObject::connect(this, &ButtonControl::clicked, [=]() {
if (text() == "ADD") { if (text() == tr("ADD")) {
QString username = InputDialog::getText("Enter your GitHub username", this); QString username = InputDialog::getText(tr("Enter your GitHub username"), this);
if (username.length() > 0) { if (username.length() > 0) {
setText("LOADING"); setText(tr("LOADING"));
setEnabled(false); setEnabled(false);
getUserKeys(username); getUserKeys(username);
} }
@ -31,10 +31,10 @@ void SshControl::refresh() {
QString param = QString::fromStdString(params.get("GithubSshKeys")); QString param = QString::fromStdString(params.get("GithubSshKeys"));
if (param.length()) { if (param.length()) {
username_label.setText(QString::fromStdString(params.get("GithubUsername"))); username_label.setText(QString::fromStdString(params.get("GithubUsername")));
setText("REMOVE"); setText(tr("REMOVE"));
} else { } else {
username_label.setText(""); username_label.setText("");
setText("ADD"); setText(tr("ADD"));
} }
setEnabled(true); setEnabled(true);
} }
@ -47,13 +47,13 @@ void SshControl::getUserKeys(const QString &username) {
params.put("GithubUsername", username.toStdString()); params.put("GithubUsername", username.toStdString());
params.put("GithubSshKeys", resp.toStdString()); params.put("GithubSshKeys", resp.toStdString());
} else { } else {
ConfirmationDialog::alert(QString("Username '%1' has no keys on GitHub").arg(username), this); ConfirmationDialog::alert(QString(tr("Username '%1' has no keys on GitHub")).arg(username), this);
} }
} else { } else {
if (request->timeout()) { if (request->timeout()) {
ConfirmationDialog::alert("Request timed out", this); ConfirmationDialog::alert(tr("Request timed out"), this);
} else { } else {
ConfirmationDialog::alert(QString("Username '%1' doesn't exist on GitHub").arg(username), this); ConfirmationDialog::alert(QString(tr("Username '%1' doesn't exist on GitHub")).arg(username), this);
} }
} }

@ -10,7 +10,7 @@ class SshToggle : public ToggleControl {
Q_OBJECT Q_OBJECT
public: public:
SshToggle() : ToggleControl("Enable SSH", "", "", Hardware::get_ssh_enabled()) { SshToggle() : ToggleControl(tr("Enable SSH"), "", "", Hardware::get_ssh_enabled()) {
QObject::connect(this, &SshToggle::toggleFlipped, [=](bool state) { QObject::connect(this, &SshToggle::toggleFlipped, [=](bool state) {
Hardware::set_ssh_enabled(state); Hardware::set_ssh_enabled(state);
}); });

@ -1,3 +1,4 @@
test test
playsound playsound
test_sound test_sound
test_translations

@ -0,0 +1,18 @@
#!/bin/bash
set -e
UI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"/..
TEST_TEXT="(WRAPPED_SOURCE_TEXT)"
TEST_TS_FILE=$UI_DIR/translations/main_test_en.ts
TEST_QM_FILE=$UI_DIR/translations/main_test_en.qm
# translation strings
UNFINISHED="<translation type=\"unfinished\"><\/translation>"
TRANSLATED="<translation>$TEST_TEXT<\/translation>"
mkdir -p $UI_DIR/translations
rm -f $TEST_TS_FILE $TEST_QM_FILE
lupdate -recursive "$UI_DIR" -ts $TEST_TS_FILE
sed -i "s/$UNFINISHED/$TRANSLATED/" $TEST_TS_FILE
lrelease $TEST_TS_FILE

@ -1,10 +1,25 @@
#define CATCH_CONFIG_RUNNER #define CATCH_CONFIG_RUNNER
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
#include <QCoreApplication>
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QTranslator>
int main(int argc, char **argv) { int main(int argc, char **argv) {
// unit tests for Qt // unit tests for Qt
QCoreApplication app(argc, argv); QApplication app(argc, argv);
QString language_file = "main_test_en";
qDebug() << "Loading language:" << language_file;
QTranslator translator;
QString translationsPath = QDir::cleanPath(qApp->applicationDirPath() + "/../translations");
if (!translator.load(language_file, translationsPath)) {
qDebug() << "Failed to load translation file!";
}
app.installTranslator(&translator);
const int res = Catch::Session().run(argc, argv); const int res = Catch::Session().run(argc, argv);
return (res < 0xff ? res : 0xff); return (res < 0xff ? res : 0xff);
} }

@ -0,0 +1,51 @@
#include "catch2/catch.hpp"
#include "common/params.h"
#include "selfdrive/ui/qt/window.h"
const QString TEST_TEXT = "(WRAPPED_SOURCE_TEXT)"; // what each string should be translated to
QRegExp RE_NUM("\\d*");
QStringList getParentWidgets(QWidget* widget){
QStringList parentWidgets;
while (widget->parentWidget() != Q_NULLPTR) {
widget = widget->parentWidget();
parentWidgets.append(widget->metaObject()->className());
}
return parentWidgets;
}
template <typename T>
void checkWidgetTrWrap(MainWindow &w) {
int i = 0;
for (auto widget : w.findChildren<T>()) {
const QString text = widget->text();
SECTION(text.toStdString() + "-" + std::to_string(i)) {
bool isNumber = RE_NUM.exactMatch(text);
bool wrapped = text.contains(TEST_TEXT);
QString parentWidgets = getParentWidgets(widget).join("->");
if (!text.isEmpty() && !isNumber && !wrapped) {
FAIL(("\"" + text + "\" must be wrapped. Parent widgets: " + parentWidgets).toStdString());
}
// warn if source string wrapped, but UI adds text
// TODO: add way to ignore this
if (wrapped && text != TEST_TEXT) {
WARN(("\"" + text + "\" is dynamic and needs a custom retranslate function. Parent widgets: " + parentWidgets).toStdString());
}
}
i++;
}
}
// Tests all strings in the UI are wrapped with tr()
TEST_CASE("UI: test all strings wrapped") {
Params().remove("HardwareSerial");
Params().remove("DongleId");
qputenv("TICI", "1");
MainWindow w;
checkWidgetTrWrap<QPushButton*>(w);
checkWidgetTrWrap<QLabel*>(w);
}

@ -59,6 +59,7 @@ function install_ubuntu_common_requirements() {
qtmultimedia5-dev \ qtmultimedia5-dev \
qtlocation5-dev \ qtlocation5-dev \
qtpositioning5-dev \ qtpositioning5-dev \
qttools5-dev-tools \
libqt5sql5-sqlite \ libqt5sql5-sqlite \
libqt5svg5-dev \ libqt5svg5-dev \
libqt5x11extras5-dev \ libqt5x11extras5-dev \

Loading…
Cancel
Save