Skip to content
Advertisement

Is there a way to avoid typing QCoreApplication.translate(“Context”,?

To translate strings in PySide2 apps I need to call QCoreApplication.translate() and specify a context, which are a lot of characters to translate a short string. ie: QCoreApplication.translate("MyClassName", "Hello") I tried to do something like this:

from PySide2.QtCore import QCoreApplication
from functools import partial


class Translate:
    def __init__(self, context):
        self.context = context
        self.translate = partial(QCoreApplication.translate, self.context)

    def __call__(self, text):
        return self.translate(text)

This way QCoreApplication.translate() was called with the right context and in my sources I could have a shorter name, but the strings weren’t picked up and stored in the *.ts files. Is there a way to at least reduce the amount of boilerplate characters needed to translate Qt software written in python? Maybe by configuring pyside2-lupdate?

Advertisement

Answer

Update
The issue has been fixed in Qt 5.15.4+ and Qt 6+


First of all thanks to both benjamin-forest and CryptoFool, their answers were useful.

I found out that there are actually three distinct issues here:

  • Custom functions are supported only by pylupdate5 from PyQt5,
    while pyside2-lupdate has no configuration options, unlike lupdate
  • Both pylupdate5 and pyside2-lupdate have no way to guess the context when using global functions, therefore every string read by them would go to the @default context
  • The default implementation of self.tr() gets its context at runtime, as stated in the official PyQt documentation

Differences Between PyQt5 and Qt

Qt implements internationalisation support through the QTranslator class, and the translate() and tr() methods. Usually tr() is used to obtain the correct translation of a message. The translation process uses a message context to allow the same message to be translated differently. In Qt tr() is actually generated by moc and uses the hardcoded class name as the context. On the other hand, translate allows the context to be specified explicitly.

Unfortunately, because of the way Qt implements tr() it is not possible for PyQt5 to exactly reproduce its behaviour. The PyQt5 implementation of tr() uses the class name of the instance as the context. The key difference, and the source of potential problems, is that the context is determined dynamically in PyQt5, but is hardcoded in Qt. In other words, the context of a translation may change depending on an instance’s class hierarchy.

My solution is to declare a private __tr() method to wrap QCoreApplication.translate()

def __tr(self, txt, disambiguation=None, n=-1):
    return QCoreApplication.translate("TestWidget", txt, disambiguation, n)

and configure pylupdate5 to look for it when called from setup.py:

if has_build_ui:
    class build_res(build_ui):
        """Build UI, resources and translations."""

        def run(self):
            # build translations
            check_call(["pylupdate5", "-tr-function", "__tr", "app.pro"])

            lrelease = os.environ.get("LRELEASE_BIN")
            if not lrelease:
                lrelease = "lrelease"

            check_call([lrelease, "app.pro"])

            # build UI & resources
            build_ui.run(self)
            # create __init__ file for compiled ui
            open("app/ui/__init__.py", "a").close()

    cmdclass["build_res"] = build_res

This way both pylupdate5 gets the right context when generating the .ts files and self.__tr() has always the right context at runtime.

Advertisement