aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/tutorials/basictutorial/translations.rst
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/doc/tutorials/basictutorial/translations.rst')
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/translations.rst232
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