.. _translations: Translating Applications ======================== .. image:: translations.png :alt: Translation Image Qt Linguist ----------- `Qt Linguist`_ and its related tools can be used to provide translations for applications. The :ref:`qt-linguist-example` example illustrates this. The example is very simple, it has a menu and shows a list of programming languages with multiselection. Translation works by passing the message strings through function calls that look up the translation. Each ``QObject`` instance provides a ``tr()`` function for that purpose. There is also ``QCoreApplication.translate()`` for adding translated texts to non-QObject classes. Qt ships its own translations containing the error messages and standard dialog captions. The linguist example has a number of messages enclosed in ``self.tr()``. The status bar message shown in response to a selection change uses a plural form depending on a count: .. code-block:: python count = len(self._list_widget.selectionModel().selectedRows()) message = self.tr("%n language(s) selected", "", count) The translation workflow for the example is as follows: The translated messages are extracted using the ``lupdate`` tool, producing XML-based ``.ts`` files: .. code-block:: bash pyside6-lupdate main.py -ts example_de.ts If ``example_de.ts`` already exists, it will be updated with the new messages added to the code in-between. If there are form files (``.ui``) and/or QML files (``.qml``) in the project, they should be passed to the ``pyside6-lupdate`` tool as well: .. code-block:: bash pyside6-lupdate main.py main.qml form.ui -ts example_de.ts The source files generated by ``pyside6-uic`` from the form files should **not** be passed. The ``lupdate`` mode of ``pyside6-project`` can also be used for this. It collects all source files and runs ``pyside6-lupdate`` when ``.ts`` file(s) are given in the ``.pyproject`` file: .. code-block:: bash pyside6-project lupdate . ``.ts`` files are translated using *Qt Linguist*. Once this is complete, the files are converted to a binary form (``.qm`` files): .. code-block:: bash pyside6-lrelease example_de.ts -qm example_de.qm ``pyside6-project`` will build the ``.qm`` file automatically when ``.ts`` file(s) are given in the ``.pyproject`` file: .. code-block:: bash pyside6-project build . To avoid having to ship the ``.qm`` files, it is recommend to put them into a Qt resource file along with icons and other applications resources (see :ref:`using_qrc_files`). The resource file ``linguist.qrc`` provides the ``example_de.qm`` under ``:/translations``: .. code-block:: xml example_de.qm At runtime, the translations need to be loaded using the ``QTranslator`` class: .. code-block:: python path = QLibraryInfo.location(QLibraryInfo.TranslationsPath) translator = QTranslator(app) if translator.load(QLocale.system(), 'qtbase', '_', path): app.installTranslator(translator) translator = QTranslator(app) path = ':/translations' if translator.load(QLocale.system(), 'example', '_', path): app.installTranslator(translator) The code first loads the translations shipped for Qt and then the translations of the applications loaded from resources. The example can then be run in German: .. code-block:: bash LANG=de python main.py .. _Qt Linguist: https://doc.qt.io/qt-6/qtlinguist-index.html GNU gettext ----------- The `GNU gettext`_ module can be used to provide translations for applications. The :ref:`gettext-example` example illustrates this. The example is very simple, it has a menu and shows a list of programming languages with multiselection. Translation works by passing the message strings through function calls that look up the translation. It is common to alias the main translation function to ``_``. There is a special translation function for sentences that contain a plural form depending on a count ("{0} items(s) selected"). It is commonly aliased to ``ngettext``. Those functions are defined at the top: .. code-block:: python import gettext # ... _ = None ngettext = None and later assigned as follows: .. code-block:: python src_dir = Path(__file__).resolve().parent try: translation = gettext.translation('example', localedir=src_dir / 'locales') if translation: translation.install() _ = translation.gettext ngettext = translation.ngettext except FileNotFoundError: pass if not _: _ = gettext.gettext ngettext = gettext.ngettext This specifies that our translation file has the base name ``example`` and will be found in the source tree under ``locales``. The code will try to load a translation matching the current language. Messages to be translated look like: .. code-block:: python file_menu = self.menuBar().addMenu(_("&File")) The status bar message shown in response to a selection change uses a plural form depending on a count: .. code-block:: python count = len(self._list_widget.selectionModel().selectedRows()) message = ngettext("{0} language selected", "{0} languages selected", count).format(count) The ``ngettext()`` function takes the singular form, plural form and the count. The returned string still contains the formatting placeholder, so it needs to be passed through ``format()``. In order to translate the messages to say German, a template file (``.pot``) is first created: .. code-block:: bash mkdir -p locales/de_DE/LC_MESSAGES xgettext -L Python -o locales/example.pot main.py This file has a few generic placeholders which can be replaced by the appropriate values. It is then copied to the ``de_DE/LC_MESSAGES`` directory. .. code-block:: bash cd locales/de_DE/LC_MESSAGES/ cp ../../example.pot . Further adaptions need to be made to account for the German plural form and encoding: .. code-block:: "Project-Id-Version: PySide6 gettext example\n" "POT-Creation-Date: 2021-07-05 14:16+0200\n" "Language: de_DE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" Below, the translated messages can be given: .. code-block:: #: main.py:57 msgid "&File" msgstr "&Datei" Finally, the ``.pot`` is converted to its binary form (machine object file, ``.mo``), which needs to be deployed: .. code-block:: bash msgfmt -o example.mo example.pot The example can then be run in German: .. code-block:: bash LANG=de python main.py .. _GNU gettext: https://docs.python.org/3/library/gettext.html