Translating Applications#
Qt Linguist#
Qt Linguist and its related tools can be used to provide translations for applications.
The 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:
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:
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:
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:
pyside6-project lupdate .
.ts
files are translated using Qt Linguist. Once this is complete,
the files are converted to a binary form (.qm
files):
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:
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 Using .qrc Files (pyside6-rcc)).
The resource file linguist.qrc
provides the example_de.qm
under :/translations
:
<!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:
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:
LANG=de python main.py
GNU gettext#
The GNU gettext module can be used to provide translations for applications.
The GNU 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:
import gettext
# ...
_ = None
ngettext = None
and later assigned as follows:
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:
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:
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:
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.
cd locales/de_DE/LC_MESSAGES/
cp ../../example.pot .
Further adaptions need to be made to account for the German plural form and encoding:
"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:
#: 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:
msgfmt -o example.mo example.pot
The example can then be run in German:
LANG=de python main.py