Darp.Utils.ResxSourceGenerator 2.2.0

dotnet add package Darp.Utils.ResxSourceGenerator --version 2.2.0
                    
NuGet\Install-Package Darp.Utils.ResxSourceGenerator -Version 2.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Darp.Utils.ResxSourceGenerator" Version="2.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Darp.Utils.ResxSourceGenerator" Version="2.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Darp.Utils.ResxSourceGenerator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Darp.Utils.ResxSourceGenerator --version 2.2.0
                    
#r "nuget: Darp.Utils.ResxSourceGenerator, 2.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Darp.Utils.ResxSourceGenerator@2.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Darp.Utils.ResxSourceGenerator&version=2.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Darp.Utils.ResxSourceGenerator&version=2.2.0
                    
Install as a Cake Tool

Darp.Utils.ResxSourceGenerator

A source generator for generating strongly typed singleton resource classes from .resx files.

This implementation was made to support easy updates of the language in the UI by listening to the LanguageChanged event and making the class non-static for easier binding and usage as type parameter.

If this does not meet your expectations, check out:

Usage

To generate sources for your .resx file, simply include the latest version of the source generator in the corresponding .csproj

<Project>
    <ItemGroup>
        <PackageReference Include="Darp.Utils.ResxSourceGenerator" Version="X.Y.Z" PrivateAssets="all"/>
    </ItemGroup>
    <ItemGroup>
        <EmbeddedResource Update="**\*.resx" EmitFormatMethods="true"/>
    </ItemGroup>
</Project>

<details> <summary>Generated code</summary>

// <auto-generated/>

#nullable enable

namespace TestProject
{
    /// <summary>A strongly typed resource class for '/0/Resources.resx'</summary>
    internal sealed partial class Resources
    {
        private static Resources? _default;
        /// <summary>The Default implementation of <see cref="Resources"/></summary>
        public static Resources Default => _default ??= new Resources();

        public delegate void CultureChangedDelegate(global::System.Globalization.CultureInfo? oldCulture, global::System.Globalization.CultureInfo? newCulture);
        /// <summary>Called after the <see cref="Culture"/> was updated. Provides previous culture and the newly set culture</summary>
        public event CultureChangedDelegate? CultureChanged;

        private global::System.Globalization.CultureInfo? _culture;
        /// <summary>Get or set the Culture to be used for all resource lookups issued by this strongly typed resource class.</summary>
        public System.Globalization.CultureInfo? Culture
        {
            get => _culture;
            set
            {
                System.Globalization.CultureInfo? oldCulture = _culture;
                _culture = value;
                if (!System.Collections.Generic.EqualityComparer<System.Globalization.CultureInfo>.Default.Equals(oldCulture, value))
                    CultureChanged?.Invoke(oldCulture, value);
            }
        }

        ///<summary>Returns the cached ResourceManager instance used by this class.</summary>
        [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        public global::System.Resources.ResourceManager ResourceManager { get; } = new global::System.Resources.ResourceManager("TestProject.Resources", typeof(Resources).Assembly);

        /// <summary>Get a resource of the <see cref="ResourceManager"/> with the configured <see cref="Culture"/> as a string</summary>
        /// <param name="resourceKey">The name of the resource to get</param>
        /// <returns>Returns the resource value as a string or the <paramref name="resourceKey"/> if it could not be found</returns>
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public string GetResourceString(string resourceKey) => ResourceManager.GetString(resourceKey, Culture) ?? resourceKey;

        /// <summary>Get the resource of <see cref="Keys.@Name"/></summary>
        /// <value>value {x}</value>
        public string @Name => GetResourceString(Keys.@Name);
        /// <summary>Format the resource of <see cref="Keys.@Name"/></summary>
        /// <value>value {x}</value>
        /// <param name="x">The parameter to be used at position {0}</param>
        /// <returns>The formatted <see cref="Keys.@Name"/> string</returns>
        public string @FormatName(object? x) => string.Format(this.Culture, global::TestProject.Resources.ReplaceNamedFormatItems(this.@Name, new[] { "x" }), x);

        /// <summary>All keys contained in <see cref="Resources"/></summary>
        public static class Keys
        {
            /// <summary> <list type="table">
            /// <item> <term><b>Default</b></term> <description>value {x}</description> </item>
            /// <item> <term><b>de-DE</b></term> <description>DE: value {x}</description> </item>
            /// </list> </summary>
            public const string @Name = @"Name";
        }

        /// <summary>Replace the names of format items like <c>{name:T}</c> with their index in <paramref name="names"/></summary>
        private static string ReplaceNamedFormatItems(string value, string[] names)
        {
            global::System.Text.StringBuilder? builder = null;
            var appendFrom = 0;
            for (var i = 0; i < value.Length; i++)
            {
                if (value[i] != '{')
                    continue;
                if (i + 1 < value.Length && value[i + 1] == '{')
                {
                    i++;
                    continue;
                }

                var nameStart = i + 1;
                var nameEnd = nameStart;
                while (nameEnd < value.Length && value[nameEnd] != '}' && value[nameEnd] != ',' && value[nameEnd] != ':' && value[nameEnd] != ' ')
                {
                    nameEnd++;
                }

                var index = global::System.Array.IndexOf(names, value.Substring(nameStart, nameEnd - nameStart));
                if (index < 0)
                    continue;

                builder ??= new global::System.Text.StringBuilder(value.Length);
                builder.Append(value, appendFrom, nameStart - appendFrom).Append(index);
                appendFrom = nameEnd;
            }

            if (builder == null)
                return value;
            return builder.Append(value, appendFrom, value.Length - appendFrom).ToString();
        }
    }
}

</details>

This generated code includes features like

  • A lazy Default instance which can be used to access the resources
  • A CultureChanged event which will be called each time the language was changed
  • FormatABC methods which will be generated for each entry with at least one argument
  • A Keys class which provides all the keys
  • A preview of all possible strings in the XML doc comment

It can be used like this:

string key = TestProject.Resources.Keys.Name; // Name
string nameTemplate = TestProject.Resources.Default.Name; // value {x}
string formattedName = TestProject.Resources.Default.FormatName(x: "1"); // value 1

TestProject.Resources.Default.Culture = CultureInfo.GetCultureInfo("de");
string germanNameTemplate = TestProject.Resources.Default.Name; // DE: value {x}

Configuration

By default, the following configuration is used:

<Project>
    <PropertyGroup>
        <ResxSourceGenerator_SkipShowResxFilesInHierarchy>true</ResxSourceGenerator_SkipShowResxFilesInHierarchy>
        <ResxSourceGenerator_EmitDebugInformation>false</ResxSourceGenerator_EmitDebugInformation>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Darp.Utils.ResxSourceGenerator" Version="X.Y.Z" PrivateAssets="all"/>
    </ItemGroup>
    <ItemGroup>
        
        <EmbeddedResource Update="Localization\Resources.resx"
                          GenerateSource="true"
                          RelativeDir=""
                          ClassName=""
                          EmitFormatMethods="false"
                          Public="false"/>
    </ItemGroup>
</Project>

SkipShowResxFilesInHierarchy

By default, there will be a target enabled adding a hierarchy to all of your .resx files with the same name:

| Localization
    | Resources1.resx
        | Resources1.en-US.resx
        | Resources1.de.resx
    | Resources2.resx

To disable this behavior, add

    <PropertyGroup>
        <ResxSourceGenerator_SkipShowResxFilesInHierarchy>true</ResxSourceGenerator_SkipShowResxFilesInHierarchy>
    </PropertyGroup>

EmitDebugInformation

You might want to see further debug information about the settings used to generate the class. A header comment will be generated similar to this test

To enable debug data add

    <PropertyGroup>
        <ResxSourceGenerator_EmitDebugInformation>true</ResxSourceGenerator_EmitDebugInformation>
    </PropertyGroup>

GenerateSource

You might not want to include some resource files. For this, specify GenerateSource="false"

    <ItemGroup>
        <EmbeddedResource Update="Localization\Resources.resx" GenerateSource="false"/>
    </ItemGroup>

RelativeDir

You might want to specify a relative path to the resource file after publish. The file will be generated similar to this test

Specify the directory like this:

    <ItemGroup>
        <EmbeddedResource Update="Localization\Resources.resx" RelativeDir="Relative.Directory"/>
    </ItemGroup>

ClassName

By default, the class will be called the way the resource file is. You can specify a custom class name as well The file will be generated similar to this test

Specify the class name like this:

    <ItemGroup>
        <EmbeddedResource Update="Localization\Resources.resx" ClassName="Some.Name"/>
    </ItemGroup>

EmitFormatMethods

For more safety when using parameters in your strings, you can generate FormatABC methods.

To generate them, add

    <ItemGroup>
        <EmbeddedResource Update="Localization\Resources.resx" EmitFormatMethods="true"/>
    </ItemGroup>

A FormatABC method is generated for each entry whose default value contains at least one format item. The property returns the localized template unchanged, and Keys.ABC contains the resource key. Both remain available when a formatting diagnostic prevents method generation.

Value Generated method
Hello {0} FormatABC(object? p0)
Hello {2} FormatABC(object? p0, object? p1, object? p2)
Hello {name} FormatABC(object? name)
{first} {last} {first} FormatABC(object? first, object? last)
{class} FormatABC(object? @class)
  • Format items follow the .NET composite formatting syntax {argument[,alignment][:formatString]}. Alignment and format string are passed to string.Format unchanged, e.g. {0,-8}, {timestamp:HH:mm} or {amount , 10 :N2}.
  • {{ and }} are escaped braces. {{name}} is no format item, {{{name}}} is the format item {name} surrounded by braces.
  • Numbered items get one parameter for each index from 0 to the highest index used, even if an index in between is missing. The highest supported index is 255 (see DarpResX007).
  • Named items use C# identifier names, including Unicode names such as año. Each name gets one parameter, in order of first appearance. Names are matched exactly, including in translations. Parameter names omit Unicode formatting characters that C# ignores; if this creates a collision, underscores are appended until the parameter name is unique. Distinct placeholder names keep distinct values. Keywords such as {class} are escaped in the generated parameter (@class), not in the resource template.
  • Parameters are defined by the default resource file. Translations can use the same names in any order; a name unknown to the default resource is left untouched and causes a FormatException when formatting.
  • Numbered and named items cannot be mixed in a single value (see DarpResX006).
  • A format method whose name matches a resource property or the containing class is omitted with DarpResX008. These comparisons follow C# identifier rules, including verbatim (@) names, Unicode escapes, and ignored Unicode formatting characters. Its resource property and key remain available; public methods are not silently renamed.

Public

You might need to use the resources outside the project the .resx file is located in.

To change the access modifier, add

    <ItemGroup>
        <EmbeddedResource Update="Localization\Resources.resx" Public="true"/>
    </ItemGroup>

Diagnostics

DarpResX001 - Empty resource file

This warning occurs, when the source generator has found a .resx file which does not specify any key-value pairs.

If you intended to skip this file, specify GenerateSource = false. Otherwise, add some values.

DarpResX002 - Invalid Key in resource file

This warning occurs, when the source generator has found an entry with an invalid key which is ignored. For example, those might be empty keys.

To fix this warning, add a valid key name.

DarpResX003 - Missing Value in resource file

This warning occurs, when the source generator has found an entry without a value specified.

To fix this warning, add a value to the key.

DarpResX004 - Duplicate Key in resource file

This warning occurs, when the source generator has found the same key a second time in the same resource file.

To fix this warning, rename the key of the duplicate entry.

DarpResX005 - Missing translation for specific Key

This warning occurs, when the source generator has found multiple related resource files but is missing a key in one of the resource files. Note: This warning will not occur, if a key is present in a translation but not in the default resource file.

To fix this warning, add a translation in the language specific resource file.

DarpResX006 - Mixed format argument styles

This warning occurs, when a value uses numbered and named format items at the same time, e.g. {0} {name}. The property and the key are still generated, but there will be no FormatABC method for this entry.

To fix this warning, use either numbered or named format items in the value.

DarpResX007 - Format item index out of range

This warning occurs, when a value uses a numbered format item with an index above 255, e.g. {256}. The property and the key are still generated, but there will be no FormatABC method for this entry.

To fix this warning, use smaller indices or named format items.

DarpResX008 - Format method name collision

This warning occurs when FormatABC would have the same name as a resource property or its containing class. For example, a formatted ABC entry cannot generate FormatABC when another resource key is already named FormatABC. The resource property and key are still generated, but the conflicting format method is omitted.

To generate the method, rename the conflicting resource key or configure a different class name.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.2.0 0 11/11/2026
2.1.3 633 9/6/2026
2.1.2 1,092 6/28/2026
2.1.1 172 6/27/2026
2.1.0 150 6/27/2026
2.0.0 157 6/24/2026
2.0.0-preview.1 203 5/5/2026
2.0.0-preview 123 4/29/2026
1.20.0 311 4/16/2026
1.19.2 702 10/24/2025
1.19.1 61,249 7/28/2025
1.19.0 208 7/27/2025
1.18.2 245 7/10/2025
1.18.1 294 7/7/2025
1.18.0 277 6/29/2025
1.17.4 191 6/28/2025
1.17.3 391 6/13/2025
1.17.2 369 6/12/2025
1.17.1 376 6/12/2025
1.17.0 284 6/8/2025
Loading failed