name: localizegen description: Generate strongly-typed localization classes from .resx resource files using Shiny.Extensions.Localization.Generator for .NET applications auto_invoke: true triggers: - localization - localize - resx - resource file - IStringLocalizer - StringLocalizer - strongly typed localization - AddStronglyTypedLocalizations - Localized class - localizegen - Shiny.Extensions.Localization.Generator - i18n - internationalization - translate - translation
Shiny Localization Generator Skill
You are an expert in Shiny.Extensions.Localization.Generator, a Roslyn incremental source generator that creates strongly-typed localization classes from .resx resource files for use with Microsoft.Extensions.Localization.
When to Use This Skill
Invoke this skill when the user wants to:
- Add localization/internationalization to a .NET application using
.resxfiles - Create strongly-typed localization wrappers around resource files
- Set up
Microsoft.Extensions.Localizationwith generated code - Add new
.resxresource files with matching classes - Use format string parameters in localized strings
- Configure public vs internal accessor generation
- Bind localized strings in XAML or Razor views with IntelliSense support
Library Overview
NuGet: Shiny.Extensions.Localization.Generator
Namespace: Generated classes use the namespace derived from the project's RootNamespace and folder structure
Target: .NET Standard 2.0 (works with any modern .NET application)
Shiny.Extensions.Localization.Generator is a Roslyn incremental source generator that:
- Reads
.resxfiles at compile time and generates strongly-typed C# classes - Creates properties for simple strings and methods for format strings (e.g.,
"Hello {0}") - Generates a
ServiceCollectionExtensionsclass withAddStronglyTypedLocalizations()for DI registration - Supports XML documentation comments from
.resxcomment fields - Respects folder structure for namespace computation
- Supports public or internal accessor generation via MSBuild property
Setup
1. Install NuGet Packages
dotnet add package Microsoft.Extensions.Localization
dotnet add package Shiny.Extensions.Localization.Generator
2. Register in DI
builder.Services.AddLocalization();
builder.Services.AddStronglyTypedLocalizations();
Important Notes
- Every
.resxfile must have a corresponding C# class with the same name in the same namespace (e.g.,MyViewModel.resxrequiresMyViewModel.cs) - If the associated class does not exist, the generated code will produce a compile error
- Only the default locale
.resxfile is processed (e.g.,MyViewModel.resx), locale-specific files likeMyViewModel.fr-ca.resxare handled by the localization framework at runtime - The generator produces a
{ClassName}Localizedclass (e.g.,MyViewModelLocalized) for each.resxfile
How It Works
Directory Structure
MyApp/
├── MyViewModel.cs
├── MyViewModel.resx # Default locale (English)
├── MyViewModel.fr-ca.resx # French-Canadian locale
├── Folder1/
│ ├── FolderViewModel.cs
│ ├── FolderViewModel.resx
│ └── FolderViewModel.fr-ca.resx
Generated Output
MyApp/
├── MyViewModelLocalized.g.cs # RootNamespace.MyViewModelLocalized
├── Folder1.FolderViewModelLocalized.g.cs # RootNamespace.Folder1.FolderViewModelLocalized
├── ServiceCollectionExtensions.g.cs # DI registration extension method
Resource Key Rules
- Dots (
.), hyphens (-), and spaces in resource keys are converted to underscores (_) - Format string parameters like
{0},{1}are detected automatically - When format parameters are present, a method is generated instead of a property
- Method names get a
Formatsuffix unless the key already ends withFormat
Code Generation Instructions
When working with this library:
1. Resource Files (.resx)
Create .resx files alongside their associated C# class:
<?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>
2. Associated Class
The class must exist with the same name and namespace as the .resx file:
namespace MyApp;
public class MyViewModel
{
public MyViewModel(MyViewModelLocalized localizer)
{
this.Localizer = localizer;
}
public MyViewModelLocalized Localizer { get; }
}
3. Generated Class Structure
The generator creates:
namespace MyApp;
public partial class MyViewModelLocalized
{
readonly IStringLocalizer localizer;
public MyViewModelLocalized(IStringLocalizer<MyViewModel> localizer)
{
this.localizer = localizer;
}
public IStringLocalizer Localizer => this.localizer;
/// <summary>
/// Greeting shown on the home page
/// </summary>
public string Welcome => this.localizer["Welcome"];
/// <summary>
/// Personalized greeting with user name and app name
/// </summary>
public string UserGreetingFormat(object parameter0, object parameter1)
{
return string.Format(this.localizer["UserGreeting"], parameter0, parameter1);
}
/// <summary>
/// You have {0} items in your cart
/// </summary>
public string ItemCountFormat(object parameter0)
{
return string.Format(this.localizer["ItemCount"], parameter0);
}
}
4. XAML Binding
Bind in XAML with full IntelliSense:
<Label Text="{Binding Localizer.Welcome}" />
5. File Organization
Place files following standard .NET conventions:
- Resource files: alongside their associated class file
- Locale variants: same location with locale suffix (e.g.,
MyClass.fr-ca.resx) - Folder structure determines namespace:
Folder1/MyClass.resx→RootNamespace.Folder1
Configuration
Internal Accessor
To generate classes with internal instead of public access modifier:
<PropertyGroup>
<GenerateLocalizersInternal>True</GenerateLocalizersInternal>
</PropertyGroup>
Usage Examples
Simple property access:
var localizer = services.GetRequiredService<MyViewModelLocalized>();
Console.WriteLine(localizer.Welcome);
Format string method:
Console.WriteLine(localizer.UserGreetingFormat("Allan", "MyApp"));
// Output: Hello Allan, welcome to MyApp!
XAML binding in MAUI:
<Label Text="{Binding Localizer.Welcome}" />
ASP.NET/Blazor usage:
@inject MyViewModelLocalized Localizer
<h1>@Localizer.Welcome</h1>
<p>@Localizer.UserGreetingFormat(User.Name, "MyApp")</p>
Best Practices
- Always create the associated class - Every
.resxfile must have a matching C# class or a compile error will occur - Use comments in .resx - Comments become XML documentation on generated properties/methods
- Use format parameters for dynamic content -
{0},{1}etc. generate type-safe methods - Use default locale for generation - Only
ClassName.resxis processed;ClassName.fr-ca.resxis for runtime localization - Inject the Localized class - Use DI to inject
{ClassName}Localized, notIStringLocalizerdirectly - Expose the raw localizer - The generated class exposes
Localizerproperty for edge cases needingIStringLocalizer - Namespace follows folder structure - Organize
.resxfiles in folders to match your namespace conventions
Reference Files
For detailed templates and examples, see:
reference/templates.md- Code generation templates for common patternsreference/api-reference.md- Full API surface, MSBuild properties, and troubleshooting
Common Packages
dotnet add package Shiny.Extensions.Localization.Generator # Source generator
dotnet add package Microsoft.Extensions.Localization # Localization framework
dotnet add package Microsoft.Extensions.DependencyInjection # DI container