對于許多軟件開發者來說,一提到國際化(亦稱為 i18n)支持就會感到害怕。 要使編寫的代碼能夠面向外國使用者,確實需要費一翻思量,因為在現有軟件的代碼中添加國際化支持可不是一件輕而易舉的事。 如果您感覺到軟件需要支持不同語言和語言環境,哪怕這種可能性很小,從一開始就做國際化項目的準備,比起項目開始后再試圖添加國際化支持也要明智得多。
有人問“國際化”是什么意思? 國際化遠不止于將用戶界面消息翻譯成不同的語言。 它還涉及到處理不同的字符編碼、日期/時間/貨幣的顯示形式、以及跨多區域時存在的一些其他差異。
版權聲明:任何獲得Matrix授權的網站,轉載時請務必保留以下作者信息和鏈接
作者:
suli2921
原文:
http://www.matrix.org.cn/resource/article/2007-04-16/I18N_a79590da-ebb0-11db-9270-6dd444a118cb.html
關鍵字:I18N;國際化
介紹 i18nlog
本文的目的并不在于討論那些關于國際化的瑣碎的方面,而是通過研究一個稱為 "I18N Messages and Logging" (簡稱 i18nlog) 的開源項目,來介紹引入國際化功能時需要執行的一些必要的任務
i18nlog 允許您在 Java 應用程序內集成國際化的消息,這是通過向以下內容提供 API 來完成的:
+標注 Java 類以識別國際化消息
+從所有支持的語言環境資源包中獲取國際化消息
+創建特定于語言環境的異常,并在其中使用國際化的消息
+使用任何日志框架創建國際化消息日志
+自動生成特定于語言環境的資源包
+自動生成幫助及參考文檔
定義國際化消息
國際化軟件時一項最為乏味的工作莫過于維護資源綁定包了。 資源綁定包是包含 "name=value" 這種信息對的屬性文件 (.properties),其中 "name" 是資源綁定包的關鍵字字符串 (key string),而 "value" 是翻譯過的消息字符串本身。 習慣上為每種語言創建一個資源綁定包,在每個綁定包中關鍵字的設置是唯一的,而各個關鍵字相關的值就要翻譯成各種語言了。 資源綁定文件的名稱應該指明它是為哪種語言創建的,例如,mybundle_en.properties 文件中的消息是用英語寫的,而 mybundle_de.properties 包含德語消息。
i18nlog 提供了一些用于定義資源綁定消息及其關鍵字字符串的標注--用于將資源注入到 i18nlog 的自定義 Ant 任務中,您可以自動生成資源綁定包,而不必為確保屬性文件與訪問屬性文件的 Java 代碼之間的一致性作過多的工作。
@I18NMessage 標注被放在常量上,這些常量就是資源綁定包的關鍵字字符串使用的常量。 使用這些常量可以迫使執行編譯時檢查;例如代碼中引入的拼寫錯誤(如常量名稱的拼寫錯誤)和使用過時消息或已刪除消息,這些錯誤在編譯時就可以被探測到。 以下是使用此標注的示例:
java 代碼
1. @I18NMessage( "Hello, {0}. You last visited on {1,date}" )
2. public static final String MSG_WELCOME = "example.welcome-msg";
上面的示例定義了一條國際化消息。 常量的值定義了資源綁定包的關鍵字字符串(key string)。 標注的值是一條實際翻譯好的消息。 您可以將這些標注過的常量放到應用程序的任何類或接口中。 可以將它們放在單獨的類或接口中(將所有消息定義集中到一個地點),也可以放在使用到它們的類中。
@I18NResourceBundle 標注用于定義存放消息的資源綁定包文件(.properties 文件)。 它可以標注整個類或接口,也可以標注特定的部分。 如果您標注了一個類或接口,則該類或接口中的所有 @I18NMessage 標注都將被存儲在該標注定義的資源綁定包中(默認情況下)。 如果只將標注放在特定的常量上,那么它就只是那個常量的綁定包。 以下是使用此標注的示例:
java 代碼
1. @I18NResourceBundle( baseName = "messages",
2. defaultLocale = "en" )
以上代碼的意思是,所有被 @I18NMessage 標注的相關消息都將放置在名為 messages_en.properties 綁定包文件中。 下面是一個更復雜的示例(包含一系列國際化消息的接口):
java 代碼
1. @I18NResourceBundle( baseName = "messages",
2. defaultLocale = "en" )
3. public interface Messages {
4. @I18NMessage( "Hello, {0}. You last visited on {1,date}" )
5. String MSG_WELCOME = "welcome-msg";
6.
7. @I18NMessage( "An error occurred, please try again" )
8. String MSG_ERR = "error-occurred";
9.
10. @I18NMessage( "The value is {0}" )
11. String MSG_VALUE = "value";
12. }
檢索國際化消息
定義了國際化常量后,就可以使用 i18nlog 的核心類提供的 API:mazz.i18n.Msg。 該 API 用于裝載存放于資源綁定屬性文件中的消息。 它還用于將值作為變量參數進行傳遞,從而替換消息中的占位符(例如, {0}, {1,date})。 另外,此 API 將您從直接使用 JDK 類進行編碼的工作中解放出來,也許您需要閱讀一下 Javadoc 的 java.text.MessageFormat 部分,以獲得對 i18nlog 工作原理的初步認識,尤其是它如何使用本地化的數據替換占位符。
下面是 Msg 類的使用實例:
java 代碼
1. // 使用靜態工廠方法創建一個 Msg 對象
2. // 假設默認綁定包的名字是 "messages"
3. System.out.println( Msg.createMsg( Messages.MSG_WELCOME,
4. name,
5. date ) );
和
java 代碼
1. // 使用構造函數創建一個 Msg 對象
2. Msg msg = new Msg( new Msg.BundleBaseName("messages") );
3. try {
4. String hello = msg.getMsg(Messages.MSG_WELCOME, name, date );
5. ... do something ...
6. }
7. catch (Exception e) {
8. throw new RuntimeException( msg.getMsg( Messages.MSG_ERR ) );
9. }
name 和 date 參數是傳遞一個任意參數列表的示例--每個對象代表各個位置上要被替換掉的占位符({0} 和 {1,date} 分別基于前面給出的 Messages.MSG_WELCOME 中的定義)。
Msg 對象會根據“綁定包的名稱”及其當前的語言環境設置獲知使用哪種語言環境的資源綁定包。 “綁定包的名稱”是綁定包文件的名稱減去語言環境符號和擴展名(在上面的示例中,綁定包的名稱是 messages)。 您可以通過傳遞到 Msg 的構造函數或者特定的靜態工廠方法的方式,來定義 Msg 對象使用的“綁定包的名稱”,如果不進行明確地指定,將使用默認的 messages。 “綁定包的名稱”在 Msg 對象的生命周期內將一直存在,可以通過調用 Msg 對象的 setLocale() 來切換語言環境(默認語言環境是 JVM 使用的語言環境)。 如果您使用了 Msg 對象并且要求根據(被分配了 Msg 對象的)使用者的需要來切換語言環境,這將是非常有用的。
本地化的異常
i18nlog 提供了兩個基本的異常類(分別用于已查看和未查看的異常-- LocalizedException 和 LocalizedRuntimeException),可以這兩個類創建自己的本地化異常子類。 這些類都具有構造函數,其函數簽名與 Msg 類非常相似。 在使用構造函數時,通過調用資源綁定包的關鍵字和占位符的變量參數列表的方法來指定異常消息,同時還可以選擇綁定包的名稱和語言環境。 這樣一來,異常消息就可以使用本地化的各種語言,其原理就是利用了 Msg 可以檢索本地化的消息。
創建國際化消息的日志
i18nlog 提供了一種方法,可以通過該方法記錄國際化的日志信息。 日志系統的主類是 mazz.i18n.Logger。 它提供了 trace、debug、info、warn、error 和 fatal 方法等一些典型的設置。 需要指出的是,與傳遞一個消息本身組成的字符串不同,您為占位符傳遞的是資源綁定包的關鍵字字符串和參數列表。 它使用具有保護作用的 mazz.i18n.Msg 類來獲取實際的本地化消息。
通過使用工廠類 mazz.i18n.LoggerFactory 來獲得 Logger 對象,與 log4j 等獲得對象的方式基本相同。 而不同的是記錄日志消息的方法,該方法與通過 mazz.i18n.Msg 對象獲取消息非常類似:
java 代碼
1. public static final mazz.i18n.Logger LOG =
2. mazz.i18n.LoggerFactory.getLogger(MyClass.class);
3. ...
4. LOG.debug(Messages.MSG_VALUE, value);
5. ...
6. try {
7. ...
8. }
9. catch (Exception e) {
10. LOG.warn(e, Messages.MSG_ERR);
11. }
如果沒有啟用日志級別,則不會查找綁定包,也不會執行任何字符串連接。這一條件有效地加快了日志調用的速度。 如果日志消息與特定的表達式相關聯,則需要將表達式的第一個參數傳遞給日志方法。 如果啟用了棧傾卸設置,這將允許棧跟蹤傾卸消息(請往下看)。
i18nlog 的日志框架中還添加了許多其他的功能,這些功能并非潛在的、第三方的日志框架所能媲美。 首先要介紹的功能就是,可以告訴 Logger 是否傾卸異常的棧跟蹤。 您可能需要查看一個特定運行任務的所有異常的棧跟蹤,也可能不想看。 請注意,對于在 FATAL 日志級別上的異常,不能禁用此功能,使用該方法記錄的致命異常必需允許傾卸棧跟蹤。 對于其他的日志級別,日志程序只有在系統屬性 i18nlog.dump-stack-traces 設置為 true (或者在代碼中調用 Logger.setDumpStackTraces(true))的時候才會傾泄棧跟蹤。
i18nlog 日志框架附加的第二項功能是,在記錄與一個消息關聯的資源綁定包關鍵字的同時,記錄消息本身。 資源綁定包關鍵字對于所有語言環境都是相同的,也就是說,無論消息是用何種語言寫成的,關鍵字是不變的。 可以把這些關鍵字想象成“消息的 ID”或“錯誤的代碼”。 這在生成涉及到這些代碼的幫助文檔時非常有用,用戶將可以參考文檔中的額外幫助文本,以得知到底要傳遞什么消息(關于如何生成這種文檔,請查看下文)。 在默認情況下,此功能處于啟用狀態,要禁用此功能,只需將系統屬性 i18nlog.dump-keys 設置為 false 或者在代碼中調用 Logger.setDumpLogKeys(false)。
提供消息 ID、避免在已禁用日志級別上拼接字符串,這些 i18nlog 提供的日志機制也許已經足夠了。 也許有人會爭論說,將(除用戶界面消息以外的)日志消息國際化是一種負擔而且也沒有必要。 我有時也感到很難說服這種觀點。 如果項目沒有這種需求,您當然不必使用國際化日志。 即使不使用 i18nlog 提供的日志功能,還可以使用它提供的其他功能嘛。
但是,我還是可以舉出一些具體的例子來證明使用國際化消息的好處。 請注意,i18nlog 允許定義一個不同的語言環境供日志程序使用(日志語言環境),該語言環境區別于 Msg 實例使用的語言環境。 這就可以幫助使用我的開發團隊的語言來記錄日志消息,而我的用戶界面使用用戶可以閱讀的語言(可能與開發團隊使用的語言不同)。 例如,我的用戶說德語,而軟件是由說法語的法國團隊開發的。 在這種情況下,德國用戶碰到一個問題時,就可以正常地給法國開發團隊發送日志,軟件可以默認地將語言環境設置為 Locale.FRENCH。 另一方面,如果德國用戶希望自行調試問題,法語的日志消息根本不會有任何幫助。 在這種情況下,德國用戶簡單地通過設置系統屬性,將日志消息記錄為德語。 關于如何切換日志語言環境的信息,請參考 mazz.i18n.LoggerLocale Javadoc。
自動生成資源綁定包
i18nlog 提供了一個 Ant 任務,用于自動生成資源綁定包屬性文件,以避免開發者擔負手動向屬性文件中添加消息、清理過時消息的任務。
該 Ant 任務將掃描類以查找 @I18N 標注,并根據這些標注為您創建資源綁定包。 這意味著,無論添加多少 @I18NMessage 標注字段,它們都將被加入到資源綁定包中。 如果您刪除了一個國際化消息常量,則該消息也將從 Ant 任務生成的資源綁定包結果中刪除。 要運行該 Ant 任務,需要在 Ant 腳本中添加以下類似代碼:
xml 代碼
1. <taskdef name="i18n"
2. classpathref="i18nlog-jar.classpath"
3. classname="mazz.i18n.ant.I18NAntTask" />
4.
5. <i18n outputdir="${classes.dir}" verify="true" verbose="true">
6. <classpath refid="my.classpath" />
7. <classfileset dir="${classes.dir}"/>
8. i18n>
必需讓 Ant 任務知道含有國際化標注類及其依賴關系的類的類路徑 ,還必須給出類文件集 ,其中包含文件集的文件列表供掃描國際化標注之用。 建議您在第一次運行 Ant 任務的時候采用“冗長”模式,以便知道 Ant 任務的執行內容。 一旦得到了想要的效果,再將“冗長”模式關閉。 執行此任務之后,資源綁定包屬性文件將出現在指定的輸出目錄中。
生成幫助文檔
使用此 Ant 任務的另一個可選功能是生成幫助文檔,該文檔是由所有對資源綁定包關鍵字名稱及其消息值的引用組成的,另外還包含了對這些消息的描述。 這是一個可在 @I18NMessage 標注中指定的可選屬性,help 屬性。 屬性值可以是深入描述消息的任何字符串。 可以將文檔想象成在特定情況下將傳遞的具體消息。 自動生成的幫助文檔可以提供消息關鍵字、消息本身以及消息描述之間的交叉引用:
java 代碼
1. @I18NMessage( value="The value is {0}",
2. help="This will show you the value of your"
3. +" current counter. If this value is over"
4. +" 1000, you should reset it.")
5. String MSG_VALUE = "value";
6.
7. @I18NMessage( value="Memory has {0} free bytes left",
8. help="The VM is very low on memory. Increase -Xmx"
9. String MSG_LOW_MEM = "low-memory";
大多數情況下,您可以將其作為“消息的 ID”或者“錯誤的代碼”列表使用,可以將其中的資源綁定包關鍵字當成一個“消息 ID”或者“錯誤的代碼”。 要生成幫助文檔,需要在 任務中使用 內部標記:
xml 代碼
1. <i18n outputdir="${classes.dir}">
2. <classpath refid="my.classpath" />
3. <classfileset dir="${classes.dir}"/>
4. <helpdoc outputdir="${doc.dir}/help"/>
5. i18n>
對于每個生成的資源綁定包,都可以在 <helpdoc> 中指定的目錄下找到一個相應的幫助文檔。 文檔是根據用于描述文檔樣式的模板生成的。 默認情況下,模板是一個簡單的 HTML 頁,使用 <table> 標記消息代碼,而輸出就是幫助文檔。 在 <helpdoc> 標記中還可以指定一些其他的屬性來自定義一個模板,以滿足自行定制幫助文檔外觀的需求。 有關更多信息,請查閱 mazz.i18n.ant.Helpdoc 類的 Javadoc。
在幫助文檔的生成結束之后,您就可以得到一個(或一些)包含消息關鍵字代碼、消息內容及任何 "help" 屬性定義內容的文檔了。
本地化
我們已經討論了許多關于如何通過獲取翻譯的消息和本地化的消息國際化軟件的內容。 嵌入國際化功能之后,剩下的工作就是手動處理那些需要被本地化的資源綁定屬性文件(由 <i18n> Ant 任務生成或者自行手動編寫)了。 必須確保將所有資源綁定包的消息翻譯成需要支持的語言,而且這些消息包含的數據也進行相應的本地化。 通過定義占位符屬性(例如,{0,date} 將使用目標語言環境的格式和語言輸出日期字符串)可以完成許多本地化方面的工作。 不言而喻的是,必須找一家優秀的翻譯和本地化公司。
小結
這篇文章介紹了如何通過一個新的開源項目 i18nlog 在應用程序中溶入國際化功能。 使用該開源項目提供的工具和 API 可以自動管理資源綁定包屬性文件(.properties),檢索和管理這些綁定包中的本地化消息,甚至還可以生成供最終用戶使用的幫助文檔。
資源
+i18nlog 用戶指南:
i18nlog.sourceforge.net/doc/users-guide.html
+本地化消息時使用的占位符語法:
java.sun.com/j2se/1.5.0/docs/api/java/text/MessageFormat.html
+Matrix Java社區:
http://www.matrix.org.cn/
John Mazzitelli 是一位 JBoss 開發者,Red Hat 的帶頭人,目前正致力于 “Boss Operations Network 管理平臺”的實現。