How to create a multi language application: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
m (Fix code formatting)
(10 intermediate revisions by 6 users not shown)
Line 1: Line 1:
{{LangSwitch}}
[[Category:HowTo]]
[[Category:HowTo]]
'''English''' [[How_to_create_a_multi_language_application_German|German]] [[How_to_create_a_multi_language_application_Greek|Ελληνικά]]
== Create a standard application, e.g. with a main window ==


[toc align_right="yes" depth="2"]
[[File:LanguageApp.png]]


= How to create a multi lingual application that can switch the language at runtime? =
In this example, we create a main window with a menu Language and some widgets. If the user opened the language menu, there is a selection of languages, which is created on startup of the application, dependent on the existing language files.


== Create a standard application, e.g. with a main window ==
[[File:LanguageAppMenu.png]]
 
[[Image:http://lh3.ggpht.com/_m1PNLlZctqY/TRELoZ10m5I/AAAAAAAAAC0/qskj_psAyCI/s800/LanguageApp.png|Hauptfenster der Applikation]]
In this example, we create a main window with a menu Language and some widgets. If the user opened the language menu, there is a selection of languages, which is created on startup of the application, dependant on the existing language files.
[[Image:http://lh3.ggpht.com/_m1PNLlZctqY/TRELoozTsDI/AAAAAAAAAC4/AWTMerfK-ME/s800/LanguageAppMenu.png|Hauptfenster der Applikation mit Sprachmenü]]


== File system structure of the application: ==
== File system structure of the application: ==


* <Application directory&amp;amp;gt;
* Application directory
** binaries
** binaries
* <Application directory&amp;amp;gt;/languages
* Application directory/languages
** For each installed language, there is an (optional) image of the size 16x16 pixel with a flag (e.g. de.png)
** For each installed language, there is an (optional) image of the size 16x16 pixel with a flag (e.g. de.png)
** The translated text files of the application (TranslationExample_'''.qm, where''' could be de, en, )
** The translated text files of the application (TranslationExample_*.qm, where * could be de, en, etc.)
** the translation files of Qt (qt_'''.qm)
** the translation files of Qt (qt_*.qm)


h2. Class definition
== Class definition ==


In the class MainWindow, the virtual method "changeEvent(QEvent''')":http://doc.qt.nokia.com/latest/qwidget.html#changeEvent is overwritten. For each translation file, that is needed, a "QTranslator":http://doc.qt.nokia.com/latest/qtranslator.html instance is created (in our example 2, on for the application texts, one for qt). The current language is stored to suppress events, if the user tries to load the same language twice.
In the class MainWindow, the virtual method {{DocLink|QWidget|changeEvent|changeEvent(QEvent*)}} is overwritten. For each translation file, that is needed, a {{DocLink|QTranslator}} instance is created (in our example 2, on for the application texts, one for qt). The current language is stored to suppress events, if the user tries to load the same language twice.
 
<code>
class MainWindow : public QMainWindow
{


protected:
<pre><code>
/** this event is called, when a new translator is loaded or the system language is changed
class MainWindow : public QMainWindow {
*/
protected:
void changeEvent(QEvent''');
  // this event is called, when a new translator is loaded or the system language is changed
  void changeEvent(QEvent*);


protected slots:
protected slots:
/** this slot is called by the language menu actions
  // this slot is called by the language menu actions
*/
  void slotLanguageChanged(QAction* action);
void slotLanguageChanged(QAction''' action);


private:
private:
/** loads a language by the given language shortcur (e.g. de, en, …)
  // loads a language by the given language shortcur (e.g. de, en)
*/
  void loadLanguage(const QString& rLanguage);
void loadLanguage(const QString&amp;amp; rLanguage);


/'''* creates the language menu dynamically from the content of m_langPath
  // creates the language menu dynamically from the content of m_langPath
*/
  void createLanguageMenu(void);
void createLanguageMenu(void);


Ui::MainWindow ui; /< ui definition from designer*/
  Ui::MainWindow ui; // ui definition from designer
QTranslator m_translator; /*'''< contains the translations for this application*/
  QTranslator m_translator; // contains the translations for this application
QTranslator m_translatorQt; /*'''< contains the translations for qt*/
  QTranslator m_translatorQt; // contains the translations for qt
QString m_currLang; /*'''< contains the currently loaded language*/
  QString m_currLang; // contains the currently loaded language
QString m_langPath; /*'''< Path of language files. This is always fixed to /languages.*/
  QString m_langPath; // Path of language files. This is always fixed to /languages.
};
};
</code>
</code></pre>


== Creation of the language menus ==
== Creation of the language menus ==
Line 61: Line 51:
The language menu is created dynamically during application start, depending on the existing translation files. The advantage of this solution is, you can deliver any translation later on, and it will just work after application restart. In this example, all text files are located in the sub folder "languages". It is possible to place some icons there (language.png) which is used as icon in the menu (e.g. a flag).
The language menu is created dynamically during application start, depending on the existing translation files. The advantage of this solution is, you can deliver any translation later on, and it will just work after application restart. In this example, all text files are located in the sub folder "languages". It is possible to place some icons there (language.png) which is used as icon in the menu (e.g. a flag).


Each language is represented by a "QAction":http://doc.qt.nokia.com/latest/qaction.html object which is added to a "QActionGroup":http://doc.qt.nokia.com/latest/qactiongroup.html. This is done to achieve, that only one slot is needed for all languages:
Each language is represented by a {{DocLink|QAction}} object which is added to a {{DocLink|QActionGroup}}. This is done to achieve, that only one slot is needed for all languages:


<code>
<code>
Line 67: Line 57:
</code>
</code>


<code>
The language menu is created as following:
// we create the menu entries dynamically, dependant on the existing translations.
 
void MainWindow::createLanguageMenu(void)
<pre><code>
{
// we create the menu entries dynamically, dependent on the existing translations.
void MainWindow::createLanguageMenu(void) {
  QActionGroup* langGroup = new QActionGroup(ui.menuLanguage);
  QActionGroup* langGroup = new QActionGroup(ui.menuLanguage);
  langGroup->setExclusive(true);
  langGroup->setExclusive(true);


connect(langGroup, SIGNAL (triggered(QAction *)), this, SLOT (slotLanguageChanged(QAction *)));
connect(langGroup, SIGNAL (triggered(QAction *)), this, SLOT (slotLanguageChanged(QAction *)));


// format systems language
// format systems language
  QString defaultLocale = QLocale::system().name(); // e.g. "de_DE"
  QString defaultLocale = QLocale::system().name(); // e.g. "de_DE"
  defaultLocale.truncate(defaultLocale.lastIndexOf('''')); // e.g. "de"
  defaultLocale.truncate(defaultLocale.lastIndexOf('_')); // e.g. "de"


  m_langPath = QApplication::applicationDirPath();
  m_langPath = QApplication::applicationDirPath();
  m_langPath.append("/languages");
  m_langPath.append("/languages");
  QDir dir(m_langPath);
  QDir dir(m_langPath);
  QStringList fileNames = dir.entryList(QStringList("TranslationExample''*.qm"));
  QStringList fileNames = dir.entryList(QStringList("TranslationExample_*.qm"));


for (int i = 0; i < fileNames.size(); +''i)
for (int i = 0; i < fileNames.size(); ++i) {
{
  // get locale extracted by filename
// get locale extracted by filename
  QString locale;
QString locale;
  locale = fileNames[i]; // "TranslationExample_de.qm"
locale = fileNames[i]; // "TranslationExample_de.qm"
  locale.truncate(locale.lastIndexOf('.')); // "TranslationExample_de"
locale.truncate(locale.lastIndexOf('.')); // "TranslationExample_de"
  locale.remove(0, locale.lastIndexOf('_') + 1); // "de"
locale.remove(0, locale.indexOf('''') + 1); // "de"


QString lang = QLocale::languageToString(QLocale(locale).language());
  QString lang = QLocale::languageToString(QLocale(locale).language());
QIcon ico(QString("%1/%2.png").arg(m_langPath).arg(locale));
  QIcon ico(QString("%1/%2.png").arg(m_langPath).arg(locale));


QAction *action = new QAction(ico, lang, this);
  QAction *action = new QAction(ico, lang, this);
action->setCheckable(true);
  action->setCheckable(true);
action->setData(locale);
  action->setData(locale);


ui.menuLanguage->addAction(action);
  ui.menuLanguage->addAction(action);
langGroup->addAction(action);
  langGroup->addAction(action);


// set default translators and language checked
  // set default translators and language checked
if (defaultLocale == locale)
  if (defaultLocale == locale) {
{
  action->setChecked(true);
action->setChecked(true);
  }
}
  }
  }
}
}
</code>
</code></pre>
 
== Switching the language ==


h2. Switching the language
If the language should be switched, the needed target language is extracted from the {{DocLink|QAction}} object and the existing translators are removed {{DocLink|QApplication|removeTranslator|QApplication::removeTranslator()}}. After that, the new language files are loaded, and if successful, the translator is installed again {{DocLink|QApplication|installTranslator|QApplication::installTranslator()}}. This is done to ensure, a {{DocLink|QEvent}}::LanguageChange is emitted by the application object. If the application only contains one top level window, that is completely created by designer, it is also possible to just read the new translation files and call <tt>ui.retranslateUi(this)</tt> directly.


If the language should be switched, the needed target language is extracted from the "QAction":http://doc.qt.nokia.com/latest/qaction.html object and the existing translators are removed ("QApplication::removeTranslator":http://doc.qt.nokia.com/latest/qapplication.html#removeTranslator ). After that, the new language files are loaded, and if successful, the translator is installed again ("QApplication::installTranslator":http://doc.qt.nokia.com/latest/qapplication.html#installTranslator ). This is done to ensure, a
<pre><code>
"QEvent::LanguageChange":http://doc.qt.nokia.com/latest/qevent.html#Type-enum event is emitted by the application object. It the application only contains one top level window, that is completely created by designer, it is also possible to just read the new translation files and call '''ui.retranslateUi(this)''' directly.
<code>
// Called every time, when a menu entry of the language menu is called
// Called every time, when a menu entry of the language menu is called
void MainWindow::slotLanguageChanged(QAction* action)
void MainWindow::slotLanguageChanged(QAction* action)
{
{
  if(0 != action)
  if(0 != action) {
{
  // load the language dependant on the action content
// load the language dependant on the action content
  loadLanguage(action->data().toString());
loadLanguage(action->data().toString());
  setWindowIcon(action->icon());
setWindowIcon(action->icon());
  }
  }
}
}


void switchTranslator(QTranslator&amp;amp; translator, const QString&amp;amp; filename)
void switchTranslator(QTranslator& translator, const QString& filename) {
{
  // remove the old translator
  // remove the old translator
  qApp->removeTranslator(&amp;translator);
  qApp->removeTranslator(&translator);


  // load the new translator
  // load the new translator
  if(translator.load(filename))
QString path = QApplication::applicationDirPath();
qApp->installTranslator(&amp;translator);
path.append("/languages/");
  if(translator.load(path + filename)) //Here Path and Filename has to be entered because the system didn't find the QM Files else
  qApp->installTranslator(&translator);
}
}


void MainWindow::loadLanguage(const QString&amp;amp; rLanguage)
void MainWindow::loadLanguage(const QString& rLanguage) {
{
  if(m_currLang != rLanguage) {
  if(m_currLang != rLanguage)
  m_currLang = rLanguage;
{
  QLocale locale = QLocale(m_currLang);
m_currLang = rLanguage;
  QLocale::setDefault(locale);
QLocale locale = QLocale(m_currLang);
  QString languageName = QLocale::languageToString(locale.language());
QLocale::setDefault(locale);
  switchTranslator(m_translator, QString("TranslationExample_%1.qm").arg(rLanguage));
QString languageName = QLocale::languageToString(locale.language());
  switchTranslator(m_translatorQt, QString("qt_%1.qm").arg(rLanguage));
switchTranslator(m_translator, QString("TranslationExample''%1.qm").arg(rLanguage));
  ui.statusBar->showMessage(tr("Current Language changed to %1").arg(languageName));
switchTranslator(m_translatorQt, QString("qt_%1.qm").arg(rLanguage));
ui.statusBar->showMessage(tr("Current Language changed to %1").arg(languageName));
  }
  }
}
}
</code>
</code></pre>
 
* <tt>QEvent::LanguageChange</tt> will always be called, if a translator object is installed in the application object
* <tt>QEvent::LocaleChange</tt> is called, when the system language is switched


* "QEvent::LanguageChange":http://doc.qt.nokia.com/latest/qevent.html#Type-enum will always be called, if a translator object is installed in the application object
<pre><code>
* "QEvent::LocaleChange":http://doc.qt.nokia.com/latest/qevent.html#Type-enum is called, when the system language is switched
void MainWindow::changeEvent(QEvent* event) {
if(0 != event) {
  switch(event->type()) {
  // this event is send if a translator is loaded
  case QEvent::LanguageChange:
    ui.retranslateUi(this);
    break;


<code>
  // this event is send, if the system, language changes
void MainWindow::changeEvent(QEvent* event)
  case QEvent::LocaleChange:
{
  {
if(0 != event)
    QString locale = QLocale::system().name();
{
    locale.truncate(locale.lastIndexOf('_'));  
switch(event->type())
    loadLanguage(locale);
{
  }
// this event is send if a translator is loaded
  break;
case QEvent::LanguageChange:
  }
ui.retranslateUi(this);
break;
// this event is send, if the system, language changes
case QEvent::LocaleChange:
{
QString locale = QLocale::system().name();
locale.truncate(locale.lastIndexOf('_'));
loadLanguage(locale);
  }
  }
break;
}
}
  QMainWindow::changeEvent(event);
  QMainWindow::changeEvent(event);
}
}
</code>
</code></pre>
 
== Add translations to the project ==
 
In your qmake project file, the following variable [http://doc.qt.io/qt-5/qmake-variable-reference.html#translations TRANSLATIONS] has to be added and must contain all language files you want to create initially.


h2. Add translations to the project
TRANSLATIONS = languages/TranslationExample_en.ts  languages/TranslationExample_de.ts


In your "QMake":http://doc.qt.nokia.com/latest/qmake-manual.html project file, the following variable "'''TRANSLATIONS'''":http://doc.qt.nokia.com/latest/qmake-variable-reference.html#translations has to be added and must contain all language filesyou want to create initially.
<code>
TRANSLATIONS = languages/TranslationExample_en.ts  languages/TranslationExample_de.ts
</code>


By calling "lupdate":http://doc.qt.nokia.com/latest/linguist-manager.html#lupdate
By calling <tt>[http://doc.qt.io/qt-5/linguist-manager.html#using-lupdate lupdate]</tt>
<code>
lupdate -verbose TranslationExample.pro
</code>
You create the language files ('''.ts), which you translate by using the tool "Qt Linguist":http://doc.qt.nokia.com/latest/linguist-manual.html


<code>
lupdate -verbose TranslationExample.pro
linguist languages/TranslationExample_en.ts languages/TranslationExample_de.ts
</code>


After doing this, you call "lrelease":http://doc.qt.nokia.com/latest/linguist-manager.html#lrelease to create the binary language files ('''.qm):
You create the language files (.ts), which you translate by using the tool ''Qt Linguist''.


<code>
linguist languages/TranslationExample_en.ts languages/TranslationExample_de.ts
lrelease TranslationExample.pro
</code>


h3. Deploying to Symbian
After doing this, you call <tt>[http://doc.qt.io/qt-5/linguist-manager.html#using-lrelease lrelease]</tt>
to create the binary language files (.qm):


In case your deploy target is a Symbian device then you need to add a special rule in the .pro file to pack the .qm files along with the executable or else the translation wont work. So:
lrelease TranslationExample.pro
<code>
symbian: {
addFiles.sources = TranslationExample_en.qm TranslationExample_de.qm
addFiles.path = .
DEPLOYMENT''= addFiles
}
</code>

Revision as of 09:00, 22 March 2021

En Ar Bg De El Es Fa Fi Fr Hi Hu It Ja Kn Ko Ms Nl Pl Pt Ru Sq Th Tr Uk Zh

Create a standard application, e.g. with a main window

LanguageApp.png

In this example, we create a main window with a menu Language and some widgets. If the user opened the language menu, there is a selection of languages, which is created on startup of the application, dependent on the existing language files.

LanguageAppMenu.png

File system structure of the application:

  • Application directory
    • binaries
  • Application directory/languages
    • For each installed language, there is an (optional) image of the size 16x16 pixel with a flag (e.g. de.png)
    • The translated text files of the application (TranslationExample_*.qm, where * could be de, en, etc.)
    • the translation files of Qt (qt_*.qm)

Class definition

In the class MainWindow, the virtual method changeEvent(QEvent*) is overwritten. For each translation file, that is needed, a QTranslator instance is created (in our example 2, on for the application texts, one for qt). The current language is stored to suppress events, if the user tries to load the same language twice.

<code>
class MainWindow : public QMainWindow {
 protected:
  // this event is called, when a new translator is loaded or the system language is changed
  void changeEvent(QEvent*);

 protected slots:
  // this slot is called by the language menu actions
  void slotLanguageChanged(QAction* action);

 private:
  // loads a language by the given language shortcur (e.g. de, en)
  void loadLanguage(const QString& rLanguage);

  // creates the language menu dynamically from the content of m_langPath
  void createLanguageMenu(void);

  Ui::MainWindow ui; // ui definition from designer
  QTranslator m_translator; // contains the translations for this application
  QTranslator m_translatorQt; // contains the translations for qt
  QString m_currLang; // contains the currently loaded language
  QString m_langPath; // Path of language files. This is always fixed to /languages.
};
</code>

Creation of the language menus

The language menu is created dynamically during application start, depending on the existing translation files. The advantage of this solution is, you can deliver any translation later on, and it will just work after application restart. In this example, all text files are located in the sub folder "languages". It is possible to place some icons there (language.png) which is used as icon in the menu (e.g. a flag).

Each language is represented by a QAction object which is added to a QActionGroup. This is done to achieve, that only one slot is needed for all languages:

connect(langGroup, SIGNAL (triggered(QAction *)), this, SLOT (slotLanguageChanged(QAction *)));

The language menu is created as following:

<code>
// we create the menu entries dynamically, dependent on the existing translations.
void MainWindow::createLanguageMenu(void) {
 QActionGroup* langGroup = new QActionGroup(ui.menuLanguage);
 langGroup->setExclusive(true);

 connect(langGroup, SIGNAL (triggered(QAction *)), this, SLOT (slotLanguageChanged(QAction *)));

 // format systems language
 QString defaultLocale = QLocale::system().name(); // e.g. "de_DE"
 defaultLocale.truncate(defaultLocale.lastIndexOf('_')); // e.g. "de"

 m_langPath = QApplication::applicationDirPath();
 m_langPath.append("/languages");
 QDir dir(m_langPath);
 QStringList fileNames = dir.entryList(QStringList("TranslationExample_*.qm"));

 for (int i = 0; i < fileNames.size(); ++i) {
  // get locale extracted by filename
  QString locale;
  locale = fileNames[i]; // "TranslationExample_de.qm"
  locale.truncate(locale.lastIndexOf('.')); // "TranslationExample_de"
  locale.remove(0, locale.lastIndexOf('_') + 1); // "de"

  QString lang = QLocale::languageToString(QLocale(locale).language());
  QIcon ico(QString("%1/%2.png").arg(m_langPath).arg(locale));

  QAction *action = new QAction(ico, lang, this);
  action->setCheckable(true);
  action->setData(locale);

  ui.menuLanguage->addAction(action);
  langGroup->addAction(action);

  // set default translators and language checked
  if (defaultLocale == locale) {
   action->setChecked(true);
  }
 }
}
</code>

Switching the language

If the language should be switched, the needed target language is extracted from the QAction object and the existing translators are removed QApplication::removeTranslator(). After that, the new language files are loaded, and if successful, the translator is installed again QApplication::installTranslator(). This is done to ensure, a QEvent::LanguageChange is emitted by the application object. If the application only contains one top level window, that is completely created by designer, it is also possible to just read the new translation files and call ui.retranslateUi(this) directly.

<code>
// Called every time, when a menu entry of the language menu is called
void MainWindow::slotLanguageChanged(QAction* action)
{
 if(0 != action) {
  // load the language dependant on the action content
  loadLanguage(action->data().toString());
  setWindowIcon(action->icon());
 }
}

void switchTranslator(QTranslator& translator, const QString& filename) {
 // remove the old translator
 qApp->removeTranslator(&translator);

 // load the new translator
QString path = QApplication::applicationDirPath();
	path.append("/languages/");
 if(translator.load(path + filename)) //Here Path and Filename has to be entered because the system didn't find the QM Files else
  qApp->installTranslator(&translator);
}

void MainWindow::loadLanguage(const QString& rLanguage) {
 if(m_currLang != rLanguage) {
  m_currLang = rLanguage;
  QLocale locale = QLocale(m_currLang);
  QLocale::setDefault(locale);
  QString languageName = QLocale::languageToString(locale.language());
  switchTranslator(m_translator, QString("TranslationExample_%1.qm").arg(rLanguage));
  switchTranslator(m_translatorQt, QString("qt_%1.qm").arg(rLanguage));
  ui.statusBar->showMessage(tr("Current Language changed to %1").arg(languageName));
 }
}
</code>
  • QEvent::LanguageChange will always be called, if a translator object is installed in the application object
  • QEvent::LocaleChange is called, when the system language is switched
<code>
void MainWindow::changeEvent(QEvent* event) {
 if(0 != event) {
  switch(event->type()) {
   // this event is send if a translator is loaded
   case QEvent::LanguageChange:
    ui.retranslateUi(this);
    break;

   // this event is send, if the system, language changes
   case QEvent::LocaleChange:
   {
    QString locale = QLocale::system().name();
    locale.truncate(locale.lastIndexOf('_')); 
    loadLanguage(locale);
   }
   break;
  }
 }
 QMainWindow::changeEvent(event);
}
</code>

Add translations to the project

In your qmake project file, the following variable TRANSLATIONS has to be added and must contain all language files you want to create initially.

TRANSLATIONS = languages/TranslationExample_en.ts  languages/TranslationExample_de.ts


By calling lupdate

lupdate -verbose TranslationExample.pro

You create the language files (.ts), which you translate by using the tool Qt Linguist.

linguist languages/TranslationExample_en.ts languages/TranslationExample_de.ts

After doing this, you call lrelease to create the binary language files (.qm):

lrelease TranslationExample.pro