【凌風雨寒】解析Asp.net中資源本地化的實現

iDotNetSpace發表於2008-07-22

本文將從Asp.net實現資源全球化和本地化的基本概念入手,闡述在Asp.net1.1和Asp.net2.0中實現全球

化和本地化的步驟、方法。

一.基本概念

1.為什麼要實現資源的本地化?

        我們的站點可能為全球各個國家和地區的人所瀏覽,每個國家和地區的人都有自身的語言文化

特點。就拿我們們偉大的祖國為例,中國大陸用簡體中文,港澳臺則使用繁體中文。另外各個國家對於貨

幣、數字、日曆等資訊的表達格式各有不同,我們國家多使用年月日的格式,而美國則是月日年。諸如

此類的區別林林總總,我也就不多舉例了。為了給我們的網站瀏覽者更好的使用者體驗,我們應該提供一

個全球化的解決方案,只要使用者選擇了他的語言和區域,站點就按照他的語言文化習慣來展現頁面資訊

,這個過程可以叫做本地化。

2.區域性、固定區域性、非特定區域性、特定區域性

        區域性名稱遵循 RFC 1766 標準,格式為“-”,其中 是從 ISO 639-1 派生的由兩個小寫字母

構成的程式碼, 是從 ISO 3166 派生的由兩個大寫字母構成的程式碼。例如,美國英語為“en-US”。在雙

字母語言程式碼不可用的情況中,將使用從 ISO 639-2 派生的三字母程式碼;例如,三字母程式碼“div”用

於使用 Dhivehi 語言的區域。某些區域性名稱帶有指定書寫符號的字尾;例如“-Cyrl”指定西里爾語

書寫符號,“-Latn”指定拉丁語書寫符號。舉例:


區域性名稱

區域性識別符號

語言-國家/地區

zh-CN

0x0804

中文-中國

zh-TW

0x0404

中文-臺灣

zh-CHS

0x0004

簡體中文

zh-CHT

0x7C04

繁體中文

en

0x0009

英語

en-US

0x0409

英語-美國

en-GB

0x0809

英語-英國

uz-UZ-Cyrl

0x0843

烏茲別克語(西里爾語)- 烏茲別克

uz-UZ-Latn

0x0443

烏茲別克語(拉丁)- 烏茲別克


         固定區域性不區分割槽域性。可以使用空字串 ("") 按名稱或者


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.Curr

entThread.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"/>
如上所示:規定站點的預設culture為en-US(此處必須為特定區域性),uiCulture為zh-CN。

        當然你也可以在每個頁面的Page標籤中進行逐頁設定:

“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

中其更加靈活。通常,您會想

 

        除了自動設定以外,您還可以為 Asp.net 指定一個站點的預設區域性: 注意:只有當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

”,否則匿名識別功能就不可用。


   
       

allowAnonymous="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中定義了,那麼可以

將上述程式碼簡化為: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。

(1).
(2).

meta.:resourcekey="lblNavigation">


 使用第一種方法時要注意使用符號$。使用第二種方法更加靈活,它可以一次性地為控制元件的很多屬性設

定值。

        在這裡仍然有問題需要注意:頁面預設的區域性資原始檔必須被建立,比如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 響應流。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-406860/,如需轉載,請註明出處,否則將追究法律責任。

相關文章