
Panduan Lengkap Lokalisasi ASP.NET Core
Dari IStringLocalizer hingga produksi: siapkan lokalisasi berbasis sumber daya di ASP.NET Core, lalu otomatiskan terjemahan dengan AI.
Aktifkan Layanan Lokalisasi
Daftarkan layanan lokalisasi di Program.cs dengan AddLocalization(), konfigurasikan kultur yang didukung, dan tambahkan middleware lokalisasi permintaan. Tindakan ini menyiapkan seluruh pipeline lokalisasi untuk aplikasi ASP.NET Core Anda.
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();Buat File Sumber Daya RESX
ASP.NET Core menggunakan file RESX (sumber daya XML) untuk terjemahan. Buat satu file per kultur untuk setiap kelas: HomeController.en.resx, HomeController.de.resx, dan seterusnya. Framework memilih file yang benar berdasarkan kultur permintaan saat ini.
<!-- 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 { }Gunakan IStringLocalizer di Controller dan Layanan
Injeksikan IStringLocalizer<T> ke controller, layanan, atau middleware apa pun melalui injeksi dependensi. Parameter tipe generik T menentukan file RESX yang akan dimuat. Gunakan sintaks kurung siku localizer["Key"] untuk mengambil string terjemahan, dengan parameter format opsional.
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 });
}
}Konfigurasikan Middleware Kultur Permintaan
ASP.NET Core menentukan kultur permintaan menggunakan rantai penyedia: string kueri, cookie, dan header Accept-Language (dalam urutan tersebut). Anda dapat menambahkan penyedia khusus — misalnya, membaca kultur dari segmen rute URL seperti /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);
}Lokalkan Anotasi Data
Atribut validasi seperti [Required], [StringLength], dan [Display] dapat dilokalkan dengan mengatur properti ErrorMessage atau Name ke nama kunci RESX. Panggil AddDataAnnotationsLocalization() di Program.cs untuk mengaktifkannya.
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"Tangani Bentuk Jamak dan Pesan ICU
.NET tidak memiliki dukungan bawaan untuk aturan bentuk jamak seperti ICU. Untuk kasus sederhana, gunakan kunci RESX terpisah (ItemCount_One, ItemCount_Other) dengan percabangan kode. Untuk dukungan penuh ICU MessageFormat pada semua kategori bentuk jamak CLDR, gunakan pustaka 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 {# عنصر}}";Lokalkan Tampilan Razor
Gunakan IViewLocalizer dalam tampilan Razor melalui @inject. IViewLocalizer memilih file RESX berdasarkan jalur file tampilan. Untuk string yang aman bagi HTML dan memiliki markup, gunakan IHtmlLocalizer. Tag Helper seperti asp-for dan asp-validation-for secara otomatis menggunakan atribut Display dan ErrorMessage yang telah dilokalkan.
@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>Otomatiskan Terjemahan RESX
Setelah penyiapan lokalisasi selesai, terjemahkan file RESX Anda menggunakan AI. Di IDE, minta asisten AI menerjemahkan RESX sumber Anda, atau gunakan CLI i18n Agent dalam pipeline CI/CD agar terjemahan tetap sinkron.
# 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,esOtomatiskan Kualitas Terjemahan
Perbaiki Fallback Locale dengan LocaleChain.NET
Hierarki CultureInfo.Parent bawaan .NET hanya menggunakan pemotongan BCP 47: pt-BR melakukan fallback ke pt lalu InvariantCulture, dengan melewati pt-PT. LocaleChain.NET menyediakan rantai fallback per locale yang dapat dikonfigurasi untuk seluruh ekosistem .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<>));Kesalahan Umum
Kultur Tidak Ditetapkan pada Permintaan
File RESX Tidak Ditemukan
Urutan Middleware Salah
Thread Latar Belakang Menggunakan Kultur yang Salah
Struktur Proyek yang Disarankan
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.csprojCoba i18n Agent Sekarang
Lepaskan file terjemahan Anda di sini
JSON, YAML, PO, XML, CSV, Markdown, Properties
atau klik untuk menjelajahi
Bahasa target
Fallback Locale dengan I18nAgent.LocaleChain
Ketika kunci terjemahan tidak tersedia dalam locale regional seperti pt-BR, .NET langsung beralih ke kultur invarian alih-alih memeriksa locale induk pt terlebih dahulu.
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"},
});Lihat Panduan Fallback Bahasa kami untuk daftar lengkap framework yang didukung dan 75 rantai bawaan. Learn more →