Localization In Blazor App Using Microsoft.JSInterop

In this article, we will see how to achieve localization in Blazor app using Microsoft.JSInterop API.

We have already seen the basic features of a Blazor application in my previous articles on C# Corner. Please refer to the below articles to get some idea of the Blazor framework.
Blazor is an experimental .NET web framework using C#/Razor and HTML that runs in the browser with WebAssembly. Blazor provides the benefits of a client-side web UI framework using .NET.
 
In this article, we will see the localization with Microsoft.JSInterop API.
 
Please open Visual Studio 2017 (I am using a free community edition) and create a Blazor app. Choose an ASP.NET Core Web Application project template.
 
Localization in Blazor App using Microsoft.JSInterop 
 
We can choose Blazor (ASP.NET Core hosted) template.
 
Localization in Blazor App using Microsoft.JSInterop

Our solution will be ready in a moment. Please note that there are three projects created in our solution - “Client”, “Server”, and “Shared”.

The client project contains all the client-side libraries and Razor Views, while the server project contains the Web API Controller and other business logic. The shared project contains the commonly shared files, like models and interfaces.

Localization in Blazor App using Microsoft.JSInterop

By default, Blazor creates many files in these three projects. We can remove all the unwanted files like “Counter.cshtml”, “FetchData.cshtml”, “SurveyPrompt.cshtml” from the Client project and “SampleDataController.cs” file from the Server project.

In this app, we do not require a Shared project as well. We can remove that from our solution.

In the Server project, please create a “Resources” folder to keep your resource files inside this folder. These resource files will be used for localization later.
 
We can create our first locale resource for “en” (English) locale.
 
Localization in Blazor App using Microsoft.JSInterop
 
We can add “Name” and “Value” inside this resource file. I will add 5 key-value pairs in this resource file.
 
Localization in Blazor App using Microsoft.JSInterop

We can create one more resource file for “hi” (Hindi) locale now.

I must add 5 key-value pairs for this resource file also.

Localization in Blazor App using Microsoft.JSInterop

We can create a new folder “Extensions” and create a new class “CsrfTokenCookieMiddleware” inside that folder.

Please copy the below code and paste to the new class.
 
CsrfTokenCookieMiddleware.cs
  1. using Microsoft.AspNetCore.Antiforgery;  
  2. using Microsoft.AspNetCore.Http;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace BlazorLocalization.Server.Extensions  
  6. {  
  7.     public class CsrfTokenCookieMiddleware  
  8.     {  
  9.         private readonly IAntiforgery _antiforgery;  
  10.         private readonly RequestDelegate _next;  
  11.   
  12.         public CsrfTokenCookieMiddleware(IAntiforgery antiforgery, RequestDelegate next)  
  13.         {  
  14.             _antiforgery = antiforgery;  
  15.             _next = next;  
  16.         }  
  17.   
  18.         public async Task InvokeAsync(HttpContext context)  
  19.         {  
  20.             if (context.Request.Cookies["CSRF-TOKEN"] == null)  
  21.             {  
  22.                 var token = _antiforgery.GetAndStoreTokens(context);  
  23.                 context.Response.Cookies.Append("CSRF-TOKEN", token.RequestToken, new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });  
  24.             }  
  25.             await _next(context);  
  26.         }  
  27.     }  
  28. }  

We can modify Startup.cs file with below changes.

Startup.cs
  1. using BlazorLocalization.Server.Extensions;  
  2. using Microsoft.AspNetCore.Blazor.Server;  
  3. using Microsoft.AspNetCore.Builder;  
  4. using Microsoft.AspNetCore.Hosting;  
  5. using Microsoft.AspNetCore.Localization;  
  6. using Microsoft.AspNetCore.ResponseCompression;  
  7. using Microsoft.Extensions.DependencyInjection;  
  8. using System.Globalization;  
  9. using System.Linq;  
  10. using System.Net.Mime;  
  11.   
  12. namespace BlazorLocalization.Server  
  13. {  
  14.   
  15.     public class Startup  
  16.     {  
  17.         public void ConfigureServices(IServiceCollection services)  
  18.         {  
  19.             services.AddMvc();  
  20.   
  21.             services.AddResponseCompression(options =>  
  22.             {  
  23.                 options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]  
  24.                 {  
  25.                     MediaTypeNames.Application.Octet,  
  26.                     WasmMediaTypeNames.Application.Wasm,  
  27.                 });  
  28.             });  
  29.   
  30.             services.AddLocalization(options => options.ResourcesPath = "Resources");  
  31.         }  
  32.   
  33.         public void Configure(IApplicationBuilder app, IHostingEnvironment env)  
  34.         {  
  35.             app.UseResponseCompression();  
  36.   
  37.             if (env.IsDevelopment())  
  38.             {  
  39.                 app.UseDeveloperExceptionPage();  
  40.             }  
  41.   
  42.             var supportedCultures = new[]  
  43.             {  
  44.                 new CultureInfo("en"),  
  45.                 new CultureInfo("hi")  
  46.             };  
  47.   
  48.             app.UseRequestLocalization(new RequestLocalizationOptions  
  49.             {  
  50.                 DefaultRequestCulture = new RequestCulture("en"),  
  51.                 // Formatting numbers, dates, etc.  
  52.                 SupportedCultures = supportedCultures,  
  53.                 // UI strings that we have localized.  
  54.                 SupportedUICultures = supportedCultures  
  55.             });  
  56.   
  57.             app.UseStaticFiles();  
  58.   
  59.             app.UseMvc(routes =>  
  60.             {  
  61.                 routes.MapRoute(name: "default", template: "{controller}/{action}/{id?}");  
  62.             });  
  63.   
  64.             app.UseMiddleware<CsrfTokenCookieMiddleware>();  
  65.             app.UseBlazor<Client.Program>();  
  66.         }  
  67.     }  
  68. }  

We have added “CultureInfo” for our two locale files. We also invoked “CsrfTokenCookieMiddleware” class in Startup file.

We can create a Web API controller now. This API will be used for localization.

Please copy below code and paste in Controller file.
 
I18nController.cs
  1. using Microsoft.AspNetCore.Mvc;  
  2. using Microsoft.Extensions.Localization;  
  3. using System.Collections.Generic;  
  4. using System.Linq;  
  5.   
  6. namespace BlazorLocalization.Server  
  7. {  
  8.     // This class is used for naming the Resource files.   
  9.     // You must specify exact name of Resource here.   
  10.     public class BlazorResource { }  
  11. }  
  12.   
  13. namespace BlazorLocalization.Server.Controllers  
  14. {  
  15.     [Route("api/[controller]/")]  
  16.     public class I18nController : Controller  
  17.     {  
  18.         private IStringLocalizer<BlazorResource> stringLocalizer;  
  19.   
  20.         public I18nController(IStringLocalizer<BlazorResource> stringLocalizer)  
  21.         {  
  22.             this.stringLocalizer = stringLocalizer;  
  23.         }  
  24.   
  25.         [HttpGet]  
  26.         public ActionResult GetClientTranslations()  
  27.         {  
  28.             var res = new Dictionary<stringstring>();  
  29.             return Ok(stringLocalizer.GetAllStrings().ToDictionary(s => s.Name, s => s.Value));  
  30.         }  
  31.     }  
  32. }  

We can add a class for our Resource files now. This class is used for naming the Resource files. Please give the same name to the class as our Resource file.

We must create this class inside the Web API Controller file. This is an empty class. We have completed the coding part in the Server project. Now, we can add files to the Client project.
 
Add a new “Services” folder. We will add some interfaces and classes inside this folder. Please add “JsInterop” class.

JsInterop.cs

  1. using Microsoft.JSInterop;  
  2. using System.Threading.Tasks;  
  3.   
  4. namespace BlazorLocalization.Client.Services  
  5. {  
  6.     public static class JsInterop  
  7.     {  
  8.   
  9.         public static async Task<string[]> Languages()  
  10.         {  
  11.             return await JSRuntime.Current.InvokeAsync<string[]>("navigatorLanguages");  
  12.         }  
  13.   
  14.         public static async Task<string> GetCookie()  
  15.         {  
  16.             return await JSRuntime.Current.InvokeAsync<string>("getDocumentCookie");  
  17.         }  
  18.   
  19.     }  
  20. }  

We will call two JavaScript methods “navigatorLanguages” and “getDocumentCookie” from this class. The JavaScript file will be added later.

Please add the below 4 interfaces and 4 classes inside Services folder.
 
IBrowserCookieService.cs
  1. using System;  
  2. using System.Threading.Tasks;  
  3.   
  4. namespace BlazorLocalization.Client.Services  
  5. {  
  6.     public interface IBrowserCookieService  
  7.     {  
  8.         Task<string> Get(Func<stringbool> filterCookie);  
  9.     }  
  10. }  
BrowserCookieService.cs
  1. using System;  
  2. using System.Linq;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace BlazorLocalization.Client.Services  
  6. {  
  7.     public class BrowserCookieService : IBrowserCookieService  
  8.     {  
  9.         public async Task<string> Get(Func<stringbool> filterCookie)  
  10.         {  
  11.             return (await JsInterop  
  12.                 .GetCookie())  
  13.                 .Split(';')  
  14.                 .Select(v => v.TrimStart().Split('='))  
  15.                 .Where(s => filterCookie(s[0]))  
  16.                 .Select(s => s[1])  
  17.                 .FirstOrDefault();  
  18.         }  
  19.     }  
  20. }  

IHttpApiClientRequestBuilder.cs

  1. using System;  
  2. using System.Threading.Tasks;  
  3.   
  4. namespace BlazorLocalization.Client.Services  
  5. {  
  6.     public interface IHttpApiClientRequestBuilder  
  7.     {  
  8.         Task GetAsync();  
  9.         HttpApiClientRequestBuilder OnOK<T>(Action<T> todo);  
  10.         void SetHeader(string v, string lg);  
  11.     }  
  12. }  

HttpApiClientRequestBuilder.cs

  1. using Microsoft.JSInterop;  
  2. using System;  
  3. using System.Net.Http;  
  4. using System.Threading.Tasks;  
  5.   
  6. namespace BlazorLocalization.Client.Services  
  7. {  
  8.     public class HttpApiClientRequestBuilder : IHttpApiClientRequestBuilder  
  9.     {  
  10.         private readonly string _uri;  
  11.         private HttpClient _httpClient;  
  12.         private Func<HttpResponseMessage, Task> _onOK;  
  13.         private IBrowserCookieService browserCookieService;  
  14.   
  15.         public HttpApiClientRequestBuilder(HttpClient httpClient, string uri, IBrowserCookieService browserCookieService)  
  16.         {  
  17.             _uri = uri;  
  18.             _httpClient = httpClient;  
  19.             this.browserCookieService = browserCookieService;  
  20.         }  
  21.   
  22.         public async Task GetAsync()  
  23.         {  
  24.             await ExecuteHttpQueryAsync(async () => await _httpClient.SendAsync(await PrepareMessageAsync(new HttpRequestMessage(HttpMethod.Get, _uri))));  
  25.         }  
  26.   
  27.         public HttpApiClientRequestBuilder OnOK<T>(Action<T> todo)  
  28.         {  
  29.             _onOK = async (HttpResponseMessage r) =>  
  30.             {  
  31.                 var response = Json.Deserialize<T>(await r.Content.ReadAsStringAsync());  
  32.                 todo(response);  
  33.             };  
  34.             return this;  
  35.         }  
  36.         public void SetHeader(string key, string value)  
  37.         {  
  38.             _httpClient.DefaultRequestHeaders.Add(key, value);  
  39.         }  
  40.   
  41.         private async Task HandleHttpResponseAsync(HttpResponseMessage response)  
  42.         {  
  43.             switch (response.StatusCode)  
  44.             {  
  45.                 case System.Net.HttpStatusCode.OK:  
  46.                     if (_onOK != null)  
  47.                         await _onOK(response);  
  48.                     break;  
  49.                 case System.Net.HttpStatusCode.BadRequest:  
  50.                     break;  
  51.                 case System.Net.HttpStatusCode.InternalServerError:  
  52.                     break;  
  53.             }  
  54.         }  
  55.         private async Task<HttpRequestMessage> PrepareMessageAsync(HttpRequestMessage httpRequestMessage)  
  56.         {  
  57.             string csrfCookieValue = await browserCookieService.Get(c => c.Equals("CSRF-TOKEN"));  
  58.             if (csrfCookieValue != null)  
  59.                 httpRequestMessage.Headers.Add("X-CSRF-TOKEN", csrfCookieValue);  
  60.             return httpRequestMessage;  
  61.         }  
  62.   
  63.         private async Task ExecuteHttpQueryAsync(Func<Task<HttpResponseMessage>> httpCall)  
  64.         {  
  65.             try  
  66.             {  
  67.                 var response = await httpCall();  
  68.                 await HandleHttpResponseAsync(response);  
  69.             }  
  70.             catch  
  71.             {  
  72.                 throw;  
  73.             }  
  74.             finally  
  75.             {  
  76.             }  
  77.         }  
  78.     }  
  79. }  

IHttpApiClientRequestBuilderFactory.cs

  1. namespace BlazorLocalization.Client.Services  
  2. {  
  3.     public interface IHttpApiClientRequestBuilderFactory  
  4.     {  
  5.         IHttpApiClientRequestBuilder Create(string url);  
  6.     }  
  7. }  

HttpApiClientRequestBuilderFactory.cs

  1. using System.Net.Http;  
  2.   
  3. namespace BlazorLocalization.Client.Services  
  4. {  
  5.     public class HttpApiClientRequestBuilderFactory : IHttpApiClientRequestBuilderFactory  
  6.     {  
  7.         private readonly HttpClient _httpClient;  
  8.         private readonly IBrowserCookieService browserCookieService;  
  9.         public HttpApiClientRequestBuilderFactory(HttpClient httpClient, IBrowserCookieService browserCookieService)  
  10.         {  
  11.             _httpClient = httpClient;  
  12.             this.browserCookieService = browserCookieService;  
  13.   
  14.         }  
  15.         public IHttpApiClientRequestBuilder Create(string url)  
  16.         {  
  17.             return new HttpApiClientRequestBuilder(_httpClient, url, browserCookieService);  
  18.         }  
  19.     }  
  20. }  

II18nService.cs

  1. using System.Threading.Tasks;  
  2.   
  3. namespace BlazorLocalization.Client.Services  
  4. {  
  5.     public interface II18nService  
  6.     {  
  7.         Task<string> Get(string name);  
  8.   
  9.         void Init(string lg);  
  10.     }  
  11. }  

I18nService.cs

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace BlazorLocalization.Client.Services  
  6. {  
  7.     public class I18nService : II18nService  
  8.     {  
  9.         private readonly IHttpApiClientRequestBuilderFactory httpApiClientRequestBuilderFactory;  
  10.         private Lazy<Task<Dictionary<stringstring>>> translations;  
  11.   
  12.         public I18nService(IHttpApiClientRequestBuilderFactory httpApiClientRequestBuilderFactory)  
  13.         {  
  14.             this.httpApiClientRequestBuilderFactory = httpApiClientRequestBuilderFactory;  
  15.             translations = new Lazy<Task<Dictionary<stringstring>>>(() => FetchTranslations(null));  
  16.         }  
  17.   
  18.         private async Task<Dictionary<stringstring>> FetchTranslations(string lg)  
  19.         {  
  20.             var client = httpApiClientRequestBuilderFactory.Create("/api/i18n");  
  21.             if (lg != null)  
  22.                 client.SetHeader("accept-language", lg);  
  23.   
  24.             Dictionary<stringstring> res = null;  
  25.             await client.OnOK<Dictionary<stringstring>>(r => res = r).GetAsync();  
  26.             return res;  
  27.         }  
  28.   
  29.         public async Task<string> Get(string key)  
  30.         {  
  31.             return !(await translations.Value).TryGetValue(key, out string value) ? key : value;  
  32.         }  
  33.   
  34.         public void Init(string lg)  
  35.         {  
  36.             translations = new Lazy<Task<Dictionary<stringstring>>>(() => FetchTranslations(lg));  
  37.         }  
  38.     }  
  39. }  

We can add “Locale.cshtml” inside the “Shared” folder. This Razor View will be used for displaying the values from Resource file using I18nService. This Razor View acts as a user-defined control/child view.

Locale.cshtml
  1. @using BlazorLocalization.Client.Services  
  2.   
  3. @inject II18nService i18n;  
  4.   
  5. @displayValue  
  6.   
  7. @functions{  
  8.     [Parameter]  
  9.     string key { getset; }  
  10.   
  11.     public string displayValue { getset; }  
  12.     protected override async Task OnInitAsync()  
  13.     {  
  14.         await i18n.Get(key).ContinueWith(t =>  
  15.         {  
  16.             displayValue = t.Result;  
  17.             this.StateHasChanged();  
  18.         });  
  19.     }  
  20.   
  21. }  

We can modify the “NavMenu.cshtml” file with the below code.

NavMenu.cshtml
  1. <div class="top-row pl-4 navbar navbar-dark">  
  2.     <a class="navbar-brand" href=""><Locale key="Title" /></a>  
  3.     <button class="navbar-toggler" onclick=@ToggleNavMenu>  
  4.         <span class="navbar-toggler-icon"></span>  
  5.     </button>  
  6. </div>  
  7.   
  8. <div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>  
  9.     <ul class="nav flex-column">  
  10.         <li class="nav-item px-3">  
  11.             <NavLink class="nav-link" href="" Match=NavLinkMatch.All>  
  12.                 <span class="oi oi-home" aria-hidden="true"></span> <Locale key="HomeMenu" />  
  13.             </NavLink>  
  14.         </li>  
  15.     </ul>  
  16. </div>  
  17.   
  18. @functions {  
  19. bool collapseNavMenu = true;  
  20.   
  21. void ToggleNavMenu()  
  22. {  
  23.     collapseNavMenu = !collapseNavMenu;  
  24. }  
  25. }  

We have used our Local Child View inside this View.

We can add “localization.js” file inside the “wwwroot” folder.
 
localization.js
  1. getDocumentCookie = function () {  
  2.     return Promise.resolve(document.cookie);  
  3. };  
  4.   
  5. navigatorLanguages = function () {  
  6.     return Promise.resolve(navigator.languages);  
  7. };  

Here, we have added two JavaScript methods inside this file.

Modify the index.html under the wwwroot folder.
 
index.html (wwwroot folder)
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <meta charset="utf-8" />  
  5.     <meta name="viewport" content="width=device-width">  
  6.     <title>Blazor Localization</title>  
  7.     <base href="/" />  
  8.     <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />  
  9.     <link href="css/site.css" rel="stylesheet" />  
  10. </head>  
  11. <body>  
  12.     <app>Loading...</app>  
  13.   
  14.     <script src="/localization.js"></script>  
  15.     <script src="_framework/blazor.webassembly.js"></script>  
  16. </body>  
  17. </html>  

I have added the script path of localization.js file in this HTML file.

We can now modify index.html under Pages folder.

index.html (Pages folder)
  1. @page "/"  
  2.   
  3. <h1><Locale key="Welcome" /></h1>  
  4. <hr />  
  5. <p><Locale key="About" /> </p>  
  6. <hr />  
  7. <p>Localization in Blazor By : <b><Locale key="Developer" /></b></p>  

Please remove the Starup.cs file from Client project. We do not require it. We can modify “Program.cs” file with below code.

Program.cs
  1. using BlazorLocalization.Client.Services;  
  2. using Microsoft.AspNetCore.Blazor.Browser.Rendering;  
  3. using Microsoft.AspNetCore.Blazor.Browser.Services;  
  4. using Microsoft.Extensions.DependencyInjection;  
  5. using Microsoft.JSInterop;  
  6. using System.Globalization;  
  7. using System.Linq;  
  8.   
  9. namespace BlazorLocalization.Client  
  10. {  
  11.     public class Program  
  12.     {  
  13.         public static BrowserServiceProvider serviceProvider;  
  14.         public static void Main(string[] args)  
  15.         {  
  16.             serviceProvider = new BrowserServiceProvider(configure =>  
  17.             {  
  18.                 configure.Add(new ServiceDescriptor(  
  19.                     typeof(IHttpApiClientRequestBuilderFactory),  
  20.                     typeof(HttpApiClientRequestBuilderFactory),  
  21.                     ServiceLifetime.Scoped));  
  22.                 configure.Add(new ServiceDescriptor(  
  23.                      typeof(IBrowserCookieService),  
  24.                      typeof(BrowserCookieService),  
  25.                      ServiceLifetime.Singleton));  
  26.                 configure.Add(new ServiceDescriptor(  
  27.                     typeof(II18nService),  
  28.                     typeof(I18nService),  
  29.                     ServiceLifetime.Singleton));  
  30.             });  
  31.   
  32.             JSRuntime.Current.InvokeAsync<string[]>("navigatorLanguages")  
  33.                .ContinueWith(t => CultureInfo.DefaultThreadCurrentCulture = t.Result.Select(c => CultureInfo.GetCultureInfo(c)).FirstOrDefault())  
  34.                .ContinueWith(t => new BrowserRenderer(serviceProvider).AddComponent<App>("app"));  
  35.         }  
  36.     }  
  37. }  

We have injected all the dependencies for our services inside this file.

We have completed all the coding part. We can run the application now.

Our application will display all the text values in English locale ( the default language of my browser is English).

Localization in Blazor App using Microsoft.JSInterop 

Now, we can change the browser language settings to Hindi.

Please refresh the screen. It will automatically change the display text to Hindi. It takes the value according to the Hindi resource file.

Localization in Blazor App using Microsoft.JSInterop 
 
We can add one more resource file. This time I will add Malayalam resource. I will again add 5 key-value pair values for Malayalam locale.
 
Localization in Blazor App using Microsoft.JSInterop
 
We can add these locale settings to Startup.cs file also.
 
Localization in Blazor App using Microsoft.JSInterop
 
We can again run the application and change the browser language to Malayalam. It will display all the text values in the Malayalam language.
 
Localization in Blazor App using Microsoft.JSInterop 

In this article, we saw localization in Blazor with the help of Microsoft.JSInterop API.

We can see more Blazor features in upcoming articles.