Globalization and Localization
Overview
.NET provides a rich globalization and localization infrastructure through System.Globalization and the ASP.NET Core request localization middleware. Globalization is the process of designing applications that support different cultures, while localization is the process of adapting a globalized application to a particular culture. Together they enable applications to format dates, numbers, currencies, and sort strings according to the conventions of any supported locale.
The core types are CultureInfo, RegionInfo, NumberFormatInfo, DateTimeFormatInfo, and the RequestLocalizationMiddleware in ASP.NET Core. These provide deterministic, standards-compliant formatting for user-facing content without hand-rolled parsing logic.
Culture-Aware Formatting
Use CultureInfo to control how values are formatted and parsed. Always specify a culture explicitly rather than relying on the thread's current culture, which can change unexpectedly in server environments.
using System.Globalization;
public static class CultureFormattingExamples
{
public static string FormatCurrency(decimal amount, string cultureName)
{
var culture = new CultureInfo(cultureName);
return amount.ToString("C", culture);
// "en-US" => "$1,234.56"
// "de-DE" => "1.234,56 €"
// "ja-JP" => "¥1,235"
}
public static string FormatDate(DateTime date, string cultureName)
{
var culture = new CultureInfo(cultureName);
return date.ToString("D", culture);
// "en-US" => "Monday, January 6, 2025"
// "fr-FR" => "lundi 6 janvier 2025"
}
public static decimal ParseNumber(string input, string cultureName)
{
var culture = new CultureInfo(cultureName);
return decimal.Parse(input, NumberStyles.Number, culture);
// "1.234,56" with "de-DE" => 1234.56m
}
}
Request Localization Middleware
ASP.NET Core provides middleware that automatically sets the current culture per-request based on query strings, cookies, or the Accept-Language header.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using System.Globalization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLocalization(options =>
options.ResourcesPath = "Resources");
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("fr-FR"),
new CultureInfo("de-DE"),
new CultureInfo("ja-JP"),
};
options.DefaultRequestCulture = new RequestCulture("en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
// Culture resolution order:
// 1. QueryStringRequestCultureProvider (?culture=fr-FR)
// 2. CookieRequestCultureProvider
// 3. AcceptLanguageHeaderRequestCultureProvider
options.RequestCultureProviders.Insert(0,
new QueryStringRequestCultureProvider());
});
var app = builder.Build();
app.UseRequestLocalization();
app.Run();
Custom Culture Provider
For applications that store user locale preferences in a database or JWT claim, implement a custom RequestCultureProvider.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using System.Security.Claims;
using System.Threading.Tasks;
public class UserProfileCultureProvider : RequestCultureProvider
{
public override Task<ProviderCultureResult?> DetermineProviderCultureResult(
HttpContext httpContext)
{
var cultureClaim = httpContext.User
.FindFirst("preferred_locale")?.Value;
if (string.IsNullOrEmpty(cultureClaim))
{
return NullProviderCultureResult;
}
var result = new ProviderCultureResult(cultureClaim);
return Task.FromResult<ProviderCultureResult?>(result);
}
}
Register the provider before the defaults:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
options.RequestCultureProviders.Insert(0,
new UserProfileCultureProvider());
});
String Comparison and Sorting
Culture-sensitive string comparison is critical for correct sorting and searching. Use StringComparer or CultureInfo.CompareInfo for locale-aware ordering.
using System;
using System.Collections.Generic;
using System.Globalization;
public static class CultureSortingExamples
{
public static List<string> SortByCulture(
List<string> items, string cultureName)
{
var culture = new CultureInfo(cultureName);
items.Sort(culture.CompareInfo.Compare);
return items;
}
public static bool CultureAwareContains(
string source, string value, string cultureName)
{
var culture = new CultureInfo(cultureName);
return culture.CompareInfo.IndexOf(
source, value, CompareOptions.IgnoreCase) >= 0;
}
}
Culture Provider Comparison
| Provider | Source | Priority | Use Case |
|---|---|---|---|
| QueryStringRequestCultureProvider | ?culture=fr-FR | Highest (default) | Testing, explicit URL switching |
| CookieRequestCultureProvider | .AspNetCore.Culture cookie | Medium | Persistent user preference |
| AcceptLanguageHeaderRequestCultureProvider | Accept-Language header | Lowest | Browser-default experience |
| Custom (e.g., UserProfileCultureProvider) | DB / JWT / session | Configurable | Enterprise apps with profiles |
Best Practices
- Always pass an explicit
CultureInfotoToString,Parse, andTryParsemethods instead of relying onCultureInfo.CurrentCulture, which varies per-thread and per-request. - Use
CultureInfo.InvariantCulturefor machine-readable data such as log entries, config files, and inter-service communication to avoid locale-dependent parsing failures. - Register
RequestLocalizationMiddlewareearly in the pipeline (before MVC/Razor), so all downstream middleware observes the correct culture. - Limit supported cultures to those you actually have translations for; do not accept arbitrary culture codes to avoid fallback surprises.
- Store culture-neutral data in databases (UTC dates, invariant decimal formats) and format only at the presentation layer.
- Test with cultures that use different decimal separators (e.g.,
de-DEuses,for decimals) to catch parsing bugs that are invisible withen-US. - Use
StringComparison.OrdinalIgnoreCasefor internal identifiers and keys; reserve culture-sensitive comparison for user-facing content. - Set
SupportedCulturesandSupportedUICulturestogether inRequestLocalizationOptionsto avoid mismatches between formatting culture and resource lookup culture. - Validate culture names with
CultureInfo.GetCultureInfoinside a try-catch before constructingCultureInfoobjects from user input, to avoidCultureNotFoundException. - Prefer
DateTimeOffsetoverDateTimewhen dealing with globalized applications to avoid time zone ambiguity across cultures.