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: culture context, Money, Currency, Formatters, Humanizers, Plural Rules |
Pragmatic.Internationalization.AspNetCore |
Culture middleware, JSON converters, validation attributes, translation endpoint, DI integration |
Pragmatic.Internationalization.EFCore |
Value converters for LocalizedString (JSON column) and CurrencyCode (varchar(3)) |
Pragmatic.SourceGenerator |
Generates the T class with compile-time-checked translation keys |
The
Tclass is generated by the unifiedPragmatic.SourceGeneratorpackage (analyzer reference).Pragmatic.Internationalization.SourceGeneratoris an internal generator that produces the ISO Country/Currency/Language data inside the module itself; consumers never reference it.
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.DefaultUICulture = CultureCode.EnglishUS; options.SupportedCultures = [CultureCode.EnglishUS, CultureCode.German, CultureCode.Italian, CultureCode.French]; options.QueryStringKey = "culture"; // ?culture=de-DE (default: "culture")});
var app = builder.Build();app.UsePragmaticInternationalization();Options can also be bound from configuration (section "I18N" by default):
builder.Services.AddPragmaticInternationalization(builder.Configuration);// appsettings.json: { "I18N": { "DefaultUICulture": "it-IT", "SupportedCultures": ["it-IT", "en-US"] } }Culture resolution order per request:
- Query string:
?culture=de-DE(must be a supported culture) Accept-Languageheader (best match against supported cultures, quality-ordered)- Configuration providers (
II18NConfigProviderchain, highestPrioritywins) - Default culture from options
Pragmatic Host Setup
Section titled “Pragmatic Host Setup”In a Pragmatic.Composition host use the fluent builder instead: the middleware is auto-wired
by InternationalizationStep (Order 95, after authentication):
app.UseI18N(i18n =>{ i18n.DefaultCulture(CultureCode.EnglishUS) .Support(CultureCode.EnglishUS, CultureCode.Italian); i18n.AddJsonTranslations("localization", watchForChanges: true); i18n.LocalizeProblemDetails();});See Integration for the full I18NBuilder surface (providers, translation
endpoint, localized ProblemDetails).
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(); // 1000Both use banker’s rounding (MidpointRounding.ToEven) by default.
Properties
Section titled “Properties”var money = Money.From(-50m, CurrencyCode.EUR);money.Amount; // -50mmoney.Currency; // CurrencyCode.EURmoney.IsZero; // falsemoney.IsPositive; // falsemoney.IsNegative; // truemoney.Abs(); // 50.00 EUR (absolute value, same currency)
// Deconstruct into amount + currencyvar (amount, currency) = money;Formatting (Culture-Aware)
Section titled “Formatting (Culture-Aware)”I18NContext.SetCulture("en-US");total.Format(); // "$107.49"
I18NContext.SetCulture("de-DE");total.Format(); // "107,49 $"
total.Format(CultureCode.German); // format for a specific culture, ignoring contextMoney Extension Helpers
Section titled “Money Extension Helpers”Ergonomic shortcuts on decimal/int and nullable Money:
19.95m.ToMoney(); // Money in I18N.Currency (the ambient currency)19.95m.ToMoney(CurrencyCode.USD); // Money in an explicit currency42.ToMoney(CurrencyCode.JPY); // int overloads too
Money? maybe = null;maybe.FormatOrDefault(); // "-" (custom fallback: FormatOrDefault("n/a"))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 (€, 2 minor units)CurrencyCode.JPY // Japanese Yen (¥, 0 minor units)CurrencyCode.GBP // British 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) { ... }CountryCode and LanguageCode
Section titled “CountryCode and LanguageCode”The same source-generated ISO catalogs exist for countries (ISO 3166-1) and languages (ISO 639-1):
var italy = CountryCode.Italy; // or CountryCode.FromCode("IT")italy.Name; // "Italy"italy.NativeName; // "Italia"italy.DefaultLanguage; // LanguageCode.Italianitaly.DefaultCurrency; // CurrencyCode.EURitaly.DateFormat; // "dd/MM/yyyy"
var arabic = LanguageCode.FromCode("ar");arabic.IsRightToLeft; // truearabic.PluralFamily; // PluralRuleFamily.ArabicCultureCode composes the two (Language + optional Country) and is the type used across
the whole module (options, context, LocalizedString lookups).
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.60%" (2 decimals by default)1_500_000L.FormatFileSize(); // "1.4 MB"Extension methods on DateTimeOffset, DateTime, DateOnly and TimeOnly, driven by the
ambient culture:
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"
// "…Long" variants use the culture's long patternsDateTimeOffset.Now.FormatDateLong(); // "January 10, 2026"DateTimeOffset.Now.FormatTimeLong(); // "3:45:12 PM"DateTimeOffset.Now.FormatDateTimeLong(); // "Saturday, January 10, 2026 3:45:12 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”All humanizers offer static factories so you rarely construct them by hand:
X.Current (ambient culture), X.ForCulture("de"), and, for durations, .Short/.Long/.Compact.
Duration
Section titled “Duration”Three formats. Construct explicitly or via the static factories:
DurationFormatter.Current.Format(TimeSpan.FromMinutes(150)); // "2h 30m" (ambient culture)DurationFormatter.Long("en").Format(TimeSpan.FromHours(2.5)); // "2 hours 30 minutes"DurationFormatter.Compact("en").Format(TimeSpan.FromMinutes(150)); // "2:30:00"new DurationFormatter("en", DurationFormat.Short).FormatMilliseconds(90_000); // "1m 30s"| Format | Example (en) |
|---|---|
Short |
2h 30m |
Long |
2 hours 30 minutes |
Compact |
2:30:00 |
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"
OrdinalFormatter.ForCulture("de").Format(1); // "1."new OrdinalFormatter("fr", OrdinalGender.Feminine).Format(1); // "1ère"Quantities
Section titled “Quantities”QuantityFormatter.Current.Format(1_500); // "1.5K" (ambient culture)QuantityFormatter.ForCulture("de").Format(1_500_000); // "1,5 Mio"new QuantityFormatter("en", decimals: 2).Format(2_345_000); // "2.35M"
// Custom suffixes when the defaults don't fitvar custom = new QuantityFormatter("en", new QuantitySuffixes(" thousand", " million", " billion", " trillion"));custom.Format(1_500); // "1.5 thousand"Relative Time
Section titled “Relative Time”Needs an IStringLocalizer because “ago”/“yesterday”/“weeks” are themselves translatable:
var relTime = new RelativeTimeFormatter(localizer);relTime.Format(TimeSpan.FromMinutes(5)); // "5 minutes ago"relTime.Format(TimeSpan.FromDays(1)); // "yesterday"relTime.Format(DateTimeOffset.Now.AddDays(-14)); // "2 weeks ago"EF Core Integration
Section titled “EF Core Integration”protected override void OnModelCreating(ModelBuilder modelBuilder){ modelBuilder.ApplyPragmaticInternationalization();}Value converters applied automatically: CurrencyCode maps to varchar(3), LocalizedString
maps to a JSON column. Money has no automatic mapping: map it as two columns
(Amount decimal + Currency varchar(3)); the MoneyConfiguration class provides the
recommended precision/scale constants (19, 4). See Integration.
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);}Or use the TestI18N / TestI18NScope helpers; see Concepts.
Next Steps
Section titled “Next Steps”- Translation Keys –
[TranslationKeys], the generatedTclass,IStringLocalizer - Integration – ASP.NET Core middleware,
I18NBuilder, translation endpoint, localized ProblemDetails, EF Core
