Skip to content

deck-dev/TranslationDemo

Repository files navigation

Deck.Dev.Translation

A type-safe, source-generated translation system for .NET. Define your translations in a simple text file, get strongly-typed C# code with full IntelliSense, and resolve translations at runtime with zero boxing of value types.

Quick Start

1. Add the packages

In your project file, reference the three Translation libraries:

<ItemGroup>
  <!-- Core types (TranslationString, ITranslationProvider) -->
  <ProjectReference Include="..\Translation.Abstractions\Translation.Abstractions.csproj" />

  <!-- Source generator — loaded as an analyzer, not a runtime dependency -->
  <ProjectReference Include="..\Translation.Generator\Translation.Generator.csproj"
                    OutputItemType="Analyzer"
                    ReferenceOutputAssembly="false" />

  <!-- Runtime services (TemplateEngine, JsonTranslationProvider) -->
  <ProjectReference Include="..\Translation.Runtime\Translation.Runtime.csproj" />
</ItemGroup>

2. Create a translations file

Add a .lockeys file to your project. The file name becomes the generated class name.

Create Translations/App.lockeys:

# Navigation
[Nav]
Home : Home
Settings : Settings

# User-facing messages
[Messages]
Welcome : Welcome back, {userName|string}!
ItemCount : You have {count|int} items
OrderTotal : Total: {total:C2|decimal}

Tell MSBuild about it:

<ItemGroup>
  <AdditionalFiles Include="Translations\*.lockeys" />
</ItemGroup>

3. Use in code

The source generator creates a static class named App (matching the file name) with nested classes for each section:

// Simple keys — just use them as strings
string homeLabel = App.Nav.Home;
// "Home"

// Parameterized keys — call as methods
string greeting = App.Messages.Welcome("Marco");
// "Welcome back, Marco!"

string items = App.Messages.ItemCount(5);
// "You have 5 items"

That's it. No setup needed for the default language — the fallback text from your .lockeys file is used automatically.

4. Add translations for other languages

Create a JSON file with translated templates. The keys must match the Section.Key format:

Translations/it.json:

{
  "Nav.Home": "Pagina iniziale",
  "Nav.Settings": "Impostazioni",
  "Messages.Welcome": "Bentornato, {userName}!",
  "Messages.ItemCount": "Hai {count} articoli",
  "Messages.OrderTotal": "Totale: {total:C2}"
}

Note: translated templates use the same parameter names as your .lockeys file, but without the type (just {userName}, not {userName|string}). Translators can change or add format specifiers (e.g., {total:N2} instead of {total:C2}).

5. Set up LocalizationContext

LocalizationContext is the central service that holds the active translation provider and notifies subscribers when it changes. It is designed for dependency injection.

First, implement ITranslationProviderFactory to tell the system how to load translations for a given culture:

public class JsonFileProviderFactory : ITranslationProviderFactory
{
    private readonly string _basePath;

    public JsonFileProviderFactory(string basePath) => _basePath = basePath;

    public ITranslationProvider? Create(CultureInfo culture)
    {
        string path = Path.Combine(_basePath, $"{culture.TwoLetterISOLanguageName}.json");
        return File.Exists(path) ? new JsonTranslationProvider(path) : null;
    }
}

Then register everything in your DI container:

var services = new ServiceCollection();

services.AddSingleton<ITranslationProviderFactory>(
    new JsonFileProviderFactory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Translations")));
services.AddSingleton<LocalizationContext>();

The DI container automatically injects the factory into LocalizationContext.

6. Switch language at runtime

Call SetCulture to switch the active language. The factory creates the appropriate provider, and all subscribers are notified:

// Switch to Italian — the factory loads Translations/it.json
_localization.SetCulture(new CultureInfo("it"));

// Resolve a translation using the active provider
string greeting = App.Messages.Welcome("Marco").Resolve(_localization.Provider);
// "Bentornato, Marco!"

// Revert to fallback (default language from .lockeys)
_localization.SetCulture(null);

7. React to language changes

LocalizationContext.ProviderChanged is an observable that emits the current provider on subscription and again every time the language changes. Use it to keep your UI in sync:

// Observe a single translation — re-resolves automatically on language change
App.Messages.Welcome("Marco")
    .Observe(localizationContext)
    .Subscribe(text => welcomeLabel.Text = text);

// Or subscribe to all provider changes for custom logic
localizationContext.ProviderChanged
    .Subscribe(provider => RefreshAllTranslations());

How it works (in one picture)

flowchart LR
    subgraph build [Build time]
        A["App.lockeys\n[Messages]\nWelcome : Hello,\n{name|str}!"]
        B["Source Generator\npublic static\nclass App { ... }"]
        A -- build --> B
    end

    subgraph runtime [Runtime]
        C["Your Code\nApp.Messages\n.Welcome('Marco')"]
        D["TranslationString\n.Resolve()"]
        E["it.json\n{ 'Messages.Welcome':\n'Ciao, {name}!' }"]
        F(["'Ciao, Marco!'"])
    end

    B -- use --> C
    C --> D
    D -- lookup --> E
    D --> F
Loading

Next steps

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages