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 | |
| Downloads |
Features
Section titled “Features”- Strongly-typed localization classes generated from
.resxfiles at compile time - Properties for simple strings, methods for format strings (
{0},{1}, etc.) - XML documentation comments generated from
.resxcomment fields — see default values in IntelliSense - Full XAML and Razor binding support with IntelliSense
- Namespace follows folder structure automatically
- DI registration generated via
AddStronglyTypedLocalizations() - No empty stub classes: a
.resxwithout a matching class gets one generated - Generated classes follow the visibility of their class (
publicorinternal) - Reflection-free
GeneratedLocalizers.Alllist, safe under trimming and NativeAOT
-
Install the NuGet packages:
Terminal window dotnet add package Microsoft.Extensions.Localizationdotnet add package Shiny.Extensions.Localization.Generator -
Register in your DI container:
builder.Services.AddStronglyTypedLocalizations(); // also calls AddLocalization() internally -
Create a
.resxfile. If it sits next to a class with the same name, that class is used; otherwise one is generated for you:MyViewModel.cs ← optionalMyViewModel.resx ← default localeMyViewModel.fr-ca.resx ← French-Canadian locale -
Inject the generated
{ClassName}Localizedclass:public class MyViewModel{public MyViewModel(MyViewModelLocalized localizer)=> this.Localizer = localizer;public MyViewModelLocalized Localizer { get; }}
How It Works
Section titled “How It Works”Resource File
Section titled “Resource File”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>Generated Class
Section titled “Generated Class”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;}Key Naming Rules
Section titled “Key Naming Rules”- 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
Formatsuffix unless the key already ends withFormat
Directory Structure and Namespaces
Section titled “Directory Structure and Namespaces”The folder structure determines the generated namespace:
MyApp/├── MyViewModel.cs├── MyViewModel.resx → MyApp.MyViewModelLocalized├── MyViewModel.fr-ca.resx├── Errors.Validation.resx → MyApp.Errors.ValidationLocalized├── Errors.Validation.fr-ca.resx├── Features/│ ├── Settings/│ │ ├── SettingsPage.cs│ │ ├── SettingsPage.resx → MyApp.Features.Settings.SettingsPageLocalized│ │ └── SettingsPage.fr-ca.resxDots in a file name become namespace segments: Errors.Validation.resx pairs with the class Validation in MyApp.Errors. That’s the name MSBuild gives the embedded resource, so IStringLocalizer<T> finds it.


