为翻译编写源代码

以支持本地化为方式编写QML和Qt C++源代码

在开发C++应用程序时,还请参阅针对C++代码的额外考虑

为翻译标记字符串

必须翻译的应用程序中的大部分文本由单个单词或短语组成。它们通常出现在窗口标题、菜单项、工具提示以及按钮、复选框和单选按钮的标签中。

Qt通过在创建窗口时翻译每个窗口的短语来最小化使用翻译的性能成本。在大多数应用程序中,主窗口仅创建一次。对话框通常创建一次,然后根据需要显示和隐藏。一旦进行初始翻译,翻译窗口就没有进一步的运行时开销。只有创建、销毁和随后重新创建的窗口才会有翻译性能成本。

您可以创建在运行时切换语言的程序,但这需要付出努力,并会带来运行时性能成本。

使用翻译函数标记QML和C++代码中的用户可见UI文本以进行翻译。Qt通过与之关联的“转换上下文”对每个可翻译字符串进行索引。相同的短语可以在多个上下文中出现而不会冲突。如果特定上下文中短语出现多次,则只翻译一次,并将翻译应用于上下文中每个发生的实例。

QML: 使用qsTr()

在QML中,您可以使用以下函数标记在.qml文件中要翻译的用户可见字符串

通常,您使用qsTr()函数

Text {
    id: txt1
    text: qsTr("Back")
}

此代码使返回成为翻译源(TS)文件中的一个关键字入口。在运行时,翻译系统查找关键字返回,然后获取对应于当前系统区域设置的对应翻译值。结果返回到text属性,UI显示当前区域设置的适当翻译。如果没有找到翻译,则qsTr()返回原始字符串。

可以使用以下方法为给定文件设置转换上下文

pragma Translator: ChosenContext

pragma Translator: "Chosen::Context"

通过qsTranslate()设置的上下文优先于通过pragma Translator设置的上下文。在QML中,默认情况下,翻译上下文是文件名。

C++: 使用 tr()

在 C++ 中,使用 tr() 函数将文本标记为可翻译,并显示翻译后的文本。翻译上下文是在使用该字符串的 QObject 子类的名称。要为基于 QObject 的新类定义翻译上下文,请在每个新类定义中使用 Q_OBJECT 宏。

当调用 tr() 时,它会使用一个 QTranslator 对象查找可翻译的字符串,您必须在应用程序对象中安装该对象,具体说明请参阅 启用翻译

例如,假设 LoginWidgetQWidget 的子类

LoginWidget::LoginWidget()
{
    QLabel *label = new QLabel(tr("Password:"));
    ...
}

这占了你可能编写的用户可见字符串的 99%。有关标记字符串字面量为可翻译的详细信息,请参阅 标记可翻译数据文本字符串

如果引用的文本不在 QObject 子类的成员函数中,则使用适当的类的 tr() 函数或直接使用 QCoreApplication::translate() 函数。

void some_global_function(LoginWidget *logwid)
{
    QLabel *label = new QLabel(
                LoginWidget::tr("Password:"), logwid);
}

void same_global_function(LoginWidget *logwid)
{
    QLabel *label = new QLabel(
                QCoreApplication::translate("LoginWidget", "Password:"), logwid);
}

注意:如果您通过在编译应用程序时定义宏 QT_NO_CAST_FROM_ASCII 禁用了 const char *QString 的自动转换,那么您很可能能够捕捉到任何丢失的字符串。有关更多信息,请参阅 QString::fromUtf8() 和 QString::fromLatin1()。

使用参数而不是连接字符串

不同的语言在短语、子句和句子中排列单词的方式不同,因此不要通过连接单词和数据创建字符串。相反,使用 % 将参数插入到字符串中。

例如,在字符串 After processing file %1, file %2 is next in line 中,%1%2 是编号参数。在运行时,%1%2 分别被替换为第一个和第二个文件名。在翻译中必须出现相同编号的参数,但不需要以相同的顺序。该字符串的德语翻译可能反转短语。例如,Datei %2 wird bearbeitet, wenn Datei %1 fertig ist。两个编号参数都出现在翻译中,但顺序相反。

QML:使用 .arg()

下面的 QML 片段有一个包含两个数字参数 %1%2 的字符串。这些参数通过 .arg() 函数插入。

Text {
    text: qsTr("File %1 of %2").arg(counter).arg(total)
}

%1 表示第一个参数,而 %2 表示第二个参数,因此此代码产生类似 File 2 of 3 的输出。

C++:使用 QString::arg()

在 C++ 中,使用 QString::arg() 函数替换参数

void FileCopier::showProgress(int done, int total,
                              const QString &currentFile)
{
    label.setText(tr("%1 of %2 files copied.\nCopying: %3")
                  .arg(done)
                  .arg(total)
                  .arg(currentFile));
}

此代码产生类5 of 10 files copied. Copying: somefile.txt 的输出。

处理复数形式

您可以向翻译函数传递一个额外的整数参数(n),并在每个可翻译字符串中使用特殊表示法(%n)表示复数形式。

根据 n 的值,翻译函数会返回不同的翻译版本,并保证目标语言的语法正确。同时,任何出现 %n 的位置都会被替换为 n 的值。

例如,字符串 %n message(s) saved 在英语和法语中的翻译需要不同的复数形式。

n无翻译法语英语
0"0 message(s) saved""0 message sauvegardé""0 messages saved"
1"1 message(s) saved""1 message sauvegardé""1 message saved"
2"2 message(s) saved""2 messages sauvegardés""2 messages saved"
37"37 message(s) saved""37 messages sauvegardés""37 messages saved"

这种习语也适用于具有多种复数形式的目标语言,例如 双重 形式。此外,这种习语还可以正确处理如法语这样的语言中 n == 0 的情况。

有关 Qt Linguistlrelease 在包含复数形式的字符串翻译中使用的规则总结,请参阅复数形式翻译规则。

要处理本地语言的复数形式,也请加载该语言的 TS 文件。使用 lupdate 工具的 -pluralonly 命令行选项,来创建仅包含复数形式条目的 TS 文件。

或者,您也可以使用 lconvert 工具的 -pluralonly 命令行选项从现有 TS 文件中删除所有非复数形式。

QML 示例

以下 QML 代码片段将源文本翻译成正确的复数形式,并将 %n 替换为 total 的值

Text {
    text: qsTr("%n message(s) saved", "", total)
}

C++ 示例

以下 C++ 代码片段将 %n 替换为 count() 函数返回的值

int n = messages.count();
showMessage(tr("%n message(s) saved", "", n));

使用区域数字设置

如果您在指定参数时包含 %L 修饰符,则数字将根据当前的区域设置进行本地化。如果您设置了它或使用系统范围的区域,则使用默认的区域。

QML: 使用 %L

例如,在下面的 QML 片段中,%L1 按照当前所选区域(地理区域)的数字格式约定对第一个参数进行格式化

Text {
    text: qsTr("%L1").arg(total)
}

如果 total 是数字 4321.56,在英语区域设置(区域)下输出为4,321.56,而在德语区域设置下为4.321,56

C++: 使用 %Ln

在 C++ 中,您可以使用 %Ln 来生成 n 的本地化表示。使用 QLocale::setDefault() 来设置默认区域。

国际化日期、时间和货币

以本地首选格式呈现日期、时间和货币。

QML: 使用 QtQml 函数

QML 没有特殊的字符串格式化修饰符用于日期和时间。相反,您需要查询当前的区域(地理区域),并使用 Date 的方法来格式化字符串。

Qt.locale() 返回一个 Locale 对象,该对象包含有关区域的信息。特别是,Locale.name 属性包含当前区域的语言和国家。您可以使用这些值直接使用或解析以确定当前区域适当的内容。

以下代码片段使用Date()获取当前日期和时间,然后将该日期转换成当前区域的字符串。然后,将日期字符串插入到%1参数中进行适当的翻译。

Text {
    text: qsTr("Date %1").arg(Date().toLocaleString(Qt.locale()))
}

要本地化货币数字,请使用Number类型。它具有与Date类型类似的函数,可以将数字转换为本地化的货币字符串。

C++: 使用 QLocale 类

在 C++ 中,使用 QLocale::timeFormat() 或 QLocale::toString(QTime) 或 toString(QDate)

QLabel *label = new QLabel(this);
label->setText(tr("Date %1").arg(QLocale().toString(QDate::currentDate()));

标记可翻译的数据文本字符串

使用 _NoOp 函数(在 QML 中)和 _NOOP 宏(在 C++ 中)来标记可翻译的字符串文字以便通过 lupdate 工具提取。

QML: 使用 _NoOp 函数

在 QML 中,使用以下函数标记可翻译的字符串文字

  • qsTrNoOp()
  • qsTranslateNoOp()
  • qsTrIdNoOp()

如果用户在不重新启动系统的情况下更改系统语言,则根据系统设置,数组、列表模型和其他数据结构中的字符串可能不会自动刷新。为了在 UI 显示时强制刷新文本,您需要使用 qsTrNoOp() 函数声明字符串。然后,在您填充要显示的对象时,您需要明确检索每个文本的翻译。

例如

ListModel {
    id: myListModel

    ListElement {
        //: Capital city of Finland
        name: qsTrNoOp("Helsinki")
    }
}

...

Text {
    text: qsTr(myListModel.get(0).name)
    // Get the translation of the name property in element 0
}

C++: 使用 _NOOP 宏

对于完全在函数外的可翻译文本,使用 QT_TR_NOOP() 和 QT_TRANSLATE_NOOP() 宏,它们展开为无上下文的纯文本。

QT_TR_NOOP() 的示例

QString FriendlyConversation::greeting(int type)
{
    static const char *greeting_strings[] = {
        QT_TR_NOOP("Hello"),
        QT_TR_NOOP("Goodbye")
    };
    return tr(greeting_strings[type]);
}

QT_TRANSLATE_NOOP() 的示例

static const char *greeting_strings[] = {
    QT_TRANSLATE_NOOP("FriendlyConversation", "Hello"),
    QT_TRANSLATE_NOOP("FriendlyConversation", "Goodbye")
};

QString FriendlyConversation::greeting(int type)
{
    return tr(greeting_strings[type]);
}

QString global_greeting(int type)
{
    return QCoreApplication::translate("FriendlyConversation",
                                       greeting_strings[type]);
}

为翻译者添加注释

您可以在标记为可翻译的字符串之前在源代码中添加注释,以澄清其目的。这些注释包含在您交付给翻译者的 TS 文件中。

注意:TS 文件是包含源文本和翻译文本位置的 XML 文件。更新的 TS 文件被转换为二进制翻译文件,并作为最终应用程序的一部分包含在内。

QML: 使用 //: 和 //~

在以下代码片段中,//: 行上的文本是翻译者的主要注释。

//~ 行上的文本是可选的额外信息。文本的第一个单词用作 XML 文件中 XML 元素的一个附加标识符,因此请确保第一个单词不是句子的一部分。例如,注释 Context Not related to back-stepping 在 TS 文件中转换为 <extra-Context>Not related to back-stepping

Text {
    id: txt1;
    // This UI string is only used here
    //: The back of the object, not the front
    //~ Context Not related to back-stepping
    text: qsTr("Back");
}

C++: 使用注释字符

要在 C++ 中添加注释,请在您的代码中的 tr() 调用旁边添加 //: 或通过标记注释的开始和结束。

以下示例中,注释与会传递给 tr() 的字符串相关联,每个调用的上下文中

//: This name refers to a host name.
hostNameLabel->setText(tr("Name:"));

/*: This text refers to a C++ code example. */
QString example = tr("Example");

要添加可选注释,请使用

//~ <field name> <field contents>

字段名称应该由域名前缀(可能包括该字段灵感来源的文件格式的传统文件扩展名)、一个连字符,以及实际字段名(使用下划线分隔的符号表示)组成。在TS文件中存储时,字段名连同前缀 extra- 将形成XML元素的名称。字段内容将进行XML转义,但除此外将以原始格式作为元素的文本内容呈现。您可以为每个消息添加任何数量的唯一字段。

示例

//: This is a comment for the translator.
//= qtn_foo_bar
//~ loc-layout_id foo_dialog
//~ loc-blank False
//~ magic-stuff This might mean something magic.
QString text = MyMagicClass::tr("Sim sala bim.");

在C++中,您使用等号添加一个唯一标识符

//= <id>

您可以使用关键词 TRANSLATOR 用于翻译注释。紧跟在 TRANSLATOR 关键词前的元数据适用于整个TS文件。

区分相同文本

翻译系统将UI文本字符串合并成唯一项,以避免重复翻译相同的文本。但是,某个文本可能与另一个文本外观相同,但具有不同的含义。例如,在英语中,“back”可以意味着向后一步,还可以指物体前部的反面。您需要通知翻译系统这两个不同的含义,以便翻译者可以创建两个不同的翻译。

QML:在qsTr()中添加去模糊化器

在QML中,将去模糊化字符串作为 qsTr() 函数的第二个参数添加。

在下面的代码片段中,ID not front 区分了这个“Back”文本和后退的“Back”文本

Text {
    id: txt1
    // This UI string is used only here
    //: The back of the object, not the front
    //~ Context Not related to back-stepping
    text: qsTr("Back", "not front")
}

C++:在tr()中添加去模糊化器

在C++中,通过调用 tr() 传递去模糊化字符串。

在下面的代码片段中,ID recipient 区分了收件人名称与发件人名称

MyWindow::MyWindow()
{
    QLabel *senderLabel = new QLabel(tr("Name:"));
    QLabel *recipientLabel = new QLabel(tr("Name:", "recipient"));
    ...

使键盘快捷键可翻译

在最常见的形式中,键盘快捷键描述了一组按键按下的组合,以执行某些操作。对于 标准快捷键,使用标准键来请求与每个快捷键相关联的平台特定键序列。

对于自定义快捷键,使用可读的字符串,例如 Ctrl+QAlt+F。您可以将它们翻译为适用于不同语言发言者的适当快捷键。

如果您的应用程序中硬编码键盘快捷键,则翻译者无法覆盖它们。

当在菜单项和按钮文本中使用键盘快捷键时,一个 助记符 字符(由下划线标记)表示按 AltCtrl 和下划线字符会执行与单击菜单项或按下按钮相同操作。

例如,应用程序经常在 文件 菜单中使用 F 作为助记符,因此您可以单击菜单项或按 Alt+F 打开菜单。要在可翻译字符串(“文件”)中定义助记符字符,在它前面加上一个 ampersand:"&File"。字符串的翻译也应包含 ampersand,最好是放在相同的字符前面。

QML 示例

在QML

Menu {
    id: fileMenu
    title: qsTr("&File")

    MenuItem {
        objectName: "quitMenuItem"
        text: qsTr("E&xit")
        onTriggered: Qt.quit()
    }
}

C++:使用 QKeySequence 类

在C++中,使用 QActionQKeySequence 对象来指定触发操作的键盘快捷键

exitAct = new QAction(tr("E&xit"), this);
exitAct->setShortcuts(QKeySequence::Quit);

键盘快捷键的翻译与 QShortcut 上下文相关。

使用区域设置扩展本地化功能

您可能会发现不同地区更适合不同的图形或音频。

通常情况下,尽量避免本地化图片。创建适应全球的图标,而不是依赖于当地的俏皮话或拉伸的比喻。然而,对于阿拉伯语和希伯来语地区,您可能需要翻转左右指向的箭头图片。

区域设置是默认的文件选择器之一,因此您可以使用文件选择来根据系统区域显示您作为资源提供的不同图片。

以下部分中的QML和C++代码示例假定您在应用程序资源中提供了以下文件,并使用语言和国家代码作为子文件夹名称。

images
├── language-icon.png
├── +en_GB
│   └── language-icon.png
└── +fi_FI
    └── language-icon.png

QML: 设置图像源

以下QML代码段显示了根据当前区域选择图标源图像的方法。

icon.source: "qrc:/images/language-icon.png"

C++: 使用QFileSelector

以下C++代码段使用QFileSelector根据系统区域从images文件夹选择语言图标。

const QFileSelector selector;
const QIcon languageIcon(selector.select(":/images/language-icon.png"));

启用翻译

TS文件名必须包含ISO语言和国家代码。

  • languageISO-639的小写语言代码。
  • countryISO-3166的大写两位国家代码。

例如,qml_de.ts将目标语言设置为德语,而qml_de_CH.ts将目标语言设置为德语并将目标国家设置为瑞士。lrelease工具会生成名为qml_de.qmqml_de_CH.qm的QM文件,应用程序将根据系统区域加载这些文件。

QML: 使用QQmlApplicationEngine

在QML中,使用QQmlApplicationEngine自动从包含主QML文件的目录中名为i18n的子目录加载翻译文件。翻译文件名必须以前缀qml_开头。例如,qml_en_US.qm

QJSEngine::uiLanguageQt.uiLanguage属性的值更改时,应用程序会重新加载翻译。

C++: 使用QTranslator

在C++中,TS文件名必须包含应用程序名称。例如,app_de_DE.ts

通常,您的Qt C++应用程序的main()函数看起来如下所示

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QTranslator myappTranslator;
    if (myappTranslator.load(QLocale::system(), u"myapp"_s, u"_"_s, u":/i18n"_s))
        app.installTranslator(&myappTranslator);

    return app.exec();
}

对于具有翻译感知的应用程序,您创建一个QTranslator对象,在运行时根据用户的UI显示区域加载翻译,并将翻译器对象安装到应用程序中。

为动态语言变化做准备

Qt Widgets和Qt Quick都使用Qt的事件系统来通知类翻译更改。

使用QCoreApplication::installTranslator()函数安装新翻译时,会发布LanguageChange事件。其他应用程序组件也可以通过向它们发布Item类型衍生的部件或QML类型发布LanguageChange事件来强制它们更新自己。

默认情况下,LanguageChange事件会传播到所有顶层窗口,然后通过Items派生的部件或QML类型的整个树进行传播。

Qt Widgets: 覆盖changeEvent

QWidget子类的默认事件处理器响应QWidget,当需要时调用changeEvent()函数以响应QEvent::LanguageChange事件。

为了让Qt小部件知道安装的QTranslator对象的变化,请重新实现小部件的changeEvent()函数来检查事件是否为LanguageChange事件,并使用tr()函数更新小部件显示的文本。例如

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

当使用Qt Designer UI文件(.ui)和uic时,可以读取新的翻译文件,并直接调用ui.retranslateUi(this)

void MyWidget::changeEvent(QEvent *event)
{
    if (event->type() == QEvent::LanguageChange) {
        ui.retranslateUi(this);
    } else
        QWidget::changeEvent(event);
}

为了传递其他更改事件,请调用函数的默认实现。

在响应LocaleChange事件或在应用程序提供允许用户更改当前应用程序语言的UI时,可能需要更改已安装的翻译器列表。

QML: 从Item派生出来的类型的事件重载

对于没有注册任何自定义C++类型的plain QML应用程序,使用QQmlApplicationEngine足以触发所有翻译绑定更新。

但是,如果您注册了从QQuickItem派生的类型,并且其中一个属性公开了翻译后的文本(或者以其他方式依赖于语言),则重写其event方法并在此处发射属性的变化信号(或者如果属性是可绑定的,则在其中调用notify)。例如

class MyItem : public QQuickItem
{
    Q_OJBECT
    QML_ELEMENT

    Q_PROPERTY(QString greeting READ greeting NOTIFY greetingChanged)

public signals:
    void greetingChanged();
public:
    QString greeting() const
    {
        return tr("Hello World!");
    }

    bool event(QEvent *ev) override
    {
        if (ev->type() == QEvent::LanguageChange)
            emit greetingChanged();
        return QQuickItem::event(ev);
    }
};

这确保了QML中使用该属性的任何绑定都会被重新评估,并考虑语言更改。

QObject衍生类: 使用事件过滤

有些类既不是从QWidget,也不是从QQuickItem派生的,但仍可能需要处理语言更改事件。在这种情况下,在QCoreApplication上安装事件过滤器

class CustomObject : public QObject
{
    Q_OBJECT

public:
    QList<QQuickItem *> managedItems;

    CustomObject(QOject *parent = nullptr) : QObject(parent)
    {
        QCoreApplication::instance()->installEventFilter(this);
    }

    bool eventFilter(QObject *obj, QEvent *ev) override
    {
        if (obj == QCoreApplication::instance() && ev->type() == QEvent::LanguageChange) {
            for (auto item : std::as_const(managedItems))
                QCoreApplication::sendEvent(item, ev);
            // do any further work on reaction, e.g. emit changed signals
        }
        return false;
    }
};

这可能是有必要的,当类提供翻译字符串,稍后又在用户界面(例如,自定义的item模型)中显示出来时;或者当类作为Widgets或Quick Items的容器,并因此负责将这些事件转发给它们。

C++代码的额外注意事项

下面的小节提供了有关在可翻译应用程序中使用Qt C++类和函数的更多信息。

为所有用户可见文本使用QString

QString在内部使用Unicode编码,因此您可以使用熟悉的文本处理操作来透明地处理世界上所有语言。此外,由于所有向用户提供文本的Qt函数都将一个QString对象作为参数,因此没有char *QString转换的开销。

定义翻译上下文

QObject及其每个子类的翻译上下文是该类的名称本身。如果您要派生QObject,请在类定义中使用Q_OBJECT宏来覆盖翻译上下文。宏会将上下文设置为子类的名称。

例如,以下类定义包含了Q_OBJECT宏,实现了一个使用MainWindow上下文的新的tr()函数

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();
    ...

如果在类定义中没有使用Q_OBJECT,上下文将从基类继承。例如,由于Qt中所有基于QObject的类都提供上下文,因此如果一个没有Q_OBJECT宏的新QWidget子类调用其tr()函数,它将使用QWidget上下文。

翻译非Qt类

对于不继承QObject或使用Q_OBJECT宏的类中的字符串,您必须提供额外的信息给lupdate。要将翻译支持添加到非Qt类,您可以使用Q_DECLARE_TR_FUNCTIONS()宏。例如

class MyClass
{
    Q_DECLARE_TR_FUNCTIONS(MyClass)

public:
    MyClass();
    ...
};

这为类提供了可用的tr()函数,您可以使用这些函数翻译与类相关的字符串,并使用lupdate在源代码中查找可翻译的字符串。

或者,您可以使用特定的上下文调用QCoreApplication::translate()函数,该上下文可由lupdate和Qt Linguist识别。

翻译不属于QObject子类的文本文本

如果引述文本不在QObject子类的成员函数中,请使用适当的类的tr()函数或直接使用QCoreApplication::translate()函数

void some_global_function(LoginWidget *logwid)
{
    QLabel *label = new QLabel(
            LoginWidget::tr("Password:"), logwid);
}

void same_global_function(LoginWidget *logwid)
{
    QLabel *label = new QLabel(
            QCoreApplication::translate("LoginWidget", "Password:"),
            logwid);
}

© 2024 Qt公司有限公司。此处包含的文档贡献是各自所有者的版权。此处提供的文档是根据自由软件基金会发布的GNU自由文档许可证版本1.3的条款许可的。Qt和相应的标志是芬兰和/或其他国家的Qt公司有限公司的商标。所有其他商标均为它们各自所有者的财产。