.NET Localization Libraries
JSON-based localization for .NET with CLDR pluralization, strong typing via source generation, and full ASP.NET Core integration.
Available Packages
| Package | Description |
|---|---|
LocalizationManager.JsonLocalization |
JSON-based localization with IStringLocalizer support |
LocalizationManager.JsonLocalization.Generator |
Source generator for compile-time type safety |
Key Features
- JSON resources - Simple JSON files instead of .resx
- CLDR pluralization - Proper plural forms for 30+ languages
- Nested keys - Support for dot notation (e.g.,
Navigation.Home) - Culture fallback - Automatic fallback chain (fr-CA → fr → default)
- ASP.NET Core integration - Drop-in IStringLocalizer replacement
- Source generator - Compile-time type safety
Installation
NuGet Packages
# Main localization library
dotnet add package LocalizationManager.JsonLocalization
# Optional: Source generator for strong typing
dotnet add package LocalizationManager.JsonLocalization.Generator
Supported Frameworks
- .NET 7.0
- .NET 8.0
- .NET 9.0
Standalone Usage
Use the library without dependency injection for console apps or simple scenarios.
Resource File Structure
Create JSON files with culture suffixes:
Resources/
├── strings.json # Default language (English)
├── strings.fr.json # French
├── strings.de.json # German
└── strings.es.json # Spanish
Resource File Format
// strings.json
{
"Welcome": "Welcome to our app!",
"Navigation": {
"Home": "Home",
"Settings": "Settings",
"Profile": "Profile"
},
"Greeting": "Hello, {0}!",
"ItemCount": {
"one": "{0} item",
"other": "{0} items"
}
}
Creating the Localizer
using LocalizationManager.JsonLocalization;
// Create localizer with file system resources
var localizer = new JsonLocalizer(
resourcePath: "./Resources",
resourceName: "strings",
defaultCulture: "en"
);
// Get translations
string welcome = localizer["Welcome"];
string home = localizer["Navigation.Home"];
// With format arguments
string greeting = localizer["Greeting", "John"];
// Change culture
localizer.SetCulture("fr");
string welcomeFr = localizer["Welcome"]; // "Bienvenue!"
Embedded Resources
Load resources embedded in your assembly:
var localizer = new JsonLocalizer(
assembly: typeof(Program).Assembly,
resourceNamespace: "MyApp.Resources",
resourceName: "strings",
defaultCulture: "en"
);
Set the JSON files' Build Action to "Embedded Resource" in your .csproj or project properties.
ASP.NET Core Integration
Full integration with ASP.NET Core's localization infrastructure.
Service Registration
// Program.cs
builder.Services.AddJsonLocalization(options =>
{
options.ResourcesPath = "Resources";
options.DefaultCulture = "en";
});
Using IStringLocalizer
public class HomeController : Controller
{
private readonly IStringLocalizer<HomeController> _localizer;
public HomeController(IStringLocalizer<HomeController> localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
ViewData["Title"] = _localizer["Welcome"];
return View();
}
}
In Razor Views
@inject IStringLocalizer<SharedResource> Localizer
<h1>@Localizer["Welcome"]</h1>
<p>@Localizer["Greeting", User.Identity.Name]</p>
Configuration Options
builder.Services.AddJsonLocalization(options =>
{
// Path to JSON resource files
options.ResourcesPath = "Resources";
// Default culture when no match found
options.DefaultCulture = "en";
// Supported cultures
options.SupportedCultures = new[] { "en", "fr", "de", "es" };
// Use embedded resources instead of file system
options.UseEmbeddedResources = false;
// Assembly for embedded resources
options.ResourceAssembly = typeof(Program).Assembly;
});
OTA (Over-The-Air) Localization
Update translations in real-time without redeploying your application. LRM is the first and only OTA localization solution for .NET!
For comprehensive OTA documentation including API reference, coming SDK announcements, and sample projects, see the dedicated OTA SDKs page.
OTA localization allows your .NET application to fetch translations from LRM Cloud at runtime, automatically syncing updates without code changes or redeployment.
How It Works
- Your app starts and uses embedded/local resources immediately
- A background service fetches translations from LRM Cloud
- Translations sync automatically (default: every 5 minutes)
- Changes in LRM Cloud reflect in your app without redeployment
Quick Setup
// Program.cs
builder.Services.AddJsonLocalizationWithOta(options =>
{
options.UseOta(
endpoint: "https://lrm-cloud.com",
apiKey: "lrm_your_read_only_api_key",
project: "@username/my-project" // or "org/project"
);
// Optional: Configure refresh interval (default: 5 minutes)
options.Ota!.RefreshInterval = TimeSpan.FromMinutes(5);
// Optional: Fall back to local resources when offline
options.FallbackToLocal = true;
options.ResourcesPath = "Resources"; // Local fallback path
});
Creating an API Key
- Go to Project Settings → API Keys in LRM Cloud
- Click "Create API Key"
- Select "Read" scope (sufficient for OTA)
- Copy the key (starts with
lrm_)
Configuration Options
| Option | Default | Description |
|---|---|---|
Endpoint |
https://lrm-cloud.com |
LRM Cloud API endpoint |
ApiKey |
- | API key with read scope (required) |
Project |
- | Project path: @user/project or org/project |
RefreshInterval |
5 minutes | How often to check for updates |
FallbackToLocal |
true |
Use local resources when offline |
Timeout |
10 seconds | HTTP request timeout |
MaxRetries |
3 | Retry attempts for failed requests |
Supported Platforms
OTA works with the entire .NET ecosystem:
- ASP.NET Core (Web APIs, MVC, Razor Pages)
- Blazor (Server + WebAssembly)
- .NET MAUI (iOS, Android, Windows, macOS)
- Avalonia (Cross-platform desktop)
- WPF and WinForms
- Console applications
- Azure Functions / AWS Lambda
- Worker Services
Network Resilience
The OTA client includes built-in resilience features:
- Retry with exponential backoff - Automatically retries failed requests
- Circuit breaker - Stops requests after repeated failures, auto-recovers
- ETag caching - Efficient bandwidth usage, only fetches when changed
- Graceful fallback - Uses local resources when cloud is unavailable
Generator Package Compatibility
When using OTA with the source generator package:
- Generated classes work for compile-time keys
- New OTA keys use dynamic access:
Strings.Localizer["NewKey"]
- Fix typos instantly - No redeployment needed
- Add new languages - Without code changes
- A/B test translations - Update specific strings
- Emergency updates - Correct mistakes immediately
Pluralization
The library supports CLDR plural rules for 30+ languages with the following categories:
| Category | Example (English) | Description |
|---|---|---|
zero |
0 items | Zero quantity (some languages) |
one |
1 item | Singular |
two |
2 items | Dual (Arabic, Welsh, etc.) |
few |
2-4 items | Few (Slavic languages) |
many |
5+ items | Many (Slavic, Arabic) |
other |
N items | Default/plural (required) |
Plural Resource Format
// strings.json (English - needs one/other)
{
"ItemCount": {
"one": "{0} item",
"other": "{0} items"
}
}
// strings.ru.json (Russian - needs one/few/many/other)
{
"ItemCount": {
"one": "{0} элемент",
"few": "{0} элемента",
"many": "{0} элементов",
"other": "{0} элемента"
}
}
// strings.ar.json (Arabic - needs zero/one/two/few/many/other)
{
"ItemCount": {
"zero": "لا عناصر",
"one": "عنصر واحد",
"two": "عنصران",
"few": "{0} عناصر",
"many": "{0} عنصرًا",
"other": "{0} عنصر"
}
}
Using Pluralization
// The library automatically selects the correct plural form
string text1 = localizer.GetPlural("ItemCount", 1); // "1 item"
string text5 = localizer.GetPlural("ItemCount", 5); // "5 items"
// With IStringLocalizer (uses first argument as count)
string text = _localizer["ItemCount", count];
Source Generator
Generate strongly-typed accessors at compile time for type safety and IntelliSense support.
Installation
dotnet add package LocalizationManager.JsonLocalization.Generator
Setup
Mark your JSON resource file as an AdditionalFile:
<!-- In your .csproj -->
<ItemGroup>
<AdditionalFiles Include="Resources/strings.json" />
</ItemGroup>
Generated Code
The generator creates a static class with properties for each key:
// Generated code (auto-generated, do not edit)
public static partial class Strings
{
public static string Welcome => _localizer["Welcome"];
public static class Navigation
{
public static string Home => _localizer["Navigation.Home"];
public static string Settings => _localizer["Navigation.Settings"];
public static string Profile => _localizer["Navigation.Profile"];
}
public static string Greeting(object arg0) =>
_localizer["Greeting", arg0];
public static string ItemCount(int count) =>
_localizer.GetPlural("ItemCount", count);
}
Usage
// Strong typing with IntelliSense
string welcome = Strings.Welcome;
string home = Strings.Navigation.Home;
string greeting = Strings.Greeting("John");
string items = Strings.ItemCount(5);
// Compile-time error if key doesn't exist
// string invalid = Strings.NonExistent; // Error!
- Type safety - Compile-time errors for missing keys
- IntelliSense - Full autocomplete support
- Refactoring - Rename keys safely across your codebase
- No runtime reflection - Better performance
Sample Projects
Explore working examples on GitHub:
| Sample | Description |
|---|---|
| ConsoleApp.Standalone | Console app with file system resources |
| ConsoleApp.Embedded | Console app with embedded resources |
| ConsoleApp.SourceGenerator | Console app with source generator |
| WebApp.AspNetCore | ASP.NET Core web app with full integration |
Running Samples
# Clone the repository
git clone https://github.com/nickprotop/LocalizationManager.git
cd LocalizationManager
# Run a sample
cd samples/ConsoleApp.Standalone
dotnet run