Skip to content

Getting Started with Pragmatic.Internationalization

This guide covers setting up the i18n system: culture context, Money type, Currency, and formatting.

PackageDescription
Pragmatic.InternationalizationCore: Money, Currency, Formatters, Humanizers, Plural Rules
Pragmatic.Internationalization.AspNetCoreMiddleware, JSON converters, DI integration
Pragmatic.Internationalization.EFCoreValue converters for Money and CurrencyCode
Pragmatic.Internationalization.SourceGeneratorGenerates T class with embedded translations
Terminal window
dotnet add package Pragmatic.Internationalization
dotnet add package Pragmatic.Internationalization.AspNetCore # for web apps
builder.Services.AddPragmaticInternationalization(options =>
{
options.DefaultCulture = CultureInfo.GetCultureInfo("en-US");
options.SupportedCultures = ["en-US", "de-DE", "it-IT", "fr-FR"];
options.QueryStringKey = "culture"; // ?culture=de-DE
});
var app = builder.Build();
app.UsePragmaticInternationalization();

Culture resolution order:

  1. Query string: ?culture=de-DE
  2. Accept-Language header
  3. Default culture

I18NContext stores the current culture using AsyncLocal<I18NContext?>, making it thread-safe and compatible with async/await:

using Pragmatic.Internationalization;
// Set ambient culture
I18NContext.SetCulture("de-DE");
// Get current culture
var culture = I18N.Culture; // CultureInfo for de-DE

If no context has been set, I18NContext.Current falls back to CultureInfo.CurrentCulture.

For temporary culture changes:

// Synchronous scope
I18NContext.WithCulture("it-IT", () =>
{
var formatted = 1234.56m.FormatNumber(); // "1.234,56"
});
// Original culture restored here
// Async scope
await I18NContext.WithCultureAsync("fr-FR", async () =>
{
await ProcessOrderAsync(); // Runs with fr-FR culture
});
// With return value
var result = I18NContext.WithCulture<string>("it-IT", () =>
{
return T.Welcome.Value; // "Benvenuto nella nostra app!"
});

The context supports separate cultures for different purposes:

ScopePurposeExample
UICultureDisplay: labels, messagesit-IT
DataCultureStorage: API responses, exportsen-US
Custom scopesSpecific needs: invoicing, reportingde-DE for invoicing
I18NContext.SetCulture("it-IT");
I18NContext.SetDataCulture(CultureCode.FromString("en-US"));
var uiCulture = I18N.UI; // it-IT
var dataCulture = I18N.Data; // en-US
// Custom scope
I18N.SetScope("invoicing", CultureCode.FromString("de-DE"));
var invoiceCulture = I18N.Scope["invoicing"]; // de-DE
using Pragmatic.Internationalization;
var price = Money.From(99.99m, CurrencyCode.USD);
var price2 = Money.From(99.99m, "EUR"); // from string code
var zero = Money.Zero(CurrencyCode.USD);
// Safe parsing
if (Money.TryFrom(99.99m, "XYZ", out var money))
Console.WriteLine(money);

All arithmetic enforces same-currency operations:

var a = Money.From(100m, CurrencyCode.USD);
var b = Money.From(25m, CurrencyCode.USD);
var sum = a + b; // $125.00
var diff = a - b; // $75.00
var scaled = a * 1.5m; // $150.00
var divided = a / 4; // $25.00
var negated = -a; // -$100.00
// Currency mismatch throws InvalidOperationException
var usd = Money.From(100m, CurrencyCode.USD);
var eur = Money.From(100m, CurrencyCode.EUR);
// var mixed = usd + eur; // InvalidOperationException!
var price = Money.From(99.999m, CurrencyCode.USD);
var rounded = price.Round(2); // $100.00
var currencyRounded = price.RoundToMinorUnit(); // $100.00 (USD has 2 minor units)
// JPY has 0 minor units
var yen = Money.From(999.5m, CurrencyCode.JPY);
var yenRounded = yen.RoundToMinorUnit(); // 1000
var money = Money.From(-50m, CurrencyCode.EUR);
money.Amount; // -50m
money.Currency; // CurrencyCode.EUR
money.IsZero; // false
money.IsPositive; // false
money.IsNegative; // true
I18NContext.SetCulture("en-US");
total.Format(); // "$107.49"
I18NContext.SetCulture("de-DE");
total.Format(); // "107,49 $"

All 180+ ISO 4217 currencies are available as static properties:

CurrencyCode.USD // US Dollar ($, 2 minor units)
CurrencyCode.EUR // Euro (euro, 2 minor units)
CurrencyCode.JPY // Japanese Yen (yen, 0 minor units)
CurrencyCode.GBP // British Pound (pound, 2 minor units)
var usd = CurrencyCode.USD;
usd.Code; // "USD"
usd.Name; // "US Dollar"
usd.Symbol; // "$"
usd.MinorUnits; // 2
var eur = CurrencyCode.FromCode("EUR");
if (CurrencyCode.TryFromCode("XYZ", out var currency)) { ... }
bool valid = CurrencyCode.IsValid("USD"); // true
foreach (var c in CurrencyCode.All) { ... }
1234.56m.FormatNumber(); // "1,234.56" (en-US) / "1.234,56" (de-DE)
1234.56m.FormatNumber(3); // With 3 decimal places
0.156m.FormatPercent(); // "15.6%"
1_500_000L.FormatFileSize(); // "1.43 MB"
DateTimeOffset.Now.FormatDate(); // "1/10/2026" (en-US) / "10.01.2026" (de-DE)
DateTimeOffset.Now.FormatTime(); // "3:45 PM" / "15:45"
DateTimeOffset.Now.FormatDateTime(); // "1/10/2026 3:45 PM"
public class OrderService(GlobalizationFormatter formatter)
{
public string FormatTotal(Order order) => formatter.FormatMoney(order.Total);
public string FormatDate(Order order) => formatter.FormatDateTime(order.CreatedAt);
}

Available methods: FormatMoney, FormatNumber, FormatInteger, FormatPercent, FormatFileSize, FormatDate, FormatTime, FormatDateTime, FormatDateTimeLong.

var formatter = new DurationFormatter("en", DurationFormat.Short);
formatter.Format(TimeSpan.FromMinutes(150)); // "2h 30m"
var longFmt = DurationFormatter.Long("en");
longFmt.Format(TimeSpan.FromHours(2.5)); // "2 hours 30 minutes"

Supported languages: en, de, fr, it, es, pt, ru, zh, ja, ko, ar, nl, pl, sv, no, da, fi.

var ordinal = new OrdinalFormatter("en");
ordinal.Format(1); // "1st"
ordinal.Format(2); // "2nd"
ordinal.Format(21); // "21st"
var german = OrdinalFormatter.ForCulture("de");
german.Format(1); // "1."
var feminine = new OrdinalFormatter("fr", OrdinalGender.Feminine);
feminine.Format(1); // "1ere"
var qty = new QuantityFormatter("en");
qty.Format(1_500); // "1.5K"
qty.Format(2_300_000); // "2.3M"
var relTime = new RelativeTimeFormatter(localizer);
relTime.Format(TimeSpan.FromMinutes(5)); // "5 minutes ago"
relTime.Format(TimeSpan.FromDays(1)); // "yesterday"
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyPragmaticInternationalization();
}

Value converters: CurrencyCode maps to varchar(3), Money is configured with amount and currency columns.

Money and Currency types serialize automatically:

{
"price": {
"amount": 99.99,
"currency": "EUR"
}
}

Use Capture() and Restore() to ensure isolation between tests:

var snapshot = I18NContext.Capture();
try
{
I18NContext.SetCulture("de-DE");
// ... test code ...
}
finally
{
I18NContext.Restore(snapshot);
}