Skip to main content

Panduan Lengkap Lokalisasi ASP.NET Core

Dari IStringLocalizer hingga produksi: siapkan lokalisasi berbasis sumber daya di ASP.NET Core, lalu otomatiskan terjemahan dengan AI.

1

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.

AddLocalization() mendaftarkan IStringLocalizer dan IStringLocalizerFactory di kontainer DI. ResourcesPath memberi tahu framework tempat menemukan file .resx Anda. AddViewLocalization() mengaktifkan IViewLocalizer dalam tampilan Razor, dan AddDataAnnotationsLocalization() mengaktifkan pesan validasi yang dilokalkan.
Program.cs
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();
2

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.{culture}.resx
<!-- 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>
Gunakan kelas SharedResource beserta file RESX-nya sendiri untuk string yang digunakan bersama di berbagai controller dan tampilan — label tombol, item navigasi, dan pesan validasi umum. Cara ini menghindari duplikasi kunci di puluhan file RESX khusus controller.
Shared resources for cross-cutting strings
<!-- 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 { }
3

Gunakan IStringLocalizer di Controller dan Layanan

Injeksikan IStringLocalizer&lt;T&gt; 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.

Controllers/HomeController.cs
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 });
    }
}
Jika IStringLocalizer mengembalikan nama kunci alih-alih nilai terjemahan, periksa tiga hal: (1) penamaan file RESX cocok dengan namespace kelas, (2) ResourcesPath dalam AddLocalization() menunjuk ke folder yang benar, dan (3) Build Action file RESX diatur ke Embedded Resource dalam Visual Studio.
4

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.

Custom RouteDataRequestCultureProvider
// 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 Action
// 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);
}
Urutan middleware itu penting. UseRequestLocalization() harus dipanggil setelah UseRouting(), tetapi sebelum UseEndpoints() atau MapControllers(). Jika ditempatkan terlalu akhir, kultur belum ditetapkan saat controller Anda dijalankan.
5

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.

ViewModels/RegisterViewModel.cs
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"
Lokalisasi anotasi data menggunakan nama kelas ViewModel untuk menemukan file RESX, bukan Controller. Untuk RegisterViewModel, framework mencari Resources/ViewModels/RegisterViewModel.de.resx. Jika file RESX Anda dinamai berdasarkan controller, pesan validasi tidak akan dilokalkan.
6

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.

Plural handling strategies
// 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 {# عنصر}}";
Jangan pernah menggunakan count == 1 untuk mendeteksi bentuk tunggal. Bahasa Prancis memperlakukan 0 sebagai bentuk tunggal. Bahasa Rusia memiliki bentuk terpisah untuk 'sedikit' dan 'banyak'. Bahasa Arab memiliki enam kategori bentuk jamak. Gunakan aturan bentuk jamak yang memahami CLDR atau pustaka seperti MessageFormat.NET yang menanganinya dengan benar.
7

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.

Views/Home/Index.cshtml
@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>
IViewLocalizer memilih file RESX berdasarkan jalur tampilan: Views/Home/Index.cshtml mencari Resources/Views/Home/Index.de.resx. Jika Anda ingin berbagi string di berbagai tampilan, injeksikan IStringLocalizer&lt;SharedResource&gt; secara terpisah.
8

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.

Terminal
# 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,es
Terjemahkan secara bertahap — ketika Anda menambahkan kunci baru ke file RESX sumber, terjemahkan hanya perbedaannya alih-alih membuat ulang semua file. Cara ini mempertahankan terjemahan yang telah ditinjau manusia dan meminimalkan perubahan yang tidak perlu.

Otomatiskan Kualitas Terjemahan

Temukan kunci hilang dan placeholder rusak sebelum dirilis dengan i18n-validate. Uji UI dengan terjemahan semu menggunakan i18n-pseudo sebelum terjemahan asli tersedia.

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.

Tanpa LocaleChain.NET, pengguna pt-BR melihat bahasa Inggris ketika string bahasa Portugis tidak tersedia — bahkan jika Anda memiliki terjemahan pt-PT yang lengkap. Masalah yang sama memengaruhi es-MX (melewati es-419), zh-Hant (melewati zh-Hans), dan puluhan varian regional lainnya.
Terminal
dotnet add package I18nAgent.LocaleChain
Program.cs
using 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

Terjemahan selalu menampilkan bahasa default. Periksa bahwa UseRequestLocalization() dipanggil dalam pipeline middleware dan penyedia kultur telah dikonfigurasi. Pastikan browser mengirim header Accept-Language. Uji dengan ?culture=de dalam string kueri untuk mengonfirmasi bahwa middleware berfungsi.

File RESX Tidak Ditemukan

IStringLocalizer mengembalikan nama kunci alih-alih nilai terjemahan. Penyebab paling umum adalah nama file RESX yang tidak cocok dengan jalur namespace lengkap kelas relatif terhadap ResourcesPath. Aktifkan pencatatan log debug untuk Microsoft.Extensions.Localization guna melihat jalur yang dicari framework.

Urutan Middleware Salah

UseRequestLocalization() harus muncul sebelum UseEndpoints() dan MapControllers(). Jika ditempatkan setelahnya, kultur permintaan belum ditetapkan ketika controller dijalankan. Dalam hosting minimal .NET 6+, panggil fungsi ini sebelum app.MapControllers().

Thread Latar Belakang Menggunakan Kultur yang Salah

CultureInfo.CurrentCulture dan CurrentUICulture berlaku per thread. Tugas latar belakang (Task.Run, layanan yang di-host) mewarisi kultur thread pool, bukan kultur permintaan. Ambil dan tetapkan kultur secara eksplisit ketika mengirim pekerjaan latar belakang.

Struktur Proyek yang Disarankan

Project Structure
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.csproj

Coba i18n Agent Sekarang

Lepaskan file terjemahan Anda di sini

JSON, YAML, PO, XML, CSV, Markdown, Properties

atau klik untuk menjelajahi

Bahasa target

Tidak perlu mendaftarEstimasi instan

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.

Terminal
dotnet add package I18nAgent.LocaleChain
Configuration
using 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 →

Pertanyaan yang Sering Diajukan