
Hướng dẫn đầy đủ về bản địa hóa ASP.NET Core
Từ IStringLocalizer đến môi trường production: thiết lập bản địa hóa dựa trên tài nguyên trong ASP.NET Core rồi tự động dịch bằng AI.
Bật dịch vụ bản địa hóa
Đăng ký dịch vụ bản địa hóa trong Program.cs bằng AddLocalization(), cấu hình các văn hóa được hỗ trợ và thêm middleware bản địa hóa yêu cầu. Thao tác này kết nối toàn bộ quy trình bản địa hóa cho ứng dụng ASP.NET Core của bạn.
using Microsoft.AspNetCore.Localization;
using System.Globalization;
var builder = WebApplication.CreateBuilder(args);
// 1. Register localization services
builder.Services.AddLocalization(o => o.ResourcesPath = "Resources");
// 2. Add MVC with view/data-annotation localization
builder.Services.AddControllersWithViews()
.AddViewLocalization()
.AddDataAnnotationsLocalization();
var app = builder.Build();
// 3. Configure supported cultures
var supportedCultures = new[] { "en", "de", "ja", "es", "pt-BR" }
.Select(c => new CultureInfo(c)).ToArray();
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures,
});
app.UseStaticFiles();
app.UseRouting();
app.MapControllers();
app.Run();Tạo tệp tài nguyên RESX
ASP.NET Core dùng tệp RESX (tài nguyên XML) cho bản dịch. Tạo một tệp cho mỗi văn hóa và mỗi lớp: HomeController.en.resx, HomeController.de.resx, v.v. Framework phân giải đúng tệp dựa trên văn hóa yêu cầu hiện tại.
<!-- Resources/Controllers/HomeController.en.resx -->
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="Welcome" xml:space="preserve">
<value>Welcome to our application</value>
</data>
<data name="Greeting" xml:space="preserve">
<value>Hello, {0}!</value>
</data>
</root>
<!-- Resources/Controllers/HomeController.de.resx -->
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="Welcome" xml:space="preserve">
<value>Willkommen in unserer Anwendung</value>
</data>
<data name="Greeting" xml:space="preserve">
<value>Hallo, {0}!</value>
</data>
</root><!-- Resources/SharedResource.en.resx — shared across controllers -->
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="AppName" xml:space="preserve">
<value>My Application</value>
</data>
<data name="Save" xml:space="preserve"><value>Save</value></data>
<data name="Cancel" xml:space="preserve"><value>Cancel</value></data>
</root>
// Marker class (empty — only used for type lookup)
namespace MyApp;
public class SharedResource { }Dùng IStringLocalizer trong controller và dịch vụ
Chèn IStringLocalizer<T> vào bất kỳ controller, dịch vụ hoặc middleware nào thông qua dependency injection. Tham số kiểu generic T xác định tệp RESX cần tải. Dùng cú pháp ngoặc vuông localizer["Key"] để truy xuất chuỗi đã dịch cùng các tham số định dạng tùy chọn.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
public class HomeController(
IStringLocalizer<HomeController> localizer,
IStringLocalizer<SharedResource> shared) : Controller
{
public IActionResult Index()
{
ViewData["Welcome"] = localizer["Welcome"];
ViewData["AppName"] = shared["AppName"];
// String interpolation with format parameters
var greeting = localizer["Greeting", User.Identity?.Name ?? "Guest"];
return View(new HomeViewModel { Greeting = greeting });
}
}Cấu hình middleware văn hóa yêu cầu
ASP.NET Core xác định văn hóa yêu cầu bằng một chuỗi nhà cung cấp: chuỗi truy vấn, cookie và header Accept-Language (theo thứ tự đó). Bạn có thể thêm nhà cung cấp tùy chỉnh — chẳng hạn đọc văn hóa từ một phân đoạn định tuyến URL như /de/home.
// Culture resolved in order: QueryString, Cookie, Accept-Language
// Custom provider: read culture from URL route segment /de/home
public class RouteDataRequestCultureProvider : RequestCultureProvider
{
public override Task<ProviderCultureResult?> DetermineProviderCultureResult(
HttpContext httpContext)
{
var culture = httpContext.GetRouteValue("culture")?.ToString();
if (string.IsNullOrEmpty(culture))
return NullProviderCultureResult;
return Task.FromResult<ProviderCultureResult?>(
new ProviderCultureResult(culture));
}
}
// Register in Program.cs (route provider first = highest priority):
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures,
RequestCultureProviders = new List<IRequestCultureProvider>
{
new RouteDataRequestCultureProvider(),
new QueryStringRequestCultureProvider(),
new CookieRequestCultureProvider(),
new AcceptLanguageHeaderRequestCultureProvider(),
}
});// Language switcher: persist choice in cookie
[HttpPost]
public IActionResult SetLanguage(string culture, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) });
return LocalRedirect(returnUrl);
}Bản địa hóa chú thích dữ liệu
Bạn có thể bản địa hóa các thuộc tính xác thực như [Required], [StringLength] và [Display] bằng cách đặt thuộc tính ErrorMessage hoặc Name thành tên khóa RESX. Gọi AddDataAnnotationsLocalization() trong Program.cs để bật tính năng này.
using System.ComponentModel.DataAnnotations;
public class RegisterViewModel
{
[Required(ErrorMessage = "NameRequired")]
[Display(Name = "FullName")]
[StringLength(100, ErrorMessage = "NameLength", MinimumLength = 2)]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "EmailRequired")]
[EmailAddress(ErrorMessage = "EmailInvalid")]
[Display(Name = "EmailAddress")]
public string Email { get; set; } = string.Empty;
[Required(ErrorMessage = "PasswordRequired")]
[StringLength(100, ErrorMessage = "PasswordLength", MinimumLength = 8)]
[Display(Name = "Password")]
public string Password { get; set; } = string.Empty;
}
// RESX keys map to ErrorMessage/Name values:
// RegisterViewModel.de.resx: NameRequired = "Name ist erforderlich"
// RegisterViewModel.de.resx: FullName = "Vollständiger Name"Xử lý dạng số nhiều và thông báo ICU
.NET không tích hợp sẵn quy tắc số nhiều như ICU. Với trường hợp đơn giản, hãy dùng các khóa RESX riêng (ItemCount_One, ItemCount_Other) cùng câu lệnh chuyển nhánh trong mã. Để hỗ trợ đầy đủ ICU MessageFormat cho mọi nhóm số nhiều CLDR, hãy dùng thư viện MessageFormat.NET.
// Option 1: Separate RESX keys with code switch
// HomeController.en.resx: ItemCount_One = "You have {0} item"
// HomeController.en.resx: ItemCount_Other = "You have {0} items"
public string GetItemCount(int count)
{
var key = count == 1 ? "ItemCount_One" : "ItemCount_Other";
return _localizer[key, count];
}
// Option 2: ICU MessageFormat (dotnet add package MessageFormat.NET)
using Jeffijoe.MessageFormat;
var formatter = new MessageFormatter();
var pattern = "{count, plural, one {# item} other {# items}} in your cart";
var result = formatter.FormatMessage(pattern,
new Dictionary<string, object> { { "count", 5 } });
// => "5 items in your cart"
// Arabic: 6 plural forms (zero, one, two, few, many, other)
var arPattern = @"{count, plural,
zero {لا عناصر} one {عنصر واحد} two {عنصران}
few {# عناصر} many {# عنصرًا} other {# عنصر}}";Bản địa hóa view Razor
Dùng IViewLocalizer trong view Razor qua @inject. Công cụ này phân giải tệp RESX dựa trên đường dẫn tệp của view. Với chuỗi chứa mã đánh dấu và an toàn cho HTML, hãy dùng IHtmlLocalizer. Các Tag Helper như asp-for và asp-validation-for tự động dùng thuộc tính Display và ErrorMessage đã bản địa hóa.
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer
@inject IHtmlLocalizer<SharedResource> SharedHtml
<h1>@Localizer["Welcome"]</h1>
<p>@Localizer["Greeting", User.Identity?.Name]</p>
@* IHtmlLocalizer: does NOT escape — use for RESX values with HTML *@
<p>@SharedHtml["TermsNotice"]</p>
@* Tag Helpers auto-localize Display/ErrorMessage attributes *@
<form asp-action="Register">
<label asp-for="Name"></label>
<input asp-for="Name" />
<span asp-validation-for="Name"></span>
<button type="submit">@Localizer["Submit"]</button>
</form>Tự động dịch RESX
Sau khi hoàn tất thiết lập bản địa hóa, hãy dịch các tệp RESX bằng AI. Trong IDE, yêu cầu trợ lý AI dịch RESX nguồn hoặc dùng i18n Agent CLI trong quy trình CI/CD để luôn đồng bộ bản dịch.
# In your IDE, ask your AI assistant:
> Translate Resources/Controllers/HomeController.en.resx to German, Japanese, Spanish
# HomeController.de.resx created (1.2s)
# HomeController.ja.resx created (1.5s)
# HomeController.es.resx created (1.1s)
# Or use the CLI in CI/CD:
npx i18n-agent translate Resources/Controllers/HomeController.en.resx --lang de,ja,esTự động đảm bảo chất lượng bản dịch
Khắc phục phương án dự phòng locale bằng LocaleChain.NET
Hệ thống phân cấp CultureInfo.Parent tích hợp sẵn của .NET chỉ dùng phép rút gọn BCP 47: pt-BR dự phòng về pt rồi InvariantCulture và bỏ qua pt-PT. LocaleChain.NET cung cấp chuỗi dự phòng có thể cấu hình cho từng locale trong toàn bộ hệ sinh thái .NET.
dotnet add package I18nAgent.LocaleChainusing I18nAgent.LocaleChain;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLocalization(o => o.ResourcesPath = "Resources");
// Zero-config: built-in chains (pt-BR -> pt-PT -> pt -> en, etc.)
LocaleChain.Configure();
// Or customize specific chains:
LocaleChain.Configure(new Dictionary<string, string[]>
{
["pt-BR"] = new[] { "pt-PT", "pt", "en" },
["es-MX"] = new[] { "es-419", "es", "en" },
});
// Register the chain-aware string localizer
builder.Services.AddSingleton(
typeof(IStringLocalizer<>),
typeof(LocaleChainStringLocalizer<>));Lỗi thường gặp
Chưa thiết lập văn hóa cho yêu cầu
Không tìm thấy tệp RESX
Thứ tự middleware không chính xác
Luồng nền dùng sai văn hóa
Cấu trúc dự án đề xuất
MyAspNetApp/
├── Controllers/
│ └── HomeController.cs
├── ViewModels/
│ └── RegisterViewModel.cs
├── Views/
│ └── Home/
│ └── Index.cshtml
├── Resources/
│ ├── Controllers/
│ │ ├── HomeController.en.resx # English (source)
│ │ ├── HomeController.de.resx # German
│ │ └── HomeController.ja.resx # Japanese
│ ├── ViewModels/
│ │ ├── RegisterViewModel.en.resx
│ │ └── RegisterViewModel.de.resx
│ ├── Views/Home/
│ │ ├── Index.en.resx
│ │ └── Index.de.resx
│ └── SharedResource.en.resx
├── SharedResource.cs # Marker class
├── Program.cs
└── MyAspNetApp.csprojDùng thử i18n Agent ngay
Thả tệp bản dịch của bạn vào đây
JSON, YAML, PO, XML, CSV, Markdown, Properties
hoặc nhấp để duyệt
Ngôn ngữ đích
Dự phòng locale với I18nAgent.LocaleChain
Khi thiếu khóa bản dịch trong locale khu vực như pt-BR, .NET chuyển thẳng sang văn hóa bất biến thay vì kiểm tra locale cha pt trước.
dotnet add package I18nAgent.LocaleChainusing I18nAgent.LocaleChain;
LocaleChain.Configure(new Dictionary<string, string[]>
{
["pt-BR"] = new[] {"pt", "en"},
["zh-Hant-HK"] = new[] {"zh-Hant", "zh", "en"},
});Xem Hướng dẫn dự phòng locale để biết danh sách đầy đủ các framework được hỗ trợ và 75 chuỗi tích hợp sẵn. Learn more →