Writing Source Code for Translation
使用 Qt 编写跨平台国际化软件是一个优雅, 渐进过程. 你可以按照以下部分描述的步骤实现国际化. 有关 Qt Quick 应用程序如何国际化, 参见 Internationalization and Localization with Qt Quick.
Using QString for All User-Visible Text
由于 QString 内部使用 Unicode 编码, 因此可以使用熟悉的文本处理操作透明地处理世界上的每种语言. 此外, 由于所有向用户呈现文本的 Qt 函数都采用 QString 作为参数, 因此不存在 char *
到 QString 转换开销.
"programmer space" 中的字符串(如 QObject 名称和文件格式文本) 不需要使用 QString; 传统的 char *
或 QByteArray 就足够了.
你不太可能注意到你正在使用 Unicode; QString, QChar 仅是传统 C 语言中 const char *
和 char
的更便捷版本.
当隐式转换为 QString 时, 源代码中的 char *
字符串被假定为 UTF-8 编码. 如果你的 C 字符串文本使用不同的编码, 使用 QString::fromLatin1() 或 QTextCodec 将文本转换为 Unicode 编码的 QString.
Using tr() for All Literal Text
无论你的程序在何处使用将呈现给用户的字符串文字 (带引号的文本), 确它由 QCoreApplication::translate() 函数处理. 本质上, 实现此目的所需要做的就是使用 tr() 函数为你的类获取翻译后的文本, 通常用于显示目的. 此函数还用于指示应用程序中的哪些文本字符串是可翻译的.
例如, 假设 LoginWidget
是 QWidget 的子类:
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 通过与其关联的翻译上下文对每个可翻译字符串进行索引; 这通常是它所使用的 QObject 子类的名称.
通过在每个新类定义中使用 Q_OBJECT 宏, 为新定义的, 基于QObject 的类定义翻译上下文.
当调用 tr() 时, 它使用 QTranslator 对象查找可翻译字符串. 为了使翻译工作, 必须按照 Enabling Translation 描述的方式将其中一个或多个安装在应用程序对象上.
在 QML 中翻译字符串的方式与在 C++ 中完全相同, 唯一的区别是你需要调用 qsTr() 而不是 tr(). 参见 Internationalization and Localization with Qt Quick.
Defining a Translation Context
QObject 和每个 QObject 子类的翻译上下文是类名本身. 子类化 QObject 的开发人员必须在类定义中使用 Q_OBJECT 宏覆盖翻译上下文. 这个宏将上下文设置为子类的名称.
例如, 以下类定义包含 Q_OBJECT 宏, 实现使用 MainWindow
上下文的新 tr():
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); ...
如果类定义中未使用 Q_OBJECT, 则上下文将从基类继承. 例如, 由于 Qt 中所有基于 QObject 的类都提供上下文, 因此, 如果调用 tr() 函数, 则没有 Q_OBJECT 宏定义的新 QWidget 子类将使用 QWidget
上下文.
Using tr() to Obtain a Translation
以下示例显示了如何获取上一节中显示的类的翻译:
void MainWindow::createMenus() { fileMenu = menuBar()->addMenu(tr("&File")); ...
这里, 翻译上下文是 MainWindow
, 因为它是调用的 MainWindow::tr()
函数. tr() 函数返回的文本是从 MainWindow
上下文获取的 "&File" 的翻译.
当 Qt 的翻译工具 lupdate 用于处理一组源文件时, tr() 调用中包含的文本存储在翻译文件中与其翻译上下文相对应的部分中.
在某些情况下, 通过完全限定对 tr() 的调用来显式给出翻译上下文很有用; 例如:
QString text = QScrollBar::tr("Page up");
这里调用从 QScrollBar
上下文中获取 "Page up" 的翻译文本. 开发人员还可以使用 QCoreApplication::translate() 函数获取特定翻译上下文的翻译文本.
Using tr() to Localize Numbers
你可以使用适当的 tr() 字符串本地化数字:
void Clock::setTime(const QTime &time) { if (tr("AMPM") == "AMPM") { // 12-hour clock } else { // 24-hour clock } }
在这个例子里, 对于美国, 我们将保留 "AMPM" 的翻译原样, 从而使用 12 小时时钟分支; 但是在欧洲, 我们将其翻译为其他内容, 以使代码使用 24 小时时钟分支.
Translating Non-Qt Classes
有时, 需要为不继承 QObject 的类中使用的字符串提供国际化支持, 或使用 Q_OBJECT 宏启用翻译功能. 由于 Qt 在运行时根据与其关联的类翻译字符串, 并且 lupdate
在源码中查找可翻译的字符串, 因此非 Qt 类必须使用也提供此信息机制.
一种方法是使用 Q_DECLARE_TR_FUNCTIONS() 宏向非 Qt 类添加翻译支持; 例如:
class MyClass { Q_DECLARE_TR_FUNCTIONS(MyClass) public: MyClass(); ... };
这个宏为类提供 tr() 函数, 用于翻译与该类关联的字符串, 并使 lupdate
可以在源代码中查找可翻译的字符串.
或者, 可以使用特定上下文调用 QCoreApplication::translate() 函数, 这将被 lupdate
和 Qt Linguist 识别.
Translator Comments
开发人员可以包含有关每个可翻译字符串的信息, 以帮助翻译人员完成翻译过程. 这些是在使用 lupdate
处理源文件时提取的. 添加注释的推荐方法是使用以下形式的注释来注释代码中的 tr() 调用:
//: ...
或
/*
: ... */
示例:
//: This name refers to a host name. hostNameLabel->setText(tr("Name:")); /*: This text refers to a C++ code example. */ QString example = tr("Example");
在这些示例中, 注释将与在每次调用的上下文中传递给 tr() 的字符串相关联.
Adding Meta-Data to Strings
附加数据可以附加到每个可翻译消息. 这些是在使用 lupdate
处理源文件时提取的. 添加元数据的推荐方法是使用以下形式的注释来注释代码中的 tr() 调用:
//= <id>
这可用于为消息提供唯一标识符以支持需要它的工具.
附加元数据的另一种方法是使用以下语法:
//~ <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.");
你可以使用关键字 TRANSLATOR 为译者评论. 在 TRANSLATOR 关键字前面的元数据适用于整个 TS 文件.
Disambiguation
如果同一翻译上下文中的不同角色使用相同的可翻译字符串, 则可以在对 tr() 的调用中传递附加标识字符串. 这个可选的消歧参数用于区分其他相同的字符串.
Example:
MyWindow::MyWindow() { QLabel *senderLabel = new QLabel(tr("Name:")); QLabel *recipientLabel = new QLabel(tr("Name:", "recipient")); ...
在 Qt 4.4 及更早版本中, 此消歧参数是向翻译人员指定注释的首选方式.
Handling Plurals
某些可翻译字符串包含整数值的占位符, 需要根据使用的值进行不同的翻译.
为了帮助解决这个问题, 开发人员向 tr() 函数传递一个额外的整数参数, 并且通常对每个可翻译字符串中的复数使用特殊符号.
如果此参数等于或大于零, 则结果字符串中出现的所有 %n
都将替换为所提供值的十进制表示形式. 此外, 所使用的翻译将根据每种语言的规则适应该值.
示例:
int n = messages.count(); showMessage(tr("%n message(s) saved", "", n));
下表显示了根据实际情况返回的字符串:
Active Translation | |||
---|---|---|---|
n | No Translation | French | English |
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 == 1 ? tr("%n message saved") : tr("%n messages saved")
因为它也适用于具有多种复数形式的目标语言 (如., 爱尔兰语有一种特殊的 "双重" 形式, 当 n
为 2 时应使用), 且对于需要的语言 (如法语), 它可以正确处理 n == 0 的单一情况.
要处理母语的复数形式, 你还需要加载该语言的翻译文件. lupdate 工具具有 -pluralonly
命令行选项, 这个选项允许创建仅包含复数形式条目的 TS 文件.
详见 Plural Forms in Translations.
你可以使用 %Ln
替代 %n
生成本地化表示. 转换使用默认区域设置, 使用 QLocale::setDefault() 设置. (如果未指定默认区域设置, 则使用系统范围的区域设置.)
翻译包含复数的字符串的规则参见 Translation Rules for Plurals.
Translating Text That is Outside of a QObject Subclass
Using QCoreApplication::translate()
如果引用文本不在 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); }
Using QT_TR_NOOP() and QT_TRANSLATE_NOOP() in C++
如果你需要完全在函数之外获得可翻译文本, 有两个宏可以提供帮助: QT_TR_NOOP() 和 QT_TRANSLATE_NOOP(). 它们只是标记要由 lupdate
工具提取的文本. 这个宏仅扩展到文本 (没有上下文).
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]); }
如果通过定义宏 QT_NO_CAST_FROM_ASCII
编译软件, 禁用 const char *
到 QString 的自动转换, 那么你很可能会捕获丢失的任何字符串. 详见 QString::fromUtf8() 和 QString::fromLatin1().
Using QKeySequence() for Accelerator Values
Ctrl+Q 或 Alt+F 等快捷键也需要转换. 如果你在应用程序中将 Qt::CTRL + Qt::Key_Q
应编码为 "quit", 翻译人员将无法覆盖它. 正确的写法如下:
exitAct = new QAction(tr("E&xit"), this); exitAct->setShortcuts(QKeySequence::Quit);
Using Numbered Arguments
QString::arg() 函数提供了一个替换参数的简单方法:
void FileCopier::showProgress(int done, int total, const QString ¤tFile) { label.setText(tr("%1 of %2 files copied.\nCopying: %3") .arg(done) .arg(total) .arg(currentFile)); }
在某些语言中, 参数的顺序可能需要修改, 这个可以通过更改 % 参数的顺序轻松实现. 例如:
QString s1 = "%1 of %2 files copied. Copying: %3"; QString s2 = "Kopierer nu %3. Av totalt %2 filer er %1 kopiert."; qDebug() << s1.arg(5).arg(10).arg("somefile.txt"); qDebug() << s2.arg(5).arg(10).arg("somefile.txt");
生成英语和挪威语的正确输出:
5 of 10 files copied. Copying: somefile.txt Kopierer nu somefile.txt. Av totalt 10 filer er 5 kopiert.
Further Reading
Qt Linguist Manual, Hello tr() Example, Translation Rules for Plurals