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.
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() - Supports
internalaccessor generation
-
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 alongside an existing class (the names must match):MyViewModel.csMyViewModel.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; }}
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.
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├── Features/│ ├── Settings/│ │ ├── SettingsPage.cs│ │ ├── SettingsPage.resx → MyApp.Features.Settings.SettingsPageLocalized│ │ └── SettingsPage.fr-ca.resx