Software localization

How to Localize Error Messages for Blazor WebAssembly Forms

Want to localize form validation error messages in a Blazor WebAssembly app? Follow this guide to learn everything you need to know.
Software localization blog category featured image | Phrase

In this guide, we will learn how to localize form validation error messages in a Blazor WebAssembly app. We will create a sample form to accept employee data. We will serve our app in two languages: English and French. We will allow the user to select the locale of the app using a drop-down list.

🗒 Note » To get the source code for the demo app, make sure you stop by at GitHub to pick up the complete source code.

🤿 Go deeper » Want to learn more about Blazor WebAssembly and how it works? Check out our complete guide on internationalization with Blazor WebAssembly.

Prerequisites

There are two ways of working on a Blazor app:

  • Using .NET Core CLI and Visual Studio Code (preferred for Linux)
  • Using Visual Studio 2019 (preferred for Windows and macOS)

In this tutorial, we will be using Visual Studio 2019. If you need to install it on your machine, make sure you have selected the ASP.NET and web development workload.

Creating a Blazor WebAssembly app

Open Visual Studio 2019, click on “Create a new project”. Search for “Blazor” from the template list. Select “Blazor WebAssembly App” from the search results. Click on the “Next” button. Refer to the image below.

Creating a new Blazor | Phrase

In the next window, set “BlazorErrorMessageL10n” as the project name and click on the “Next” button. You’ll be then asked to select the Target framework. Set the framework version to .NET 5.0 from the dropdown menu. Click on the “Create” button to create a new Blazor WebAssembly application (see below).

Adding .NET 5.0 Framework | Phrase

Installing the localization package

We will now install the Localization package, providing application localization services and default implementation based on ResourceManager to load localized assembly resources.

We will install the current stable version of the Microsoft.Extensions.Localization package. Navigate to the NuGet library and copy the command shown in the “package manager” tab. While writing this article, the current stable version is 5.0.5.

To install the package, navigate to Tools > NuGet Package Manager > Package Manager Console. What you will see next is the Package Manager Console. Run the command as shown below.

Install-Package Microsoft.Extensions.Localization -Version 5.0.5

Creating the language selector component

We will add a new component to our Blazor project, allowing us to select the app language during runtime.

To add a new component, right-click on the BlazorErrorMessageL10n/Pages folder and then select Add > Razor Component. Set the name of the file as LanguageSelector.razor. Click on the Add button (see below).

Adding LanguageSelector.razor | Phrase

We will also add a base class for the LanguageSelector component. Right-click on the BlazorErrorMessageL10n/Pages folder, then select Add > Class. Set the file name as LanguageSelector.razor.cs. Click on the Add button (see below).

LanguageSelector.razor selecting class | Phrase

Add the following code to the LanguageSelector.razor.cs file:

using Microsoft.AspNetCore.Components;

using Microsoft.JSInterop;

using System.Globalization;

namespace BlazorErrorMessageL10n.Pages

{

    public class LanguageSelectorBase : ComponentBase

    {

        [Inject]

        IJSRuntime JSRuntime { get; set; }

        [Inject]

        public NavigationManager NavigationManager { get; set; }

        protected CultureInfo[] supportedLanguages = new[]

        {

        new CultureInfo("en-US"),

        new CultureInfo("fr-FR"),

        };

        protected CultureInfo Culture

        {

            get => CultureInfo.CurrentCulture;

            set

            {

                if (CultureInfo.CurrentCulture != value)

                {

                    var js = (IJSInProcessRuntime)JSRuntime;

                    js.InvokeVoid("appCulture.set", value.Name);

                    NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);

                }

            }

        }

    }

}

Insert the following code in the LanguageSelector.razor file:

@inherits LanguageSelectorBase

<div class="col-md-3">

    <select class="form-control" @bind="Culture">

        @foreach (var language in supportedLanguages)

        {

            <option value="@language">@language.DisplayName</option>

        }

    </select>

</div>

Our app will support two languages—English and French. We have created a drop-down list to display the supported language for our app. Upon selecting the value from the drop-down, we will store the selected language in the local storage with the help of JS interop and then reload the app.

Open the BlazorErrorMessageL10n/wwwroot/index.html file, and add the following lines of code to the end of the <body> tag.

<script>

    window.appCulture = {

        get: () => window.localStorage['AppLanguage'],

        set: (value) => window.localStorage['AppLanguage'] = value

    };

</script>

The appCulture property is used to store the user's locale selection in their browser. Later in this guide, we will configure the application to fetch the locale information from the local storage to set the app culture.

We will add the language selector component to the MainLayout component. This will make sure that it is accessible on all pages.

To add a Blazor component to another, we need to use the file name of the component as the tag name. Therefore, we will update the BlazorWebassemblyI18n/Shared/MainLayout.razor file as follows:

<div class="main">

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

        <LanguageSelector />

        <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>

    </div>

    @*rest of code in this block*@

</div>

Updating the application project file

To support the feature of changing the application language dynamically, we need to configure BlazorWebAssemblyLoadAllGlobalizationData in the project file. Open the BlazorErrorMessageL10n\BlazorErrorMessageL10n.csproj file, and add the following code to it:

<PropertyGroup>

    <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>

</PropertyGroup>

Creating the language resource file

Right-click on the project and select Add > New Folder and name the folder “Resources”. Again, right-click on the Resources folder and select Add > New Item. In the Add New Item window, select the Resources File template and name the file App.resx. Click on the Add button to create the file (see below):

Adding resource file app.resx | Phrase

A resource file is used to manage the translated content. The resource file will contain a set of key-value pairs for translation. The key will be the text to be translated, and the value will be the text after translation. We will add Name and Value fields for all the text we want to translate (refer to the image below).

App.resx Access Modifier | Phrase

The App.resx is the default resource file for our app. It will hold the translations for the default language (English). Set the access modifier for the file to Public as shown in the image. Doing so will generate a class file with the name App.Designer.cs, having an App class inside it. The App class will contain all the translation keys as public properties. This will allow us to create strongly typed resource references.

Similarly, we will add another resource file, App.fr.resx, for the French language. Set the access modifier for this file to Public as well.

You can refer to GitHub to check all the resource files.

Creating the model

Right-click on the project and select Add > New Folder and name the folder “Models”. Again, right-click on the Models folder and select Add > Class. Set the file name as Employee.cs. Click on the Add button, and add the following code to the Employee.cs file:

using System;

using System.ComponentModel.DataAnnotations;

namespace BlazorErrorMessageL10n.Models

{

    public class Employee

    {

        [Required(ErrorMessageResourceName = nameof(Resources.App.Name_Validation), ErrorMessageResourceType = typeof(Resources.App))]

        public string Name { get; set; }

        [Required(ErrorMessageResourceName = nameof(Resources.App.City_Validation), ErrorMessageResourceType = typeof(Resources.App))]

        public string City { get; set; }

        [Required(ErrorMessageResourceName = nameof(Resources.App.Gender_Validation), ErrorMessageResourceType = typeof(Resources.App))]

        public string Gender { get; set; }

        [Required]

        [Range(1, int.MaxValue, ErrorMessageResourceName = nameof(Resources.App.Salary_Validation), ErrorMessageResourceType = typeof(Resources.App))]

        public int Salary { get; set; }

    }

}

We have defined the Employee class with four properties. The validation rules for the properties are defined with the help of data annotations attributes. The ErrorMessageResourceType property will define the resource type to be used for error-message lookup if the validation fails. In that case, the ErrorMessageResourceName property will define an error message resource name to be used to look up the ErrorMessageResourceType property values.

When the user submits the form with invalid values, the form validations will kick in. The validation error messages that are now localized will be fetched from the resources file.

Creating the EmployeeData component

To create the EmployeeData component, we will add the EmployeeData.razor file to the BlazorErrorMessageL10n/Pages folder. Also, add a base class file EmployeeData.razor.cs to the Pages folder.

Put the following code inside the EmployeeData.razor.cs file.

using BlazorErrorMessageL10n.Models;

using Microsoft.AspNetCore.Components;

using Microsoft.Extensions.Localization;

using Microsoft.JSInterop;

namespace BlazorErrorMessageL10n.Pages

{

    public class EmployeeDataBase : ComponentBase

    {

        [Inject]

        IJSRuntime JSRuntime { get; set; }

        [Inject]

        IStringLocalizer<App> Localize { get; set; }

        protected string title;

        readonly string companyName = "Phrase";

        protected Employee employee = new();

        protected override void OnInitialized()

        {

            SetTitle();

        }

        void SetTitle()

        {

            string localizedTitle = Localize["Title"];

            title = string.Format(localizedTitle, companyName);

        }

        protected async void SaveEmployeeData()

        {

            await JSRuntime.InvokeVoidAsync("console.log", employee);

        }

    }

}

The OnInitialized is a Blazor lifecycle method that is invoked when the component is initialized. With the setTitle method, the title of the page is set by fetching the localized content from the resources file. In the default resources file, we set the value for the Title key as “Tutorial by {0}”. Here, we will replace the “{0}” dynamically with the company name “Phrase” using string.Format. When the app locale is set to English, the title will be displayed as “Tutorial by Phrase”.

The SaveEmployeeData method will display the form data in the browser’s console.

Put the following code inside the EmployeeData.razor file:

@page "/employee"

@inherits EmployeeDataBase

<h1>@title</h1>

<br />

<EditForm Model="@employee" OnValidSubmit="SaveEmployeeData">

    <DataAnnotationsValidator />

    <div class="form-group row">

        <label class="control-label col-md-12">Name</label>

        <div class="col-md-4">

            <InputText class="form-control" @bind-Value="employee.Name" />

        </div>

        <ValidationMessage For="@(() => employee.Name)" />

    </div>

    <div class="form-group row">

        <label class="control-label col-md-12">Gender</label>

        <div class="col-md-4">

            <InputSelect class="form-control" @bind-Value="employee.Gender">

                <option value="">-- Select Gender --</option>

                <option value="Male">Male</option>

                <option value="Female">Female</option>

            </InputSelect>

        </div>

        <ValidationMessage For="@(() => employee.Gender)" />

    </div>

    <div class="form-group row">

        <label class="control-label col-md-12">City</label>

        <div class="col-md-4">

            <InputText class="form-control" @bind-Value="employee.City" />

        </div>

        <ValidationMessage For="@(() => employee.City)" />

    </div>

    <div class="form-group row">

        <label class="control-label col-md-12">Salary</label>

        <div class="col-md-4">

            <InputNumber class="form-control" @bind-Value="employee.Salary" />

        </div>

        <ValidationMessage For="@(() => employee.Salary)" />

    </div>

    <div class="form-group">

        <button type="submit" class="btn btn-primary">Save</button>

    </div>

</EditForm>

We have defined a form that will allow the user to add a new employee record. The DataAnnotationsValidator is a Blazor component that allows us to validate the form using the data annotations attributes defined for the model class that is bound to the form. The form validation error message will be displayed beside each form field with the help of the ValidationMessage component.

Clicking on the Save button will submit the form, which will then invoke the SaveEmployeeData method.

Configuring the app to use the language resource file

We have created a language selector component to select the app language during runtime. We have also created a set of language resource files to be used for each language. Now we need to configure the app to dynamically set the culture of the app. Add the following lines inside the Main method of the BlazorErrorMessageL10n/Program.cs file:

builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");

var jsInterop = builder.Build().Services.GetRequiredService<IJSRuntime>();

var appLanguage = await jsInterop.InvokeAsync<string>("appCulture.get");

if (appLanguage != null)

{

    CultureInfo cultureInfo = new(appLanguage);

    CultureInfo.DefaultThreadCurrentCulture = cultureInfo;

    CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;

}

We will fetch the current app language from the local storage using JS interop and set the app culture. The culture will define the locale for the app. This piece of code will help us to set the locale as the app loads.

The app will load only the resource file required for a particular locale. When we change the language from the dropdown, the app will reload with the resource file for another locale.

Adding a link to the navigation menu

Before running the app, we will add the link of our EmployeeData component in the navigation menu. Open BlazorErrorMessageL10n/Shared/NavMenu.razor file, and add the following code to it.

<li class="nav-item px-3">

    <NavLink class="nav-link" href="employee">

            <span class="oi oi-list-rich" aria-hidden="true"></span> Employee data

    </NavLink>

</li>

Execution demo

Launch the application. In the navigation menu on the left, click Employee Data, and you will see the following output.

Finished demo app | Phrase

Extending the app by adding a new language

We have designed our app in such a manner that it is easy to add support for a new language with minimal code changes. For example, if you want to add support for German, simplay follow the following steps:

  • Create a new language resource file with the name App.de.resx. Add all the required translations in the German language. Set the access modifier for App.de.resx to public.
  • Add the new language inside the supportedLanguages array in the LanguageSelectorBase class.

Wrapping things up

In this guide, we learned how to localize form validation error messages in a Blazor WebAssembly app. We created a sample form to accept employee data for a company and implemented form validation with the help of data annotations. The app is served in two languages—English and French.

Moving forward, if you add further languages to your app, you will need a reliable localization solution to handle the translation of your app content. Why not give Phrase a try? Sign up for a 14-day trial today, and see how the leanest, fastest, and most reliable localization platform on the market can make your life easier.