Skip to main content

Ghidul complet pentru localizarea ASP.NET Core

De la IStringLocalizer la producție: configurați localizarea bazată pe resurse în ASP.NET Core, apoi automatizați traducerile cu IA.

1

Activați serviciile de localizare

Înregistrați serviciile de localizare în Program.cs cu AddLocalization(), configurați culturile acceptate și adăugați middleware-ul pentru localizarea solicitării. Astfel se configurează întregul flux de localizare al aplicației dumneavoastră ASP.NET Core.

AddLocalization() înregistrează IStringLocalizer și IStringLocalizerFactory în containerul DI. ResourcesPath indică platformei unde să găsească fișierele .resx. AddViewLocalization() activează IViewLocalizer în vizualizările Razor, iar AddDataAnnotationsLocalization() activează mesajele de validare localizate.
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

Creați fișiere de resurse RESX

ASP.NET Core utilizează fișiere RESX (resurse XML) pentru traduceri. Creați câte un fișier pentru fiecare cultură și clasă: HomeController.en.resx, HomeController.de.resx etc. Platforma identifică fișierul corect pe baza culturii solicitării curente.

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>
Utilizați o clasă SharedResource cu propriile fișiere RESX pentru șirurile comune controlerelor și vizualizărilor, precum etichetele butoanelor, elementele de navigare și mesajele de validare uzuale. Astfel evitați duplicarea cheilor în zeci de fișiere RESX specifice controlerelor.
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

Utilizați IStringLocalizer în controlere și servicii

Injectați IStringLocalizer&lt;T&gt; în orice controler, serviciu sau middleware prin injectarea dependențelor. Parametrul de tip generic T stabilește ce fișier RESX se încarcă. Utilizați sintaxa cu paranteze drepte localizer["Key"] pentru a prelua șirurile traduse, cu parametri de formatare opționali.

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 });
    }
}
Dacă IStringLocalizer returnează numele cheii în locul valorii traduse, verificați trei aspecte: (1) denumirea fișierului RESX corespunde spațiului de nume al clasei, (2) ResourcesPath din AddLocalization() indică dosarul corect și (3) proprietatea Build Action a fișierului RESX este setată la Embedded Resource în Visual Studio.
4

Configurați middleware-ul pentru cultura solicitării

ASP.NET Core determină cultura solicitării folosind un lanț de furnizori: șirul de interogare, cookie-ul și antetul Accept-Language, în această ordine. Puteți adăuga furnizori personalizați — de exemplu, pentru a citi cultura dintr-un segment al rutei URL, precum /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);
}
Ordinea middleware-urilor este importantă. UseRequestLocalization() trebuie apelată după UseRouting(), dar înainte de UseEndpoints() sau MapControllers(). Dacă este plasată prea târziu, cultura nu va fi setată când sunt executate controlerele.
5

Localizați adnotările de date

Atributele de validare precum [Required], [StringLength] și [Display] pot fi localizate setând proprietățile ErrorMessage sau Name la numele unor chei RESX. Apelați AddDataAnnotationsLocalization() în Program.cs pentru a activa această funcționalitate.

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"
Localizarea adnotărilor de date utilizează numele clasei ViewModel pentru a găsi fișierele RESX, nu numele controlerului. Pentru RegisterViewModel, platforma caută Resources/ViewModels/RegisterViewModel.de.resx. Dacă fișierele RESX poartă numele controlerului, mesajele de validare nu vor fi localizate.
6

Gestionați formele de plural și mesajele ICU

.NET nu include suport integrat pentru regulile de plural precum ICU. Pentru cazurile simple, utilizați chei RESX separate (ItemCount_One, ItemCount_Other) și o ramificație în cod. Pentru suport complet al ICU MessageFormat în toate categoriile de plural CLDR, utilizați biblioteca 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 {# عنصر}}";
Nu utilizați niciodată count == 1 pentru a identifica formele de singular. În franceză, 0 este tratat ca singular. Rusa are forme separate pentru „few” și „many”. Araba are șase categorii de plural. Utilizați reguli de plural compatibile cu CLDR sau o bibliotecă precum MessageFormat.NET, care le gestionează corect.
7

Localizați vizualizările Razor

Utilizați IViewLocalizer în vizualizările Razor prin @inject. Acesta identifică fișierele RESX pe baza căii fișierului vizualizării. Pentru șiruri cu marcaje HTML redate în siguranță, utilizați IHtmlLocalizer. Tag Helper-ele precum asp-for și asp-validation-for utilizează automat atributele Display și ErrorMessage localizate.

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 identifică fișierele RESX pe baza căii vizualizării: pentru Views/Home/Index.cshtml caută Resources/Views/Home/Index.de.resx. Dacă doriți să partajați șiruri între vizualizări, injectați separat IStringLocalizer&lt;SharedResource&gt;.
8

Automatizați traducerea fișierelor RESX

După finalizarea configurării localizării, traduceți fișierele RESX cu ajutorul IA. În IDE, solicitați asistentului IA să traducă fișierul RESX sursă sau utilizați CLI-ul i18n Agent în fluxul CI/CD pentru a menține traducerile sincronizate.

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
Traduceți incremental — când adăugați chei noi în fișierul RESX sursă, traduceți numai diferențele, fără a regenera toate fișierele. Astfel păstrați traducerile verificate de oameni și reduceți la minimum modificările inutile.

Automatizați controlul calității traducerilor

Identificați cheile lipsă și substituenții nevalizi înainte de lansare cu i18n-validate. Testați interfața cu pseudotraduceri folosind i18n-pseudo înainte de sosirea traducerilor reale.

Corectați mecanismul de rezervă pentru setările regionale cu LocaleChain.NET

Ierarhia CultureInfo.Parent integrată în .NET utilizează numai trunchierea BCP 47: pt-BR revine la pt, apoi la InvariantCulture, omițând pt-PT. LocaleChain.NET oferă lanțuri de rezervă configurabile pentru fiecare setare regională în întregul ecosistem .NET.

Fără LocaleChain.NET, un utilizator pt-BR vede textul în engleză atunci când lipsește un șir în portugheză — chiar dacă există o traducere pt-PT completă. Aceeași problemă afectează es-MX (omite es-419), zh-Hant (omite zh-Hans) și zeci de alte variante regionale.
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<>));

Probleme frecvente

Cultura solicitării nu este setată

Traducerile apar întotdeauna în limba implicită. Verificați dacă UseRequestLocalization() este apelată în fluxul middleware și dacă furnizorii de cultură sunt configurați. Verificați dacă browserul trimite anteturi Accept-Language. Testați cu ?culture=de în șirul de interogare pentru a confirma funcționarea middleware-ului.

Fișierul RESX nu a fost găsit

IStringLocalizer returnează numele cheii în locul valorii traduse. Cea mai frecventă cauză este un nume de fișier RESX care nu corespunde căii complete a spațiului de nume al clasei în raport cu ResourcesPath. Activați jurnalizarea pentru depanare în Microsoft.Extensions.Localization pentru a vedea căile căutate de platformă.

Ordinea middleware-urilor este incorectă

UseRequestLocalization() trebuie să apară înainte de UseEndpoints() și MapControllers(). Dacă este plasată după acestea, cultura solicitării nu este setată când sunt executate controlerele. În găzduirea minimală .NET 6+, apelați-o înainte de app.MapControllers().

Firul de fundal utilizează cultura greșită

CultureInfo.CurrentCulture și CurrentUICulture sunt specifice fiecărui fir. Sarcinile de fundal (Task.Run, serviciile găzduite) moștenesc cultura grupului de fire, nu cultura solicitării. Capturați și setați explicit cultura când distribuiți lucrări în fundal.

Structura recomandată a proiectului

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

Încercați acum i18n Agent

Plasați aici fișierul de traducere

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

sau faceți clic pentru a-l selecta

Limbi țintă

Nu este necesară înregistrareaEstimare instantanee

Mecanism de rezervă pentru setările regionale cu I18nAgent.LocaleChain

Atunci când lipsește o cheie de traducere dintr-o setare regională precum pt-BR, .NET revine direct la cultura invariantă în loc să verifice mai întâi setarea regională părinte 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"},
});

Consultați Ghidul nostru privind mecanismele de rezervă pentru setările regionale, care conține lista completă a platformelor acceptate și cele 75 de lanțuri integrate. Learn more →

Întrebări frecvente