Globalization (Toàn cầu hóa) và Localization (Bản địa hóa) trong ASP.NET Core
Nguồn: Globalization and localization in ASP.NET Core | Phiên bản: .NET 8.0
Tác giả: Rick Anderson, Damien Bowden, Bart Calixto, Nadeem Afana, và Hisham Bin Ateya
Một trang web đa ngôn ngữ cho phép tiếp cận đối tượng người dùng rộng hơn. ASP.NET Core cung cấp các dịch vụ và Middleware (phần mềm trung gian) để bản địa hóa nội dung sang các ngôn ngữ và Culture (văn hóa/ngôn ngữ) khác nhau.
Để biết hướng dẫn về Localization (bản địa hóa) trong Blazor — bao gồm hoặc thay thế hướng dẫn trong bài viết này — hãy xem ASP.NET Core Blazor globalization and localization.
Thuật ngữ
- Globalization (G11N) (Toàn cầu hóa): Quá trình làm cho ứng dụng hỗ trợ nhiều ngôn ngữ và vùng địa lý khác nhau. Chữ viết tắt lấy từ chữ cái đầu, chữ cái cuối và số lượng chữ cái ở giữa.
- Localization (L10N) (Bản địa hóa): Quá trình tùy chỉnh một ứng dụng đã được toàn cầu hóa cho một ngôn ngữ và vùng cụ thể.
- Internationalization (I18N) (Quốc tế hóa): Bao gồm cả Globalization (toàn cầu hóa) và Localization (bản địa hóa).
- Culture (Văn hóa/Ngôn ngữ): Một ngôn ngữ và tùy chọn là một vùng địa lý.
- Neutral culture (Culture trung tính): Một Culture có ngôn ngữ được chỉ định nhưng không có vùng địa lý (ví dụ: "en", "es").
- Specific culture (Culture cụ thể): Một Culture có ngôn ngữ và vùng địa lý được chỉ định (ví dụ: "en-US", "en-GB", "es-CL").
- Parent culture (Culture cha): Culture trung tính chứa một Culture cụ thể (ví dụ: "en" là Culture cha của "en-US" và "en-GB").
- Locale (Ngôn ngữ địa phương): Locale giống với Culture.
Mã ngôn ngữ và mã quốc gia/vùng lãnh thổ
Định dạng RFC 4646 cho tên Culture là <language code>-<country/region code>, trong đó <language code> xác định ngôn ngữ và <country/region code> xác định Culture con. Ví dụ: es-CL cho tiếng Tây Ban Nha (Chile), en-US cho tiếng Anh (Hoa Kỳ) và en-AU cho tiếng Anh (Úc). RFC 4646 là sự kết hợp của mã Culture ISO 639 gồm hai chữ cái thường liên kết với một ngôn ngữ và mã Culture con ISO 3166 gồm hai chữ cái hoa liên kết với một quốc gia hoặc vùng lãnh thổ. Để biết thêm thông tin, xem System.Globalization.CultureInfo.
Các nhiệm vụ để bản địa hóa ứng dụng
Việc toàn cầu hóa và bản địa hóa một ứng dụng bao gồm các nhiệm vụ sau:
- Làm cho nội dung ứng dụng ASP.NET Core có thể bản địa hóa.
- Cung cấp các tài nguyên đã được bản địa hóa cho các Culture mà ứng dụng hỗ trợ
- Triển khai chiến lược để chọn Culture cho mỗi request
Xem hoặc tải xuống mã mẫu (cách tải xuống)
Làm cho nội dung ứng dụng có thể bản địa hóa
IStringLocalizer và IStringLocalizer<T> được thiết kế nhằm cải thiện năng suất khi phát triển các ứng dụng đã bản địa hóa. IStringLocalizer sử dụng ResourceManager và ResourceReader để cung cấp tài nguyên theo Culture tại thời điểm chạy. Interface này có một indexer và IEnumerable để trả về các chuỗi đã được bản địa hóa. IStringLocalizer không yêu cầu lưu trữ các chuỗi ngôn ngữ mặc định trong một Resource file (file tài nguyên). Bạn có thể phát triển ứng dụng hướng đến việc bản địa hóa mà không cần tạo Resource file sớm trong quá trình phát triển. Đoạn code dưới đây cho thấy cách bao bọc chuỗi "About Title" để bản địa hóa.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
namespace Localization.Controllers
{
[Route("api/[controller]")]
public class AboutController : Controller
{
private readonly IStringLocalizer<AboutController> _localizer;
public AboutController(IStringLocalizer<AboutController> localizer)
{
_localizer = localizer;
}
[HttpGet]
public string Get()
{
return _localizer["About Title"];
}
}
}Trong đoạn code trên, implementation của IStringLocalizer<T> đến từ Dependency Injection. Nếu giá trị đã bản địa hóa của "About Title" không được tìm thấy, thì khóa indexer sẽ được trả về, tức là chuỗi "About Title". Bạn có thể để các chuỗi literal ngôn ngữ mặc định trong ứng dụng và bao bọc chúng trong localizer, để bạn có thể tập trung vào việc phát triển ứng dụng. Bạn phát triển ứng dụng với ngôn ngữ mặc định của mình và chuẩn bị cho bước bản địa hóa mà không cần tạo Resource file mặc định trước. Ngoài ra, bạn có thể sử dụng cách tiếp cận truyền thống và cung cấp khóa để lấy chuỗi ngôn ngữ mặc định.
Sử dụng implementation IHtmlLocalizer<T> cho các tài nguyên có chứa HTML. IHtmlLocalizer HTML encode các đối số được định dạng trong chuỗi tài nguyên, nhưng không HTML encode bản thân chuỗi tài nguyên. Trong ví dụ được tô sáng bên dưới, chỉ có giá trị của tham số name được HTML encode.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
namespace Localization.Controllers
{
public class BookController : Controller
{
private readonly IHtmlLocalizer<BookController> _localizer;
public BookController(IHtmlLocalizer<BookController> localizer)
{
_localizer = localizer;
}
public IActionResult Hello(string name)
{
ViewData["Message"] = _localizer["<b>Hello</b><i> {0}</i>", name];
return View();
}Lưu ý: Thông thường, chỉ nên bản địa hóa văn bản, không phải HTML.
Ở mức thấp nhất, bạn có thể lấy IStringLocalizerFactory từ Dependency Injection:
{
public class TestController : Controller
{
private readonly IStringLocalizer _localizer;
private readonly IStringLocalizer _localizer2;
public TestController(IStringLocalizerFactory factory)
{
var type = typeof(SharedResource);
var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
_localizer = factory.Create(type);
_localizer2 = factory.Create("SharedResource", assemblyName.Name);
}
public IActionResult About()
{
ViewData["Message"] = _localizer["Your application description page."]
+ " loc 2: " + _localizer2["Your application description page."];Đoạn code trên minh họa cả hai phương thức factory create.
Bạn có thể phân vùng các chuỗi đã bản địa hóa theo controller, area, hoặc chỉ có một container. Trong ứng dụng mẫu, một class giả tên SharedResource được dùng cho các tài nguyên chia sẻ.
// Dummy class to group shared resources
namespace Localization
{
public class SharedResource
{
}
}Một số developer dùng class Startup để chứa các chuỗi toàn cục hoặc chia sẻ. Trong ví dụ bên dưới, các localizer InfoController và SharedResource được sử dụng:
public class InfoController : Controller
{
private readonly IStringLocalizer<InfoController> _localizer;
private readonly IStringLocalizer<SharedResource> _sharedLocalizer;
public InfoController(IStringLocalizer<InfoController> localizer,
IStringLocalizer<SharedResource> sharedLocalizer)
{
_localizer = localizer;
_sharedLocalizer = sharedLocalizer;
}
public string TestLoc()
{
string msg = "Shared resx: " + _sharedLocalizer["Hello!"] +
" Info resx " + _localizer["Hello!"];
return msg;
}View localization (Bản địa hóa View)
Dịch vụ IViewLocalizer cung cấp các chuỗi đã bản địa hóa cho một view. Class ViewLocalizer implement interface này và tìm vị trí tài nguyên từ đường dẫn file view. Đoạn code sau đây cho thấy cách sử dụng implementation mặc định của IViewLocalizer:
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer
@{
ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<p>@Localizer["Use this area to provide additional information."]</p>Implementation mặc định của IViewLocalizer tìm Resource file (file tài nguyên) dựa trên tên file view. Không có tùy chọn để sử dụng một Resource file chia sẻ toàn cục. ViewLocalizer implement localizer bằng cách sử dụng IHtmlLocalizer, vì vậy Razor không HTML encode chuỗi đã bản địa hóa. Bạn có thể tham số hóa các chuỗi tài nguyên và IViewLocalizer sẽ HTML encode các tham số, nhưng không encode chuỗi tài nguyên. Xem xét markup Razor sau đây:
@Localizer["<i>Hello</i> <b>{0}!</b>", UserManager.GetUserName(User)]Một Resource file tiếng Pháp có thể chứa nội dung sau:
| Key | Value |
|---|---|
<i>Hello</i> <b>{0}!</b> | <i>Bonjour</i> <b>{0} !</b> |
View được render sẽ chứa markup HTML từ Resource file.
Lưu ý: Thông thường, chỉ nên bản địa hóa văn bản, không phải HTML.
Để sử dụng một Resource file chia sẻ trong một view, inject IHtmlLocalizer<T>:
@using Microsoft.AspNetCore.Mvc.Localization
@using Localization.Services
@inject IViewLocalizer Localizer
@inject IHtmlLocalizer<SharedResource> SharedLocalizer
@{
ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h1>@SharedLocalizer["Hello!"]</h1>DataAnnotations localization (Bản địa hóa DataAnnotations)
Các thông báo lỗi của DataAnnotations được bản địa hóa bằng IStringLocalizer<T>. Sử dụng tùy chọn ResourcesPath = "Resources", các thông báo lỗi trong RegisterViewModel có thể được lưu trữ ở một trong các đường dẫn sau:
- Resources/ViewModels.Account.RegisterViewModel.fr.resx
- Resources/ViewModels/Account/RegisterViewModel.fr.resx
public class RegisterViewModel
{
[Required(ErrorMessage = "The Email field is required.")]
[EmailAddress(ErrorMessage = "The Email field is not a valid email address.")]
[Display(Name = "Email")]
public string Email { get; set; }
[Required(ErrorMessage = "The Password field is required.")]
[StringLength(8, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}Các attribute không phải validation cũng được bản địa hóa.
Sử dụng một chuỗi tài nguyên cho nhiều class
Đoạn code sau đây cho thấy cách sử dụng một chuỗi tài nguyên cho các attribute validation với nhiều class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddDataAnnotationsLocalization(options => {
options.DataAnnotationLocalizerProvider = (type, factory) =>
factory.Create(typeof(SharedResource));
});
}Trong đoạn code trên, SharedResource là class tương ứng với resx nơi các thông báo validation của bạn được lưu trữ. Với cách tiếp cận này, DataAnnotations sẽ chỉ sử dụng SharedResource, thay vì tài nguyên cho từng class.
Cung cấp tài nguyên đã bản địa hóa cho các ngôn ngữ và Culture bạn hỗ trợ
SupportedCultures và SupportedUICultures
ASP.NET Core cho phép bạn chỉ định hai giá trị Culture: SupportedCultures và SupportedUICultures. Đối tượng CultureInfo cho SupportedCultures xác định kết quả của các hàm phụ thuộc vào Culture, chẳng hạn như định dạng ngày, giờ, số và tiền tệ. SupportedCultures cũng xác định thứ tự sắp xếp văn bản, quy ước về chữ hoa/thường và so sánh chuỗi. SupportedUICultures xác định những chuỗi đã được dịch (từ các file .resx) nào được ResourceManager tra cứu. ResourceManager tra cứu các chuỗi theo Culture cụ thể được xác định bởi CurrentUICulture. Mỗi thread trong .NET đều có các đối tượng CurrentCulture và CurrentUICulture. Framework kiểm tra các giá trị này khi render các hàm phụ thuộc vào Culture. Nếu Culture của thread hiện tại được đặt thành en-US (English, United States), DateTime.Now.ToLongDateString() hiển thị Thursday, February 18, 2016; nhưng nếu CurrentCulture được đặt thành es-ES (Spanish, Spain), đầu ra là jueves, 18 de febrero de 2016.
Resource file (File tài nguyên)
Một Resource file là một cơ chế hữu ích để tách các chuỗi có thể bản địa hóa khỏi code. Các chuỗi đã được dịch cho ngôn ngữ không phải ngôn ngữ mặc định được tách biệt trong các Resource file .resx. Ví dụ: bạn có thể muốn tạo Resource file tiếng Tây Ban Nha có tên Welcome.es.resx chứa các chuỗi đã được dịch. "es" là mã ngôn ngữ cho tiếng Tây Ban Nha. Để tạo Resource file này trong Visual Studio:
- Trong Solution Explorer, nhấp chuột phải vào thư mục sẽ chứa Resource file > Add > New Item.
- Trong hộp Search installed templates, nhập "resource" và đặt tên file.
- Nhập giá trị khóa (chuỗi gốc) vào cột Name và chuỗi đã dịch vào cột Value.
Visual Studio hiển thị file Welcome.es.resx.
!Solution Explorer showing the Welcome Spanish (es) resource file
Đặt tên Resource file
Tài nguyên được đặt tên theo tên type đầy đủ của class trừ đi tên assembly. Ví dụ: một tài nguyên tiếng Pháp trong một project có assembly chính là LocalizationWebsite.Web.dll cho class LocalizationWebsite.Web.Startup sẽ được đặt tên là Startup.fr.resx. Một tài nguyên cho class LocalizationWebsite.Web.Controllers.HomeController sẽ được đặt tên là Controllers.HomeController.fr.resx. Nếu namespace của class mục tiêu không giống với tên assembly, bạn sẽ cần tên type đầy đủ. Ví dụ: trong project mẫu, một tài nguyên cho type ExtraNamespace.Tools sẽ được đặt tên là ExtraNamespace.Tools.fr.resx.
Trong project mẫu, phương thức ConfigureServices đặt ResourcesPath thành "Resources", vì vậy đường dẫn tương đối của project cho Resource file tiếng Pháp của home controller là Resources/Controllers.HomeController.fr.resx. Ngoài ra, bạn có thể dùng thư mục để tổ chức Resource file. Đối với home controller, đường dẫn sẽ là Resources/Controllers/HomeController.fr.resx. Nếu bạn không sử dụng tùy chọn ResourcesPath, file .resx sẽ nằm trong thư mục gốc của project.
| Tên tài nguyên | Quy ước đặt tên |
|---|---|
| Resources/Controllers.HomeController.fr.resx | Dùng dấu chấm |
| Resources/Controllers/HomeController.fr.resx | Dùng đường dẫn |
Các Resource file sử dụng @inject IViewLocalizer trong Razor views tuân theo một mẫu tương tự. Resource file cho một view có thể được đặt tên theo quy ước dấu chấm hoặc đường dẫn. Các Resource file Razor view bắt chước đường dẫn của file view liên kết. Giả sử chúng ta đặt ResourcesPath thành "Resources", Resource file tiếng Pháp liên kết với view Views/Home/About.cshtml có thể là một trong các đường dẫn sau:
- Resources/Views/Home/About.fr.resx
- Resources/Views.Home.About.fr.resx
Nếu bạn không sử dụng tùy chọn ResourcesPath, file .resx cho một view sẽ nằm trong cùng thư mục với view đó.
RootNamespaceAttribute
Attribute RootNamespaceAttribute cung cấp root namespace của một assembly khi root namespace của assembly khác với tên assembly.
Cảnh báo: Điều này có thể xảy ra khi tên project không phải là một .NET identifier hợp lệ. Ví dụ: my-project-name.csproj sẽ sử dụng root namespace my_project_name và tên assembly my-project-name, dẫn đến lỗi này.
Nếu root namespace của assembly khác với tên assembly:
- Localization (bản địa hóa) sẽ không hoạt động theo mặc định.
- Localization thất bại do cách tài nguyên được tìm kiếm trong assembly.
RootNamespacelà một giá trị build-time không có sẵn cho tiến trình đang thực thi.
Nếu RootNamespace khác với AssemblyName, hãy thêm nội dung sau vào AssemblyInfo.cs (thay thế các giá trị tham số bằng giá trị thực tế):
using System.Reflection;
using Microsoft.Extensions.Localization;
[assembly: ResourceLocation("Resource Folder Name")]
[assembly: RootNamespace("App Root Namespace")]Đoạn code trên cho phép phân giải thành công các file resx.
Hành vi fallback của Culture
Khi tìm kiếm tài nguyên, quá trình Localization thực hiện "culture fallback" (dự phòng Culture). Bắt đầu từ Culture được yêu cầu, nếu không tìm thấy, nó sẽ chuyển về Culture cha của Culture đó. Thuộc tính CultureInfo.Parent đại diện cho Culture cha. Điều này thường (nhưng không phải lúc nào cũng) có nghĩa là xóa ký hiệu quốc gia khỏi ISO. Ví dụ: phương ngữ tiếng Tây Ban Nha được nói ở Mexico là "es-MX". Nó có Culture cha là "es" — tiếng Tây Ban Nha không đặc trưng cho bất kỳ quốc gia nào.
Hãy tưởng tượng trang web của bạn nhận được một request cho tài nguyên "Welcome" sử dụng Culture "fr-CA". Hệ thống Localization tìm kiếm các tài nguyên sau theo thứ tự và chọn kết quả khớp đầu tiên:
- Welcome.fr-CA.resx
- Welcome.fr.resx
- Welcome.resx (nếu
NeutralResourcesLanguagelà "fr-CA")
Ví dụ: nếu bạn xóa ký hiệu Culture ".fr" và đặt Culture thành tiếng Pháp, Resource file mặc định sẽ được đọc và các chuỗi được bản địa hóa. Resource manager chỉ định một tài nguyên mặc định hoặc dự phòng khi không có gì phù hợp với Culture được yêu cầu. Nếu bạn muốn chỉ trả về khóa khi thiếu tài nguyên cho Culture được yêu cầu, bạn không được có Resource file mặc định.
Tạo Resource file bằng Visual Studio
Nếu bạn tạo một Resource file trong Visual Studio mà không có Culture trong tên file (ví dụ: Welcome.resx), Visual Studio sẽ tạo một class C# với một thuộc tính cho mỗi chuỗi. Đó thường không phải là điều bạn muốn với ASP.NET Core. Thông thường bạn không có một Resource file .resx mặc định (một file .resx không có tên Culture). Chúng tôi đề xuất bạn tạo file .resx với tên Culture (ví dụ: Welcome.fr.resx). Khi bạn tạo file .resx với tên Culture, Visual Studio sẽ không tạo file class.
Thêm các Culture khác
Mỗi tổ hợp ngôn ngữ và Culture (ngoại trừ ngôn ngữ mặc định) yêu cầu một Resource file riêng. Bạn tạo Resource file cho các Culture và locale khác nhau bằng cách tạo các Resource file mới trong đó mã ngôn ngữ ISO là một phần của tên file (ví dụ: en-us, fr-ca và en-gb). Các mã ISO này được đặt giữa tên file và phần mở rộng .resx, như trong Welcome.es-MX.resx (tiếng Tây Ban Nha/Mexico).
Triển khai chiến lược chọn ngôn ngữ/Culture cho mỗi request
Cấu hình Localization
Localization (bản địa hóa) được cấu hình trong phương thức Startup.ConfigureServices:
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();AddLocalizationthêm các dịch vụ Localization vào container dịch vụ. Đoạn code trên cũng đặt đường dẫn tài nguyên thành "Resources".AddViewLocalizationthêm hỗ trợ cho các file view đã bản địa hóa. Trong ví dụ này, Localization view dựa trên hậu tố file view. Ví dụ: "fr" trong fileIndex.fr.cshtml.AddDataAnnotationsLocalizationthêm hỗ trợ cho các thông báo validationDataAnnotationsđã bản địa hóa thông qua các abstractionIStringLocalizer.
Localization Middleware (Phần mềm trung gian bản địa hóa)
Culture hiện tại trên một request được đặt trong Localization Middleware. Localization Middleware được bật trong phương thức Startup.Configure. Localization Middleware phải được cấu hình trước bất kỳ Middleware nào có thể kiểm tra Culture của request (ví dụ: app.UseMvcWithDefaultRoute()). Localization Middleware phải xuất hiện sau Routing Middleware nếu sử dụng RouteDataRequestCultureProvider. Để biết thêm thông tin về thứ tự Middleware, xem ASP.NET Core Middleware.
var supportedCultures = new[] { "en-US", "fr" };
var localizationOptions = new RequestLocalizationOptions().SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);UseRequestLocalization khởi tạo một đối tượng RequestLocalizationOptions. Trên mỗi request, danh sách RequestCultureProvider trong RequestLocalizationOptions được duyệt và provider đầu tiên có thể xác định thành công Culture của request sẽ được sử dụng. Các provider mặc định đến từ class RequestLocalizationOptions:
QueryStringRequestCultureProviderCookieRequestCultureProviderAcceptLanguageHeaderRequestCultureProvider
Danh sách mặc định đi từ cụ thể nhất đến ít cụ thể nhất. Ở phần sau của bài viết, chúng ta sẽ thấy cách bạn có thể thay đổi thứ tự và thậm chí thêm một custom culture provider. Nếu không có provider nào xác định được Culture của request, thì DefaultRequestCulture sẽ được sử dụng.
QueryStringRequestCultureProvider
Một số ứng dụng sẽ sử dụng query string để đặt CultureInfo. Đối với các ứng dụng sử dụng cách tiếp cận cookie hoặc Accept-Language header, việc thêm query string vào URL rất hữu ích để debug và test code. Theo mặc định, QueryStringRequestCultureProvider được đăng ký là provider Localization đầu tiên trong danh sách RequestCultureProvider. Bạn truyền các tham số query string culture và ui-culture. Ví dụ sau đặt Culture cụ thể (ngôn ngữ và vùng) thành tiếng Tây Ban Nha/Mexico:
http://localhost:5000/?culture=es-MX&ui-culture=es-MX
Nếu bạn chỉ truyền một trong hai (culture hoặc ui-culture), query string provider sẽ đặt cả hai giá trị bằng cách sử dụng giá trị bạn đã truyền. Ví dụ: chỉ đặt Culture sẽ đặt cả Culture và UICulture:
http://localhost:5000/?culture=es-MX
CookieRequestCultureProvider
Các ứng dụng production thường cung cấp cơ chế để đặt Culture bằng cookie Culture của ASP.NET Core. Sử dụng phương thức MakeCookieValue để tạo cookie.
DefaultCookieName của CookieRequestCultureProvider trả về tên cookie mặc định được dùng để theo dõi thông tin Culture ưa thích của người dùng. Tên cookie mặc định là .AspNetCore.Culture.
Định dạng cookie là c=%LANGCODE%|uic=%LANGCODE%, trong đó c là Culture và uic là UICulture, ví dụ:
c=en-UK|uic=en-US
Nếu bạn chỉ chỉ định một trong hai thông tin Culture và UI Culture, Culture được chỉ định sẽ được dùng cho cả thông tin Culture và UI Culture.
Accept-Language HTTP header
Accept-Language header có thể được đặt trong hầu hết các trình duyệt và ban đầu được thiết kế để chỉ định ngôn ngữ của người dùng. Cài đặt này cho biết trình duyệt đã được cấu hình để gửi gì hoặc đã kế thừa từ hệ điều hành nền. Accept-Language HTTP header từ request của trình duyệt không phải là cách chắc chắn để phát hiện ngôn ngữ ưa thích của người dùng (xem Setting language preferences in a browser). Một ứng dụng production nên bao gồm cách để người dùng tùy chỉnh lựa chọn Culture của họ.
Đặt Accept-Language HTTP header trong IE
- Từ biểu tượng gear, nhấn Internet Options.
- Nhấn Languages.
- Nhấn Set Language Preferences.
- Nhấn Add a language.
- Thêm ngôn ngữ.
- Nhấn ngôn ngữ, sau đó nhấn Move Up.
Sử dụng custom provider
Giả sử bạn muốn cho phép khách hàng lưu trữ ngôn ngữ và Culture của họ trong cơ sở dữ liệu. Bạn có thể viết một provider để tra cứu các giá trị này cho người dùng. Đoạn code sau đây cho thấy cách thêm một custom provider:
private const string enUSCulture = "en-US";
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo(enUSCulture),
new CultureInfo("fr")
};
options.DefaultRequestCulture = new RequestCulture(culture: enUSCulture, uiCulture: enUSCulture);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.AddInitialRequestCultureProvider(new CustomRequestCultureProvider(async context =>
{
// My custom request culture logic
return await Task.FromResult(new ProviderCultureResult("en"));
}));
});Sử dụng RequestLocalizationOptions để thêm hoặc xóa các Localization provider.
Thay đổi thứ tự của các request culture provider
RequestLocalizationOptions có ba request culture provider mặc định: QueryStringRequestCultureProvider, CookieRequestCultureProvider và AcceptLanguageHeaderRequestCultureProvider. Sử dụng thuộc tính RequestLocalizationOptions.RequestCultureProviders để thay đổi thứ tự của các provider này như được hiển thị trong ví dụ sau:
app.UseRequestLocalization(options =>
{
var questStringCultureProvider = options.RequestCultureProviders[0];
options.RequestCultureProviders.RemoveAt(0);
options.RequestCultureProviders.Insert(1, questStringCultureProvider);
});Trong ví dụ trên, thứ tự của QueryStringRequestCultureProvider và CookieRequestCultureProvider được hoán đổi, vì vậy RequestLocalizationMiddleware sẽ tìm kiếm Culture từ cookie trước, sau đó mới đến query string.
Như đã đề cập trước đó, hãy thêm một custom provider thông qua AddInitialRequestCultureProvider, phương thức này đặt thứ tự thành 0, vì vậy provider này có quyền ưu tiên hơn các provider khác.
Đặt Culture theo chương trình
Project mẫu Localization.StarterWeb trên GitHub có UI để đặt Culture. File Views/Shared/_SelectLanguagePartial.cshtml cho phép bạn chọn Culture từ danh sách các Culture được hỗ trợ:
@using Microsoft.AspNetCore.Builder
@using Microsoft.AspNetCore.Http.Features
@using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Options
@inject IViewLocalizer Localizer
@inject IOptions<RequestLocalizationOptions> LocOptions
@{
var requestCulture = Context.Features.Get<IRequestCultureFeature>();
var cultureItems = LocOptions.Value.SupportedUICultures
.Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
.ToList();
var returnUrl = string.IsNullOrEmpty(Context.Request.Path) ? "~/" : $"~{Context.Request.Path.Value}";
}
<div title="@Localizer["Request culture provider:"] @requestCulture?.Provider?.GetType().Name">
<form id="selectLanguage" asp-controller="Home"
asp-action="SetLanguage" asp-route-returnUrl="@returnUrl"
method="post" class="form-horizontal" role="form">
<label asp-for="@requestCulture.RequestCulture.UICulture.Name">@Localizer["Language:"]</label> <select name="culture"
onchange="this.form.submit();"
asp-for="@requestCulture.RequestCulture.UICulture.Name" asp-items="cultureItems">
</select>
</form>
</div>File Views/Shared/_SelectLanguagePartial.cshtml được thêm vào phần footer của file layout để nó sẽ có sẵn cho tất cả các view:
<div class="container body-content" style="margin-top:60px">
@RenderBody()
<hr>
<footer>
<div class="row">
<div class="col-md-6">
<p>© @System.DateTime.Now.Year - Localization</p>
</div>
<div class="col-md-6 text-right">
@await Html.PartialAsync("_SelectLanguagePartial")
</div>
</div>
</footer>
</div>Phương thức SetLanguage đặt cookie Culture.
[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);
}Model binding route data và query string
Xem Globalization behavior of model binding route data and query strings.
Các thuật ngữ Globalization và Localization
Quá trình bản địa hóa ứng dụng của bạn cũng đòi hỏi hiểu biết cơ bản về các bộ ký tự có liên quan thường được sử dụng trong phát triển phần mềm hiện đại và hiểu các vấn đề liên quan đến chúng. Mặc dù tất cả các máy tính đều lưu trữ văn bản dưới dạng số (code), các hệ thống khác nhau lưu trữ cùng một văn bản bằng các số khác nhau. Quá trình Localization đề cập đến việc dịch giao diện người dùng (UI) của ứng dụng cho một Culture/locale cụ thể.
Localizability là một quá trình trung gian để xác minh rằng một ứng dụng đã được toàn cầu hóa sẵn sàng cho việc bản địa hóa.
Định dạng RFC 4646 cho tên Culture là <languagecode2>-<country/regioncode2>, trong đó <languagecode2> là mã ngôn ngữ và <country/regioncode2> là mã Culture con. Ví dụ: es-CL cho tiếng Tây Ban Nha (Chile), en-US cho tiếng Anh (Hoa Kỳ) và en-AU cho tiếng Anh (Úc).
Internationalization (Quốc tế hóa) thường được viết tắt là "I18N". Chữ viết tắt lấy chữ cái đầu tiên và cuối cùng cùng với số lượng chữ cái ở giữa, vì vậy 18 đại diện cho số chữ cái giữa chữ "I" đầu tiên và chữ "N" cuối cùng. Tương tự áp dụng cho Globalization (G11N) và Localization (L10N).
Các thuật ngữ:
- Globalization (G11N) (Toàn cầu hóa): Quá trình làm cho ứng dụng hỗ trợ các ngôn ngữ và vùng địa lý khác nhau.
- Localization (L10N) (Bản địa hóa): Quá trình tùy chỉnh ứng dụng cho một ngôn ngữ và vùng địa lý nhất định.
- Internationalization (I18N) (Quốc tế hóa): Mô tả cả Globalization (toàn cầu hóa) và Localization (bản địa hóa).
- Culture (Văn hóa/Ngôn ngữ): Một ngôn ngữ và tùy chọn là một vùng địa lý.
- Neutral culture (Culture trung tính): Một Culture có ngôn ngữ được chỉ định, nhưng không có vùng địa lý (ví dụ: "en", "es").
- Specific culture (Culture cụ thể): Một Culture có ngôn ngữ và vùng địa lý được chỉ định (ví dụ: "en-US", "en-GB", "es-CL").
- Parent culture (Culture cha): Culture trung tính chứa một Culture cụ thể (ví dụ: "en" là Culture cha của "en-US" và "en-GB").
- Locale (Ngôn ngữ địa phương): Locale giống với Culture.
Lưu ý: Bạn có thể không nhập được dấu phẩy thập phân trong các trường thập phân. Để hỗ trợ jQuery validation cho các locale không phải tiếng Anh sử dụng dấu phẩy (",") làm dấu thập phân và các định dạng ngày không phải US-English, bạn phải thực hiện các bước để toàn cầu hóa ứng dụng. Xem GitHub comment 4076 để được hướng dẫn thêm dấu phẩy thập phân.
Lưu ý: Trước ASP.NET Core 3.0, các ứng dụng web ghi một log loại LogLevel.Warning cho mỗi request nếu Culture được yêu cầu không được hỗ trợ. Việc ghi một LogLevel.Warning cho mỗi request có thể tạo ra các file log lớn với thông tin dư thừa. Hành vi này đã được thay đổi trong ASP.NET Core 3.0. RequestLocalizationMiddleware ghi một log loại LogLevel.Debug, giúp giảm kích thước log production.
Tài nguyên bổ sung
- Troubleshoot ASP.NET Core localization
- Localization.StarterWeb project được sử dụng trong bài viết.
- Globalizing and localizing .NET applications
- Resources in .resx Files
- Localization & Generics
- Make an ASP.NET Core app's content localizable
- Provide localized resources for languages and cultures in an ASP.NET Core app
- Strategies for selecting language and culture in a localized ASP.NET Core app
- Url culture provider using middleware as filters in ASP.NET Core
- Applying the RouteDataRequest CultureProvider globally with middleware as filters