Localisation in ASP.NET Core Razor Pages - Cultures

This is the first in a series of articles that explores localisation in ASP.NET Core Razor Pages applications. This article will look at the configuration required to prepare a site for content localisation, in other words, to globalise the site. Future articles will cover the creation of localised content and how to present it.

Overview of Globalization in ASP.NET Core

Globalisation is the preparation of an application to cater for different languages based on the user's preference. Localisation is the process of adapting the site content for different countries, regions or cultures. The starting point to globalisation of a web application is being able to determine the language or culture for each request. Following that, a mechanism is required to select the content based on the current request culture. In this article, I look at the role that the CultureInfo class plays in localisation, and how to implement a view component to enable users to select their preferred culture for requests.

The steps below add basic localisation to a Razor Pages application which is generated from the standard ASP.NET Core 3.0 Web Application template with no authentication configured. I called my app "Localisation". You may want to name yours differently. If you do, be careful with namespaces if you want to copy and paste code from this article.

  1. Start by opening the Startup.cs file and adding the following using directives:

    using System.Globalization;
    using Microsoft.AspNetCore.Localization;
    using Microsoft.Extensions.Options;
  2. Localisation is an opt-in feature. It is not enabled by default. Amend the ConfigureServices method to include AddLocalization, which makes the various supporting localisation services available to the dependency injection system. Then add the code to configure RequestLocalizationOptions for the application.

    services.Configure<RequestLocalizationOptions>(options =>
    {
       var supportedCultures = new[]
        {
            new CultureInfo("en"),
            new CultureInfo("de"),
            new CultureInfo("fr"),
            new CultureInfo("es"),
            new CultureInfo("ru"),
            new CultureInfo("ja"),
            new CultureInfo("ar"),
            new CultureInfo("zh"),
            new CultureInfo("en-GB")
        };
        options.DefaultRequestCulture = new RequestCulture("en-GB");
        options.SupportedCultures = supportedCultures;
        options.SupportedUICultures = supportedCultures;
    });

    You need to specify the languages or cultures that you plan to support in your application. Cultures are represented in .NET by the CultureInfo class, which holds information about number and date formatting, calendars, writing systems, sort orders and other locale-specific matters. The overload of the CultureInfo class constructor used here takes a string representing the name of the culture. Permitted values are ISO 639-1 codes that represent the language (e.g. "en" for English) optionally with an ISO 3166 subculture code that represents the country or dialect (e.g "en-GB" for Great Britain, or "en-ZA" for South Africa). In the example above a number of languages are supported, including one subculture - British English which has been set as the default culture.

  3. Now that the RequestLocalizationOptions have been configured, they can be applied to the request localization middleware, which needs to be added in the Configure method after app.UseRouting():

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    
    var localizationOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>().Value;
    app.UseRequestLocalization(localizationOptions);
     
    

Setting the Request Culture

The request localization middleware makes use of components called RequestCultureProviders to determine the culture of the current request. Three of these are added by default:

  • QueryStringRequestCultureProvider, which gets the culture from the query string
  • CookieRequestCultureProvider, which gets the culture from a cookie
  • AcceptHeadersRequestCultureProvider, which gets the culture from the browser's Accept-Language header

The request culture providers are called one after the other until one is able to determine the culture for the request. It is also possible to create your own request culture provider, which I plan to look at in a future article. In the meantime, I will show how to create a View Component that enables the user to set the culture for the current request.

  1. The first step is to create a folder named Models, and within that add a class file named CultureSwitcherModel.cs.

    using System.Collections.Generic;
    using System.Globalization;
     
    namespace Localisation.Models
    {
        public class CultureSwitcherModel
        {
            public CultureInfo CurrentUICulture { get; set; }
            public List<CultureInfo> SupportedCultures { get; set; }
        }
    }
    
  2. Add a folder named ViewComponents to the project, and within it, add a new C# class file named CultureSwitcherViewcomponent.cs. Then replace the content with the following code:

    using Localisation.Models;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Localization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Options;
    using System.Linq;
     
    namespace Localisation.ViewComponents
    {
        public class CultureSwitcherViewComponent : ViewComponent
        {
            private readonly IOptions<RequestLocalizationOptions> localizationOptions;
            public CultureSwitcherViewComponent(IOptions<RequestLocalizationOptions> localizationOptions) =>
                this.localizationOptions = localizationOptions;
     
            public IViewComponentResult Invoke()
            {
                var cultureFeature = HttpContext.Features.Get<IRequestCultureFeature>();
                var model = new CultureSwitcherModel
                {
                    SupportedCultures = localizationOptions.Value.SupportedUICultures.ToList(),
                    CurrentUICulture = cultureFeature.RequestCulture.UICulture
                };
                return View(model);
            }
        }
    }
    
  3. Add a new folder to the Pages folder and name it Components. Within that, add another folder named CultureSwitcher. Then add a Razor View to that named default.cshtml, and replace the existing content with the following:

    @model CultureSwitcherModel
     
    <div>
        <form id="culture-switcher">
            <select name="culture" id="culture-options">
                <option></option>
                @foreach (var culture in Model.SupportedCultures)
                {
                    <option value="@culture.Name" selected="@(Model.CurrentUICulture.Name == culture.Name)">@culture.DisplayName</option>
                }
            </select>
        </form>
    </div>
     
     
    <script>
        document.getElementById("culture-options").addEventListener("change", () => {
            document.getElementById("culture-switcher").submit();
        });
    </script>
    

    The view component is a simple select element, populated with the supported cultures that were configured in Startup. The form that it sits within uses the default get method, which means that the submitted value will appear in the query string with a name of culture. The QueryStringRequestCultureProvider is designed to look for an item in the query string with a key of culture (and/or ui-culture).

    The CurrentCulture for the request determines the locale-specific formatting to be used for such things as dates and numbers. The CurrentUICulture is used to select the correct resource containing translated strings. I will look at how to use resource files for localising static content in the next article in the series. It is possible to set different values for the CurrentCulture and the CurrentUICulture, but more often it makes sense for both to have the same value. If only one is set (e.g via a single query string value), then the value is assigned to both properties.

  4. At this stage, you probably have some red wavy lines in the view that you just created, so open the _ViewImports.cshtml file and add the second and third using directives below, along with the last line that enables you to use a tag helper to render the view component:

    @using Localisation
    @using Localisation.Models
    @using System.Globalization
    @namespace Localisation.Pages
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    @addTagHelper *, Localisation
  5. Include the culture switcher view component within the layout page, using the tag helper approach as shown in the last line here.

    <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
        <ul class="navbar-nav flex-grow-1">
            <li class="nav-item">
                <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
            </li>
            <li class="nav-item">
                <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
            </li>
        </ul>
    </div>
    <vc:culture-switcher/>
  6. Alter Index.cshtml to include the code in the code block and the HTML for the table that displays various bits of data:

    @page
    @using Microsoft.AspNetCore.Localization
    @model IndexModel
    @{
        ViewData["Title"] = "Home page";
        var requestCultureFeature = HttpContext.Features.Get<IRequestCultureFeature>();
        var requestCulture = requestCultureFeature.RequestCulture;
    }
     
    <div class="text-center">
        <h1 class="display-4">Welcome</h1>
        <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
     
        <table class="table culture-table">
            <tr>
                <td style="width:50%;">Culture</td>
                <td>@requestCulture.Culture.DisplayName {@requestCulture.Culture.Name}</td>
            </tr>
            <tr>
                <td>UI Culture</td>
                <td>@requestCulture.UICulture.Name</td>
            </tr>
            <tr>
                <td>UICulture Parent</td>
                <td>@requestCulture.UICulture.Parent</td>
            </tr>
            <tr>
                <td>Date</td>
                <td>@DateTime.Now.ToLongDateString()</td>
            </tr>
            <tr>
                <td>Currency</td>
                <td>
                    @(12345.00.ToString("c"))
                </td>
            </tr>
            <tr>
                <td>Number</td>
                <td>
                    @(123.45m.ToString("F2"))
                </td>
            </tr>
        </table>
    </div>

When you first run the application, the cuture for the request is the set by the AcceptHeadersCultureRequestProvider. When you use the the dropdown to select different cultures, the culture is set by the QueryStringCultureRequestProvider. Try adding a ui-culture key to the query string with a different value to the culture key (e.g. https://localhost:xxxxx/?culture=es&ui-culture=de) to see what effect that has.

Summary

This article is an introduction to localisation in Razor Pages. It covered configuration of the cultures that you wish to support in your application, and how to pass that configuration to localisation middleware. It shows how the culture for the current request works in terms of formatting content for display. It also shows how you can enable your visitors to change the culture to suit their preference.

You may have noticed that while names of days and months are automatically translated, based on the calendar associated with the current culture, other static content remains in English. In the next article, I will show how to use Resource files (.resx) to manage multiple translations for this type of content.