Globalization and Localization with Resource Files in ASP.NET Core

Globalization and Localization with Resource Files in ASP.NET Core

If you are working with Globalization & Localization feature of ASP.NET Core then resource files will always come into play. A resource file (.resx) is a useful mechanism for separating culture specific strings from code. These files contain key/values items and can be created from Visual Studio by selecting Add ➤ New Item in the solution explorer. For example, if your website has 3 supported cultures – French, Spanish, & English (English being the default culture) then you will have to create 2 types of resource files, i.e. for French and for Spanish.

In this tutorial I will show how to fetch and display localized culture based string from different areas of the website, these are:

  • 1. Controllers using IStringLocalizer object.
  • 2. Data Annotations error messages.
  • 3. Custom Validation attribute error messages.
  • 4. Views through IViewLocalizer object.

I will explain all this by creating a Multilingual Job Application form which will be based in 3 languages – English, French & Spanish.

This tutorial is a part of Globalization and Localization in ASP.NET Core. There are 3 tutorials to master it:

Configuring Startup

The first step is to tell the application which cultures your website will support, add the localisation services in your application, and do other necessary setups. So go to Startup class and first add the following namespaces to it:

using System.Globalization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

Next add the below shown code in the ConfigureServices() method.

public void ConfigureServices(IServiceCollection services)
{ 
    services.AddMvc().AddViewLocalization(LanguageViewLocationExpanderFormat
                     .Suffix).AddDataAnnotationsLocalization();
    services.Configure<RequestLocalizationOptions>(options =>
    {
        var supportedCultures = new[]
        {
            new CultureInfo("en-US"),
            new CultureInfo("fr"),
            new CultureInfo("es")
        };
        options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
        options.SupportedCultures = supportedCultures;
        options.SupportedUICultures = supportedCultures;
    });
}

This code basically tells 2 things:

  • 1. You want Localization feature to be made ready for Data Annotations and Views.
  • 2. Your site supports 3 cultures – ‘en-US’ (English – us), ‘fr’ (French) & ‘es’ (Spain). The ‘en-US’ being the default one.

Next, add the below code to the Configure() method to allow the use of Localisation Services in your application as shown below:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // after app.UseRouting();
    var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
    app.UseRequestLocalization(locOptions.Value); 
    //..
}

You are now all set to use Globalization & Localization feature of ASP.NET Core and create a multi language job application form using resource files.

DataAnnotations localization using Resource files

The first thing you do is create a ‘Model’ for the job application form. Therefore create a new class inside the Models folder and name it JobApplication.cs. This class contains fields for Name of the applicant, email address, DOB, etc. Also, applied different attributes like [Required], [RegularExpression], [Range], [Display] to various fields.

gRPC Remote Procedure Calls is now available in ASP.NET Core. Start with gRPC right now – How to implement gRPC in ASP.NET Core

The class full code is given below:

public class JobApplication
{
    [Required(ErrorMessage = "Please provide your name")]
    [Display(Name = "Job applicant name")]
    public string Name { get; set; }

    [RegularExpression("^[a-zA-Z0-9_\\.-][email protected]([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "E-mail is not valid")]
    [Display(Name = "Job applicant email")]
    public string Email { get; set; }

    [CustomDate]
    [Display(Name = "Date of Birth")]
    public DateTime DOB { get; set; }

    [Required(ErrorMessage = "Please select your sex")]
    [Display(Name = "Job applicant sex")]
    public string Sex { get; set; }

    [Range(2, 4, ErrorMessage = "{0} must be a number between {1} and {2}")]
    [Display(Name = "Job applicant experience")]
    public int Experience { get; set; }

    [Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the Terms")]
    [Display(Name = "Terms")]
    public bool TermsAccepted { get; set; }
}

Next, create resource files to contain localized strings for these error messages and display names. So create 2 resource files in the same directory of the ‘JobApplication’ class and name them as:

  • JobApplication.es.resx
  • JobApplication.fr.resx

Note : See the names of the resource files where I have put ‘.es’ for Spanish one and ‘.fr’ for French.

In each of these resource files you have to add the strings (i.e. error messages and display name of the attributes) and their respective French and Spanish texts. Do this for each of the strings. I have shown this in the below 2 images of these 2 resource files.

French Language Resource File.resx
Spanish Language Resource File.resx

Custom Validation Localization Strings

I have created a custom validation attribute called [CustomDate] for data of birth field. In this I will perform my custom validation on the dob field of the applicant.

[CustomDate]
[Display(Name = "Date of Birth")]
public DateTime DOB { get; set; }

So create a new class called ‘CustomDate.cs’ and place it inside a new folder called ‘Infrastructure’. You can name this folder anything based on your liking. Since this class is a custom validator so you have to inherit it from ValidationAttribute class. The full code of this class is given below:

public class CustomDate : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var _localizationService = (IStringLocalizer<CustomDate>)validationContext.GetService(typeof(IStringLocalizer<CustomDate>));

        if ((DateTime)value > DateTime.Now)
            return new ValidationResult(_localizationService["Date of Birth cannot be in the future"]);
        else if ((DateTime)value < new DateTime(1980, 1, 1))
            return new ValidationResult(_localizationService["Date of Birth should not be before 1980"]);
        return ValidationResult.Success;
    }
}

Explanation: Here I used IStringLocalizer object to fetch my culture-specific resources at run time. It is a service that provides localized strings stored in resource files.

var _localizationService = (IStringLocalizer<CustomDate>)validationContext.GetService(typeof(IStringLocalizer<CustomDate>));

Notice how I am fetching the language specific strings from the resource file by the following codes:

_localizationService["Date of Birth cannot be in the future"]
(_localizationService["Date of Birth should not be before 1980"]

Next, create 2 resource files in the same directory of the custom validation class (i.e. ‘Infrastructure’ folder) and name them as:

  • CustomDate.es.resx
  • CustomDate.fr.resx

Inside these resource files put the French and Spanish text for the string – ‘Date of Birth cannot be in the future’ & ‘Date of Birth should not be before 1980’.

Don’t worry about the codes as you can download them from the link given at the end. All you have to do is understand how everything works here.

Controllers localization using Resource files

The controller has one action method which is called when the form is submitted. On successful submission I have to show success message in 3 languages (en, fr, es) based on the user’s culture.

So first I have to inject IStringLocalizer object on the controller’s constructor using dependency injection feature of asp.net core, and then fetch the relevant culture specific string message from the resource file. This message is shown to the user on the form submission.

The controller’s code is given below:

public class HomeController : Controller
{
    private readonly IStringLocalizer<HomeController> _localizer;

    public HomeController(IStringLocalizer<HomeController> localizer)
    {
        _localizer = localizer;
    }

    public IActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Index(JobApplication jobApplication)
    {
        if (ModelState.IsValid)
            ViewBag.Message = _localizer["Your application is accepted"];
        return View();
    }
}

ViewBag is filled with the localized message stored in the resource file from:

ViewBag.Message = _localizer["Your application is accepted"]

The _localizer is the variable that contains IStringLocalizer object, and it’s task is to provide me with culture specific text stored in the resource file.

Next, create the 2 resource files inside the ‘Controllers’ folder and name them as:

  • HomeController.es.resx
  • HomeController.fr.resx

Note that ‘HomeController’ is the name of my controller file and this may be different in your case.

Are you working with REST APIs then check my newest tutorial – How to secure APIs with JWT in ASP.NET Core 3.1 [with source codes]

Since I am having only one string which is ‘Your application is accepted’, therefore both the resource files will have just one entry.

I have shown this in the below 2 images of the controller’s resource files:

Controller Spanish Resource File
Controller French Resource File

Views localization using Resource files

The IViewLocalizer service provides localized strings for a view and to use it you have to inject in your View as shown below:

@inject IViewLocalizer Localizer

The Job Application form resides in my Index view and it may be different in your case. So first add the necessary namespace in your View:

@using Microsoft.AspNetCore.Builder
@using Microsoft.AspNetCore.Localization
@using Microsoft.Extensions.Options
@using Microsoft.AspNetCore.Mvc.Localization 

Then inject IViewLocalizer & IOptions in the view as shown below:

@inject IViewLocalizer Localizer
@inject IOptions<RequestLocalizationOptions> LocOptions

Through the RequestLocalizationOptions object I will fill a select control with the cultures that the website will support. This will also help user to select their culture and the form will be presented based on the selected culture of the user.

Next add the below code that will create and fill the select control’s options.

@{
    var requestCulture = Context.Features.Get<IRequestCultureFeature>();
    var cultureItems = LocOptions.Value.SupportedUICultures
        .Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
        .ToList();
}

<label>Language:</label>
<select onchange="SetCulture(this.value)" asp-for="@requestCulture.RequestCulture.UICulture.Name" asp-items="cultureItems">
</select>

A bit of JavaScript code will be needed to redirect the users to their selected culture based job application form. This JS code which you will add to the view is given below:

<script>
    function SetCulture(selectedValue) {
        var url = window.location.href.split('?')[0];
        var culture = "?culture=" + selectedValue + "&ui-culture=" + selectedValue;
        window.location.href = url + culture;
    }
</script>

Here I am using QueryStringRequestCultureProvider where the culture which is selected by the user is added to the query string of the url. So the French version of the application form will have the url given by:

https://localhost:44356/?culture=fr&ui-culture=fr

While the Spanish version of the form will have the url as:

https://localhost:44356/?culture=es&ui-culture=es

The English version does not need to add the culture in the query string so the form’s url will be:

https://localhost:44356

Next all you have to do is put the form in the View. The full code of the form is given below:

<form class="m-1 p-1" asp-action="Index" asp-route-culture="@culture" asp-route-ui-culture="@uiculture" method="post">
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="DOB"></label>
        <input asp-for="DOB" type="text" asp-format="{0:d}" class="form-control" />
        <span asp-validation-for="DOB" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Sex"></label>
        <div>
            <input asp-for="Sex" type="radio" value="M" />@Localizer["Male"]
            <input asp-for="Sex" type="radio" value="F" />@Localizer["Female"]
        </div>
        <span asp-validation-for="Sex" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Experience"></label>
        <select asp-for="Experience" class="form-control">
            <option value="Select">@Localizer["Select"]</option>
            <option value="0">Fresher</option>
            <option value="1">0-1 years</option>
            <option value="2">1-2 years</option>
            <option value="3">2-3 years</option>
            <option value="4">3-4 years</option>
            <option value="5">4-5 years</option>
        </select>
        <span asp-validation-for="Experience" class="text-danger"></span>
    </div>
    <div class="form-group">
        <input asp-for="TermsAccepted" />
        <label asp-for="TermsAccepted" class="form-check-label">
            @Localizer["I accept the terms & conditions"]
        </label>
        <span asp-validation-for="TermsAccepted" class="text-danger"></span>
    </div>
    <button name="formsubmit" value="Button Control" type="submit" class="btn btn-primary">@Localizer["Submit Application"]</button>
</form>
Model validation occurs after model binding and reports errors where data doesn’t conform to business rules. You can learn them in just 2 articles:

Explanation : The data annotations messages will come from the model resources file which I explained in the earlier part. For the other string of the view the culture related strings are fetched from the ‘IViewLocalizer’ by using the code – @Localizer["SomeString"].

See the radio button code where I did this thing:

<input asp-for="Sex" type="radio" value="M" />@Localizer["Male"]
<input asp-for="Sex" type="radio" value="F" />@Localizer["Female"]

Similarly the terms label is doing the same thing:

<label asp-for="TermsAccepted" class="form-check-label">
    @Localizer["I accept the terms & conditions"]
</label>

Next create 2 resource file in the same direction of the view. In my case it is Views ➤ Home directory. Name these 2 resource files as:

  • Index.es.resx
  • Index.fr.resx

Add the necessary string and there Spanish & French texts in these files.

I have given the snapshot of these 2 resource files below:

View French Resource File
View Spanish Resource File
Testing

Let’s test how it all works. I have shown these in 2 videos, the first one showing the user selecting his culture and the relevant form version showing up in the browser.

Selecting Culture for Multilingual form Video

The next video shown the data annotation messages in Spanish & French version.

Multilingual Form Video

You can download the full source code by clicking the below link:

Download

Resource files location

You can also change resource files location by adding the following code in Startup.ConfigureServices method.

services.AddLocalization(options => options.ResourcesPath = "Resources");

Here I have set the ‘Resources’ folder, which is located on the root of the website, to contain all the resources files.

So in this case the resource file for HomeController should be located on any of the 2 locations:

1. Inside the Resources/Controllers/ folder as:

  • Resources/Controllers/HomeController.es.resx
  • Resources/Controllers/HomeController.fr.resx

2. Inside the Resources folder as:

  • Resources/Controllers.HomeController.es.resx
  • Resources/Controllers.HomeController.fr.resx

The resources files for data annotations should be located in:

  • Resources/Models.JobApplication.es.resx
  • Resources/Models.JobApplication.fr.resx

Or

  • Resources/Models/JobApplication.es.resx
  • Resources/Models/JobApplication.fr.resx

The resource files for the View should be located in:

  • Resources/Views.Home.Index.es.resx
  • Resources/Views.Home.Index.fr.resx

Or

  • Resources/Views/Home/Index.es.resx
  • Resources/Views/Home/Index.fr.resx
Conclusion

This tutorial looked at how to use resource files as part of localising the content of an asp.net core website.

Resource files were made for a specific page, and for more general use around the site. I also explained the naming of these resource file and where you need to place them.

I hoped you enjoyed reading as much as I enjoyed creating this long tutorial.

Share this article -

yogihosting

ABOUT THE AUTHOR

This article has been written by the Technical Staff of YogiHosting. Check out other articles on "ASP.NET Core, jQuery, EF Core, SEO, jQuery, HTML" and more.

Leave a Reply

Your email address will not be published. Required fields are marked *