Hello World
Spiga

本地化与Atlas对于本地化的支持

2006-09-22 19:18 by 老赵, 3882 visits
  周三我参与的项目的Beta版终于发布了,这个项目是对今年2月已经在美国上线的产品进行本地化工作。很奇怪,在Production环境中使用下来感觉比想象中要好很多,忐忑的心情放轻松了不少。虽然我那个项目没有用到Atlas,不过也就趁这个机会,简单讲一下本地化和Atlas对于本地化的支持吧。

一、什么是本地化

本地化,英文是Localization,缩写为L10n(与之类似的还有Globalization - G11n和Internationalization - I17n),其工作是将一个市场的产品进行某种方式的修改,目的在于向另一个市场的推广。一般来说,这两种市场的语言环境是不同的,因此“翻译”似乎就是本地化工作的重中之重,其重要程度已经达到了让某些人误以为本地化工作完全就是一个翻译工作。其实不然。正像刚才所说的,本地化是为了让一个产品在另一个市场得到推广,因此它需要做的事情还不少:

首先当然最重要的就是文字的翻译。一个产品会有大量的文字信息,在不同的市场中,对于这些文字自然应该翻译成符合该市场语言。翻译后语言应该要保持语句流畅和意义明确。需要注意的是,这里的“文字信息”不单指产品中的“文本”,图片中的文字和以图片方式显示的文字也一样需要被翻译。在ASP.NET中,对于这方面的本地化工作有着很好的支持。

其次是环境的转换。打个比方,在产品中很可能会有关于人的图片,则在美国市场应该显示为美国人,而在中国大陆市场则必须换作中国人——当然,如果用香港台湾韩国日本人可以吗?这……

还要注意的是语言习惯和环境属性。不同的市场往往会有不同的语言习惯和环境属性,比如时间格式、数字格式、货币单位、距离单位、时区、距离单位与标准长度单位的比等等。同样的信息在不同市场的显示出来时会有很大的不同。这里大部分的属性在.NET Framework的System.Globalization.CultureInfo类有描述。而本文也着重于Atlas对于这一点的支持。

不同的市场往往还需要运用不同的样式。姑且不说市场不同语言之间文字的阅读方向会不同,就拿同样是从左往右的两种不同语言,在样式上就会有所不同。比如拿英语与中文来说,首先他们如果用相同的字号,则中文会显得偏小。如果放大一些文字那么文字与边框,边框与边框,文字与图片之间的距离就会显得偏小,也需要一并改变。英文字母所组成的文章会有参差的感觉,而中文汉字基本上都是方块字,同样是文章汉字给人的感觉会比较密。单个英语单词中间不能换行,而中文任意两个汉字之间都能换行,其限制主要在在标点符号上。诸如此类等等。如果是大规模的产品,会涉及到许多产品的本地化工作,会专门成立一个小组用来研究各种语言文字下的样式,并提供开发人员全面的参考。这样保证了同一个,或者同一系列的产品,在不同市场会应用不同样式,却又保持了风格的美观和统一。

本地化工作还包括开发市场特定功能。不同的市场在很多地方会不相同,比如当地的法律限制了有些功能无法发布,有些功能必须修改和标注等等。为了更好地迎合用户,也有可能会为本地用户开发出新的功能。

其他例如还有广告商,赞助商等等,这里就不多说了。


二、Atlas对于本地化的支持

Atlas对于本地化工作的支持主要体现在客户端的Sys.CultureInfo对象。打开Atlas.js文件,可以发现如下的代码:
Sys.CultureInfo =
{
    
"Name" : "en-US"
    
"NumberFormat" : { ... }, 
    
"DateTimeFormat" : { ... }
};

代码比较长也比较无聊,因此就不完整贴出了。Sys.CultureInfo对象对于日期和时间的格式有着良好的支持,Atlas的客户端代码对于Date和Number的扩展都是基于Sys.CultureInfo对象的定义,具体代码也可以在Atlas.js文件中看到。那么Atlas是如何支持别的Culture的呢?我们新建一个aspx页面,加入一个ScriptManager,代码如下:
<atlas:ScriptManager ID="ScriptManager1" runat="server" />

然后在浏览器中打开这张页面。查看网页的源文件,可以发现这么一段代码:
<script src="atlasglob.axd" type="text/javascript"></script>

在浏览器里访问这个atlasglob.axd文件就可以查看这个“文件”的内容。它也是一段和上面相似的Sys.CultureInfo代码,只是内容会有所不同(如果相同的话那么访问atlasglob.axd?c=zh-cn就可以看到zh-cn的Sys.CultureInfo内容)。它后与Atlas.js文件加载,因此它的内容会覆盖Atlas.js文件中的定义。这就是Atlas对于本地化的支持。

在ScriptManager中有一个属性EnableScriptGlobalization,默认值为True。如果不需要这个功能,则可以把它设为False,那么页面就不会产生前面的<script />元素,加快加载速度。事实上,ScriptManager判断是否加载atlasglob.axd的逻辑会先判断EnabledScriptGlobalization的值,如果是True,并且Request.UserLanguages[0]不是“en-us”(en-us的Sys.CultureInfo已经被包含在Atlas.js文件中),则会在页面中引用atlasglob.axd文件。

那么atlasglob.axd文件是什么呢?它是如何工作的呢?首先,如果使用在web.config中可以看到下面这样定义:
<httpHandlers>
    
<add verb="*" path="atlasglob.axd" type="Microsoft.Web.Globalization.GlobalizationHandler" validate="false"/>
</httpHandlers>

这个定义表示将atlasglob.axd文件交由Microsoft.Web.Globalization.GlobalizationHandler处理,我们要它如何运行的,就必须看一下那个类的代码了。它的ProcessRequest方法代码:
 1public void ProcessRequest(HttpContext context)
 2{
 3      string culture = context.Request.QueryString["c"];
 4
 5      if ((culture == null&& (context.Request.UserLanguages != null))
 6      {
 7            culture = context.Request.UserLanguages[0];
 8      }

 9
10      if (culture == null)
11      {
12            culture = "en-us";
13      }

14
15      AtlasCultureInfo info = AtlasCultureInfo.Create(culture);
16      string script = JavaScriptObjectSerializer.Serialize(info);
17      context.Response.Write("Sys.CultureInfo = ");
18      context.Response.Write(script);
19      context.Response.Write(";");
20}

首先先查看Query String里“c”的值(因此前面通过atlasglob.axd?c=zh-cn可以访问到zh-cn的信息),如果没有提供c的值,那么则会查看Request对象里UserLanguages数组的第一个值。那么什么是UserLanguage?它反映了客户端的设定,我们打开IE,点击菜单“工具——Internet选项”,再点击“语言”按钮,会弹出语言首选项窗口,如图:


在这里可以添加删除UserLanguage,比如现在UserLanguage[0]是“zh-cn”。在上面的ProcessRequest的方法中,如果没有Query String和UserLanguage的设定,则默认使用en-us。接着将culture值传入AtlasCultureInfo的静态方法Create得到一个AtlasCultureInfo对象。然后将对象序列化输出,就得到了我们看到的结果:一个Sys.CultureInfo的定义。

AtlasCultureInfo的静态方法Create会使用用户传入的cultureName字符串,根据System.Globalization.CultureInfo类的静态方法CreateSpecificCulture得到一个CultureInfo对象。可以看出,这个cultureName必须代表了一个specific culture(例如zh-CN和en-GB),否则就会抛出异常(关于invariant culture,neutral culture和specific culture的区别和联系可以参考MSDN相关内容)。然后根据得到的CultureInfo对象使用AtlasCultureInfo的私有构造函数得到一个AtlasCultureInfo对象,这个对象会把传入的CultureInfo对象的Name,NumberFormat和DateTimeFormat复制给自己的相关属性。因此,序列化后的信息就是我们前面从atlasglob.axd访问得到的数据了。


三、为Atlas的本地化的支持自定义Culture Detection规则

分析了Atlas对于本地化的支持,是不是发现还缺了点什么?默认Handler会使用用户UserLanguage设定,那么如果用户没有UserLanguage设定该怎么办呢?如果我们要根据访问页面的Query String,用户当前的Cookie设定,或者用户帐户设定又该怎么办呢?如果我们只是想把默认的Culture设为zh-cn而不是en-us又该怎么办呢?其实我们需要的只是将自己的Culture Detection规则告诉Atlas。

还好我们能从atlasglob.axd的Query String下手。首先,我们先将ScriptManager的EnableScriptGlobalization属性设成False,以避免ScriptManager自动生成对于atlasglob.axd文件的script加载。代码如下:
<atlas:ScriptManager ID="ScriptManager1" runat="server" EnableScriptGlobalization="false" />

接着,我们可以用自己的方式在网页里添加对于atlasglob.axd的script引用了。注意,引用必须在Atlas.js的script引用(在网页源文件可以看到是一个对WebResource.axd的script引用)之后才能生效。当然最简单的办法就是通过ScriptManager使用RegisterScriptReference方法添加引用了。比如我们需要在Form_Load里注册,代码如下:
protected void Page_Load(object sender, EventArgs e)
{
    
string cultureName = GetUserCulture();
    
this.ScriptManager1.RegisterScriptReference("atlasglob.axd?c=" + cultureName);
}

打开网页后查看页面的源代码,就能发现下面的xml-script:
<script type="text/xml-script">
    
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
        
<references>
            
<add src="atlasglob.axd?c=en-GB" />
        
</references>
        
<components />
    
</page>
</script>

那么我们就来检验了它有没有生效,在浏览器地址栏里输入“javascript:alert(Sys.CultureInfo.Name)”并回车,弹出的MessageBox就显示了目前Sys.CultureInfo对象的名称。如图:


如果当前页面无法访问到ScriptManager对象,那么使用下面的代码也能完成相同的功能:
protected void Page_Load(object sender, EventArgs e)
{
    
string cultureName = GetUserCulture();
    ScriptManager.GetCurrent(
this.Page).RegisterScriptReference("atlasglob.axd?c=" + cultureName);
}

ScriptManager类的静态方法GetCurrent是取出作为参数的System.Web.UI.Page对象里的ScriptManager实例。当然,它的前提是当前Page对象里有ScriptManager对象。在Atlas类库里大量使用了这个方法,各个控件(比如UpdatePanel)都是通过这个方法获得当前页面的ScriptManager对象,然后通过各种逻辑改变页面的行为。


四、更好地控制Atlas本地化支持

我们已经做到了自定义的Culture Detection,足够了吗?事实上,这样的做法还有比较明显的缺陷。首先它将Culture Detection的逻辑放在了页面中,很可能会在多个地方出现类似的代码,不利于维护,我们应该集中的处理这段逻辑。再者,如果我们还想更进一步地操作Atlas的本地化支持,该怎么做呢?比如,我们希望修改部分的CultureInfo信息,甚至完全自定义,又该怎么办呢?

我们自然可以完全放弃atlasglob.axd而使用自己生成的Scripts,但是我更倾向于在Atlas的基础上作。毕竟,atlasglob.axd的使用是ScriptManager的built-in功能,我们不妨就在这个基础上扩展。我们知道,atlasglob.axd自身没有任何功能,因为它不是一个文件,根本不存在。唯一起作用的是Microsoft.Web.Globalization.GlobalizationHandler类,它生成了我们需要的所有CultureInfo代码。因此,我们为什么不使用自定义的Handler?

写这样一个Handler非常简单,唯一要关注的只有ProcessRequest方法。这样,我们就可以将所需culture在Handler里得到,然后和上面贴出的GlobalizationHandler.ProcessRequest代码完全一样地输出即可。

唔?AtlasCultureInfo是internal类,外界无法访问?没有关系,那么我们自己写一个:
public class JeffzAtlasCultureInfo
{
    
public DateTimeFormatInfo DateTimeFormat;
    
public string Name;
    
public NumberFormatInfo NumberFormat;

    
public JeffzAtlasCultureInfo(CultureInfo culture)
    
{
        
this.Name = cultureInfo.Name;
        
this.NumberFormat = cultureInfo.NumberFormat;
        
this.DateTimeFormat = cultureInfo.DateTimeFormat;
    }

}

然后在ProcessRequest方法里使用即可。

如果我们有特殊的要求,想修改部分Sys.CultureInfo信息又该怎么办呢?当然我们可以自己定义一个类并序列化输出,但是CultureInfo还是有不少信息,编写起来有着不小的工作量,而我们往往只需要做一点点修改。因此我推荐在输出代码的最后输出额外的修改代码即可。例如:
public void ProcessRequest(HttpContext context)
{
    
// the original output
    context.Response.Write("Sys.CultureInfo.NumberFormat.CurrencySymbol = '@';");
}

这样,Sys.CultureInfo对象的内容就会被修改了,非常地方便。

当我们写好了自己的Handler,只需再web.config文件中把Handler注册给atlasglob.axd文件即可。剩下的工作,就交给ScriptManager去处理吧。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

25 条回复

  1. 壮志
    *.*.*.*
    链接

    壮志 2006-09-22 20:39:00

    学习一下

  2. 老赵
    admin
    链接

    老赵 2006-09-23 02:04:00

    @壮志
    多谢支持。:)
    其实我最怕写出来的东西没有人要看,而且现在好像差不多就是这种情况……

  3. wwyy[未注册用户]
    *.*.*.*
    链接

    wwyy[未注册用户] 2006-09-23 03:39:00

    支持lz

  4. buliangdedeng
    *.*.*.*
    链接

    buliangdedeng 2006-09-23 08:04:00

    我是初学者,支持一下

  5. Dflying Chen
    *.*.*.*
    链接

    Dflying Chen 2006-09-23 08:32:00

    @Jeffrey Zhao
    没有示例程序,像教科书一样,当然不喜欢看了。

  6. 老赵
    admin
    链接

    老赵 2006-09-23 09:25:00

    @Dflying Chen
    啊?很像教科书吗?这篇没有做,但是我已经Step by Step。我以前几篇是有示例程序的,还是不行……

  7. 老赵
    admin
    链接

    老赵 2006-09-23 09:25:00

    @buliangdedeng
    @wwyy
    谢谢支持。:)

  8. S.Sams
    *.*.*.*
    链接

    S.Sams 2006-09-24 00:15:00

    还是有收获的,支持!

  9. 戴南
    *.*.*.*
    链接

    戴南 2006-09-24 01:44:00

    我还是喜欢看你的文章

  10. 老赵
    admin
    链接

    老赵 2006-09-24 12:55:00

    @S.Sams
    @戴南
    谢谢支持,我会更加努力的。:)

  11. ylk[未注册用户]
    *.*.*.*
    链接

    ylk[未注册用户] 2006-09-26 12:43:00

    支持一下!

  12. 老赵
    admin
    链接

    老赵 2006-09-26 15:27:00

    @ylk
    谢谢。

  13. SilentAcorn[未注册用户]
    *.*.*.*
    链接

    SilentAcorn[未注册用户] 2006-10-05 10:37:00

    没有G11n的说法,I17n也不对,应该是I18N, n也应该是大写。
    这两个缩写是头尾字母加上中间字母的个数。

  14. 老赵
    admin
    链接

    老赵 2006-10-05 22:22:00

    @SilentAcorn
    多谢纠正,我记得有G11N的说法,I18N是我数错了。:)

  15. cathsfz
    *.*.*.*
    链接

    cathsfz 2006-10-24 01:37:00

    为什么非单字节语言的格式化字符串会使用单引号作为字符界限符,例如中文的"yyyy'年'MM'月'dd'日'",能否解释下?以及在JavaScript中如何处理这种情况?

    例如在您给出的这个例子中就没把单引号处理掉:
    http://www.jeffzlive.net/Samples/CultureSeletor/Default.aspx

  16. 老赵
    admin
    链接

    老赵 2006-10-24 01:50:00

    @cathsfz
    我的例子只是为了掩饰一个控件的写法,并没有为了真正提供一个功能,因此只是简单地使用了Atlas的功能。:)
    至于出现您所说问题的原因,我认为是Atlas的实现问题。Atlas的Sys.CultureInfo只是在服务器端获得一个代表了Specific Culture的CultureInfo对象,并将其的一些一些属性,例如DateTimeFormat序列化输出。您提到的“yyyy'年'MM'月'dd'日”正是CultureInfo.DateTimeFormat.LongDatePattern的值,至于使用单引号分割我认为只是.NET Framework定义或者使用的规则。这个规则在.NET Framework中被正确使用了,因此没有出现Atlas的“2006'年'10'月'24'日' 1:49:26”这样的问题。
    至于是Atlas实现的问题,也谈不上如何解决了,正确地使用这个Pattern就行了。:)

  17. yy[匿名][未注册用户]
    *.*.*.*
    链接

    yy[匿名][未注册用户] 2006-11-10 17:05:00

    打个比方,在产品中很可能会有关于人的图片,则在美国市场应该显示为美国人,而在中国大陆市场则必须换作中国人——当然,如果用香港台湾韩国日本人可以吗?这……
    ---------------------------------
    台湾香港不算中国吗?

  18. 老赵
    admin
    链接

    老赵 2006-11-10 17:08:00

    @yy[匿名]
    sorry,我的口误。我在前面用了“中国大陆市场”后面就疏忽了。

  19. 蛙蛙池塘
    *.*.*.*
    链接

    蛙蛙池塘 2006-12-01 22:19:00

    先睡了,明天有空再看,你的文章要学习,你的精神也要学习,这种深入的精神确实需要好好学习。

  20. 老赵
    admin
    链接

    老赵 2006-12-02 00:08:00

    @蛙蛙池塘
    谢谢鼓励:)

  21. 蛙蛙池塘
    *.*.*.*
    链接

    蛙蛙池塘 2006-12-02 22:29:00

    这篇文章感觉没啥用呀,我都没看懂这本地化到底是怎么实现的。如果显示一个时间吧,客户端懂服务端获取这个时间,自动根据浏览器设置显示成英文还是会中文?如果有100多种国家,这些格式的对应信息客户端都要下载下来,一一对应后显示?比如说是en-us,那么en-us的时间格式,日期格式,货币这些规则客户端都下载下来吗?还是啥意思?

  22. 老赵
    admin
    链接

    老赵 2006-12-02 23:16:00

    会根据用户的设置选择时间的显示格式,其实本地化还是非常重要的。
    其实不会将100多个都下载下来的,需要什么则下载什么。:)

  23. reonlyrun
    *.*.*.*
    链接

    reonlyrun 2007-03-13 15:52:00

    不错

  24. 崔启亮[未注册用户]
    *.*.*.*
    链接

    崔启亮[未注册用户] 2008-05-21 12:10:00

    好文章,我可否转发到本地化世界网上?

  25. 老赵
    admin
    链接

    老赵 2008-05-21 15:06:00

    @崔启亮
    保留一个原链接就可以.

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我