Visual Studio 是否有任何ResXFileCodeGenerator替代方案可以使用线程安全本地化?

gmxoilav  于 2023-02-09  发布在  其他
关注(0)|答案(2)|浏览(217)

ResourceManagerResXFileCodeGenerator为本地化提供了强大的功能:创建带有语言前缀 (例如MyStrings.ru.resx) 的相同.resx文件就足够了,因此,通过在生成的Designer文件中设置相应的MyStrings.Culture属性并调用必要的字符串相关属性,我们可以顺利地使用不同的语言:

MyStrings.Culture = new CultureInfo("ru");
Console.Write(MyStrings.MyTranslatedString); // Russian output

MyStrings.Culture = CultureInfo.InvariantCulture;
Console.Write(MyStrings.MyTranslatedString); // English output

我非常喜欢这种方法。但不幸的是它在多线程模式下会失败,因为提到。Culture属性是静态的。

我希望保持相同的功能 (轻松编辑资源文件;支持Inlellisense等自动生成的属性),但能够在多线程模式下工作。

当然,我可以直接使用ResourceManager,如下所示:

ResourceManager.GetString("Commands description", resourceCulture);

但在这种情况下,如果我在.resx文件中更改一个名称(键),我将不得不在.cs文件中手动更改,这不够方便。

yrwegjxp

yrwegjxp1#

在您的示例中,属性MyStrings.MyTranslatedString是在生成的代码中定义的,要实现您所要求的,您需要生成该文件的自己的实现。
您可以使用源代码生成器来实现这一点,尽管这样做涉及到相当多的步骤。
https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview
或者你可以手工编写一个类来完成这个任务。也许你可以添加一个单元测试来确保你的.resx文件中的所有键也存在于你的手工编写的类中,以捕捉它不同步的情况。这比源代码生成器的工作量要少,但是需要更多的持续维护。如果你的资源不经常改变,这可能是更好的选择。

gudnpqoy

gudnpqoy2#

将自动生成的静态设计器类转换为基于示例的设计器类的原始解决方案是创建T4模板(ThreadSafeTranslation.tt 文件):

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#
var fileName = "TranslationBase.Designer.cs";

var oldName = "TranslationBase";
var newName = "Translation";

var resourcePath = this.Host.ResolvePath(fileName);
var code = File.ReadAllText(resourcePath);

code = code.Replace($"internal class {oldName} {{", $"internal class {newName} {{");
code = code.Replace($"internal {oldName}() {{", $"internal {newName}() {{");
code = code.Replace($"({oldName}).Assembly);", $"({newName}).Assembly);");

code = code.Replace("private static global::System.Globalization.CultureInfo resourceCulture;",
    "private global::System.Globalization.CultureInfo resourceCulture;");

code = code.Replace("internal static global::System.Globalization.CultureInfo Culture {",
    "internal global::System.Globalization.CultureInfo Culture {");

code = code.Replace("internal static string ", "internal string ");
#>
<#=code#>

我还编辑了.cproj,使这个T4模板在每次构建时都可执行(我不知道如何仅在更改依赖文件时触发它):

<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec Command="&quot;$(DevEnvDir)TextTransform.exe&quot; -out &quot;$(ProjectDir)Translations\ThreadSafeTranslation.cs&quot; &quot;$(ProjectDir)Translations\ThreadSafeTranslation.tt&quot;" />
  </Target>

但这种方法有明显的缺陷:当我在.resx中更改键时,它会自动反映在ResXFileCodeGenerator生成的类中(如果是这样的话,也会反映在对该属性的所有引用中--这太棒了!2),但是它不会在对我生成的类的引用中更改...:(我必须查看代码并手动修复对前一字段的所有引用。
当然,如果您必须直接操作字符串值,那么在早期阶段看到这些错误要比得到运行时错误要好得多。

相关问题