Globalization and Localization
|
This section documents ASP.NET Core on .NET 10 (LTS), the current release — the minimal hosting model, the middleware pipeline, dependency injection, Minimal APIs, MVC & Razor Pages, Blazor with the current render modes, SignalR and gRPC, EF Core, ASP.NET Core Identity and policy-based authorization, output caching, rate limiting, and Native-AOT-aware building — as described by the official documentation at Microsoft Learn, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. .NET ships a major release every November and its APIs continue to evolve: the examples here target .NET 10 / C# 14. This section’s bibliography lists the reference material consulted while preparing these pages. |
Globalization is formatting dates, numbers, and currency for a culture; localization is translating an app’s
own text into that culture’s language. ASP.NET Core supports both through IStringLocalizer and the request
localization middleware.
IStringLocalizer and IViewLocalizer
builder.Services.AddLocalization(o => o.ResourcesPath = "Resources");
builder.Services.AddControllersWithViews().AddViewLocalization();
public sealed class ProductsController(IStringLocalizer<ProductsController> localizer) : Controller
{
public IActionResult Index()
{
ViewData["Title"] = localizer["ProductsTitle"];
return View();
}
}
@inject IViewLocalizer Localizer
<h1>@Localizer["Welcome"]</h1>
IStringLocalizer<T> resolves strings scoped to type T’s resource file; `IViewLocalizer is the equivalent
for a Razor view, scoped by the view’s own path. Both fall back to the requested key itself (in square brackets
during development, depending on configuration) when no translation exists, making a missing translation
visible rather than silently blank. See
Globalization and localization in
ASP.NET Core.
.resx conventions
Resources/
Controllers.ProductsController.es.resx # matches IStringLocalizer<ProductsController>
Views.Products.Index.es.resx # matches a Razor view under Views/Products/Index.cshtml
A .resx file with no culture suffix (ProductsController.resx) is the fallback/neutral resource; one per
supported culture (.es.resx, .fr.resx, …) overrides it. The file’s path mirrors the type’s or view’s
namespace/folder path under the configured ResourcesPath, which is how IStringLocalizer<T> finds the right
file without explicit registration per type.
RequestLocalizationMiddleware and culture providers
var supportedCultures = new[] { "en", "es", "fr" };
app.UseRequestLocalization(new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures));
The middleware determines the current request’s culture by asking a chain of `IRequestCultureProvider`s, in order, and uses the first one that returns a supported culture:
| Provider (default order) | Reads the culture from |
|---|---|
|
|
|
A cookie ( |
|
The browser’s |
Add a route provider (reading /es/products style URLs) by inserting a custom IRequestCultureProvider
ahead of the defaults when the app needs the culture visible in the URL itself, e.g. for SEO.
// persist the user's explicit choice as the cookie provider reads
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture("es")));
UseRequestLocalization must run early in the pipeline (before anything that reads the current culture) — see Request Pipeline and Middleware for the general
ordering rules. See
Localization
middleware.
Data annotations localization
Validation messages from DataAnnotations (see
Model Binding and Validation) are localized the same
way as any other string, through IStringLocalizer<T> resolved for the model’s own type:
builder.Services.AddControllersWithViews()
.AddDataAnnotationsLocalization();
public sealed class ProductInput
{
[Required(ErrorMessage = "NameRequired")] // looked up in ProductInput.{culture}.resx
public string Name { get; set; } = "";
}
Blazor localization
Blazor components inject IStringLocalizer<T> exactly like an MVC controller, but culture selection works
differently per render mode:
@inject IStringLocalizer<Counter> Localizer
<p>@Localizer["ClickedTimes", count]</p>
-
Static SSR / Interactive Server — the culture is resolved the same way as any other request, via
RequestLocalizationMiddleware, and stays fixed for the circuit’s lifetime once the interactive connection starts (changing it typically requires a full reload/redirect so a fresh circuit picks up the new cookie). -
Interactive WebAssembly / standalone WASM — there is no server-side middleware pipeline per request; the app sets
CultureInfo.DefaultThreadCurrentCulture/CurrentUICultureexplicitly at startup, typically from a value persisted in browser storage (see Blazor State Management) or the browser’s own locale.