Home > OS >  How to do dynamic translation during runtime using UI forms in Qt?
How to do dynamic translation during runtime using UI forms in Qt?

Time:06-02

I am looking for a relatively minimal example how to dynamically translation. I am creating a tutorial on using the Qt resource system along with UI and TS files. For this purpose I've created a basic UI form:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>widget_form</class>
 <widget  name="widget_form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>860</width>
    <height>527</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Get Started</string>
  </property>
  <layout  name="verticalLayout_2">
   <item>
    <layout  name="horizontalLayout_2">
     <item>
      <widget  name="btn_import_file">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="styleSheet">
        <string notr="true">image: url(:/icons/files/import);</string>
       </property>
       <property name="text">
        <string/>
       </property>
      </widget>
     </item>
     <item>
      <widget  name="btn_check_file">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="styleSheet">
        <string notr="true">image: url(:/icons/files/check);</string>
       </property>
       <property name="text">
        <string/>
       </property>
      </widget>
     </item>
     <item>
      <widget  name="btn_delete_file">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="styleSheet">
        <string notr="true">image: url(:/icons/files/delete);</string>
       </property>
       <property name="text">
        <string/>
       </property>
      </widget>
     </item>
     <item>
      <widget  name="btn_add_file">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="styleSheet">
        <string notr="true">image: url(:/icons/files/add);</string>
       </property>
       <property name="text">
        <string/>
       </property>
      </widget>
     </item>
     <item>
      <widget  name="cb_change_lang">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="styleSheet">
        <string notr="true"/>
       </property>
      </widget>
     </item>
    </layout>
   </item>
   <item>
    <layout  name="horizontalLayout">
     <item>
      <widget  name="pte_file_view">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
         <horstretch>1</horstretch>
         <verstretch>1</verstretch>
        </sizepolicy>
       </property>
      </widget>
     </item>
     <item>
      <widget  name="vline_right">
       <property name="orientation">
        <enum>Qt::Vertical</enum>
       </property>
      </widget>
     </item>
     <item>
      <layout  name="verticalLayout">
       <item>
        <widget  name="btn_csv">
         <property name="sizePolicy">
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="styleSheet">
          <string notr="true">image: url(:/icons/files/csv);</string>
         </property>
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item>
        <widget  name="btn_code">
         <property name="sizePolicy">
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="styleSheet">
          <string notr="true">image: url(:/icons/files/code);</string>
         </property>
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item>
        <widget  name="btn_json">
         <property name="sizePolicy">
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="styleSheet">
          <string notr="true">image: url(:/icons/files/json);</string>
         </property>
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item>
        <widget  name="btn_xml">
         <property name="sizePolicy">
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="styleSheet">
          <string notr="true">image: url(:/icons/files/xml);</string>
         </property>
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
      </layout>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources>
  <include location="../resources.qrc"/>
 </resources>
 <connections/>
</ui>

which I add to a QRC file along with a bunch of images that I use as icons:

<RCC>
  <qresource prefix="icons/files">
    <file alias="xml">icons/file-formats/icons8-xml-96.png</file>
    <file alias="json">icons/file-formats/icons8-json-96.png</file>
    <file alias="csv">icons/file-formats/icons8-csv-96.png</file>
    <file alias="code">icons/file-formats/icons8-code-96.png</file>
  </qresource>
  <qresource prefix="icons/files">
    <file alias="import">icons/file-ops/icons8-import-96.png</file>
    <file alias="delete">icons/file-ops/icons8-delete-file-96.png</file>
    <file alias="check">icons/file-ops/icons8-check-file-96.png</file>
    <file alias="add">icons/file-ops/icons8-add-file-96.png</file>
  </qresource>
  <qresource prefix="icons/flags">
    <file alias="fr">icons/flags/icons8-france-80.png</file>
    <file alias="de">icons/flags/icons8-germany-80.png</file>
    <file alias="gb">icons/flags/icons8-great-britain-80.png</file>
    <file alias="ru">icons/flags/icons8-russian-federation-80.png</file>
  </qresource>
  <qresource prefix="forms">
     <file alias="main">ui/widget.ui</file>
  </qresource>
</RCC>

I manage my project with the following CMakeLists.txt:

cmake_minimum_required(VERSION 3.13)

project(ExampleGetStarted LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_POSITION_INDEPENDENT_CODE OFF)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

if(CMAKE_VERSION VERSION_LESS "3.7.0")
    set(CMAKE_INCLUDE_CURRENT_DIR ON)
endif()

find_package(Qt5
    COMPONENTS
        Widgets
        Xml
        XmlPatterns
        Concurrent
        UiTools
        LinguistTools
    REQUIRED
)
#find_package(Qt5XmlPatterns REQUIRED)

# TODO https://stackoverflow.com/questions/51217734/how-to-add-qt-translations-to-cmake

set(RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/resources/resources.qrc)
set(TS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/resources/translations)
qt5_create_translation(TS_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_DIR}/en.ts ${TS_DIR}/de.ts)
#qt5_add_translation(TS_FILES_FINISHED ${TS_DIR}/en.ts ${TS_DIR}/de.ts)
# TODO Convert configure_file to custom post-build action. QM files are initially not available
#configure_file(${QM_FILES} ${CMAKE_BINARY_DIR} COPYONLY)
#qt5_add_translation(QM_FILES ${TS_FILES})

add_executable(example_get_started
    example_get_started.cpp
    ${RESOURCES}
    ${TS_FILES}
)
target_link_libraries(example_get_started
    Qt5::Widgets
    Qt5::Xml Qt5::XmlPatterns
    Qt5::Concurrent
    Qt5::UiTools
    #Qt5::LinguistTools
)
# Copy translations to binary directory where executable can access thoses
foreach(TS_FILE ${TS_FILES})
    add_custom_command(
        TARGET example_get_started POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy
                ${TS_FILE}
                ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
        COMMENT "Copied ${TS_FILE} to binary directory"
        )
endforeach(TS_FILE)

The UI form is loaded with the following code (work in progress):

#include <vector>
#include <utility>
#include <iostream>

#include <QApplication>
#include <QtWidgets>
#include <QtUiTools>
#include <QFile>
#include <QTranslator>

QScopedPointer<QTranslator> translator(new QTranslator());

static QWidget *loadUiFile(QWidget *parent)
{
    QFile file(":/forms/main");
    file.open(QIODevice::ReadOnly);

    QUiLoader loader;
    return loader.load(&file, parent);
}

static void retranslate(QString lang_code)
{
    std::cout << "Translating to " << lang_code.toStdString() << std::endl;
    std::cout << "Looking into directory \"" << QApplication::applicationDirPath().toStdString() << "\"" << std::endl;
    if (lang_code == QString("fr"))
    {
        if (translator->load("fr", QApplication::applicationDirPath()))
        {
            QApplication::instance()->installTranslator(translator.data());
            std::cout << "Switched to " << lang_code.toStdString() << std::endl;
        }
        else
            std::cout << "Unable to load translation for " << lang_code.toStdString() << std::endl;
    }
    else if (lang_code == QString("de"))
    {
        if (translator->load("de", QApplication::applicationDirPath()))
        {
            QApplication::instance()->installTranslator(translator.data());
            std::cout << "Switched to " << lang_code.toStdString() << std::endl;
        }
        else
            std::cout << "Unable to load translation for " << lang_code.toStdString() << std::endl;
    }
    else if (lang_code == QString("ru"))
    {
        if (translator->load("ru", QApplication::applicationDirPath()))
        {
            QApplication::instance()->installTranslator(translator.data());
            std::cout << "Switched to " << lang_code.toStdString() << std::endl;
        }
        else
            std::cout << "Unable to load translation for " << lang_code.toStdString() << std::endl;
    }
    else
    {
        QApplication::instance()->removeTranslator(translator.data());
    }
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    // Translations need to be created before widgets they affect
    /*
if (!translator->load("en_GB.qm"))
    std::cout << "Unable to load translation file \"en_GB\"" << std::endl;
app.installTranslator(translator.data());
if (!translator->load("de_DE.qm"))
    std::cout << "Unable to load translation file \"de_DE\"" << std::endl;
app.installTranslator(translator.data());
std::cout << translator->language().toStdString() << std::endl;
*/

    QWidget *widget = loadUiFile(nullptr);
    widget->findChild<QPushButton *>("btn_add_file")->setToolTip(QObject::tr("Create a new file"));
    widget->findChild<QPushButton *>("btn_check_file")->setToolTip(QObject::tr("Check the opened file's contents for errors"));
    widget->findChild<QPushButton *>("btn_delete_file")->setToolTip(QObject::tr("Deletes the opened file's contents"));
    widget->findChild<QPushButton *>("btn_import_file")->setToolTip(QObject::tr("Import existing file"));
    QComboBox *cb_change_lang = widget->findChild<QComboBox *>("cb_change_lang");
    std::vector<std::pair<QString, QIcon>> langs = {
        std::pair<QString, QIcon>(QObject::tr("English (en)"), QIcon(":/icons/flags/gb")),
        std::pair<QString, QIcon>(QObject::tr("Deutsch (de)"), QIcon(":/icons/flags/de")),
        std::pair<QString, QIcon>(QObject::tr("Français (fr)"), QIcon(":/icons/flags/fr")),
        std::pair<QString, QIcon>(QObject::tr("Руский (ru)"), QIcon(":/icons/flags/ru"))};
    auto lang_idx = 0;
    for (auto lang : langs)
    {
        cb_change_lang->insertItem(lang_idx, lang.first);
        cb_change_lang->setItemIcon(lang_idx, lang.second);
        lang_idx  ;
    }

    QRegularExpression regex("\\(([^()] )\\)");
    QObject::connect(cb_change_lang,
                     qOverload<int>(&QComboBox::currentIndexChanged),
                     [=](int idx)
                     {
                         retranslate(regex.match(langs.at(cb_change_lang->currentIndex()).first).captured(1));
                     });
    std::cout << regex.match(langs.at(cb_change_lang->currentIndex()).first).captured(1).toStdString() << std::endl;

    widget->show();
    return app.exec();
}

The widget looks like this:

Widget that uses QRC, TS and UI files

I would like to keep the creation of my widget as it is (UI file loaded in C and some extra functionality added to it). The reason why my QTranslator is a global is because of that plus the fact that a translator needs to live as long as QApplication's instance is alive.

The way it's supposed to work (but currently is not) is for the tooltips of each button to be translated whenever the currently selected item inside the QComboBox (top right corner in the screenshot) is changed. I am very new to the translation facilities of Qt and the documentation is not really stellar in this regard (personal opinion here).

Before I started working on the dynamic translation, I loaded one of the QM files inside my main() and it worked - the tooltips were in the language the QM file was made for. Only after adding the new functionality I don't see any change.

CodePudding user response:

I searched around and also asked in the official Qt forum. Some of the tips I was given made me rework my code:

CMakeLists.txt

cmake_minimum_required(VERSION 3.13)

project(ExampleGetStarted LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

find_package(Qt5
    COMPONENTS
        Core
        Widgets
        Xml
        XmlPatterns
        Concurrent
        UiTools
        LinguistTools
    REQUIRED
)

set(RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/resources/resources.qrc)
set(TS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/resources/translations)
qt5_create_translation(TS_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_DIR}/en.ts ${TS_DIR}/de.ts ${TS_DIR}/fr.ts ${TS_DIR}/ru.ts)


add_executable(example_get_started
    example_get_started.cpp
    ${RESOURCES}
    ${TS_FILES}
)
target_link_libraries(example_get_started
    Qt5::Core
    Qt5::Widgets
    Qt5::Xml Qt5::XmlPatterns
    Qt5::Concurrent
    Qt5::UiTools
)
# Copy translations to binary directory where executable can access these
foreach(TS_FILE ${TS_FILES})
    add_custom_command(
        TARGET example_get_started POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy
                ${TS_FILE}
                ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
        COMMENT "Copied ${TS_FILE} to binary directory"
        )
endforeach(TS_FILE)

CPP file

#include <vector>
#include <utility>

#include <QApplication>
#include <QtWidgets>
#include <QtUiTools>
#include <QFile>
#include <QTranslator>
#include <QDebug>

// Needs to be included so that the header file is generated. Note that if the UI file is in a subdirectory, that also needs to be specified here
#include "resources/ui/ui_widget.h"

QScopedPointer<QTranslator> translator(new QTranslator());

class MainWidget : public QWidget
{
    Q_OBJECT

    Ui::widget_form *ui;
    QString lang_curr = "en";

public:
    MainWidget(QWidget* parent=nullptr)
        : QWidget(parent),
          ui(new Ui::widget_form)
    {
        ui->setupUi(this);

        QRegularExpression regex("\\(([^()] )\\)");
        QObject::connect(ui->cb_change_lang,
                         qOverload<int>(&QComboBox::currentIndexChanged),
                         [=](int idx)
                         {
                             retranslate(regex.match(ui->cb_change_lang->currentText()).captured(1));
                         });
    }

    ~MainWidget()
    {
        delete ui;
    }
protected:
    void changeEvent(QEvent *event) override
    {
        if (event->type() == QEvent::LanguageChange)
        {
            ui->retranslateUi(this);
        }

        return QWidget::changeEvent(event);
    }
private:
    void retranslate(QString lang_code)
    {
        if (lang_code == QString("fr"))
        {
            if (translator->load("fr", QApplication::applicationDirPath()))
            {
                QApplication::instance()->removeTranslator(translator.data());
                QApplication::instance()->installTranslator(translator.data());
                lang_curr = lang_code;
            }
            else
                qDebug() << "Unable to load translation for " << lang_code;
        }
        else if (lang_code == QString("de"))
        {
            if (translator->load("de", QApplication::applicationDirPath()))
            {
                QApplication::instance()->removeTranslator(translator.data());
                QApplication::instance()->installTranslator(translator.data());
                lang_curr = lang_code;
            }
            else
                qDebug() << "Unable to load translation for " << lang_code;
        }
        else if (lang_code == QString("ru"))
        {
            if (translator->load("ru", QApplication::applicationDirPath()))
            {
                QApplication::instance()->removeTranslator(translator.data());
                QApplication::instance()->installTranslator(translator.data());
                lang_curr = lang_code;
            }
            else
                qDebug() << "Unable to load translation for " << lang_code;
        }
        else
        {
            if (translator->load("en", QApplication::applicationDirPath()))
            {
                QApplication::instance()->removeTranslator(translator.data());
                QApplication::instance()->installTranslator(translator.data());
                lang_curr = lang_code;
            }
            else
                qDebug() << "Unable to load translation for " << lang_code;
        }
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MainWidget mw;
    mw.show();
    return app.exec();
}

#include "example_get_started.moc"

XML UI form file

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>widget_form</class>
 <widget  name="widget_form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>860</width>
    <height>527</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string notr="true">Get Started</string>
  </property>
  <layout  name="verticalLayout_2">
   <item>
    <layout  name="horizontalLayout_2">
     <item>
      <widget  name="btn_create_file">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="toolTip">
        <string>Create a new file</string>
       </property>
       <property name="styleSheet">
        <string notr="true">image: url(:/icons/files/create);</string>
       </property>
       <property name="text">
        <string/>
       </property>
      </widget>
     </item>
     <item>
      <widget  name="btn_import_file">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="toolTip">
        <string>Import existing file</string>
       </property>
       <property name="styleSheet">
        <string notr="true">image: url(:/icons/files/import);</string>
       </property>
       <property name="text">
        <string/>
       </property>
      </widget>
     </item>
     <item>
      <widget  name="btn_delete_file">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="toolTip">
        <string>Delete the opened file's contents</string>
       </property>
       <property name="styleSheet">
        <string notr="true">image: url(:/icons/files/delete);</string>
       </property>
       <property name="text">
        <string/>
       </property>
      </widget>
     </item>
     <item>
      <widget  name="btn_check_file">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="toolTip">
        <string>Check the opened file's contents for errors</string>
       </property>
       <property name="styleSheet">
        <string notr="true">image: url(:/icons/files/check);</string>
       </property>
       <property name="text">
        <string/>
       </property>
      </widget>
     </item>
     <item>
      <widget  name="cb_change_lang">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="styleSheet">
        <string notr="true"/>
       </property>
       <item>
        <property name="text">
         <string>English (en)</string>
        </property>
        <property name="icon">
         <iconset resource="../resources.qrc">
          <normaloff>:/icons/flags/gb</normaloff>:/icons/flags/gb</iconset>
        </property>
       </item>
       <item>
        <property name="text">
         <string>Deutsch (de)</string>
        </property>
        <property name="icon">
         <iconset resource="../resources.qrc">
          <normaloff>:/icons/flags/de</normaloff>:/icons/flags/de</iconset>
        </property>
       </item>
       <item>
        <property name="text">
         <string>Français (fr)</string>
        </property>
        <property name="icon">
         <iconset resource="../resources.qrc">
          <normaloff>:/icons/flags/fr</normaloff>:/icons/flags/fr</iconset>
        </property>
       </item>
       <item>
        <property name="text">
         <string>Руский (ru)</string>
        </property>
        <property name="icon">
         <iconset resource="../resources.qrc">
          <normaloff>:/icons/flags/ru</normaloff>:/icons/flags/ru</iconset>
        </property>
       </item>
      </widget>
     </item>
    </layout>
   </item>
   <item>
    <layout  name="horizontalLayout">
     <item>
      <widget  name="pte_file_view">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
         <horstretch>1</horstretch>
         <verstretch>1</verstretch>
        </sizepolicy>
       </property>
      </widget>
     </item>
     <item>
      <widget  name="vline_right">
       <property name="orientation">
        <enum>Qt::Vertical</enum>
       </property>
      </widget>
     </item>
     <item>
      <layout  name="verticalLayout">
       <item>
        <widget  name="btn_csv">
         <property name="sizePolicy">
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="toolTip">
          <string>Create CSV file</string>
         </property>
         <property name="styleSheet">
          <string notr="true">image: url(:/icons/files/csv);</string>
         </property>
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item>
        <widget  name="btn_code">
         <property name="sizePolicy">
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="toolTip">
          <string>Create script file</string>
         </property>
         <property name="styleSheet">
          <string notr="true">image: url(:/icons/files/code);</string>
         </property>
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item>
        <widget  name="btn_json">
         <property name="sizePolicy">
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="toolTip">
          <string>Create JSON file</string>
         </property>
         <property name="styleSheet">
          <string notr="true">image: url(:/icons/files/json);</string>
         </property>
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item>
        <widget  name="btn_xml">
         <property name="sizePolicy">
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="toolTip">
          <string>Create XML file</string>
         </property>
         <property name="styleSheet">
          <string notr="true">image: url(:/icons/files/xml);</string>
         </property>
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
      </layout>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources>
  <include location="../resources.qrc"/>
 </resources>
 <connections/>
</ui>

What I did is:

  • Replace the QUiLoader with the standard way of loading the UI form file, which I have more experience with (I have very little experience with UI forms in general :D)

  • Moved all the tooltips I was setting in the source code to the UI form file - more or that in the next point

  • Added changeEvent() to the widget in the source coded that use the UI form file - by handling the QEvent::LanguageChanged and using this way of loading the UI one gets access to the retranslateUi() function. This function is automatically generated when the header (here ui_widget.h) that represents the UI form file is generated. The function makes it possible to automatically retranslate all strings marked as translatable (in Qt Designer this is the translateable checkbox). Initially (before previous point) I saw that retranslateUi() does not include any of my tooltips. Apparently mixing strings in source code with UI form strings doesn't work as I thought it will. By moving the tooltips to the UI form file I got from the initial

     void retranslateUi(QWidget *widget_form)
     {
         btn_create_file->setText(QString());
         btn_import_file->setText(QString());
         btn_delete_file->setText(QString());
         btn_check_file->setText(QString());
         cb_change_lang->setItemText(0, QCoreApplication::translate("widget_form", "English (en)", nullptr));
         cb_change_lang->setItemText(1, QCoreApplication::translate("widget_form", "Deutsch (de)", nullptr));
         cb_change_lang->setItemText(2, QCoreApplication::translate("widget_form", "Fran\303\247ais (fr)", nullptr));
         cb_change_lang->setItemText(3, QCoreApplication::translate("widget_form", "\320\240\321\203\321\201\320\272\320\270\320\271 (ru)", nullptr));
         btn_csv->setText(QString());
         btn_code->setText(QString());
         btn_json->setText(QString());
         btn_xml->setText(QString());
         (void)widget_form;
     } // retranslateUi
    

    to

     void retranslateUi(QWidget *widget_form)
     {
     #if QT_CONFIG(tooltip)
         btn_create_file->setToolTip(QCoreApplication::translate("widget_form", "Create a new file", nullptr));
     #endif // QT_CONFIG(tooltip)
         btn_create_file->setText(QString());
     #if QT_CONFIG(tooltip)
         btn_import_file->setToolTip(QCoreApplication::translate("widget_form", "Import existing file", nullptr));
     #endif // QT_CONFIG(tooltip)
         btn_import_file->setText(QString());
     #if QT_CONFIG(tooltip)
         btn_delete_file->setToolTip(QCoreApplication::translate("widget_form", "Delete the opened file's contents", nullptr));
     #endif // QT_CONFIG(tooltip)
         btn_delete_file->setText(QString());
     #if QT_CONFIG(tooltip)
         btn_check_file->setToolTip(QCoreApplication::translate("widget_form", "Check the opened file's contents for errors", nullptr));
     #endif // QT_CONFIG(tooltip)
         btn_check_file->setText(QString());
         cb_change_lang->setItemText(0, QCoreApplication::translate("widget_form", "English (en)", nullptr));
         cb_change_lang->setItemText(1, QCoreApplication::translate("widget_form", "Deutsch (de)", nullptr));
         cb_change_lang->setItemText(2, QCoreApplication::translate("widget_form", "Fran\303\247ais (fr)", nullptr));
         cb_change_lang->setItemText(3, QCoreApplication::translate("widget_form", "\320\240\321\203\321\201\320\272\320\270\320\271 (ru)", nullptr));
    
     #if QT_CONFIG(tooltip)
         btn_csv->setToolTip(QCoreApplication::translate("widget_form", "Create CSV file", nullptr));
     #endif // QT_CONFIG(tooltip)
         btn_csv->setText(QString());
     #if QT_CONFIG(tooltip)
         btn_code->setToolTip(QCoreApplication::translate("widget_form", "Create script file", nullptr));
     #endif // QT_CONFIG(tooltip)
         btn_code->setText(QString());
     #if QT_CONFIG(tooltip)
         btn_json->setToolTip(QCoreApplication::translate("widget_form", "Create JSON file", nullptr));
     #endif // QT_CONFIG(tooltip)
         btn_json->setText(QString());
     #if QT_CONFIG(tooltip)
         btn_xml->setToolTip(QCoreApplication::translate("widget_form", "Create XML file", nullptr));
     #endif // QT_CONFIG(tooltip)
         btn_xml->setText(QString());
         (void)widget_form;
     } // retranslateUi
    

Note: I forgot to mark the combobox items as not translatable so that's why those are present here. It's considered generally a good practice to put the language names in their original language and for those to remain invariant to the change in the locale since otherwise users will be confused (not knowing English but wanting to switch to Chinese with the language name written in English is a guarantee for poor UX).

So the final results is (used Google Translate for French and Russian :P)

UI - English

UI - German

UI - French

UI - Russian

  • Related