Localization trong ASP.NET Core hoạt động như thế nào?
Resource files là gì và cách sử dụng?
Làm sao để localize controllers và views?
Culture providers hoạt động ra sao?
Localize data annotations và validation messages?
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
builder.Services.AddControllersWithViews()
.AddViewLocalization(LanguageViewLocationFormatSuffixFormat.Suffix)
.AddDataAnnotationsLocalization();
var app = builder.Build();
// Supported cultures
var supportedCultures = new[] { "en-US", "vi-VN", "fr-FR" };
var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
app.Run();
Project/
├── Resources/
│ ├── Controllers/
│ │ ├── HomeController.en-US.resx
│ │ ├── HomeController.vi-VN.resx
│ │ └── HomeController.fr-FR.resx
│ ├── Views/
│ │ ├── Home/
│ │ │ ├── Index.en-US.resx
│ │ │ └── Index.vi-VN.resx
│ │ └── Shared/
│ │ └── _Layout.en-US.resx
│ ├── SharedResource.en-US.resx
│ └── SharedResource.vi-VN.resx
└── Program.cs
<!-- SharedResource.en-US.resx -->
<data name="Hello" xml:space="preserve">
<value>Hello</value>
</data>
<data name="Welcome" xml:space="preserve">
<value>Welcome to our application</value>
</data>
<!-- SharedResource.vi-VN.resx -->
<data name="Hello" xml:space="preserve">
<value>Xin chào</value>
</data>
<data name="Welcome" xml:space="preserve">
<value>Chào mừng bạn đến với ứng dụng</value>
</data>
public class HomeController : Controller
{
private readonly IStringLocalizer<SharedResource> _localizer;
public HomeController(IStringLocalizer<SharedResource> localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
ViewData["Message"] = _localizer["Hello"];
ViewData["Welcome"] = _localizer["Welcome"];
return View();
}
}
// Resource file
// "Greeting" = "Hello {0}, welcome to {1}"
public IActionResult Index(string name, string city)
{
var message = _localizer["Greeting", name, city];
// Output: "Hello John, welcome to New York"
return View();
}
// IStringLocalizer - plain text
public class MyController : Controller
{
private readonly IStringLocalizer<MyController> _localizer;
public MyController(IStringLocalizer<MyController> localizer)
{
_localizer = localizer;
}
}
// IHtmlLocalizer - HTML content (không encode HTML)
public class MyViewComponent : ViewComponent
{
private readonly IHtmlLocalizer<SharedResource> _localizer;
public MyViewComponent(IHtmlLocalizer<SharedResource> localizer)
{
_localizer = localizer;
}
public IViewComponentResult Invoke()
{
// Resource: "Bold" = "<strong>Bold text</strong>"
var html = _localizer["Bold"]; // Không encode HTML
return View(html);
}
}
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer
<h1>@Localizer["Welcome"]</h1>
<p>@Localizer["Hello"]</p>
<!-- Hoặc dùng IHtmlLocalizer -->
@inject IHtmlLocalizer<SharedResource> HtmlLocalizer
<p>@HtmlLocalizer["Description"]</p>
@model Product
@inject IViewLocalizer Localizer
<h1>@Localizer["ProductDetails"]</h1>
<div>
<label>@Localizer["Name"]</label>
<span>@Model.Name</span>
</div>
<div>
<label>@Localizer["Price"]</label>
<span>@Model.Price</span>
</div>
public class Product
{
[Required(ErrorMessage = "NameRequired")]
[Display(Name = "ProductName")]
public string Name { get; set; } = string.Empty;
[Range(0.01, 9999.99, ErrorMessage = "PriceRange")]
[Display(Name = "ProductPrice")]
public decimal Price { get; set; }
}
// Resource file
// "NameRequired" = "Tên sản phẩm là bắt buộc"
// "ProductName" = "Tên sản phẩm"
// "PriceRange" = "Giá phải từ 0.01 đến 9999.99"
// "ProductPrice" = "Giá sản phẩm"
builder.Services.AddControllersWithViews()
.AddViewLocalization()
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider =
(type, factory) => factory.Create(typeof(SharedResource));
});
┌─────────────────────────────────────────────────────────────────┐
│ CULTURE PROVIDERS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Thứ tự kiểm tra (theo mặc định): │
│ 1. QueryStringRequestCultureProvider │
│ ?culture=vi-VN&ui-culture=vi-VN │
│ │
│ 2. CookieRequestCultureProvider │
│ Cookie: .AspNetCore.Culture=c=vi-VN\|uic=vi-VN │
│ │
│ 3. AcceptLanguageHeaderRequestCultureProvider │
│ Accept-Language: vi-VN, en-US;q=0.9 │
│ │
│ Custom Provider: │
│ - Route data (/vi/products, /en/products) │
│ - Subdomain (vi.example.com, en.example.com) │
│ - Custom header (X-Culture: vi-VN) │
│ │
└─────────────────────────────────────────────────────────────────┘
public class RouteDataRequestCultureProvider : RequestCultureProvider
{
public int RouteDataIndex = 0;
public override Task<ProviderCultureResult> DetermineProviderCultureResult(
HttpContext httpContext)
{
var culture = httpContext.Request.RouteValues["culture"]?.ToString();
if (string.IsNullOrEmpty(culture))
{
return NullProviderCultureResult;
}
var result = new ProviderCultureResult(culture);
return Task.FromResult(result);
}
}
// Đăng ký
var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture("en-US")
.AddSupportedCultures("en-US", "vi-VN");
// Thêm custom provider vào đầu danh sách
localizationOptions.RequestCultureProviders.Insert(
0, new RouteDataRequestCultureProvider());
app.UseRequestLocalization(localizationOptions);
// Set culture cookie
app.MapGet("/set-culture/{culture}", (string culture, HttpContext context) =>
{
context.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture)));
return Results.Redirect("/");
});
// ✅ Good - centralized resources
public class SharedResource { } // Empty class
// Inject shared localizer
public class HomeController : Controller
{
private readonly IStringLocalizer<SharedResource> _localizer;
public HomeController(IStringLocalizer<SharedResource> localizer)
{
_localizer = localizer;
}
}
var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture("en-US")
.AddSupportedCultures("en-US", "vi-VN")
.AddSupportedUICultures("en-US", "vi-VN")
.SetFallbackCulture("en-US"); // Fallback nếu không tìm thấy culture
// Route với culture
app.MapControllerRoute(
name: "localized",
pattern: "{culture=en-US}/{controller=Home}/{action=Index}");
// URLs:
// /en-US/Home/Index
// /vi-VN/Home/Index