首页 > Silverlight, 技术随笔 > 莫名其妙的Silverlight资源文件引用问题

莫名其妙的Silverlight资源文件引用问题

Silverlight, 技术随笔

last update:2012-8-28

问题描述

最近项目中遇到一个和资源文件有关的诡异问题。项目中的Silverlight应用程序会根据当前Url中的特定参数来切换当前线程的Culture以及UICulture,以此来决定使用哪一个资源文件(关于Silverlight中如何正确打包资源,参考Silverlight 2 RTM 多国语言支持)。

在其他机器上运行没有问题,传入zh-Hans和en-US界面都能正确显示相应语言的文字。但是在我的系统上(Win7),不管传入什么语言字符串参数,最终UI上都是显示英文,而不是本地化后的文本。

问题分析

打开工程看了一下,在资源文件夹下只定义了两个资源,一个是主资源文件XXX.resx,一个是英文Culture的资源文件XXX.en.resx。编译之后,主资源文件会被打包到主程序集中,而其他资源则被编译成单独的程序集,称为卫星程序集(Satellite Assembly)。

我们的Silverlight程序中写死了一个简单粗暴的逻辑,一旦判断传入的语言字符串不是英文,那么就直接使用zh-Hans。也就是说,就算Url参数中写了fr,最终按说也应该显示中文。

分析加调试弄了好一会之后,我判定应该不是程序代码的bug。因为在某些人的机器上是正常的,而在我的机器还有另外一个同事的机器上却不正常,并且问题是可重现的。这说明了问题和本地环境有关。

清缓存,清Silverlight独立存储之后发现问题依然存在。难道是操作系统的问题?!我用的是win7,同事用的是win2008,都是高版本的windows,而其他人用的不是xp就是2003。为了证实我的想法,我又找了一台2008的机器,测试结果和我机器上的一样。看来很有可能问题就在这。可是,为什么呢?

为了查找问题的原因,我重新查阅了一下Silverlight使用资源文件的相关材料(和DotNet其实差不多)。

Culture分类和组织形式

在了解Silverlight资源查找方式之前,我们首先需要知道,DotNet中的Culture分为三类:Invariant Culture,Specific Cultures以及Neutral Cultures。

其中Invariant Culture(语言无关)是微软根据英语语言抽象出来的一套语言规则。在DotNet的Globalization命名空间下有很多API都需要显式传入一个CultureInfo实例,这是因为Culture本身定义了很多规则,用于字符串比较,日期格式化等操作。为了在不同语言环境下,能够得到一致的输出,微软定义了这样一个特殊的语言。在C#中,我们可以通过以下三种方式来构造Invariant Culture:

// 静态实例
Console.WriteLine(CultureInfo.InvariantCulture.LCID);
// 空字符串构造
Console.WriteLine(new CultureInfo("").LCID);
// 显式指定Locale Identifier
Console.WriteLine(new CultureInfo(127).LCID);

需要注意,虽然Invariant Culture是基于英语设计出来的,但是它和en这个Culture是完全不同的。

Specific Cultures(具体语言)则是和单一地区或国家紧密相关的语言规则,例如zh-CN是中国使用的语言规则,zh-HK是中国香港地区使用的语言规则。

Neutral Cultures(中立语言)指的是不和单一地区或国家直接相关的语言规则,例如zh-Hans代表简体中文,zh-Hant表示繁体中文,fr代表法语。

在DotNet中,Culture是以树形结构(或者说星型结构也行)的方式来组织的。每个Culture都有唯一的Parent。树的根节点就是Invariant Culture。

每个Specific Culture的Parent要么是Neutral Culture,要么是根节点Invariant Culture;每个Neutral Culture的Parent可能属于更高级别的Neutral Culture,或者根节点Invariant Culture。值得注意的是,Invariant Culture的父节点是其自身,而不是null。

资源文件组织层次

Silverlight的资源打包以及查找方式

Silverlight和DotNet的资源文件查找方式类似,只不过简化了许多。

资源文件采用约定俗成的命名方式进行打包,即XXX.语言标识字符串.resx,如果语言标识字符串缺失,则表示该资源文件最终会被打包到主程序集中,每个语言的资源文件都被打包到单独的卫星程序集中,然后放到相应的语言目录下,如下图所示:

资源文件

卫星程序集

 

主程序集中可以在AssemblyInfo.cs文件中设置其使用的中立资源语言以及中立资源所在的程序集。

[assembly: NeutralResourcesLanguage("zh-CN", UltimateResourceFallbackLocation.MainAssembly)]

注意,设置此中立资源语言的作用有两个:

1. 作为所有资源查找失败之后的Fallback资源

2. 提高资源查找性能,相当于预先告知Silverlight的ResourceManager,特定语言的资源存储在哪个地方。这样一旦需要lookup的语言和中立资源相同时,ResourceManager就直接到相应的程序集里头去取资源了,而不需要进行任何lookup。

从第2点中也可以看出,假如我们设置主程序集的中立语言资源为zh-Hans,并且位置是在MainAssembly中,那么即使当前文件夹下存在zh-Hans的卫星程序集,也会自动被忽略的。

另外,如果指定了该语言的资源在卫星程序集里头,但是该卫星程序集又不存在,那么当用到中立资源时会直接抛出System.Resources.MissingSatelliteAssemblyException异常。

 

Silverlight在查找一个ResourceKey的时候,首先需要定位到资源文件所在的程序集,然后定位到程序集中的资源文件,然后在找到资源文件中对应的资源key。这其中任何一个步骤失败,都视为查找失败。

整个查找的流程分为如下几步:

1. 查找具体语言资源中的ResourceKey是否存在

如设置了zh-CN,那么就查找zh-CN资源文件(存在于卫星程序集中)中是否有指定的ResourceKey。如果查找失败,则进入下一步。

需要注意,正如上面提到的,如果主程序集已经设置了NeutralResourcesLanguage并且和当前查找的具体语言相匹配,则自动从NeutralResourcesLanguage中指定的Location去加载程序集然后查找,这相当于一个短路逻辑。

2. 查找父级语言资源中的ResourceKey是否存在

查找具体语言的父级语言资源,例如en-US查找失败,则查找en是否存在。zh-CN查找失败,则查找zh-CHS,然后再是zh-Hans,最后是zh(注意,DotNet 4.0才添加zh作为zh-Hans和zh-Hant的公共父级)。如果失败,则进入下一步。

同样的,也会应用NeutralResourcesLanguage的短路逻辑。

3. 应用操作系统的Fallback逻辑

如果操作系统支持回溯(Fallback)逻辑,则操作系统会提供一份推荐的语言回溯清单。Silverlight会根据这份清单去按顺序去查找,查找方式和第1、2步相同,也是先找具体的,再找父级。如果依然失败,则进入下一步。

4. 使用中立资源

如果主程序集设置了NeutralResourcesLanguageAttribute属性,则看NeutralResourcesLanguageAttribute中设置的Location是主程序集还是卫星程序集。如果是在卫星程序集里头,而程序集又缺失,则抛出MissingSatelliteAssemblyException;

接下来在该程序集中去查找此资源文件,如果资源文件不存在,则抛出MissingManifestResourceException异常;

如果资源Key找不到,则返回空字符串

5. 使用主程序中的资源

如果主程序集没有设置NeutralResourcesLanguageAttribute属性,那么会直接使用主程序集中的资源文件。同样的,如果资源文件不存在,则抛出MissingManifestResourceException异常;如果资源Key找不到,则返回空字符串。否则返回资源本身。

 

需要注意,Silverlight和DotNet在资源的查找上还有些差别:

1. Silverlight永远都是在当前目录下查找卫星程序集,也就是xap包里头,而DotNet在上面每一个步骤中,都会先从GAC中查找,然后再到当前目录查找。

2. DotNet中并没有操作系统Fallback的逻辑,也就是说,第3步会直接跳过。

问题所在

在网上查到MSDN文章说,自打Vista开始(win2008也是一样),操作系统开始支持这种“Fallback”的逻辑了。通过系统API GetThreadPreferredUILanguages可以获取操作系统核心推荐使用的语言清单。

在我的中文win7机器上,获取到的清单如下:

  • zh-CN
  • zh-Hans
  • zh
  • en-US

由于出问题的Silverlight应用程序中并没有zh-Hans的资源文件,也没有设置主程序集的中立语言,因此在第1、2步都会查找失败;

这时候由于操作系统支持fallback逻辑,因此Silverlight会使用操作系统提供的语言清单去查找,查找en-US的父级成功,因此如果随便输入一个不支持的语言,最后都会显示英文。

而在其他低版本的windows上,因为操作系统不支持这份清单,而主程序集又没有设置中立语言,因此每次都会走到第5步,也就是取主程序集中的资源。

这就是为什么在低版本windows上,随便输入一个不支持的语言,默认显示的都是中文,而在Vista等高版本windows上,默认显示的是英文的缘故。

解决问题的办法

经过上面的分析,解决问题其实不止一种。按照微软的建议,主程序集是一定要显式通过NeutralResourcesLanguageAttribute属性指定中立语言资源,一方面是出于性能考虑,一方面也是为了更好进行Fallback。

而由于不同环境的系统,只有对英文支持是全球通用的,因此最好主程序集将中立语言设置为en,其他语言全都放到单独的卫星程序集中。这样任何资源查找失败,最终都会应用英文资源。

参考链接:

Localizing Silverlight-based Applications

Packaging and Deploying Resources

== Kevin Yang ==

本博客遵循CC协议2.5,即署名-非商业性使用-相同方式共享
写作很辛苦,转载请注明作者以及原文链接~
如果你喜欢我的文章,你可以订阅我的博客:-D点击订阅我的文章

  1. X﹏X 到现在还没有评论~
  1. 暂时没有trackbacks.