国际化与本地化
国际化(Internationalization,简称 i18n)是指设计应用程序使其能够适应不同语言和地区的过程。本地化(Localization,简称 l10n)是将国际化的应用程序适配到特定语言和地区的过程。Qt 提供了完整的国际化支持,帮助开发者轻松创建多语言应用程序。
国际化概述
核心概念
- 国际化:在开发阶段设计应用程序,使其能够支持多种语言,无需修改代码即可添加新语言
- 本地化:将国际化的应用翻译成特定语言,包括文本翻译、日期时间格式、数字格式等
- 区域设置(Locale):特定地区和语言的组合,如
zh_CN(简体中文)、en_US(美国英语)
Qt 国际化工具
Qt 提供三个核心工具用于国际化:
| 工具 | 功能 |
|---|---|
lupdate | 从源代码提取可翻译字符串,生成或更新 .ts 文件 |
lrelease | 将 .ts 文件编译为高效的 .qm 文件 |
Qt Linguist | 图形化翻译工具,用于编辑 .ts 文件 |
工作流程
在代码中标记可翻译字符串
C++ 中的标记方法
使用 tr() 函数
所有继承自 QObject 的类都可以使用 tr() 函数标记可翻译字符串:
#include <QApplication>
#include <QLabel>
#include <QPushButton>
#include <QMessageBox>
class MainWindow : public QMainWindow
{
Q_OBJECT // 必须包含此宏才能使用 tr()
public:
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
{
// 使用 tr() 标记可翻译字符串
setWindowTitle(tr("My Application"));
QLabel *label = new QLabel(tr("Welcome to my application!"), this);
setCentralWidget(label);
// 创建菜单
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(tr("&New"), this, &MainWindow::newFile);
fileMenu->addAction(tr("&Open..."), this, &MainWindow::openFile);
fileMenu->addSeparator();
fileMenu->addAction(tr("E&xit"), this, &MainWindow::close);
QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
helpMenu->addAction(tr("&About"), this, &MainWindow::about);
}
private slots:
void newFile()
{
QMessageBox::information(this, tr("New File"),
tr("Create a new file."));
}
void openFile()
{
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open File"),
QString(),
tr("All Files (*);;Text Files (*.txt)"));
}
void about()
{
QMessageBox::about(this, tr("About"),
tr("<h3>About My Application</h3>"
"<p>This is a sample Qt application.</p>"));
}
};
非 QObject 类中的翻译
对于不继承 QObject 的类,使用 QCoreApplication::translate():
#include <QCoreApplication>
class Helper
{
public:
static QString errorMessage(int code)
{
// 使用 QCoreApplication::translate
// 参数:上下文名称(通常是类名)、源文本
switch (code) {
case 1:
return QCoreApplication::translate("Helper", "File not found");
case 2:
return QCoreApplication::translate("Helper", "Access denied");
default:
return QCoreApplication::translate("Helper", "Unknown error");
}
}
};
// 或者使用 QT_TR_NOOP 宏预先标记字符串
class Config
{
public:
// 使用 QT_TR_NOOP 标记,在运行时翻译
static const char * const messages[];
static QString getMessage(int index)
{
return QCoreApplication::translate("Config", messages[index]);
}
};
const char * const Config::messages[] = {
QT_TR_NOOP("Configuration loaded"),
QT_TR_NOOP("Configuration saved"),
QT_TR_NOOP("Configuration reset")
};
处理复数形式
不同语言有不同的复数规则,Qt 提供了对复数的支持:
// 使用 %n 作为数字占位符
QString message = tr("You have %n message(s)", "", messageCount);
// 示例输出:
// 英文单数:You have 1 message
// 英文复数:You have 5 messages
// 中文:您有 5 条消息(中文不区分单复数)
对于更复杂的复数情况:
// 使用 QT_TR_N_NOOP 宏
static const char * const singular = QT_TR_N_NOOP("%n file found", "%n files found");
QString result = QCoreApplication::translate("Search", singular, "", fileCount);
消除歧义
当同一个词在不同上下文有不同含义时,使用消歧参数:
// "Right" 可以是"正确"或"右边"
QString correct = tr("Right", "correct answer");
QString direction = tr("Right", "direction");
// Qt Linguist 会显示消歧信息帮助翻译
QML 中的标记方法
使用 qsTr() 函数
在 QML 中使用 qsTr() 标记可翻译字符串:
import QtQuick
import QtQuick.Controls
ApplicationWindow {
title: qsTr("My Application")
width: 400
height: 300
Column {
anchors.centerIn: parent
spacing: 20
Text {
text: qsTr("Welcome to my application!")
font.pixelSize: 20
}
Button {
text: qsTr("Click Me")
onClicked: messageDialog.open()
}
}
Dialog {
id: messageDialog
title: qsTr("Message")
Label {
text: qsTr("Hello, World!")
}
standardButtons: Dialog.Ok
}
menuBar: MenuBar {
Menu {
title: qsTr("&File")
Action { text: qsTr("&New"); onTriggered: newFile() }
Action { text: qsTr("&Open..."); onTriggered: openFile() }
MenuSeparator {}
Action { text: qsTr("E&xit"); onTriggered: close() }
}
Menu {
title: qsTr("&Help")
Action { text: qsTr("&About"); onTriggered: about() }
}
}
function newFile() {
console.log(qsTr("Creating new file..."))
}
function openFile() {
console.log(qsTr("Opening file..."))
}
function about() {
console.log(qsTr("About dialog"))
}
}
QML 中的复数处理
Text {
property int messageCount: 5
// 使用 %1 作为占位符
text: qsTr("You have %1 message(s)").arg(messageCount)
}
标记字符串但不立即翻译
使用 QT_TR_NOOP 和 qsTrNoOp() 标记字符串但不立即翻译:
// C++ 中
static const char * const statusMessages[] = {
QT_TR_NOOP("Loading..."),
QT_TR_NOOP("Ready"),
QT_TR_NOOP("Error")
};
// 稍后翻译
QString message = tr(statusMessages[status]);
// QML 中
property var messages: [
QT_TR_NOOP("Loading..."),
QT_TR_NOOP("Ready"),
QT_TR_NOOP("Error")
]
function getStatusMessage(index) {
return qsTr(messages[index])
}
配置项目文件
使用 CMake
现代 Qt 项目推荐使用 CMake 配置国际化:
cmake_minimum_required(VERSION 3.16)
project(MyApp VERSION 1.0.0 LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Widgets LinguistTools)
qt_standard_project_setup(I18N_TRANSLATED_LANGUAGES de fr zh_CN)
qt_add_executable(MyApp
main.cpp
mainwindow.cpp
mainwindow.h
)
target_link_libraries(MyApp PRIVATE Qt6::Core Qt6::Widgets)
# 自动处理翻译
qt_add_translations(MyApp)
qt_add_translations 命令会:
- 自动生成
.ts文件(如果不存在) - 在构建时调用
lupdate更新.ts文件 - 将
.qm文件嵌入到应用程序资源中
手动指定翻译源文件:
# 指定翻译文件名
qt_add_translations(MyApp
TS_FILES
translations/myapp_de.ts
translations/myapp_fr.ts
translations/myapp_zh_CN.ts
)
# 或者不嵌入资源,部署到文件系统
qt_add_translations(MyApp
TS_FILES translations/myapp_zh_CN.ts
QM_FILES_OUTPUT_VARIABLE qm_files
)
install(FILES ${qm_files} DESTINATION translations)
使用 qmake
如果使用 qmake 构建系统:
# MyApp.pro
QT += core widgets
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
# 指定翻译文件
TRANSLATIONS += \
translations/myapp_de.ts \
translations/myapp_fr.ts \
translations/myapp_zh_CN.ts
# 或使用通配符
# TRANSLATIONS += translations/*.ts
对于 QML 文件,使用 lupdate_only 块:
# 让 lupdate 能看到 QML 文件,但编译器忽略
lupdate_only {
SOURCES += \
main.qml \
MainPage.qml \
components/*.qml
}
使用 Qt Linguist 翻译
启动 Qt Linguist
Qt Linguist 是一个图形化翻译工具,提供:
- 上下文视图:显示源代码中的翻译上下文
- 字符串列表:显示所有可翻译字符串
- 翻译区域:输入翻译文本
- 短语建议:基于已有翻译提供建议
从 Qt 安装目录启动:
# Linux
$QT_DIR/bin/linguist
# Windows
%QT_DIR%\bin\linguist.exe
# macOS
$QT_DIR/bin/linguist.app
翻译工作流
- 打开 TS 文件:文件 → 打开 → 选择
.ts文件 - 查看上下文:左侧显示源代码中的类/组件
- 选择字符串:中间显示待翻译的字符串列表
- 输入翻译:在下方翻译区域输入翻译文本
- 标记完成:翻译完成后点击"完成"标记
- 保存:文件 → 保存或 Ctrl+S
翻译状态
Qt Linguist 使用不同颜色表示翻译状态:
| 状态 | 颜色 | 说明 |
|---|---|---|
| 未翻译 | 灰色 | 字符串尚未翻译 |
| 翻译中 | 黄色 | 翻译了但未标记完成 |
| 已完成 | 绿色 | 翻译完成并标记 |
| 有警告 | 橙色 | 翻译存在潜在问题(如缺少标点) |
| 已过时 | 删除线 | 源文本已更改,需要更新翻译 |
批量操作
# 使用命令行工具批量更新所有 TS 文件
lupdate MyApp.pro
# 编译所有 TS 文件为 QM 文件
lrelease MyApp.pro
# 只处理特定语言
lrelease translations/myapp_zh_CN.ts
运行时加载翻译
基本加载方法
在应用程序启动时加载翻译文件:
#include <QApplication>
#include <QTranslator>
#include <QLocale>
#include <QDir>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 创建翻译器
QTranslator translator;
// 方法 1:根据系统语言自动选择
QString locale = QLocale::system().name(); // 如 "zh_CN"
// 从资源文件加载翻译
if (translator.load(locale, ":/i18n")) {
app.installTranslator(&translator);
}
// 方法 2:从文件系统加载
QString translationsPath = QCoreApplication::applicationDirPath() + "/translations";
if (translator.load(locale, translationsPath)) {
app.installTranslator(&translator);
}
// 方法 3:使用 QLocale 更精细控制
if (translator.load(QLocale(), "myapp", ".", translationsPath)) {
app.installTranslator(&translator);
}
MainWindow window;
window.show();
return app.exec();
}
加载 Qt 模块翻译
Qt 自带的对话框和控件也需要翻译:
#include <QLibraryInfo>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 加载应用程序翻译
QTranslator appTranslator;
if (appTranslator.load(QLocale::system(), "myapp", "_",
QCoreApplication::applicationDirPath() + "/translations")) {
app.installTranslator(&appTranslator);
}
// 加载 Qt 基础模块翻译
QTranslator qtTranslator;
if (qtTranslator.load(QLocale::system(), "qtbase", "_",
QLibraryInfo::path(QLibraryInfo::TranslationsPath))) {
app.installTranslator(&qtTranslator);
}
// ... 其他代码
}
动态切换语言
在运行时切换语言需要重新加载翻译器并通知界面更新:
#include <QEvent>
#include <QCoreApplication>
class LanguageManager : public QObject
{
Q_OBJECT
public:
static LanguageManager* instance()
{
static LanguageManager manager;
return &manager;
}
void switchLanguage(const QString& locale)
{
// 移除旧翻译
if (m_translator) {
QCoreApplication::removeTranslator(m_translator.get());
}
// 加载新翻译
m_translator = std::make_unique<QTranslator>();
if (m_translator->load(locale, ":/i18n")) {
QCoreApplication::installTranslator(m_translator.get());
m_currentLocale = locale;
emit languageChanged();
}
}
QString currentLocale() const { return m_currentLocale; }
signals:
void languageChanged();
private:
LanguageManager() : m_translator(std::make_unique<QTranslator>()) {}
std::unique_ptr<QTranslator> m_translator;
QString m_currentLocale = "en";
};
// 在主窗口中响应语言变化
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
{
setupUi();
// 监听语言变化
connect(LanguageManager::instance(), &LanguageManager::languageChanged,
this, &MainWindow::retranslateUi);
}
private:
void setupUi()
{
// 创建 UI 组件
m_label = new QLabel(this);
m_button = new QPushButton(this);
// 初始翻译
retranslateUi();
}
void retranslateUi()
{
// 重新翻译所有文本
m_label->setText(tr("Welcome!"));
m_button->setText(tr("Click Me"));
setWindowTitle(tr("My Application"));
}
QLabel *m_label;
QPushButton *m_button;
};
QML 中动态切换语言
import QtQuick
import QtQuick.Controls
ApplicationWindow {
id: root
title: qsTr("My Application")
property string currentLanguage: "en"
function switchLanguage(locale) {
// 调用 C++ 切换语言
LanguageManager.switchLanguage(locale)
currentLanguage = locale
}
Column {
anchors.centerIn: parent
spacing: 20
Text {
text: qsTr("Welcome!")
}
Row {
spacing: 10
Button {
text: "English"
onClicked: switchLanguage("en")
}
Button {
text: "中文"
onClicked: switchLanguage("zh_CN")
}
Button {
text: "Deutsch"
onClicked: switchLanguage("de")
}
}
}
// 监听语言变化,刷新绑定
Connections {
target: LanguageManager
function onLanguageChanged() {
// 触发重新绑定
root.title = Qt.binding(function() { return qsTr("My Application") })
}
}
}
处理特殊格式
日期和时间
使用 QLocale 格式化日期时间:
#include <QLocale>
#include <QDateTime>
void formatDateTime()
{
QLocale locale = QLocale::system();
QDateTime now = QDateTime::currentDateTime();
// 使用本地格式
QString dateStr = locale.toString(now.date(), QLocale::ShortFormat);
QString timeStr = locale.toString(now.time(), QLocale::ShortFormat);
// 自定义格式
QString customStr = locale.toString(now, "yyyy-MM-dd hh:mm:ss");
// 特定区域设置
QLocale usLocale(QLocale::English, QLocale::UnitedStates);
QLocale cnLocale(QLocale::Chinese, QLocale::China);
QString usDate = usLocale.toString(now.date(), QLocale::LongFormat);
QString cnDate = cnLocale.toString(now.date(), QLocale::LongFormat);
qDebug() << "US:" << usDate; // "Saturday, January 1, 2024"
qDebug() << "CN:" << cnDate; // "2024年1月1日星期六"
}
数字和货币
#include <QLocale>
void formatNumbers()
{
QLocale locale = QLocale::system();
// 数字格式
QString number = locale.toString(1234567.89); // "1,234,567.89" 或 "1.234.567,89"
// 货币格式
QString currency = locale.toCurrencyString(1234.56);
// 英文: "$1,234.56"
// 中文: "¥1,234.56"
// 德文: "1.234,56 €"
// 百分比
QString percent = locale.toString(0.75, 'f', 2);
// 解析数字
bool ok;
double value = locale.toDouble("1,234.56", &ok);
}
文本排序
不同语言有不同的排序规则:
#include <QCollator>
#include <QStringList>
void sortStrings()
{
QStringList words = {"apple", "Apple", "zebra", "äpfel", "Zebra"};
// 使用本地排序规则
QCollator collator;
collator.setCaseSensitivity(Qt::CaseInsensitive);
collator.setIgnorePunctuation(true);
std::sort(words.begin(), words.end(), [&collator](const QString &a, const QString &b) {
return collator.compare(a, b) < 0;
});
qDebug() << words;
}
最佳实践
代码编写规范
- 始终使用 tr() 或 qsTr():所有用户可见文本都应标记
- 提供上下文信息:使用消歧参数帮助翻译者理解
- 避免字符串拼接:使用
arg()方法格式化
// 不好的做法
QString msg = tr("File") + " " + fileName + " " + tr("not found");
// 好的做法
QString msg = tr("File '%1' not found").arg(fileName);
- 标记翻译注释:帮助翻译者理解上下文
//: This is the window title
setWindowTitle(tr("My Application"));
/*: This message appears in the status bar when
a file is successfully saved. %1 is the file name. */
statusBar()->showMessage(tr("File '%1' saved successfully").arg(fileName));
文件组织
myapp/
├── translations/
│ ├── myapp_en.ts # 英文(源语言)
│ ├── myapp_zh_CN.ts # 简体中文
│ ├── myapp_zh_TW.ts # 繁体中文
│ ├── myapp_de.ts # 德语
│ └── myapp_fr.ts # 法语
├── CMakeLists.txt
└── src/
└── ...
翻译更新流程
- 开发完成后运行
lupdate提取新字符串 - 翻译者在 Qt Linguist 中翻译新字符串
- 构建前运行
lrelease编译.qm文件 - 测试各语言版本
# 开发者:更新翻译源
lupdate CMakeLists.txt
# 翻译者:使用 Qt Linguist 翻译
linguist translations/myapp_zh_CN.ts
# 构建前:编译翻译
lrelease translations/*.ts
常见问题
问题:翻译未生效
检查项:
.qm文件是否在正确位置- 加载路径是否正确
- 是否正确调用了
installTranslator()
问题:部分字符串未翻译
检查项:
- 源代码中是否使用了
tr() - 类是否包含
Q_OBJECT宏 lupdate是否成功提取了字符串
问题:日期数字格式不正确
确保使用 QLocale 而不是硬编码格式:
// 不好的做法
QString date = QDate::currentDate().toString("yyyy-MM-dd");
// 好的做法
QString date = QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat);