QT国际化(Internationalization,简称I18N)是指将一个软件应用程序的界面、文本、日期、数字等元素转化为不同的语言和文化习惯的过程。这使得软件能够在不同的国家和地区使用,并且可以根据用户的语言和地区提供本地化的使用体验。
QT是一种跨平台的应用程序开发框架,提供了很多工具和功能来支持国际化。下面将介绍QT国际化的一些基本概念和示例代码。
一、QT国际化三部曲
lupdate(更新),linguist(编辑),lrelease(发布)
1、lupdate
lupdate是一个命令行工具,随Qt框架一起提供,它的主要作用是从源代码中提取出所有的可翻译字符串,并更新.ts文件(Translation Source文件)。.ts文件是XML格式的,包含了源代码中出现的所有可翻译字符串及其对应的翻译。
lupdate的工作原理大致如下:
(1)解析源代码:lupdate遍历指定的源文件或源代码目录,解析C++、QML或JavaScript文件,寻找所有的可翻译字符串。在C++中,这通常是通过tr()函数、QT_TR_NOOP()宏、QObject::tr()方法或者其他相关的Qt翻译宏来标识的。
(2)提取字符串:当lupdate找到一个可翻译的字符串时,它会提取出字符串及其上下文信息。上下文通常是包含该字符串的类名,这有助于翻译人员了解字符串在应用程序中的用途。
(3)更新.ts文件:lupdate然后将这些字符串添加到对应的.ts文件中。如果字符串已经存在,它会保留现有的翻译并标记任何更改。如果字符串是新的,它将被添加,等待翻译。
(4)处理旧字符串:对于在源代码中不再出现的字符串,lupdate可以标记它们为“obsolete”(过时的),或者根据命令行参数的不同,直接从.ts文件中删除。
(5)保存.ts文件:更新后的.ts文件将包含所有从源代码中提取的字符串,以及任何现有的翻译和新的、未翻译的或标记为过时的条目。
选项和参数
lupdate 提供了一些选项来控制其行为:
-pro filename :指定 Qt 项目的 Pro 文件,lupdate 将自动从 Pro 文件配置中查找源文件。
-ts filename [filename …] :指定输出的翻译文件,可以是多个文件。
-recursive :递归地扫描目录及其子目录中的源文件。
-no-obsolete :移除翻译文件中不再存在于源代码中的条目。
-extensions extensions :指定源文件的扩展名,例如 .cpp, .h, .qml。
2、linguist
Linguist 是 Qt 提供的一个用于翻译和本地化的工具套件,它包括三个关键的命令行工具:lupdate、lrelease 和 linguist 主程序。
这些工具分别用于提取可翻译字符串、生成可执行的翻译文件以及编辑翻译文件。这里主要介绍 linguist 主程序的工作原理和使用方法。
Linguist 主程序的工作原理:
(1)打开翻译文件:Linguist 打开以 .ts 为扩展名的 Qt 翻译文件。这些文件通常由 lupdate 工具生成,其中包含待翻译的字符串、上下文及其在源代码中的定位信息。
(2)编辑翻译条目:
翻译人员可以使用 Linguist 编辑每个翻译条目的翻译内容。界面显示原始字符串和待翻译的字符串,翻译人员可以直接输入相应的翻译。
(3)翻译状态管理:Linguist 支持不同的翻译状态,如“未翻译”、“已翻译”、“已校对”等,帮助翻译人员和项目管理人员跟踪翻译进度和质量。
(4)术语库和建议:Linguist 能够使用术语库和以前的翻译建议,提高翻译的一致性和效率。这些建议可以从已翻译的字符串和外部的术语库文件中获得。
(5)语法和拼写检查:Linguist 提供内置的语法和拼写检查功能,提醒翻译人员可能的拼写错误或常见语法问题。
(6)生成二进制翻译文件:翻译完成后,可以通过 lrelease 工具将 .ts 文件编译成 .qm 文件,供应用程序在运行时加载。
3、lrelease
lrelease 是 Qt 工具链的一部分,用于将 .ts (Qt 翻译源文件) 编译为 .qm (Qt 翻译二进制文件) 文件。.qm 文件用于在运行时加载翻译内容,从而实现多语言支持。
lrelease 的工作原理和使用方法如下:
(1)读取 .ts 文件:lrelease 读取一个或多个 .ts 文件,这些文件包含源语言字符串及其翻译内容。
(2)解析 XML 数据:.ts 文件是基于 XML 格式的文本文件,lrelease 解析这些 XML 数据,提取所有翻译条目信息。
(3)生成二进制文件:lrelease 将这些解析后的数据编译成高效的二进制格式 .qm 文件,这些文件可以在运行时被 Qt 应用程序加载。
二、应用
Qt开发中的构建工具常用qmake和cmake。
1、使用qmake进行国际化
.pro文件中添加支持的语言
TRANSLATIONS = lanague_cn.ts\
lanague_en.ts
在Qt Creator中调用lupdate导出ts文件
或在命令行输入命令:lupdate trans.pro -ts lanague_en.ts
然后用语言家(linguist)打开要翻译的ts文件进行编辑翻译
编辑完发布(调用lrelease)生成qm文件 (Qt 翻译二进制文件)
使用qm文件
QTranslator translator;
translator.load("D:/QTDemo/trans/lanague_en.qm");
a.installTranslator(&translator);
2、使用qmake进行国际化
在CMakeLists.txt文件中添加下列代码
set(TS_FILES
"${CMAKE_SOURCE_DIR}/zh_CN.ts"
"${CMAKE_SOURCE_DIR}/en_US.ts"
)
find_program(LUPDATE_EXECUTABLE lupdate)
find_program(LRELEASE_EXECUTABLE lrelease)
foreach(_ts_file ${TS_FILES})
execute_process(
COMMAND ${LUPDATE_EXECUTABLE} -recursive ${CMAKE_SOURCE_DIR} -ts ${_ts_file})
execute_process(
COMMAND ${LRELEASE_EXECUTABLE} ${_ts_file})
endforeach()
执行CMake
三、切换语言
1、重启程序
使用QProcess类静态方法
// program, 要启动的程序名称
// arguments, 启动参数
bool startDetached(const QString &program, const QStringList &arguments);
示例代码:
ui
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void slotCurrentTextChanged(const QString &str);
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QSettings>
#include <QProcess>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->comboBox->addItem("简体中文");
ui->comboBox->addItem("English");
QSettings settings(QCoreApplication::applicationDirPath()+"/config.ini", QSettings::IniFormat);
QString str = settings.value("Set/Language").toString();
if(!str.isEmpty())
ui->comboBox->setCurrentText(str);
connect(ui->comboBox,&QComboBox::currentTextChanged,this,&MainWindow::slotCurrentTextChanged);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::slotCurrentTextChanged(const QString &str)
{
QSettings settings(QCoreApplication::applicationDirPath()+"/config.ini", QSettings::IniFormat);
settings.setValue("Set/Language", str);
// qApp->quit();
// QProcess::startDetached(qApp->applicationFilePath(), QStringList());
qApp->exit(777);
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QTranslator>
#include <QSettings>
#include <QProcess>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QSettings settings(QCoreApplication::applicationDirPath()+"/config.ini", QSettings::IniFormat);
QString str = settings.value("Set/Language").toString();
QTranslator translator;
if(str == "English")
{
translator.load("D:/QTDemo/trans/lanague_en.qm");
a.installTranslator(&translator);
}
MainWindow w;
w.show();
// return a.exec();
int e = a.exec();
if(e == 777)
{
QProcess::startDetached(qApp->applicationFilePath(), QStringList());
return 0;
}
return e;
}
2、不重启程序
监听QEvent::LanguageChange事件,当切换翻译器时会在底层发出一个LanguageChange事件,所有的需要改变文本的窗口都监听这个事件。
示例代码:
在窗口中重写 bool event(QEvent *event)
bool MainWindow::event(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this); //重新翻译UI界面
}
return QWidget::event(event);
}
需要注意:在加载新的语言时,需要先将已绑定的语言移除。
void MainWindow::slotCurrentTextChanged(const QString &str)
{
static QTranslator* translator;
if (translator != NULL)
{
//先将已绑定的语言移除
qApp->removeTranslator(translator);
delete translator;
translator = NULL;
}
translator = new QTranslator;
if(str == "English")
{
translator->load("D:/QTDemo/trans/lanague_en.qm");
qApp->installTranslator(translator);
}
}
四、注意事项
不能直接翻译全局变量、静态变量、符号常量字符串
因为全局变量、静态变量初始化发生在QTranslator::installTranslator之前,Qt无法替换(翻译)这些变量。而通过QT_TR_NOOP宏可以标识出静态生存期变量,让Qt可以晚一些再翻译这些变量,称为delayed translation
对于常量字符串、符号常量字符串,它们甚至在编译时就被编译器替换好了,就更不可能经QCoreApplication::translate翻译了。像下面的做法都是徒劳:
#define DEFINE_MESSAGE_ QT_TR_NOOP("Failed to 1")
const char *kConstMessage = QT_TR_NOOP("Failed to 2");
static const char *kStaticConstMessage = QT_TR_NOOP("Failed to 3");
...
QMessageBox::critical(nullptr, tr("Error"), tr(DEFINE_MESSAGE_);
QMessageBox::critical(nullptr, tr("Error"), tr(kConstMessage);
QMessageBox::critical(nullptr, tr("Error"), tr(kStaticConstMessage);
...
一种替代方案是通过一个类封装全局变量,并将类声明Q_DECLARE_TR_FUNCTIONS
宏或者继承QObject
//GlobalMessageWarpper.h
class GlobalMessageWarpper
{
Q_DECLARE_TR_FUNCTIONS(GlobalMessageWarpper)
public:
static QString message() { return tr(kMessage); }
static const char* kMessage;
};
//GlobalMessageWarpper.cpp
const char* GlobalMessageWarpper::kMessage = QT_TR_NOOP("Failed to ...");
...
QMessageBox::critical(nullptr, tr("Error"), GlobalMessageWarpper::message());
...