跳到主要内容

国际化与本地化

国际化(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_NOOPqsTrNoOp() 标记字符串但不立即翻译:

// 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 命令会:

  1. 自动生成 .ts 文件(如果不存在)
  2. 在构建时调用 lupdate 更新 .ts 文件
  3. .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

翻译工作流

  1. 打开 TS 文件:文件 → 打开 → 选择 .ts 文件
  2. 查看上下文:左侧显示源代码中的类/组件
  3. 选择字符串:中间显示待翻译的字符串列表
  4. 输入翻译:在下方翻译区域输入翻译文本
  5. 标记完成:翻译完成后点击"完成"标记
  6. 保存:文件 → 保存或 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;
}

最佳实践

代码编写规范

  1. 始终使用 tr() 或 qsTr():所有用户可见文本都应标记
  2. 提供上下文信息:使用消歧参数帮助翻译者理解
  3. 避免字符串拼接:使用 arg() 方法格式化
// 不好的做法
QString msg = tr("File") + " " + fileName + " " + tr("not found");

// 好的做法
QString msg = tr("File '%1' not found").arg(fileName);
  1. 标记翻译注释:帮助翻译者理解上下文
//: 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/
└── ...

翻译更新流程

  1. 开发完成后运行 lupdate 提取新字符串
  2. 翻译者在 Qt Linguist 中翻译新字符串
  3. 构建前运行 lrelease 编译 .qm 文件
  4. 测试各语言版本
# 开发者:更新翻译源
lupdate CMakeLists.txt

# 翻译者:使用 Qt Linguist 翻译
linguist translations/myapp_zh_CN.ts

# 构建前:编译翻译
lrelease translations/*.ts

常见问题

问题:翻译未生效

检查项:

  1. .qm 文件是否在正确位置
  2. 加载路径是否正确
  3. 是否正确调用了 installTranslator()

问题:部分字符串未翻译

检查项:

  1. 源代码中是否使用了 tr()
  2. 类是否包含 Q_OBJECT
  3. lupdate 是否成功提取了字符串

问题:日期数字格式不正确

确保使用 QLocale 而不是硬编码格式:

// 不好的做法
QString date = QDate::currentDate().toString("yyyy-MM-dd");

// 好的做法
QString date = QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat);

参考资源