🌐 Building Multilingual Blazor Apps Made Easy!

🌐 Building Multilingual Blazor Apps Made Easy!

Ever struggled with dynamically translating resources in your Blazor Web Applications? You're not alone! At Clean Code Factory, we've successfully implemented seamless language switching in Blazor using built-in .NET localization and cookies and MudBlazor.

In this practical guide, we walk through a step-by-step approach to setting up dynamic resource translation in Blazor—from configuring localization services and managing culture settings, to building a user-friendly language switcher component.

Ready to get started? Let's dive deeper into the technical details. In this comprehensive step-by-step guide, we'll show you exactly how we implemented dynamic resource translation in our Blazor applications, complete with code snippets and clear explanations:

1. Server-Side Localization Setup (Program.cs)

In the server project, you need to register localization services and configure the supported cultures. This code snippet shows how to set up localization middleware, register controllers, and configure the default culture.

#region Localization

builder.Services.AddControllers();

// Define the supported cultures.
var supportedCultures = new[]
{
    new CultureInfo("en"),
    new CultureInfo("bg")
};

// Configure RequestLocalizationOptions
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    options.DefaultRequestCulture = new RequestCulture("en");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;
    // Optionally, use cookies as one of the providers
    options.RequestCultureProviders.Insert(0, new CookieRequestCultureProvider());
});
#endregion Localization

#region Localization
app.MapControllers();
// Enable localization middleware (this must be called before routing or endpoints)
var localizationOptions = app.Services.GetRequiredService<IOptions<RequestLocalizationOptions>>().Value;
app.UseRequestLocalization(localizationOptions);
#endregion Localization        

Explanation:

  1. AddControllers: The server registers controllers (if not already registered).
  2. Supported Cultures: Two cultures are supported: English ("en") and Bulgarian ("bg"). These cultures will be used for both formatting and UI texts.
  3. Request Localization Options: The default culture is set to English, and cookie-based culture detection is prioritized by inserting CookieRequestCultureProvider at the beginning of the provider list.
  4. Middleware Registration: The localization middleware is applied before any routing, ensuring that the correct culture is set for each request.

2. Creating the Culture Controller (CultureController.cs)

This controller provides an API endpoint to change the current culture dynamically. It creates a cookie with the selected culture and sets an expiration.

using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;

namespace WineCabinet7.Server.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class CultureController : ControllerBase
    {
        [HttpPost("SetCulture")]
        public IActionResult SetCulture([FromBody] string culture)
        {
            // Create a cookie value based on the provided culture.
            var cookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
            Response.Cookies.Append(
                CookieRequestCultureProvider.DefaultCookieName,
                cookieValue,
                new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
            );
            return Ok();
        }
    }
}        

Explanation:

  1. API Endpoint: The SetCulture endpoint accepts a culture string (e.g., "en" or "bg") in the request body.
  2. Cookie Creation: It uses CookieRequestCultureProvider.MakeCookieValue to format the cookie value and appends it to the response. This cookie ensures that subsequent requests use the selected culture.
  3. Longevity: The cookie is set to expire in one year, making the culture selection persistent across sessions.

3. Client-Side Culture Detection via JavaScript (App.razor)

Include a JavaScript file that reads the current culture from the cookie:

<script src="js/culture.js"></script>        

wwwroot/js/culture.js

This script retrieves the culture from the browser cookies. It scans the document cookies for the specific cookie name and extracts the culture code.

window.blazorCulture = {
    get: function () {
        const cookieName = ".AspNetCore.Culture=";
        const decodedCookie = decodeURIComponent(document.cookie);
        const cookies = decodedCookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            let cookie = cookies[i].trim();
            if (cookie.indexOf(cookieName) === 0) {
                // Get the cookie value
                const value = cookie.substring(cookieName.length);
                // Value format is "c=bg|uic=bg". Split by '|' to get parts.
                const parts = value.split("|");
                for (let j = 0; j < parts.length; j++) {
                    let part = parts[j].split("=");
                    if (part[0] === "c" && part[1]) {
                        return part[1];
                    }
                }
            }
        }
        return null;
    }
};

window.getBrowserLocale = function () {
    return navigator.language || navigator.userLanguage;
};        

Explanation:

  • Cookie Parsing: The script looks for the cookie named .AspNetCore.Culture. Once found, it extracts the culture value from the formatted string (e.g., "c=bg|uic=bg").
  • Usage: This value is later used on the client to initialize the application culture.

-Retrieve Locale: The code calls your JavaScript function to get the browser’s locale.

4. Client Project Configuration (Client.csproj)

Ensure that Blazor WebAssembly loads all globalization data by adding the following property:

<PropertyGroup>
    <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
</PropertyGroup>        

Explanation:

  • Globalization Data: This setting makes sure that the Blazor WebAssembly application loads all the necessary data for culture-specific operations, such as date and number formatting.

5. Client-Side Localization Setup (Program.cs)

Register localization services in the Client project and read the current culture from JavaScript:

// Read the culture from JavaScript
var host = builder.Build();
var jsInterop = host.Services.GetRequiredService<IJSRuntime>();
var cultureName = await jsInterop.InvokeAsync<string>("blazorCulture.get");

if (!string.IsNullOrEmpty(cultureName))
{
    var culture = new CultureInfo(cultureName);
    CultureInfo.DefaultThreadCurrentCulture = culture;
    CultureInfo.DefaultThreadCurrentUICulture = culture;
}
else
{
    var browserLocale = await jsInterop.InvokeAsync<string>("getBrowserLocale");
    CultureInfo culture;

    try
    {
        // Create a CultureInfo from the browser locale
        var browserCulture = new CultureInfo(browserLocale);
        // Extract the two-letter ISO language name
        var twoLetterCode = browserCulture.TwoLetterISOLanguageName.ToLower();

        // Check if it is one of our allowed cultures ("en" or "bg")
        if (twoLetterCode != "en" && twoLetterCode != "bg")
        {
            twoLetterCode = "en"; // Default to English if not supported
        }

        culture = new CultureInfo(twoLetterCode);
    }
    catch (Exception)
    {
        // If any error occurs, fallback to default "en"
        culture = new CultureInfo("en");
    }

    // Apply the selected culture to the application
    CultureInfo.DefaultThreadCurrentCulture = culture;
    CultureInfo.DefaultThreadCurrentUICulture = culture;
}

#endregion Localization        

Explanation:

  • Service Registration: The AddLocalization() call makes the localization services available to the Client project.
  • Build the Host: The host is built from the configured builder. This host provides access to the service container.
  • Obtain IJSRuntime: The JavaScript interop service (IJSRuntime) is retrieved from the host’s service provider. This service allows calling JavaScript functions from C#.
  • JS Interop for Culture: By invoking the JavaScript method blazorCulture.get, the app retrieves the culture value from the browser cookies. This value can be used to set the current culture on the client.
  • Check for a Valid Culture Name: If cultureName is not empty or null, the code creates a CultureInfo object from it.
  • Apply the Culture: The newly created CultureInfo is set as the default for both DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture. This ensures that all cultural-specific operations (like date formatting, number formatting, and resource lookups) use this culture.
  • Fallback if No Custom Culture is Found: If cultureName is null or empty, the code invokes another JavaScript function (getBrowserLocale) to obtain the browser’s default locale.
  • Try-Catch Block for Safety:
  • The code attempts to create a CultureInfo object (browserCulture) from the browser locale.
  • It extracts the two-letter ISO language code (for example, "en" or "bg") using TwoLetterISOLanguageName and converts it to lowercase.
  • The code then checks if this two-letter code is either "en" or "bg". If it is not, it defaults to "en".
  • In case any exception occurs (for instance, if an invalid locale string is returned), the catch block ensures that the default culture "en" is used.
  • Apply the Fallback Culture: The selected culture is then applied to both DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture.

6. Building the Language Switcher Component (LanguageSwitcher.razor)

This component uses MudBlazor to provide a UI menu for switching languages. When a language is selected, it posts the new culture to the server and reloads the page to apply the changes.

@using System.Globalization
@inject HttpClient Http
@inject NavigationManager Navigation

<MudMenu Class="IconDia">
    <ActivatorContent>
        <MudImage Src="@_SelectedFlag" Style="width: 32px" />
    </ActivatorContent>

    <ChildContent>
        <MudMenuItem OnClick="@(async () => await SelectLanguage("en", "/en-us-circle.svg"))">
            <MudImage Src="/en-us-circle.svg" Style="width: 24px" Class="me-2" /> English
        </MudMenuItem>
        <MudMenuItem OnClick="@(async () => await SelectLanguage("bg", "/bg-circle.svg"))">
            <MudImage Src="/bg-circle.svg" Style="width: 24px" Class="me-2" /> Bulgarian
        </MudMenuItem>
    </ChildContent>
</MudMenu>

@code {
    private string _SelectedLanguage = "en";
    private string _SelectedFlag = "/en-us-circle.svg";

    protected override void OnInitialized()
    {
        base.OnInitialized();
        switch (CultureInfo.CurrentCulture.TwoLetterISOLanguageName)
        {
            case "en":
                _SelectedFlag = "/en-us-circle.svg";
                break;
            default:
                _SelectedFlag = "/bg-circle.svg";
                break;
        }
    }
    private async Task SelectLanguage(string lang, string flag)
    {
        if (!string.IsNullOrWhiteSpace(lang))
        {
            _SelectedFlag = flag;
            // Post the new culture to the server.
            await Http.PostAsJsonAsync("api/Culture/SetCulture", lang);
            // Reload the page to apply the new culture.
            Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
        }
    }
}        

Explanation:

  • MudBlazor Components: The language switcher is built with MudMenu, MudMenuItem, and MudImage components for a visually appealing dropdown.
  • Dynamic Flag Display: The component checks the current culture on initialization and sets the flag accordingly.
  • Language Selection: When a user selects a language, the SelectLanguage method posts the language code to the server's SetCulture endpoint. Then, it forces a page reload to ensure that the new culture is applied across the application.

7. Using Localized Resource Files (Resources and PublicResXFileCodeGenerator)

Place your resource file (e.g., UiTranslation.resx) in the Client project's Resources folder. To ensure that the auto-generated class for the resource file is public, use a custom tool like PublicResXFileCodeGenerator. This allows you to inject and use the resource file in your components.

Injecting and Using Localized Strings

In a component, inject the localizer and use it to retrieve localized strings. For example:

@inject IStringLocalizer<UiTranslation> _Localizer

<!-- Usage in a component -->
<p>@_Localizer[UiTranslation.Citation]</p>        

Explanation:

  • Resource Injection: The IStringLocalizer<UiTranslation> is injected so that the localized strings defined in UiTranslation.resx are accessible.
  • Key-Based Access: In the example, UiTranslation.Citation is a key defined in the resource file. The localizer retrieves the appropriate translation based on the current culture.

Summary

This example demonstrates a complete solution for dynamic resource translation in a Blazor Web Application:

Server-Side Setup:

  • Register localization services.
  • Configure supported cultures and localization middleware.
  • Implement an API controller to update the culture cookie.

Client-Side Setup:

  • Configure globalization settings in the project file.
  • Register localization services and read the culture via JavaScript interop.
  • Build a language switcher component using MudBlazor that posts the new culture to the server and reloads the page.

Localized Resources:

  • Store resource files in the Client project’s Resources folder.
  • Use a custom code generator to ensure resource classes are public.
  • Inject IStringLocalizer<T> in your components to retrieve localized strings.

By following this structured approach, you can provide dynamic language switching and localized content in your Blazor application.


🎯 Did this guide help you simplify localization in Blazor? We’d love your feedback! Share your thoughts or questions below.

For more insights and tips, make sure to follow Clean Code Factory. Happy coding! 🚀

#Blazor #Localization #DotNet #MudBlazor #CleanCodeFactory #DeveloperCommunity

To view or add a comment, sign in

More articles by Clean Code Factory

Others also viewed

Explore content categories