Qt實現多國語言
文章目錄
一、概述
根據“物件模型(Object Model)”所述,Qt 中有而 C++ 沒有的特性就包括翻譯這一部分。你試想一下用純 C++ 寫一個“Hello world”然後把它翻譯,是不是就懵逼了?
是不是不知道該怎麼辦了?Qt 已經為你提供了翻譯的一條龍服務,使用起來非常的方便。本節的內容就和大家聊聊 Qt 中該如何進行翻譯操作。
二、工具集
三、相關類解析
Qt 的翻譯功能很簡單,所用到的工具類就那麼幾個,最常用的就是 QTranslator、QTextCodec、QLocale 這三個類。所有關於翻譯的類及其說明如下:
QTranslator
:儲存翻譯檔案,執行翻譯操作。QLocale
:儲存本機的區域設定,還可以不同區域格式的轉換。QTextCodec
:一個編/解碼的小工具。QTextDecoder
:可以根據位元組流的狀態正確拼接位元組流,從而進行解碼操作,常用於網路。QTextEncoder
:可以根據位元組流的狀態正確拼接位元組流,從而進行編碼操作,常用於網路。QCollator
:基於不同區域來對比字串的類。QCollatorSortKey
四、語言程式碼表
請跳轉到目標連結檢視語言程式碼表...
五、多國語言實現方案
*利用 Qt Linguist
製作單一國語言檔案 *.ts
, 由翻譯人員將 *.ts
檔案內的源語言翻譯為目標國語言, 在程式中根據使用者需求載入對應國語言翻譯檔案 *.qm
, *
根據 Qt 的 QTranslator::translate
進行翻譯工作。這樣的話切換目標國語言時就不會原始碼有任何的影響。
圖4.1 Qt翻譯加工流程
感覺有一點迭代的意思,其實不影響翻譯。因為最後一步進行載入 qm翻譯檔案所寫的程式碼已經沒有和介面相關的字串了
六、製作單一國語言方法
- 在
*.pro
TRANSLATIONS += yourlanguage.ts
*.pro
...
CONFIG += c++11
TRANSLATIONS += language_ZH_CN.ts \
language_EN.ts
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
...
- 更新翻譯檔案,
工具
>>外部
>>Qt語言家
>>更新翻譯
圖6.2 更新翻譯檔案
- 在
Qt Linguist
裡面開啟語言檔案進行編輯
圖6.3 Qt Linguist編輯翻譯檔案介面
圖6.4 釋出翻譯檔案
七、應用單一國語言方法
應用大致分為兩步驟
1. 載入語言*.qm檔案並且應用到QApplication
QTranslator* translator = new QTranslator;
QString qm_filename = ":/qt_zh_CN.qm";
qDebug() << "try laod .qm " << qm_filename;
if(translator->load(qm_filename))
{
qDebug() << "try apply .qm " << qm_filename;
QApplication::instance()->installTranslator(translator);
}
2. 重寫語言改變事件響應方法,因為我們在語言改變事件時要更新介面上的語言
void ExampleMultipleLanguageWidget::changeEvent(QEvent* event)
{
// In the case of events changing the application language
if (event->type() == QEvent::LanguageChange)
{
qDebug() << "update .qm ";
ui->retranslateUi(this); // translate the window again
}
return QWidget::changeEvent(event);
}
八、如何進行翻譯?
翻譯的前提主要有以下幾點:
-
編寫規範的程式碼
- 用 QString 包裹不需要翻譯的文字。
- 因為 QString 內部採用 Unicode 編碼格式,而 Unicode 幾乎能表達世界上任何一個語言,並且很多 Qt 庫函式的引數也是 - QString 型別,所以處理起來會比較方便。 - 當然用 char* 也可以,但是便宜的時候 Qt 內部還是會轉換成 QString,這就會帶來一定的系統開銷。關於 char* -> QString 的 - 轉換問題,Qt 預設會把 char* 當成 UTF-8 編碼格式。因此如果 char* 中的內容是其他編碼格式的,需要用 QTextCodec 類來轉換。 - 參考 QTextCodec 類 - 編/解碼小工具”。
- 用 tr() 包裹需要翻譯的文字
- 那麼凡是你要進行翻譯的文字都要用 tr() 函式來包裹。這個 tr() 是 QObject 類的一個函式,用它包裹的文字會被 - Qt Linguist(Qt語言家)捕捉到從而進行翻譯工作。或者你也可以這樣理解,用 tr() 包裹的文字會新增到 ts 檔案中。關於 ts 檔案 - 在下文會說到。例如我們的示例工程就是這樣寫的 - \code - this->ui->label->setText(tr("Hello Wolrd")); - \endcode - QML 的翻譯是用 qsTr() 來代替 tr() 函式
- 定義上下文
- 上下文一般指這個要翻譯的文字屬於哪個類。QObject 類及其子類只要使用了 Q_OBJECT 巨集,預設是當前類作為上下文. - 當然你也可以顯示的呼叫某個類的 tr() 函式來改變文字所屬的上下文. - 如指定 QLabel 類作為上下文,程式碼如下 - this->ui->labelTranslator->setText(QObject::tr("Hello World"));
-
Qt多國語言翻譯分析
根據不同的使用方法如QObject::tr()
、QCoreApplication::tr()
、QCoreApplication::translate()
還是其他巨集定義用法, 其根本
上最後呼叫的翻譯介面還是QTranslator::translate()
方法,這裡我們直接解析QTranslator::translate()
方法。其他具體用法看看源代
碼也就知道如何使用。
// QTranslator::translate 的實現程式碼, 參考自 Qt5.12.10
QString QTranslator::translate(const char *context, const char *sourceText, const char *disambiguation,
int n) const
{
Q_D(const QTranslator);
return d->do_translate(context, sourceText, disambiguation, n);
}
這裡就不多說了,這是調到 QTranslator::do_translate的堆疊了。
// QTranslator::do_translate 的實現程式碼, 參考自 Qt5.12.10
// 這裡程式碼略多,直接貼主要程式碼,不給人看的程式碼就不貼了。方法內部實現大概如下:
// >> 傳入引數安全判斷
// >> 檢查上下文資訊是否有效, True 的話進行提取,否則忽略
// >> 提取翻譯檔案內的訊息體,提取訊息體資訊有兩條執行路徑
// >> 1. 通過直接 getMessage() 方法直接獲取
// >> 2. 通過遞迴呼叫 Translator::translate() 介面提取
QString QTranslatorPrivate::do_translate(const char *context, const char *sourceText,
const char *comment, int n) const
{
// 省略一部分程式碼...
for (;;) {
quint32 h = 0;
elfHash_continue(sourceText, h);
elfHash_continue(comment, h);
elfHash_finish(h);
const uchar *start = offsetArray;
const uchar *end = start + ((numItems-1) << 3);
while (start <= end) {
const uchar *middle = start + (((end - start) >> 4) << 3);
uint hash = read32(middle);
if (h == hash) {
start = middle;
break;
} else if (hash < h) {
start = middle + 8;
} else {
end = middle - 8;
}
}
if (start <= end) {
// go back on equal key
while (start != offsetArray && read32(start) == read32(start-8))
start -= 8;
while (start < offsetArray + offsetLength) {
quint32 rh = read32(start);
start += 4;
if (rh != h)
break;
quint32 ro = read32(start);
start += 4;
QString tn = getMessage(messageArray + ro, messageArray + messageLength, context,
sourceText, comment, numerus);
if (!tn.isNull())
return tn;
}
}
if (!comment[0])
break;
comment = "";
}
searchDependencies:
for (QTranslator *translator : subTranslators) {
QString tn = translator->translate(context, sourceText, comment, n);
if (!tn.isNull())
return tn;
}
return QString();
}
那麼到這裡我們都知道只剩下最後一步了,因為遞迴最重要的方法就是從 getMessage()
方法提取出我們目標國語言的資訊。
static QString getMessage(const uchar *m, const uchar *end, const char *context,
const char *sourceText, const char *comment, uint numerus)
{
const uchar *tn = 0;
uint tn_length = 0;
const uint sourceTextLen = uint(strlen(sourceText));
const uint contextLen = uint(strlen(context));
const uint commentLen = uint(strlen(comment));
for (;;) {
uchar tag = 0;
if (m < end)
tag = read8(m++);
switch((Tag)tag) {
case Tag_End:
goto end;
case Tag_Translation: {
int len = read32(m);
if (len % 1)
return QString();
m += 4;
if (!numerus--) {
tn_length = len;
tn = m;
}
m += len;
break;
}
case Tag_Obsolete1:
m += 4;
break;
case Tag_SourceText: {
quint32 len = read32(m);
m += 4;
if (!match(m, len, sourceText, sourceTextLen))
return QString();
m += len;
}
break;
case Tag_Context: {
quint32 len = read32(m);
m += 4;
if (!match(m, len, context, contextLen))
return QString();
m += len;
}
break;
case Tag_Comment: {
quint32 len = read32(m);
m += 4;
if (*m && !match(m, len, comment, commentLen))
return QString();
m += len;
}
break;
default:
return QString();
}
}
end:
if (!tn)
return QString();
QString str = QString((const QChar *)tn, tn_length/2);
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
QChar *data = str.data();
qbswap<sizeof(QChar)>(data, str.length(), data);
}
return str;
}
根據以上程式碼,我們可以清楚的知道Qt是通過源語言的字串、長度、上下文、上下文長度等資訊提取*.qm
檔案內的內容的
具體的可根據*.ts
檔案結合理解。
九、總結
對於程式多國語言這個功能而言,C++相較於指令碼語言來說是比較麻煩的,Qt的設計理念還是非常棒的,將所有的語言翻譯程式碼
和使用解耦合在Translator類中。把更多的事情交給使用者來處理,這也是不可避免的,同時也是非常明智的,畢竟在實際使用
過程中,語言翻譯有很多歧義。