一.基本概念
1.為什麼要實現資源的本地化?
我們的站點可能為全球各個國家和地區的人所浏覽,每個國家和地區的人都有自身的語言文化特點。就拿咱們偉大的祖國為例,中國大陸用簡體中文,港澳台則使用繁體中文。另外各個國家對於貨幣、數字、日歷等信息的表達格式各有不同,我們國家多使用年月日的格式,而美國則是月日年。諸如此類的區別林林總總,我也就不多舉例了。為了給我們的網站浏覽者更好的用戶體驗,我們應該提供一個全球化的解決方案,只要用戶選擇了他的語言和區域,站點就按照他的語言文化習慣來展現頁面信息,這個過程可以叫做本地化。
2.區域性、固定區域性、非特定區域性、特定區域性
區域性名稱遵循 RFC 1766 標准,格式為“<languagecode2>-<country/regioncode2>”,其中 <languagecode2> 是從 ISO 639-1 派生的由兩個小寫字母構成的代碼,<country/regioncode2> 是從 ISO 3166 派生的由兩個大寫字母構成的代碼。例如,美國英語為“en-US”。在雙字母語言代碼不可用的情況中,將使用從 ISO 639-2 派生的三字母代碼;例如,三字母代碼“div”用於使用 Dhivehi 語言的區域。某些區域性名稱帶有指定書寫符號的後綴;例如“-Cyrl”指定西裡爾語書寫符號,“-Latn”指定拉丁語書寫符號。舉例:
區域性名稱
區域性標識符
語言-國家/地區
zh-CN
0x0804
中文-中國
zh-TW
0x0404
中文-台灣
zh-CHS
0x0004
簡體中文
zh-CHT
0x
繁體中文
en
0x0009
英語
en-US
0x0409
英語-美國
en-GB
0x0809
英語-英國
uz-UZ-Cyrl
0x0843
烏茲別克語(西裡爾語)- 烏茲別克斯坦
uz-UZ-Latn
0x0443
烏茲別克語(拉丁)- 烏茲別克斯坦
固定區域性不區分區域性。可以使用空字符串 ("") 按名稱或者按區域性標識符 0x007F 來指定固定區域性。固定區域性由CultureInfo類的InvariantCulture屬性來代表固定區域性的實例。固定區域性僅與英語語言關聯,不與任何國家/地區關聯。它幾乎可用在要求區域性的“全局化”命名空間中的所有方法中。如果你的程序進行字符串比較或大小寫更改操作,則應該使用 InvariantCulture 確保無論系統如何設置的區域性,行為都將按照InvariantCulture所代表的英語語言的固定區域性來完成。但是,固定區域性必須僅由需要不依賴區域性的結果的進程(如系統服務)使用;否則,它得到的結果可能在語言上不正確,或者在文化上不合適。舉例:CultureInfo Invc = New CultureInfo("");
CultureInfo Invc = CultureInfo.InvariantCulture;這兩行代碼的作用相同,目的是獲得固定區域性實例。
比如你現在要對一個DateTime的實例dateTime執行dateTime.ToString()方法。這個方法實際是使用你當前線程的CurrentCulture作為默認的區域性,根據這個區域性將日期實例轉化為相應的字符串形式。那麼如果我們此時不需要它按照線程或系統的區域性進行ToString操作,那麼我們應該用這個方法dateTime.ToString(“G”, CultureInfo.InvariantCulture)或者dateTime.ToString(“G”, DateTimeFormatInfo.InvariantInfo)。
非特定區域性是與某種語言關聯但不與國家/地區關聯的區域性。特定區域性是與某種語言和某個國家/地區關聯的區域性。例如,“en”是非特定區域性,而“en-US”是特定區域性。注意,“zh-CHS”(簡體中文)和“zh-CHT”(繁體中文)均為非特定區域性。
區域性有層次結構,即特定區域性的父級是非特定區域性,而非特定區域性的父級是 InvariantCulture。CultureInfo類的Parent屬性將返回與特定區域性關聯的非特定區域性。如果特定區域性的資源在系統中不存在,或因其它原因不可用,則使用非特定區域性的資源;如果非特定區域性的資源也不可用,那麼使用主程序集中嵌入的資源。
3.實現本地化常用的類型、屬性和方法
CultureInfo類表示有關特定區域性的信息,包括區域性的名稱、書寫體系和使用的日歷,以及有關對常用操作(如格式化日期和排序字符串)提供信息的區域性特定對象的訪問。CultureInfo類的實例化一般有兩個途徑,如下所示:
CultureInfo culture = CultureInfo. CreateS
二者的區別是,使用第一種方法,只能創建固定區域性或特定區域性的CultureInfo實例。如果name為空字符串,則建立固定區域性的實例,如果name為非特定區域性,那麼建立name 關聯的默認特定區域性的 CultureInfo實例。第二種方法,則是建立一個name所指定的區域性的CultureInfo實例,它可以是固定的,非特定的或特定區域性的。
Thread類的CurrentCulture屬性用來獲取或設置當前線程的區域性。它必須被設置為特定區域性。Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");如果Thread.CurrentThread.CurrentCulture = new CultureInfo("en ");就會報錯!
Thread類的CurrentUICulture屬性用來獲取或設置資源管理器使用的當前區域性以便在運行時查找區域性特定的資源。這裡的資源管理器可以關聯為ResourceManger類。
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
ResourceManger類可以查找區域性特定的資源,當本地化資源不存在時提供代用資源,並支持資源序列化。常用的ResourceManager的構造函數是public ResourceManager(string,Assembly)。其含義是初始化 ResourceManager類的新實例,它使用指定的根名稱從給定的Assembly中查找資源文件。所謂根名稱是例如名為“MyResource.en-US.resources”的資源文件的根名稱為“MyResource”。在根名稱的表達中可以加上命名空間,如“MyWebSite.Resource.UserFolder. MyResource”。而Assembly可以是需要調用資源文件的頁面所在的Assembly,如typeof(MyPage).Assembly。ResourceManager類的GetString方法用來獲得資源文件中的指定鍵的值。舉例:當已設置了線程的CurrentUICulture屬性之後按如下方法。
ResourceManager rm = new ResourceManager("items", Assembly.GetExecutingAssembly());
String str = rm.GetString("welcome");
如果想按照指定的區域性來獲得資源則按照如下寫法:
ResourceManager rm = new ResourceManager("items", Assembly.GetExecutingAssembly());
CultureInfo ci = Thread.CurrentThread.CurrentCulture;
String str = rm.GetString("welcome",ci);
二.在ASP.Net1.1中實現資源本地化
首先應在網站項目WebTest中建立一個Resource文件夾,在這個文件夾中存放整個項目公用的資源文件。比如我們建立了以下三個資源文件:MyResource.en.resx,MyResource.en-US.resx,MyResource.zh-CN.res。每個資源文件中都有兩個鍵值對,鍵值為State和Address。在需要使用資源文件的頁面MyPage.ASPx中調用資源文件,如下所示:
Thread.CurrentThread.CurrentCulture= CultureInfo.CreateSpecificCulture("zh-CN");
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
ResourceManager rm = new ResourceManager("WebTest.Resource.MyResource", typeof (MyPage).Assembly);
Label1.Text = rm.GetString("State");
Label2.Text = rm.GetString("Address");
好了,這個時候Label1和Label2就按照MyResource.zh-CN.resx文件中的規定顯示“州”和“地址”。以上是一個最基本最簡單的本地化方法,這裡隱含著一些問題,我們來逐一解決並優化該方法。
1. 如何獲得用戶的默認區域性
通過用戶浏覽器“屬性”->“語言”選項裡的設置,取最上面那條作為用戶的默認語言。
CultureInfo cultureInfo = CultureInfo.CreateSpecificCulture(Request.UserLanguages[0]);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
一般情況下,設定CurrentCulture和CurrentUICulture具有相同的區域性,當然也可以不相同,比如你規定CurrentCulture為en-US,而CurrentUICulture為zh-CN。那麼這樣造成的效果是,頁面中貨幣、日期等信息都按照美國英語的格式顯示,而需要從資源文件中取值的內容,資源管理器會從MyResource.zh-CN.resx文件裡獲得。
如果你的站點頁面上並沒有提供讓用戶選擇語言的功能,那麼也就是默認按照用戶浏覽器設置的區域性進行顯示,因此你就可以把上述代碼放在Global.asax.cs文件的Application_BeginRequest方法中。這樣每次用戶對頁面發出請求時,我們的程序都會首先進行區域性設置。
2. 記住用戶的區域性設置
通過會話可以記住浏覽者的區域性設置或選擇。但是這個操作不能在Global.asax.cs文件中Application_BeginRequest方法中進行,因為那時會話還處於不可用狀態。如果你的站點並沒有提供讓用戶選擇語言的功能,那麼你也沒什麼必要記住用戶的區域性設置,只要按照上面介紹的在Global.asax.cs文件中Application_BeginRequest方法裡設置一下就可以了,不影響性能。這主要可以避免用戶在中途突然改變了浏覽器中語言的設置,而網站仍按照會話中存儲的區域性為用戶顯示頁面內容的沖突。
如果你提過了讓用戶選擇語言的功能,那顯然要在頁面程序中使用會話來記錄用戶的區域性選擇。因為從客戶端到服務器段的每次請求,服務器段都會開啟一個新的線程進行處理和響應。如果你的程序沒有記住客戶的選擇,那麼只能按照默認的區域性進行響應。
3. 資源管理器如何查找指定區域性的相應資源文件?
在執行取值操作時,也就是執行ResourceManager類的GetString方法時,資源管理器會按照當前線程的CurrentUICulture屬性去尋找相對應的資源文件。有如下幾種情況:
(1). 比如當前CurrentUICulture對應的區域性是en-US,那麼首先找MyResource.en-US.resx是否存在,如存在則從中取值;如不存在,則看MyResource.en.resx是否存在。
(2). 比如當前CurrentUICulture對應的區域性是en,因為en是非特定區域性的,那麼首先找其默認關聯的特定區域性en-US的資源文件MyResource.en-US.resx是否存在,如存在則從中取值;如不存在,則看MyResource.en.resx是否存在。
(3). 比如當前CurrentUICulture對應的區域性是en-GB,那麼首先找資源文件MyResource.en-GB.resx,如不存在,則看MyResource.en.resx是否存在,如存在則從中取值;如也不存在,則看en關聯的默認特定區域性en-US的資源文件MyResource.en-US.resx是否存在,如果此時MyResource.en-US.resx不存在,但是MyResource.en-CA.resx存在,則程序依然會拋出找不到合適資源文件的異常。
因此我們可以總結一下,當前線程CurrentUICulture對應的是特定區域性時,資源管理器優先查找此特定區域性對應的資源文件,如果沒找到,則去找其非特定區域性的資源文件,如果還沒找到,再去找其非特定區域性關聯的默認區域性的資源文件。當前線程CurrentUICulture對應的是非特定區域性時,資源管理器優先查找此非特定區域性對應的默認特定區域性的資源文件是否存在,如果不存在,則去看此非特定區域性對應的資源文件是否存在,如果也不存在則拋出異常。
4.如何處理未提供本地化支持的區域性?
如果站點沒有提供相應的資源文件支持用戶默認的區域性,那麼必須將其當前線程的CurrentUICulture轉化為你站點默認的區域性,比如en-US或zh-CN。轉化的時機有兩個:
一是當你在獲得Request.UserLanguages[0]時,用其與配置文件中預先設定的被支持的區域性進行比較,如果確認其為不被支持的,那麼立刻設置CurrentUICulture為默認區域性。
二是在使用ResourceManager的GetString方法進行取值的時候,使用try catch結構,捕獲MissingManifestResourceException異常,在異常處理中,首先將CurrentUICulture設為默認區域性,之後再重新使用GetString取值。
5. 通過Web.config設定站點默認的culture和uiCulture
<globalization requestEncoding="utf-8" responseEncoding="utf-8" uiCulture="zh-CN" culture="en-US"/>
如上所示:規定站點的默認culture為en-US(此處必須為特定區域性),uiCulture為zh-CN。
當然你也可以在每個頁面的Page標簽中進行逐頁設定:<@Page Culture=“zh-CN” UICulture=“en”>。這裡就不管web.config是如何設置的,頁面會按照Page標簽的設定進行顯示。
三.在ASP.Net2.0 中實現資源本地化
Asp.net2.0中為資源本地化提供了更加多樣的實現方法。我這裡著重談其與ASP.Net1.1中的不同之處。
1.通過Web.config設定站點默認的culture和uiCulture
在Asp.net1.1中使用web.config文件進行站點區域性設定的方法已經講過了,而在Asp.net2.0中其更加靈活。通常,您會想要站點中的所有頁面都符合相同的區域性設置。只需按如下所示在web.config中,為globalization元素的UICulture 和 Culture(區域性)屬性分配一個站點范圍的“auto”值, 注意這個“auto”值在Asp.net1.1中是不被接受的。<globalization uiCulture="auto" culture="auto" /> auto的意義在於ASP.Net 通過檢查浏覽器發送的 HTTP 標題獲取到的用戶首選區域性設置,並使用這個區域性設置站點的默認區域性,即當前線程的CurrentUICulture和CurrentCulture屬性。
除了自動設置以外,您還可以為 Asp.net 指定一個站點的默認區域性: <globalization uiCulture="auto:zh-CN" culture="auto:zh-CN" /> 注意:只有當ASP.Net無法找到 HTTP 標題來確定用戶的首選區域性,比如浏覽器的“屬性”->“語言”中沒有任何區域性設置完全是空的時候,auto後面設定的默認區域性才會生效。
在web.config中進行了globalization配置之後,你的應用程序不需要寫任何代碼,線程的CurrentUICulture和CurrentCulture就會按照在globalization元素中設置的uiCulture和culture屬性值獲得區域性設置。如果沒有進行globalization配置,則線程的CurrentUICulture和CurrentCulture就會默認為en-US。
2.使用Web.config文件跟蹤用戶的區域性選擇
在Asp.net1.1中,那些提供了區域性選擇的站點,一般使用會話來記錄用戶的選擇,以便在用戶每次對站點發出請求時,都按照用戶選擇的區域性對顯示內容進行本地化。在ASP.Net2.0中提供了另一個方法,那就是使用web.config文件來跟蹤用戶的區域性選擇。
您可以在web.config文件中添加一個名為 LanguagePreference 的基於字符串的配置文件屬性來支持匿名識別用戶區域性的功能。請注意anonymousIdentification元素的enabled屬性必須為“true”,否則匿名識別功能就不可用。
<anonymousIdentification enabled="true"/>
<profile>
<propertIEs>
<add name="LanguagePreference" type="string" defaultValue="auto" allowAnonymous="true" />
</propertIEs>
</profile>
下面我將闡述在ASP.Net2.0中如何針對LanguagePreference屬性編程。首先,可以寫一個PageBase類,它繼承自System.Web.UI.Page,並作為站點中所有頁面類的基類。這麼做的目的其實很簡單,就是為了將各個頁面中一些共同的處理過程提煉出來放到基類中,以減少代碼重復,提高可維護性。然後在PageBase類中寫如下代碼:protected override void InitializeCulture()
{
base.InitializeCulture();
string LanguagePreference = ((ProfileCommon)this.Context.Profile).LanguagePreference;
//該用戶首次訪問本站,Profile.LanguagePreference為空時,識別用戶浏覽器的語言設置
if(string.IsNullOrEmpty(LanguagePreference))
{
if (this.Context.Request.UserLanguages != null)
{
LanguagePreference = this.Context.Request.UserLanguages[0];
((ProfileCommon)Context.Profile).LanguagePreference = LanguagePreference;
}
}
else
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(LanguagePreference);
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(LanguagePreference);
}
}
System.Web.UI.Page類的InitializeCulture方法是在ASP.Net2.0中新加的,它為當前線程設置Culture和UICulture。頁面生命周期已被設計為InitializeCulture方法先於頁面的Init和Load運行。在上述代碼中,首先使用((ProfileCommon)this.Context.Profile).LanguagePreference;獲得當前LanguagePreference配置文件屬性的值,判斷其是否為空,也就是是否已經為用戶保存了區域性設置。如果為空,則從Http頭中獲取用戶的首選區域性設置,並通過((ProfileCommon)Context.Profile).LanguagePreference = LanguagePreference;保存用戶的首選區域性設置。如果不為空,說明已經保存了用戶的區域性設置,那麼使用這個區域性設置當前線程的CurrentUICulture和CurrentCulture屬性。
如果Web.config中定義了<globalization uiCulture="auto" culture="auto" />,那麼可以將上述代碼簡化為:protected override void InitializeCulture()
{
base.InitializeCulture();
string LanguagePreference = ((ProfileCommon)this.Context.Profile).LanguagePreference;
if(!string.IsNullOrEmpty(LanguagePreference))
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(LanguagePreference);
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(LanguagePreference);
}
else
{
((ProfileCommon)Context.Profile).LanguagePreference = Thread.CurrentThread.CurrentCulture.Name;
}
}
如果在站點中提供了讓用戶選擇區域性的功能,比如在站點的母版頁中放了一個選擇語言的列表,那麼可以通過以下語句來記住用戶對區域性的選擇:
protected void lstLanguage_SelectedIndExchanged(object sender,EventArgs e)
{
if (lstLanguage.SelectedValue != "Auto") //默認選項是Auto
{
Profile.LanguagePreference = lstLanguage.SelectedValue;
}
else
{
Profile.LanguagePreference = null;
}
Response.Redirect(Request.Url.AbsolutePath);
}
注意Response.Redirect(Request.Url.AbsolutePath);這行代碼,因為事件處理代碼是在Page_Load之後執行的,要是想讓頁面迅速發生變化必須執行重定向操作。
3. 在ASP.Net2.0中使用資源文件
在站點中建立全局資源文件的時候,VS.Net2005會自動建立一個App_GlobalResources文件夾專門來存放全局資源文件。所謂全局資源文件,也就是給站點中多個頁面文件或母版頁使用的資源文件。假設我們創建名為MyResource.resx和MyResource.zh-cn.resx的文件。在程序中我們可以使用以下代碼來獲得資源文件中的值:this.lblCountry.Text = Resources.MyResource.Country;
其中Country是資源文件中的鍵。顯然,這比ASP.Net1.1中從資源文件獲取值要容易很多。
這裡有兩個問題需要注意:第一,在創建一組具有相同根名稱的資源文件時,沒有區域性標示的文件必須建立,比如MyResource.resx是必須有的,其它如MyResource.en-gb.resx和MyResource.zh-cn.resx的建立是根據需要的。如果不建立MyResource.resx只建立了MyResource.zh-cn.resx等,則上述代碼中的Resources命名空間下就不會出現MyResource,因此上述代碼編譯無法通過。MyResource.resx中應該存放站點默認語言的內容,以備在找不到與當前線程CurrentUICulture匹配的本地化資源文件或在本地化資源文件中找不到相應鍵值時使用。Asp.net是以MyResource.resx文件中的鍵為准,假如在MyResource.resx中不存在Country鍵,而在MyResource.zh-cn.resx中存在Country鍵,那麼上述代碼在編譯時也會報錯。第二,ASP.Net在找不到相應區域的本地化資源時,不會報告任何異常,會自動從MyResource.resx文件中獲取值,但並不改變當前線程的CurrentUICulture。
在站點中建立局部資源文件的時候,VS.Net2005會自動建立一個App_LocalResources文件夾專門來存放局部資源文件。所謂局部資源文件,也就是給站點中單一頁面文件使用的資源文件。它的命名方式一般是Default.aspx.resx和Default.aspx.zh-cn.resx。現在我在Default資源文件中添加三個鍵Language、lblNavigation.Text和lblNavigation.ForeColor。其中我為Default.aspx.resx的lblNavigation.ForeColor設置blue,為Default.aspx.zh-cn.resx的lblNavigation.ForeColor設置red。在頁面文件中Default.ASPx中從局部資源文件裡獲得內容的方法如下有兩種:
(1). <asp:Label ID="lblLanguage" runat="server" Text="<%$ Resources:Language %>"></ASP:Label>
(2). <asp:Label ID="lblNavigaion" runat="server" meta:resourcekey="lblNavigation"></ASP:Label>
使用第一種方法時要注意使用符號$。使用第二種方法更加靈活,它可以一次性地為控件的很多屬性設定值。
在這裡仍然有問題需要注意:頁面默認的局部資源文件必須被建立,比如Default.aspx.resx是必須的,而Default.ASPx.zh-cn.resx則根據需要。如果你不建立默認的局部資源文件,而在頁面中卻要使用局部資源文件時,當使用第一種方法進行綁定時,出編譯錯誤;當使用第二種方法進行綁定時,不會出編譯錯誤,但是這些屬性的設置全都沒起作用,如同沒寫一樣。
4.顯示本地化圖像
顯示本地化圖像也是Asp.net2.0的新功能。在ASP.Net2.0中資源文件已經不僅限於string類型的鍵值對組合,它可以保存多種類型的文件。利用這一功能可以實現圖像的本地化。其實所謂本地化圖像,無非就是將給不同區域性准備的圖像放到不同的本地化資源文件中去。比如將LitwareSlogan.jpg放到MyResource.resx中,把LitwareSlogan.cn.jpg放到MyResource.zh-cn.resx中。
當不同本地化版本的全局資源文件中含有本地化版本的圖像文件時,您可以自定義一個名為 MyLocalImage.ashx 的處理程序文件,基於用戶的語言首選項來有條件地進行加載,代碼如下所示。
頁面中的調用方法:
<ASP:Image ID="Image1" runat="server" ImageUrl="~/ MyLocalImage.ashx" />
MyLocalImage.ashx 的處理程序的寫法:
public class MyLocalImage : IHttpHandler
{
public void ProcessRequest (HttpContext context)
{
context.Response.ContentType = "image/png";
string LanaguageReference = ((ProfileCommon)context.Profile).LanguagePreference;
if (!string.IsNullOrEmpty(LanaguageReference))
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(LanaguageReference);
}
Bitmap bm = Resources.Litware.LitwareSlogan;
MemoryStream image = new MemoryStream();
bm.Save(image,ImageFormat.Png);
context.Response.BinaryWrite(image.GetBuffer());
}
}
MyLocalImage.ashx 中定義的自定義處理程序類可使用您以前在自定義 InitializeCulture 方法中看到的類似邏輯,在從全局資源文件中檢索圖像文件以前,初始化當前線程的 CurrentUICulture 設置。您可能疑問為何在頁面的基類中已經設置了當前線程的CurrentUICulture,而在這裡還要重新設置,那是因為這裡的線程與基類中處理的線程不是同一線程。在該自定義處理程序正確初始化了 CurrentUICulture 設置之後,它即可通過 MyResource.resx 的強類型化資源類來訪問圖像文件。然後,便只需將圖像文件的數位編寫到 HTTP 響應流。