Localizzare un applicazione ASP.NET Core
Localizzare un applicazione .NET Core (versione 2.1) è diverso rispetto ad una applicazione ASP.NET standard.
Per prima cosa va aggiunta la configurazione in Startup.cs:
nella configurazione vengono definite:
per uniformità la creo nella cartella Resources.
ed uno o più file localizzati SharedResource.en.resx:
SharedResource.en-US.resx:
Adesso abbiamo l'infrastruttura per gestire le risorse nel codice:
oppure nella view:
Di default il midleware, RequestLocalizationMiddleware, permette di impostare la culture corrente con 3 metodi differenti:
Quindi, se ad esempio, se imposto la culture tramite query string, http://localhost/home/index?culture=en-US , non verranno presi in considerazione l'eventuale cookie (.AspNetCore.Culture) ne gli header inviati dal browser.
Quindi a meno che non volgiamo usare le impostazioni del browser, conviene eliminare i provider non necessari per avere sempre il fallback sulla culture di default:
Se uso i cookie devo avere un metodo che mi permetta di impostare la nuova culture, ad esempio SetLanguage nel controller Home:
e posso aggiungere nella _Layout.cshtml la scelta della culture:
Per maggiori informazioni vedi Globalizzazione e localizzazione in ASP.NET Core
Per prima cosa va aggiunta la configurazione in Startup.cs:
C#
...
using System.Globalization;
...
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Options;
namespace SgartItLocalization
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//1 - aggiungo i servizi di localizzazione (inject IStringLocalizer)
services.AddLocalization(options =>
{
//definisco la cartella che conterrà i file resx;
options.ResourcesPath = "Resources";
});
//2 - definisco le culture supportate e quella di default (RequestLocalizationMiddleware)
services.Configure<RequestLocalizationOptions>(options =>
{
var defaultCulture = "it-IT";
// culture supportate dall'applicazione
var supportedCultures = new List<CultureInfo>
{
new CultureInfo(defaultCulture),
new CultureInfo("en-US"),
new CultureInfo("en-GB"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr"),
new CultureInfo("es-MX"),
new CultureInfo("de"),
};
options.DefaultRequestCulture = new RequestCulture(defaultCulture);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
//3 - add localization service per le view (inject IViewLocalizer)
.AddViewLocalization(
LanguageViewLocationExpanderFormat.Suffix,
opts => { opts.ResourcesPath = "Resources"; })
.AddDataAnnotationsLocalization();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
//4 - add localization, prima di app.UseMvc
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
- le dipendence injection (DI) per i servizi di localizzazione (IStringLocalizer)
- la posizione della cartella che conterrà i file .rexs (Resources)
- la culture di default (DefaultRequestCulture)
- le culture supportate dall'applicazione
- il supporto per la localizzazione delle view (AddViewLocalization)
- il supporto per la localizzazione delle data annotation (AddDataAnnotationsLocalization)
C#
// classe fittizia per le risorse condivise
// non specificare nessun mame space
public class SharedResource
{
// vuota
}
Attenzione non specificare nessun namespace per questa classe.
La risorsa viene cercata concatenando il nome dell'assembly, la cartella Rsources (specificata in Startup) e il nome classe passato in IStringLocalizer. Nell'esempio MvcMovie.Resources.SharedResource.
il passo successivo è creare un file di risorse comune, con lo stesso nome della classe precedente, SharedResource.resx:XML
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="Prova" xml:space="preserve">
<value>Prova (shared)</value>
</data>
</root>
XML
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="Prova" xml:space="preserve">
<value>Prova (en)</value>
</data>
</root>
XML
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="Prova" xml:space="preserve">
<value>Prova (en-US)</value>
</data>
</root>
Se non si creano i file .resx come default viene ritornato come valore la chiave passata, quindi si può lavorare, inizialmente, senza file resx.
Adesso abbiamo l'infrastruttura per gestire le risorse nel codice:
C#
...
using Microsoft.AspNetCore.Localization;
...
using Microsoft.Extensions.Localization;
namespace SgartItLocalization.Controllers
{
public class HomeController : Controller
{
//variabile interna per accedere alla localizzazione (SharedResources)
private readonly IStringLocalizer<SharedResource> _localizer;
//viene fatto l'inject di IStringLocalizer nel costruttore
public HomeController(IStringLocalizer<SharedResource> localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
// accedo alla risorsa con chiave "Prova"
ViewData["Prova"] = _localizer["Prova"];
return View();
}
}
}
HTML
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization
@*
@inject IViewLocalizer Localizer
IViewLocalizer si aspetta di trovare una risorsa in: SgartItLocalization.Resources.Views.Home.Index
*@
@inject IStringLocalizer<SharedResource> Localizer
@{
ViewData["ProvaView"] = Localizer["Prova"];
}
<h1>Code: @ViewData["Prova"]</h1>
<h2>View: @ViewData["ProvaView"]</h2>
<h3>Localizer: @Localizer["Prova"]</h3>
- tramite query string (Microsoft.AspNetCore.Localization.QueryStringRequestCultureProvider)
- tramite cookie (Microsoft.AspNetCore.Localization.CookieRequestCultureProvider)
- tramite le impostazioni di lingua del browser (Microsoft.AspNetCore.Localization.AcceptLanguageHeaderRequestCultureProvider)
Quindi, se ad esempio, se imposto la culture tramite query string, http://localhost/home/index?culture=en-US , non verranno presi in considerazione l'eventuale cookie (.AspNetCore.Culture) ne gli header inviati dal browser.
Attenzione: IViewLocalizer trova il file resx corrispondente componendo il percorso del file di risorse come: [nomeAssembly].Resources.Views.[nomeController].[nomeAction].resx (SgartItLocalization.Resources.Views.Home.Index).
Quindi deve esistere sotto la cartella Resources un file chiamato Views.Home.Index.resx o Views.Home.Index.[culture].resx.
In alternativa è possibile creare una gerarchia di cartelle /Resources/Views/Home/Index.resx.
A questo proposito va evidenziato che se la culture passata non è tra quelle configurate in Startup.cs, come default, verrà presa la lingua del browser (AcceptLanguageHeaderRequestCultureProvider) e non la default culture indicata in Startup.cs.Quindi deve esistere sotto la cartella Resources un file chiamato Views.Home.Index.resx o Views.Home.Index.[culture].resx.
In alternativa è possibile creare una gerarchia di cartelle /Resources/Views/Home/Index.resx.
Quindi a meno che non volgiamo usare le impostazioni del browser, conviene eliminare i provider non necessari per avere sempre il fallback sulla culture di default:
C#
//2 - definisco le culture supportate e quella di default (RequestLocalizationMiddleware)
services.Configure<RequestLocalizationOptions>(options =>
{
...
//salvo il provider che voglio tenere, quello con i cookie
var rcpCookie = options.RequestCultureProviders.FirstOrDefault(x => x is Microsoft.AspNetCore.Localization.CookieRequestCultureProvider);
//cancello tutti i provider definiti di default
options.RequestCultureProviders.Clear();
//aggiungo il provider salvato precedentemente
if (rcpCookie != null)
options.RequestCultureProviders.Add(rcpCookie);
});
C#
using Microsoft.AspNetCore.Http;
...
[HttpPost]
public IActionResult SetLanguage(string culture, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
//scadenza dopo 1 anno
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
if (string.IsNullOrWhiteSpace(returnUrl))
{
returnUrl = "/";
}
return LocalRedirect(returnUrl);
}
HTML
<form id=form-set-language method="post" action="/home/setlanguage">
<input type="hidden" id="form-set-language-culture" name="culture">
<input type="hidden" id="form-set-language-return-url" name="returnUlr" value="@Context.Request.Path">
<a onclick="setlanguage('en')">set en</a>
<a onclick="setlanguage('en-US')">set en-US</a>
<a onclick="setlanguage('en-GB')">set en-GB</a>
<a onclick="setlanguage('it-IT')">set it-IT</a>
<a onclick="setlanguage('fr')">set fr</a>
<a onclick="setlanguage('fr-FR')">set fr-FR</a>
<a onclick="setlanguage('es')">set es</a>
<a onclick="setlanguage('es-ES')">set es-ES</a>
<a onclick="setlanguage('es-MX')">set es-MX</a>
<a onclick="setlanguage('de')">set de</a>
<a onclick="setlanguage('de-DE')">set de-DE</a>
</form>
...
@RenderSection("Scripts", required: false)
<script>
function setlanguage(culture) {
document.getElementById("form-set-language-culture").value = culture;
document.getElementById("form-set-language").submit();
}
</script>
Attenzione, se dopo la prima compilazione vengono aggiunti dei file di risorsa e non sono visibili dall'applicazione, prova a cancellare i file sotto bin/debug o bin/release e ricompilare.
Attenzione 2: se non si riesce ad impostare la lingua tramite cookie e il cookie non viene inviato al browser, vedi Non si riesce ad impostare un cookie in .NET Core.
Per maggiori informazioni vedi Globalizzazione e localizzazione in ASP.NET Core