Qt 的多語言支持的翻譯機制
來源 https://zhuanlan.zhihu.com/p/44536503
概述
根據“對象模型(Object Model)”所述,Qt 中有而 C++ 沒有的特性就包括翻譯這一部分。你試想一下用純 C++ 寫一個“Hello world”然后把它翻譯,是不是就懵逼了?是不是不知道該怎么辦了?Qt 已經為你提供了翻譯的一條龍服務,使用起來非常的方便。本節的內容就和大家聊聊 Qt 中該如何進行翻譯操作。
我們到底在翻譯什么?
大多數情況我們可能只進行文字上的翻譯,比如英語翻譯成漢語、俄語、日語等等這些肉眼就可以看得到的東西。但是你有想過阿拉伯文可是從右往左書寫的;各國的貨幣符號可是不一樣的(人民幣是¥,美元是$);各國的大數表示方式可是不一樣的,比如我們寫100萬就是“100 0000”,美國人習慣於“1,000,000”,中間的逗號咋辦呀?要是靜態的數字還好辦,那萬一要顯示的數字是個變量就沒轍了。所以“區域”的設置也屬於翻譯的一部分。參考“QLocale 類 - 存儲本機的區域設置”。
除了肉眼能看到的以外,還有就是眼睛看不到的字符編碼轉換問題。這部分不是給用戶看的,而是給程序員看的。假設我們得到一段以“KIO8-R”為編碼格式的俄文字節流,我要怎么把這段數據轉換成比如 GBK18030 編碼的中文啊?所以“編碼的轉換”也屬於翻譯的一部分。Qt 也同樣提供了針對這個問題的類,參考“QTextCodec 類 - 編/解碼小工具”。
綜上所述,一個完整的翻譯包括三部分:(1)文字的翻譯;(2)區域格式的翻譯;(3)語言編碼的翻譯。然鵝,絕大多數的新手可能只關注第一條而忽略了后兩條。
和翻譯相關的 Qt 類
Qt 的翻譯功能很簡單,所用到的工具類就那么幾個,最常用的就是 QTranslator、QTextCodec、QLocale 這三個類。所有關於翻譯的類及其說明如下:
- QTranslator:存儲翻譯文件,執行翻譯操作。
- QLocale:存儲本機的區域設置,還可以不同區域格式的轉換。
- QTextCodec:一個編/解碼的小工具。
- QTextDecoder:可以根據字節流的狀態正確拼接字節流,從而進行解碼操作,常用於網絡。
- QTextEncoder:可以根據字節流的狀態正確拼接字節流,從而進行編碼操作,常用於網絡。
- QCollator:基於不同區域來對比字符串的類。
- QCollatorSortKey:用於加速一個字符串的排序。
翻譯流程框架圖
大致的流程是這樣的:首先源代碼產生 ts 文件,然后送給 Qt Linguist(Qt語言家)這個 Qt 自帶的小工具進行處理產生 qm 翻譯文件,最后源代碼里加載這個 qm 翻譯文件。

感覺有一點迭代的意思,其實不影響翻譯。因為最后一步進行加載 qm 翻譯文件所寫的代碼已經沒有和界面相關的字符串了。
如何進行翻譯?
從這部分開始我們詳細的說下每一個步驟該如何做。為方便講述,這里有一個示例工程,MainWindow 類中就添加一個 QLabel 對象。
第一步:寫規范的代碼
(1)用 QString 包裹不需要翻譯的文本。
因為 QString 內部采用 Unicode 編碼格式,而 Unicode 幾乎能表達世界上任何一個語言,並且很多 Qt 庫函數的參數也是 QString 類型,所以處理起來會比較方便。
當然用 char* 也可以,但是便宜的時候 Qt 內部還是會轉換成 QString,這就會帶來一定的系統開銷。關於 char* -> QString 的轉換問題,Qt 默認會把 char* 當成 UTF-8 編碼格式。因此如果 char* 中的內容是其他編碼格式的,需要用 QTextCodec 類來轉換。參考“QTextCodec 類 - 編/解碼小工具”。
(2)用 tr() 包裹需要翻譯的文本
回顧本文最開頭的概述,那么凡是你要進行翻譯的文本都要用 tr() 函數來包裹。這個 tr() 是 QObject 類的一個函數,用它包裹的文本會被 Qt Linguist(Qt語言家)捕捉到從而進行翻譯工作。或者你也可以這樣理解,用 tr() 包裹的文本會添加到 ts 文件中。關於 ts 文件在下文會說到。例如我們的示例工程就是這樣寫的:

QML 的翻譯是用 qsTr() 來代替 tr() 函數。
(3)定義上下文
什么叫上下文?請看下圖,上下文一般指這個要翻譯的文本屬於哪個類。QObject 類及其子類只要使用了 Q_OBJECT 宏,默認是當前類作為上下文。
當然你也可以顯示的調用某個類的 tr() 函數來改變文本所屬的上下文。比如還是下圖,如果直接寫 tr("Hello"),那么打開 Qt Linguist 的話這一條會屬於 MainWindow 這個上下文。但我這里寫 QScroller::tr("Hello") 的話,圖中顯示就是屬於 QScroller 這個類作為上下文。

(4)如何翻譯非 Qt 類
QObject 類有現成的 tr() 函數可以方便使用,但是如果是非 QObject 類沒有 tr() 函數怎么辦呢?方法有四種:
第一種:利用 QCoreApplication::tr() 函數
用 QCoreApplication::tr() 這個函數來包裹要翻譯的文本,只有這樣才能被 Qt Linguist 捕捉到。例如:

第二種:利用 QCoreApplication::translate() 函數
用 QCoreApplication 類的 translate() 函數包裹要翻譯的文本也能被 Qt Linguist 捕捉到。

第三種:利用 QCoreApplication 類的 Q_DECLARE_TR_FUNCTIONS 宏
使用這個宏當然要 #include <QCoreApplication> 啦。使用之后就可以用 tr() 函數了。
內部的原理依靠元對象系統,使用該宏后會自動在該類添加如下兩個靜態函數。這樣就可以用啦。
static inline QString tr(const char *sourceText, const char *comment = 0); static inline QString trUtf8(const char *sourceText, const char *comment = 0);

第四種:使用 QT_TR_NOOP() 宏和 QT_TRANSLATE_NOOP() 宏
示例代碼如下,可以被捕捉到。但是這個宏一般不這樣用,你也發現了在 Qt Linguist 的短語和表單這個窗口內沒有“world”字符串了。

正確做法是用一個數組來存儲用該宏包裹的字符串,這個數組在運行時存儲的就會變為翻譯后的文本。正確做法如下:
QString FriendlyConversation::greeting(int type)
{
static const char *greeting_strings[] = {
QT_TR_NOOP("world"),
QT_TR_NOOP("hello")
}
return tr(greeting_strings[type]);
}
(5)給翻譯添加個注釋
添加注釋可以更好的幫助翻譯人員進行理解這個文本的含義,尤其是不同語境下有不同含義時。添加注釋有兩種方法,一種是采用固定的注釋格式,另一種就是利用 tr() 的第二個參數。
第一種:添加注釋的格式為:
//: ...
/*: ... */

第二種:tr() 函數的雙參數
給翻譯人員提供額外的信息來幫助理解不僅可以添加注釋,在 Qt 4.4 之前最常用的方法是用 tr() 函數的第二個參數。

(6)如何翻譯復數?
有時候會遇到這樣一個場景,英語中有這樣兩句話:
I have 1 book.
I have 2 books.
此時就可以利用 tr() 的第三個參數,寫代碼就這樣寫:
int n = books.count();
showMessage(tr("I have %n book(s).", "", n));
上述代碼的 tr() 函數,第一個參數是實際展示的文本,變量用 %n表示,就好比 %1、%2等之類的。book 的復數形式用括號括起來,翻譯之后就會根據數來顯示不同的形式;第二個參數是注釋用的,這里面寫不寫看你自己,反正是給翻譯人員看的;第三個就是這個變量 n。
第二步:轉成 ts 文件
要想轉成 ts 文件,一方面需要在 pro 文件中指定 ts 文件名,另一方面用 lupdate 功能生成。
在 pro 文件中指定 ts 文件
在 .pro 文件中添加如下代碼:
TRANSLATIONS = app_zh.ts \
app_de.ts
注意 TS 文件名,標明區域語言對運行時加載何種語言會很有用。Qt Linguist 會根據 TS 文件名自動設置區域。例如 app_de.ts 會將最終的翻譯語言設置為德語,app_de_ch.ts 會將語言設置為德語,國家設置為瑞士。啥意思呢?其實是方便翻譯人員的一個小技巧,看下圖的不同點。看到沒,它會根據本機的區域設置自動顯示對應的語言。如果文件名不遵循該規范,也可以在 Qt Linguist 的“編輯”-“翻譯文件設置”中顯式的設置區域信息。

用 lupdate 生成 ts 文件
在 Qt Creator 的菜單欄中依次點擊“工具”-“外部”-“Qt語言家”-“更新翻譯(lupdate)”,就會在源代碼文件所在的目錄生成 ts 文件。
第三步:翻譯並生成 qm 文件
用 Qt Linguist 打開 ts 文件並翻譯后記得時刻保存,至此 ts 文件制作完畢,接下來就是 qm 文件的生成。
qm 文件生成可以直接在 Qt Linguist 里點擊“文件”-“發布”,或者在 Qt Creator 中點擊“工具”-“外部”-“Qt語言家”-“發布翻譯(lrelease)”都可以。
至此,qm 文件生成完畢。
最后一步:源代碼中加載、安裝翻譯文件
還記得 QTranslator 類嗎?參考“QTranslator 類 - 翻譯文件的容器”。示例代碼如下,很簡單吧。
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QTranslator translator;
translator.load("app_zh");
app.installTranslator(&translator);
return app.exec();
}
多說兩句,在上文第二步我們說過,翻譯文件的命名如果遵循 Qt 的規范寫的話會自動被識別這是哪個語言的翻譯文件,比如“app_zh.ts”中的“zh”。所以高手們用 QTranslator:: load() 函數一般是這樣的:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QString locale = QLocale::system().name();
QTranslator translator;
translator.load(QString("app_") + locale);
app.installTranslator(&translator);
return app.exec();
}
你看,用 QLocale 直接就能獲得計算機的區域設置“zh”了。如果這個程序在德語的操作系統環境下就會加載“app_de.qm”文件。
結語
整個有關 Qt 翻譯的內容就是這么多了。筆者在寫這篇文章時參考的官方內容有:
- Internationalization with Qt
- Writing Source Code for Translation
- Qt Linguist Manual
- Hello tr()示例
- Arrow Pad示例
- Troll Print示例
================= End
