专注收集记录技术开发学习笔记、技术难点、解决方案
网站信息搜索 >> 请输入关键词:
您当前的位置: 首页 > WinRT Metro

【WinRT】使用 T4 模板简单化字符串的本地化

发布时间:2011-06-23 13:56:20 文章来源:www.iduyao.cn 采编人员:星星草
【WinRT】使用 T4 模板简化字符串的本地化

在 WinRT 中,对控件、甚至图片资源的本地化都是极其方便的,之前我在博客中也介绍过如何本地化应用名称:http://www.cnblogs.com/h82258652/p/4292157.html

关于 WinRT 中的本地化,CodeProject 上有一篇十分值得大家一看的文章:http://www.codeproject.com/Articles/862152/Localization-in-Windows-Universal-Apps

大家可以去仔细学习一下。

以前的本地化:

说回重点,如果有 Winform、WPF、Windows Phone Silverlight 本地化经验的都知道,打开 Resources.resx 文件,然后填写键值对就可以了(其它语言则在添加相应的 resx 文件,例如美国的就添加 Resources.en-US.resx)。这种方法最大的优点就是,填写的键会生成相应的属性,例如我在 resx 中填写了一个 China 的键,则 resx 文件会生成一个相应的 cs 文件,并且包含这么一段:

然后我们就可以使用:resx的名字.China 来使用相应的本地化的字符串了。

现在 Windows Runtime 的本地化:

在 WinRT 中,本地化很多东西都变得方便了,唯独就是字符串的本地化变麻烦了,资源文件不再给我们生成强类型的属性来访问字符串了,要我们自己来手动写代码来获取每一个键对应的值。

例如我们也在 WinRT 的资源文件——resw 里填写一个 China 的键:

那么在 cs 代码中访问这个 China 就是:

一两个键还好,手动写一下,问题我们的程序不可能就只有那么点需要本地化的字符串,少则几十个,多则上百上千。而且手动编写的话,还会存在拼写错误的情况,最主要的是,根本没法重构!!

解决方案:

那么必然就需要使用一种自动化的,生成代码的技术,这里我们选择 T4 模板来解决这个问题。

首先,我们先来研究下,究竟 resw 是如何存放我们编写好的数据呢?用记事本或者其它文本编辑器打开 resw。

可以看出,本质是一个 XML 文件,并且我们可以轻易看到,我们填写的键值存放在 data 节点中。

假设我们知道这个 resw 文件的路径的话,我们可以编写出如下代码:

 1         XmlDocument document = new XmlDocument();
 2         document.Load(reswPath);
 3 
 4         // 获取 resw 文件中的 data 节点。
 5         XmlNodeList dataNodes = document.GetElementsByTagName("data");
 6         foreach(var temp in dataNodes)
 7         {
 8             XmlElement dataNode = temp as XmlElement;
 9             if(dataNode != null)
10             {
11                 string value = dataNode.GetAttribute("name");
12                 // key 中包含 ‘.’ 的作为控件的多语言化,不处理。
13                 if(value.Contains(".") == false)
14                 {
15                     names.Add(value);
16                 }
17             }
18         }
获取 resw 中的所有键

其中 names 变量用于存放 resw 中的键。

需要注意的是,键中包含 ‘.’ 的键是用于控件的本地化的,所以这些键我们跳过。(因为 ‘.’ 也不能包含在属性名中)

接下来就是如何获得这些 resw 的路径了。

这里我们往上想一步,获得当前工程的路径,然后搜索 resw 文件不就行了吗?

在获取当前工程的路径时,我们需要用到 T4 的语法,这里我编写成帮助函数。(注意:T4 的帮助函数必须放到 T4 文件的最后

1 <#+
2     // 获取当前 T4 模板所在的工程的目录。
3     public string GetProjectPath()
4     {
5         return Host.ResolveAssemblyReference("$(ProjectDir)");
6     }
7 #>
获取当前工程目录

这里有一点要注意,由于使用到 Host 属性,所以需要把 T4 文件前面的 hostspecific 修改为 true

然后搜索 resw 就很简单了:

 1     string projectPath = GetProjectPath();
 2 
 3     string stringsPath = Path.Combine(projectPath, "Strings");
 4     string[] reswPaths;
 5 
 6     // 当前项目存在 Strings 文件夹。
 7     if(Directory.Exists(stringsPath))
 8     {
 9         // 获取 Strings 文件夹下所有的 resw 文件的路径。
10         reswPaths = Directory.GetFiles(stringsPath, "*.resw", SearchOption.AllDirectories);
11     }
12     else
13     {
14         reswPaths = new string[0];
15     }
获取所有 resw 的路径

这里进行了路径拼接是因为生成程序的目录下,也会有由于 VS 调试生成的 resw 文件,这里我们是不需要的,而且这样搜索的效率会高一点。我们仅仅需要 Strings 文件夹下的 resw。

需要的基本都准备好了,最后一个大难题就是,我们想生成的 cs 的命名空间跟当前项目相同。关于这一点,博客园好像找不到,最后在 stackoverflow 上找到了一个差不多的答案。修改符合我们需求后,我们将获取命名空间的也封装成帮助函数:

1     // 获取当前 T4 模板所在的工程的默认命名空间。
2     public string GetProjectDefaultNamespace()
3     {
4         IServiceProvider serviceProvider = (IServiceProvider)this.Host;
5         EnvDTE.DTE dte = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE));
6         EnvDTE.Project project = (EnvDTE.Project)dte.Solution.FindProjectItem(this.Host.TemplateFile).ContainingProject;
7         return project.Properties.Item("DefaultNamespace").Value.ToString();
8     }
获取当前 T4 所在项目的默认命名空间

最后附上完整代码:

  1 <#@ template debug="false" hostspecific="true" language="C#" #>
  2 <#@ assembly name="System.Core" #>
  3 <#@ assembly name="System.Xml" #>
  4 <#@ assembly name="EnvDTE" #>
  5 <#@ import namespace="System.Linq" #>
  6 <#@ import namespace="System.Text" #>
  7 <#@ import namespace="System.Collections.Generic" #>
  8 <#@ import namespace="System.IO" #>
  9 <#@ import namespace="System.Xml" #>
 10 <#@ output extension=".cs" #>
 11 
 12 <#
 13     // 用于存放所有 resw 的 key。
 14     HashSet<string> names = new HashSet<string>();
 15     string projectPath = GetProjectPath();
 16 
 17     string stringsPath = Path.Combine(projectPath, "Strings");
 18     string[] reswPaths;
 19 
 20     // 当前项目存在 Strings 文件夹。
 21     if(Directory.Exists(stringsPath))
 22     {
 23         // 获取 Strings 文件夹下所有的 resw 文件的路径。
 24         reswPaths = Directory.GetFiles(stringsPath, "*.resw", SearchOption.AllDirectories);
 25     }
 26     else
 27     {
 28         reswPaths = new string[0];
 29     }
 30     
 31     foreach(string reswPath in reswPaths)
 32     {
 33         XmlDocument document = new XmlDocument();
 34         document.Load(reswPath);
 35 
 36         // 获取 resw 文件中的 data 节点。
 37         XmlNodeList dataNodes = document.GetElementsByTagName("data");
 38         foreach(var temp in dataNodes)
 39         {
 40             XmlElement dataNode = temp as XmlElement;
 41             if(dataNode != null)
 42             {
 43                 string value = dataNode.GetAttribute("name");
 44                 // key 中包含 ‘.’ 的作为控件的多语言化,不处理。
 45                 if(value.Contains(".") == false)
 46                 {
 47                     names.Add(value);
 48                 }
 49             }
 50         }
 51     }
 52 #>
 53 <#
 54     if(names.Count > 0)
 55     {
 56         #>
 57 using Windows.ApplicationModel.Resources;
 58 
 59 namespace <# Write(GetProjectDefaultNamespace());#>
 60 {
 61     public class LocalizedStrings
 62     {
 63         private readonly static ResourceLoader Loader = new ResourceLoader();
 64         
 65         <#
 66             foreach(string name in names)
 67             {
 68                 if(string.IsNullOrWhiteSpace(name))
 69                 {
 70                     continue;
 71                 }
 72 
 73                 // 将 key 的第一个字母大写,作为属性名。
 74                 string propertyName = name[0].ToString().ToUpper() + name.Substring(1);
 75                 #>
 76                 public static string <#=propertyName#>
 77         {
 78             get
 79             {
 80                 return Loader.GetString("<#=name#>"); 
 81             }
 82         }
 83                 <#
 84             }
 85         #>
 86     }
 87 }
 88         <#
 89     }
 90 #>
 91 <#+
 92     // 获取当前 T4 模板所在的工程的目录。
 93     public string GetProjectPath()
 94     {
 95         return Host.ResolveAssemblyReference("$(ProjectDir)");
 96     }
 97 
 98     // 获取当前 T4 模板所在的工程的默认命名空间。
 99     public string GetProjectDefaultNamespace()
100     {
101         IServiceProvider serviceProvider = (IServiceProvider)this.Host;
102         EnvDTE.DTE dte = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE));
103         EnvDTE.Project project = (EnvDTE.Project)dte.Solution.FindProjectItem(this.Host.TemplateFile).ContainingProject;
104         return project.Properties.Item("DefaultNamespace").Value.ToString();
105     }
106 #>
完整代码

其中 <#=variable#> 为输出变量的值,学习过 Asp.net 的园友应该会很熟悉的了。

最后效果:

 

除了 T4 生成出来的没格式化这点比较蛋疼之外,其它一切问题都被 T4 解决好了,使用 LocalizedStrings.China 就可以访问到 China 这个键对应的值了。以后修改完 resw 之后,重新生成一下项目就可以更新 LocalizedStrings 了。

友情提示:
信息收集于互联网,如果您发现错误或造成侵权,请及时通知本站更正或删除,具体联系方式见页面底部联系我们,谢谢。

其他相似内容:

热门推荐: