Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QObject::tr() is not working



  • Hello,
    I'm creating an application in which I have used placeholder QStrings wrapped into tr() to have them later replaced by the translation from a qm file. I have gotten to the point where I have a working qm file loaded into a QTranslator and the QTranslator installed into my main application:

    int main(int argc, char* argv[])
    {
      launcher::gui::Application application(argc, argv);
      application.connect(&application, SIGNAL(lastWindowClosed()),
                          &application, SLOT(quit()));
    
      QTranslator translator;
      qDebug() << translator.load("lang.en_US.qm");
      application.installTranslator(&translator);
    }
    

    In the class itself, the code looks like this:

    namespace launcher
    {
      namespace gui
      {
        namespace dialog
        {
          NewObject::NewObject(..., QWidget* parent) 
          : Base(parent)
          {
            ...
            QLabel* name = new QLabel(tr("object_name"))
          }
        }
      }
    }
    

    In the documentation, there's a hint that the Q_OBJECT macro automatically sets the context for the class correctly, i.e. it defines tr(x) inside the class as qApp->translate("NewObject", x).

    And this seems to be where my translation is broken. The following two lines should - as far as I understand - generate the exact same translated output.

    qDebug() << tr("object_name");
    qDebug() << QCoreApplication::translate("NewObject", "object_name");
    

    But The outputs are different with the second one being the actual translation from the qm file:

    "object_name"
    "Object Name*"
    

    What am I missing here? Is it something with the namespaces and if so, how and where can I factor them in?

    Thanks a lot!
    Tobias



  • @tobiSF said in QObject::tr() is not working:

    Q_OBJECT macro

    Just in case, could you please post the NewObject class definition (usually .h file). Remember that:

    The Q_OBJECT macro must appear in the private section of a class definition that declares its own signals and slots or that uses other services provided by Qt's meta-object system.



  • Hi Pablo,

    here's the relevant part of the class definition:

    namespace launcher
    {
      namespace gui
      {
        namespace dialog
        {
          class NewObject : public launcher::gui::dialog::BaseDialog
          {
            Q_OBJECT
            
            public:
          
              NewObject(..., QWidget* parent = nullptr);
     
              ...
     
         };
        }
      }
    }
    

    As you see, NewObject inherits from a Base class. This Base dialog class inherits from QDialog.
    And all the signal and slot connections work perfectly fine in the class, so I would assume, the Q_OBJECT macro is placed correctly.


  • Lifetime Qt Champion

    Hi,

    @tobiSF said in QObject::tr() is not working:

    qDebug() << translator.load("lang.en_US.qm");

    Might be a silly question but since that's relative path, did you put the file in the same folder as the executable ?



  • Hi @SGaist,

    I have a folder called installation in which I have various subfolders:

    bin/     // contains all executables
    doc/     // contains html help files, the Qt help (myHelp.qhc), and the translation file(s)
    

    Is it necessary to have the translation files in the same folder? Loading into the QTranslater worked with no problem and - as stated - the qApp->translate() method works fine when I give the context. I have simplified the code in my initial question but I am using the relative path to the .qm file as ../doc.


  • Qt Champions 2019

    @tobiSF said in QObject::tr() is not working:

    Is it necessary to have the translation files in the same folder?

    If you use relative paths as @SGaist pointed out then yes.



  • I have tracked the problem down, together with a colleague, to the namespaces around the classes. For some reason, lupdate extracts only the class name as context for the translation, not the fully qualified name that includes all namespaces. In my example the context in my *.ts file is

    NewObject
    

    while it should be

    launcher::gui::dialog::NewObject
    

    in the moc source file moc_NewObject.cpp the context is listed as the latter.

    If I overwrite the context in the ts file using the full namespaces, everything works and my class displays the translated texts. So the question now is, how do I make sure, lupdate recognizes and factors in the namespaces around my classes. I found some posts online that said to include a translator specific comment:

    /*
      TRANSLATOR launcher::gui::dialog::NewObject
    */
    

    But that was not working either.

    When running lupdate the strings wrapped in tr() are detected, but there is also this warning:

    Qualifying with unknown namespace/class ::NewObject
    

    I'm also not using a *.pro file but feed in the source and header files:

    lupdate ../src/launcher/gui/dialog/*Object.* -ts translate.ts
    

  • Moderators

    @tobiSF
    just a shot into the dark, but worth a try.
    Add the Q_NAMESPACE macro to your namespaces.
    But i would be surprised when lupdate would depend on MOC.

    I also have the faint feeling that i also already stumbled upon this issue. But i think i didn't track it down to a solution. Also it still was in Qt4.


  • Lifetime Qt Champion

    Can you recreate that situation with a minimal project using only one of your classes ?


  • Qt Champions 2019



  • @SGaist I have not tried that out yet, will do today and then post it here.
    @raven-worx, I'm not saying lupdate depends on MOC but the context that lupdate extracts is a different one than what is set by the Q_OBJECT macro. I'll try the Q_NAMESPACE macro, maybe that'll do the trick.
    @Christian-Ehrlicher, is there another tool that is better at this?



  • So, here's the small(ish) example program to reproduce the problem. In the full project, the namespaces in the source and header files correspond to the location in (sub)folders of the project.

    main.cpp

    #include <NewObject.hpp>
    
    #include <QtWidgets/QApplication>
    #include <QtCore/QTranslator>
    #include <QtCore/QDebug>
    
    int main(int argc, char* argv[])
    {
      QApplication application(argc, argv);
      application.connect(&application, SIGNAL(lastWindowClosed()),
                          &application, SLOT(quit()));
    
      QTranslator translator;
      qDebug() << "Translator loaded: "<< translator.load("test.qm");
      application.installTranslator(&translator);
    
      launcher::gui::dialog::NewObject object;
      object.show();
    
      return application.exec();
    }
    

    NewObject.hpp

    #pragma once
    
    #include <QtWidgets/QDialog>
    
    namespace launcher
    {
      namespace gui
      {
        namespace dialog
        {
          class NewObject : public QDialog
          {
            Q_OBJECT
    
            public:
    
              NewObject(QWidget* parent = nullptr);
          };
        }
      }
    }
    

    NewObject.cpp

    #include <NewObject.hpp>
    
    #include <QtWidgets/QLabel>
    #include <QtWidgets/QLineEdit>
    #include <QtWidgets/QPushButton>
    #include <QtWidgets/QHBoxLayout>
    #include <QtWidgets/QFormLayout>
    #include <QtCore/QDebug>
    
    namespace launcher
    {
      namespace gui
      {
        namespace dialog
        {
          NewObject::NewObject(QWidget* parent) : QDialog(parent)
          {
            QLabel* username_label = new QLabel(tr("username"));
            QLineEdit* username_edit = new QLineEdit();
    
            QLineEdit* password_edit = new QLineEdit();
            password_edit->setEchoMode(QLineEdit::Password);
    
            QPushButton* ok = new QPushButton(tr("ok"));
            connect(ok, &QPushButton::clicked, this, &QDialog::accept);
            
            QPushButton* cancel = new QPushButton(tr("cancel"));
            connect(cancel, &QPushButton::clicked, this, &QDialog::reject);
    
            auto button_layout = new QHBoxLayout();
            button_layout->addWidget(ok);
            button_layout->addWidget(cancel);
    
            auto layout = new QFormLayout();
            layout->addRow(username_label, username_edit);
            layout->addRow(tr("password"), password_edit);
            layout->addRow("", button_layout);
    
            setLayout(layout);
          }
        }
      }
    }
    

    CMakeLists.txt

    cmake_minimum_required (VERSION 3.9)
    project (NamespaceTest  LANGUAGES CXX)
    
    set (CMAKE_CXX_STANDARD 11)
    set (CMAKE_CXX_STANDARD_REQUIRED true)
    set (CMAKE_INCLUDE_CURRENT_DIR ON)
    set (CMAKE_AUTOMOC ON)
    
    find_package (Qt5 REQUIRED
        Core
        Gui
        Help
        Widgets
    )
    
    add_definitions ( ${Qt5Widgets_DEFINITIONS} )
    
    include_directories (${CMAKE_SOURCE_DIR})
    
    set (NamespaceTestSources
        main.cpp
        NewObject.cpp
        )
    
    set (NamespaceTestQtHeaders
        NewObject.hpp
        )
    
    add_executable (NamespaceTest
        ${NamespaceTestSources}
    )
    
    target_link_libraries (NamespaceTest ${Qt5Core_LIBRARIES} ${Qt5Widgets_LIBRARIES})
    

    To generate the help files, I do

    1. lupdate ./*.cpp -ts test.ts
    2. Use Qt 5 Linguist to do the actual translations and update the test.ts file
    3. lrelease -nounfinished test.ts -qm test.qm
    4. Copy the resulting test.qm file into the same directory as the executable

    The debug output in the main.cpp confirms that the translation file was loaded correctly, but the translations are not applied to the gui because of the context in the ts file being "NewObject" and not "launcher::gui::dialog::NewObject".



  • All, the mystery is solved after I asked Qt support about the issue. Turns out, it is associated with the lupdate routine not finding the header files for the source files scanned. In order to get the correct and fully namespace qualified context, you'll have to run this (assuming your sources are in a folder src):

    lupdate ./src -ts test.ts -I ./src
    

    With the -I flag, the include path for the header file is handed to lupdate and with this information it produces a correct ts file.

    Thanks all for trying and your help!


Log in to reply