Blazor Localization
Registration in Program.cs
- Nuget install Microsoft.Extensions.Localization
- In _Imports.razor you probably need @using Microsoft.Extensions.Localization
- In csproj add <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
- In Program.cs add the service builder.Services.AddLocalization();
- Store and load the culture in localStorage and set in program.cs (between host.build() and host.RunAsync())
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.JSInterop;
using System.Globalization;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
//add localization support
builder.Services.AddLocalization();
var webAssemblyHost = builder.Build();
//look in localStorage, otherwise use a default
var js = webAssemblyHost.Services.GetRequiredService<IJSRuntime>();
var currentLanguage = await js.InvokeAsync<string>("localStorage.getItem", "currentLanguage") ?? "en-GB";
//always set culture + ui culture to the same thing
CultureInfo cultureInfo;
try
{
cultureInfo = new CultureInfo(currentLanguage);
}
catch (CultureNotFoundException )
{
//protect against poison values
currentLanguage = "en-GB";
cultureInfo = new CultureInfo(currentLanguage);
await js.InvokeVoidAsync("localStorage.setItem", "currentLanguage", currentLanguage);
}
CultureInfo.DefaultThreadCurrentCulture =
CultureInfo.DefaultThreadCurrentUICulture =
cultureInfo;
await webAssemblyHost.RunAsync();
using Microsoft.JSInterop;
using System.Globalization;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
//add localization support
builder.Services.AddLocalization();
var webAssemblyHost = builder.Build();
//look in localStorage, otherwise use a default
var js = webAssemblyHost.Services.GetRequiredService<IJSRuntime>();
var currentLanguage = await js.InvokeAsync<string>("localStorage.getItem", "currentLanguage") ?? "en-GB";
//always set culture + ui culture to the same thing
CultureInfo cultureInfo;
try
{
cultureInfo = new CultureInfo(currentLanguage);
}
catch (CultureNotFoundException )
{
//protect against poison values
currentLanguage = "en-GB";
cultureInfo = new CultureInfo(currentLanguage);
await js.InvokeVoidAsync("localStorage.setItem", "currentLanguage", currentLanguage);
}
CultureInfo.DefaultThreadCurrentCulture =
CultureInfo.DefaultThreadCurrentUICulture =
cultureInfo;
await webAssemblyHost.RunAsync();
Language Picker component
In your language buttons, use NavigateTo with forceLoad to reload the culture
@using System.Globalization
@inject IJSRuntime JsRuntime
@inject NavigationManager Navigation
@inject IStringLocalizer<PageResources> Loc
@if (!_refreshing)
{
<form action="#">
<fieldset>
<legend>@Loc["Language"] @_currentLanguage @DateTime.Now.ToString("f")</legend>
<button @onclick="SetFrench">@Loc["French"]</button>
<button @onclick="SetEnglish">@Loc["English"]</button>
</fieldset>
</form>
}
else
{
<p>Page will be refreshed</p>
}
@code {
private string _currentLanguage;
private bool _refreshing;
private async Task SetFrench()
{
_refreshing = true;
await JsRuntime.InvokeVoidAsync("localStorage.setItem", "currentLanguage", "fr-Fr");
Navigation.Refresh(true);
}
private async Task SetEnglish()
{
_refreshing = true;
await JsRuntime.InvokeVoidAsync("localStorage.setItem", "currentLanguage", "en-GB");
Navigation.Refresh(true);
}
protected async override Task OnInitializedAsync()
{
_currentLanguage = await JsRuntime.InvokeAsync<string>("localStorage.getItem", "currentLanguage");
if (string.IsNullOrEmpty(_currentLanguage))
{
_currentLanguage = CultureInfo.DefaultThreadCurrentCulture.Name;
}
}
}
@inject IJSRuntime JsRuntime
@inject NavigationManager Navigation
@inject IStringLocalizer<PageResources> Loc
@if (!_refreshing)
{
<form action="#">
<fieldset>
<legend>@Loc["Language"] @_currentLanguage @DateTime.Now.ToString("f")</legend>
<button @onclick="SetFrench">@Loc["French"]</button>
<button @onclick="SetEnglish">@Loc["English"]</button>
</fieldset>
</form>
}
else
{
<p>Page will be refreshed</p>
}
@code {
private string _currentLanguage;
private bool _refreshing;
private async Task SetFrench()
{
_refreshing = true;
await JsRuntime.InvokeVoidAsync("localStorage.setItem", "currentLanguage", "fr-Fr");
Navigation.Refresh(true);
}
private async Task SetEnglish()
{
_refreshing = true;
await JsRuntime.InvokeVoidAsync("localStorage.setItem", "currentLanguage", "en-GB");
Navigation.Refresh(true);
}
protected async override Task OnInitializedAsync()
{
_currentLanguage = await JsRuntime.InvokeAsync<string>("localStorage.getItem", "currentLanguage");
if (string.IsNullOrEmpty(_currentLanguage))
{
_currentLanguage = CultureInfo.DefaultThreadCurrentCulture.Name;
}
}
}
NB: in the .net8 interactive components, ensure this is @rendermode="new InteractiveWebAssemblyRenderMode(prerender: false)"
Component Resources
- As in standard .net, you can add .resx files as embedded resources.
- @inject IStringLocalizer<PageResources> Loc
@Loc["Language"] - In WASM, you must create a dummy empty file with the same name, so the StringLocalizer can find it.
- OR you can still use the resources codegen (eg @Resources.MyString). Because the file name exists, it works with IStringLocalizer
- And you can still use traditional .net strongly typed resources. Which frankly are easier and better.