有时候需要为程序提供多语言支持,然而 .NET 自带的方案有点不便于使用。下面介绍两个简易的工具来让这个工作更轻松些,主要针对字符串资源。

字符串获取

问题

我们知道,.NET 上多语言支持的标准方式是使用 .resx 资源文件,默认语言的资源编译后嵌入主程序集,其它语言的资源文件编译成卫星程序集(satellite assembly)。

Visual Studio 自带的工具可以把对资源的使用生成代码,比如Resources.resx文件就会生成Resources.Designer.cs,其中包含Resources类。资源可以通过这个类上的属性获取。大部分情况下还是挺方便的,但是对于有{0}{1}这种占位符的字符串就很麻烦。每次都要自己string.Format(...)下,尤其还得先确定下里面具体是几个占位符,这是个让人恼火的事情。

方案

某日,在 GitHub 上乱逛,发现了做 EF 7 和 ASP.NET 5 的人搞了个小工具处理这个事情:https://github.com/aspnet/EntityFramework/blob/master/tools/Resources.tt

它还有个更早些的版本。这是个 T4 模板,功能就是处理工程里所有的 .resx 文件,把资源生成代码。它生成的方式更好:如果没有{0}之类的,就生成属性;如果有,就生成方法,占位符作为参数。

比如添加一个Strings.resx文件,关掉 VS 的代码生成,然后在里面添加键OperationFailed,值Could not perform operation '{0}' due to '{1}'.,它生成的类就是:

public static class Strings
{
    // ...

    /// <summary>
    /// Could not perform operation '{0}' due to '{1}'.
    /// </summary>
    public static string OperationFailed([CanBeNull] object p0, [CanBeNull] object p1)
    {
        return string.Format(CultureInfo.CurrentCulture,
            GetString("OperationFailed"), p0, p1);
    }

    // ...
}

生成出来的文件也叫做Strings.Designer.cs,在模板所在的目录下,需要把它添加到工程里。

这样不需要自己每次string.Format(...),可以通过方法来传递参数,注释来看字符串的值(VS 生成那个也有注释,但是会加一些讨厌的前缀),这一点改动可以让写代码的过程更流畅。

发散

这个小工具如果自己要用,可能还需要稍微改改,因为它纯粹是为了 EF 和 ASP.NET 的开发服务的。就没考虑 .resx 中会有其它的资源类型,也没考虑比如Strings.zh-Hans.resx直接添加在工程里的情况。可以通过分离字符串资源成为独立的 .resx 文件解决,然后可以在它获取所有 .resx 文件那里改改,通过自己定义好的文件名规范来过滤掉不需要处理的。(它还用了JetBrains.Annotations,不用这个的就把using[CanBeNull]去掉)

其实更大的好处是给我们提供了一个使用 T4 模板的框架,因为大部分人都不熟悉 DTE 接口(VS 暴露的 COM 接口,方便开发插件和设计器),想获取到工程的路径可能就要查半天。而这里已经都写好了,并且实现了一个 T4 模板生成多个文件的功能,自己就可以根据需要修改。特别是可以思考下,可能有些原来运行时动态生成的代码,其实全部或者部分在编译时生成也是可行的。这有助于静态检查,提高程序质量。

我用它改了改做了一个方便生成需要的 P/Invoke 代码的功能(因为要支持[DllImport]LoadLibrary两种方式),还有计算需要嵌入程序集的 .dll 文件的 hash 值的功能。

顺便提一下,这种使用模板生成代码是编译时代码生成的一种方式,它发生在编译前。还有另一种方式,发生在编译后,就是 IL Weaving 技术,属于静态 AOP。它可以修改编译后的程序集,一般用来实现一些“侧面”功能。比如为类型添加INotifyPropertyChanged接口,在需要的地方添加日志记录、性能分析、异常捕获等等代码。

.NET 上最强大的 AOP 工具应该是PostSharp,也有像Fody这样小巧灵活的方案。

无论是编译前生成代码,还是之后的静态 AOP,或者运行时的动态 AOP,如果一个团队里能有高手用好它们,其它人都会轻松很多(减少了开发人员的重复劳动)。当然,用好并不容易,不过这也是提高自己技术能力的一个方向,可以尝试。

(一不留神又发散多了,下面回到主题。)

字符串翻译

VS 没有提供不同语言资源对照翻译的功能,.resx 文件是 xml 的,有额外的标签干扰文本对比,文本对比还有顺序的问题。如果用 .txt 文件保存字符串,同样是文本对比,而且还要自己写生成代码的逻辑。总之就是各种不方便。

于是找找插件,发现这个ResXManager。它可以加载解决方案里面所有 .resx 文件中的字符串,非常方便按语言分列对比翻译。还可以导入导出 Excel 文件,最近还添加了调用微软的翻译服务进行机翻的功能。确实各种方便,所以它的评价也是相当好。

有了这个插件和前面说的 T4 模板,无论翻译还是使用都方便多了。当然,如果有更多更复杂的需求,那可能需要找一些商业化的产品和解决方案来实现。

发散 2

顺便又找到一个EmojiVS的插件,它支持 VS2013 和 VS2015,可以在注释里添加 emoji 表情。让注释更有表现力是相当好的,更容易理解的注释就是更好的可维护性,而且感觉装上这个以后我更喜欢写注释了。

看到这个插件时,就感觉作者的名字很熟悉:Jb Evain。看了下还真是写Mono.Cecil的那位,现在在领导Visual Studio Tools for Unity的开发。果然是牛人,闲暇时间一边继续维护 Cecil,然后还做个 VS 插件出来。

那个 Cecil 就是前面发散里提到的 Fody 使用的修改程序集的底层工具。其实大部分修改程序集的工具都是用它,因为也没什么选择。前几年又出来一个dnlib,这个东西应该说更强大,它主要是用来做混淆 / 反混淆用的,我试了试感觉它比 Cecil 更好用。

(感觉再继续下去就停不下来了,还是及时结束吧。)

如果有什么希望了解的知识与技术(非业务功能的),或者是开发过程中遇到的类似本文里这种开发过程中不方便的事情(主要是指影响了思维的流畅表达),都可以在下面或者留言板留言给我。

如果是我懂的,有空就会写 blog 发出来,不懂的我会去了解下看看能不能学会,切实的需求又没有现成解决方案的,我会尝试实现工具来解决。

from GKarch 博客