Software localization
Blazor WebAssembly I18n from Start to Finish
This guide will walk you through the Blazor WebAssembly i18n process from start to finish. We will do so by creating a sample form to add the data of an employee joining a company. The employee data saved via the form will eventually be displayed in a table. We will serve our app in three languages: English, French, and Arabic. 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.
What Is Blazor?
An open-source .NET web framework, Blazor makes it possible to create client-side applications using C# and HTML. It enables us to create rich and interactive UIs using C# instead of JavaScript. It further allows us to render the UI as HTML and CSS with extensive browser support, including mobile browsers.
Blazor provides a full-stack .NET development experience by allowing us to use .NET throughout our application. We can create both the server and client-side of the application using the same language, such as C#. It also allows us to share the common model class across the client and server.
Blazor has a component-based architecture – a component in Blazor can be defined as a UI element, such as a form, nav menu, data grid, etc.
What Is WebAssembly?
WebAssembly (abbreviated as Wasm) is a low-level assembly-like language that has a compact-binary format and can run on all modern web browsers. It is suitable for compilation on the web because it is portable and has an efficient size and load time. It cannot be read or written by humans, as it is in a low-level binary format. However, we can compile the code from other high-level languages, such as C#, in WebAssembly to facilitate their execution on the browser. WebAssembly is a subset of JavaScript and is designed to run alongside JavaScript. It enables us to run code written in high-level languages on the browser at the native speed. WebAssembly is an open web standard and is supported on all major web browsers.
What Is Mono runtime?
Mono is an open-source .NET runtime based on ECMA standards for C# and the Common Language Runtime (CLR) that allows us to create cross-platform apps using .NET.
Blazor’s Core Features
Let us have a look at the features that make Blazor an amazing framework for web development.
- It allows us to use the modern and feature-rich language of C#, which makes development easier,
- We can use existing .NET APIs and tools to create rich web applications,
- We do not need to create separate model classes for clients and servers. Blazor allows us to reuse the same model class by sharing it with both the client and the server,
- If you are already on a .NET platform, then the learning curve for Blazor is almost flat, as the only requirement to get started with it is an understanding of the C# language. A basic understanding of HTML and CSS is required,
- Blazor is supported on both Visual Studio and Visual Studio Code. This provides a great .NET development experience across multiple platforms, including Linux, Windows, and macOS,
- It is an open-source framework with great community support.
The Blazor Hosting Models
To display and update the UI changes in the browser, Blazor uses different renderers. These renderers are more commonly referred to as hosting models. Currently, the Blazor framework supports two hosting models, as explained below:
- Blazor WebAssembly
- Blazor Server
Blazor WebAssembly
This is the primary hosting model of Blazor, which allows running the client-side in the browser with the help of WebAssembly. When the Blazor WebAssembly app is built and executed, the app, its dependencies, and the Mono .NET runtime are downloaded to the browser. The app is executed directly on the browser UI thread. UI updates and event handling occur within the same process.
Blazor Server
The Blazor Server model allows the Blazor application to run on the server on top of the full .NET Core runtime. When the app is launched, a small JavaScript file is downloaded, which establishes a real-time, two-way SignalR connection with the server. Any user interaction with the app is then transmitted back to the server over the SignalR connection for the server to process. After the server is done, the UI updates are transmitted back to the client and applied to the DOM.
What Is JavaScript Interop?
The Blazor framework mainly uses C# or Razor code to create its components, but there are still a few scenarios where we need to access JavaScript:
- When accessing browser DOM elements: Blazor uses WebAssembly and Mono runtime for its execution, neither of which have direct access to a browser's DOM; since Blazor is a client-side framework, it needs to access and manipulate the DOM to create a user interface.
- When accessing JavaScript APIs: Sometimes, developers want to refer to JavaScript APIs to add new functionality or to enhance an existing functionality in an application
So, how can Blazor access JavaScript code? The ability to access a JavaScript method using a high-level language such as C#, and vice versa, is achieved through JavaScript Interop. JavaScript interop is a feature of WebAssembly, and therefore Blazor can implement it.
To explore Blazor in-depth, please refer to the official Blazor docs.
Prerequisites
We can use the following two ways to work on a Blazor app.
- Using .NET Core CLI and Visual Studio Code – preferred for Linux, or
- Using Visual Studio 2019 – preferred for Windows and macOS
In this tutorial, we will be using Visual Studio 2019. Please install the following software:
- Download the latest version of Visual Studio 2019 from the Visual Studio download page. While installing, make sure you have selected the ASP.NET and web development workload.
- Download the .NET 5.0 SDK from the .NET 5.0 download page.
Creating the Blazor WebAssembly app
Open Visual Studio 2019, click on “Create a new project”. Select “Blazor App” and click on the “Next” button. Refer to the image shown below.
On the next window, put BlazorWebassemblyI18n
as the project name and click on the “Create” button. You'll be then asked to select the type of Blazor app, as well as the .NET version. Select “Blazor WebAssembly App”. Set the .NET version to .NET 5.0 from the dropdown on the top. Click on the “Create” button to create a new Blazor WebAssembly application (refer to the image below).
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.0.
To install the package, navigate to Tools > NuGet Package Manager > Package Manager Console. It will open the Package Manager Console. Run the command as shown below.
Install-Package Microsoft.Extensions.Localization -Version 5.0.0
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 BlazorWebassemblyI18n/Pages
folder and then select Add > Razor Component. Put the name of the file as LanguageSelector.razor
. Click on the Add button. Refer to the image shown below.
Put the following code inside the LanguageSelector.razor
file.
@using System.Globalization @inject IJSRuntime JSRuntime @inject NavigationManager Nav <div class="col-md-3"> <select class="form-control" @bind="Culture"> @foreach (var language in supportedLanguages) { <option value="@language">@language.DisplayName</option> } </select> </div> @code { CultureInfo[] supportedLanguages = new[] { new CultureInfo("en-US"), new CultureInfo("fr-FR"), new CultureInfo("ar-AE"), }; CultureInfo Culture { get => CultureInfo.CurrentCulture; set { if (CultureInfo.CurrentCulture != value) { var js = (IJSInProcessRuntime)JSRuntime; js.InvokeVoid("appCulture.set", value.Name); Nav.NavigateTo(Nav.Uri, forceLoad: true); } } } }
We have created a drop-down list to display the supported language for our app. We are supporting three languages – English, French, and Arabic. 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 reloads the app.
Open the BlazorWebassemblyI18n/wwwroot/index.html
file and add the following lines of code at the end of the <body>
tag.
<script> window.appCulture = { get: () => window.localStorage['AppLanguage'], set: (value) => window.localStorage['AppLanguage'] = value }; </script>
This method is used to get and set the app culture from the local storage.
We will add the language selector component to the MainLayout component. This will make sure that it is accessible on all the 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 add the following line to the BlazorWebassemblyI18n/Shared/ MainLayout.razor
page. Add this line inside the div element having the class as top-row.
<LanguageSelector />
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 BlazorWebassemblyI18n\BlazorWebassemblyI18n.csproj
file and add the following code to it:
<PropertyGroup> <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData> </PropertyGroup>
Creating the EmployeeData component
To create the EmployeeData
component, we will add the EmployeeData.razor
file to the BlazorWebassemblyI18n/Pages
folder. Add the following lines at the top of the file.
@page "/employee" @using Newtonsoft.Json; @inject IJSRuntime JSRuntime @inject Microsoft.Extensions.Localization.IStringLocalizer<App> Localize
We have defined the route for this component as “/employee”. We have also injected the IStringLocalizer
class and set an alias name to “Localize”. We will use the alias name to localize the strings in this component.
Add the following piece of code in the @code
section of the file.
Employee employee = new Employee(); List<Employee> lstEmployees = new List<Employee>(); string title; string companyName = "Phrase"; string[] TableHeader = { "Name", "Gender", "City", "Salary", "Joining Date" }; protected override async Task OnInitializedAsync() { setTitle(); var empGetJS = (IJSInProcessRuntime)JSRuntime; var empList = await empGetJS.InvokeAsync<string>("employeeData.get"); FetchEmployeeFromLocalStorage(empList); } void SaveEmployeeToLocalStorage() { employee.JoiningDate = DateTime.Now; lstEmployees.Add(employee); var empSetJS = (IJSInProcessRuntime)JSRuntime; empSetJS.InvokeVoid("employeeData.set", JsonConvert.SerializeObject(lstEmployees)); employee = new Employee(); } void FetchEmployeeFromLocalStorage(string empList) { if (empList != null) { lstEmployees = JsonConvert.DeserializeObject<List<Employee>>(empList); } } void setTitle() { string localizedTitle = Localize["Title"]; title = string.Format(localizedTitle, companyName); } class Employee { public string Name { get; set; } public string Gender { get; set; } public string City { get; set; } public int Salary { get; set; } public DateTime JoiningDate { get; set; } }
The SaveEmployeeToLocalStorage
method will add the newly added employee to the lstEmployees
variable. We will then serialize lstEmployees
to string and save it to the local storage with the help of JS Interop. This will ensure that the employee data does not get lost when the app loads.
The OnInitializedAsync
is a Blazor lifecycle method that is invoked when the component is initialized. If the data exists in the local storage, we will fetch the list of employees and set the lstEmployees
variable.
We will use interpolation to display the title of the page. The title will contain the company name i.e. Phrase. The method setTitle
will fetch the translated content from the resource file and then we will use the string format to add the company name dynamically.
Finally, add the following HTML code to the component.
<h1>@title</h1> <br /> <EditForm Model="@employee" OnSubmit="SaveEmployeeToLocalStorage"> <div class="row"> <div class="col-md-4"> <div class="form-group"> <label class="control-label col-md-12">@Localize["Name"]</label> <input class="form-control" @bind="employee.Name" /> </div> <div class="form-group"> <label class="control-label col-md-12">@Localize["Gender"]</label> <select class="form-control" @bind="employee.Gender"> <option value="">@Localize["Select Gender"]</option> <option value="Male">@Localize["Male"]</option> <option value="Female">@Localize["Female"]</option> </select> </div> </div> <div class="col-md-4"> <div class="form-group"> <label class="control-label col-md-12">@Localize["City"]</label> <input class="form-control" @bind="employee.City" /> </div> <div class="form-group"> <label class="control-label col-md-12">@Localize["Salary"]</label> <input type="number" class="form-control" @bind="employee.Salary" /> </div> </div> </div> <button type="submit" class="btn btn-primary">Save</button> </EditForm> <hr /> <div class="row"> <div class="col-md-8"> <table class='table'> <thead class="table-active"> <tr> @foreach (string header in TableHeader) { <th> @Localize[header] </th> } </tr> </thead> <tbody> @foreach (Employee emp in lstEmployees) { <tr> <td>@emp.Name</td> <td>@Localize[emp.Gender]</td> <td>@emp.City</td> <td>@emp.Salary.ToString("C2")</td> <td>@emp.JoiningDate</td> </tr> } </tbody> </table> </div> </div>
We have defined a form that will allow the user to add a new employee record. To localize the labels in the form, we are using the Localize alias of the IStringLocalizer
class. We have defined a table just below the form, which will display the list of employees. The character “C” is the currency format specifier that will convert a number to a string to represents a currency amount. The integer succeeding the character “C” is known as the precision specifier which indicates the desired number of decimal places. Clicking on the Save button will submit the form which will then invoke the SaveEmployeeToLocalStorage
method.
🤿 Go deeper » Wanna learn how to localize specifically error messages for Blazor WebAssembly forms? Check out our tutorial.
!To get and set the employee data to the local storage we will add the following JS code in the BlazorWebassemblyI18n/wwwroot/index.html
file at the end of the <body>
tag.
<script> window.employeeData = { get: () => window.localStorage['empData'], set: (value) => window.localStorage['empData'] = value }; </script>
For this sample app, we have saved the employee data in local storage. However, in a real-world app, the data should be saved to a database.
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 put the name of the file as App.resx
. Click on the Add button to create the file (see below):
A resource file is used to manage the translated content. The resource file will contain a set of key-value pairs for the translation. The key will be the text to be translated, and the value will be the text after translation. We will add the Name and Value field for all the text we want to translate (refer to the image below):
The App.resx is the default resource file for our app. It will hold the translations for the default language i.e. English. Similarly, we will add two more resource files, App.fr.resx
and App.ar.resx
, for the French and Arabic languages respectively. You can refer to GitHub to check all the resource files.
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 BlazorWebassemblyI18n/Program.cs
file.
var jsInterop = builder.Build().Services.GetRequiredService<IJSRuntime>(); var appLanguage = await jsInterop.InvokeAsync<string>("appCulture.get"); if (appLanguage != null) { CultureInfo cultureInfo = new CultureInfo(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 Link to the Navigation Menu
Before running the app, we will add the link of our EmployeeData component in the navigation menu. Open BlazorWebassemblyI18n/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. Click on the Employee Data button on the nav menu on the left. You can see the output as displayed below.
Wrapping Up The Blazor WebAssembly I18n Tutorial
In this tutorial, we explored how to internationalize a Blazor WebAssembly app. We created a sample form to accept employee data for a company. The app is served in three languages – English, French, and Arabic.
If you’re looking for a reliable partner in localizing software for global markets, Phrase is the way to go! Sign up for a 14-day trial and see what the leanest, fastest, and most reliable localization platform on the market can do for you!