You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过源生成器自动为Resx文件添加资源键值对?

实现开发模式下自动填充本地化键的源生成器

完全可以实现这样的源生成器,核心是利用.NET的源生成器在编译时扫描代码中的本地化调用,提取键并自动补充到主语言的.resx文件中,且仅在开发模式运行。以下是具体实现思路和关键步骤:

核心实现步骤

1. 创建源生成器项目

创建类库项目,引用必要的NuGet包:

  • Microsoft.CodeAnalysis.CSharp:用于语法和语义分析
  • Microsoft.CodeAnalysis.Analyzers:提供源生成器基础框架
  • System.Xml:用于解析和修改.resx文件(本质为XML格式)

在项目文件中标记为源生成器:

<PropertyGroup>
  <TargetFramework>netstandard2.0</TargetFramework>
  <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>

<ItemGroup>
  <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
  <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
</ItemGroup>

2. 扫描代码提取本地化键

实现ISourceGenerator接口,在Execute方法中完成以下操作:

  • 先判断当前编译模式是否为Debug,非Debug模式直接跳过执行
  • 遍历所有语法树,定位IStringLocalizer/IHtmlLocalizer的索引器调用(即_localizer["key"]格式的代码)
  • 通过语义分析确认调用对象的类型确实是本地化接口,避免误判
  • 提取字符串字面量形式的键值(动态生成的键无法在编译时确定,直接跳过)

3. 修改主.resx文件

找到项目中的主语言.resx文件(比如Resources.resx):

  • 解析.resx的XML结构,检查目标键是否已存在
  • 若不存在,则新增<data>节点,设置name属性为提取的键,<value>节点内容与键相同
  • 保存修改后的XML文件

4. 限制仅开发模式运行

通过编译配置判断是否执行逻辑:

if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_configuration", out var config) || config != "Debug")
{
    return;
}

关键代码示例

以下是源生成器的核心逻辑片段:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Xml;

[Generator]
public class LocalizationKeyGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // 无需额外初始化操作
    }

    public void Execute(GeneratorExecutionContext context)
    {
        // 仅在Debug模式运行
        if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_configuration", out var config) || config != "Debug")
        {
            return;
        }

        // 遍历所有语法树
        foreach (var tree in context.Compilation.SyntaxTrees)
        {
            var root = tree.GetRoot(context.CancellationToken);
            var elementAccesses = root.DescendantNodes().OfType<ElementAccessExpressionSyntax>();

            foreach (var elementAccess in elementAccesses)
            {
                // 定位_localizer的成员访问
                if (elementAccess.Expression is not MemberAccessExpressionSyntax memberAccess)
                    continue;

                // 语义分析确认类型
                var semanticModel = context.Compilation.GetSemanticModel(tree);
                var symbol = semanticModel.GetSymbolInfo(memberAccess.Expression).Symbol;
                if (symbol == null || (!symbol.ToString().Contains("IStringLocalizer") && !symbol.ToString().Contains("IHtmlLocalizer")))
                    continue;

                // 提取字符串字面量键
                if (elementAccess.ArgumentList.Arguments.First().Expression is not LiteralExpressionSyntax literal)
                    continue;

                var key = literal.Token.Value.ToString();
                AddKeyToMainResx(context, key);
            }
        }
    }

    private void AddKeyToMainResx(GeneratorExecutionContext context, string key)
    {
        // 找到主resx文件(示例为Resources.resx)
        var resxFile = context.AdditionalFiles.FirstOrDefault(f => f.Path.EndsWith("Resources.resx"));
        if (resxFile == null) return;

        var xmlDoc = new XmlDocument();
        xmlDoc.Load(resxFile.Path);

        // 检查键是否已存在
        var existingKey = xmlDoc.SelectSingleNode($"//data[@name='{key}']");
        if (existingKey != null) return;

        // 创建新的本地化节点
        var dataNode = xmlDoc.CreateElement("data");
        dataNode.SetAttribute("name", key);
        dataNode.SetAttribute("xml:space", "preserve");

        var valueNode = xmlDoc.CreateElement("value");
        valueNode.InnerText = key;
        dataNode.AppendChild(valueNode);

        xmlDoc.DocumentElement.AppendChild(dataNode);
        xmlDoc.Save(resxFile.Path);
    }
}

注意事项

  • 避免重复操作:每次编译仅添加不存在的键,防止重复写入
  • 文件权限:确保开发环境下源生成器有.resx文件的写入权限
  • 动态键处理:对于非字面量的动态键(如变量、表达式),编译时无法提取,需手动补充
  • 多项目适配:若解决方案包含多个项目,需确保源生成器仅处理当前项目的.resx文件
  • XML格式兼容:修改.resx时保持原有XML格式(如编码、缩进),避免破坏文件结构

内容的提问来源于stack exchange,提问作者Enricosoft

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 23:22:09