配置CLion管理Qt項目國際化支持的方法
隨著Qt 6的發佈,cmake也正式宣告接管qmake的工作瞭。
在之前的一篇博客裡我介紹瞭如何使用cmake管理你的qt項目,不過有一點我沒有講,那就是對國際化(i18n)的處理。
今天我們就來介紹下如何使用cmake+clion配置管理一個包含瞭國際化支持的項目。
準備工作
你需要準備下面的工具
- Qt 5.13+(我使用的是Qt 5.15.2)
- CLion 2020.3+
- GCC 9.0+ (最好支持c++17,最低要求是支持c++11)
其中GCC一般自己安裝的Qt會有附帶,否則在Windows上使用vs2019的編譯器也是可以的。
在Linux上如果不想自己下載安裝Qt的話也可以使用系統倉庫打包好的:
# 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的話可以參考這篇文章配置Qt項目。
Qt 6在cmake的配置上是類似的,隻需要修改幾個函數的名稱即可,後面會提及。
創建項目
前置工作完成之後就可以創建項目瞭,如下圖所示:
默認是c++14標準,我個人更喜歡用c++17,不過今天的例子使用c++14也是可以的。
創建完成後你會得到如下的CMakeLists.txt文件:
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
。點擊編譯運行你就會看到程序創建的窗口瞭。
下面我們就該進入正題瞭。
配置國際化支持
為瞭能更好地展示國際化,我們需要把例子代碼改成下面這樣:
#include <QApplication> #include <QHBoxLayout> #include <QMessageBox> #include <QPushButton> #include <QWidget> 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
處理即可。
點擊運行,你會看到界面上都是英文,因為現在我們還沒添加國際化支持:
Qt的翻譯文件由ts文件和qm文件組成,ts文件用於人類進行翻譯工作,而Qt會根據ts文件生成qm文件,這是供程序使用的二進制文件,人類無法直接閱讀。
所以在項目中我們隻需要關心ts文件即可,下面我們創建一個lang子目錄,我們要在其中進行英語到漢語和日語的翻譯工作:
mkdir lang
不過我們不用自己創建ts文件,因為這是Qt能自動完成的。
下面我們修改一下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})
哇,配置變得更長更嚇人瞭,其實核心內容一共隻有這幾行:
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的時候進行,比如這樣:
添加翻譯
添加翻譯沒什麼好講的,你可以直接編輯ts文件,因為它是xml格式的,編輯起來還是很容易的。
不過有時候翻譯需要參考文本和代碼的上下文,這時候就需要用到Qt Linguist瞭:
具體的使用細節不再贅述,你可以參考園內和網上的其他優質文章。
編譯運行後你就會在構建目錄看到兩個qm文件,這時候我們的程序還沒有完成國際化支持,在代碼中我們需要使用這些qm文件:
#include <QLocale> #include <QTranslator> 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”的名字。
我的系統默認設置是日語,因此運行程序會看到這樣的界面:
如果你的環境是中文的,那麼顯示的界面也是中文的,這就是Qt的i18n國際化支持。
將多語言資源綁定進程序
到上一節結束我們其實就把i18n講完瞭,你完全可以打包程序的時候把qm文件和程序放在一起,安裝的時候也放在同一目錄。
但這種方案還是有些缺點的:
- 如果支持的語言比較多,那麼就會有大量的小文件需要處理,難免會出錯;
- 需要把qm這種無關的文件放進編譯依賴裡
對於有代碼潔癖的我來說第二點尤其忍不瞭,但是如果不用這些qm文件的話最終是不會生成他們的,怎麼辦呢?
其實還有辦法,我們可以把這些qm文件都集成到qrc裡。
首先在目錄下創建一個translations.qrc
文件:
<RCC> <qresource> <file>zh_CN.qm</file> <file>ja_JP.qm</file> </qresource> </RCC>
這裡不用管資源文件的路徑,因為我們還要對CMakeLists.txt做些修改處理這些問題:
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被生成瞭:
因為使用瞭Qt的rcc把資源嵌入瞭程序,所以cpp代碼也得做一些調整:
QTranslator trans; -if (trans.load("./" + QLocale().name() + ".qm")) { +if (trans.load(":/" + QLocale().name() + ".qm")) { QCoreApplication::installTranslator(&trans); }
註意.
變成瞭:
,這代表我們指定的路徑是嵌入資源的。
這樣國際化支持就徹底完成瞭,通過修改Linux的LANG
環境變量我們可以自由設置程序的語言,效果如下:
總結
現在我把完整的CMakeLists.txt貼上來作為參考,對於你自己的項目當然是要做調整的:
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})
更進一步
現在你已經能用clion管理自己的多語言支持項目瞭,然而目前的方案仍稱不上完美。
因為對ts文件的更新和qm文件的創建必須在編譯期間進行,無法單獨進行上述的工作。根據文檔的說法來看,對於多語言的支持通常是在軟件本身功能基本完善之後進行的,ts文件和qm文件不會經常進行變更,所以在編譯期間一並處理是可以接受的。
不過事實上軟件的功能往往會增量更新,翻譯也會進行訂正和改進,為此需要重新編譯整個項目作為代價顯然太過高昂。
那麼能不能把i18n的處理獨立出來呢,答案是可以的,不過我們不能再借助LinguistTools瞭。因為LinguistTools在內部使用的是add_custom_command
,無法獨立構成一個編譯目標,所以得另尋出路。
下面來看看修改後的代碼:
# 去掉瞭LinguistTools find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} REQUIRED) # 創建生成ts文件和qm文件的目標 # ALL指定加入make all add_custom_target(update_all_ts_files ALL) add_custom_target(create_all_qm_files ALL) # 找到$PATH裡的lupdate和lrelease,你也可以自己設置他們的安裝路徑 find_file(LUPDATE_PATH lupdate) find_file(LRELEASE_PATH lrelease) # 對於每一個ts文件,都生成一個update_ts_file_<NAME>和create_qm_file_<NAME>目標 foreach(TS_FILE ${TS_FILES}) # 把zh_CN.ts中的zh_CN提取出來 get_filename_component(I18N_NAME ${TS_FILE} NAME_WE) set(TS_TARGET_NAME "update_ts_file_${I18N_NAME}") add_custom_target(${TS_TARGET_NAME} COMMAND ${LUPDATE_PATH} ${CMAKE_CURRENT_SOURCE_DIR} -ts ${TS_FILE} VERBATIM) # 將update_ts_file_<NAME>添加為update_all_ts_files的依賴,下同 add_dependencies(update_all_ts_files ${TS_TARGET_NAME}) set(QM_TARGET_NAME "create_qm_file_${I18N_NAME}") set(QM_FILE "${CMAKE_CURRENT_BINARY_DIR}/${I18N_NAME}.qm") add_custom_target(${QM_TARGET_NAME} COMMAND ${LRELEASE_PATH} ${TS_FILE} -qm ${QM_FILE} VERBATIM) # 因為得先有ts文件才能生成qm文件,所以把構建ts文件的目標作為自己的依賴 add_dependencies(${QM_TARGET_NAME} ${TS_TARGET_NAME}) add_dependencies(create_all_qm_files ${QM_TARGET_NAME}) endforeach() configure_file(translations.qrc ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) add_executable(${PROJECT_NAME} main.cpp ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) # 因為qrc依賴qm文件,所以需要先讓qm文件創建完成 add_dependencies(${PROJECT_NAME} create_all_qm_files)
看上去很復雜,然而實際上上面的代碼隻做瞭一件事,生成瞭如下的依賴鏈:
現在更新ts文件和生成qm文件都可以作為單獨的步驟而存在瞭,這是qt5_create_translation
和qt5_add_translation
做不到的:
很好,我們可以任意更改翻譯而不用重新編譯整個項目瞭,自己動手豐衣足食。
如果想要看更具體的項目是如何配置i18n的,我這也有一個例子,如果不嫌棄覺得有幫助的話可以star一下。
到此這篇關於配置CLion管理Qt項目國際化支持的方法的文章就介紹到這瞭,更多相關CLion Qt國際化內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- None Found