Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

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 .tkeys file to your project with an @class directive that sets the generated class name.

Create Translations/App.tkeys:

@class App

# 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\*.tkeys" />
</ItemGroup>

3. Use in code

The source generator creates a static class named App (matching the @class directive) 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 .tkeys 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/localization.it-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 .tkeys 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, $"localization.{culture.Name}.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>();
services.AddSingleton<ILocalizationContext>(sp => sp.GetRequiredService<LocalizationContext>());

The DI container automatically injects the factory into LocalizationContext. Consumers should depend on ILocalizationContext for easy mocking in unit tests.

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/localization.it-IT.json
_localization.SetCulture(new CultureInfo("it-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 .tkeys)
_localization.SetCulture(null);

7. Locale-aware formatting

Resolve() accepts an optional IFormatProvider so that formatted placeholders (like {total:C2}) render according to the correct locale:

var german = CultureInfo.GetCultureInfo("de-DE");

TranslationString price = App.Messages.OrderTotal(1234.50m);

string english = price.Resolve();                            // "Total: $1,234.50"
string germanFmt = price.Resolve(formatProvider: german);    // "Total: 1.234,50 €"

// With a translation provider + locale together
string full = price.Resolve(germanProvider, german);         // "Bestellsumme: 1.234,50 €"

For full control over how values are converted to strings (including object parameters), implement ICustomFormatter — see Runtime Configuration.

8. React to language changes

LocalizationContext.ProviderChanged is an IObservable<T> (BCL) that emits the current provider on subscription and again every time the language changes. Use it to keep your UI in sync.

Note: The library has no dependency on System.Reactive. To use .Subscribe(Action<T>), add System.Reactive to your project — or use a framework that already includes it (ReactiveUI, etc.).

// 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.tkeys\n@class App\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["localization.it-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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages