本页内容

基于ID的本地化时钟转换

本示例展示了在 CMake 和Qt Quick 中使用 Qt 基于 ID 的翻译功能的最佳实践,包括不同语言中复数形式的处理以及本地化时间格式。

用户界面

该示例以系统区域设置和语言显示当前时间和日期。支持以下语言的翻译:英语、德语、法语、西班牙语、意大利语、日语、韩语、葡萄牙语、阿拉伯语和中文。如果您的桌面语言为其他语言,则会回退到英语。

该示例还支持将区域设置作为命令行参数传入,以便在不更改系统设置的情况下测试不同的语言和区域设置:localizedClockIdBased --locale en_GB 或localizedClockIdBased --locale de

默认情况下,该应用程序会以当前区域设置显示时间和日期,但同时也提供了一个按钮,点击后会打开一个包含时区列表的对话框。用户可以通过该对话框更改时钟的时区。

基于 ID 的翻译

在此示例中,我们采用基于 ID 的翻译,其中可翻译文本通过唯一 ID进行标识,而非传统的上下文 + 文本组合(参见“基于文本 ID 的翻译”)。这种方法允许在不同上下文中复用翻译内容。 我们将通过在Main.qml与“时区对话框”(一个用 C++ 编写的基于QWidget 的表单)之间共享翻译内容来演示这一点。基于 ID 的翻译的另一个特点是,它将 UI 中显示的文本与开发人员分离,使得源代码独立于实际呈现给用户的文字。

在以下两张英文版应用程序的截图中,主窗口(QML)和对话框(C++)中出现的两处“Select time zone: ”文本,通过使用相同的 ID 共享了同一条翻译。

英文版应用程序窗口的屏幕截图:

显示时区列表的对话框(英文版):

德语版应用程序的主窗口:

显示时区列表的对话框(德语版):

实现

该应用程序由五个部分组成:

CMakeLists.txt

该应用程序的 CMake 文件启用了 Qt 基于 ID 的翻译和本地化支持。以下是相关代码片段:

find_package(Qt6 REQUIRED COMPONENTS Core Linguist Qml Quick):查找并链接所需的 Qt 6 模块,包括对国际化至关重要的 `Linguist`。qt_standard_project_setup():配置国际化系统,以支持所列的语言环境。尽管源语言为英语,但在使用基于 ID 的翻译时仍需提供英语译文。 源代码中仅包含 ID,不会涉及源文本。因此,我们需要在项目中配置英文翻译,以便 qt_add_translations 能为英语生成 TS 文件;否则,运行时将出现缺失。

qt_standard_project_setup(REQUIRES 6.8
    I18N_TRANSLATED_LANGUAGES de ar ko zh ja fr it es pt en)

qt_add_translations(...):整合了lupdate 和lrelease 的功能,通过以clock 为基名在“i18n”目录中生成翻译源文件(TS 文件),并将其编译为二进制.qm 文件(如果文件中包含翻译内容)。

  • 根据qt_standard_project_setup 中I18N_TRANSLATED_LANGUAGES 列出的每种语言生成一个 TS 文件。
  • MERGE_QT_TRANSLATIONS 并让QT_TRANSLATION_CATALOGS qtbase 将 Qt 翻译包含到项目中。这是为了翻译时区对话框中 QDialog 控件的按钮。由于这些按钮上的文本由QDialog 控制,如果不包含 Qt 翻译,这些按钮将无法被翻译(参见“基于 ID 的翻译”中德语截图所示的对话框翻译文本)。
qt_add_translations(localizedClockIdBased
    TS_FILE_BASE i18n/clock
    MERGE_QT_TRANSLATIONS
    QT_TRANSLATION_CATALOGS qtbase
    RESOURCE_PREFIX i18n
)

qt_add_qml_module(...):在 URIqtexamples.localizedclock 下添加一个 QML 模块,包含Main.qml文件,并将“时区管理器”的源文件和头文件导入到该 QML 模块中。

qt_add_qml_module(localizedClockIdBased
    URI qtexamples.localizedclock
    VERSION 1.0
    QML_FILES
        Main.qml
    RESOURCES dialog.ui
    SOURCES
        timezonemanager.h timezonemanager.cpp
        dialog.h dialog.cpp
)
main.cpp

应用程序的起点。此部分负责设置区域设置、安装所需的翻译文件以及加载用户界面。以下是对相关代码片段的说明:

定义区域设置参数,例如:--locale en_US 或--locale de_DE :

    QCommandLineParser parser;
    QCommandLineOption localeOption("locale"_L1, "Locale to be used in the user interface"_L1,
                                    "locale"_L1);
    parser.addOption(localeOption);
    parser.addHelpOption();
    parser.process(app);

解析参数,获取提供的区域设置,并将输入区域设置设为应用程序的默认区域设置:

        QLocale locale(parser.value(localeOption));
        qInfo() << "Setting locale to" << locale.name();
        QLocale::setDefault(locale);

无论区域设置如何,都安装英文翻译,以便允许其他语言的翻译不完整。QTranslator 按翻译安装顺序的逆序查询文本的翻译:

    QTranslator enPlurals;
    const autoenPluralsPath= ":/i18n/clock_en.qm"_L1;
    if(!enPlurals.load(enPluralsPath))
        qFatal("Could not load %s!", qUtf8Printable(enPluralsPath));
    app.installTranslator(&enPlurals);

根据给定的区域设置安装翻译:

    QTranslator translation;
    if(QLocale().language()!=QLocale::English) {
        if(translation.load(QLocale(), "clock"_L1, "_"_L1, ":/i18n/"_L1)) {
            qInfo("Loading translation %s",
                  qUtf8Printable(QDir::toNativeSeparators(translation.filePath())));
            if(!app.installTranslator(&translation))
                qWarning("Could not install %s!",
                         qUtf8Printable(QDir::toNativeSeparators(translation.filePath())));
        }else{
            qInfo("Could not load translation to %s. Using English.",
                  qUtf8Printable(QLocale().name()));
        }
    }

由于我们在上一步中安装了英语翻译,因此可能会有两个已安装的翻译。对于任何重叠的键,Qt 会使用最近安装的翻译。因此,特定于区域设置的翻译将优先于英语翻译;如果存在任何缺失的翻译,QTranslator 会回退到英语。

时区对话框

该类是一个基于 C++QWidget 的对话框(QDialog ),用于显示包含时区列表的QComboBox 。以下是对代码的说明:

在 UI 表单(dialog.ui)中启用基于 ID 的翻译:

<ui version="4.0" idbasedtr="true">

使用基于 ID 的翻译设置标题(dialog.ui)。此处,“title”是该翻译的唯一 ID:

<property name="windowTitle">
    <string id="title">Time Zone</string>
</property>

添加一个基于 ID 的翻译(dialog.ui)的标签,其 ID 为“timezonelabel”:

<widget class="QLabel" name="label">
...
<property name="text">
<string id="timezonelabel">Select time zone</string>
</property>
...
</widget>
时区管理器

位于C++ 中的QML_SINGLETON 类,负责处理时区变更。

当用户选择一个时区后,TimeZoneManager 的实例会记住所选的时区:

        connect(m_dialog.get(), &Dialog::timeZoneSelected, this, &TimeZoneManager::setTimeZone);

更新时区会触发 TimeZoneManager::timeZoneChanged 信号:

        connect(m_dialog.get(), &Dialog::timeZoneSelected, this, &TimeZoneManager::setTimeZone);
    }
    m_dialog->show();
}

TimeZoneManager::currentTimeZoneOffsetMs() 该方法带有Q_INVOKABLE 标记,并返回所选时区的时差。由于TimeZoneManager 类被声明为QML_ELEMENT 和QML_SINGLETON,因此可以直接从QML访问该方法以更新显示的时间;另请参阅Main.qml。

qint64 TimeZoneManager::currentTimeZoneOffsetMs()
{
    const QTimeZone tz(m_timeZone.toLatin1());
    if (!tz.isValid())
        return 0;
    const QDateTime nowUtc = QDateTime::currentDateTimeUtc();

    const int targetOffset = tz.offsetFromUtc(nowUtc);
    const int systemOffset = QTimeZone::systemTimeZone().offsetFromUtc(nowUtc);

    return (targetOffset - systemOffset) * 1000;
}
Main.qml

主 QML 文件定义了应用程序的用户界面。该界面显示时间、日期、当前时区以及秒数计数器。它还提供了一个按钮,用于打开“时区对话框”以更改时区。以下是对相关代码片段的说明:

使用qsTrId()定义窗口并设置其标题,以实现基于ID的翻译。源语言的文本使用元字符串语法//% 指定(参见“基于 ID 的文本翻译”)。lupdate会解析这些元字符串,并将定义的源文本写入 TS 文件。由于源文本是通过元字符串以注释形式指定的,因此运行时应用程序无法看到这些文本。 因此,当应用程序在英语区域设置下加载时,即使源语言为英语,仍需安装英语翻译才能显示英语文本。否则,将显示原始ID“Main-Digital-Clock”。这也是我们在CMakeLists.txt的配置中,通过I18N_TRANSLATED_LANGUAGES 指定“en”的原因。

//% "Digital Clock"
title: qsTrId("Main-Digital-Clock")

使用支持复数的qsTrId()显示秒数。同样,源语言的文本通过//% 元字符串指定。通过在元字符串的源文本中使用特殊标记“%n”来启用复数形式(参见“处理复数形式”)。 根据 n 的值,翻译函数会返回不同的翻译结果,并确保目标语言中数词的语法正确。例如在英语中,如果root.seconds 的值大于 1,则使用复数形式;否则使用单数形式。在《复数形式的翻译规则》中,您可以查阅不同语言的复数规则。

//% "%n second(s)"
text: qsTrId("Main-n-second-s", root.seconds)

使用基于 ID 的翻译显示当前选定的时区,其中 `//% "Time zone: " ` 指定源语言的文本:

//% "Time zone: "
text: qsTrId("timezone") + TimeZoneManager.timeZone;

一个用于打开“时区”对话框的按钮。该按钮上的文本通过qsTrId()进行基于ID的翻译。在此情况下,源文本不再由元字符串定义,因为这是对ID“timezonelabel”的复用,该ID此前已在dialog.ui文档中使用过(参见“时区”对话框)。 在基于 ID 的翻译中,只需在项目中针对每个 ID 指定一次源文本即可:

        Button {
            text: qsTrId("timezonelabel")
            onClicked: TimeZoneManager.openDialog()
            Layout.alignment: Qt.AlignHCenter
        }

在更改时区并接收到TimeZoneManager::timeZoneChanged() 信号后(参见“时区管理器”),请使用所选时区的时差值更新diff 变量:

    Connections {
        target: TimeZoneManager
        function onTimeZoneChanged() {
            root.diff = TimeZoneManager.currentTimeZoneOffsetMs();
        }
    }

声明一个每秒触发一次的定时器,用于更新时间、日期和秒数属性。该定时器通过将当前时间与所选时区的时间偏移量相加来计算时间:

    Timer {
        interval: 1000
        running: true
        repeat: true
        triggeredOnStart: true

        onTriggered: {
            const now = new Date(new Date().getTime() + root.diff);
            const locale = Qt.locale();
            root.time = now.toLocaleTimeString(locale, Locale.ShortFormat);
            root.date = now.toLocaleDateString(locale);
            root.seconds = now.getSeconds();
        }
    }

区域设置会影响日期和时间的显示方式。这些内容会根据当前区域设置所在国家的惯例进行格式化。例如,德国区域设置会采用 24 小时制和DD.MM.YYYY日期格式,而美国区域设置则使用 12 小时制和MM/DD/YYYY日期格式。

示例项目 @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.