1. 程式人生 > >配置CLion管理Qt專案國際化支援

配置CLion管理Qt專案國際化支援

隨著Qt 6的釋出,cmake也正式宣告接管qmake的工作了。 在之前的一篇[部落格](https://www.cnblogs.com/apocelipes/p/10353698.html)裡我介紹瞭如何使用cmake管理你的qt專案,不過有一點我沒有講,那就是對國際化(i18n)的處理。 今天我們就來介紹下如何使用cmake+clion配置管理一個包含了國際化支援的專案。 ## 準備工作 你需要準備下面的工具 1. Qt 5.13+(我使用的是Qt 5.15.2) 2. CLion 2020.3+ 3. GCC 9.0+ (最好支援c++17,最低要求是支援c++11) 其中GCC一般自己安裝的Qt會有附帶,否則在Windows上使用vs2019的編譯器也是可以的。 在Linux上如果不想自己下載安裝Qt的話也可以使用系統倉庫打包好的: ```bash # ubuntu sudo apt-get install build-essential libglu1-mesa-dev libpulse-dev libglib2.0-dev sudo apt-get --no-install-recommends install libqt*5-dev qt*5-dev qml-module-qtquick-* qt*5-doc-html # Arch/Manjato sudo pacman -S base-devel sudo pacman -S --needed qt5 ``` 選擇CLion的2020.3及以上版本是因為它提供了自帶的Qt專案模板,省去了我們自己搭框架的麻煩。 當然如果你還在使用舊版CLion的話可以參考[這篇文章](https://www.cnblogs.com/apocelipes/p/10353698.html)配置Qt專案。 Qt 6在cmake的配置上是類似的,只需要修改幾個函式的名稱即可,後面會提及。 ## 建立專案 前置工作完成之後就可以建立專案了,如下圖所示: ![](https://img2020.cnblogs.com/blog/1434464/202102/1434464-20210201104547114-385729468.png) 預設是c++14標準,我個人更喜歡用c++17,不過今天的例子使用c++14也是可以的。 建立完成後你會得到如下的CMakeLists.txt檔案: ```cmake cmake_minimum_required(VERSION 3.17) project(untitled1) set(CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) set(QT_VERSION 5) # 設定需要用到的Qt modules set(REQUIRED_LIBS Core Gui Widgets) set(REQUIRED_LIBS_QUALIFIED Qt5::Core Qt5::Gui Qt5::Widgets) add_executable(${PROJECT_NAME} main.cpp) # 提示你應該指定qt的cmake模組的路徑,使用系統預設配置時無需關心 # 嫌這個警告囉嗦的話完全可以註釋掉或者刪除 if (NOT CMAKE_PREFIX_PATH) message(WARNING "CMAKE_PREFIX_PATH is not defined, you may need to set it " "(-DCMAKE_PREFIX_PATH=\"path/to/Qt/lib/cmake\" or -DCMAKE_PREFIX_PATH=/usr/include/{host}/qt{version}/ on Ubuntu)") endif () # 引入並連結用到的Qt modules find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} REQUIRED) target_link_libraries(${PROJECT_NAME} ${REQUIRED_LIBS_QUALIFIED}) ``` CLion就是靠cmake來組織專案的,對於cmake的配置自然是必不可少的。 專案下還有一個提前寫入了Hello World示例的`main.cpp`。點選編譯執行你就會看到程式建立的視窗了。 下面我們就該進入正題了。 ## 配置國際化支援 為了能更好地展示國際化,我們需要把例子程式碼改成下面這樣: ```c++ #include #include #include #include #include int main(int argc, char *argv[]) { QApplication a(argc, argv); QWidget window; auto btn1 = new QPushButton{QObject::tr("click left button")}; QObject::connect(btn1, &QPushButton::clicked, [w = &window]() { QMessageBox::information(w, QObject::tr("clicked left button"), QObject::tr("you clicked left button")); }); auto btn2 = new QPushButton{QObject::tr("click right button")}; QObject::connect(btn2, &QPushButton::clicked, [w = &window]() { QMessageBox::information(w, QObject::tr("clicked right button"), QObject::tr("you clicked right button")); }); auto mainLayout = new QHBoxLayout; mainLayout->addWidget(btn1); mainLayout->addWidget(btn2); window.setLayout(mainLayout); window.show(); return QApplication::exec(); } ``` 對於國際化的細節我們不過多介紹,這裡只要知道需要翻譯的文字要用`QObject::tr`處理即可。 點選執行,你會看到介面上都是英文,因為現在我們還沒新增國際化支援: ![](https://img2020.cnblogs.com/blog/1434464/202102/1434464-20210201104524489-1006389728.png) Qt的翻譯檔案由ts檔案和qm檔案組成,ts檔案用於人類進行翻譯工作,而Qt會根據ts檔案生成qm檔案,這是供程式使用的二進位制檔案,人類無法直接閱讀。 所以在專案中我們只需要關心ts檔案即可,下面我們建立一個lang子目錄,我們要在其中進行英語到漢語和日語的翻譯工作: ```bash mkdir lang ``` 不過我們不用自己建立ts檔案,因為這是Qt能自動完成的。 下面我們修改一下cmake,讓他能支援國際化: ```cmake cmake_minimum_required(VERSION 3.17) project(untitled1) set(CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) set(QT_VERSION 5) set(REQUIRED_LIBS Core Gui Widgets) set(REQUIRED_LIBS_QUALIFIED Qt5::Core Qt5::Gui Qt5::Widgets) set(TS_FILES ${CMAKE_SOURCE_DIR}/lang/zh_CN.ts ${CMAKE_SOURCE_DIR}/lang/ja_JP.ts) find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} LinguistTools REQUIRED) qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES}) add_executable(${PROJECT_NAME} main.cpp ${TS_FILES}) if (NOT CMAKE_PREFIX_PATH) message(WARNING "CMAKE_PREFIX_PATH is not defined, you may need to set it " "(-DCMAKE_PREFIX_PATH=\"path/to/Qt/lib/cmake\" or -DCMAKE_PREFIX_PATH=/usr/include/{host}/qt{version}/ on Ubuntu)") endif () target_link_libraries(${PROJECT_NAME} ${REQUIRED_LIBS_QUALIFIED}) ``` 哇,配置變得更長更嚇人了,其實核心內容一共只有這幾行: ```cmake set(TS_FILES ${CMAKE_SOURCE_DIR}/lang/zh_CN.ts ${CMAKE_SOURCE_DIR}/lang/ja_JP.ts) find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} LinguistTools REQUIRED) qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES}) add_executable(${PROJECT_NAME} main.cpp ${TS_FILES} ${QM_FILES}) ``` 第一行很好理解,把我們的需要的ts檔案的名字先設定到變數裡。 接著我們引入`Qt5::LinguistTools`,這不是c++庫,只是一個幫助生成ts檔案和qm檔案的cmake模組,所以不能連結到程式裡。 最後一行也很簡單,把ts檔案加入編譯程式的依賴專案裡,一旦發生改變就重新構建我們的程式。 關鍵在於`qt5_create_translation`這裡,這個函式會幫我們建立ts檔案,如果ts檔案已經存在就會更新ts檔案把新新增的翻譯追加進去; 到這步還沒結束,在更新完ts檔案後它會檢查ts檔案中是否有有效的翻譯資訊,如果有就在cmake的編譯目錄下生成和ts檔案同名的qm檔案。 中間的`${CMAKE_CURRENT_SOURCE_DIR}`就是指定從哪個目錄下的原始檔裡獲得需要翻譯的文字的。 注意,如果qm檔案最終沒被用到的話,那麼實際上不會被生成。為了能生成qm檔案所以我們把它加入了依賴,還有更好的辦法,後面介紹。 在Qt 6中我們只需要把函式名改成`qt_create_translation`就行了。 如果你想自己建立和更新ts檔案,只需要把函式換成`qt5_add_translation`,它會自動根據ts檔案生成qm檔案,不過要是沒有ts檔案存在他就會報錯。在Qt 6中它的名字會變為`qt_add_translation`。 上述的工作會在make的時候進行,比如這樣: ![](https://img2020.cnblogs.com/blog/1434464/202102/1434464-20210201104501178-1243107349.png) ## 新增翻譯 新增翻譯沒什麼好講的,你可以直接編輯ts檔案,因為它是xml格式的,編輯起來還是很容易的。 不過有時候翻譯需要參考文字和程式碼的上下文,這時候就需要用到Qt Linguist了: ![](https://img2020.cnblogs.com/blog/1434464/202102/1434464-20210201104442933-1437272556.png) 具體的使用細節不再贅述,你可以參考園內和網上的其他優質文章。 編譯執行後你就會在構建目錄看到兩個qm檔案,這時候我們的程式還沒有完成國際化支援,在程式碼中我們需要使用這些qm檔案: ```c++ #include #include int main() { QApplication a(argc, argv); QTranslator trans; if (trans.load("./" + QLocale().name() + ".qm")) { QCoreApplication::installTranslator(&trans); } ... return a.exec(); } ``` 我們用`QTranslator`載入本地的qm檔案,`QLocale`的name方法正好可以返回諸如“ja_JP”的名字。 我的系統預設設定是日語,因此執行程式會看到這樣的介面: ![](https://img2020.cnblogs.com/blog/1434464/202102/1434464-20210201104422693-1542670132.png) 如果你的環境是中文的,那麼顯示的介面也是中文的,這就是Qt的i18n國際化支援。 ## 將多語言資源繫結程序序 到上一節結束我們其實就把i18n講完了,你完全可以打包程式的時候把qm檔案和程式放在一起,安裝的時候也放在同一目錄。 但這種方案還是有些缺點的: 1. 如果支援的語言比較多,那麼就會有大量的小檔案需要處理,難免會出錯; 2. 需要把qm這種無關的檔案放進編譯依賴裡 對於有程式碼潔癖的我來說第二點尤其忍不了,但是如果不用這些qm檔案的話最終是不會生成他們的,怎麼辦呢? 其實還有辦法,我們可以把這些qm檔案都整合到qrc裡。 首先在目錄下建立一個`translations.qrc`檔案: ```xml zh_CN.qm
ja_JP.qm
``` 這裡不用管資原始檔的路徑,因為我們還要對CMakeLists.txt做些修改處理這些問題: ```cmake set(CMAKE_AUTORCC ON) # 一定得開啟rcc # 注意這行 configure_file(translations.qrc ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) ... add_executable(${PROJECT_NAME} main.cpp ${TS_FILES} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) ``` 需要注意的是`configure_file`那行,我們把qrc檔案原樣複製到了編譯目錄裡,因為qm檔案也是在那裡生成的。 現在qrc會被自動處理,然後我們的qm檔案作為其依賴項也能被自動生成了。 再次編譯專案,這次我們就能發現qm被生成了: ![](https://img2020.cnblogs.com/blog/1434464/202102/1434464-20210201104359262-580531648.png) 因為使用了Qt的rcc把資源嵌入了程式,所以cpp程式碼也得做一些調整: ```diff QTranslator trans; -if (trans.load("./" + QLocale().name() + ".qm")) { +if (trans.load(":/" + QLocale().name() + ".qm")) { QCoreApplication::installTranslator(&trans); } ``` 注意`.`變成了`:`,這代表我們指定的路徑是嵌入資源的。 這樣國際化支援就徹底完成了,通過修改Linux的`LANG`環境變數我們可以自由設定程式的語言,效果如下: ![](https://img2020.cnblogs.com/blog/1434464/202102/1434464-20210201104339473-2113231398.png) ![](https://img2020.cnblogs.com/blog/1434464/202102/1434464-20210201104327322-1427507160.png) ## 總結 現在我把完整的CMakeLists.txt貼上來作為參考,對於你自己的專案當然是要做調整的: ```cmake cmake_minimum_required(VERSION 3.17) project(untitled1) set(CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) set(QT_VERSION 5) set(REQUIRED_LIBS Core Gui Widgets) set(REQUIRED_LIBS_QUALIFIED Qt5::Core Qt5::Gui Qt5::Widgets) set(TS_FILES ${CMAKE_SOURCE_DIR}/lang/zh_CN.ts ${CMAKE_SOURCE_DIR}/lang/ja_JP.ts) find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} LinguistTools REQUIRED) configure_file(translations.qrc ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TS_FILES}) add_executable(${PROJECT_NAME} main.cpp ${TS_FILES} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) message(${QM_FILES}) target_link_libraries(${PROJECT_NAME} ${REQUIRED_LIBS_QUALIFIED}) ``` 不過這種方案也不是沒有問題,那就是每次只能在編譯期間更新ts檔案,這點需要注意。 如果想要看更具體的專案是如何配置i18n的,我這也有一個[例子](https://github.com/apocelipes/pHashChecker),如果不嫌棄覺得有幫助的話可以star