Reactive translation

Reactive translation is one of the internationalization techniques. Reactive translation provides the best user experience because it does not require the user to reload the page. The translation process automatically applies to text and formats (date time format, number format, etc...) to all pages.

In this guide, you will be covered on how to i18n your website with 2 methodologies for detecting the user's preferred language:

  1. Language in cookies.
  2. Language in URL.
You can download the example code used in this topic on GitHub.

Prerequisites

To prepare your website for translations, you should have a basic understanding of the following subjects.


Using cookies to detect language

With this technique, the selected language will be stored on the cookies of the browser, you are going to use JavaScript to change the cookies.The reason is Blazor Server is an SPA framework. Therefore, the users only send one initial request to the server and you cannot modify the cookies based on that request unlike the Passive translation technique.

Customizing IStringLocalizer

You first need to customize the IStringLocalizer because later on, you will have a customized RequestCultureProvider. Create a new class BlazorSchoolStringLocalizer as follows:

public class BlazorSchoolStringLocalizer<TComponent> : IStringLocalizer<TComponent>
{
    private readonly IOptions<LocalizationOptions> _localizationOptions;

    public LocalizedString this[string name] => FindLocalziedString(name);
    public LocalizedString this[string name, params object[] arguments] => FindLocalziedString(name, arguments);

    public BlazorSchoolStringLocalizer(IOptions<LocalizationOptions> localizationOptions)
    {
        _localizationOptions = localizationOptions;
    }

    public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
    {
        var resourceManager = CreateResourceManager();
        var result = new List<LocalizedString>();

        try
        {
            var resourceSet = resourceManager.GetResourceSet(CultureInfo.CurrentCulture, true, true);
            result = resourceSet.Cast<DictionaryEntry>().Select(item =>
                    new LocalizedString((string)item.Key, (string)item.Value, false, GetResourceLocaltion()))
                .ToList();
        }
        catch
        {
            result.Add(new("", "", true, GetResourceLocaltion()));
        }

        return result;
    }

    private LocalizedString FindLocalziedString(string name, object[] arguments = null)
    {
        var resourceManager = CreateResourceManager();
        LocalizedString result;

        try
        {
            string value = resourceManager.GetString("Name");

            if (arguments is not null)
            {
                value = string.Format(value, arguments);
            }

            result = new(name, value, false, GetResourceLocaltion());
        }
        catch
        {
            result = new(name, "", 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;
    }
}

The BlazorSchoolStringLocalizer will read the settings from LocalizationOptions, create a ResourceManager and provide the translation text.

Customizing RequestCultureProvider

You need to create a custom RequestCultureProvider and use this class as a culture determination on your website, this class will take the culture in cookies and decide which culture to show on the screen. Create a new class BlazorSchoolRequestCultureProvider as follows:

public class BlazorSchoolRequestCultureProvider : RequestCultureProvider
{
    public string DefaultCulture { get; set; }

    public BlazorSchoolRequestCultureProvider(string defaultCulture)
    {
        DefaultCulture = defaultCulture;
    }

    public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
    {
        string inputCulture = httpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName];
        var result = CookieRequestCultureProvider.ParseCookieValue(inputCulture);

        if (result is null)
        {
            CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(DefaultCulture);
            CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(DefaultCulture);
            result = new(DefaultCulture);
        }
        else
        {
            CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(result.Cultures.First().Value);
            CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(result.UICultures.First().Value);
        }

        return Task.FromResult(result);
    }
}

This class also has a default culture and later on you can change its default in Startup.cs.

Localize your website

Follow the section "Localization" in the guide Internationalization and Localization, and once you have done so, you should have a Resources folder that contains all the language files. The next step is to use the Resources folder in Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddLocalization(options => options.ResourcesPath = "Resources");
    services.AddScoped(typeof(IStringLocalizer<>), typeof(BlazorSchoolStringLocalizer<>));
    services.Configure<RequestLocalizationOptions>(options =>
    {
        options.AddSupportedCultures(new[] { "en", "fr" });
        options.AddSupportedUICultures(new[] { "en", "fr" });
        options.RequestCultureProviders = new List<IRequestCultureProvider>()
        {
            new BlazorSchoolRequestCultureProvider("en")
        };
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRequestLocalization();
    ...
}

Adding JS to update cookies

You will need to manipulate cookies using JavaScript as mentioned at the beginning, Blazor Server is a SPA framework and only send 1 initilializing request. Therefore, you cannot modify this request since it's already been sent. The purpose of manipulating the cookies for storing selected language for later visit or to open multiple tabs with the same language. Add a new JS file BlazorUtil.js as follows:
class BlazorUtil
{
    addCookies(key, value)
    {
        document.cookie = `${key}=${value}`;
    }
}

window.BlazorUtil = new BlazorUtil();

Then, include it in the _Host.cshtml.

...
<body>
    ...
    <script src="~/js/BlazorUtil.js" type="text/javascript"></script>
    <script src="_framework/blazor.server.js"></script>
</body>
...

Changing the language

To change the language, you need to change the CultureInfo.CurrentCulture and CultureInfo.CurrentUICulture to change the current language. Then call the BlazorUtil.addCookies(key, value) to store the selected language in the cookies for later use. You can see our example at I18N.razor. The following code is a snippet taken from our example.

<select @onchange="OnChangeLanguage">
    <option value="">Select</option>
    <option value="en">English</option>
    <option value="fr">France</option>
</select>

@code {
    private void OnChangeLanguage(ChangeEventArgs e)
    {
        CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo((string)e.Value);
        CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo((string)e.Value);
        JSRuntime.InvokeVoidAsync("BlazorUtil.addCookies", CookieRequestCultureProvider.DefaultCookieName, CookieRequestCultureProvider.MakeCookieValue(
            new(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture)));
    }
}

Using URL to detect language

With this technique, Blazor Server will determine the language displayed on the website by the requested URL. Every component has to subscribe to the NavigationManager.LocationChanged event and call StateHasChanged() when the LocationChanged occurs.

Customizing LocalizationOptions

By default, the LocalizationOptions only allow you to set the Resource folder, you will also need to add a default language to this options because later on, you will re-use the default language in App.razor. You can create a new class to customize LocalizationOptions as follows:
public class BlazorSchoolLocalizationOptions : LocalizationOptions
{
    public string DefaultLanguage { get; set; }
}

Customizing IStringLocalizer

Just like the cookies appoach, you will need to have a customize IStringLocalizer. Create a new class BlazorSchoolStringLocalizer as follows:

public class BlazorSchoolStringLocalizer<TComponent> : IStringLocalizer<TComponent>
{
    private readonly IOptions<BlazorSchoolLocalizationOptions> _localizationOptions;

    public LocalizedString this[string name] => FindLocalziedString(name);
    public LocalizedString this[string name, params object[] arguments] => FindLocalziedString(name, arguments);

    public BlazorSchoolStringLocalizer(IOptions<BlazorSchoolLocalizationOptions> localizationOptions)
    {
        _localizationOptions = localizationOptions;
    }

    public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
    {
        var resourceManager = CreateResourceManager();
        var result = new List<LocalizedString>();

        try
        {
            var resourceSet = resourceManager.GetResourceSet(CultureInfo.CurrentCulture, true, true);
            result = resourceSet.Cast<DictionaryEntry>().Select(item =>
                    new LocalizedString((string)item.Key, (string)item.Value, false, GetResourceLocaltion()))
                .ToList();
        }
        catch
        {
            result.Add(new("", "", true, GetResourceLocaltion()));
        }

        return result;
    }

    private LocalizedString FindLocalziedString(string name, object[] arguments = null)
    {
        var resourceManager = CreateResourceManager();
        LocalizedString result;

        try
        {
            string value = resourceManager.GetString("Name");

            if (arguments is not null)
            {
                value = string.Format(value, arguments);
            }

            result = new(name, value, false, GetResourceLocaltion());
        }
        catch
        {
            result = new(name, "", 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;
    }
}

Customizing RequestCultureProvider

You also need to customize the RequestCultureProvider. This class will take the requested URL and parse the language for the initial request. Create a new class BlazorSchoolRequestCultureProvider as follows:

public class BlazorSchoolRequestCultureProvider : RequestCultureProvider
{
    private readonly BlazorSchoolLocalizationOptions _localizationOptions;
    private string _selectedLanguage { get; set; }

    public BlazorSchoolRequestCultureProvider(BlazorSchoolLocalizationOptions localizationOptions)
    {
        _localizationOptions = localizationOptions;
    }

    public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
    {
        if (httpContext.Request.Headers["Sec-Fetch-Dest"] == "document")
        {
            var query = httpContext.Request.Query;
            string selectedLanguage = query["language"].ToString() ?? _localizationOptions.DefaultLanguage;
            CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(selectedLanguage);
            CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(selectedLanguage);
            var result = new ProviderCultureResult(selectedLanguage);

            _selectedLanguage = selectedLanguage;

            return Task.FromResult(result);
        }
        else
        {
            return Task.FromResult(new ProviderCultureResult(_selectedLanguage));
        }
    }
}

In our example, we use language as the parameter name for the language in the URL, you can also change this parameter name.

Subscribing NavigationManager.LocationChanged event

The BlazorSchoolRequestCultureProvider only provides the language for the initial request. Blazor Server is a SPA framework and therefore, there is only one request. You need to subscribe to the NavigationManager.LocationChanged event to detect if the user has chosen a new language. You need to subscribe to the event in the first Razor Component called by _Host.cshtml. By default, it is App.razor.

@using System.Globalization
@using System.Web;
@using ReactiveI18NUrlBlazorServer.Data
@using Microsoft.Extensions.Options 
@inject NavigationManager NavigationManager
@inject IOptions<BlazorSchoolLocalizationOptions> BlazorSchoolLocalizationOptions
@implements IDisposable

...

@code {

    protected override void OnInitialized()
    {
        NavigationManager.LocationChanged += DetermineLanguage;
    }

    private void DetermineLanguage(object sender, LocationChangedEventArgs e)
    {
        var uri = new Uri(NavigationManager.Uri);
        var urlParameters = HttpUtility.ParseQueryString(uri.Query);
        CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(urlParameters["language"] ?? BlazorSchoolLocalizationOptions.Value.DefaultLanguage);
        CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(urlParameters["language"] ?? BlazorSchoolLocalizationOptions.Value.DefaultLanguage);
    }

    public void Dispose()
    {
        NavigationManager.LocationChanged -= DetermineLanguage;
    }
}

Localize your website

Follow the section "Localization" in the guide Internationalization and Localization, and once you have done so, you should have a Resources folder that contains all the language files. The next step is to use the Resources folder in Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.Configure<BlazorSchoolLocalizationOptions>(options =>
    {
        options.ResourcesPath = "Resources";
        options.DefaultLanguage = "en";
    });
    services.AddScoped(typeof(IStringLocalizer<>), typeof(BlazorSchoolStringLocalizer<>));
    services.Configure<RequestLocalizationOptions>(options =>
    {
        options.AddSupportedCultures(new[] { "en", "fr" });
        options.AddSupportedUICultures(new[] { "en", "fr" });
        options.RequestCultureProviders = new List<IRequestCultureProvider>()
        {
            new BlazorSchoolRequestCultureProvider(Configuration.Get<BlazorSchoolLocalizationOptions>())
        };
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseRequestLocalization();
    app.UseStaticFiles();
    app.UseRouting();
    ...
}

Applying in a component

The final step is to apply to a component. Subscribe to the NavigationManager.LocationChanged event and re-render the component every time the event is fired, also remember to unsubscribe from the event when the component is being disposed.

@using Microsoft.Extensions.Localization
@using System.Globalization
@inject IStringLocalizer<I18N> Localizer
@inject NavigationManager NavigationManager

<h3>@Localizer["Name"]</h3>

@code {
    protected override void OnInitialized()
    {
        NavigationManager.LocationChanged += Render;
    }

    private void Render(object sender, LocationChangedEventArgs e)
    {
        StateHasChanged();
    }

    public void Dispose()
    {
        NavigationManager.LocationChanged -= Render;
    }
}

Once you have applied to your component, you can navigate to your component and include language to change the language. For example, http://localhost:5283/i18n?language=fr, http://localhost:5283/i18n, http://localhost:5283/i18n?language=en.

BLAZOR SCHOOL
Designed and built with care by our dedicated team, with contributions from a supportive community. We strive to provide the best learning experience for our users.
Docs licensed CC-BY-SA-4.0
Copyright © 2021-2025 Blazor School
An unhandled error has occurred. Reload 🗙