本文將從Asp.net實現資源全球化和在地化的基本概念入手,闡述在Asp.net1.1和Asp.net2.0中實現全球化和在地化的步驟、方法。
一.基本概念
1.為什麼要實現資源的在地化?
我們的網站可能為全球各個國家和地區的人所瀏覽,每個國家和地區的人都有自己的語言文化特徵。就拿咱們偉大的祖國為例,中國大陸用簡體中文,港澳台則使用繁體中文。另外各國對於貨幣、數字、日曆等資訊的表達格式各有不同,我們國家多使用年月日的格式,而美國則是月日年。諸如此類的差異林林總總,我就不多舉例了。為了給我們的網站瀏覽者更好的用戶體驗,我們應該提供一個全球化的解決方案,只要用戶選擇了他的語言和區域,站點就按照他的語言文化習慣來展現頁面信息,這個過程可以叫做本地化。
2.區域性、固定區域性、非特定區域性、特定區域性
區域性名稱遵循RFC 1766 標準,格式為“ 區域名稱 區域性識別符 語言-國家/地區 zh-CN 0x0804 中文-中國 zh-TW 0x0404 中文-台灣 zh-CHS 0x0004 簡體中文 zh-CHT 0x
7C |
04 | 繁體中文 | |
0x0009 | 英語 | en |
0x0409 | 英語-美國 | en-GB |
0x0809 | 英語 | -英國 |
uz-UZ- | Cyrl | 0x0843烏茲別克語 |
( | 西里爾語) -烏茲別克斯坦uz-UZ-Latn 0x0443 | ) -烏茲別克斯坦uz |
固定區域性不區分區域性。可以使用空字串("") 按名稱或按區域性識別碼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. CreateSpecificCulture (name);
CultureInfo culture = new CultureInfo(name);
二者的差異是,使用第一種方法,只能建立固定區域性或特定區域性的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
如上所示:規定站點的預設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中是不被接受的。
除了自動設定以外,您還可以為Asp.net 指定一個網站的預設區域性:
在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”,否則匿名識別功能就無法使用。
下面我將闡述在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中定義了
{
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).
(2).
使用第一種方法時要注意使用符號$。使用第二種方法更加靈活,它可以一次性地為控制項的許多屬性設定值。
這裡仍然有問題需要注意:頁面預設的局部資源檔案必須建立,例如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 的處理程序文件,基於使用者的語言首選項來有條件地進行加載,程式碼如下所示。
頁面中的呼叫方法:
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 響應流。