Instant translation

Instant translation is an approach to translate a website that doesn't require a refresh to see the translated content. Not only that, but you can break free from RESX file, this technique can read from multiple data source like database, YML, JSON files. You can decide the priority of each language selection strategies. In this tutorial, we will walk you through this amazing technique step by step with the following content:

  • What is instant translation?
  • Comparing instant translation with deferred translation.
  • Involved building blocks and their roles.
  • Building the language notifier.
  • Fetching translated content.
  • Implement language selection strategies.
  • Using the instant translation.
You can download the example code used in this topic on GitHub.

What is instant translation?

There are 2 approaches when making a multilingual website, instant translation is one of the approaches. When a user selects a language, the translation will process immediately, which means the user doesn't need to refresh the web page to see the new language. Furthermore, you can choose where to store the language, whether it is in the database, whether it is in the YML/JSON file. You can also have the control over the priority of language selection strategy.+


Comparing instant translation with deferred translation

Deferred Translation Instant Translation
User experience Bad Good
Language selection strategy Not customizable: only supports cookies, browser settings, and URL. Cannot change the priority of the strategies. Customizable: support cookies, browser settings, URL, and custom strategies. Can change the priority of the strategies.
Resource type .resx files only. Support any file extensions and database.
Implement effort Low High

Involved building blocks and their roles

In instant translation approach, there are 3 main building blocks:

String localizer: Fetch the translated text by the provided key.

Request culture provider: Determine the fallback language when no language is specified (the most common case is the user visits your website for the first time). Decide which language selection strategy wins, either cookies or URL.

Language notifier: When the language changes, the language notifier will notify all the components to update themselves.


Building the language notifier

This is a continuation of the previous tutorial, begins at step 7.

  1. Create a language notifier class as follows:
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);

    public 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));
            }
        }
    }
}
  1. Register at Program.cs as follows:
builder.Services.AddScoped<BlazorSchoolLanguageNotifier>();

Fetching translated content

By implementing the IStringLocalizer interface, you can fetch the translated content from many data sources (database, YML/JSON/RESX files). In this example, we will fetch resource from the RESX files. The interface IStringLocalizer has 2 important indexes, that are: LocalizedString this[string name] and LocalizedString this[string name, params object[] arguments]. The former index allows you to find the translated content by its name, the latter has the same functionality, but you can have some arguments act as placeholders in the translated content. For example in English, we say "31st" (no space) in English but in Chinese, it is "31 日" (has a space between the number and text); The number 31 is the parameter to the translated content.

  1. Create a string localizer class. 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)
    {
        throw new NotImplementedException();
    }
}
  1. Inject IOptions<LocalizationOptions> so we can access the resource location. For example:
public class BlazorSchoolStringLocalizer<TComponent> : IStringLocalizer<TComponent>
{
    ...
    private readonly IOptions<LocalizationOptions> _localizationOptions;
    
    public BlazorSchoolStringLocalizer(IOptions<LocalizationOptions> localizationOptions)
    {
        _localizationOptions = localizationOptions;
    }
}
  1. Create methods to fetch the translated content. The following example method is to fetch translated text from RESX files:
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);

            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;
    }
}

The method FindLocalziedString will be called by the indexes of the string localizer class.

  1. Register at Program.cs. For example:
builder.Services.AddScoped(typeof(IStringLocalizer<>), typeof(BlazorSchoolStringLocalizer<>));

Implement language selection strategies

There are 2 suitable language selection strategies for the instant translation approach: cookies and URL.

Cookie strategy

In this strategy, you will store the user preferred language in the cookie. You will check if the cookie has value, then use the language in the cookie. Otherwise, use the fallback language.

  1. Create a new JavaScript module. For example:
export function addCookies(key, value)
{
    document.cookie = `${key}=${value}`;
}
  1. Create a culture provider class. For example:
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<ProviderCultureResult?>(result);
    }
}
  1. Register at Program.cs. For example:
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    options.AddSupportedCultures(new[] { "en", "fr" });
    options.AddSupportedUICultures(new[] { "en", "fr" });
    options.RequestCultureProviders = new List<IRequestCultureProvider>()
        {
            new BlazorSchoolRequestCultureProvider("en")
        };
});

In this code sample, we select English as the default language.

  1. Build the language selector component. For example:
@inject NavigationManager NavigationManager
@inject IJSRuntime JSRuntime
@inject BlazorSchoolLanguageNotifier BlazorSchoolLanguageNotifier
@implements IAsyncDisposable

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

@code {
    private Lazy<IJSObjectReference> LanguageSelectorModule = new();

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        LanguageSelectorModule = new(await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/BlazorSchoolLanguageSelector.js"));
    }

    private void OnChangeLanguage(ChangeEventArgs e)
    {
        CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo((string)e.Value);
        CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo((string)e.Value);
        LanguageSelectorModule.Value.InvokeVoidAsync("addCookies", CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture)));
        BlazorSchoolLanguageNotifier.NotifyLanguageChange();
    }

    public async ValueTask DisposeAsync()
    {
        if (LanguageSelectorModule.IsValueCreated)
        {
            await LanguageSelectorModule.Value.DisposeAsync();
        }
    }
}

URL strategy

In this strategy, you will determine the user preferred language by the URL. When the user send the first request, you will check if the URL contains the language parameter, then use the language in the URL. Otherwise, use the fallback language.

Because this is a different strategy so we will start at step 13:

  1. Create a culture provider class. For example:
public class BlazorSchoolRequestCultureProvider : RequestCultureProvider
{
    private readonly string _defaultLanguage;

    public BlazorSchoolRequestCultureProvider(string defaultLanguage)
    {
        _defaultLanguage = defaultLanguage;
    }
}
  1. Add a method to determine the language by the URL. For example:
public class BlazorSchoolRequestCultureProvider : RequestCultureProvider
{
    ...
    private string? GetLanguageFromUrl(string url)
    {
        var uri = new Uri(url);
        var urlParameters = HttpUtility.ParseQueryString(uri.Query);

        return urlParameters["language"];
    }
}
  1. Override the DetermineProviderCultureResult method to change the culture. For example:
public class BlazorSchoolRequestCultureProvider : RequestCultureProvider
{
    ...
    public override Task<ProviderCultureResult?> DetermineProviderCultureResult(HttpContext httpContext)
    {
        if (httpContext.Request.Headers["Sec-Fetch-Dest"] == "document")
        {
            string url = httpContext.Request.GetDisplayUrl();
            _selectedLanguage = GetLanguageFromUrl(url);

            if (string.IsNullOrEmpty(_selectedLanguage))
            {
                _selectedLanguage = _defaultLanguage;
            }

            CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(_selectedLanguage);
            CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(_selectedLanguage);
            var result = new ProviderCultureResult(_selectedLanguage);

            return Task.FromResult<ProviderCultureResult?>(result);
        }
        else
        {
            return Task.FromResult<ProviderCultureResult?>(new ProviderCultureResult(_selectedLanguage));
        }
    }
}
The browser sends many requests of different types like images, JavaScript, stylesheet so the filter httpContext.Request.Headers["Sec-Fetch-Dest"] == "document" is needed to only change the language for the HTML document.
  1. Register at Program.cs. For example:
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    options.AddSupportedCultures(new[] { "en", "fr" });
    options.AddSupportedUICultures(new[] { "en", "fr" });
    options.RequestCultureProviders = new List<IRequestCultureProvider>()
        {
            new BlazorSchoolRequestCultureProvider("en")
        };
});
  1. Build the language selector component. For example:
@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()}");
        CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(cultureEscaped);
        CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(cultureEscaped);
        BlazorSchoolLanguageNotifier.NotifyLanguageChange();
    }
}

Using the instant translation

When create a new Razor component, subcribe the language change from the language notifier. For example:

@inject IStringLocalizer<ChangeLanguageDemonstrate> Localizer
@inject BlazorSchoolLanguageNotifier BlazorSchoolLanguageNotifier
@implements IDisposable

<h3>ChangeLanguageDemonstrate</h3>
@Localizer["String1"]
<LanguageSelector />

@code {
    protected override void OnInitialized() => BlazorSchoolLanguageNotifier.SubscribeLanguageChange(this);
    public void Dispose() => BlazorSchoolLanguageNotifier.UnsubscribeLanguageChange(this);
}
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 🗙