diff options
Diffstat (limited to 'sources/pyside6/doc/tutorials/basictutorial/translations.rst')
-rw-r--r-- | sources/pyside6/doc/tutorials/basictutorial/translations.rst | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/sources/pyside6/doc/tutorials/basictutorial/translations.rst b/sources/pyside6/doc/tutorials/basictutorial/translations.rst new file mode 100644 index 000000000..21c16cdcd --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/translations.rst @@ -0,0 +1,232 @@ +.. _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 + + <!DOCTYPE RCC><RCC version="1.0"> + <qresource prefix="translations"> + <file>example_de.qm</file> + </qresource> + </RCC> + +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 |