自定义多语言的实现
2013-03-07 21:24 Mike.Jiang 阅读(1891) 评论(2) 编辑 收藏 举报1背景
界面支持多种语言,在使用ASP.NET自带的多语言方案时遇到下列问题:
- 在做管理类的功能时,有添加、修改和查看页面,需要支持多语言的控件基本相同,但要维护多处,产生冗余(ASP.NET有共享的资源,但它是全局的,不能分 模 块,我们不能所模块的信息入在全局资源中);
- 在页面中必须要指定资源文件中的KEY;
- 当页面慢来慢多时,页面与资源的匹配实在难以维护;
所以我认为一个理想的支持多语言框架,需要有以下特性:
- 分模块解决数据冗余问题;
- 自动匹配页面与资源文件之间的联系;
- 易于维护,能通过页面快速定位到资源文件中;
2 资源存储于单个文件
先不将最后解决方案贴出来,我们先看看解决方案的第一个版本(在此版本中未解决分模块解决数据冗余的问题, 但更容易理解一些),此方案与ASP.NET原有方案类似,Resource中的KEY是控件的ID,这样我们在BasePage中直接循环页面中的控件,然后根据控件ID找资源。
类图:
代码如下(其中ResourceAccess用ResourceAccess1表示,BasePage用BasePage1表示,CommonResource_EN 和CommonResource_CN是资源文件):
View Code
public class ResourceAccess1 { private ResourceManager resourceManager = null; public ResourceAccess1(ResourceManager resourceManager) { this.resourceManager = resourceManager; } public string GetString(string name) { string str = this.resourceManager.GetString(name); if (string.IsNullOrEmpty(str)) { str = string.Format("【{0}】not exist", name); } return str; } } public class CommonResourceFactory { public static ResourceAccess GetResource(string lang) { ResourceAccess resourceAccess = null; switch (lang) { case "CN": { resourceAccess = new ResourceAccess(CommonResource_CN.ResourceManager); break; } default: { resourceAccess = new ResourceAccess(CommonResource_EN.ResourceManager); break; } } return resourceAccess; } } public class BasePage1 : Page { #region Mult Language private ResourceAccess resourceAccess; /// <summary> /// 是否重新加载语音 /// </summary> public bool ReRenderLanguage; private Literal lit; private Button btn; private LinkButton libtn; protected override void OnPreRenderComplete(EventArgs e) { if (!IsPostBack || ReRenderLanguage) { resourceAccess = ProductResourceFactory.GetResource("EN");//此处的语言EN,实际运行时,从SESSION中取出 foreach (Control c in Page.Controls)//页面大对象 { foreach (Control childC in c.Controls)//页面中的普通控件 { BindControlLanguage(childC); foreach (Control childCC in childC.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; 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; libtn.Text = resourceAccess.GetString(libtn.ID); } } #endregion }
说明:
- 因为有一个系列对象创建的工作(CommonResource_EN,CommonResource_CN),所以用了一个简单工厂方法;
- 因为原有简单工厂方法产生对象是ResourceManager, 在System.Resources 命名空间下,这样在引用这个工厂的地方就要引用这个空间,以及为了能在获取信息时做一些处理,所以我们加了一个代理,即ResourceAccess;
- BasePage, 在OnPreRenderComplete事件与BindControlLanguage方法,是处理控件与资源文件映射的功能。其中如果需要加一些多语言的控件,可以在BindControlLanguage中添加;如果要处理一些功能,如为Repeater类的控件,动态添加一行时需要重新加载多语音,可以设置IsRenderLanguage=true,否则默认仅在页面第一次加载时做多语言。
- 为什么在OnPreRenderComplete事件中做多语言,因为此事件在Page load 和控件事件之后,在呈现之前,这样我们就可以在控件事件中做一些设置处理。
3 资源存储于多个文件
此解决方案中解决了数据冗余的问题,并可以分模块来存储资源。
类图:
部分代码:
View Code
public interface IResourceFactory { ResourceAccess GetResource(string lang); } public class ProductResourceFactory : IResourceFactory { public ResourceAccess GetResource(string lang) { ResourceAccess resourceAccess = null; switch (lang) { case "CN": { resourceAccess = new ResourceAccess(ProductResource_CN.ResourceManager, CommonResource_CN.ResourceManager); break; } default: { resourceAccess = new ResourceAccess(ProductResource_EN.ResourceManager, CommonResource_EN.ResourceManager); break; } } return resourceAccess; } } public class ResourceAccess { private ResourceManager resourceManager = null; private ResourceManager commonResourceManager = null; public ResourceAccess(ResourceManager commonResourceManager) { this.commonResourceManager = commonResourceManager; } 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 string GetComString(string name) { string str = this.commonResourceManager.GetString(name); if (string.IsNullOrEmpty(str)) { str = string.Format("【{0}】not exist", name); } return str; } } public class BasePage : Page { public IResourceFactory BaseResourceFactory { set; get; } /// <summary> /// 是否重新加载语音 /// </summary> public bool ReRenderLanguage; private ResourceAccess resourceAccess; private Literal lit; private Button btn; private LinkButton libtn; protected override void OnPreRenderComplete(EventArgs e) { if (!IsPostBack || ReRenderLanguage) { if (BaseResourceFactory == null)// if BaseResourceFactory is null, then we see it as default "Name+ResourceFacotry" { string[] names = this.GetType().BaseType.Namespace.Split('.'); string name = names[names.Length - 1]; string resourceLayer = "BTS.Resource"; BaseResourceFactory = (IResourceFactory)Assembly.Load(resourceLayer).CreateInstance(string.Format("{0}.{1}ResourceFactory",resourceLayer, name)); } if (BaseResourceFactory != null) { resourceAccess = BaseResourceFactory.GetResource(loginInfo.Language); //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; 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; libtn.Text = resourceAccess.GetString(libtn.ID); } } }
说明:
- 因为要分不同的文件去存储资源信息,这样就产生了多系统的资源文件。并且在BasePage中处理这些代码相同,所以用了一个工厂方法,来解决多系列对象创建的工作。
- 在BasePage中的OnPreRenderComplete事件中,判断了如果BaseResourceFactory不存在,则根据默认命名空间创建ResourceFactory,即页面的命名空间名称默认为工厂名称(去掉ResourceFactory),这样页面就不用做任何的处理了。如果页面的命名空间不是默认的,则需要设置BaseResourceFactory.
- ResourceAccess中的GetString方法中的规则是:如果模块资源文件中未找到对应的资源,则去公共资源文件中去找。
希望对您有所帮助,如果您有更好的解决方法,请告诉我。。。