Internationalisation

OpenCOR is a multilingual application, currently supporting both English and French. By default, it will try to use the system language, but if it is not supported, then English will be used instead. Alternatively, any of the languages supported by OpenCOR can be used.

OpenCOR

There are two sets of language files to consider:

  1. Qt-specific translation files: these files are originally located under [Qt]/[QtVersion]/[CompilerVersion]/translations/qtXXX_xx.qm with XXX the type of translations (e.g., base) and xx the language code (e.g., fr for French). For every language supported by OpenCOR (except for English, which is natively supported by Qt), we need to add the corresponding Qt-specific translation files to [OpenCOR]/res/translations/. Then, an entry for those files must be added to [OpenCOR]/res/translations.qrc:

    <file>translations/qt_help_xx.qm</file>
    <file>translations/qtbase_xx.qm</file>
    <file>translations/qtxmlpatterns_xx.qm</file>
    
  2. OpenCOR-specific translation files: for each supported language (again, except for English), a file called [OpenCOR]/i18n/OpenCOR_xx.ts must be created. The best way to go about it is by starting from an existing language file (e.g., [OpenCOR]/i18n/OpenCOR_fr.ts). From there, locate the following line:

    <TS version="2.1" language="xx_XX" sourcelanguage="en_GB">
    

    and replace xx_XX accordingly. Otherwise, as for the Qt-specific file above, an entry for the OpenCOR-specific translation file must be added to [OpenCOR]/res/i18n.qrc.in (PROJECT_BUILD_DIR is automatically replaced during the build process):

    <file alias="app_xx">${PROJECT_BUILD_DIR}/OpenCOR_xx.qm</file>
    

    OpenCOR_xx.qm gets automatically generated from OpenCOR_xx.ts when building OpenCOR. This does, however, require updating the update_language_files() macro in [OpenCOR]/cmake/common.cmake, so that LANGUAGES gets set as follows:

    set(LANGUAGES ... xx ...)
    

On the GUI side of OpenCOR, both an action and a menu item must be created for each supported language. The best way to add GUI support for a new language is by mimicking what has been done for actionEnglish in [OpenCOR]/src/mainwindow.ui. Then, a similar mimicking work should be done in [OpenCOR]/src/mainwindow.cpp and [OpenCOR]/src/mainwindow.h (look for actionEnglish and EnglishLocale).

Plugins

A file called [PluginName]_xx.ts must be created for each plugin that requires internationalisation and it must be located in [PluginName]/i18n (e.g., [OpenCOR]/src/plugins/miscellaneous/Core/i18n/Core_fr.ts for the Core plugin):

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="xx_XX" sourcelanguage="en_GB">
<context>
</context>
</TS>

A [PluginName]_i18n.qrc.in file must also be created in [PluginName]/res (e.g., [OpenCOR]/src/plugins/miscellaneous/Core/res/Core_i18n.qrc.in for the Core plugin; PLUGIN_NAME and PROJECT_BUILD_DIR are automatically replaced during the build process):

<RCC>
    <qresource prefix="/">
        <file alias="${PLUGIN_NAME}_xx">${PROJECT_BUILD_DIR}/${PLUGIN_NAME}_xx.qm</file>
    </qresource>
</RCC>

A plugin requires a plugin class and, for internationalisation to be supported, it needs to inherit from I18nInterface, as well as reference OpenCOR::I18nInterface and include the i18ninterface.inl file (e.g., [OpenCOR]/src/plugins/miscellaneous/Core/src/coreplugin.h for the Core plugin):

class PluginNamePlugin : ..., public I18nInterface, ...
{
    ...
    Q_INTERFACES(OpenCOR::I18nInterface)
    ...

public:
...
#include "i18ninterface.inl"
...
};

The internationalisation interface has only one method that needs to be implemented (e.g., [OpenCOR]/src/plugins/miscellaneous/Core/src/coreplugin.cpp for the Core plugin):

//==============================================================================
// I18n interface
//==============================================================================

void PluginNamePlugin::retranslateUi()
{
    ...
}

//==============================================================================

Qt objects (e.g., menus, actions) need to be retranslated either by the plugin class itself or by an object owned directly or indirectly by the plugin class (e.g., [OpenCOR]/src/plugins/miscellaneous/Core/src/coreplugin.cpp for the Core plugin). To help with this process, I18nInterface comes with two methods that ensure that menus and actions get properly retranslated: retranslateMenu(QMenu *pMenu, const QString &pTitle) and retranslateAction(QAction *pAction, const QString &pTextAndToolTip, const QString &pStatusTip).

It may happen that a plugin does not own any Qt objects, but still needs to support internationalisation. This is the case with our Editor widget, which implements a Qt widget that can be both instantiated and retranslated by others. This means that its retranslateUi() method is empty (see [OpenCOR]/src/plugins/widget/EditorWidget/src/editorwidgetplugin.cpp).