Skip to content
Welcome to our documentation!

Localization Generator

Microsoft.Extensions.Localization provides great abstractions, but you lose the strong typing and IDE tooling that .resx files originally gave you. This library fixes that — it source-generates a strongly-typed class for each .resx file, giving you compile-time safety, IntelliSense, and format-string methods with the correct number of parameters.

  • GitHub stars for shinyorg/localizegen
  • NuGet downloads for Shiny.Extensions.Localization.Generator
Frameworks
.NET
  • Strongly-typed localization classes generated from .resx files at compile time
  • Properties for simple strings, methods for format strings ({0}, {1}, etc.)
  • XML documentation comments generated from .resx comment fields — see default values in IntelliSense
  • Full XAML and Razor binding support with IntelliSense
  • Namespace follows folder structure automatically
  • DI registration generated via AddStronglyTypedLocalizations()
  • Supports internal accessor generation
  1. Install the NuGet packages:

    Terminal window
    dotnet add package Microsoft.Extensions.Localization
    dotnet add package Shiny.Extensions.Localization.Generator
  2. Register in your DI container:

    builder.Services.AddStronglyTypedLocalizations(); // also calls AddLocalization() internally
  3. Create a .resx file alongside an existing class (the names must match):

    MyViewModel.cs
    MyViewModel.resx ← default locale
    MyViewModel.fr-ca.resx ← French-Canadian locale
  4. Inject the generated {ClassName}Localized class:

    public class MyViewModel
    {
    public MyViewModel(MyViewModelLocalized localizer)
    => this.Localizer = localizer;
    public MyViewModelLocalized Localizer { get; }
    }

Every .resx file must have a matching C# class with the same name in the same namespace. If the class doesn’t exist, the generated code will produce a compile error.

Create a .resx file with your localization keys:

<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="Welcome" xml:space="preserve">
<value>Welcome to our app!</value>
<comment>Greeting shown on the home page</comment>
</data>
<data name="UserGreeting" xml:space="preserve">
<value>Hello {0}, welcome to {1}!</value>
<comment>Personalized greeting with user name and app name</comment>
</data>
<data name="ItemCount" xml:space="preserve">
<value>You have {0} items in your cart</value>
</data>
</root>

The generator produces a class with properties for simple strings and methods for format strings:

public partial class MyViewModelLocalized
{
readonly IStringLocalizer localizer;
public MyViewModelLocalized(IStringLocalizer<MyViewModel> localizer)
=> this.localizer = localizer;
/// <summary>
/// Greeting shown on the home page
/// Default: Welcome to our app!
/// </summary>
public string Welcome => this.localizer["Welcome"];
/// <summary>
/// Personalized greeting with user name and app name
/// Default: Hello {0}, welcome to {1}!
/// </summary>
public string UserGreetingFormat(object parameter0, object parameter1)
=> string.Format(this.localizer["UserGreeting"], parameter0, parameter1);
/// <summary>
/// Default: You have {0} items in your cart
/// </summary>
public string ItemCountFormat(object parameter0)
=> string.Format(this.localizer["ItemCount"], parameter0);
/// <summary>
/// Access the underlying IStringLocalizer for edge cases
/// </summary>
public IStringLocalizer Localizer => this.localizer;
}
  • Dots (.), hyphens (-), and spaces in resource keys are converted to underscores (_)
  • Format parameters ({0}, {1}, etc.) are detected automatically and generate a method
  • Method names get a Format suffix unless the key already ends with Format

The folder structure determines the generated namespace:

MyApp/
├── MyViewModel.cs
├── MyViewModel.resx → MyApp.MyViewModelLocalized
├── MyViewModel.fr-ca.resx
├── Features/
│ ├── Settings/
│ │ ├── SettingsPage.cs
│ │ ├── SettingsPage.resx → MyApp.Features.Settings.SettingsPageLocalized
│ │ └── SettingsPage.fr-ca.resx