3

I integrated the QTranslator class in my Project. So far everything works and on restart of the program all text fields are translated. Now I would like to provide dynamic translation, so the users don't need to restart the application.

What I found in my research is that its necessary to reimplement the changeEvent() function like this:

void MyWidget::changeEvent(QEvent *event)
{
    if (event->type() == QEvent::LanguageChange) {
        titleLabel->setText(tr("Document Title"));
        ...
        okPushButton->setText(tr("&OK"));
    } else
        QWidget::changeEvent(event);
}

(Source: http://doc.qt.io/qt-5/internationalization.html#dynamic-translation)

For applications written with Qt designer it seems like its possible to just call

ui->retranslateUi(this);

within the changeEvent() function and all text fields will be translated. But for all other texts in the application the text must be set as in the example above. Which I find painful because I always need to update the text at two places when I change something (in the changeEvent function and in the main part of my program). With a lot of text fields it could easily happen to miss something.

Is there a way to update these text fields without the need to duplicate the "text-setting-methods"?

cbuchart
  • 10,847
  • 9
  • 53
  • 93
J.A.Norton
  • 115
  • 1
  • 12
  • 1
    In my project i just create in classes where i need translations methods like retranslate(); which would be called when re-translation is needed. Basically a call retranslate on parent and he in turn calls retranslate methods on child classes – Shf Mar 14 '17 at 14:11
  • What happens inside these retranslate() methods? – J.A.Norton Mar 14 '17 at 14:21
  • @J.A.Norton these are methods generated by the Qt MOC tool. They amount to a long list of `widget->setText(tr("WidgetText", "WidgetContext"));` calls – Caleth Mar 14 '17 at 15:19

2 Answers2

2

I'm not sure why would you need to duplicate the text setters.

The main idea is to set the translatable texts once in the changeEvent() handler and manually send the LanguageChange event as stated in the docs. This will also fire the event for the child widgets.

MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
    titleLabel = new QLabel(this);
    okPushButton = new QPushButton(this);
    // Fire the LanguageChange event - the event handler will set the texts:
    QEvent languageChangeEvent(QEvent::LanguageChange);
    QCoreApplication::sendEvent(this, &languageChangeEvent);
}

void MyWidget::changeEvent(QEvent *event)
{
    if (event->type() == QEvent::LanguageChange) {
        titleLabel->setText(tr("Document Title"));
        okPushButton->setText(tr("&OK"));
    } else {
        QWidget::changeEvent(event);
    }
}

You can also use some initial QCoreApplication::installTranslator(). This will fire the LanguageChange event and there will be no need to manually post it.


Another approach is to use your own functions instead of events. This approach is generally the same except that you need to call your function manually for child widgets.

MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
    titleLabel = new QLabel(this);
    okPushButton = new QPushButton(this);
    myChildWidget = new MyChildWidget(this);
    retranslate();
}

void MyWidget::retranslate()
{
    titleLabel->setText(tr("Document Title"));
    okPushButton->setText(tr("&OK"));
    myChildWidget->retranslate();
}
kefir500
  • 4,184
  • 6
  • 42
  • 48
  • Duplicate text setters: Because when I go through the code I want to be able to see instantly what text is shown on that (e.g.) button. Sure I could choose a proper variable name and add a comment that holds the text aswell, but I would prefer having it all in one place. When the purpose of the button changes I would need to change the text in changeEvent() and the variablename (and/or the comment). But I guess I got to live with it then. Thank you for your answer! – J.A.Norton Mar 15 '17 at 09:16
  • 1
    An even more interesting scenario is when translations has been generated due to some user interaction (such as pressing several times a button that concatenates a string o reading translatable data from a file). In those cases you have to be able to store the current state and replicate the taken actions. – cbuchart Mar 15 '17 at 12:54
  • 1
    Also, be aware of an [existing bug](https://bugreports.qt.io/browse/QTBUG-3863) that makes `QComboBoxes` to discard current selection when the `retraslateUI()` method is called. The quick solution is similar to my previous comment: save `currentIndex()` and re-select after translation using `setCurrentIndex()`. – cbuchart Mar 15 '17 at 12:59
  • 1
    You may have problems if your data is sorted since the order will change after translation. In this case add an unique ID to each item, use the `currentData()` to save it and find the corresponding index using `findData(...)` after translation. – cbuchart Mar 15 '17 at 12:59
  • 1
    More on, be careful about connected slots that may be called when current index changes. – cbuchart Mar 15 '17 at 13:01
  • 1
    Finally, depending on your application, if you've taken any decision based on widgets size you'd need to check them again since buttons, labels, and so on may have been resized due to longer or shorter texts. And yes, dynamic translation is wonderful but may have many tricky corner ;). – cbuchart Mar 15 '17 at 13:02
1

In the general case, no, you can't avoid this.

One option is rather than having application code directly setting text, connect a signal to a lambda setting the text, then fire the signal. The event handler then just needs to fire the signal too.

An example:

MyWidget::someCalculation() {
    // Some stuff
    disconnect(this, &MyWidget::updateText, button, Q_NULLPTR);
    connect(this, &MyWidget::updateText, button, [someLocalString](){ button->setText(tr("Button Text %1").arg(someLocalString)); });
    // More stuff
    emit updateText();
}

// Other methods

void MyWidget::changeEvent(QEvent *event)
{
    if (event->type() == QEvent::LanguageChange) {
        emit updateText();
    } else {
        QWidget::changeEvent(event);
    }
}
Caleth
  • 52,200
  • 2
  • 44
  • 75