Although English is one of the most common languages, a well designed user interface should be shown using the language most appropriate for the user. CopperSpice provides built in support for text translations. This example will show how to switch between English and German text at run time by using an external translation file.
class MainWindow : public QWidget
{
CS_OBJECT(MainWindow)
public:
MainWindow();
protected:
void changeEvent(QEvent *event) override;
private:
void setLanguage(const QString language);
void retranslate_UI();
QRadioButton *rb0;
QRadioButton *rb1;
QRadioButton *rb2;
QRadioButton *rb3;
QRadioButton *rb4;
QRadioButton *rb5;
QRadioButton *rb6;
QPushButton *english_pb;
QPushButton *german_pb;
QPushButton *close_pb;
QTranslator *m_translator;
};
This declaration for MainWindow declares multiple radio buttons and push buttons for the user interface. The private variable m_translator is used to store a pointer to the active translator object.
On line 3 is a call to the CS_OBJECT() macro. This call should be included any time a class inherits from QObject or a child class such as QWidget. The macro is required in this example because the class name “MainWindow” is used in the language translation process.
MainWindow::MainWindow()
{
setMinimumSize(700, 300);
rb0 = new QRadioButton;
rb1 = new QRadioButton;
rb2 = new QRadioButton;
rb3 = new QRadioButton;
rb4 = new QRadioButton;
rb5 = new QRadioButton;
rb6 = new QRadioButton;
QVector<QRadioButton *> list =
{rb0, rb1, rb2, rb3, rb4, rb5, rb6};
for (auto item : list) {
QFont font = item->font();
font.setPointSize(11);
item->setFont(font);
}
english_pb = new QPushButton();
german_pb = new QPushButton();
close_pb = new QPushButton();
QGridLayout *grid = new QGridLayout();
grid->setContentsMargins(75, 35, 75, 25);
grid->setHorizontalSpacing(20);
grid->setVerticalSpacing(30);
grid->addWidget(rb0, 0, 0);
grid->addWidget(rb1, 1, 0);
grid->addWidget(rb2, 2, 0);
grid->addWidget(rb3, 3, 0);
grid->addWidget(rb4, 4, 0);
grid->addWidget(rb5, 5, 0);
grid->addWidget(rb6, 6, 0);
QHBoxLayout *layout_pb = new QHBoxLayout();
layout_pb->addStretch();
layout_pb->addWidget(english_pb);
layout_pb->addSpacing(20);
layout_pb->addWidget(german_pb);
layout_pb->addSpacing(20);
layout_pb->addWidget(close_pb);
layout_pb->addStretch();
QVBoxLayout *layoutMain = new QVBoxLayout(this);
layoutMain->addLayout(grid);
layoutMain->addSpacing(50);
layoutMain->addLayout(layout_pb);
connect(english_pb, &QPushButton::clicked,
this, [this] () { setLanguage("english.qm"); });
connect(german_pb, &QPushButton::clicked,
this, [this] () { setLanguage("german.qm"); });
connect(close_pb, &QPushButton::clicked,
this, &QWidget::close);
m_translator = new QTranslator;
retranslate_UI();
}
This code defines and constructs the user interface. Lines 13 through 20 will walk through a list of the radio buttons using a range based for loop. The default font size of the text for each radio button is increased for readability.
Line 62 is used to create a new QTranslator object. On line 63 is a direct call to the retranslate_UI() method. This call must occur after the radio buttons and m_translator are instantiated. This method is responsible for displaying the radio button and push button text. Since no translation file has been loaded at this point the English text will be displayed.
Signal/Slot Connections
In the previous block of code lines 53 and 56 set up what action should be taken when the English or German push buttons are clicked. The fourth parameter in the call to connect() is a basic lambda expression which will invoke the setLanguage() method and pass the name of a translation file for the given language.
void MainWindow::setLanguage(const QString language)
{
static const QString qmPath =
QApplication::applicationDirPath();
if (m_translator->load(language, qmPath)) {
QApplication::instance()->
installTranslator(m_translator);
} else {
QMessageBox::warning(this, tr("Example 37"),
tr("Unable to load translation file: ") + language);
}
}
void MainWindow::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange) {
retranslate_UI();
}
QWidget::changeEvent(event);
}
void MainWindow::retranslate_UI()
{
rb0->setText(tr("Sunday"));
rb1->setText(tr("Monday"));
rb2->setText(tr("Tuesday"));
rb3->setText(tr("Wednesday"));
rb4->setText(tr("Thursday"));
rb5->setText(tr("Friday"));
rb6->setText(tr("Saturday"));
english_pb->setText(tr("English"));
german_pb->setText(tr("German"));
close_pb->setText(tr("Close"));
}
On line 6 of the setLanguage() method is a call to the QTranslator load() method. The first parameter we are passing is the name of a compiled translation file. Refer to the section below for more information about .qm files.
If the translation file is found then it is installed, otherwise an error message is displayed. Loading a new translation file is not enough to change the text in the user interface. The call to installTranslator() on line 8 will trigger a GUI system event. The CopperSpice event system responds to a variety of different things like a mouse press, focus events, and resize events.
Installing a new translation file produces an event which is processed by the protected changeEvent() method. This event method is also responsible for changes like maximizing and minimizing a window and a keyboard layout change. The changeEvent() method should never be called directly in your user code. The test on line 17 ensures the text translation only happens when a new language was actually installed.
The retranslate_UI() on line line 18 is rather interesting since the text on lines 26 though 36 are shown in English, however the text displayed in the user interface might be shown in a different language. The call to the tr() method on each of these lines is where all the magic happens.
Translation TS and QM Files
The translation text is added to a human readable .ts file and during the build process it is compiled to a binary file with a .qm extension. The following block shows a portion of the German ts file.
The method tr() uses the passed string to look up a translation. For example, on line 26 the string “Sunday” is used as the source and the associated translation is returned. If nothing is found the source string is returned.
// portion of the german.ts file
. . .
<context>
<name>MainWindow</name>
<message>
<location filename="main.cpp" line="130"/>
<source>Sunday</source>
<translation>Sonntag</translation>
</message>
<message>
<location filename="main.cpp" line="131"/>
<source>Monday</source>
<translation>Montag</translation>
</message>
. . .
Main Function
Since the source code for main() has not changed there is no need to show it again. Refer to example 3 or download the full source for this example.
Running the Example
To build and run this example use the same CMake build file and commands as we showed for the first example. The only suggested modification is on line 2 of the CMakeLists.txt file, change “example_1” to “example_37”.