一、使用翻译目的:
由于产品会面对不同的客户,使用翻译可以使得产品更好卖,会存在更多机会。二、开发环境:
Window 10、Cmake、 C++、Qt。使用cmake管理组织代码,在开发时使用cmake生成Visual studio 2019工程,然后在这个工程里进行开发。三、使用翻译的具体步骤:
cmake的代码组织架构:
project/
├── CMakeLists.txt # 项目的最外层构建文件
├── subdirectory/ # 代码目录
├── subdirectory/ # 代码目录
├── subdirectory/ # 代码目录
├── ...
├── install/bin # 软件安装目录
├── install/cmake
│ └── common.cmake # 公共的cmake文件
├── install/component # 项目组件路径
├── install/include # 存放头文件目录
├── install/lib # 存放库文件目录
└── install/...
cmake中的相关设置:
1. 在最外层cmake文件中设置一个全局属性:set_property(GLOBAL PROPERTY INTERNATIONALIZATION ON)
- 在需要翻译的工程中进行如下设置
# 获取全局属性
get_property(INTERNATIONALIZATION GLOBAL PROPERTY "INTERNATIONALIZATION")
...
# 若在最外层设置开启翻译则生成项目的翻译文件
if(INTERNATIONALIZATION)
find_package(Qt5LinguistTools)
set(TS_FILES ${CMAKE_SOURCE_DIR}/translations/${PROJECT_NAME}_zh_cn.ts)
qt5_create_translation(QM_FILES ${SRC_FILES} ${SRC_UI_FILES} ${TS_FILES} OPTIONS -source-language en_us -no-obsolete)
add_library(${PROJECT_NAME} SHARED ... ${QM_FILES})
else()
add_library(${PROJECT_NAME} SHARED ...)
endif()
对多个翻译文件进行合并:
由于多个待翻译的项目会生成多个翻译文件,但要是对每个翻译文件进行一次编译,太麻烦。合并翻译文件代码:// 将多个翻译文件合并为一个
bool mergeTsFileList(const QStringList tsFiles, const QString& outputFilePath)
{
QStringList contentList;
for (const auto& path : tsFiles)
{
QFile file(path);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
qDebug() << "open fail, xmlPath is:" << path + "\n" + file.errorString();
return false;
}
QTextStream in(&file);
// 设置为utf8才能保证中英文的正常读取
in.setCodec("UTF-8");
// 读取所有内容并移除前三行和最后一行
QStringList content;
while (!in.atEnd()) {
content.push_back(in.readLine());
}
content.pop_front();
content.pop_front();
content.pop_front();
content.pop_back();
contentList += content;
}
// 保存目标XML文件
QFile targetFile(outputFilePath);
if (!targetFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qDebug() << "无法打开目标文件";
return false;
}
QTextStream out(&targetFile);
// 写入XML声明
out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << endl;
out << "<!DOCTYPE TS>" << endl;
out << "<TS version=\"2.1\" language=\"zh_CN\" sourcelanguage=\"en_us\">" << endl;
for (const auto& temp : contentList) {
out << temp << endl;
}
out << "</TS>" << endl;
return true;
}
int main()
{
// 替换为自己的目录
QString dir = "xxx";
// 替换为合并后的绝对路径
QString mergedFile = "xxx\\language_en_us.ts";
QDir directory(dir);
QStringList filters;
filters << "*.ts";
QStringList tsFileList = directory.entryList(filters);
for (auto& temp : tsFileList) {
temp = dir + "\\" + temp;
}
mergeTsFileList(tsFileList, mergedFile);
return 0;
}
制作短语书,并在合并文件后进行翻译:
1. 制作单词书:根据产生的翻译文件,把所有用source标注的文本进行提取到std::set中,保证唯一性,之后对std::set的内容全部打印后复制到翻译软件获取译文。有了原句和翻译好的句子,按照单词书的格式填入就行。翻译文件的大致格式为:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN" sourcelanguage="en_us">
<context>
<name>QObject</name>
<message>
<location filename="../idl/src/global_setting.cpp" line="60"/>
<source>相对标尺</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../idl/src/global_setting.cpp" line="61"/>
<source>绝对标尺</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../idl/src/global_setting.cpp" line="62"/>
<source>自定义</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>
单词书的格式:
<!DOCTYPE QPH>
<QPH language="en-us">
<phrase>
<source>控制</source>
<target>Control</target>
</phrase>
</QPH>
- 读取单词书的内容,对合并后的翻译文件进行翻译:
// 传入词典路径,获取词典内容
QMap<QString, QString> getDictionContent(const QString& path)
{
QMap<QString, QString> dictionContent;
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "Error opening file:" << path;
return dictionContent;
}
QXmlStreamReader xmlReader(&file);
while (!xmlReader.atEnd() && !xmlReader.hasError()) {
if (xmlReader.readNextStartElement() && xmlReader.name() == "source") {
QString source = xmlReader.readElementText();
xmlReader.readNextStartElement();
QString translation = xmlReader.readElementText();
dictionContent.insert(source, translation);
}
}
return dictionContent;
}
// 传入字典内容和翻译文件路径,在翻译后更新翻译文件
bool translateFile(const QMap<QString, QString>& dictionContent, const QString& path)
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "Error opening file:" << path;
return false;
}
QDomDocument docXML;
QString errStr;
int row, column;
if (!docXML.setContent(&file, false, &errStr, &row, &column)) {
qDebug() << path + "\nsetContent failed at line: " + QString::number(row) + "\n" + errStr;
file.close();
return false;
}
file.close();
QDomElement root = docXML.documentElement();
auto ctxList = root.childNodes();
for (int ctxIndex = 0, ctxSize = ctxList.size(); ctxIndex < ctxSize; ++ctxIndex)
{
QDomNodeList msgList = ctxList.at(ctxIndex).childNodes();
for (int msgIndex = 0, msgSize = msgList.size(); msgIndex < msgSize; ++msgIndex)
{
QDomElement msgElement = msgList.at(msgIndex).toElement();
if (msgElement.nodeName() != "message") {
qDebug() << "current name is:" << msgElement.text();
}
else {
QDomElement translation = msgElement.firstChildElement("translation");
QDomElement source = msgElement.firstChildElement("source");
if (!translation.isNull() && !source.isNull()) {
if (dictionContent.contains(source.text()) == false) {
continue;
}
if (translation.attribute("type") == "unfinished") {
translation.removeAttribute("type");
}
QDomText newText = docXML.createTextNode(dictionContent[source.text()]);
translation.appendChild(newText);
}
}
}
}
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "Cannot open file for writing:" << path;
return false;
}
QTextStream out(&file);
docXML.save(out, 4); // 第二个参数是缩进空格数,用于格式化输出
file.close();
return true;
}
使用Qt语言家生成翻译的二进制文件:
使用Qt语言家打开翻译文件后,点击文件->发布,即可在翻译文件目录生成翻译好的二进制文件。 在可执行文件中使用翻译文件:QApplication a(argc, argv);
// 安装翻译器,若是找到文件则翻译,一定要在QApplication构造之后QWidget构造之前进行加载
// tips:一定要使用二进制文件才可以进行翻译,否则load会失败
QTranslator translator;
if (translator.load(oct::rcf::utility::getAppPath() + "\\language_en_us.qm") && a.installTranslator(&translator)) {
LOG_INFO << "Load translator success";
}
else {
LOG_WARNING << "Load translator fail!";
}
QWidget w;
w.show();
return a.exec();
到此翻译就完成了,Qt的翻译是真的很方便,在.ui文件里面无论啥内容都不用管,.cpp文件只要用tr()包起来,一点也不污染代码,给它点赞。