I am currently evaluating the C++ framework Qt v5.4.1. Currently I am trying to understand and apply Internationalization with Qt.
I successfully implemented switching of the human language at runtime via the help of the article How to create a multi lingual application that can switch the language at runtime? and I know how-to work with Qt Linguist Translation source (.ts) files and how to generate Qt Linguist Message (.qm) files. I use the build system CMake to automate the generation and it works very nice.
In my project the translations are loaded from a Qt Resource Collection (.qrc)
file compiled into the application. I know how-to translate "static" strings via
the member functions QObject::tr()
and QObject::translate()
.
Now comes the tricky part: I want to add languages while my application evolves. Currently I have the following two .ts files:
foo_ui_de_DE.ts
foo_ui_en_US.ts
These are compiled via lrelease
into the following two .qm files by the build
process:
foo_ui_de_DE.qm
foo_ui_en_US.qm
The build process automatically generates a .qrc file translations.qrc
and
compiles that file into the executable via rcc
.
Relevant source code from the declaration file (.h):
#include <QMainWindow>
#include <QLocale>
#include <QString>
#include <QTranslator>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
/**
* Initializes a new instance of the MainWindow class with the given parent.
*
* @param parent The parent.
*/
explicit MainWindow(QWidget* parent = 0);
private slots:
/**
* Loads a language by the given language shortcut (e.g. `de_DE`, `en_US`).
*/
void LoadLanguage(QLocale const& kLocale);
void SwitchTranslator(QTranslator& translator,
QString const& kLocale,
QString const& kFilename);
/**
* Creates the language menu dynamically.
*/
void CreateLanguageMenu();
protected:
/**
* Handler which is activated when a new translator is loaded or the system
* language is changed.
*/
void changeEvent(QEvent* event);
protected slots:
/**
* Slot which is called by the language menu actions.
*/
void slotLanguageChanged(QAction* action);
private:
/**
* The translations for this application.
*/
QTranslator translator_;
/**
* The translations for the Qt Widgets used in this application.
*/
QTranslator qt_translator_;
/**
* Contains the currently loaded locale.
*/
QLocale locale_;
/**
* The main window of the application.
*/
Ui::MainWindow* ui_;
};
Relevant source code from the definition file (.cc):
#include <QLibraryInfo>
#include "main_window.h"
#include "ui_main_window.h"
MainWindow::MainWindow(QWidget* the_parent)
: QMainWindow{the_parent},
ui_{new Ui::MainWindow} {
ui_->setupUi(this);
CreateLanguageMenu();
}
MainWindow::~MainWindow() {
delete ui_;
}
void MainWindow::LoadLanguage(QLocale const& kNewLocale) {
QLocale::setDefault(kNewLocale);
QString const kLanguageName{QLocale::languageToString(kNewLocale.language())};
SwitchTranslator(translator_, "qt_" + kNewLocale.bcp47Name(),
QLibraryInfo::location(QLibraryInfo::TranslationsPath));
SwitchTranslator(qt_translator_,
qApp->applicationName() + '_' + kNewLocale.name(),
":/translations");
statusBar()->showMessage(
tr("Language changed to %1").arg(kLanguageName));
locale_ = kNewLocale;
}
void MainWindow::SwitchTranslator(QTranslator& translator,
QString const& kLocale,
QString const& kFilename) {
qApp->removeTranslator(&translator);
if (translator.load(kLocale, kFilename)) {
qApp->installTranslator(&translator);
}
}
void MainWindow::CreateLanguageMenu() {
// TODO(wolters): This is not optimal, since it does not work automatically
// with the .qm files added as a resource to the application.
//: Translation for the human language German.
QT_TR_NOOP("German");
//: Translation for the human language English.
QT_TR_NOOP("English");
QActionGroup* language_group{new QActionGroup(ui_->menuLanguage)};
language_group->setExclusive(true);
connect(language_group, SIGNAL(triggered(QAction*)), this,
SLOT(slotLanguageChanged(QAction*)));
QLocale const kDefaultLocale{QLocale::system()};
QDir const kDirectory{QApplication::applicationDirPath() + "/.."};
QStringList const kFileNames{kDirectory.entryList(QStringList("*.qm"))};
for (QString const& kFileName : kFileNames) {
QLocale const kLocale{QFileInfo{kFileName}.completeBaseName().replace(
qApp->applicationName() + "_", "")};
QString const kCountryCode{
kLocale.name().toLower().mid(kLocale.name().lastIndexOf('_') + 1)};
QIcon const kIcon{":/icons/flags/" + kCountryCode + ".png"};
QAction* action{new QAction{
kIcon,
// TODO(wolters): This does not work.
tr(QLocale::languageToString(kLocale.language()).toStdString().c_str()),
this}};
action->setCheckable(true);
action->setData(kLocale);
ui_->menuLanguage->addAction(action);
language_group->addAction(action);
if (kDefaultLocale == kLocale) {
action->setChecked(true);
}
}
}
void MainWindow::changeEvent(QEvent* the_event) {
if (nullptr != the_event) {
switch (the_event->type()) {
// QEvent::LanguageChange is send if a translator is loaded.
case QEvent::LanguageChange:
ui_->retranslateUi(this);
break;
// QEvent::LocaleChange is send, if the system language changes.
case QEvent::LocaleChange:
LoadLanguage(QLocale::system());
break;
default:
break;
}
}
QMainWindow::changeEvent(the_event);
}
void MainWindow::slotLanguageChanged(QAction* action) {
if (nullptr != action) {
LoadLanguage(qvariant_cast<QLocale>(action->data()));
}
}
The source code already describes the problem I have in the comments.
- If the application is started, the current system locale is used to
re-translate the UI. This works and I can see that the items in the menu
Languages
appear in the German human language (I've translated both items in the .ts files). But when I switch the language via the menu from German to English, both item labels do not get translated. - The approach is in general not optimal, since I do not want to modify the
source code (
QT_TR_NOOP
above), if a new human language is added to the application. The optimal work flow would be:- Add all supported languages to all .ts files in their proper language.
- Dynamically translate the menu items.
I think I am misunderstanding something, but I couldn't found a solution while searching the WWW for a while.
Update 2015-04-01: I think I use the wrong approach. The important part is that the Languages menu is created dynamically in the member function CreateLanguageMenu()
. I need an answer to the question how-to translate the dynamically created menu items. So it's all about the line QAction* action{new QAction{kIcon, tr(QLocale::languageToString(kLocale.language()).toStdString().c_str()), this}};
in that function. I need some kind of lookup-functionality available at compile time I think...