莫名其妙的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点击订阅我的文章
最近评论