Skip to main content

La guida completa alla localizzazione di ASP.NET Core

Da IStringLocalizer alla produzione: configuri la localizzazione basata sulle risorse in ASP.NET Core, quindi automatizzi le traduzioni con l'IA.

1

Abilitare i servizi di localizzazione

Registri i servizi di localizzazione in Program.cs con AddLocalization(), configuri le culture supportate e aggiunga il middleware di localizzazione delle richieste. In questo modo viene collegata l'intera pipeline di localizzazione per l'applicazione ASP.NET Core.

AddLocalization() registra IStringLocalizer e IStringLocalizerFactory nel contenitore DI. ResourcesPath indica al framework dove trovare i file .resx. AddViewLocalization() abilita IViewLocalizer nelle viste Razor, mentre AddDataAnnotationsLocalization() abilita i messaggi di convalida localizzati.
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

Creare file di risorse RESX

ASP.NET Core usa file RESX (risorse XML) per le traduzioni. Crei un file per ogni cultura e classe: HomeController.en.resx, HomeController.de.resx e così via. Il framework risolve il file corretto in base alla cultura della richiesta corrente.

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>
Usi una classe SharedResource con i propri file RESX per le stringhe condivise tra controller e viste, come etichette dei pulsanti, elementi di navigazione e messaggi di convalida comuni. Evita così di duplicare le chiavi in decine di file RESX specifici dei 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

Usare IStringLocalizer nei controller e nei servizi

Inserisca IStringLocalizer&lt;T&gt; in qualsiasi controller, servizio o middleware tramite l'inserimento delle dipendenze. Il parametro di tipo generico T determina quale file RESX caricare. Usi la sintassi tra parentesi quadre localizer["Key"] per recuperare le stringhe tradotte, con parametri di formato facoltativi.

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 });
    }
}
Se IStringLocalizer restituisce il nome della chiave anziché il valore tradotto, controlli tre aspetti: (1) la denominazione del file RESX corrisponde allo spazio dei nomi della classe, (2) ResourcesPath in AddLocalization() indica la cartella corretta e (3) Build Action del file RESX è impostata su Embedded Resource in Visual Studio.
4

Configurare il middleware della cultura della richiesta

ASP.NET Core determina la cultura della richiesta mediante una catena di provider: stringa di query, cookie e intestazione Accept-Language, in questo ordine. Può aggiungere provider personalizzati, ad esempio per leggere la cultura da un segmento di route URL come /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);
}
L'ordine dei middleware è importante. UseRequestLocalization() deve essere chiamato dopo UseRouting(), ma prima di UseEndpoints() o MapControllers(). Se viene posizionato troppo tardi, la cultura non sarà impostata durante l'esecuzione dei controller.
5

Localizzare le annotazioni dei dati

Gli attributi di convalida come [Required], [StringLength] e [Display] possono essere localizzati impostando le rispettive proprietà ErrorMessage o Name sui nomi delle chiavi RESX. Chiami AddDataAnnotationsLocalization() in Program.cs per abilitare questa funzione.

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"
La localizzazione delle annotazioni dei dati usa il nome della classe ViewModel per trovare i file RESX, non il controller. Per RegisterViewModel, il framework cerca Resources/ViewModels/RegisterViewModel.de.resx. Se i file RESX prendono il nome dal controller, i messaggi di convalida non verranno localizzati.
6

Gestire plurali e messaggi ICU

.NET non offre un supporto integrato delle regole del plurale analogo a ICU. Per i casi semplici, usi chiavi RESX separate (ItemCount_One, ItemCount_Other) con uno switch nel codice. Per il supporto completo di ICU MessageFormat in tutte le categorie del plurale CLDR, usi la 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 {# عنصر}}";
Non usi mai count == 1 per rilevare le forme singolari. Il francese considera 0 singolare. Il russo ha forme separate per 'few' e 'many'. L'arabo ha sei categorie del plurale. Usi regole del plurale compatibili con CLDR o una biblioteca come MessageFormat.NET, che le gestisce correttamente.
7

Localizzare le viste Razor

Usi IViewLocalizer nelle viste Razor tramite @inject. Risolve i file RESX in base al percorso del file della vista. Per stringhe con markup e HTML sicuro, usi IHtmlLocalizer. I Tag Helper come asp-for e asp-validation-for usano automaticamente gli attributi Display ed ErrorMessage localizzati.

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 risolve i file RESX in base al percorso della vista: Views/Home/Index.cshtml cerca Resources/Views/Home/Index.de.resx. Se desidera condividere stringhe tra le viste, inserisca separatamente IStringLocalizer&lt;SharedResource&gt;.
8

Automatizzare le traduzioni RESX

Dopo aver completato la configurazione della localizzazione, traduca i file RESX con l'IA. Nell'IDE, chieda all'assistente IA di tradurre il file RESX di origine oppure usi la CLI di i18n Agent nella pipeline CI/CD per mantenere sincronizzate le traduzioni.

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
Traduca in modo incrementale: quando aggiunge nuove chiavi al file RESX di origine, traduca soltanto il diff anziché rigenerare tutti i file. In questo modo preserva le traduzioni revisionate da persone e riduce al minimo le modifiche superflue.

Automatizzare la qualità

Con i18n-validate, rilevi chiavi mancanti e segnaposto non validi prima del rilascio. Testi l'interfaccia con le pseudotraduzioni di i18n-pseudo prima che arrivino le traduzioni reali.

Correggere il fallback delle lingue con LocaleChain.NET

La gerarchia CultureInfo.Parent integrata in .NET usa soltanto il troncamento BCP 47: pt-BR passa a pt e poi a InvariantCulture, ignorando pt-PT. LocaleChain.NET offre catene di fallback configurabili per singola lingua nell'intero ecosistema .NET.

Senza LocaleChain.NET, un utente pt-BR vede l'inglese quando manca una stringa portoghese, anche se è disponibile una traduzione pt-PT completa. Lo stesso problema riguarda es-MX (ignora es-419), zh-Hant (ignora zh-Hans) e decine di altre varianti regionali.
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<>));

Problemi comuni

Cultura non impostata nella richiesta

Le traduzioni mostrano sempre la lingua predefinita. Controlli che UseRequestLocalization() venga chiamato nella pipeline middleware e che i provider della cultura siano configurati. Verifichi che il browser invii intestazioni Accept-Language. Esegua un test con ?culture=de nella stringa di query per confermare il funzionamento del middleware.

File RESX non trovato

IStringLocalizer restituisce il nome della chiave anziché il valore tradotto. La causa più comune è un nome di file RESX che non corrisponde al percorso completo dello spazio dei nomi della classe rispetto a ResourcesPath. Abiliti la registrazione di debug per Microsoft.Extensions.Localization per vedere i percorsi cercati dal framework.

Ordine dei middleware errato

UseRequestLocalization() deve apparire prima di UseEndpoints() e MapControllers(). Se viene posizionato dopo, la cultura della richiesta non è impostata durante l'esecuzione dei controller. Nell'hosting minimo di .NET 6 e versioni successive, lo chiami prima di app.MapControllers().

Il thread in background usa la cultura errata

CultureInfo.CurrentCulture e CurrentUICulture sono specifici per thread. Le attività in background (Task.Run, servizi ospitati) ereditano la cultura del pool di thread, non quella della richiesta. Acquisisca e imposti esplicitamente la cultura quando invia il lavoro in background.

Struttura del progetto consigliata

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

Provi subito i18n Agent

Trascinare qui il file di traduzione

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

oppure fare clic per selezionarlo

Lingue di destinazione

Nessuna registrazione richiestaPreventivo immediato

Fallback della lingua con I18nAgent.LocaleChain

Quando manca una chiave di traduzione in una lingua regionale come pt-BR, .NET passa direttamente alla cultura invariante anziché controllare prima la lingua principale 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"},
});

Consultare la Guida al fallback delle lingue per l'elenco completo dei framework supportati e delle 75 catene integrate. Learn more →

Domande frequenti