使用本地化感知语言键构建 C# 应用程序

Building C# application with localization-aware language keys

简介

我正在寻找更个性化的解决方案来翻译我的应用程序。我将在 获取条目后使用 HumanizerSmart.Format 。问题是首先定义密钥以获取它们。

要求

要求是:

  1. 语言键必须在代码中定义,最好在使用它们的地方附近
  2. 语言键必须包含默认英语值
  3. 必须列出所有语言键(XML、CSV、JSON、任何内容)构建应用套件后
  4. 语言条目必须从外部源(如 JSON 文件)提供,无需任何类型的重新编译
  5. 该应用程序可能包含多个可执行文件、共享库等。所有这些都以 C# 应用程序的形式出现

废弃解

首先,我丢弃的东西:

  • 内置 C# Resources.dll;他们违反了 (1) 和 (4)
  • 带密钥的外部文件。违反 (1)

我的处理思路

现在,我的解决方案想法看起来就是这样(并且受到 C++ GetText 的启发)

有一个模板 class,其中包含键:

private sealed class Module1Keys : LocalizationKeys<Module1Keys>
{
    public static readonly LocalizationKey HelloWorld = DefineKey("/foo", "Hello World!");
    public static readonly LocalizationKey HelloWorld2 = DefineKey("/bar", "Hello World2!");
}

并且 class LocalizationKeys 包含一个静态方法,该方法实际上会在简单集合中注册键

public abstract class LocalizationKeys<T> where T : LocalizationKeys<T>
{
    protected static LocalizationKey DefineKey(string path, string english)
    {
        var ret = new LocalizationKey(typeof(T), path, english);
        // Following registers localization key in runtime:
        Localization.Instance.RegisterLocalizableKey(ret);

        return ret;
    }
}

问题

在这种方法中要处理的事情是在构建期间列出可本地化的键...这是我撞墙的地方。在 运行 期间很容易列出它们,但我 不能 运行 构建时间 上的代码(特别是它可以构建为共享库)。

也许我想多了,有更好、更干净的解决方案 - 我不需要坚持使用这个解决方案,但谷歌搜索并没有产生更好的结果......

搞定了。在 GetText 次中,我们不得不求助于手动解析代码。

...但是现在,有了 CSharp,我们有了 Roslyn,有了 CodeAnalysis API。

解决方案

连接包含 Microsoft.CodeAnalysis NuGet 的自定义控制台构建工具并使用如下代码:

var model = compilation.GetSemanticModel(tree);
var methods = root.DescendantNodes().OfType<InvocationExpressionSyntax>();

foreach(var method in methods)
{
    if(model.GetSymbolInfo(method).Symbol is IMethodSymbol symbol &&
        symbol.ContainingNamespace.Name == "MyProject" &&
        symbol.ContainingType.Name == "LocalizationKeys" &&
        symbol.Name == "DefineKey")
    {
        var key = method.ArgumentList.Arguments.FirstOrDefault();
        var eng = method.ArgumentList.Arguments.Skip(1).FirstOrDefault();

        if(key.Expression is LiteralExpressionSyntax literalKey &&
            eng.Expression is LiteralExpressionSyntax literalEng)
        {
            // "/foo" -> "Hello World!"
            // "/bar" -> "Hello World2!"
            Console.WriteLine(literalKey + " -> " + literalEng);
        }
        else
        {
            // Bonus: detect violation of key definition rule. It is not a literal!
        }
    }
}

将此控制台工具编译为可执行文件并将其添加为 post-构建步骤。利润.