.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"
);
Embedded Resources

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!

Full OTA Documentation

For comprehensive OTA documentation including API reference, coming SDK announcements, and sample projects, see the dedicated OTA SDKs page.

What is OTA?

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

  1. Your app starts and uses embedded/local resources immediately
  2. A background service fetches translations from LRM Cloud
  3. Translations sync automatically (default: every 5 minutes)
  4. 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

  1. Go to Project SettingsAPI Keys in LRM Cloud
  2. Click "Create API Key"
  3. Select "Read" scope (sufficient for OTA)
  4. 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"]
Use Cases
  • 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!
Benefits
  • 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