Getting Started with Pragmatic.Internationalization
This guide covers setting up the i18n system: culture context, Money type, Currency, and formatting.
Packages
Section titled “Packages”| Package | Description |
|---|---|
Pragmatic.Internationalization | Core: Money, Currency, Formatters, Humanizers, Plural Rules |
Pragmatic.Internationalization.AspNetCore | Middleware, JSON converters, DI integration |
Pragmatic.Internationalization.EFCore | Value converters for Money and CurrencyCode |
Pragmatic.Internationalization.SourceGenerator | Generates T class with embedded translations |
Step 1: Add Packages
Section titled “Step 1: Add Packages”dotnet add package Pragmatic.Internationalizationdotnet add package Pragmatic.Internationalization.AspNetCore # for web appsStep 2: Configure Culture Context
Section titled “Step 2: Configure Culture Context”ASP.NET Core Setup
Section titled “ASP.NET Core Setup”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:
- Query string:
?culture=de-DE Accept-Languageheader- Default culture
Manual Culture Context
Section titled “Manual Culture Context”I18NContext stores the current culture using AsyncLocal<I18NContext?>, making it thread-safe and compatible with async/await:
using Pragmatic.Internationalization;
// Set ambient cultureI18NContext.SetCulture("de-DE");
// Get current culturevar culture = I18N.Culture; // CultureInfo for de-DEIf no context has been set, I18NContext.Current falls back to CultureInfo.CurrentCulture.
Scoped Culture Changes
Section titled “Scoped Culture Changes”For temporary culture changes:
// Synchronous scopeI18NContext.WithCulture("it-IT", () =>{ var formatted = 1234.56m.FormatNumber(); // "1.234,56"});// Original culture restored here
// Async scopeawait I18NContext.WithCultureAsync("fr-FR", async () =>{ await ProcessOrderAsync(); // Runs with fr-FR culture});
// With return valuevar result = I18NContext.WithCulture<string>("it-IT", () =>{ return T.Welcome.Value; // "Benvenuto nella nostra app!"});Multi-Scope Culture
Section titled “Multi-Scope Culture”The context supports separate cultures for different purposes:
| Scope | Purpose | Example |
|---|---|---|
| UICulture | Display: labels, messages | it-IT |
| DataCulture | Storage: API responses, exports | en-US |
| Custom scopes | Specific needs: invoicing, reporting | de-DE for invoicing |
I18NContext.SetCulture("it-IT");I18NContext.SetDataCulture(CultureCode.FromString("en-US"));
var uiCulture = I18N.UI; // it-ITvar dataCulture = I18N.Data; // en-US
// Custom scopeI18N.SetScope("invoicing", CultureCode.FromString("de-DE"));var invoiceCulture = I18N.Scope["invoicing"]; // de-DEStep 3: Money Type
Section titled “Step 3: Money Type”Creating Money Values
Section titled “Creating Money Values”using Pragmatic.Internationalization;
var price = Money.From(99.99m, CurrencyCode.USD);var price2 = Money.From(99.99m, "EUR"); // from string codevar zero = Money.Zero(CurrencyCode.USD);
// Safe parsingif (Money.TryFrom(99.99m, "XYZ", out var money)) Console.WriteLine(money);Arithmetic
Section titled “Arithmetic”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.00var diff = a - b; // $75.00var scaled = a * 1.5m; // $150.00var divided = a / 4; // $25.00var negated = -a; // -$100.00
// Currency mismatch throws InvalidOperationExceptionvar usd = Money.From(100m, CurrencyCode.USD);var eur = Money.From(100m, CurrencyCode.EUR);// var mixed = usd + eur; // InvalidOperationException!Rounding
Section titled “Rounding”var price = Money.From(99.999m, CurrencyCode.USD);var rounded = price.Round(2); // $100.00var currencyRounded = price.RoundToMinorUnit(); // $100.00 (USD has 2 minor units)
// JPY has 0 minor unitsvar yen = Money.From(999.5m, CurrencyCode.JPY);var yenRounded = yen.RoundToMinorUnit(); // 1000Properties
Section titled “Properties”var money = Money.From(-50m, CurrencyCode.EUR);money.Amount; // -50mmoney.Currency; // CurrencyCode.EURmoney.IsZero; // falsemoney.IsPositive; // falsemoney.IsNegative; // trueFormatting (Culture-Aware)
Section titled “Formatting (Culture-Aware)”I18NContext.SetCulture("en-US");total.Format(); // "$107.49"
I18NContext.SetCulture("de-DE");total.Format(); // "107,49 $"Step 4: CurrencyCode
Section titled “Step 4: CurrencyCode”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)Currency Properties
Section titled “Currency Properties”var usd = CurrencyCode.USD;usd.Code; // "USD"usd.Name; // "US Dollar"usd.Symbol; // "$"usd.MinorUnits; // 2Parsing
Section titled “Parsing”var eur = CurrencyCode.FromCode("EUR");if (CurrencyCode.TryFromCode("XYZ", out var currency)) { ... }bool valid = CurrencyCode.IsValid("USD"); // trueforeach (var c in CurrencyCode.All) { ... }Step 5: Formatting Extensions
Section titled “Step 5: Formatting Extensions”Numbers
Section titled “Numbers”1234.56m.FormatNumber(); // "1,234.56" (en-US) / "1.234,56" (de-DE)1234.56m.FormatNumber(3); // With 3 decimal places0.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"GlobalizationFormatter (DI-Injectable)
Section titled “GlobalizationFormatter (DI-Injectable)”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.
Step 6: Humanizers
Section titled “Step 6: Humanizers”Duration
Section titled “Duration”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.
Ordinals
Section titled “Ordinals”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"Quantities
Section titled “Quantities”var qty = new QuantityFormatter("en");qty.Format(1_500); // "1.5K"qty.Format(2_300_000); // "2.3M"Relative Time
Section titled “Relative Time”var relTime = new RelativeTimeFormatter(localizer);relTime.Format(TimeSpan.FromMinutes(5)); // "5 minutes ago"relTime.Format(TimeSpan.FromDays(1)); // "yesterday"EF Core Integration
Section titled “EF Core Integration”protected override void OnModelCreating(ModelBuilder modelBuilder){ modelBuilder.ApplyPragmaticInternationalization();}Value converters: CurrencyCode maps to varchar(3), Money is configured with amount and currency columns.
JSON Serialization
Section titled “JSON Serialization”Money and Currency types serialize automatically:
{ "price": { "amount": 99.99, "currency": "EUR" }}Test Isolation
Section titled “Test Isolation”Use Capture() and Restore() to ensure isolation between tests:
var snapshot = I18NContext.Capture();try{ I18NContext.SetCulture("de-DE"); // ... test code ...}finally{ I18NContext.Restore(snapshot);}Next Steps
Section titled “Next Steps”- Translation Keys —
[TranslationKeys], SG-generated constants, ILocalizer