Localization In Blazor Server With .NET Core 3.1

Introduction

Blazor is a framework built by Microsoft for creating interactive client-side web UI with a .NET codebase. We can write both client-side and server-side code in C#.NET itself. There are two hosting models available for Blazor. Blazor Server and Blazor WebAssembly. Blazor server is officially shipped with .NET Core 3.0 and the current version is 3.1. Blazor WebAssembly is still in preview and will be released on May 2020, as per Microsoft roadmap. I have already written eight articles about Blazor Server on C# Corner. Please refer to the below articles for more basics about the Blazor framework.

Create a Blazor server application in Visual Studio 2019

Currently, Visual Studio 2019 is available with a Blazor server template. Since we are creating an Employee app, I am using a Visual Studio extension to create an Employee app with Entity Framework Core. This extension will provide the template with four Razor components for CRUD actions and provide the Entity framework-related codes. You can get the extension from this Visual Studio Marketplace link.

Install the extension and create a Blazor project with this extension.

Blazor project

Give a name to the project and create it.

Blazor localization

If you look at the project structure, you can see that Employee interface, Employee service, SqlDbContext and four Razor components are automatically created by the extension template.

You can execute “update-database” command in Package Manager Console to create SQL database and Employee table using EF Migration. By default, extension is using the local SQL server to store the data. If needed, you can change the server name and database name.

We are not modifying the default service layer. We can create a “Resource” folder inside the root directory and also create a “Pages” folder under that folder. You can give any name for Resource folder but should correctly mention inside the Startup class. I will explain those details soon. Inside the Resource folder, you should name the folder as “Pages”. This is the convention followed by Microsoft in .NET Core localization.

As I mentioned earlier, we have four Razor components for Employee CRUD actions. We can create four resource files for each component. Please note, you can create all the localization keys in single resource file also. But for simplicity and clarity, I am creating separate resource file for each component. We will create two supported language cultures in this application, “English” and “Malayalam”. Hence, we must create total eight resource files. As I pointed earlier, you can reduce single resource file for one culture if you wish.

We can add a new resource file under “Resources\Pages” folder for the ListEmployees component.

Resources\Pages

You can notice that, I have not added any culture code with this file name. Because I will set English as my default request culture. Otherwise, we must provide the file name as “ListEmployees.en.resx”

You can add all keys and values for localization inside this file.

Localization

Above is the keys and values for ListEmployees component in English culture. We can add the Malayalam culture for same component.

ListEmployees component

All the keys in both culture for same component must be same. Values are different as per culture.

We can add resource files for remaining three components.

  • AddEmployee.resx
  • AddEmployee.ml.resx
  • EditEmployee.resx
  • EditEmployee.ml.resx
  • DeleteEmployee.resx
  • DeleteEmployee.ml.resx

We can modify the Razor components by replacing the static texts with resource keys.

Before that, we must modify “_Imports.razor” file by adding below using statement.

“Microsoft.Extensions.Localization”

Razor components

We will use “IStringLocalizer” interface from above library to localize text file in every component.

We can modify ListEmployees component with below code.

ListEmployees.razor

@page "/listemployees"
@inject IEmployeeService EmployeeService
@inject IStringLocalizer<ListEmployees> L

<h2>@L["Title"]</h2>
<p>
    <a class="btn btn-primary" href='/addemployee'>@L["CreateEmployee"]</a>
</p>
@if (employees == null)
{
    <p>@L["Loading"]...</p>
}
else
{
    <table class='table'>
        <thead>
            <tr>
                <th>@L["EmpName"]</th>
                <th>@L["EmpDepartment"]</th>
                <th>@L["EmpDesignation"]</th>
                <th>@L["EmpCompany"]</th>
                <th>@L["EmpCity"]</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var employee in employees)
            {
                <tr>
                    <td>@employee.Name</td>
                    <td>@employee.Department</td>
                    <td>@employee.Designation</td>
                    <td>@employee.Company</td>
                    <td>@employee.City</td>
                    <td>
                        <a class="btn btn-warning" href='/editemployee/@employee.Id'>@L["Edit"]</a>
                        <a class="btn btn-danger" href='/deleteemployee/@employee.Id'>@L["Delete"]</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    List<Employee> employees;

    protected override async Task OnInitializedAsync()
    {
        employees = await EmployeeService.GetEmployees();
    }
}

I have injected IStringLocalizer with current page name inside the component.

@inject IStringLocalizer<ListEmployees> L

We can use the variable “L” for localization and replace the static texts with resource keys.

We can modify the remaining three components in the same way.

AddEmployee.razor

@page "/addemployee"
@inject NavigationManager NavigationManager
@inject IEmployeeService EmployeeService
@inject IStringLocalizer<AddEmployee> L

<h2>@L["CreateEmployee"]</h2>
<hr />
<form>
    <div class="row">
        <div class="col-md-8">
            <div class="form-group">
                <label for="Name" class="control-label">@L["EmpName"]</label>
                <input for="Name" class="form-control" @bind="@employee.Name" />
            </div>
            <div class="form-group">
                <label for="Department" class="control-label">@L["EmpDepartment"]</label>
                <input for="Department" class="form-control" @bind="@employee.Department" />
            </div>
            <div class="form-group">
                <label for="Designation" class="control-label">@L["EmpDesignation"]</label>
                <input for="Designation" class="form-control" @bind="@employee.Designation" />
            </div>
            <div class="form-group">
                <label for="Company" class="control-label">@L["EmpCompany"]</label>
                <input for="Company" class="form-control" @bind="@employee.Company" />
            </div>
            <div class="form-group">
                <label for="City" class="control-label">@L["EmpCity"]</label>
                <input for="City" class="form-control" @bind="@employee.City" />
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-4">
            <div class="form-group">
                <input type="button" class="btn btn-primary" @onclick="@CreateEmployee" value="@L["Save"]" />
                <input type="button" class="btn" @onclick="@Cancel" value="@L["Cancel"]" />
            </div>
        </div>
    </div>
</form>

@code {

    Employee employee = new Employee();

    protected async Task CreateEmployee()
    {
        await EmployeeService.CreateEmployee(employee);
        NavigationManager.NavigateTo("listemployees");
    }

    void Cancel()
    {
        NavigationManager.NavigateTo("listemployees");
    }
}

EditEmployee.razor

@page "/editemployee/{id}"
@inject NavigationManager NavigationManager
@inject IEmployeeService EmployeeService
@inject IStringLocalizer<EditEmployee> L

<h2>@L["EditEmployee"]</h2>
<hr />
<form>
    <div class="row">
        <div class="col-md-8">
            <div class="form-group">
                <label for="Name" class="control-label">@L["EmpName"]</label>
                <input for="Name" class="form-control" @bind="@employee.Name" />
            </div>
            <div class="form-group">
                <label for="Department" class="control-label">@L["EmpDepartment"]</label>
                <input for="Department" class="form-control" @bind="@employee.Department" />
            </div>
            <div class="form-group">
                <label for="Designation" class="control-label">@L["EmpDesignation"]</label>
                <input for="Designation" class="form-control" @bind="@employee.Designation" />
            </div>
            <div class="form-group">
                <label for="Company" class="control-label">@L["EmpCompany"]</label>
                <input for="Company" class="form-control" @bind="@employee.Company" />
            </div>
            <div class="form-group">
                <label for="City" class="control-label">@L["EmpCity"]</label>
                <input for="City" class="form-control" @bind="@employee.City" />
            </div>
        </div>
    </div>
    <div class="row">
        <div class="form-group">
            <input type="button" class="btn btn-primary" @onclick="@UpdateEmployee" value="@L["Update"]" />
            <input type="button" class="btn" @onclick="@Cancel" value="@L["Cancel"]" />
        </div>
    </div>
</form>

@code {

    [Parameter]
    public string id { get; set; }

    Employee employee = new Employee();

    protected override async Task OnInitializedAsync()
    {
        employee = await EmployeeService.SingleEmployee(id);
    }

    protected async Task UpdateEmployee()
    {
        await EmployeeService.EditEmployee(id, employee);
        NavigationManager.NavigateTo("listemployees");
    }

    void Cancel()
    {
        NavigationManager.NavigateTo("listemployees");
    }
}

DeleteEmployee.razor

@page "/deleteemployee/{id}"
@inject NavigationManager NavigationManager
@inject IEmployeeService EmployeeService
@inject IStringLocalizer<DeleteEmployee> L

<h2>@L["ConfirmDelete"]</h2>
<p>@L["CofirmMessage"] ? Id : <b> @id </b></p>
<br />
<div class="col-md-4">
    <table class="table">
        <tr>
            <td>@L["EmpName"]</td>
            <td>@employee.Name</td>
        </tr>
        <tr>
            <td>@L["EmpDepartment"]</td>
            <td>@employee.Department</td>
        </tr>
        <tr>
            <td>@L["EmpDesignation"]</td>
            <td>@employee.Designation</td>
        </tr>
        <tr>
            <td>@L["EmpCompany"]</td>
            <td>@employee.Company</td>
        </tr>
        <tr>
            <td>@L["EmpCity"]</td>
            <td>@employee.City</td>
        </tr>
    </table>
    <div class="form-group">
        <input type="button" value="@L["Delete"]" @onclick="@Delete" class="btn btn-primary" />
        <input type="button" value="@L["Cancel"]" @onclick="@Cancel" class="btn" />
    </div>
</div>

@code {

    [Parameter]
    public string id { get; set; }
    Employee employee = new Employee();

    protected override async Task OnInitializedAsync()
    {
        employee = await EmployeeService.SingleEmployee(id);
    }

    protected async Task Delete()
    {
        await EmployeeService.DeleteEmployee(id);
        NavigationManager.NavigateTo("listemployees");
    }

    void Cancel()
    {
        NavigationManager.NavigateTo("listemployees");
    }
}

Finally, we can modify the “ConfigureServices” and “Configure” methods in “Startup” class file.

Modify the ConfigureServices method

You can notice that, I have set the resource path as “Resources”. You can give any name for this resource path. But it should match with the actual folder name which contain the resource files.

ConfigureServices (method)

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor(options => options.DetailedErrors = true);
    services.AddSingleton<WeatherForecastService>();

    services.AddDbContext<SqlDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("SqlDbContext")));

    services.AddScoped<IEmployeeService, EmployeeService>();

    services.AddLocalization(options => options.ResourcesPath = "Resources");
    var supportedCultures = new List<CultureInfo> { new CultureInfo("en"), new CultureInfo("ml") };
    services.Configure<RequestLocalizationOptions>(options =>
    {
        options.DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("en");
        options.SupportedUICultures = supportedCultures;
    });
}

We can modify the Configure method also by adding request localization pipeline.

Configure (method)

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
    }

    app.UseRequestLocalization();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");
    });
}

We have completed the entire coding part. We can run the application.

Entire coding part

Click “Employee data” link and click “New Employee” button to add a new employee data.

New Employee

If you are using Chrome browser, go to Settings -> Advanced -> Languages and choose another language. I have added Malayalam culture in this application. Hence, I can set Malayalam as default language in language preferences. After that, we can refresh the page again.

Advanced

You can notice that, the language is automatically changed to new culture. Application will automatically detect browser language preference and will show the text accordingly.

Browser language

Conclusion

In this post, I have explained how to set localization in Blazor server app. I have created this project using a Visual Studio extension. I have added resource files for Razor components used for CRUD operations in Employee app. I have used two cultures English and Malayalam to demonstrate localization. I believe that, you have got a very good understanding on Localization with Blazor server app. Please feel free to send your valuable feedbacks.


Similar Articles