aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/tutorials/basictutorial/translations.rst
blob: 44280580ca41cc37c790e4d3f3365029992a35be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
.. _translations:

Translating Applications
========================

Qt Linguist
-----------

`Qt Linguist <https://doc.qt.io/qt-6/qtlinguist-index.html>`_ and
its related tools can be used to provide translations for applications.

The ``examples/widgets/linguist`` 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.

``.ts`` files are translated using *Qt Linguist*. Once this is complete,
the files are converted to a binary form (``.qm`` files):

    .. code-block:: bash

        mkdir translations
        pyside6-lrelease example_de.ts -qm translations/example_de.qm

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>
            <file>translations/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

GNU gettext
-----------

The `GNU gettext <https://docs.python.org/3/library/gettext.html>`_ module
can be used to provide translations for applications.

The ``examples/widgets/gettext`` 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