Every website owner wants to reach a larger audience. We typically create web applications that support multiple languages and deliver localized content according to user region and culture to realize this dream. With its extensive support for localization and globalization, ASP.NET Core has a number of built-in features that make it simple for developers to create web applications that support multiple languages. I’ll walk you through a few of the common features you can use to deliver localized content in accordance with user preference in this tutorial.
Configuring Localization in ASP.NET Core
The RequestLocalizationMiddleware middleware must be configured in order to configure localization in ASP.NET Core, which uses middleware to configure the majority of its cross-cutting features. Based on the data sent by the client, this middleware enables the automatic setting of the culture for HTTP requests.
Create a new ASP.NET Core MVC Web Application called AspNetCoreLocalizationDemo in Visual Studio 2019 by clicking the “New” button. Open the Startup.cs file and call the AddLocalization method to configure the localization middleware. The localization services are added to the services container by the AddLocalization method. The relative path under the application root where the resource files are located is specified by the ResourcePath property.
services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
Additionally, the AddViewLocalization method, which we will use later in this tutorial, adds support for localized view files.
services.AddControllersWithViews()
.AddViewLocalization();
Note that the AddDataAnnotationsLocalization method in ASP.NET Core also adds support for localized DataAnnotations.
Next, we must decide which cultures our application will support and which one will serve as the default culture. Let’s configure these three cultures as follows since I want to support the French, German, and English languages in this tutorial:
services.Configure<RequestLocalizationOptions>(options =>
{
options.DefaultRequestCulture = new RequestCulture("en-US");
var cultures = new CultureInfo[]
{
new CultureInfo("en-US"),
new CultureInfo("de-DE"),
new CultureInfo("fr-FR")
};
options.SupportedCultures = cultures;
options.SupportedUICultures = cultures;
});
Following is the complete code of the ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
services.AddControllersWithViews()
.AddViewLocalization();
services.Configure<RequestLocalizationOptions>(options =>
{
options.DefaultRequestCulture = new RequestCulture("en-US");
var cultures = new CultureInfo[]
{
new CultureInfo("en-US"),
new CultureInfo("de-DE"),
new CultureInfo("fr-FR")
};
options.SupportedCultures = cultures;
options.SupportedUICultures = cultures;
});
}
The localization middleware must be enabled using the UseRequestLocalization method as the last configuration step.
app.UseRequestLocalization();
The RequestLocalizationOptions object we configured above is initialized by the UseRequestLocalization method. RequestCultureProvider is a concept in the RequestLocalizationOptions that decides the culture information in each request. We will cover these providers in greater detail later in this tutorial. The RequestLocalizationOptions class configures the QueryStringRequestCultureProvider, CookieRequestCultureProvider, and AcceptLanguageHeaderRequestCultureProviders as default providers. The list of RequestCultureProvider is enumerated when requests arrive at the server, and the first provider that can successfully identify the request culture is used. The DefaultRequestCulture property setting will be used in the event that none of the providers are able to identify the request culture.
options.DefaultRequestCulture = new RequestCulture("en-US");
The foundational localization settings for our application are now set up. The location and naming guidelines for the resource files must be chosen next.
Resource Files Naming Conventions and Location
We can separate the localizable resources, such as strings, icons, images, etc., from the source code by using a resource (.resx) file. We typically include information about the culture in the file name when creating resource files for various languages and cultures. For instance, the file name for a resource file to store resources in English would be MyResourceFile.en.resx. The file name will be MyResourceFile.en-US.resx if you want to make a separate resource file for US English. You can right-click on the folder where you want to create the resource file and select Add > New Item from the menu in Visual Studio to create a new resource file.
The full type name of the class is used by ASP.NET Core localization to find the resource files in the project. For instance, the resource file name would be as follows if we were to create French language resources for the HomeController of our AspNetCoreLocalizationDemo application.
AspNetCoreLocalizationDemo.Controllers.HomeController.fr-FR.resx
Another strategy is to save the resource file next to the HomeController class with the name HomeController.fr-FR.resx. The English, French, and German resource files that were created alongside the HomeController class are displayed in the following screenshot.
Please be aware that the ResourcesPath property has been set as follows in the Startup.cs file’s ConfigureServices method:
options.ResourcesPath = "Resources";
It implies that we can now create the Resources folder and place all resource files in it in the project root folder. The resource files can also be organized using nested folders with the same naming patterns. For instance, we could make a Controllers folder inside the Resources folder and move the three HomeController-related resource files there.
Depending on how you want to organize your resource files, you can either use the dot naming convention or copy the file path. If you want to mimic the path, your resource file will be located at Resources/Controllers/HomeController.en-US.resx, whereas if you want to use the dot naming convention, you will name your file as Resources/Controllers.HomeController.en-US.resx.
Let’s add some string resources to the three resource files by opening them all. The two English language string resources added to the HomeController.en-US.resx resource file are shown in the following screenshot.
The following screenshot shows the two French language string resources added in HomeController.fr-FR.resx resource file.
The following screenshot shows the two English language string resources added in HomeController.de-DE.resx resource file.
Introducing .NET Core Localizer Services
Localized services are a new concept introduced by.NET Core to increase developer productivity. These services can give you access to the resources saved in resource files and can be injected using dependency injection. These are the three localizer services:
- IStringLocalizer or IStringLocalizer<T>
- IHtmlLocalizer or IHtmlLocalizer<T>
- IViewLocalizer
Using IStringLocalizer Service
A service that offers localized strings is represented by IStringLocalizer. As shown in the code snippet below, let’s inject the IStringLocalizerT> into the application’s HomeController.
HomeController.cs
public class HomeController : Controller
{
private readonly IStringLocalizer<HomeController> _stringLocalizer;
public HomeController(IStringLocalizer<HomeController> stringLocalizer)
{
_stringLocalizer = stringLocalizer;
}
public IActionResult Index()
{
ViewData["PageTitle"] = _stringLocalizer["page.title"].Value;
ViewData["PageDesc"] = _stringLocalizer["page.description"].Value;
return View();
}
}
When the localizer is ready, you can use its indexer to retrieve the localized strings from the resource file that correspond to the current cultural context. Using the keys page.title and page.description, we are retrieving localized strings in the aforementioned example and storing them in a ViewData object. In our razor view, we can then access this ViewData object and display the localized strings as displayed in the example below.
Index.cshtml
<div class="text-center">
<h1 class="display-4">
@ViewData["PageTitle"]
</h1>
<p>
@ViewData["PageDesc"]
</p>
</div>
If you run the application, the localized strings in English will appear on the page.
By directly injecting the IStringLocalizerT> into your razor views, as shown in the following code snippet, you can avoid having to pass strings from controllers to views using the ViewData object and cut down on some of the code from the above example.
Index.cshtml
@using AspNetCoreLocalizationDemo.Controllers
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<HomeController> Localizer
<div class="text-center">
<h1 class="display-4">
@Localizer["page.title"]
</h1>
<p>
@Localizer["page.description"]
</p>
</div>
Using IHtmlLocalizer Service
Let’s introduce the HTML bold tag to your string to add some HTML content, as seen in the screenshot below.
You will notice that the browse does not display the formatted string if you run the application right away. This is due to the IStringLocalizer’s inability to encode the HTML that is present in the strings. Utilize IHtmlLocalizer, which will HTML-encode the resource string’s HTML characters but not the resource string itself.
The injection and use of IHtmlLocalizer are very similar to those of IStringLocalizer. The following shows how IHtmlLocalizer is used in the razor view.
Index.cshtml
@using AspNetCoreLocalizationDemo.Controllers
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<HomeController> Localizer
@inject IHtmlLocalizer<HomeController> HtmlLocalizer
<div class="text-center">
<h1 class="display-4">
@Localizer["page.title"]
</h1>
<p>
@HtmlLocalizer["page.description"]
</p>
</div>
Restart the application, and the word “home page” should appear bold this time.
Using IViewLocalizer Service
A view receives localized strings from the IViewLocalizer service. The naming convention and location of the resource files become even more crucial when we use the IViewLocalizer service. This is so that the resource file can be located using the view file name rather than the default implementation of IViewLocalizer. This also implies that IViewLocalizer cannot use the global shared resource files.
Create a resource file called Index.en-US.resx at the ResourcesViewsHome folder with the name Index.razor since our view file’s name is Index.razor, as shown in the following screenshot.
For demonstration purposes, let’s add a different string in the Index.en-US.resx file.
To use IViewLocalizer, inject it in the Index.cshtml file as shown in the code snippet below.
Index.cshtml
@using AspNetCoreLocalizationDemo.Controllers
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<HomeController> Localizer
@inject IHtmlLocalizer<HomeController> HtmlLocalizer
@inject IViewLocalizer ViewLocalizer
<div class="text-center">
<h1 class="display-4">
@Localizer["page.title"]
</h1>
<p>
@HtmlLocalizer["page.description"]
</p>
<p>
@ViewLocalizer["view.breadcrumb.text"]
</p>
</div>
When you rerun the application, you should see the words “Home – Index” displayed on the page thanks to the IViewLocalizer service.
You can add more languages and set any language as your default language if you’re using the Chrome browser by going to Settings. For instance, I added the German language to the screenshot below and instructed the Chrome browser to display everything in German.
Run your application right away to see the translated strings from the German language resource file displayed on the page.
Overview of Default Request Culture Providers
There are some request culture providers included right out of the box when we configure and use the request localization in.NET Core. In our application, we have the option to either use these default providers or define our own unique request culture provider. The list of default request culture providers is provided below.
- AcceptLanguageHeaderRequestCultureProvider
- QueryStringRequestCultureProvider
- CookieRequestCultureProvider
AcceptLanguageHeaderRequestCultureProvider
Web browsers automatically send the client culture along with each request using the Accept-Header, which is typically set to the user’s native language. The tools in the Chrome browser make it simple to see this header and its value. For instance, because I added the German language in the earlier section, my browser is currently displaying both en-US and de-DE.
QueryStringRequestCultureProvider
To override the culture, you can pass a query string to this provider. Because the query string’s culture value can be changed, developers can quickly test or debug various cultures. Culture and ui-culture parameters can be passed to the query string provider. One of the two culture or ui-culture values may be passed in place of the other, and the query string provider will set both values using that value. By including fr-FR in the query string, let’s test this provider in our application. You’ll notice that the page’s contents are converted to French strings right away.
Let’s change the value to de-DE and the page contents will update accordingly.
CookieRequestCultureProvider
To establish the culture, most production apps typically use cookies. The cookie with the name is used by the CookieRequestCultureProvider.AspNetCore.Culture by default, and you can use the tools in your browser to see how the cookie value is formatted. c=%LANGCODE%|uic=%LANGCODE%, where c stands for Culture and uic for UICulture, is the cookie format.
Switching Language in ASP.NET Core Applications
Now that we are aware of the standard methods for changing the ASP.NET Core application culture, such as Accept-Header, the query string, and cookies, let’s build a sample demo that will give the application’s end user the option to switch to a different language at runtime using one of the standard methods mentioned above. To display supported cultures in a dropdown at the top of the page, we must first obtain the list of supported cultures. The following code snippet should be added to the top of the _Layout.cshtml file after opening it.
_Layout.cshtml
@using Microsoft.AspNetCore.Builder
@using Microsoft.AspNetCore.Localization
@using Microsoft.Extensions.Options
@inject IOptions<RequestLocalizationOptions> LocOptions
@{
string returnUrl = ViewContext.HttpContext.Request.Path;
var requestCulture = Context.Features.Get<IRequestCultureFeature>();
var cultureItems = LocOptions.Value.SupportedUICultures
.Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
.ToList();
}
Access to the collection of feature interfaces for the current request is made possible by the HttpContext’s Features property. We are particularly interested in the IRequestCultureFeature, which stands for the feature that conveys details about the culture of the active request.
var requestCulture = Context.Features.Get<IRequestCultureFeature>();
We are using the RequestLocalizationOptions object to retrieve the complete list of supported cultures that we configured in the Startup.cs file at the beginning of this post. In order to bind the SelectListItem with the HTML select control, we are also creating a SelectListItem from the list of SupportedUICultures.
var cultureItems = LocOptions.Value.SupportedUICultures
.Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
.ToList();
The asp-items property will be used to link the HTML select dropdown with the cultures in the form that will be added to the top navigation bar. To ensure that the current culture is pre-selected automatically on page refresh, the current culture name is also bound with the asp-for property. Additionally, I automated the submission of the form to the ChangeLanguage action method, which will perform all the magic for us, using the onchange event.
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<form asp-action="ChangeLanguage" asp-controller="Home" method="post">
<input type="hidden" id="returnUrl" name="returnUrl" value="@returnUrl" />
<select id="culture"
name="culture"
class="form-control"
onchange="this.form.submit();"
asp-items="cultureItems"
asp-for="@requestCulture.RequestCulture.UICulture.Name">
</select>
</form>
</li>
</ul>
The primary function of the HomeController class’s ChangeLanguage action method is to create a cookie with the currently selected culture and add it to the HTTP response cookies collection. It takes the culture and returnUrl as parameters.
[HttpPost]
public IActionResult ChangeLanguage(string culture, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddDays(7)
}
);
return LocalRedirect(returnUrl);
}
You only need that one line of code to implement language switching. The page contents will be automatically translated to French when you launch the application and choose French from the dropdown.
Now try to select the German language from the dropdown and the page contents will update accordingly.
At this point, if you check the cookies in the browser developer tools, you will see that the culture is currently set to German.
Summary
Many web developers find localization to be a frightening subject, but you will agree with me that ASP.NET Core has made localization incredibly easy to implement. The configuration of multiple languages, the creation of localized string resources, and the use of the default request culture providers were all covered in this post. Additionally, we have mastered the art of dynamic language switching. I sincerely hope this post was helpful to you. Please leave your feedback in the section below if you have any suggestions or comments. Don’t forget to spread the word about this tutorial to your friends and neighbors.