了解如何使用 GTK+ 庫創建支持多種語言并適用于世界不同的地方的圖形用戶界面 (GUI) 應用程序。本文向您介紹如何避免常見的錯誤和創建可以可靠地處理國際需求的應用程序。
世界正在不斷地發展。如今,您不能夠忽視全球市場的存在,并且計算機也不是那些花費大量時間和精力去研究其中復雜情況的少數人的昂貴的玩具。因此,創建適合于國際用戶的即時可用的應用程序的需求也在日益地增長。
通常,圖形用戶界面 (GUI),特別是 GTK+ 應用程序也不例外。實際上,經過巨大改進的國際化(以下稱為 i18n,表示單詞 internationalization i 和 n 之間的 18 個字母)支持是從 GTK+ V1.x 到 V2.x 的重大更新中的一個非常重要的部分。
本文說明了如何使用這些功能來創建可以理解和尊重不同文化和語言的用戶的需求的 GUI。您將了解可以創建哪些應用程序,并預覽如何實現它們,同時本文還提供了一些使得您能夠踏上正確的開發之路的建議。
國際化需求
![]() |
|
很明顯,GTK+ 的創建者從一開始就意識到了國際化的需求,并且將其深深地嵌入到 GTK+ 庫中的各個方面。為了達到這個目標,其中存在許多的功能,您可以使用它們來創建在面臨多種語言用戶需求時正常運行的應用程序。
這些功能中包括:
![]() ![]() |
![]()
|
為國際化準備您的應用程序
正確的國際化需要兩個互補的資產。首先是正確的設計思想,摒除特定于任何語言的假定,并且能夠意識到當您的應用程序遷移到另一種語言時可能或將會發生的變化。其次是正確的工具集,一種能夠支持摒除假定 編程風格的工具集。
下面,您將看到關于可能遇到的問題和可以應用的解決方案的簡要概述。這個概述并不是全面的或權威性的:正確的國際化是一個廣泛而深入的主題。但是對于本文中沒有進行深入介紹的所有細節內容,我都提供了相應的參考資料的鏈接,以便為您提供所需的資源。
請確保您需要進行本地化
根據過去的經驗所知的一些對國際化的錯誤處理,如為不同的語言配送不同的、不兼容二進制代碼或使用 16 進制的編輯器胡亂切割數據文件,這些都不是正確的解決方案,并且在這里我也不會對它們進行討論。唯一真正的處理國際化的方法是正確地標記和提取那些需要進行本地化的內容,并從此處開始將這些部分作為單獨的實體進行獨立的處理。您將在“代碼”部分中了解如何進行這樣的處理。
了解語言之間的差異
顯現出這些差異的一種情況是在自動生成的消息中,特別是那些涉及到復數的消息。請考慮下面的兩種方法:
|
和
|
這兩種方法都是錯誤的,而且在英語(即使在英語中也是有問題的,比如您需要處理 fish 或 stories,而不是 files)以外的語言中沒有任何價值,它們不適合于實際應用,除非您的目的是創建笨重和糟糕的界面。另外,第一種解決方案 存在嚴重的可讀性問題。
相反,可以使用一種專門的解決方案,如 GNU gettext 庫中的 ngettext()
函數:
|
使用提供的數值參數和語言翻譯器提供的規則,ngettext()
函數可以在運行時為指定的語言確定正確的格式,或者在不存在任何格式時使用后退字符串(上面的代碼中兩次使用了該字符串)。有關使用 ngettext()
函數的詳細信息,請參考 gettext 庫手冊(請參見參考資料部分)。
從上面的示例中可以看到,無論是否涉及到復數,您都不應該試圖通過字符串連接或其他的代碼技巧來生成消息。這樣做可以防止您的翻譯器根據它們的需要而更改句子的順序,并將這些要翻譯的內容變成難以理解的、支離破碎的文本(因為它們將得到一些文本塊而不是一個完整的句子,并且沒有任何關于這些文本塊之間的關系的提示信息)。請始終使用完整的、有意義的句子,并將關聯的文本組織在一起。
遵守文化習俗
國際化決不僅僅只是翻譯字符串。不同語言之間的差別在于它們所使用的小數點分隔符(逗號還是點號)、日期格式、使用 12 小時還是 24 小時的時鐘、貨幣格式等等。另外,您還需要處理字母的排序和一般文本操作等常用的操作,即什么是字母、每個字母在字母順序中的位置、什么是標點符號等等。所有這些細節信息都位于所謂的區域設置 定義文件中,并且假定來自于操作系統。GLib 對不同操作系統之間的差異進行了抽象,并且提供了一些關于文本和區域的實用函數。請花些時間瀏覽 GLib API 參考手冊中關于 Unicode、日期和時間、字符串的部分(請參見參考資料部分)。
操作系統和 C
語言提供的可以識別區域設置的服務也有另一方面的問題。請注意,在缺省情況下,大多數 C
庫函數都以一種與區域相關的方式運行。這就意味著,例如,如果您在一臺美國的計算機上使用 strtod()
函數保存了一個浮點數值,并稍后試圖在一臺波蘭的計算機上讀取該數值,那么這個操作將會失敗,因為這兩種區域設置使用了不同的小數點分隔符。相反,在您需要保存數據時,如配置文件,可以使用 GLib 提供的 g_ascii_*
系列函數。
請多加小心
國際性問題要比您可能預期的更加復雜。例如,在您對圖形進行選擇時,請加以小心,因為對您來說可能是一個簡單的圖標,而對不同文化的人來說則可能是一個嚴重的冒犯。這條規則特別適用于合并人體不同部分的圖標。請始終使用系統提供的常用資源,如圖標。如果沒有合適的常用圖像,那么需要對您所使用的圖像進行注冊,以便本地供應商可以使用主題來替換它,而不必對源代碼進行修補。
重用原則也適用于代碼:不要重寫庫中已經提供的任何與區域設置有關的函數。如果您發現庫中缺少某個函數,請參見 GTK+ 開發人員郵件列表(請參見參考資料部分):它可能是一個錯誤,并且在您提交修補程序時,您的代碼將使所有人獲益。
請非常 小心地處理任何內容,即使僅具有微不足道的政治意義:在您引用旗幟、地圖或具有政治含義的名稱之前,請三思。排序代碼中的錯誤可能令人討厭,但是不得不從整個次大陸召回您的產品,就像 Microsoft® 對 Microsoft Windows® 95 操作系統所做的那樣,這比令人討厭要糟糕的多。
請始終使用 Pango 進行文本的表示
您無法再創造出處理世界上每種語言的代碼,所以不應該考慮使用塊來構建文本,因為文本并不是塊。別再猶豫了:請使用 Pango。
![]() ![]() |
![]()
|
代碼
既然您已經了解了國際化的基本思想,那么接下來看看如何在 GTK+ 代碼中處理它們。
首先,您必須聲明一些允許 gettext 庫為您的應用程序找到正確消息的名稱。請注意,在現實的場景中,構建系統將為您處理這些名稱,但是出于我們的需要,可以使用下面的名稱:
|
在此之后,您必須正確地包含 gettext Header。完成該任務的最簡單的方法是使用 GLib 提供的可用 Header(在 Versions 2.4 及更高版本中可用):
|
這個 Header 為您提供了 _()
和 N_()
宏,用來標記可翻譯的字符串,稍后您將看到。
現在,您必須以一種 gettext
可以識別并在運行時進行翻譯的方式來標記用戶可見的字符串。出于這兩個目的,您可以使用 _()
宏,它是 gettext()
函數完整調用的簡短別名。
gettext()
函數查找消息目錄中提供的字符串,以判斷對于當前語言,是否存在一個經過翻譯的合適的版本。如果存在,它會返回翻譯結果;否則,它會返回原始字符串。當您為源代碼進行翻譯準備工作時,在掃描過程中將單詞 gettext 作為標記,以便提取要翻譯的消息并將其放置到單獨的文件中。
認識到了這一點,您就可以開始以不同的語言進行表達了。在程序開始執行之前,您必須初始化 gettext:
|
現在,您可以使用 gettext()
調用來替換所出現的每個可以翻譯的字符串。因此,下面的代碼行形式:
|
變為:
|
這就是與此有關的全部內容,其中有兩點例外之處。一個是不能在靜態字符串中使用 gettext()
,因為它是一個函數。在這種情況下,可以使用 N_()
宏,它不進行任何擴展,但會被作為對翻譯內容進行標記的關鍵詞。稍后在需要使用的地方,可以和前面一樣使用 _()
。因此:
|
另一個例外是包含某個數值變量的情況,如檢索的文件的數目。在這種情況下,可以使用 ngettext()
函數,它能夠理解復數。GNU gettext 庫手冊中包含了該函數的細節信息,以及關于 gettext()
函數的使用和操作的細節信息。
最后,請正確地選擇哪些內容應該翻譯,哪些內容不應該翻譯。通常,所有用戶可見的字符串都是翻譯工作的候選對象。然而,對于調試信息和其他面向開發人員的信息,可以保留其中的部分或所有內容不進行翻譯,以使得您自己和其他開發人員能夠理解并在源代碼中對其進行查找。FooWidget 便是這種方式的一個示例(請參見下面的“自定義小部件”),它包含了一個模擬的調試模式,在該模式中 RTL
或者 LTR
標記都沒有進行翻譯。
與此類似,避免翻譯那些不是真正的單詞的內容。例如,不要翻譯 TCP/IP 狀態標記,即使它們最初來自于英語單詞。這類錯誤幾乎隨處可見,例如,在 Microsoft Windows 的網絡工具中,類似 SYN_ACK 的內容被翻譯 為毫無意義的波蘭語 ZGODN_POTW。其結果是所有的人,包括說本族語的人,都被弄糊涂了,感到無法理解,甚至在 Internet 上查閱這樣的信息。
自定義小部件
提供(或不提供,在合適的情況下)經過翻譯的字符串是整個工作中重要的部分。其他的部分則是能夠正確地顯示這些字符串。要實現這個目標,需要您的應用程序能夠處理文本方向為 RTL 的區域設置,而不是通常的英語中從左到右 (LTR) 的文本。因為這些區域設置中的文本是從右到左的,所以必須對 GUI 進行邏輯鏡像(請參見圖 1)。
幸運的是,在百分之九十九的情況下,您幾乎不用做任何事情 就可以啟用這種模式。GTK+ 對這些工作進行自動處理,此外,由于布局代碼根據小部件之間的邏輯關系而不是硬編碼的像素座標進行操作。
即使您創建自定義的小部件,通常無需進行任何工作就可以實現對 RTL 區域設置的支持。只要您的小部件是其他小部件混合而成的,那么 GTK+ 中的布局邏輯將生效并完成正確的工作。真正需要您考慮本文方向的唯一情況是,您的小部件包含無法自動地進行鏡像的自定義繪制代碼,或者包含依賴于文本方向的其他邏輯。
作為示例,我包含了一個模擬的 FooWidget 應用程序,它并不完成任何特別有價值的工作,但是它可以對文本方向起作用并設置適當的消息。真正需要的只是使用 gtk_widget_get_direction()
進行簡單的檢查。因此,對于 FooWidget,下面代碼中的技巧位于它的 _init()
函數中:
|
正如您所看到的,對用戶的區域設置進行調整是非常簡單的,并且僅依賴于您的小部件的復雜程度。如果您所完成的工作比較復雜,那么相應的代碼可能會有些棘手。但是對于大多數用戶,確保正常運行所需的工作僅包括簡單的檢查和直接的更改,如使用加法代替減法。
![]() ![]() |
![]()
|
最后的說明
本文篇幅有限,無法詳細地說明有關國際化的所有內容。特別是,本文沒有涉及 Pango 的使用。幸運的是,如果您不打算編寫很多的自定義小部件,那么您就不需要經常使用 Pango,并且大多數人也不需要這樣做。然而,如果您發現自己需要進行文本顯示,那么請務必使用 Pango 函數來進行布局、測量以及表示文本。
本文沒有涉及到的另一個重要的方面是與您的構建系統進行集成。這種集成區別很大,具體取決于您所使用的對象,但是您必須正確地集成國際化以確保您的翻譯是最新的。一種可能是使用 GNU 自動工具,它具有對 GNU gettext 庫和相關實用程序以及來自 GNU gettext 庫和相關實用程序的內置支持。這在開放源碼項目中特別重要,因為自動工具被認為是缺省的構建系統。然而,由于其靈活性和強大的表達能力,自動工具以違反國際化的相關問題而出名,但沒有什么是不能克服的,只是這需要一些(或大量的)專業知識。當您發現自己孤立無助的時候,您可以詢問 GTK+ 用戶郵件列表中其他的用戶(請參見參考資料部分)??赡苡腥嗽浥龅竭^您所遇到的問題。
請閱讀 GNU gettext 庫手冊。即使您不打算使用 gettext,至少可以閱讀一下引言部分:它包含了大量的信息,詳細地說明了如何以及為什么庫中所完成的工作在很大程度上獨立于您所使用的任何特殊工具。
![]() ![]() |
![]()
|
結束語
您了解了與應用程序國際化相關的一些挑戰和常見的問題,以及如何解決它們。您知道了建立可以識別和支持不同語言和文化的用戶需求的 GUI 所需進行的工作。使用本文中包括的參考資料,您可以獲得各種各樣的工具,以便可以創建能更好地滿足更多用戶的需求的程序。