In this article, we are going to learn how to implement localization in Blazor WebAssembly applications. We are going to start with a simple introduction to Globalization and Localization. Then, we are going to show you how to implement localization in Blazor WebAssembly applications and also how to enable users to choose the culture they want.

To download the source code for this article, you can visit the Localization in Blazor WebAssembly repository

We are going to divide this article into the following sections:

So, let’s start.

About Globalization and Localization

So, what is Globalization?

Globalization is the process of creating an application that supports multiple cultures across the globe. So this means that our app can display numbers or dates differently, collect translations from the resource files rather than using hard-coded ones and also, allowing the application’s UI to work with different languages.

What about Localization?

Localization is the process of adapting our globalized application which is already prepared to support localizability. This means that we want to customize our application to support a specific language and region.

You can also find the term Internationalization when reading about these two previously mentioned terms. In simple words, Internationalization just describes both Globalization and Localization.

To work with Localization in Blazor WebAssembly applications, we are going to use the CultureInfo class that helps us in the process. Of course, this class is used for .NET development overall. Two properties CultureInfo.DefaultThreadCurrentCulture and CultureInfo.DefaultThreadCurrentUICulture are going to help us to configure the default culture and the default UI culture. In this article, we are going to see how.

Implementing Localization in Blazor WebAssembly

After we are familiar with the required terms, we can start by building our localized application.

The first thing we are going to do is to create a new Blazor WebAssembly application:

Creationg of Blazor WebAssembly Application

As soon as we start the app, we are going to see the Home page with a couple of messages:

Starting screen for implementing Localization in Blazor WebAssembly app

We are going to localize the two selected sentences. This will be enough to show how you can do it for the entire application.

So, let’s get to work.

The first thing we have to do is to install the Microsoft.Extensions.Localization package:

Install-Package Microsoft.Extensions.Localization -Version 5.0.0

After the installation, we are going to register services for application localization. To do that, we are going to modify the Program class:

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("#app");

    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
    builder.Services.AddLocalization();

    await builder.Build().RunAsync();
}

At this point, we need a place to store our localization resources. You can do that in different ways, but in this article, we are going to use resource (.resx) files.

Working with Resource Files

So, let’s create a new ResourceFiles folder under the Shared folder, and let’s create two resource files inside it:

Resource files for Englisn and German

The Resource.resx file will store the English translations and the Resource.de.resx will hold the German translations. The resource files store the translations as key-value pairs, so let’s modify them in such a manner.

First, let’s modify the Resource.resx file:

English resource file to support Localization in Blazor WebAssembly app.

And then the other one:

German resource file to support Localization in Blazor WebAssembly app.

So, we have key-value pairs in both files, and both files have a public access modifier.

After these modifications, we can add the required using directives in the _Imports.razor file:

@using BlazorWasmLocalization.Shared.ResourceFiles
@using Microsoft.Extensions.Localization

And then, we can modify the Index.razor file:

@page "/"
@inject IStringLocalizer<Resource> localizer

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

@localizer["welcome"]

<SurveyPrompt Title="How is Blazor working for you?" />

Here we inject the IStringLocalizer service to provide localized strings. As you can see, we provide the Resource class for the type because this is the class that contains localized strings. Then, instead of the hard-coded strings, we use the localizer variable and provide a key, which is a key from the resource file. Of course, this will return a value from the same resource file.

Now if we start the application, we are going to see the same result, but this time our strings are localized. Of course, if we open the browser settings and set the second language as the first one:

Language preference

And reload the page, we are going to see a different result:

Localization in BlazorWebAssembly Home page

Excellent.

But if we want to enable our users to choose the culture from the application, we have to implement a different logic.

Creating UI for Choosing the Culture

Let’s start by creating a new CultureSelector component (both razor and razor.cs files) under the Shared folder.

We are going to modify the razor file first:

<strong>Culture:</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>

We create a select element and bind it to the Culture property and also for each supported culture, we create a select option. Of course, we are missing the Culture property and the cultures collection, so let’s add it to the razor.cs file:

public partial class CultureSelector
{
    [Inject]
    public NavigationManager NavManager { get; set; }

    [Inject]
    public IJSRuntime JSRuntime { get; set; }

    CultureInfo[] cultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("de-DE")
    };

    CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var js = (IJSInProcessRuntime)JSRuntime;
                js.InvokeVoid("blazorCulture.set", value.Name);

                NavManager.NavigateTo(NavManager.Uri, forceLoad: true);
            }
        }
    }
}

Before we start explaining this code, we need to add thee using statements:

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Globalization;

Now we can explain the code.

First, we inject two services, the NavigationManager and IJSRuntime.

To learn more about NavigationManager and routing, you can read the Routing in Blazor WebAssembly article. Also, to learn more about using JSInterop to work with JavaScript from C#, you can read our JSInterop part in the Blazor WebAssembly series.

Then, we create a cultures array with two supported cultures – en-US and de-DE. Also, we create a missing Culture property of the CultureInfo type. This property returns the current culture used in the application. Also, when we select a new culture in our drop-down list, this property does some logic in the set part. We first check if the current culture is different than a selected one. If it is, we use JSInterop to invoke the Javascript’s blazorCulture object with a set accessor that sets the culture name in the local storage. Finally, we use the NavigationManager to navigate the user to the requested URI and use the forceLoad parameter to reload the page. We do that because once we reload the app, the logic from the Program.cs class will trigger and will set a new culture as a default one.

Of course, we don’t have that logic yet, so let’s add it.

Setting the Default Culture for the Application

First, let’s create a new Extensions folder and inside it a new WebAssemblyHostExtension class:

public static class WebAssemblyHostExtension
{
    public async static Task SetDefaultCulture(this WebAssemblyHost host)
    {
        var jsInterop = host.Services.GetRequiredService<IJSRuntime>();
        var result = await jsInterop.InvokeAsync<string>("blazorCulture.get");

        CultureInfo culture;

        if (result != null)
            culture = new CultureInfo(result);
        else
            culture = new CultureInfo("en-US");

        CultureInfo.DefaultThreadCurrentCulture = culture;
        CultureInfo.DefaultThreadCurrentUICulture = culture;
    }
}

We create this extension class and method to remove the extra logic from the Program.cs class. In this extension method, we extend the WebAssemblyHost type and use JSInterop to call the get accessor from the blazorCulture Javascript object. This get accessor will return the culture name from the locale storage. If the name is returned, we create a new CultureInfo object with that name, otherwise, we create a new CultureInfo object with the en-US as a parameter. Finally, we set the DefaultThreadCurrentCulture and the DefaultThreadCurrentUICulture properties to the created culture.

Of course, we need a few using directives for this to work:

using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using System.Globalization;
using System.Threading.Tasks;

Now, we can modify the Program.cs class:

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("#app");

    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
    builder.Services.AddLocalization();

    var host = builder.Build();

    await host.SetDefaultCulture();

    await host.RunAsync();
}

Good.

The only thing left is to modify the index.html file, to add the Javascript object that we call to get and set the culture name in the local storage:

<body>
    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
    <script>
        window.blazorCulture = {
            get: () => localStorage['BlazorCulture'],
            set: (value) => localStorage['BlazorCulture'] = value
        };
    </script>
</body>

Just to mention, if you want, you can create a new Javascript file and move this logic in that file. For example sake, it could stay here.

Lastly, we have to include our new component instead of the About link in the MainLayout.razor file:

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

    <div class="content px-4">
        @Body
    </div>
</div>

Adding Missing PropertyGroup in the Project file

If we start our application now, we are going to get an error:

BlazorWebAssemblyLoadAllGlobalizationData error

The error is pretty self-explanatory, so let’s do exactly that:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

    <PropertyGroup>
        <TargetFramework>net5.0</TargetFramework>
        <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
    </PropertyGroup>

    ...

</Project>

Now if we start our app, we are going to see our new component with en (US) culture selected:

Localization in BlazorWebAssembly component works

And once we change the culture:

Localization in BlazorWebAssembly component works german

We can see our localization works like a charm.

Conclusion

In this article, we have learned:

  • More about the Globalization and Localization
  • How to implement Localization in Blazor WebAssembly applications
  • How to enable users to change the culture from the app

Until the next article.

Best regards.