Internationalization In ASP.NET Core MVC

Introduction
 
Internationalization describes both, Globalization and Localization.
 
ASP.NET Core
 
Globalization is a process of supporting different cultures. Globalization allows us to create multilingual web sites with ASP.NET Core. It adds support the display of input and output of a defined language that is related to specific geographic areas. It involves identifying the culture and writing code that functions equally to any supported culture.
 
We can have an application which supports multiple cultures and culture preferences can be selected from different sources such a browser's preferred language. Our application displays the data in the selected language.
 
The localization process includes translating the UI of the application for the specific culture. In other term, it is the process of adapting a globalized application that we already processed for localization for specific cultures.
 
To describe how to do globalization and localization with ASP.NET Core MVC, I have created a Web application.
 
The next step is to add localization support in startup class. In the ConfigureSevices method of startup class, I have added localization options such as path of resource files. In this method, I have also configured requested localization option. In this option, I have added supported cultures and default culture for request.
 
Startup.cs
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     // Add framework services.  
  4.     services.AddApplicationInsightsTelemetry(Configuration);  
  5.     services.AddLocalization(options => options.ResourcesPath = "Resources");  
  6.   
  7.     services.Configure<RequestLocalizationOptions>(options =>  
  8.     {  
  9.         var supportedCultures = new[]  
  10.         {  
  11.                 new CultureInfo("en-US"),  
  12.                 new CultureInfo("de-DE")  
  13.         };  
  14.   
  15.         options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");  
  16.         options.SupportedCultures = supportedCultures;  
  17.         options.SupportedUICultures = supportedCultures;  
  18.     });  
  19.   
  20.     services.AddMvc();  
  21. }  
  22.   
  23. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  24. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
  25. {  
  26.     loggerFactory.AddConsole(Configuration.GetSection("Logging"));  
  27.     loggerFactory.AddDebug();  
  28.   
  29.     var localizationOption = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();  
  30.     app.UseRequestLocalization(localizationOption.Value);  
  31.   
  32.     if (env.IsDevelopment())  
  33.     {  
  34.         app.UseDeveloperExceptionPage();  
  35.         app.UseBrowserLink();  
  36.     }  
  37.     else  
  38.     {  
  39.         app.UseExceptionHandler("/Home/Error");  
  40.     }  
  41.   
  42.     app.UseStaticFiles();  
  43.   
  44.     app.UseMvc(routes =>  
  45.     {  
  46.         routes.MapRoute(  
  47.             name: "default",  
  48.             template: "{controller=Home}/{action=Index}/{id?}");  
  49.     });  
  50. }  
The next step is to add resource folder and resource file in it. For demo purposes, I have added two resource files: one for English and another one for German.
 
ASP.NET Core
 
This resources folder must contain the resources file in specific folder structure or file name otherwise it will not work as expected. ASP.NET Core introduces two interfaces: IStringLocalizer and IStringLocalizer<T>. These interfaces help us to improve productivity when developing localized application. The IStringLocalizer interface use Resource manager and resource reader to provide culture specific resources at run-time. It has IEnumerable and indexer to return the specific string.
 
The IStringLocalizer<T> implementation comes from Dependency Injection (DI) where T is our controller class Type. The indexer of this interface returns key if it is not found in resource file or resource file is not found. Our controller code look like as following.
 
HomeController.cs
  1. public class HomeController : Controller  
  2. {  
  3.     private readonly IStringLocalizer<HomeController> _localizer;  
  4.     public HomeController(IStringLocalizer<HomeController> localizer)  
  5.     {  
  6.         _localizer = localizer;  
  7.     }  
  8.     public IActionResult Index()  
  9.     {  
  10.         ViewData["Message"] = _localizer["Test String"];  
  11.         return View();  
  12.     }  
  13. }  
Here To verify resource string, I am displaying it on view by using following code.
 
Index.cshtml
  1. <div class="col-md-11">  
  2.     <h2>Localization Test</h2>  
  3.     <br />  
  4.     <br />  
  5.     <h3>@ViewData["Message"]</h3>  
  6. </div>  
The IStringLocalizer<T> will find the provided string based on the current culture and controller type i.e. HomeController in this case. Currently, we are using en-US culture, then the localizer will look for the either of files: Resources/ Controllers.HomeController.en-US.resx or Resources/ Controllers/HomeController.en-US.resx. If the file exists, localizer tries to find the key from the resource file. If key is not found from resource file key itself will be used as the resource and return.
 
ASP.NET Core
 
Now I am running my application. But as a result, it show me resource key instead of resource value.
 
Output 
 
ASP.NET Core
 
To resolve this issue, we need to add reference of "Localization.AspNetCore.TagHelpers" in our project. This tag helper helps us to make working with localized content. We can add the reference either by using nuget package or we can add reference in .csproj file and download referent using "dotnet restore" command.
 
.csproj file
  1. <ItemGroup>  
  2.     <PackageReference Include="Localization.AspNetCore.TagHelpers" Version="0.3.0" />  
  3. …  
  4. …  
  5. </ItemGroup>  
Now I am re-running the application and found it working as expected.
 
Output 

ASP.NET Core
 
Now I am adding a new resource file for German language (de-DE) and adding the same key as the English resource file. To test application on German language, I am changing the language from browser and re-run my application.
 
ASP.NET Core
 
Change the language from the browser in windows 10 

ASP.NET Core 
 
Output 

ASP.NET Core
 
View localization
 
The interface IViewLocalizer service provides localized string for view. A ViewLocalizer class implements this interface and finds the resource location from the view file path. The behavior of this localizer is the same as controller localizer described above i.e. if key is not find in resource file, it's return key.
 
To demonstrate the concept, I have created view and resources file. The IViewLocalizer use the name of the view file to find the associated resources. For this example, I have added view resource for about.cshtml view, so the resource file will look like Resources/Views.Home.About.en-US.resx or Resources/Views/Home/About.en-US.resx. This interface is used in a similar way as IStringLocalizer<T> is used to find resource key.

ASP.NET Core
 
About.cshtml
  1. @using Microsoft.AspNetCore.Mvc.Localization  
  2.   
  3. @inject IViewLocalizer Localizer  
  4.   
  5. <h3>@Localizer["HelloFriends"]</h3>  
When I run this page, it throws the error and says “InvalidOperationException: No service for type 'Microsoft.AspNetCore.Mvc.Localization.IViewLocalizer' has been registered.”
 
 
ASP.NET Core
 
This error is due to the fact that we have not registered the service for view localization. To resolve the error, we need to register “AddViewLocalization” service in ConfigureServices method of startup class.
 
Startup.cs 
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3. …  
  4. …  
  5.     services.AddMvc().AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);  
  6. …  
  7. …  
  8. }  
ASP.NET Core
 
DataAnnotations localization
 
DataAnnotation error messages are localized with the interface IStringLocalizer<T>. We can set resource file path using Option.ResourcePath property and error messages can be stored either paths Resources/ViewModels.Home.MyViewModel.en-US.resx or Resources/ViewModels/Home/MyViewModel.en-US.resx
 
We need to register data annotation localization service in ConfigureServices method of Startup class.
 
Startup.cs
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3. …  
  4. …  
  5.   
  6. services.AddMvc()  
  7.        .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)  
  8.        .AddDataAnnotationsLocalization();  
  9. …  
  10. …  
  11. }  
In this example, I have created a model and defined one property and property decorated with “Required” and “DisplayName” data annotation attribute. With Required attribute, I am passing resource key instead of the error message and same as for DisplayName attribute.
 
MyViewModel.cs
  1. using System.ComponentModel.DataAnnotations;  
  2.   
  3. namespace demoApp.ViewModels.Home  
  4. {  
  5.     public class MyViewModel  
  6.     {  
  7.         [Required(ErrorMessage = "RequiredName")]  
  8.         [Display(Name = "Name")]  
  9.         public string Name { getset; }  
  10.     }  
  11. }  
Contact.cshtml
  1. @model demoApp.ViewModels.Home.MyViewModel  
  2. @using Microsoft.AspNetCore.Mvc.Localization  
  3.   
  4. <form action="/Home/Save" method="post">  
  5.     @Html.LabelFor(model => model.Name)  
  6.     <br />  
  7.     @Html.EditorFor(model => model.Name)  
  8.     @Html.ValidationMessageFor(model => model.Name)  
  9.   
  10.     <br />  
  11.     <br />  
  12.     <input type="submit" value="Save" class="btn btn-default" />  
  13. </form>  
Resource file for English language

ASP.NET Core
 
Ouput 

ASP.NET Core

Resource file naming
 
Resource files are named for full type of their class except assembly name. For example English resource in our project which main assembly is "demoApp.dll" and if we are creating for class "demoApp.Controllers.HomeController" then the resource file name become "Controllers.HomeController.en-Us.resx". In the ConfigureServices method we need to set resource file path. If we did not set the ResourcePath property, System will looks .resx files in project base directory.
 
Alternatively we can use folders to organize resource files. For the above explain example, the path would be "Resources/Controllers/HomeController.en-US.resx".
 
Resource file name for Razor views follow very similar pattern. It is either dot naming or path naming. Razor view resource files has similar path of their associated view. For example English resource file name for view Views/Home/Index.cshtml is either Resources/Views/Home/Index.resx or Resources/Views.Ho,e.Index.resx. If we are not defining the ResourcePath property, resource file would be located in same folder as view.
 
Summary
 
Internationalization involves Localization and Globalization. Both are critical aspects of many web applications.
 
Internationalization is abbreviated to "I18N". The abbreviation takes first and last character and number of characters between them. Here there are 18 characters between "I" and "N" character. Same things is applied to Localization (L10N) and Globalization (G11N).
  • Globalization (G11N) - It is the process of making an application support different languages and regions.
  • Localization (L10N) - The process of customizing an application for a given language and region.
  • Internationalization (I18N) - Describes both globalization and localization.