zoukankan      html  css  js  c++  java
  • 【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 了。

    后记:

    今天有空,再调整了下 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,之前没装插件,没有智能感知所以不知道。。。

      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
  • 相关阅读:
    java基础72 junit单元测试
    java基础71 XML解析中的【DOM和SAX解析工具】相关知识点(网页知识)
    java基础70 负责静态的网页制作语言XML(网页知识)
    管理表空间和数据文件概要
    关于在博客中记录解决报错的问题
    ORACLE监听配置及测试实验(2)
    ORACLE监听配置及测试实验
    ORACLE network environment
    ORACLE INSTANCE与EM系统
    ORACLE DB体系结构
  • 原文地址:https://www.cnblogs.com/h82258652/p/4382622.html
Copyright © 2011-2022 走看看