Search code examples
pythonqmltranslationpyside2

QML Translation of Properties


I know the translation process in QML for strings. But is there a way to translate QML properties completely with QML functions? I am working with PySide2 and I would need to translate properties defined on Python and QML side. As a minimalistic example without translation functions yet:

main. py

class example_model(QObject):
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self._model = QStandardItemModel()

    @Property(str, constant=True)
    def python_property(self):
        return "Example String"

def main():
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    context = engine.rootContext()
    model = example_model( parent=context)
    context.setContextProperty("exampleModel", model)
    engine.load(QUrl(QUrl.fromLocalFile(os.path.join(os.path.dirname(inspect.getfile(lambda: None)), "main.qml"))))

    return app.exec_()
    
if __name__ == '__main__':
    sys.exit(main())

main.qml

import QtQuick 2.12
import QtQuick.Controls 2.12

ApplicationWindow {
    id: root
    width: 200
    height: 400
    visible: true
    property string qmlProperty: "exampleString"

    Text{
        text: QT_TR_NOOP(exampleModel.python_property)
    }
    Text{
        text: QT_TR_NOOP(root.qmlProperty)
    }
}

How can I achieve it that those two properties (python_property and qmlProperty) are correctly detected by Qt's lupdate function or any other function, respectively. I need a dynamic translation, so the engine.retranslate() function will be used.


Solution

  • The translation is similar to QML only that to use the python translation you have to use qsTranslate where you must pass the context (name of the python class) since it differs from the context of qml (name of the .qml file).

    On the other hand it seems that the OP does not understand the operation of QT_TR_NOOP so it is advisable to review the docs.

    Using the following example I will show the translation process:

    import os
    import sys
    from pathlib import Path
    
    from PySide2.QtCore import (
        Property,
        QCoreApplication,
        QObject,
        Qt,
        QTimer,
        QTranslator,
        QUrl,
    )
    from PySide2.QtGui import QGuiApplication
    from PySide2.QtQml import QQmlApplicationEngine
    
    CURRENT_DIRECTORY = Path(__file__).resolve().parent
    QML_DIRECTORY = CURRENT_DIRECTORY / "qml"
    TRANSLATIONS_DIR = CURRENT_DIRECTORY / "translations"
    
    
    class PythonModel(QObject):
        def python_property(self):
            return self.tr("Python Example String")
    
        pythonProperty = Property(str, fget=python_property, constant=True)
    
    
    def main():
        app = QGuiApplication(sys.argv)
    
        py_tranlator = QTranslator()
        res = py_tranlator.load(os.fspath(TRANSLATIONS_DIR / "py.qm"))
        assert res
    
        qml_tranlator = QTranslator()
        res = qml_tranlator.load(os.fspath(TRANSLATIONS_DIR / "qml.qm"))
        assert res
    
        python_model = PythonModel(app)
    
        engine = QQmlApplicationEngine()
        engine.rootContext().setContextProperty("pythonModel", python_model)
    
        filename = os.fspath(QML_DIRECTORY / "main.qml")
        url = QUrl.fromLocalFile(filename)
    
        def handle_object_created(obj, obj_url):
            if obj is None and url == obj_url:
                QCoreApplication.exit(-1)
    
        engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
        engine.load(url)
    
        ok = False
    
        def handle_timeout():
            nonlocal ok
            if ok:
                QCoreApplication.installTranslator(py_tranlator)
                QCoreApplication.installTranslator(qml_tranlator)
            else:
                QCoreApplication.removeTranslator(py_tranlator)
                QCoreApplication.removeTranslator(qml_tranlator)
    
            engine.retranslate()
    
            ok = not ok
    
        timer = QTimer(interval=1000, timeout=handle_timeout)
        timer.start()
    
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()
    
    import QtQuick 2.12
    import QtQuick.Controls 2.12
    
    ApplicationWindow {
        id: root
    
        property string qmlProperty: qsTr("QML Example String")
    
        width: 200
        height: 400
        visible: true
    
        Column {
            Text {
                text: qsTranslate("pythonModel", pythonModel.pythonProperty)
            }
    
            Text {
                text: root.qmlProperty
            }
    
        }
    
    }
    
    ├── main.py
    ├── qml
    │   └── main.qml
    └── translations
        ├── py.qm
        ├── py.ts
        ├── qml.qm
        └── qml.ts
    
    1. Generate the .ts using pyside2-lupdate and lupdate:

      pyside2-lupdate main.py -ts translations/py.ts
      lupdate qml/main.qml -ts translations/qml.ts 
      
    2. Add the translations using Qt Linguist tool.

    3. Generate the .qm using lrelease:

      lrelease translations/py.ts translations/py.qm
      lrelease translations/qml.ts translations/qml.qm
      
    4. Then you have to load both translations.

    If you don't want to have multiple .ts or .qm then you can use lconvert to join the files.

    You can find the complete example here.