ASP.NET 2.0 本地化技术之研究(二)

ASP.NET 2.0 本地化技术之研究的回复中提到了以下两点:

1.这只是单个页面的切换,如何做整个站点的切换呢?( hjh
2.关于如何将资源直接显示……既然控件能够将嵌入dll的资源直接显示,不知道网站能否也将嵌入资源直接调用WebResource显示呢?(Cat Chen

由于不是一两句可以说清,所以再开一篇仔细讲一下。


内容列表:

1.整站本地化资源的切换
2.使用ProFile保存用户选择的语言
3.关于WebResource的使用
4.代码下载

1.整站本地化资源的切换

在上一篇里我们讲到,可以通过重载页面的InitializeCulture函数,在其中切换当前线程的CurrentUICulture和CurrentCulture来实现本页的资源切换。那么整站呢?总不能在每个页面里都写上这几句吧。。。

首先,我想到的是使用MasterPage,如果在MasterPage里加上资源切换的代码,那么所有使用该母板的页面都具备这种能力了吧,呵呵(想得不错)。但如意算盘打破了,MasterPage是使用@Master来声明的,根本和Page是两个继承路线,所以MasterPage里没有InitializeCulture这个虚函数!

没办法,想到了另一个解决方案,创建一个从System.Web.UI.Page继承下来的基类,在其中实现资源切换,而站内所有页面的实现类都从该类继承。OK,就这么办!

打开上一篇完成的网站,选中网站,右键在弹出菜单中点击[添加ASP.NET文件夹]-[App_Code]。
选中该文件夹,右键点击[添加新项],在弹出式窗口中选择“类”,命名为LocalizedPage.cs,点击[添加]完成,如图所示:


编辑LocalizedPage.cs,代码如下:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Threading;
using System.Globalization;

/// <summary>
/// 所有需要本地化资源切换的页面基类
/// </summary>
public class LocalizedPage : System.Web.UI.Page 
{
    
protected override void InitializeCulture()
    {
        String s 
= Request.QueryString["currentculture"];
        
if (!String.IsNullOrEmpty(s))
        {
            
//UICulture - 决定了采用哪一种本地化资源,也就是使用哪种语言
            
//Culture - 决定各种数据类型是如何组织,如数字与日期
            Thread.CurrentThread.CurrentUICulture = new CultureInfo(s);
            Thread.CurrentThread.CurrentCulture 
= CultureInfo.CreateSpecificCulture(s);
        }
    }
}

编辑Image.aspx.cs,去除其重载的InitialzeCulture()函数,将其基类改为LocalizedPage,代码如下:
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Threading;
using System.Globalization;

public partial class Image : LocalizedPage
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        System.Drawing.Bitmap img 
= (System.Drawing.Bitmap)GetGlobalResourceObject(
            
"LocalizedText",
            CultureInfo.CurrentCulture.Name.ToLower().Replace(
"-""_"+ "_flag");

        System.IO.MemoryStream ms 
= new System.IO.MemoryStream();
        img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);

        Response.ClearContent();
        Response.ContentType 
= "image/jpeg";
        Response.BinaryWrite(ms.ToArray());

        img.Dispose();
        ms.Dispose();
        ms.Flush();
    }
}

运行网站,可以看到由Image页面负责输出的图片,可以按选中的语言正常切换。

再编辑Default.aspx.cs,调整代码如下:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Globalization;
using System.Threading;


public partial class _Default : LocalizedPage 
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        String s 
= Request.QueryString["currentculture"];
        Image1.ImageUrl 
= "~/Image.aspx?currentculture=" + s;
    }

    
protected void Button1_Click(object sender, EventArgs e)
    {
        Localize1.Text 
= (String)GetLocalResourceObject("Label1Resource1.Text"+ " " +
            (String)GetGlobalResourceObject(
"LocalizedText""Msg1");
    }
}

运行程序,一切正常。

总结:利用这种方式,新建页面时只需修改其继承的基类为LocalizedPage即可。对于已经建好的站点,同理,也可以很方便的加入资源切换的支持。

2.使用ProFile保存用户选择的语言

前面我们是通过URL传参的方式将用户选择的语言传递到各个页面,感觉不爽。那么使用Session呢?听上去不错,但是你没听过ProFile吗?这可是ASP.NET 2.0的新特性之一呀!与Session一样ProFile是针对一个特定用户的,但ProFile更好用,因为它有以下特点:
  1)可存储,默认是保存在SQL Server Express中,但通过实现Provider可以将它存储到任何地方
  2)支持匿名使用,在用户认证后还可以迁移到认证用户中(具体实现方法据说是非常的“巧妙”)
  3)支持生成和管理报告
不错吧,那么我们就用ProFile来保存用户选择的语言信息吧。

注意:由于ProFile默认是由SQL Server Express来存储的,所以要保证你的VS2005已经安装该模块。

编辑Web.Config,在system.web节点下增加以下配置
<configuration>
    
<system.web>
        
<anonymousIdentification enabled="true"/>
        
<profile>
            
<properties>
                
<add name="LanguagePreference" type="string" 
                    defaultValue
="Auto" allowAnonymous="true" />
            
</properties>
        
</profile>
    
</system.web>
</configuration>

同时加入的anonymousIdentification节,是为了让系统自动为匿名用户生成唯一标识。另外的allowAnonymous="true"表明LanguagePreference属性可以被匿名用户访问。

编辑Default.aspx,切换到[设计]视图,删除原来用于切换语言的两个链接“中文(中国)”和“English(USA)”。从工具箱中拖一个DropDownList控件到页面上,设置其AutoPostBack属性为True(切记!),然后编辑它的Items属性,如图所示:


中文(中国)的Value为zh-cn,英文(美国)的Value为en-us。

编辑Default.aspx.cs,为DropDownList编写SelectedIndexChanged事件的实现,代码如下:
    protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
    {
        Profile.LanguagePreference 
= DropDownList1.SelectedValue;
        Response.Redirect(Request.Url.AbsolutePath);
    }

修改Page_Load的实现,代码如下:
    protected void Page_Load(object sender, EventArgs e)
    {
        String s 
= Profile.LanguagePreference;
        Image1.ImageUrl 
= "~/Image.aspx";
    }

然后再编辑LocalizedPage.cs,代码如下:
    protected override void InitializeCulture()
    {
        String s 
= (String)Context.Profile.GetPropertyValue("LanguagePreference");
        
if (!String.IsNullOrEmpty(s) && (s != "Auto")) 
        {
            
//UICulture - 决定了采用哪一种本地化资源,也就是使用哪种语言
            
//Culture - 决定各种数据类型是如何组织,如数字与日期
            Thread.CurrentThread.CurrentUICulture = new CultureInfo(s);
            Thread.CurrentThread.CurrentCulture 
= CultureInfo.CreateSpecificCulture(s);
        }
    }

注意:我们在Default.aspx.cs之所以可以直接使用Profile来访问用户个人信息,是因为ASP.NET在页面运行时自动为我们生成了一个继承自System.Web.Profile.ProfileBase的ProfileCommon类。而在App_Code目录的代码开始执行时,ProfileCommon还没有生成,更别提Profile了。所幸的是,我们可以通过上面代码的方式访问到用户个人信息(真的研究了好长时间。。。

运行程序,切换语言,运行效果如图所示:
中文(中国)


英文(美国)


注意哟,退出程序后,再次运行,所有页面将按你上次设置的语言显示,Profile真的很不错。

3.关于WebResource的使用


ASP.NET是在运行时将全局资源和本地资源进行编译,象.aspx文件一样,所以我们只需要将.resx文件xcopy到正在运行的WEB服务器上,即可为新语言提供本地化的支持。但如果我们开发了一个WEB控件,其中使用到了一些资源(如图片),那就要求我们必须将DLL和资源文件一起部署到WEB服务器上,比较麻烦。

ASP.NET开发团队考虑到了这一点,现在我们可以在网站里使用资源DLL,这样在发布DLL时资源也同时被分配了。该技术是通过在控件代码里调用GetWebResourceUrl方法,这个方法指向一个名为WebResource.axd的内置HTTP处理程序的URL。通过加载一个名为AssemblyResourceLoader的HttpHandler类,ASP.NET运行时响应WebResource.axd的请求,返回指定资源的URL。

该技术有以下缺点:
  1)只能在面向 ASP.NET 2.0 网站的 DLL 项目内使用该技术,而无法网站内直接使用该技术
  2)该技术实际上并不支持任何形式的本地化(说到这,感觉把这家伙写到本随笔里不太合适。。。管它呢,先写完再说!

选中网解决方案,右键在弹出式菜单里点击[添加]->[新建项目],在弹出窗口选中Visual C#项目下的类库,并设好保存路径,如图所示:


点击确定,删除Class1.cs。选中ClassLibrary1项目,右键在弹出菜单里点击[添加]->[新建项],在弹出窗口选择“WEB 自定义控件”,如图所示:


点击[添加],现在解决方案里已经包含两个项目了,如图所示:


右键ClassLibrary1项目,选择[添加]->[现有项],随便找一张图片(我使的是园子的logo,嘿嘿),如图所示:


点击[添加],右键刚添加的图片点击[属性],将“生成操作”设为“嵌入的资源”,如图所示:


编辑WebCustomControl1.cs,代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

[assembly: WebResource(
"ClassLibrary1.logo.gif""image/gif")]

namespace ClassLibrary1
{
    [ToolboxData(
"<{0}:WebCustomControl1 runat=server></{0}:WebCustomControl1>")]
    
public class WebCustomControl1 : WebControl
    {
        
protected override void RenderContents(HtmlTextWriter output)
        {
            output.WriteBeginTag(
"image");
            String url 
= Page.ClientScript.GetWebResourceUrl(GetType(), "ClassLibrary1.logo.gif");
            output.WriteAttribute(
"src", url);
            output.WriteEndTag(
"image");
        }
    }
}

编辑网站的Default.aspx文件,切换到[设计]视图,将工具箱的ClassLibrary1面板里的WebCustomControl1控件拖到页面上,运行程序,效果如图所示:



4.代码下载

下载地址:http://www.cnblogs.com/Files/reonlyrun/WebLocalizationTaste2.rar
posted @ 2007-03-16 05:23 Clark Zheng 阅读(6232) 评论(40)  编辑 收藏 所属分类: A. .NET

  回复  引用  查看    
#1楼 2007-03-16 08:06 | finesite      
谢谢 正学习
  回复  引用  查看    
#2楼 2007-03-16 08:09 | Leepy      
个人比较倾向第一种方法,因为我的项目是这么做的:)
  回复  引用  查看    
#3楼 2007-03-16 08:46 | weizhuangzhi [未注册用户]
不错
  回复  引用  查看    
#4楼 2007-03-16 09:30 | finesite      
能留下你的其中一个联系方式吗?谢谢 
  回复  引用  查看    
#5楼 [楼主]2007-03-16 09:49 | reonlyrun      
@Leepy
你说的第一种方法是哪个?然后URL传参,然后在每个页面里都写设置CurrentUICulture和CurrentCulture的代码?

@finesite
见左上角的公告,有我的联系方式。
  回复  引用  查看    
#6楼 2007-03-16 11:22 | fhmsha [未注册用户]
我一般用Page_PreInit和cookie
  回复  引用  查看    
#7楼 [楼主]2007-03-16 11:52 | reonlyrun      
@fhmsha
个人感觉还是用InitializeCulture和ProFile舒服
  回复  引用  查看    
#8楼 2007-03-16 13:13 | 喝酒的猫      
学习中!
  回复  引用  查看    
#9楼 2007-04-18 16:46 | lullaby [未注册用户]
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
Profile.LanguagePreference = DropDownList1.SelectedValue;
Response.Redirect(Request.Url.AbsolutePath);
}

我觉得 Response.Redirect 没有必要了。你这样页面实际上是提交了一次,跳转一次。(1.AutoPostback, 2.Redirect)

我觉得不用 Response.Redirect 也可以。因为当页面执行到重写的
InitializeCulture 方法时, Request里已经有了所需要的信息了。

如下:
protected override void InitializeCulture()
{
string s = Request["DropDownList1"] as string;
if (!string.IsNullOrEmpty(s))
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(s);
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(s);
}
}
这样的话也不需要Profile了。。。 当然毛病在你得提前知道 DropDownList1 的 ClientID。 因为在 InitializeCulture 的时候, 服务端还没有为你组装好 DropDownList1 的实例。
  回复  引用  查看    
#10楼 [楼主]2007-04-18 16:59 | Clark Zheng      
@lullaby
Profile的作用是可以在你下次登录时还可以记住你选择的信息
  回复  引用  查看    
#11楼 2007-04-23 13:48 | lzm [未注册用户]
碰到一个奇怪的问题,我要在IE语言里把"英语(美国) en-us"优先级设为首位才能正常显示"美国国旗"图片,而"中国国旗"图片则没有影响,不知道这是那里出问题了.

  回复  引用  查看    
#12楼 2007-04-23 15:01 | lzm [未注册用户]
找到原因了,原来生成图片的页面忘了InitializeCulture()了.
  回复  引用  查看    
#13楼 2007-04-23 15:04 | lzm [未注册用户]
只是用第二种方法还是要在每个页面重写InitializeCulture(),而且还要重定向页面,有没有更好的方法啦.
  回复  引用  查看    
#14楼 2007-04-26 10:36 | Kagilo [未注册用户]
写的不错, 两篇文章都看了。。

想问下,, 这样只是服务器控制可用啊,

如果我网站很少用服务器控件,(可能都DIV+CSS)布局的,
所有会尽量少用服务器控件,

那全球化该怎么办呢?
  回复  引用  查看    
#15楼 2007-05-31 11:14 | zhu [未注册用户]
为什么只有C#的代码?????
  回复  引用  查看    
#16楼 [楼主]2007-06-01 08:58 | Clark Zheng      
@lzm
暂时没想到更好的办法

@Kagilo
那可能就要自己做了,我主要写的是针对ASP.NET的本地化,因为在2.0里这是它的一个新特性

@zhu
sorry,我很少使VB.NET或C++.NET,所以只写了C#的代码

  回复  引用  查看    
#17楼 2007-06-07 13:42 | Dennis [未注册用户]
protected override void InitializeCulture()
{
string s = Request["DropDownList1"] as string;
if (!string.IsNullOrEmpty(s))
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(s);
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(s);
}
}

谢谢,学习了!
  回复  引用  查看    
#18楼 2007-06-13 15:57 | csi      
写的很清晰,昨天在一个英文网站看过这方面的介绍,还是你的写的好,tks
  回复  引用  查看    
#19楼 2007-06-28 20:49 | 乖_乖_同      
用session 做比较简单吧。不过session 有时间限制,
  回复  引用  查看    
#20楼 2007-07-31 15:50 | gao [未注册用户]
有什么办法可以给布置了全局资源的控件再添加一个值,使得没有了全局资源后控件还有一个默认的值??
  回复  引用  查看    
#21楼 2007-08-09 19:22 | myds [未注册用户]
太好了,谢谢,学习学习
  回复  引用  查看    
#22楼 2007-08-14 11:05 | zhou [未注册用户]
我有好多疑问请帮我解答一下 :

如果我想在Literal控件中添加 动态从数据库中读取的数据时,该怎么做呢?难道数据库中还要有2张表是不同语言的么?
现在在资源文件中的内容是固定的 在开发时就写好的.那么如果我后期想更改内容一定要重新发布么?有没有什么办法在后台管理工具中是直接写入的?

谢谢!
  回复  引用  查看    
#23楼 [楼主]2007-08-14 11:17 | Clark Zheng      
@zhou
1.如果你的Literal里显示的是文档形数据,如新闻、文章等当然是要从数据库里读取不出语言的文章。这里谈到的本地化技术一般都是按钮、标签、提示和导航这些东西,简单来说是界面元素而不是内容元素

2.资源文件的修改可以自己开发,使用System.Resources命名空间里的类
  回复  引用  查看    
#24楼 2007-08-15 08:56 | zhou [未注册用户]
那么如果我想把中文的翻译成英语的还要自己写么?没有可以将数据库中信息直接转换的么?

我想给网站做个后台管理系统,那如果是双语言的,客户就要填写两遍,很不人性化呵呵

谢谢你
  回复  引用  查看    
#25楼 [楼主]2007-08-15 09:04 | Clark Zheng      
@zhou
不知道你的客户是什么,反正内容形式的都需要自己来翻译,到目前为止好象没有一个能用的全文翻译软件吧^_^
  回复  引用  查看    
#26楼 2007-08-15 09:24 | zhou [未注册用户]
那我明白了  谢谢您

  回复  引用  查看    
#27楼 2007-09-17 17:16 | 月下骑士      
文章太好拉,正需要那
  回复  引用  查看    
#28楼 2007-09-28 10:45 | xiangpiren [未注册用户]
文章写的太好了,我终于明白资源文件了, 我借用到我的博客上了, 谢谢
  回复  引用  查看    
#29楼 2007-10-02 16:53 | stlh [未注册用户]
为什么不用cookie?我觉得cookie正是应该用在这种地方的吧
  回复  引用  查看    
#30楼 2007-10-13 13:16 | cby [未注册用户]
这个多语言我们05年初就已实现了,并且比你的还好^_^
  回复  引用  查看    
#31楼 [楼主]2007-10-15 08:50 | Clark Zheng      
@cby
不敢称我的实现有多好,只不过是一个在asp.net2.0下,实现标准本地化网站的一个例子而己
  回复  引用  查看    
#32楼 2007-10-20 17:57 | 简单·快乐 [未注册用户]
profile方法相当于session
这种方法太好用了
做界面很好

@cby
难道有更好的方法?

  回复  引用  查看    
#33楼 2008-01-03 20:19 | 陆培 [未注册用户]
你好!

非常喜欢你写的文章!
我现在有一难题也是关于本地化技术的,我不知道如何本地化我的sitemap和Masterpage,希望你能给我提供相关的源码-要能运行的。

很希望和你取的联系,我在德国墨尼黑。
  回复  引用  查看    
#34楼 2008-01-17 17:13 | cwj [未注册用户]
我整个网站很多DIV里都是静态的文本,那要怎么实现可以变成英文.
  回复  引用  查看    
#35楼 [楼主]2008-01-17 17:36 | Clark Zheng      
@cwj
静态文本。。。估计只能做两套页面了

  回复  引用  查看    
#36楼 2008-04-01 18:58 | 林子      
方法不错
  回复  引用  查看    
#37楼 2008-04-03 11:33 | 风卷残云 [未注册用户]
请教一下。GRIDVIEW中的HEADTitle的文本怎么才能本地化。我试过用隐式的不行。不知道要怎么弄,麻烦指导一下。有时间回到我邮箱,万分感谢。
  回复  引用  查看    
#38楼 2008-04-04 16:43 | 崔启亮 [未注册用户]
这篇介绍网站国际化和本地化的技术文章内容和步骤都很清楚,值得学习。

为了使耕读的读者阅读,本文已经被收录到“本地化世界网”的“国际化”栏目下的“网站国际化”板块中了。

希望作者不断发表更多的关于网站国际化和本地化方面的文章,国内这方面的原创技术文章太少了。
  回复  引用  查看    
#39楼 2008-04-18 10:57 | 云中帆 [未注册用户]
如果在一处选择了语言,则整个网站全部改为相同的语言,而非单个页面的修改.
代码如下:
public class LocalizedPage:System.Web.UI.Page
{
private static string s;
protected override void InitializeCulture()
{
string ls = Request.QueryString["currentculture"];
switch(ls)
{
case "zh-cn":
{
Session["Language"] = ls;
break;
}
case "en-us":
{
Session["Language"] = ls;
break;
}
case "ja":
{
Session["Language"] = ls;
break;
}
}

s = Convert.ToString(Session["Language"]);

// s = Request.QueryString["currentculture"];
// s =Convert.ToString(Session["Language"]);

if (!String.IsNullOrEmpty(s))
{
//UICulture - 决定了采用哪一种本地化资源,也就是使用哪种语言
//Culture - 决定各种数据类型是如何组织,如数字与日期
Thread.CurrentThread.CurrentUICulture = new CultureInfo(s);
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(s);
}
}

}