找回密码
 立即注册
首页 业界区 安全 dotnet 读 WPF 源代码 学习使用 Microsoft.DotNet.Arcad ...

dotnet 读 WPF 源代码 学习使用 Microsoft.DotNet.Arcade.Sdk 处理代码里的多语言

忌才砟 前天 09:00
在 WPF 开源代码里面,可以看到是从各个项目的 Strings.resx 和对应的 xlf 文件,生成对应项目的多语言程序集。这里的多语言程序集可用于抛出异常时,给出本地化的消息提示
在 dotnet 庞大的生态集里,打包工具链是开源中很重要的部分工作。通过 https://github.com/dotnet/arcade 将打包中重复的工作放在一个仓库中,减少基础设施能力在多个项目中重复进行。就像我所在的团队开源的 DotNETBuildSDK 项目一样,提供各种构建工具用在各个项目里面
翻遍整个 WPF 仓库,都无法直接找到任何的从 Strings.resx 和对应的 xlf 文件生成多语言卫星程序集的逻辑。这是因为多语言的核心转换是放在 Microsoft.DotNet.Arcade.Sdk 里面,在 WPF 仓库里面只有一些配置项
整个 WPF 开源仓库的组织是相对清晰的,所有和构建相关的配置都放在 eng 文件夹里面。其中对 Microsoft.DotNet.Arcade.Sdk 的引用分别放在 eng\WpfArcadeSdk\Sdk\Sdk.props 和 eng\WpfArcadeSdk\Sdk\Sdk.targets 文件里。核心代码只有以下这两句
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
复制代码
多语言配置部分的逻辑放在 eng\WpfArcadeSdk\tools\SystemResources.props 文件里,其代码较多,咱就先不展开细看
从 WPF 代码仓库里面是没有看到详尽的多语言转换过程逻辑的,但看了这几个文件也够咱自己学习模仿 WPF 用 Microsoft.DotNet.Arcade.Sdk 处理代码里的多语言的方式。接下来我将新建一个 WPF 空项目,在此和大家演示使用 Microsoft.DotNet.Arcade.Sdk 处理多语言,相信大家能够学会用此构建工具生成多语言程序集
新建一个空白的 WPF 项目
虽然按照 .NET 的惯例,使用一个库的第一件事就是用 NuGet 进行库的安装。但 Microsoft.DotNet.Arcade.Sdk 比较特殊,这是一个 SDK 而不是一个 Library 库。直接使用 NuGet 安装会报告以下错误
  1. 包“Microsoft.DotNet.Arcade.Sdk 11.0.0-beta.25556.1”具有一个包类型“MSBuildSdk”,项目“Xxxxx”不支持该类型。
复制代码
正确的使用方法如下
第一步是添加 NuGet.config 文件,设置使用 dotnet-eng 源。因为 Microsoft.DotNet.Arcade.Sdk 库是没有放在公网 NuGet 源里面的。修改之后的 NuGet.config 文件内容如下
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  4.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  5.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  6.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  7.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
复制代码
第二步是添加 global.json 文件,设置 Microsoft.DotNet.Arcade.Sdk 的版本。这一步就类似于使用 NuGet 进行安装的过程,只不过用的是 SDK 的方式
  1. {
  2.   "msbuild-sdks":
  3.   {
  4.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  5.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />"Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25411.109"
  6.   }
  7. }
复制代码
第三步就是在 csproj 项目文件里面添加引用,代码如下
  1.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  2.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
复制代码
如此三步就可以完成 Microsoft.DotNet.Arcade.Sdk 库安装
完成安装之后,就可以尝试多语言的加入了。只需放入 resx 文件,无论命名和放在哪个文件夹内。为了简单起见,我随便从 WPF 仓库拷贝了一个 Strings.resx 文件,编辑之后的内容如下
1.png

此时直接构建肯定是没有效果的,因为还没有设置 GenerateResxSource 属性为 true 值,用于配置让 Arcade 进行多语言生成
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />true  
复制代码
再设置 EmbeddedResource 属性,配置好生成的类型的命名空间和类名,配置的代码如下
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  4.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  5.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />true  
  6.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  7.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />FxResources.$(AssemblyName).SR  
  8.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  9.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />MS.Utility.SR  
  10.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  11.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
复制代码
以上代码里面的 GenerateSource 设置为 true 表示当前项用来配置多语言的生成。以上代码的 ManifestResourceName 只是一个用来标识资源存在的程序集,用来执行 typeof 获取 ResourceManager 的资源,命名上比较随意。以上的 ClassName 为重点部分,用来表示从 resx 文件应该生成的类型全名,采用命名空间加类型名的表示法。如 MS.Utility.SR 将生成命名空间为 MS.Utility 且类型名为 SR 的类型
通过 ClassName 的配置,即可让各个程序集采用不同的命名空间配置。如在 WPF 仓库的 eng\WpfArcadeSdk\tools\SystemResources.props 文件里,就使用了以下类似的代码为各个程序集配置不同的命名空间
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  4.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />true  
  5.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  6.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />FxResources.$(AssemblyName).SR  
  7.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  8.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />MS.Utility.SR  
  9.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  10.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />System.SR  
  11.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  12.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />MS.Internal.WindowsBase.SR  
  13.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  14.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />...  
  15.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  16.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />MS.Internal.PresentationCore.SR  
  17.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  18.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />System.SR  
  19.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  20.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />System.SR  
  21.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  22.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
复制代码
以上逻辑就能够完成多语言生成的配置
然而现在还不能通过构建,一构建将提示类似如下的错误
  1. C:\Users\lindexi\.nuget\packages\microsoft.dotnet.arcade.sdk\10.0.0-beta.25411.109\tools\Version.BeforeCommonTargets.targets(88,5): error MSB4184: 无法计算表达式“"".GetValue(1)”。Index was outside the bounds of the array.
复制代码
这是因为在 Version.BeforeCommonTargets.targets 文件里面存在如下代码
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />$(MajorVersion).$(MinorVersion).$([MSBuild]::ValueOrDefault('$(PatchVersion)', '0'))  
  4.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  5.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  $(VersionPrefix.Split('.')[0])  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  6.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />$(VersionPrefix.Split('.')[1])  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  7.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />$(_VersionPrefixMajor).$(_VersionPrefixMinor).$([MSBuild]::ValueOrDefault($(_PatchNumber), '0'))  
  8.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  9.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
复制代码
尽管我认为这是 Microsoft.DotNet.Arcade.Sdk 库的设计不够开箱即用,但考虑到这是一个专用的库,这一点也能接受。继续编辑 csproj 项目文件,添加如下代码,添加版本号信息
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />1  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  4.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />2  
复制代码
如此即可完成构建准备,尝试构建一下。此时细心的伙伴也许就发现了,在 obj 文件夹下,生成了 obj\Debug\net9.0-windows\MS.Utility.SR.cs 文件,且在此文件里面填满了在 Strings.resx 资源字典定义的多语言项。其生成代码大概如下
  1. using System.Reflection;namespace FxResources.QewheefanallJabayhejage{  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  2.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />internal static class SR { }}namespace MS.Utility{  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />internal static partial class SR  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  4.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />{  
  5.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  6.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  private static global::System.Resources.ResourceManager s_resourceManager;  
  7.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  8.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  internal static global::System.Resources.ResourceManager ResourceManager => s_resourceManager ?? (s_resourceManager = new global::System.Resources.ResourceManager(typeof(FxResources.QewheefanallJabayhejage.SR)));  
  9.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  10.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  internal static global::System.Globalization.CultureInfo Culture { get; set; }#if !NET20  
  11.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  12.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]#endif  
  13.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  14.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  internal static string GetResourceString(string resourceKey, string defaultValue = null) =>  ResourceManager.GetString(resourceKey, Culture);  
  15.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  16.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  /// Enumerating attached properties on object '{0}' threw an exception.  
  17.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  18.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  internal static string @APSException => GetResourceString("APSException");  
  19.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  20.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  /// Add value to collection of type '{0}' threw an exception.  
  21.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  22.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  internal static string @AddCollection => GetResourceString("AddCollection");  
  23.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  24.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  /// Add value to dictionary of type '{0}' threw an exception.  
  25.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  26.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  internal static string @AddDictionary => GetResourceString("AddDictionary");  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  27.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />}}
复制代码
细心的伙伴还能看到,此时在项目里面被新建了 xlf 文件夹,在此文件夹内充满了各个语言文化对应的 xlf 文件。这些 xlf 文件是为翻译人员准备的,方便对接翻译平台进行翻译。每个 xlf 文件都会在 obj 文件夹生成对应的 resx 文件,再由 resx 文件生成对应的程序集
2.png

这里的 xlf 文件是采用 https://en.wikipedia.org/wiki/XLIFF 多语言翻译规范的文件,这是一个现有的规范的格式。其内容大概如下
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  4.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  5.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  6.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  7.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  Enumerating attached properties on object '{0}' threw an exception.  
  8.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  9.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  枚举对象“{0}”的附加属性时引发了异常。  
  10.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  11.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  12.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  13.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  14.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  15.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  16.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  17.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  18.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />Add value to collection of type '{0}' threw an exception.  
  19.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  20.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  向类型为“{0}”的集合中添加值引发了异常。  
  21.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  22.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  23.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  24.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  25.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  26.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  27.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  28.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  29.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />Add value to dictionary of type '{0}' threw an exception.  
  30.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  31.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  Add value to dictionary of type '{0}' threw an exception.  
  32.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  33.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  34.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  35.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  36.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  37.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
复制代码
可以看到 XLIFF 格式里面可以为翻译人员提供双语对照,也能通过 state="translated" 还是 state="new" 标记出已经翻译的还是新添加的多语言项
从这里也能看到 Microsoft.DotNet.Arcade.Sdk 的好用之处,只需添加 resx 文件,就会自动生成各个语言文化对应的 xlf 文件,方便翻译人员对接
以下是我的最简使用 Microsoft.DotNet.Arcade.Sdk 对接多语言的 csproj 项目的代码
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />WinExe  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  4.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />net9.0-windows  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  5.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />enable  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  6.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />enable  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  7.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />true  
  8.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  9.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  1  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  10.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />2  
  11.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  12.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  true  
  13.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  14.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  15.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  16.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  17.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  18.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  19.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />true  
  20.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  21.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />FxResources.$(AssemblyName).SR  
  22.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  23.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />MS.Utility.SR  
  24.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  25.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
复制代码
以上被注释掉的 GenerateResxSourceOmitGetResourceString 属性用来配置 Microsoft.DotNet.Arcade.Sdk 生成的类型里面,不要生成 GetResourceString 等代码。如此即可在自己程序集里面自己定义多语言获取的类型,提供更高的自由。在 WPF 仓库里面,就是自己定义的 GetResourceString 方法,用来处理多语言找不到的情况
也许有伙伴好奇在 Microsoft.DotNet.Arcade.Sdk 底层是如何对接多语言代码的生成的。事实上这部分逻辑也十分简单,从 https://github.com/dotnet/arcade 仓库可以找到明确的代码
先是在 GenerateResxSource.targets 文件里面执行对接逻辑,核心代码如下
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  4.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  5.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  6.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  7.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  8.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  9.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  10.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  11.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  12.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  13.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
复制代码
可见就是从 _BatchGenerateResxSource 调用 Microsoft.DotNet.Arcade.Sdk.GenerateResxSource 执行生成逻辑。在 _GenerateResxSource 里面将生成的文件加入构建
上面代码的 EmbeddedResourceSGResx 内容仅是取出本文在 csproj 的 ItemDefinitionGroup 里面定义的属性内容,再配合添加一些过滤条件而已
核心的 GenerateResxSource 生成类的定义代码如下
  1.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  2.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />public sealed class GenerateResxSource : Microsoft.Build.Utilities.Task  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />{  
  4.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  5.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  private const int maxDocCommentLength = 256;  
  6.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  7.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  8.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  9.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   /// Language of source file to generate.  Supported languages: CSharp, VisualBasic  
  10.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  11.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  12.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  13.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   [Required]  
  14.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  15.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  public string Language { get; set; }  
  16.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  17.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  18.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  19.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   /// Resources (resx) file.  
  20.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  21.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  22.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  23.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   [Required]  
  24.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  25.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  public string ResourceFile { get; set; }  
  26.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  27.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  28.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  29.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   /// Name of the embedded resources to generate accessor class for.  
  30.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  31.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  32.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  33.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   [Required]  
  34.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  35.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  public string ResourceName { get; set; }  
  36.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  37.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  38.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  39.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   /// Optionally, a namespace.type name for the generated Resources accessor class.  Defaults to ResourceName if unspecified.  
  40.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  41.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  42.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  43.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   public string ResourceClassName { get; set; }  
  44.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  45.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  46.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  47.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   /// If set to true the GetResourceString method is not included in the generated class and must be specified in a separate source file.  
  48.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  49.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  50.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  51.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   public bool OmitGetResourceString { get; set; }  
  52.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  53.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  54.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  55.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   /// If set to true, emits constant key strings instead of properties that retrieve values.  
  56.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  57.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  58.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  59.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   public bool AsConstants { get; set; }  
  60.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  61.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  62.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  63.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   /// If set to true calls to GetResourceString receive a default resource string value.  
  64.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  65.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  66.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  67.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   public bool IncludeDefaultValues { get; set; }  
  68.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  69.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  70.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  71.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   /// If set to true, the generated code will include .FormatXYZ(...) methods.  
  72.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  73.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ///  
  74.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  75.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />   public bool EmitFormatMethods { get; set; }  
  76.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  77.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  [Required]  
  78.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  79.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  public string OutputPath { get; set; }  
  80.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  81.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  private enum Lang  
  82.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  83.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  {  
  84.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  85.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  86.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  87.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />CSharp,  
  88.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  89.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  90.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  91.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />VisualBasic,  
  92.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  93.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  }  
  94.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  95.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  ...  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  96.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />}
复制代码
其生成逻辑是根据 C# 或 VB 进行拼接字符串方式生成的多语言代码的
读取 resw 字典也是直接使用 XDocument 的方式读取,核心代码如下
  1.   
  2.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  3.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  4.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  5.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />string classIndent = (namespaceName == null ? "" : "  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  6.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />");  
  7.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  8.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  9.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  10.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />string memberIndent = classIndent + "  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  11.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />";  
  12.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  13.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  14.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  15.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />var strings = new StringBuilder();  
  16.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  17.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  18.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  19.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />foreach (var node in XDocument.Load(ResourceFile).Descendants("data"))  
  20.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  21.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  22.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  23.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />{  
  24.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  25.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  26.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  27.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  28.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />string name = node.Attribute("name")?.Value;  
  29.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  30.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  31.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  32.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  33.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />string value = node.Elements("value").FirstOrDefault()?.Value.Trim();  
  34.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  35.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  36.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  37.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  38.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />strings.AppendLine($"{memberIndent}internal static string @{identifier} => GetResourceString("{name}"{defaultValue});");  
  39.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  40.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />  
  41.   <Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />
  42.   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />}
复制代码
实际的代码比我以上有删减部分略微复杂,如果大家感兴趣,还请自行去查看源代码
本文代码放在 github 和 gitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
  1. git init
  2. git remote add origin https://gitee.com/lindexi/lindexi_gd.git
  3. git pull origin 69bd783e97b03e767017ebbbe61aad89b9a8104d
复制代码
以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码
  1. git remote remove origin
  2. git remote add origin https://github.com/lindexi/lindexi_gd.git
  3. git pull origin 69bd783e97b03e767017ebbbe61aad89b9a8104d
复制代码
获取代码之后,进入 WPFDemo/QewheefanallJabayhejage 文件夹,即可获取到源代码
更多技术博客,请参阅 博客导航

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册