Articoli

Localizzare il nostro sito in Blazor: il front-end (Parte 1)

da

Quante volte ci siamo arrabbiati perché un determinato sito non gestisce l’italiano? Non facciamo lo stesso errore e traduciamo le UI dei nostri siti Blazor.

Per poterlo fare ci sono diversi modi, ma in Blazor Webassembly il più performante è quello consigliato da Microsoft ed è basato su IStringLocalizer e su file di risorse. Ma andiamo con ordine.

Globalizzazione e localizzazione

Cos’è la globalizzazione?

In inglese “Globalization” è il processo che permette alla nostra applicazione di essere “globale” ovvero di supportare varie culture del mondo.

Cos’è la localizzazione

In inglese “Localization” è il processo di traduzione della nostra applicazione secondo la lingua scelta dall’utente.

Sporchiamoci le mani

Ora che abbiamo capito cosa sono questi due termini, ci sporchiamo le mani e iniziamo ad applicare la globalizzazione alla nostra applicazione Blazor Webassembly.

Creiamo quindi un nuovo progetto Blazor Webassembly e per comodità scegliamo “ASP.NET Hosted” nelle opzioni ci creazione.

a questo punto clicchiamo con il tasto destro sul progetto Client e andiamo a caricare il nuget package Microsoft.Extensions.Localization

A questo punto dobbiamo registrare il servizio di localizzazione nel nostro Program.cs del progetto Client che sarà questo:

using BlazorLocalization.Client;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddLocalization();
await builder.Build().RunAsync();

La localizzazione nativa di Microsoft si basa su files di risorse che sono poi embedded in una DLL e caricata lato client sul browser.

I files di risorse

I files di risorse conterranno le nostre lingue e saranno strutturati come chiave-valore e suddivisi per lingua. Ovvero un file per ogni lingua. Creiamo una cartella nel progetto Client chiamata LocalizationLanguages all’interno della quale creeremo un primo file RESX chiamato ResourceLanguage.resx che conterrà la nostra lingua di default. Per noi sarà l’inglese.

Dobbiamo anche decidere cosa tradurre per poter valorizzare correttamente tutte le righe chiave-valore del file di risorse. Decido per il momento di tradurre solo “Hello World” che troviamo all’avvio dell’applicazione.

Creeremo quindi una chiave “HelloWorld” e un valore “Hello, World!”

Dopo la lingua di default creiamo ora un secondo file di risorse nella stessa directory chiamato ResourceLanguage.it.resx, che conterrà le traduzioni in italiano

Ultime due cose da fare sono:

  • Impostare l’access modifier di ogni file di risorsa su “Public” in modo che le risorse possano essere disponibili per tutta l’applicazione
  • Aggiungere nel file _Import.razor le seguenti righe
@using BlazorLocalization.Client.LocalizationLanguages
@using Microsoft.Extensions.Localization

Traduciamo le pagine

Bene, a questo punto non ci resta che tradurre la nostra pagina. Apriamo il file Index.razor e, sfruttando la dependency injection, aggiungiamo la seguente linea di codice subito sotto la diretiva @page per utilizzare il servizio di localizzazione

@inject IStringLocalizer<ResourceLanguage> localizer

tramite IStringLocalizer possiamo richiamare automaticamente i nostri files di risorse

<h1>@localizer["HelloWorld"]</h1>

Ottenendo il seguente risultato

Come potete notare, la scritta è localizzata in italiano perché di default viene presa la lingua del browser ospite (ovviamente se presente nelle risorse).

Splendido! Ma se l’utente volesse scegliere un’altra lingua?

Scegliamo la lingua

Per poter dare la possibilità all’utente di scegliere la lingua desiderata, creiamo uno user control che permetta proprio di fare questo.

Creiamo, nella directory Shared del progetto Client, il file LanguageSelector.razor, che sarà fatto così:

@inject NavigationManager NavManager
@using System.Globalization
@using Microsoft.JSInterop
@inject IStringLocalizer<ResourceLanguage> localizer
<strong>@localizer["Language"]</strong>
<select class="form-control" @bind="Culture" style="width:300px; margin-left:10px;">
    @foreach (var culture in cultures)
    {
        <option value="@culture">@culture.DisplayName</option>
    }
</select>
@code
{
    [Inject]
    public IJSRuntime JSRuntime { get; set; }
    CultureInfo[] cultures = new[]
            {
            new CultureInfo("en"),
            new CultureInfo("it")
        };
    CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var js = (IJSInProcessRuntime)JSRuntime;
                js.InvokeVoid("blazorLanguage.set", value.Name);
                NavManager.NavigateTo(NavManager.Uri, forceLoad: true);
            }
        }
    }
}

come si può notare, ci sono due passaggi principali per impostare la lingua. Vengono aggiunte le lingue disponibili come nuove culture e viene utilizzato uno script JavaScript per impostare la lingua (blazorLanguage.set) che sarà da aggiungere in Index.html nella cartella wwwroot.

    <script>
        window.blazorLanguage= {
            get: () => localStorage['BlazorLanguage'],
            set: (value) => localStorage['BlazorLanguage'] = value
        };
    </script>

A questo punto dobbiamo crearci un array da poter essere caricato in una dropdown box e facilitare l’utente a selezionare la lingua. Questa dropdown avrà al suo interno una sua logica per poter correttamente caricare la lingua nella sessione del browser e salvarla nel local storage. Dopo aver impostato la lingua del browser occorre ricaricare la pagina per poter richiamare tutti i record dei files RESX e valorizzare correttamente ogni campo da tradurre.

Impostare una cultura

Impostiamo una cultura di default tramite un extension method fatto in questo modo

    public static class CultureExtension
    {
        public async static Task SetDefaultCulture(this WebAssemblyHost host)
        {
            var jsInterop = host.Services.GetRequiredService<IJSRuntime>();
            var result = await jsInterop.InvokeAsync<string>("blazorLanguage.get");
            CultureInfo culture;
            if (result != null)
                culture = new CultureInfo(result);
            else
                culture = new CultureInfo("en");
            CultureInfo.DefaultThreadCurrentCulture = culture;
            CultureInfo.DefaultThreadCurrentUICulture = culture;
        }
    }

Questo extension method lo richiameremo da Program.cs dove avremo avuto l’accortezza di modificarlo in questo modo:

var host=builder.Build();
await host.SetDefaultCulture();
await host.RunAsync();

Cambiamo la lingua

Siamo in dirittura d’arrivo. Non ci resta che aprire il nostro file MainLayout.razor nella cartella Shared ed inserire il nostro componente di localizzazione.

    <div class="top-row px-4">
        <LanguageSelector />
    </div>

E avviamo tranquillamente, ma… BOOM!

Cos’è successo??? Se guardiamo l’errore, esso ci suggerisce esattamente la modifica da effettuare.

Apriamo il file di progetto e aggiungiamo la seguente riga come suggerito

<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>

In questo modo:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
	<PropertyGroup>
		<TargetFramework>net6.0</TargetFramework>
		<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
		<Nullable>enable</Nullable>
		<ImplicitUsings>enable</ImplicitUsings>
		<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
	</PropertyGroup>
...
</Project>

Una volta fatto questo possiamo avviare tranquillamente la nostra applicazione e… voilà!

Ecco la nostra pagina con la nostra selezione della lingua. Ovviamente anche la label Language è stata tradotta.

Troverete come al solito sul repository github a questo indirizzo.

Alla prossima!

Scritto da: