对于有跨国业务的web系统来说,一般都需要提供多语言功能。然而在众多多语言方案里,如.net自带的Resource方式,都会在程序开发时增加程序员的额外负担,不易开发:
1.各种不同的地方实现多语言,如aspx的control绑定,js脚本,cs代码,procedure里的提示信息等,这些多语言实现方式各不相同,加重了开发难度。
2.在要实现多语言的代码中都要与获取语系文字的代码进行强耦合,不方便扩展和维护。如.net的Resource方式某个label实现多语言:
lblName.Text = Resources.Strings.name;
3.开发和维护程序时,程序员都必须同时打开source code和多语言资源文件,特别是在多人开发时,共享一个多语言文件,且对于共享的文字实难处理和同步。
以上这些只是简单地罗列了一下传统多语言开发时的困扰,那有什么方式解决这些问题,轻松实现多语言功能呢?
记得有人说过web编程,实际上就是字符串处理。
为什么呢?因为web,实际上就是Request和Response,而Request和Response就是字符串。在我们各种程序里,最终输出到Browser的都是html格式的字符串,因此,只要我们统一在程序最后一步输出html格式时,能够识别其中需要进行多语言转换的文字,将将其替换为当前设定的语言版本的文字就可以了。
在asp.net中,因为有一个Response.Filter的属性,让这一切变得很简单。
什么是Response.Filter?
简单地说,就是在经过层层转换后,最终asp.net要输出html,在输出的过程中,是将html放到一个管道(pipeline)里,然后在管道那头取出要发往客户端的html进行Response,asp.net提供Response.Filter属性,让你可以对经过的html进行相应的转换。
因此只要将多语言转换功能放在这里,并识别“中文”,将其转换为user设定的相应语系就可以完成了。
1. 首先要实现一个多语言Stream:
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
public class HttpMulLangStream : Stream
{
private Stream strSink;
private long lngPosition;
public HttpMulLangStream(Stream sink)
{
strSink = sink;
}
// The following members of Stream must be overriden.
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override long Length
{
get { return 0; }
}
public override long Position
{
get { return lngPosition; }
set { lngPosition = value; }
}
public override long Seek(long offset, System.IO.SeekOrigin direction)
{
return strSink.Seek(offset, direction);
}
public override void SetLength(long length)
{
strSink.SetLength(length);
}
public override void Close()
{
strSink.Close();
}
public override void Flush()
{
strSink.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return strSink.Read(buffer, offset, count);
}
// 关键代码.
public override void Write(byte[] buffer, int offset, int count)
{
//读出写的文字
byte[] data = new byte[count];
Buffer.BlockCopy(buffer, offset, data, 0, count);
string inputstring = Encoding.UTF8.GetString(data);
//找出汉字(对于复杂一些的,可以设定固定识别方式,如使用这些标记识别[@多语言文字],并按设定的语言作转换
inputstring = Regex.Replace(inputstring, "[\u3000-\u9fff]+", new MatchEvaluator(capText));
//将翻译后的语言写入response
byte[] newdata = Encoding.UTF8.GetBytes(inputstring);
strSink.Write(newdata, 0, newdata.Length);
}
private string capText(Match m)
{
//简单的替换,实际开发过程可以从xml文件,Resouce资源,进行缓存等等替换策略
string x = m.Value;
if (x.Equals("账号"))
return "Account";
else if (x.Equals("登录"))
return "Login";
else if (x.Equals("你的名字"))
return "Your Name";
//如果没找到,原样返回
return x;
}
}
2. 其次要在Global.asax中设定Response.Filter
<%@ Application Language="C#" %>
<%@ Import Namespace="System.IO" %>
<script runat="server">
//在每次Request之前设定好Response.Filter属性
void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
Response.Filter = new HttpMulLangStream(Response.Filter);
}
</script>
3. 测试代码 test.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="Login" %>
<!DOCTYPE html PUBLIC "-//W
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>未命名页面</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="Label1" runat="server" Text="账号"></asp:Label>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="登录" /></div>
</form>
</body>
</html>
使用这种方式对于程序员开发系统基本上是零侵入,程序员基本感受不到多语言功能增加需要的额外工作。
上面只是一个简单的Demo,实际运用中,还有几点需要说明:
1.识别:
不能靠单纯地识别汉字,来进行转换。因为在有的地方,确实不需要进行转换,如数据内容。比如某个user的姓名叫张三,不管在英文还是中文,都显示张三。这时候就需要对程序开发加入一点侵入,如程序员在开发时,需将多语言转换的字符串作特别标记,如[@姓名:],这样在识别时,就以[@.*]来作为识别方式。
2.转换:
转换是这种方式最成功的地方,一是效率,可以利用static或cache等方式缓存资源,提高性能。二就是,对于没有做翻译的地方,可以原样输出。这样有几个好处,可以先开发程序,最后增加资源文件来进行多语言转换。二就是有的确实不需要多语言转换时,可以不提供资源文件,它就不会转,而不需要额外写程序去指定。
3.通用性:
对于一个多语言方案来说,通用性比较重要,有的地方需要对单独的js文件进行多语言转换,这时就要将js文件在IIS中加入到asp.net引擎处理。而更简单的方式就是直接把js文件的后缀名改为aspx。
至于从DB中的程序过来的字符串,以及嵌入在aspx中的script,因为都最终会通过aspx输出到页面上,所以不用额外处理。
4.重用:
透过asp.net的HttpModule特性,将多语言实现方案进行单独封装,然后在web.config中配置就可动态装载多语言功能了。