🟥 Not applicable to Blazor WebAssembly
Instant translation offers several advantages over deferred translation. Firstly, it allows your website to be translated into different languages without requiring a page reload, providing a seamless user experience. Additionally, instant translation is more flexible when it comes to working with resource files, as it can work with a variety of formats such as JSON and YML, whereas deferred translation is limited to using only RESX resources. In this tutorial, we will use RESX resources in the example, but you can use other formats as needed. We will cover the following topics:
You can download the example code used in this topic on GitHub.
The instant translation feature consists of two main classes:
public class BlazorSchoolLanguageNotifier { private readonly List<ComponentBase> _subscribedComponents = new(); public void SubscribeLanguageChange(ComponentBase component) => _subscribedComponents.Add(component); public void UnsubscribeLanguageChange(ComponentBase component) => _subscribedComponents.Remove(component); private void NotifyLanguageChange() { foreach (var component in _subscribedComponents) { if (component is not null) { var stateHasChangedMethod = component.GetType()?.GetMethod("StateHasChanged", BindingFlags.Instance | BindingFlags.NonPublic); _ = (stateHasChangedMethod?.Invoke(component, null)); } } } }
public class BlazorSchoolLanguageNotifier { ... private CultureInfo _currentCulture; public CultureInfo CurrentCulture { get => _currentCulture; set { if (string.IsNullOrWhiteSpace(value.Name)) { _currentCulture = CultureInfo.CurrentCulture; NotifyLanguageChange(); } else { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); if (allCultures.Contains(value)) { _currentCulture = value; NotifyLanguageChange(); } } } } public BlazorSchoolLanguageNotifier(IOptions<RequestLocalizationOptions> options) { _currentCulture = options.Value.DefaultRequestCulture.Culture; } }
builder.Services.AddScoped<BlazorSchoolLanguageNotifier>();
IStringLocalizer<T>
interface to provide the translated content. For example:public class BlazorSchoolStringLocalizer<TComponent> : IStringLocalizer<TComponent> { public LocalizedString this[string name] => FindLocalziedString(name); public LocalizedString this[string name, params object[] arguments] => FindLocalziedString(name, arguments); public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) { // Blazor Server will not use this method so you can leave it empty } }
IOptions<LocalizationOptions>
to access the resource location and the language notifier class to determine the current language. For example:public class BlazorSchoolStringLocalizer<TComponent> : IStringLocalizer<TComponent> { ... private readonly IOptions<LocalizationOptions> _localizationOptions; private readonly BlazorSchoolLanguageNotifier _blazorSchoolLanguageNotifier; public BlazorSchoolStringLocalizer(IOptions<LocalizationOptions> localizationOptions, BlazorSchoolLanguageNotifier blazorSchoolLanguageNotifier) { _localizationOptions = localizationOptions; _blazorSchoolLanguageNotifier = blazorSchoolLanguageNotifier; } }
public class BlazorSchoolStringLocalizer<TComponent> : IStringLocalizer<TComponent> { ... private LocalizedString FindLocalziedString(string key, object[]? arguments = default) { var resourceManager = CreateResourceManager(); LocalizedString result; try { string value = resourceManager.GetString(key, _blazorSchoolLanguageNotifier.CurrentCulture); if (arguments is not null) { value = string.Format(value, arguments); } result = new(key, value, false, GetResourceLocaltion()); } catch { result = new(key, "", true, GetResourceLocaltion()); } return result; } private ResourceManager CreateResourceManager() { string resourceLocaltion = GetResourceLocaltion(); var resourceManager = new ResourceManager(resourceLocaltion, Assembly.GetExecutingAssembly()); return resourceManager; } private string GetResourceLocaltion() { var componentType = typeof(TComponent); var nameParts = componentType.FullName.Split('.').ToList(); nameParts.Insert(1, _localizationOptions.Value.ResourcesPath); string resourceLocaltion = string.Join(".", nameParts); return resourceLocaltion; } }
builder.Services.AddScoped(typeof(IStringLocalizer<>), typeof(BlazorSchoolStringLocalizer<>));
The local storage strategy is a language selection method used by developers to determine a user's preferred language when creating a multilingual website. Local storage is a web storage API that allows web applications to store data locally within a user's browser.
With the local storage strategy, developers can store the user's preferred language as a value in the local storage object. When the user visits the website again, the website can read the stored value from the local storage object and display the website content in the preferred language.
Blazor Server is rendered twice, once on the server-side and once on the client-side. When Blazor Server is rendered on the server-side, there is no local storage available, and the default language is used. However, when it is rendered on the client-side, it can read the local storage and display the user's preferred language. Due to this situation, you may notice a brief flash of the default language before quickly translating to the user's preferred language.
To implement the local storage strategy for instant translation, follow these steps:
builder.Services.Configure<RequestLocalizationOptions>(options => { options.SetDefaultCulture("fr"); options.AddSupportedCultures(new[] { "en", "fr" }); options.AddSupportedUICultures(new[] { "en", "fr" }); });
CurrentCulture
property of the language notifier instance in App.razor to the value stored in local storage, if available.@inject ProtectedLocalStorage ProtectedLocalStorage @inject BlazorSchoolLanguageNotifier BlazorSchoolLanguageNotifier <Router AppAssembly="@typeof(App).Assembly"> ... </Router> @code { protected override async Task OnInitializedAsync() { try // when render at server side, there is no local storage. Wrap the code in an empty try catch is necessary { var inputCultureLocalStorageResult = await ProtectedLocalStorage.GetAsync<string>("BlazorSchoolLanguage"); if (inputCultureLocalStorageResult.Success && !string.IsNullOrEmpty(inputCultureLocalStorageResult.Value)) { var selectedCulture = CultureInfo.GetCultureInfo(inputCultureLocalStorageResult.Value); BlazorSchoolLanguageNotifier.CurrentCulture = selectedCulture; } } catch { } } }
CurrentCulture
property of the BlazorSchoolLanguageNotifier
instance.@inject NavigationManager NavigationManager @inject IJSRuntime JSRuntime @inject BlazorSchoolLanguageNotifier BlazorSchoolLanguageNotifier @inject ProtectedLocalStorage ProtectedLocalStorage <select @onchange="OnChangeLanguageAsync"> <option value="">Select</option> <option value="en">English</option> <option value="fr">France</option> </select> @code { private async Task OnChangeLanguageAsync(ChangeEventArgs e) { string selectedCulture = e.Value as string; if (!string.IsNullOrEmpty(selectedCulture)) { await ProtectedLocalStorage.SetAsync("BlazorSchoolLanguage", selectedCulture); BlazorSchoolLanguageNotifier.CurrentCulture = CultureInfo.GetCultureInfo(selectedCulture); } } }
The URL strategy allows users to easily switch to their preferred language or share a link with the preferred language.
To implement the URL strategy for instant translation, follow these steps:
builder.Services.Configure<RequestLocalizationOptions>(options => { options.SetDefaultCulture("fr"); options.AddSupportedCultures(new[] { "en", "fr" }); options.AddSupportedUICultures(new[] { "en", "fr" }); });
@inject NavigationManager NavigationManager @inject BlazorSchoolLanguageNotifier BlazorSchoolLanguageNotifier @implements IDisposable <Router AppAssembly="@typeof(App).Assembly"> ... </Router> @code { protected override void OnInitialized() { NavigationManager.LocationChanged += OnLocationChanged; LoadCulture(); } private void LoadCulture() { var cultureFromUrl = GetCultureFromUrl(NavigationManager.Uri); BlazorSchoolLanguageNotifier.CurrentCulture = cultureFromUrl; } private void OnLocationChanged(object? sender, LocationChangedEventArgs locationChangedEventArgs) { LoadCulture(); } private CultureInfo GetCultureFromUrl(string url) { var uri = new Uri(url); var urlParameters = HttpUtility.ParseQueryString(uri.Query); var culture = CultureInfo.GetCultureInfo(urlParameters["language"] ?? ""); return culture; } public void Dispose() => NavigationManager.LocationChanged -= OnLocationChanged; }
The code subscribes to the LocationChanged
event of the NavigationManager
and updates the current culture of the BlazorSchoolLanguageNotifier
instance based on the culture code extracted from the URL parameter language
. It also calls the LoadCulture()
method in the OnInitialized
lifecycle method to load the culture from the URL when the component is first initialized. The Dispose
method unsubscribes from the LocationChanged
event to avoid memory leaks.
CurrentCulture
property of the language notifier instance. The following code shows an example of a language selector component.@inject NavigationManager NavigationManager @inject IJSRuntime JSRuntime @inject BlazorSchoolLanguageNotifier BlazorSchoolLanguageNotifier <select @onchange="OnChangeLanguage"> <option value="">Select</option> <option value="en">English</option> <option value="fr">France</option> </select> @code { private void OnChangeLanguage(ChangeEventArgs e) { var uri = new Uri(NavigationManager.Uri); var culture = CultureInfo.GetCultureInfo(e.Value as string); var cultureEscaped = Uri.EscapeDataString(culture.Name); var urlParameters = HttpUtility.ParseQueryString(uri.Query); urlParameters["language"] = cultureEscaped; string urlWithoutQuery = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped); NavigationManager.NavigateTo($"{urlWithoutQuery}?{urlParameters.ToString()}"); BlazorSchoolLanguageNotifier.CurrentCulture = CultureInfo.GetCultureInfo(cultureEscaped); } }
After creating all the necessary classes and preparing the translated resources, you can begin using instant translation. When creating a new component, it's important to subscribe and unsubscribe to the language change notification:
@inject IStringLocalizer<ChangeLanguageDemonstrate> Localizer @inject BlazorSchoolCultureProvider BlazorSchoolCultureProvider @implements IDisposable <h3>ChangeLanguageDemonstrate</h3> @Localizer["Hello Blazor School {0} {1}", "optional param1", "optional param2"] @code { protected override async Task OnInitializedAsync() => await BlazorSchoolCultureProvider.SubscribeLanguageChangeAsync(this); public void Dispose() => BlazorSchoolCultureProvider.UnsubscribeLanguageChange(this); }