Skip to main content

Ang Kumpletong Gabay sa ASP.NET Core Localization

Mula IStringLocalizer hanggang production: i-set up ang resource-based localization sa ASP.NET Core, pagkatapos ay i-automate ang pagsasalin gamit ang AI.

1

I-enable ang Localization Services

I-register ang localization services sa Program.cs gamit ang AddLocalization(), i-configure ang mga sinusuportahang culture, at idagdag ang request localization middleware. Ikinakabit nito ang buong localization pipeline para sa inyong ASP.NET Core application.

Nagre-register ang AddLocalization() ng IStringLocalizer at IStringLocalizerFactory sa DI container. Sinasabi ng ResourcesPath kung saan hahanapin ng framework ang inyong mga .resx file. Pinapagana ng AddViewLocalization() ang IViewLocalizer sa mga Razor view, at pinapagana ng AddDataAnnotationsLocalization() ang mga localized validation message.
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

Gumawa ng mga RESX Resource File

Gumagamit ang ASP.NET Core ng mga RESX (XML resource) file para sa mga pagsasalin. Gumawa ng isang file bawat culture bawat class: HomeController.en.resx, HomeController.de.resx, atbp. Reresolbahin ng framework ang tamang file batay sa kasalukuyang request culture.

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>
Gumamit ng SharedResource class na may sarili nitong mga RESX file para sa mga string na ibinabahagi sa mga controller at view — mga label ng button, item sa navigation, at mga karaniwang validation message. Iniiwasan nito ang pagdodoble ng mga key sa dose-dosenang controller-specific na RESX file.
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

Gamitin ang IStringLocalizer sa mga Controller at Service

I-inject ang IStringLocalizer&lt;T&gt; sa anumang controller, service, o middleware sa pamamagitan ng dependency injection. Ang generic type parameter na T ang nagtatakda kung aling RESX file ang ilo-load. Gamitin ang bracket syntax na localizer["Key"] para kunin ang isinaling string, na may opsyonal na mga format parameter.

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 });
    }
}
Kung ibinabalik ng IStringLocalizer ang pangalan ng key sa halip na ang isinaling value, suriin ang 3 bagay: (1) tumutugma ang pangalan ng RESX file sa buong namespace ng class, (2) tumuturo ang ResourcesPath sa AddLocalization() sa tamang folder, at (3) naka-set sa Embedded Resource sa Visual Studio ang Build Action ng RESX file.
4

I-configure ang Request Culture Middleware

Tinutukoy ng ASP.NET Core ang request culture gamit ang chain ng provider: query string, cookie, at Accept-Language header (sa ganitong pagkakasunod). Maaari kayong magdagdag ng mga custom provider — halimbawa, pagbabasa ng culture mula sa URL route segment tulad ng /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);
}
Mahalaga ang pagkakasunod-sunod ng middleware. Dapat tawagin ang UseRequestLocalization() pagkatapos ng UseRouting() ngunit bago ang UseEndpoints() o MapControllers(). Kapag nailagay nang masyadong huli, hindi mase-set ang culture kapag nag-e-execute ang inyong mga controller.
5

I-localize ang Data Annotations

Maaaring i-localize ang mga validation attribute tulad ng [Required], [StringLength], at [Display] sa pamamagitan ng pag-set ng kanilang ErrorMessage o Name property sa mga pangalan ng RESX key. Tawagin ang AddDataAnnotationsLocalization() sa Program.cs upang paganahin ito.

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"
Ginagamit ng data annotation localization ang pangalan ng ViewModel class para hanapin ang mga RESX file, hindi ang Controller. Para sa RegisterViewModel, hinahanap ng framework ang Resources/ViewModels/RegisterViewModel.de.resx. Kung ang mga RESX file ninyo ay pinangalanan ayon sa controller, hindi malolocalize ang mga validation message.
6

Hawakan ang mga Plural at ICU Message

.NET ay walang built-in na suporta para sa plural rule tulad ng ICU. Para sa mga simpleng kaso, gumamit ng hiwa-hiwalay na RESX key (ItemCount_One, ItemCount_Other) na may code switch. Para sa kumpletong ICU MessageFormat support sa lahat ng CLDR plural category, gamitin ang MessageFormat.NET library.

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 {# عنصر}}";
Huwag kailanman gamitin ang count == 1 para tukuyin ang mga singular na anyo. Tinatrato ng French ang 0 bilang singular. May magkakahiwalay na anyo ang Russian para sa 'few' at 'many'. May 6 na plural category ang Arabic. Gumamit ng mga CLDR-aware plural rule o ng library tulad ng MessageFormat.NET na tama itong hinahawakan.
7

I-localize ang mga Razor View

Gumamit ng IViewLocalizer sa mga Razor view sa pamamagitan ng @inject. Tinutugma nito ang mga RESX file batay sa file path ng view. Para sa mga string na may markup at ligtas sa HTML, gamitin ang IHtmlLocalizer. Awtomatikong ginagamit ng mga Tag Helper tulad ng asp-for at asp-validation-for ang mga localized na Display at ErrorMessage attribute.

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>
Tinutugma ng IViewLocalizer ang mga RESX file ayon sa view path: ang Views/Home/Index.cshtml ay naghahanap ng Resources/Views/Home/Index.de.resx. Kung gusto ninyong magbahagi ng mga string sa mga view, i-inject nang hiwalay ang IStringLocalizer&lt;SharedResource&gt;.
8

I-automate ang Pagsasalin ng RESX

Kapag kumpleto na ang inyong localization setup, isalin ang inyong mga RESX file gamit ang AI. Sa inyong IDE, hilingin sa inyong AI assistant na isalin ang source RESX, o gamitin ang i18n Agent CLI sa inyong CI/CD pipeline upang panatilihing naka-sync ang mga pagsasalin.

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
Isalin nang paunti-unti — kapag nagdagdag kayo ng mga bagong key sa inyong source RESX file, isalin lamang ang diff sa halip na i-regenerate ang lahat ng file. Pinapanatili nito ang anumang na-review ng tao na mga pagsasalin at binabawasan ang churn.

I-automate ang Kalidad ng Pagsasalin

Matukoy ang mga nawawalang key at sirang placeholder bago pa ma-ship gamit ang i18n-validate. Subukan ang inyong UI gamit ang pseudo-translation sa pamamagitan ng i18n-pseudo bago dumating ang tunay na mga pagsasalin.

Ayusin ang Locale Fallback gamit ang LocaleChain.NET

BCP 47 truncation lang ang ginagamit ng built-in na CultureInfo.Parent hierarchy ng .NET: bumabagsak ang pt-BR sa pt at pagkatapos sa InvariantCulture, at nilalaktawan ang pt-PT. Nagbibigay ang LocaleChain.NET ng configurable na per-locale fallback chain para sa buong .NET ecosystem.

Kung walang LocaleChain.NET, makakakita ang pt-BR user ng English kapag may nawawalang Portuguese string — kahit mayroon kayong kumpletong pt-PT translation. Ang parehong problema ay nakaaapekto sa es-MX (nilalaktawan ang es-419), zh-Hant (nilalaktawan ang zh-Hans), at dose-dosenang iba pang regional variant.
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<>));

Mga Karaniwang Pitfall

Hindi Naka-set ang Culture sa Request

Palaging lumalabas ang default na wika sa mga pagsasalin. Tiyaking natatawag ang UseRequestLocalization() sa middleware pipeline at naka-configure ang mga culture provider. I-verify na nagpapadala ang browser ng mga header na Accept-Language. Subukan gamit ang ?culture=de sa query string upang matiyak na gumagana ang middleware.

Hindi Nahanap ang RESX File

Ibinabalik ng IStringLocalizer ang pangalan ng key sa halip na ang isinaling value. Ang pinakakaraniwang sanhi ay pangalan ng RESX file na hindi tumutugma sa full namespace path ng class relative sa ResourcesPath. I-enable ang debug logging para sa Microsoft.Extensions.Localization upang makita kung aling mga path ang hinahanapan ng framework.

Mali ang Pagkakasunod-sunod ng Middleware

Dapat lumitaw ang UseRequestLocalization() bago ang UseEndpoints() at MapControllers(). Kapag inilagay pagkatapos, hindi nase-set ang request culture kapag nag-e-execute ang mga controller. Sa .NET 6+ minimal hosting, tawagin ito bago ang app.MapControllers().

Maling Culture ang Ginagamit ng Background Thread

Per-thread ang CultureInfo.CurrentCulture at CurrentUICulture. Ang mga background task (Task.Run, hosted services) ay nag-iinherit ng thread pool culture, hindi ng request culture. I-capture at i-set nang hayagan ang culture kapag nagdi-dispatch kayo ng background work.

Inirerekomendang Project Structure

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

Subukan ang i18n Agent Ngayon

I-drop dito ang inyong translation file

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

o i-click para mag-browse

Mga target language

Hindi kailangan ang signupInstant na estimate

Locale Fallback gamit ang I18nAgent.LocaleChain

Kapag may nawawalang translation key sa isang regional locale tulad ng pt-BR, diretsong tumatalon ang .NET sa invariant culture sa halip na tingnan muna ang parent locale na pt.

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"},
});

Tingnan ang aming Locale Fallback Guide para sa kumpletong listahan ng mga sinusuportahang framework at 75 built-in na chain. Learn more →

Mga Madalas Itanong