Blazor - Change UI CultureInfo at runtime

Viewed 3285

I need to change cultures at runtime according to resource files for each culture.

Expected

User clicks a button that switches from current language to the other language. The texts in the page are refreshed according to resource files for each culture.

Actual

User clicks a button that switches from current language to the other language. The texts in the page are refreshed according to resource files for each culture. As soon as the user does any action on any component (I have several radio buttons, buttons and checkboxes) the texts go back to what was written in the first time. It's like the CultureInfo hasn't been changed at all.

Tries

Created an AppState class that is injected to the components

    public class AppState
{
    public CultureInfo currentCulture { get; private set; } = CultureInfo.CurrentCulture;
    public event Action OnChange;
    public void ChangeCulture(CultureInfo newCulture)
    {
        currentCulture = newCulture;
        System.Threading.Thread.CurrentThread.CurrentCulture.ClearCachedData();
        System.Threading.Thread.CurrentThread.CurrentUICulture.ClearCachedData();
        System.Threading.Thread.CurrentThread.CurrentCulture = newCulture;
        System.Threading.Thread.CurrentThread.CurrentUICulture = newCulture;
        NotifyStateChanged();
    }

    public CultureInfo getCurrentCulture()
    {
        if (currentCulture == null)
        {
            ChangeCulture(CultureInfo.CurrentCulture);
        }
        return currentCulture;

    }

    private void NotifyStateChanged() => OnChange?.Invoke();
}

Added localization to the project in startup.cs and AppState instante as well

        services.AddScoped<AppState>();
        services.AddMvc().AddMvcLocalization();
        services.AddLocalization();
        var supportedCultures = new List<CultureInfo> { new CultureInfo("en-US"), new CultureInfo("es-ES") };
        services.Configure<RequestLocalizationOptions>(options =>
        {
            options.DefaultRequestCulture = new RequestCulture("es-ES");
            options.SupportedUICultures = supportedCultures;
            options.SupportedCultures = supportedCultures;
        });

Component has the AppState injected as well as the IStringLocalizer object to get the strings from the resource file.

@inject AppState state
@inject IStringLocalizer<Index> _localizer

@implements IDisposable

<div class="uk-container" id="data_container">
    <button @onclick="e => CambiarIdioma()">Holadenuevo</button>

    <TablaReconocimientos @ref="tablaReconocimientos" searchText="@navigatableSearchInput"></TablaReconocimientos>
</div>

And then has the events set

    protected override void OnInitialized()
    {
        _lematizador = new LematizadorService();

        state.OnChange += OnCultureChange;
    }

    public void Dispose()
    {
        state.OnChange -= OnCultureChange;
    }
    private void CambiarIdioma()
    {
        if (state.getCurrentCulture().Equals(new CultureInfo("es-ES")))
        {
            state.ChangeCulture(new CultureInfo("en-US"));
        }
        else
        {
            state.ChangeCulture(new CultureInfo("es-ES"));
        }
    }

    private void OnCultureChange()
    {
        CultureInfo.CurrentCulture = state.currentCulture;
        CultureInfo.CurrentUICulture = state.currentCulture;

        StateHasChanged();
    }

So now when clicking the button that triggers CambiarIdioma() the project works fine. But as soon as any action is done inside the component (lets say that I check something inside the TablaReconocimientos component or I click on a radiobutton) the texts are reverted into their original strings.

Every component that needs to have their texts localized has the AppState injected and implemented this functions:

@inject AppState state
@inject IStringLocalizer<TablaModos> _localizer

@implements IDisposable


@*Component stuff*@

@functions{
    protected override void OnInitialized()
    {
        state.OnChange += OnCultureChange;
    }

    public void Dispose()
    {
        state.OnChange -= OnCultureChange;
    }

    private void OnCultureChange()
    {
        CultureInfo.CurrentCulture = state.currentCulture;
        CultureInfo.CurrentUICulture = state.currentCulture;

        StateHasChanged();
    }

}

Image as exmaple of whats happening at the moment: enter image description here

1 Answers

You need to use Cookies to store the selected culture. See Microsoft documentation. https://docs.microsoft.com/en-us/aspnet/core/blazor/globalization-localization?view=aspnetcore-5.0#cookies

From the documentation :

Blazor Server Blazor Server apps are localized using Localization Middleware. The middleware selects the appropriate culture for users requesting resources from the app.

The culture can be set using one of the following approaches:

Cookies Provide UI to choose the culture For more information and examples, see Globalization and localization in ASP.NET Core.

Cookies A localization culture cookie can persist the user's culture. The Localization Middleware reads the cookie on subsequent requests to set the user's culture.

Use of a cookie ensures that the WebSocket connection can correctly propagate the culture. If localization schemes are based on the URL path or query string, the scheme might not be able to work with WebSockets, thus fail to persist the culture. Therefore, use of a localization culture cookie is the recommended approach.

Any technique can be used to assign a culture if the culture is persisted in a localization cookie. If the app already has an established localization scheme for server-side ASP.NET Core, continue to use the app's existing localization infrastructure and set the localization culture cookie within the app's scheme.

The following example shows how to set the current culture in a cookie that can be read by the Localization Middleware. Create a Razor expression in the Pages/_Host.cshtml file immediately inside the opening tag:

CSHTML

Copy

@using System.Globalization
@using Microsoft.AspNetCore.Localization

...

<body>
    @{
        this.HttpContext.Response.Cookies.Append(
            CookieRequestCultureProvider.DefaultCookieName,
            CookieRequestCultureProvider.MakeCookieValue(
                new RequestCulture(
                    CultureInfo.CurrentCulture,
                    CultureInfo.CurrentUICulture)));
    }

    ...
</body>

Localization is handled by the app in the following sequence of events:

The browser sends an initial HTTP request to the app. The culture is assigned by the Localization Middleware. The Razor expression in the _Host page (_Host.cshtml) persists the culture in a cookie as part of the response. The browser opens a WebSocket connection to create an interactive Blazor Server session. The Localization Middleware reads the cookie and assigns the culture. The Blazor Server session begins with the correct culture. When working with a RazorPage, use the Context property:

razor

Copy

@{
    this.Context.Response.Cookies.Append(
        CookieRequestCultureProvider.DefaultCookieName,
        CookieRequestCultureProvider.MakeCookieValue(
            new RequestCulture(
                CultureInfo.CurrentCulture,
                CultureInfo.CurrentUICulture)));
}

Provide UI to choose the culture To provide UI to allow a user to select a culture, a redirect-based approach is recommended. The process is similar to what happens in a web app when a user attempts to access a secure resource. The user is redirected to a sign-in page and then redirected back to the original resource.

The app persists the user's selected culture via a redirect to a controller. The controller sets the user's selected culture into a cookie and redirects the user back to the original URI.

Establish an HTTP endpoint on the server to set the user's selected culture in a cookie and perform the redirect back to the original URI:

C#

Copy

[Route("[controller]/[action]")]
public class CultureController : Controller
{
    public IActionResult SetCulture(string culture, string redirectUri)
    {
        if (culture != null)
        {
            HttpContext.Response.Cookies.Append(
                CookieRequestCultureProvider.DefaultCookieName,
                CookieRequestCultureProvider.MakeCookieValue(
                    new RequestCulture(culture)));
        }

        return LocalRedirect(redirectUri);
    }
}
Related