一 前言
界面支持多種語言,在使用ASP.NET自帶的多語言方案時遇到下列問題:
- 在做管理類的功能時,有添加、修改和查看頁面,需要支持多語言的控件基本相同,但要維護多處,產生冗余(ASP.NET有共享的資源,但它是全局的,不能分 模 塊,我們不能所模塊的信息入在全局資源中);
- 在頁面中必須要指定資源文件中的KEY;
- 當頁面慢來慢多時,頁面與資源的匹配實在難以維護;
所以我認為一個理想的支持多語言框架,需要有以下特性:
- 分模塊解決數據冗余問題;
- 自動匹配頁面與資源文件之間的聯系;
- 易於維護,能通過頁面快速定位到資源文件中;
- 支持后台消息的多語言實現
- 支持前台JS消息的多語言實現
二 后台消息多語言的實現
在實現后台消息多言的實現時,主要利用ASP.NET的特性,通過修改當前線程的區域語言,來獲取對應版本的資源。因為ASP.NET在處理請求時,會使用一個單獨的線程來執行一次請求的所有代碼,所以我們只需要在一個地方重寫當前線程的語言信息,在其它任何地方都可以獲取當前語言版本的資源文件。重寫當前線程的區域語言的代碼如下:
protected override void InitializeCulture() { string language = "en";//默認為英文 if (Session["Language"] != null) language = Session["Language"].ToString(); Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(language); Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(language); }
在命名資源文件名時,規則如下:
- 默認語言的版本直接當正常命名,如命名OrderResource.resx
- 其它版本的資源文件名,應加上區域名稱, 如OrderResource.zh-cn.resx, OrderResource.en-us.resx.
將后台消息放在資源文件后,程序中只引用主的資源文件,這樣在線程的區域信息更改后,會自動的引用其它語言版本的資源。比如在程序中使用如下代碼:
Response.Write(OrderResource.FiledRequired);//當區域信息為英文時,輸出"The Filed is required!";當區域信息為中文時,輸出"字段必填!"
三 前台JS多語言的實現
前台的多語言實現,即在JS中需要彈出一些提示也需要多語言。前台多語言的實現方法實現有很多,在本例中,我們采用的統一的管理方式,把JS多語言信息同樣放在資源文件中,只不過在一個公共的地方,把資源序列成一個json對象,然后JS中便可以使用了。當然,在實現此功能時,需要做一些緩存的工作。主要代碼如下:
public static Dictionary<string, string> JsonResources = new Dictionary<string, string>(); //把資源文件序列化成JSON public static string GetJsonResource() { string key = string.Format("JSResource.{0}", System.Threading.Thread.CurrentThread.CurrentCulture.Name); if (!JsonResources.ContainsKey(key)) { JavaScriptSerializer serialzer = new JavaScriptSerializer(); ResourceSet rs = JSResource.ResourceManager.GetResourceSet(System.Threading.Thread.CurrentThread.CurrentCulture, true, true); string json = string.Format("[{0}]", serialzer.Serialize(rs.OfType<DictionaryEntry>().ToDictionary(x => x.Key, x => x.Value))); JsonResources.Add(key, json); } return JsonResources[key]; } protected override void OnPreRenderComplete(EventArgs e) { //輸出JSON對象到頁面 Page.ClientScript.RegisterStartupScript(Page.GetType(), "", "<script language='javascript'>var json=eval(" + ResourceCache.GetJsonResource() + ");JSResource=json[0];</script>"); }
在頁面中就可以使用JSResource對象了,如alert(window.JSResource.JSMsg);
四 頁面中的元素自動匹配資源文件
此功能有一個前提就是資源文件中的KEY為控件ID並且為頁面指定特定資源文件,這樣我們就可以為控件自動匹配了。在匹配的過程中,如果在指定的資源文件中未找到,那么會到公共的文件中查找。
注意:
1>為頁面指定特定資源文件,主要是用於多個頁面(模塊)共享一個資源,模塊的大小可以自己控制;
2>有一個公共的資源文件,在整個項目中,總有一些信息是各個模塊所通用的;
主要代碼:

public class ResourceFactory { public static ResourceAccess GetResource(System.Resources.ResourceManager resMgr) { return new ResourceAccess(resMgr, CommonResource.ResourceManager); } } public class ResourceAccess { private ResourceManager resourceManager = null; private ResourceManager commonResourceManager = null; public ResourceAccess(ResourceManager resourceManager,ResourceManager commonResourceManager) { this.resourceManager = resourceManager; this.commonResourceManager = commonResourceManager; } public string GetString(string name) { string str = this.resourceManager.GetString(name); if (string.IsNullOrEmpty(str)) { str = this.commonResourceManager.GetString(name); if (string.IsNullOrEmpty(str)) { str = string.Format("【{0}】not exist", name); } } return str; } } public class BasePage:Page { #region Mult Language protected override void InitializeCulture() { string language = "en";//默認為英文 if (Session["Language"] != null) language = Session["Language"].ToString(); Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(language); Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(language); } public System.Resources.ResourceManager PageResourceManager { set; get; } private ResourceAccess resourceAccess; private Literal lit; private Button btn; private LinkButton libtn; protected override void OnPreRenderComplete(EventArgs e) { //輸出JSON對象到頁面 Page.ClientScript.RegisterStartupScript(Page.GetType(), "", "<script language='javascript'>var json=eval(" + ResourceCache.GetJsonResource() + ");JSResource=json[0];</script>"); if (!IsPostBack) { //綁定頁面的文本 if (PageResourceManager != null) { resourceAccess = ResourceFactory.GetResource(PageResourceManager); //we do not use recursion, because it create too much stack. foreach (Control c in Page.Controls)//Large control of page,like HTML,Form { foreach (Control childC in c.Controls)//Normal control of page, like button, literal { BindControlLanguage(childC); foreach (Control childCC in childC.Controls) //Container control of page(if one contrl has child controls) { BindControlLanguage(childCC); foreach (Control childCCC in childCC.Controls) { BindControlLanguage(childCCC); foreach (Control childCCCC in childCCC.Controls) { BindControlLanguage(childCCCC); } } } } } } } base.OnPreRenderComplete(e); } private void BindControlLanguage(Control c) { if (c is Literal) { lit = (Literal)c; if (lit.Text == "") { lit.Text = resourceAccess.GetString(lit.ID); } } else if (c is Button) { btn = (Button)c; btn.Text = resourceAccess.GetString(btn.ID); } else if (c is LinkButton) { libtn = (LinkButton)c; if (libtn.Text == "") { libtn.Text = resourceAccess.GetString(libtn.ID); } } } #endregion }
五 最終效果
英文版本
中版本