Creating a Qt Quick Multilingual App

This is a translation of the Japanese blog post Qt Quick多言語アプリの作り方 by Solutions Engineer Mikio Hirai.
 

This blog post provides step-by-step instructions on how to create a multilingual application with Qt Quick.

Scope

  • Learn how to use lupdate, lrelease, and Qt Linguist to make Qt apps available in multiple languages
  • Learn how to use qsTr() and qsTrId()
  • Automate the translation workflow with CMake
  • Translate string data in arrays and models using QT_TR_NOOP() and QT_TRID_NOOP()

Environment

The following is the environment for the projects shown here.

  • Windows 11
  • Qt 6.7.1 (MSVC 2019) Kit

Create a Project

Now, let's get started with creating a project. After you have installed Qt, open Qt Creator and let's do this together. You can install Qt from here.

In Qt Creator, press Create Project from the Welcome screen.

In the New Project dialog, select Application (Qt) > Qt Quick Application > Choose

In the Project Location dialog, enter the name of the project in the Name field and the path where the project is to be created in the Create in field and and select Next.

In the Project Details dialog, press Next without changing anything.

 

In Kit Selection, select Desktop Qt 6.7.x MSVC2029 64-bit and click Next..

 

In Project Management, select the version control Git. This is optional. This creates a local Git repository and a .gitignore file with common exclusion patterns for Qt.

Finally select Finish to create the project.

Displaying Strings

Since we are creating a multilingual application, let's start by displaying some strings. The easiest method to show strings in Qt Quick is by using the QML Text element.

 

Add Text as a child element of Window in Main.qml as shown above.

The text property is set to “Hello World!” as the string to be displayed this time, and anchors.centerIn is set to parent. The latter setting causes the text to be displayed centered in the window.

Now build and run the program by pressing the green Run button on the bottom left of Qt Creator.


We will change the size of the Hello World! text before translating it. Let's find out how to do this.

In Qt Creator, pressing F1 after clicking a QML type or property will display the documentation of the selected QML type or property in the IDE.

Click inside the help pane and search for "size" by pressing Ctrl + F to bring up the search box. This will find properties that are related to the size of the text. The following setting looks related.

Click on font.pixelSize for more information. This looks like a property that allows you to specify the pixel size of the font in the Text element.

    Text {
        anchors.centerIn: parent
        text: qsTr("Hello World!")
        font.pixelSize: 24
    }

In the QML code, set it to 24 and run the application again. The text "Hello World!" should now be bigger.

Let's try multilingualization

Now that we have created one string, let's take a look at the multilingualization workflow. To make a string multilingual, you need to enclose it in a QML API that indicates “this string is to be translated”.

qsTr()

qsTr() is the most orthodox multilingual API. To use it, enclose the string to be translated as follows. Here, the argument string is the string written in the original language (in this case, English).

If the application is run after this change, the result will still look the same.

To translate strings, you need to start with a command line tool called lupdate. It is located in the bin folder of the installation directory of the Qt kit, for example, C:\Qt\6.7.2\msvc2019_64\bin.

When you scan your project using lupdate, it will generate a file with extension .ts which is an XML file containing the strings enclosed in qsTr() and other multilingual APIs in the QML file.

First create a folder named i18n in your project folder. In this post, we will save our files in this folder (this is optional, you may use a different structure). Now let's generate the .ts file from our Main.qml.

We will run lupdate from Qt Creator's terminal or from a command prompt. A list of command line options can be found in the documentation here. Running the command as follows will generate qml_ja_JP.ts in the i18n folder.

C:\Qt\6.7.2\msvc2019_64\bin\lupdate . -extensions qml -ts i18n/qml_ja_JP.ts
Scanning directory '.'...
Updating 'qml_ja_JP.ts'...
    Found 2 source text(s) (2 new and 0 already existing)

This command says, “Recursively scan files with a .qml extension in the current directory and make a file called qml_ja_JP.ts in the i18n folder from the strings enclosed in APIs like qsTr()."

Incidentally, the name of the .ts file for a multilingual QML application must start with "qml_".

The .ts file can end with ISO-639 language code (lower case) and ISO-3166 country code (uppercase), so this time we chose ja_JP because we are creating a Japanese translation for an English source text. A .ts file is created for each language (or language + region) to be translated. In this case, only qml_ja_JP.ts was created for the English source text and Japanese target, but if you want to translate to Korean, for example, qml_ko_KR.ts should be created as well.

If you open the .ts file with a text editor, you will see that the two strings are included as shown below. (If you look closely, you will notice that the window title, Hello World, is was also enclosed in qsTr().

Since it is difficult to read the .ts files when opened in a regular editor, we use a tool called Qt Linguist to edit them. It is located in the bin folder of the Qt installation directory and can be started from the Windows Start menu.

Open the qml_ja_JP.ts file generated earlier (File menu > Open), and you will see the following screen. Reference: Qt Linguist user interface.

The various parts of the tool are described below.

  • Context - Displays the context of the translation. In this example it is simply the name of the QML file. The file name is Main.qml, so it is shown as Main.
    If multiple .qml files are scanned with lupdate, the name of each file is displayed here, and the strings belonging to the selected QML file are displayed in Strings on the right.
    In this case, "Hello World" (from the title) and "Hello World! (from the Text element) are displayed.
  • When a Source text item is selected, the line of the QML file corresponding to it is highlighted in Sources and Forms on the right.

Let's start with the first translation, the title “Hello World”. Select  it as shown below, and fill in "Translation to 日本語 (Japanese)" and "Translator comments for Japanese".

Click the question mark (?) on the left to change it to a green check mark that indicates the translation is complete.

Once the changes made so far are saved with the save toolbar icon in the upper left, the source phrase, translation, and definition will appear in Phrases and guesses. Simply put, you can think of this as a list of source and target texts.

When you save your changes using the Save toolbar icon in the upper left corner, the source phrase, translation, and definition appear in Phrases and guesses. You can think of this as a list of source and target texts.

Then select the second text "Hello World" and translate it in the same way.

Now a warning appears saying "The translation does not end with the same punctuation as the source text. In fact, "Hello World!" ends with an exclamation mark (!) in the source, but not in the translation. This has been pointed out.

Open Qt Linguist's Validation menu to see what kind of validation is being performed and to enable/disable each validation.

The current translation is fine as it is, so we will ignore this warning this time. Click the exclamation mark to the left of "Hello World!" and change it to a yellow checkmark. Then save your changes.

Note that depending on the source language, there are cases where the same word must be translated into a completely different word, and the intent must be clearly communicated to the translator so as not to cause misunderstanding. For example, if the word is "orange," it may not be clear whether it refers to the fruit "orange" or the color "orange" (which are different words in Japanese). To avoid misunderstandings in such cases, QML's qsTr() and other translation APIs provide convenience functions. Let's take a look at them.

Comments for translators allows translators to see comments in Qt Linguist. Main comments can be defined by starting them with //:. Let's add a translator comment to Main.qml like this:

Window {
    width: 640
    height: 480
    visible: true
    //: This is the window title. We want to greet the world.
    title: qsTr("Hello World")

    Text {
        anchors.centerIn: parent
        //: This is the text displayed in the center of the window.
        text: qsTr("Hello World!")
        font.pixelSize: 24
    }
}

Once this is done, run lupdate again to update the qml_ja_JP.ts file.

NOTE: At the time of writing this post, comments added after the .ts file is generated are not added to the existing file. This is a known issue and has been reported in QTBUG-128537. As a workaround, create a new file and manually copy the <extacomment> lines into the existing file that contains translations.



When the file is reopened in Qt Linguist, the comments can be seen as follows under Developer comments. These can be used by the translator to understand the developer's intent.

In addition to comments to the translator, there is another way to communicate the developer's intent to the translator. One such method is disambiguation, used when translating words with different meanings. qsTr() can take a second argument, which is a disambiguation comment.

Window {
    width: 640
    height: 480
    visible: true
    //: This is the window title. We want to greet the world.
    title: qsTr("Hello World")

    Column {
        anchors.fill: parent
        spacing: 10

        Text {
            //: This is the text displayed in the window.
            text: qsTr("Hello World!")
            font.pixelSize: 24
        }
        Text {
            text: qsTr("Orange", "The color orange")
            font.pixelSize: 24
        }
        Text {
            text: qsTr("Orange", "The fruit")
            font.pixelSize: 24
        }
    }
}

The example above uses this second argument. Run lupdate again to update qml_ja_JP.ts. 

NOTE: At the time of writing, Qt Linguist must be closed before updating the file. Bug reports QTBUG-128603 and QTBUG-128604

The second argument of qsTr() appears as a developer comment. We will translate the strings based on these comments.

Displaying the UI in Multiple Languages

Having completed the translation work up to this point, before introducing the translation APIs other than qsTr(), let's move on to actually checking the translated text on the GUI.

When we run the application, we still see only the original English text. We will modify the application so that it automatically displays the text using the operating system's locale settings.

First open the top-level CMakeLists.txt file and add LinguistTools to find_package(). This enables a convenient CMake API used to make Qt applications multilingual.

Next add I18N_TRANSLATED_LANGUAGES ja_JP to the end of qt_standard_project_setup(). In this example, only ja_JP is added because the English source text is translated only to Japanese. If there are multiple target languages, add the corresponding language and country codes delimited by spaces.

Finally, add the qt_add_translations() lines. The code snippet below shows all these additions.

project(multilingual_demo VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 6.5 REQUIRED COMPONENTS Quick LinguistTools)
qt_standard_project_setup(REQUIRES 6.5 I18N_TRANSLATED_LANGUAGES ja_JP)

qt_add_executable(appmultilingual_demo
    main.cpp
)

qt_add_qml_module(appmultilingual_demo
    URI multilingual_demo
    VERSION 1.0
    QML_FILES
        Main.qml
)

qt_add_translations(appmultilingual_demo
    TS_FILE_BASE qml
    TS_FILE_DIR i18n
    RESOURCE_PREFIX qt/qml/multilingual_demo/i18n
    LRELEASE_OPTIONS -idbased
)

 

NOTE: TS_FILE_BASE must be set to "qml" for QML applications and RESOURCE_PREFIX is of the format qt/qml<name of folder with QML files>/i18n, where i18n is the folder where .ts files were placed.

CMake is automatically run when the file is saved. It can also be run manually by right clicking on the project and selecting Run CMake.

Next select the Projects icon on the toolbar on the left of Qt Creator, select the Build item under the Qt kit in use (for example, Desktop Qt 6.7.2 MSVC2019 64bit), expand Details of Build Steps and scroll down to see <project name>_lrelease and <project name>_lupdate. Select the check boxes to run these operations as a part of the application build, which will make the application multilingual.

When executed, the text appears in Japanese if the OS locale is Japanese. The screenshot below shows the application in the original language and after translation.

NOTE: You can change the OS language in the Windows Settings app as shown below.

Dynamically Changing the Display Language

So far, we learned how to translate the application's text based on the system locale at the time of startup. How do we switch the language while the application is running?

Actually, Qt provides a simple way. By changing the global variable Qt.uiLanguage in QML, you can switch the display language.

As a test, add the following line of code to Main.qml.

Component.onCompleted: Qt.uiLanguage = "en"

Component.onCompleted is an event that fires when the enclosing component is created, and the code after the colon is executed. In the example above, the UI will always be in English, regardless of the OS language.

To make it more obvious that the display language can be changed dynamically, let's have the UI display language switch between Japanese and English when a button is pressed. 

Add a button as shown in the snippet. The Button's onClicked event is used to toggle the language every time it is clicked.

import QtQuick
import QtQuick.Controls

Window {
    width: 640
    height: 480
    visible: true
    //: This is the window title. We want to greet the world.
    title: qsTr("Hello World")

    Button {
        anchors.centerIn: parent
        text: qsTr("Translate")
        onClicked: {
            Qt.uiLanguage = Qt.uiLanguage === "en" ? "ja" : "en"
        }
    }

NOTE: If necessary, translate the button text in Qt Linguist using the method described above.

toggle_between_ja_en

qsTrId()

You may have written multilingual applications using identifiers instead of directly writing the string in the source language. Qt uses qsTrId() instead of qsTr in such cases.

NOTE: Creating ID-based multilingual applications is described in detail in the the official documentation.

Using the previous application as an example, we will learn the ID-based multilingualization workflow.
First, change all qsTr() in Main.qml to qsTrId().

qsTr() and qsTrId() cannot be used together in the same application, be sure to use only one of them consistently!

Remove the second argument used for disambiguation. It is not needed because qsTrId uses a different ID based on the meaning of the word, even if it is the same in the source language.

import QtQuick
import QtQuick.Controls

Window {
    width: 640
    height: 480
    visible: true
    //: This is the window title. We want to greet the world.
    //% "Hello World"
    title: qsTrId("window-title")

    Button {
        anchors.centerIn: parent
        //% "Translate"
        text: qsTrId("translate-button")
        onClicked: {
            Qt.uiLanguage = Qt.uiLanguage === "en" ? "ja" : "en"
        }
    }

    Column {
        anchors.fill: parent
        spacing: 10

        Text {
            //: This is the text displayed in the window.
            //% "Hello World!"

            text: qsTrId("user-greeting")
            font.pixelSize: 24
        }
        Text {
            //% "Orange"

            text: qsTrId("orange-color")
            font.pixelSize: 24
        }
        Text {
            //% "Orange"

            text: qsTrId("orange-fruit")
            font.pixelSize: 24
        }
    }
}

 

Naming conventions for string IDs vary, but here we use all lowercase with hyphens separating words. When run in this state, the string IDs are displayed instead of the actual text. 

When using qsTrId(), you can write the source text enclosed in double quotes as a comment starting with //%. Qt Linguist will pick them up automatically. This is shown in the code above.

This time we need two .ts files, one for Japanese and the other for English, because the QML contains only string IDs. To generate them, make following additions to CMakeLists.txt. 

First back up the existing .ts file by renaming it because we will create both files from scratch.

Add English (en) to qt_standard_project_setup:

qt_standard_project_setup(REQUIRES 6.5 I18N_TRANSLATED_LANGUAGES ja_JP en)


Add LRELEASE_OPTIONS -idbased to qt_add_translations.

qt_add_translations(appmultilingual_demo
    TS_FILE_BASE qml # Must be “qml” to make a QML app multilingual
    TS_FILE_DIR i18n
    RESOURCE_PREFIX qt/qml/multilingual_demo/i18n # Must be “qt/qml/<folder with qml files>/i18n
    LRELEASE_OPTIONS -idbased
)

 

Run CMake, rebuild the project and open the generated qml_en.ts file. You will notice that there is no context enclosed in <name></name>. This is by design (QTBUG-128617), but manually enter "Main" here so that it will be shown when the file is opened in Qt Linguist. Do the same for qml_ja_JP.ts.

Now open qml_ja_JP.ts in Qt Linguist. Click OK on the source and target language confirmation dialog. The target locale must be correctly identified as Language: Japanese and Country/Region: Japan

Without closing qml_ja_JP.ts, open qml_en.ts too. This time select Language: English and Country/Region: Any Territory. Note that if the file had been for a specific country like qml_en_US.ts or qml_en_GB.ts, you would have selected the corresponding country.

 

By opening multiple files, Qt Linguist will show fields for each language so that all text can be entered at the same time. (You can also select multiple files in the file open dialog)

Qt Linguist will now look like this. 

Enter the source text and translations, save the files and rerun the application. The result must be the same as when using qsTr(). 

toggle_between_ja_en

Translating Strings in Arrays and Models

Finally, we will discuss the case where the data in a list contains strings that need to be translated.
In QML, you can use ListModel and ListView to implement a software structure that separates the content of the data from the format in which it is displayed (see Models and Views in Qt Quick).

For example, if you write the following QML code, the data defined in each ListElement in the ListModel will be displayed as a list in the format defined in the delegate of ListView.

Window {
    width: 640
    height: 480
    visible: true

    ListView {
        anchors.fill: parent

        // This defines the data to be displayed in the list
        // Note that it contains only data and does not define the appearance 

        model: ListModel {
            ListElement {
                name: "Apple"
            }
            ListElement {
                name: "Orange"
            }
            ListElement {
                name: "Banana"
            }
        }

        // Defines how each item defined as ListElements in ListModel are shown
        delegate: Text {
            required property string name
            text: name
            font.pixelSize: 24
            color: "green"
        }
    }

 

How should we translate the text defined in the ListElements? The obvious way would be to wrap the strings in a translation API like qsTr() or qsTrId().

However, if we look at the Qt documentation, it says that the text in arrays and models may not be updated if the system is not restarted after changing the language. To avoid this, QT_TR_NOOP() and QT_TRID_NOOP() should be used instead of qsTr() and qsTrId().

NOTE: The API names given in the documentation are a typo and will be fixed in this commit.

Therefore, we need to write the QML code as shown in the snippet below.

    ListView {
        anchors.fill: parent

        model: ListModel {

            ListElement {
                name: QT_TR_NOOP("Apple")
            }
            ListElement {
                name: QT_TR_NOOP("Orange")
            }
            ListElement {
                name: QT_TR_NOOP("Banana")
            }
        }

        delegate: Text {
            required property string name
            text: qsTr(name)
            font.pixelSize: 24
            color: "green"
        }
    }

Note that the delegate uses qsTr() when the model is translated with QT_TR_NOOP(). For ID based translations using QT_TRID_NOOP(), it uses qsTrId().

The strings are translated with Qt Linguist using the same method as described in the previous sections. 

Conclusion

In this article, we have summarized the steps involved in making a Qt Quick application multilingual. For more in-depth topics on internationalizing and localizing applications, such as right-to-left support and automatic string truncation checking, see the following reference articles.


Blog Topics:

Comments