目录
-
为什么需要国际化(i18n)
-
环境与目录结构
-
第一个带翻译标记的 Qt 界面
-
生成
.ts翻译文件(lupdate) -
用 Qt Linguist 翻译
-
生成
.qm文件(lrelease) -
程序运行时加载
.qm -
通式多语言切换(LanguageManager)
-
自动记忆 & 系统语言优先
-
打包与部署(含 Qt 基础控件翻译)
-
常见问题与踩坑总结
-
源码清单(可直接复制运行)
1. 为什么需要国际化(i18n)
-
走向全球:桌面应用需要满足不同地区用户。
-
体验一致:菜单、对话框、提示信息一致切换。
-
Qt 自带完整方案 :
tr()标记 →.ts翻译(XML) →.qm运行时资源。
2. 环境与目录结构
本文以最小工程示例:
MyI18nApp/
├─ main.cpp
├─ mainwindow.h / mainwindow.cpp / mainwindow.ui
├─ languagemanager.h / languagemanager.cpp
├─ translations/
│ ├─ app_en_US.ts / app_zh_CN.ts / app_ja_JP.ts ← lupdate 生成
│ ├─ app_en_US.qm / app_zh_CN.qm / app_ja_JP.qm ← lrelease 生成
│ └─ qtbase_zh_CN.qm / qtbase_ja_JP.qm ← 可选(Qt 基础控件翻译)
├─ resources.qrc
├─ MyI18nApp.pro (qmake 方案)
└─ CMakeLists.txt (CMake 方案)
你可选 qmake 或 CMake,其它文件通用。
3. 第一个带翻译标记的 Qt 界面
3.1 在 .ui / .cpp 中使用 tr()
在 mainwindow.ui 放一个 QLabel 与一个 QPushButton。
在 mainwindow.cpp 中(或直接在 .ui 属性里)加入可翻译文本:
// mainwindow.cpp(示例)
ui->label->setText(tr("Hello, World!"));
ui->pushButton->setText(tr("Switch Language"));
关键 :只有被
tr()包裹的字符串会被提取到.ts。
4. 生成 .ts 翻译文件(lupdate)
qmake 项目
在 MyI18nApp.pro 中加入:
QT += widgets
TEMPLATE = app
TARGET = MyI18nApp
SOURCES += main.cpp mainwindow.cpp languagemanager.cpp
HEADERS += mainwindow.h languagemanager.h
FORMS += mainwindow.ui
RESOURCES += resources.qrc
TRANSLATIONS += translations/app_en_US.ts \
translations/app_zh_CN.ts \
translations/app_ja_JP.ts
命令行执行:
lupdate MyI18nApp.pro
CMake 项目(Qt 6)
CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(MyI18nApp)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
find_package(Qt6 REQUIRED COMPONENTS Widgets LinguistTools)
set(SRCS
main.cpp
mainwindow.cpp
languagemanager.cpp
resources.qrc
)
set(HDRS mainwindow.h languagemanager.h)
set(UIS mainwindow.ui)
set(TS_FILES
translations/app_en_US.ts
translations/app_zh_CN.ts
translations/app_ja_JP.ts
)
qt_add_executable(MyI18nApp ${SRCS} ${HDRS} ${UIS})
# 生成 QM 文件(Qt6 推荐)
qt_add_translations(MyI18nApp QM_FILES ${TS_FILES})
target_link_libraries(MyI18nApp PRIVATE Qt6::Widgets)
命令行执行:
cmake -S . -B build
cmake --build build
# 或单独生成 ts:lupdate . -ts translations/app_zh_CN.ts ...
5. 用 Qt Linguist 翻译
-
打开
translations/*.ts; -
在右侧填入译文;
-
保存后状态会从 unfinished 变为 finished。
.ts 结构示例(节选):
<TS version="2.1" language="zh_CN">
<context>
<name>MainWindow</name>
<message>
<source>Hello, World!</source>
<translation>你好,世界!</translation>
</message>
<message>
<source>Switch Language</source>
<translation>切换语言</translation>
</message>
</context>
</TS>
6. 生成 .qm 文件(lrelease)
lrelease translations/app_en_US.ts
lrelease translations/app_zh_CN.ts
lrelease translations/app_ja_JP.ts
生成的 .qm 文件放进资源或部署目录。
7. 程序运行时加载 .qm
在 resources.qrc 中把 .qm 打到资源里:
<RCC>
<qresource prefix="/">
<file alias="translations/app_en_US.qm">translations/app_en_US.qm</file>
<file alias="translations/app_zh_CN.qm">translations/app_zh_CN.qm</file>
<file alias="translations/app_ja_JP.qm">translations/app_ja_JP.qm</file>
<!-- 可选:Qt 基础控件翻译 -->
<file alias="translations/qtbase_zh_CN.qm">translations/qtbase_zh_CN.qm</file>
<file alias="translations/qtbase_ja_JP.qm">translations/qtbase_ja_JP.qm</file>
</qresource>
</RCC>
8. 通式多语言切换(LanguageManager)
一次实现,支持任意多语言;动态切换后 UI 自动刷新;兼容 Qt5/Qt6。
8.1 languagemanager.h
#pragma once
#include <QObject>
#include <QTranslator>
#include <QLocale>
#include <QSet>
class LanguageManager : public QObject {
Q_OBJECT
public:
explicit LanguageManager(QObject* parent=nullptr);
void addSupported(const QString& localeName); // "en_US" / "zh_CN" / "ja_JP"
QStringList supported() const;
QString current() const;
bool switchTo(const QString& localeName); // 动态切换
signals:
void languageChanged(const QString& localeName);
private:
bool loadTranslators(const QString& localeName);
void unloadTranslators();
QString m_current;
QTranslator m_appTr;
QTranslator m_qtbaseTr; // 可选
QSet<QString> m_supported;
};
8.2 languagemanager.cpp
#include "languagemanager.h"
#include <QApplication>
LanguageManager::LanguageManager(QObject* p): QObject(p) {}
void LanguageManager::addSupported(const QString& n){ m_supported.insert(n); }
QStringList LanguageManager::supported() const {
auto list = QStringList(m_supported.begin(), m_supported.end());
std::sort(list.begin(), list.end());
return list;
}
QString LanguageManager::current() const { return m_current; }
bool LanguageManager::switchTo(const QString& name) {
if (!m_supported.contains(name)) return false;
if (m_current == name) return true;
unloadTranslators();
if (!loadTranslators(name)) return false;
m_current = name;
emit languageChanged(name);
return true;
}
bool LanguageManager::loadTranslators(const QString& name) {
const QString app = QString(":/translations/app_%1.qm").arg(name);
if (!m_appTr.load(app)) return false;
const QString qtbase = QString(":/translations/qtbase_%1.qm").arg(name);
m_qtbaseTr.load(qtbase); // 允许失败
qApp->installTranslator(&m_appTr);
if (!m_qtbaseTr.isEmpty()) qApp->installTranslator(&m_qtbaseTr);
return true;
}
void LanguageManager::unloadTranslators() {
if (!m_appTr.isEmpty()) qApp->removeTranslator(&m_appTr);
if (!m_qtbaseTr.isEmpty()) qApp->removeTranslator(&m_qtbaseTr);
m_appTr = QTranslator();
m_qtbaseTr = QTranslator();
}
8.3 mainwindow.h
#pragma once
#include <QMainWindow>
class LanguageManager;
namespace Ui { class MainWindow; }
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(LanguageManager* lm, QWidget* parent=nullptr);
~MainWindow();
private slots:
void onLanguageChanged(const QString&); // 信号响应
private:
void rebuildLanguageMenu(); // 动态构建"语言"菜单
void retranslateUi(); // 统一刷新 UI
Ui::MainWindow* ui;
LanguageManager* m_lang;
};
8.4 mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "languagemanager.h"
#include <QActionGroup>
#include <QSettings>
#include <QLocale>
static QString pretty(const QString& n){
const QLocale loc(n);
return QString("%1 (%2)")
.arg(QLocale::languageToString(loc.language()))
.arg(QLocale::countryToString(loc.country()));
}
MainWindow::MainWindow(LanguageManager* lm, QWidget* parent)
: QMainWindow(parent), ui(new Ui::MainWindow), m_lang(lm) {
ui->setupUi(this);
connect(m_lang, &LanguageManager::languageChanged,
this, &MainWindow::onLanguageChanged);
rebuildLanguageMenu();
retranslateUi();
}
MainWindow::~MainWindow(){ delete ui; }
void MainWindow::rebuildLanguageMenu() {
// 在 .ui 中放一个菜单:objectName = menuLanguage
ui->menuLanguage->clear();
auto* group = new QActionGroup(this); group->setExclusive(true);
for (const auto& n : m_lang->supported()){
auto* act = ui->menuLanguage->addAction(pretty(n));
act->setCheckable(true);
act->setData(n);
if (n == m_lang->current()) act->setChecked(true);
group->addAction(act);
}
connect(group, &QActionGroup::triggered, this, [this](QAction* a){
m_lang->switchTo(a->data().toString());
});
}
void MainWindow::onLanguageChanged(const QString&){
retranslateUi();
rebuildLanguageMenu();
QSettings s("YourCompany","MyI18nApp");
s.setValue("lang", m_lang->current());
}
void MainWindow::retranslateUi(){
ui->retranslateUi(this); // 刷新 .ui 文本
// 如果有纯代码里的动态字符串,也在这里用 tr() 重新设置
}
8.5 main.cpp
#include "mainwindow.h"
#include "languagemanager.h"
#include <QApplication>
#include <QSettings>
#include <QLocale>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
LanguageManager lm;
// 新增语言只需要在这里加一行,或做目录扫描自动添加
lm.addSupported("en_US");
lm.addSupported("zh_CN");
lm.addSupported("ja_JP");
// 1) 读取上次选择;2) 否则按系统语言;3) 再回退英文
QSettings s("YourCompany","MyI18nApp");
QString target = s.value("lang").toString();
if (target.isEmpty()) {
const QString sys = QLocale().name();
target = lm.supported().contains(sys) ? sys : "en_US";
}
lm.switchTo(target);
MainWindow w(&lm);
w.show();
return a.exec();
}
9. 自动记忆 & 系统语言优先
-
QSettings持久化当前语言,下次启动沿用; -
首次运行优先用
QLocale().name()与已支持语言匹配; -
不匹配时回退到
en_US(或你希望的默认语言)。
10. 打包与部署(含 Qt 基础控件翻译)
-
资源内置 :使用
:/translations/...最省心; -
外部目录:用磁盘路径加载,便于热更新;
-
Qt 基础控件翻译 :拷贝
qtbase_xx_YY.qm(随 Qt 安装提供)一起部署,文件对话框等系统控件才会被翻译; -
路径排查 :
QTranslator::load()失败 90% 是路径问题,建议配合QFile::exists()调试。
11. 常见问题与踩坑总结
| 现象 | 常见原因 | 解决方案 |
|---|---|---|
| 切换后部分文本不变 | 忘记刷新 UI | 切换后统一调用 ui->retranslateUi(this),并在其中重设纯代码字符串 |
.ts 内容很少/为空 |
忘记 tr() 或未扫描 .ui |
所有文本用 tr();确保 .pro/CMake 把 .ui/.cpp/.h 都包含 |
.qm 无效 |
未 installTranslator() 或加载到旧文件 |
先 removeTranslator() 再 installTranslator();确认 .qm 最近 lrelease 过 |
| 对话框仍英文 | 未加载 qtbase_*.qm |
额外加载 qtbase 对应语言 |
| 多窗口没刷新 | 新窗口在切换后才创建 | 发 languageChanged 信号或在构造时再次 retranslateUi() |
12. 源码清单(可直接复制运行)
提示 :把以下所有文件按章节名保存到你的工程里,
lupdate / lrelease后即刻运行。
12.1 MyI18nApp.pro(qmake 选其一)
QT += widgets
TEMPLATE = app
TARGET = MyI18nApp
SOURCES += main.cpp mainwindow.cpp languagemanager.cpp
HEADERS += mainwindow.h languagemanager.h
FORMS += mainwindow.ui
RESOURCES += resources.qrc
TRANSLATIONS += translations/app_en_US.ts \
translations/app_zh_CN.ts \
translations/app_ja_JP.ts
12.2 CMakeLists.txt(CMake 选其一)
cmake_minimum_required(VERSION 3.16)
project(MyI18nApp)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
find_package(Qt6 REQUIRED COMPONENTS Widgets LinguistTools)
set(SRCS
main.cpp
mainwindow.cpp
languagemanager.cpp
resources.qrc
)
set(HDRS mainwindow.h languagemanager.h)
set(UIS mainwindow.ui)
set(TS_FILES
translations/app_en_US.ts
translations/app_zh_CN.ts
translations/app_ja_JP.ts
)
qt_add_executable(MyI18nApp ${SRCS} ${HDRS} ${UIS})
qt_add_translations(MyI18nApp QM_FILES ${TS_FILES})
target_link_libraries(MyI18nApp PRIVATE Qt6::Widgets)
12.3 resources.qrc
<RCC>
<qresource prefix="/">
<file alias="translations/app_en_US.qm">translations/app_en_US.qm</file>
<file alias="translations/app_zh_CN.qm">translations/app_zh_CN.qm</file>
<file alias="translations/app_ja_JP.qm">translations/app_ja_JP.qm</file>
<!-- 可选:Qt 基础控件翻译 -->
<file alias="translations/qtbase_zh_CN.qm">translations/qtbase_zh_CN.qm</file>
<file alias="translations/qtbase_ja_JP.qm">translations/qtbase_ja_JP.qm</file>
</qresource>
</RCC>
12.4 main.cpp
#include "mainwindow.h"
#include "languagemanager.h"
#include <QApplication>
#include <QSettings>
#include <QLocale>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
LanguageManager lm;
lm.addSupported("en_US");
lm.addSupported("zh_CN");
lm.addSupported("ja_JP");
QSettings s("YourCompany","MyI18nApp");
QString target = s.value("lang").toString();
if (target.isEmpty()) {
const QString sys = QLocale().name();
target = lm.supported().contains(sys) ? sys : "en_US";
}
lm.switchTo(target);
MainWindow w(&lm);
w.show();
return a.exec();
}
12.5 languagemanager.h / languagemanager.cpp
见第 8 章,原样复制即可。
12.6 mainwindow.h
#pragma once
#include <QMainWindow>
class LanguageManager;
namespace Ui { class MainWindow; }
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(LanguageManager* lm, QWidget* parent=nullptr);
~MainWindow();
private slots:
void onLanguageChanged(const QString&);
private:
void rebuildLanguageMenu();
void retranslateUi();
Ui::MainWindow* ui;
LanguageManager* m_lang;
};
12.7 mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "languagemanager.h"
#include <QActionGroup>
#include <QSettings>
#include <QLocale>
static QString pretty(const QString& n){
const QLocale loc(n);
return QString("%1 (%2)")
.arg(QLocale::languageToString(loc.language()))
.arg(QLocale::countryToString(loc.country()));
}
MainWindow::MainWindow(LanguageManager* lm, QWidget* parent)
: QMainWindow(parent), ui(new Ui::MainWindow), m_lang(lm) {
ui->setupUi(this);
connect(m_lang, &LanguageManager::languageChanged,
this, &MainWindow::onLanguageChanged);
rebuildLanguageMenu();
retranslateUi();
}
MainWindow::~MainWindow(){ delete ui; }
void MainWindow::rebuildLanguageMenu() {
ui->menuLanguage->clear();
auto* group = new QActionGroup(this); group->setExclusive(true);
for (const auto& n : m_lang->supported()) {
auto* act = ui->menuLanguage->addAction(pretty(n));
act->setCheckable(true); act->setData(n);
if (n == m_lang->current()) act->setChecked(true);
group->addAction(act);
}
connect(group, &QActionGroup::triggered, this, [this](QAction* a){
m_lang->switchTo(a->data().toString());
});
}
void MainWindow::onLanguageChanged(const QString&) {
retranslateUi();
rebuildLanguageMenu();
QSettings s("YourCompany","MyI18nApp");
s.setValue("lang", m_lang->current());
}
void MainWindow::retranslateUi() {
ui->retranslateUi(this);
// 代码构造的字符串也在这里使用 tr() 重新设置
}
12.8 mainwindow.ui 关键点
-
放一个
QMenuBar,增加一个菜单,objectName = menuLanguage(作为"语言"菜单)。 -
放一个
QLabel(文本:Hello, World!),一个QPushButton(文本:Switch Language)。 -
.ui自动使用tr(),会被lupdate提取到.ts。
结束语
到这里,你已经完成了 Qt 国际化的全链路:
-
tr()标记 →.ts提取(lupdate) → Linguist 翻译 →.qm编译(lrelease) -
程序运行时加载/卸载翻译器
-
通式多语言切换 + 自动记忆 + Qt 基础控件翻译
-
qmake/CMake 均支持,工程可即刻运行