【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 文件,并且包含这么一段:

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

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

现在 Windows Runtime 的本地化:

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

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

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

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

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

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

解决方案:

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

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

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

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

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

【WinRT】使用 T4 模板简化字符串的本地化
 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 文件的最后

【WinRT】使用 T4 模板简化字符串的本地化
1 <#+

2     // 获取当前 T4 模板所在的工程的目录。

3     public string GetProjectPath()

4     {

5         return Host.ResolveAssemblyReference("$(ProjectDir)");

6     }

7 #>
获取当前工程目录

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

然后搜索 resw 就很简单了:

【WinRT】使用 T4 模板简化字符串的本地化
 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 上找到了一个差不多的答案。修改符合我们需求后,我们将获取命名空间的也封装成帮助函数:

【WinRT】使用 T4 模板简化字符串的本地化
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 所在项目的默认命名空间

最后附上完整代码:

【WinRT】使用 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 的园友应该会很熟悉的了。

最后效果:

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

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

 

后记:

今天有空,再调整了下 T4 的格式,生成的代码的格式终于没问题了。

【WinRT】使用 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     // 用于存放所有 resw 的 key。

 13     HashSet<string> names = new HashSet<string>();

 14     string projectPath = GetProjectPath();

 15 

 16     string stringsPath = Path.Combine(projectPath, "Strings");

 17     string[] reswPaths;

 18 

 19     // 当前项目存在 Strings 文件夹。

 20     if(Directory.Exists(stringsPath))

 21     {

 22         // 获取 Strings 文件夹下所有的 resw 文件的路径。

 23         reswPaths = Directory.GetFiles(stringsPath, "*.resw", SearchOption.AllDirectories);

 24     }

 25     else

 26     {

 27         reswPaths = new string[0];

 28     }

 29     

 30     foreach(string reswPath in reswPaths)

 31     {

 32         XmlDocument document = new XmlDocument();

 33         document.Load(reswPath);

 34 

 35         // 获取 resw 文件中的 data 节点。

 36         XmlNodeList dataNodes = document.GetElementsByTagName("data");

 37         foreach(var temp in dataNodes)

 38         {

 39             XmlElement dataNode = temp as XmlElement;

 40             if(dataNode != null)

 41             {

 42                 string value = dataNode.GetAttribute("name");

 43                 // key 中包含 ‘.’ 的作为控件的多语言化,不处理。

 44                 if(value.Contains(".") == false)

 45                 {

 46                     names.Add(value);

 47                 }

 48             }

 49         }

 50     }

 51 #>

 52 <#

 53     if(names.Count > 0)

 54     {

 55 #>

 56 using Windows.ApplicationModel.Resources;

 57 

 58 namespace <# WriteLine(GetProjectDefaultNamespace());#>

 59 {

 60     public class LocalizedStrings

 61     {

 62         private readonly static ResourceLoader Loader = new ResourceLoader();

 63 <#

 64             foreach(string name in names)

 65             {

 66                 if(string.IsNullOrWhiteSpace(name))

 67                 {

 68                     continue;

 69                 }

 70 

 71                 // 将 key 的第一个字母大写,作为属性名。

 72                 string propertyName = name[0].ToString().ToUpper() + name.Substring(1);

 73 #>

 74 

 75         public static string <#=propertyName#>

 76         {

 77             get

 78             {

 79                 return Loader.GetString("<#=name#>"); 

 80             }

 81         }

 82 <#

 83             }

 84 #>

 85     }

 86 }

 87 <#

 88     }

 89 #>

 90 <#+

 91     // 获取当前 T4 模板所在的工程的目录。

 92     public string GetProjectPath()

 93     {

 94         return Host.ResolveAssemblyReference("$(ProjectDir)");

 95     }

 96 

 97     // 获取当前 T4 模板所在的工程的默认命名空间。

 98     public string GetProjectDefaultNamespace()

 99     {

100         IServiceProvider serviceProvider = (IServiceProvider)this.Host;

101         EnvDTE.DTE dte = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE));

102         EnvDTE.Project project = (EnvDTE.Project)dte.Solution.FindProjectItem(this.Host.TemplateFile).ContainingProject;

103         return project.Properties.Item("DefaultNamespace").Value.ToString();

104     }

105 #>
格式化 fix

关键在于要将<##>这些控制块顶端对齐,也就是<#前面的空格在之前的版本被输出了,所以不好控制输出的格式。

 

2015年5月29日补充:

之前的方案没法支持多个 resw,只能支持 Resources.resw。resw 改成别的名字就不可以用了。

顺便获取默认命名空间有直接的 API,之前没装插件,没有智能感知所以不知道。。。

【WinRT】使用 T4 模板简化字符串的本地化
  1 <#@ template debug="false" hostspecific="true" language="C#" #>

  2 <#@ assembly name="System.Core" #>

  3 <#@ assembly name="System.Xml" #>

  4 <#@ import namespace="System.Linq" #>

  5 <#@ import namespace="System.Text" #>

  6 <#@ import namespace="System.IO" #>

  7 <#@ import namespace="System.Collections.Generic" #>

  8 <#@ import namespace="System.Xml" #>

  9 <#@ output extension=".cs" #>

 10 <#

 11     bool hadOutput = false;

 12 

 13     HashSet<KeyName> resourceKeys = new HashSet<KeyName>();// 存放 key 和对应的 resw 的名字。

 14 

 15     foreach (var reswPath in GetAllReswPath())

 16     {

 17         string name = GetReswName(reswPath);

 18         var keys = GetReswKeys(reswPath).ToList();

 19 

 20         for (int i = 0; i < keys.Count; i++)

 21         {

 22             var key = keys[i];

 23 

 24             if (string.IsNullOrWhiteSpace(key))

 25             {

 26                 continue;

 27             }

 28 

 29             KeyName keyName = KeyName.Create(key, name);

 30             resourceKeys.Add(keyName);

 31         }

 32     }

 33 #>

 34 <#

 35     if (resourceKeys.Any())

 36     {

 37 #>

 38 using Windows.ApplicationModel.Resources;

 39 

 40 namespace <#= GetNamespace() #>

 41 {

 42     public static partial class LocalizedStrings

 43     {

 44 <#

 45     }

 46 #>

 47 <#

 48     foreach (var keyName in resourceKeys)

 49     {

 50         string key = keyName.Key;

 51         string name = keyName.Name;

 52         

 53         // 将 key 的第一个字母大写,作为属性名。

 54         string propertyName = key[0].ToString().ToUpper() + key.Substring(1);

 55                 

 56         // ResourceLoader 的 key,如果为 "Resources",则可以省略。

 57         string resourceName = string.Equals(name, "Resources", StringComparison.OrdinalIgnoreCase) ? string.Empty : ("\"" + name + "\"");

 58 

 59         // 不是第一个属性,添加换行。

 60         if (hadOutput == true)

 61         {

 62             WriteLine(string.Empty);

 63         }

 64 #>

 65         public static string <#= propertyName #>

 66         {

 67             get

 68             {

 69                 return ResourceLoader.GetForCurrentView(<#= resourceName #>).GetString("<#= key #>");

 70             }

 71         }

 72 <#

 73         hadOutput = true;        

 74     }

 75 #>

 76 <#

 77     if (resourceKeys.Any())

 78     {

 79 #>

 80     }

 81 }

 82 <#

 83     }

 84 #>

 85 <#+ 

 86     /// <summary>

 87     /// 获取当前项目的默认命名空间。

 88     /// </summary>

 89     /// <returns>当前项目的默认命名空间。</returns>

 90     private string GetNamespace()

 91     {

 92         return this.Host.ResolveParameterValue("directiveId", "namespaceDirectiveProcessor", "namespaceHint");

 93     }

 94 

 95     /// <summary>

 96     /// 获取当前项目的绝对路径。

 97     /// </summary>

 98     /// <returns>当前项目的绝对路径。</returns>

 99     private string GetProjectPath()

100     {

101         return this.Host.ResolveAssemblyReference("$(ProjectDir)");

102     }

103 

104     /// <summary>

105     /// 获取 Strings 文件夹内的所有 resw 的绝对路径。

106     /// </summary>

107     /// <returns>Strings 文件夹内的所有 resw 的绝对路径。如果没有,则返回空集合。</returns>

108     private IEnumerable<string> GetAllReswPath()

109     {

110         string projectPath = GetProjectPath();

111         string stringsPath = Path.Combine(projectPath, "Strings");

112         

113         // 当前项目存在 Strings 文件夹。

114         if (Directory.Exists(stringsPath))

115         {

116             // 获取 Strings 文件夹下所有的 resw 文件的路径。

117             return Directory.GetFiles(stringsPath, "*.resw", SearchOption.AllDirectories);

118         }

119         else

120         {

121             return Enumerable.Empty<string>();

122         }

123     }

124 

125     /// <summary>

126     /// 获取 resw 的文件名。

127     /// </summary>

128     /// <returns>resw 的文件名。</returns>

129     private string GetReswName(string reswPath)

130     {

131         return Path.GetFileNameWithoutExtension(reswPath);

132     }

133 

134     /// <summary>

135     /// 获取 resw 内的所有键的名称。

136     /// </summary>

137     /// <returns>resw 内所有键的名称,不包含用于本地化控件属性的键。</returns>

138     private IEnumerable<string> GetReswKeys(string reswPath)

139     {

140         XmlDocument document = new XmlDocument();

141         document.Load(reswPath);

142 

143         // 获取 resw 文件中的 data 节点。

144         XmlNodeList dataNodes = document.GetElementsByTagName("data");

145         foreach(var temp in dataNodes)

146         {

147             XmlElement dataNode = temp as XmlElement;

148             if (dataNode != null)

149             {

150                 string key = dataNode.GetAttribute("name");

151                 // key 中包含 ‘.’ 的作为控件的多语言化,不处理。

152                 if (key.Contains(".") == false)

153                 {

154                     yield return key;

155                 }

156             }

157         }

158     }

159 

160     // 辅助结构体

161     private struct KeyName

162     {

163         internal string Key

164         {

165             get;

166             set;

167         }

168 

169         internal string Name

170         {

171             get;

172             set;

173         }

174 

175         internal static KeyName Create(string key, string name)

176         {

177             return new KeyName()

178                 {

179                     Key = key,

180                     Name = name

181                 };

182         }

183     }

184  #>
2015/5/29 fix

 

你可能感兴趣的:(字符串)