Back to Repositories

Validating UI Translation Implementation in OpenPilot

This test suite validates internationalization (i18n) implementation in the OpenPilot UI, ensuring all text strings are properly wrapped with translation functions. It systematically checks UI components like buttons and labels to maintain consistent translation support across the interface.

Test Coverage Overview

The test suite provides comprehensive coverage of UI string translation wrapping, focusing on QPushButton and QLabel elements.

Key areas tested include:
  • String wrapping validation for all UI text elements
  • Dynamic text handling verification
  • Number-only text exclusion
  • Parent widget hierarchy tracking

Implementation Analysis

The testing approach uses a template-based implementation to check different widget types systematically. It employs Catch2 testing framework with Qt-specific validation patterns.

Key implementation features:
  • Template function for widget type flexibility
  • Regular expression matching for number detection
  • Parent widget traversal functionality
  • Environment parameter management

Technical Details

Testing infrastructure includes:
  • Catch2 testing framework
  • Qt UI testing utilities
  • Custom parameter management
  • Environment variable configuration
  • Regular expression pattern matching
  • Widget hierarchy traversal functions

Best Practices Demonstrated

The test implementation showcases several testing best practices for UI internationalization validation.

Notable practices include:
  • Systematic widget traversal
  • Clear failure messaging with context
  • Dynamic text handling warnings
  • Clean test environment setup
  • Modular test structure

commaai/openpilot

selfdrive/ui/tests/test_translations.cc

            
#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) {
  for (auto widget : w.findChildren<T>()) {
  const QString text = widget->text();
    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());
    }
  }
}

// Tests all strings in the UI are wrapped with tr()
TEST_CASE("UI: test all strings wrapped") {
  Params().remove("LanguageSetting");
  Params().remove("HardwareSerial");
  Params().remove("DongleId");
  qputenv("TICI", "1");

  MainWindow w;
  checkWidgetTrWrap<QPushButton*>(w);
  checkWidgetTrWrap<QLabel*>(w);
}