static void

Blazor Localization

Registration in Program.cs

using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.JSInterop;
using System.Globalization;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
//add localization support
builder.Services.AddLocalization();

var webAssemblyHost = builder.Build();

//look in localStorage, otherwise use a default
var js = webAssemblyHost.Services.GetRequiredService<IJSRuntime>();
var currentLanguage = await js.InvokeAsync<string>("localStorage.getItem""currentLanguage") ?? "en-GB";

//always set culture + ui culture to the same thing
CultureInfo cultureInfo;
try
{
    cultureInfo = new CultureInfo(currentLanguage);
}
catch (CultureNotFoundException )
{
    //protect against poison values
    currentLanguage = "en-GB";
    cultureInfo = new CultureInfo(currentLanguage);
    await js.InvokeVoidAsync("localStorage.setItem""currentLanguage", currentLanguage);
}

CultureInfo.DefaultThreadCurrentCulture = 
    CultureInfo.DefaultThreadCurrentUICulture = 
        cultureInfo;

await webAssemblyHost.RunAsync();

Language Picker component

In your language buttons, use NavigateTo with forceLoad to reload the culture

@using System.Globalization
@inject IJSRuntime JsRuntime
@inject NavigationManager Navigation
@inject IStringLocalizer<PageResourcesLoc
@if (!_refreshing)
{
    <form action="#">
        <fieldset>
            <legend>@Loc["Language"@_currentLanguage @DateTime.Now.ToString("f")</legend>
            <button @onclick="SetFrench">@Loc["French"]</button>
            <button @onclick="SetEnglish">@Loc["English"]</button>
        </fieldset>
    </form>
}
else
{
    <p>Page will be refreshed</p>
}
@code {
    private string _currentLanguage;
    private bool _refreshing;

    private async Task SetFrench()
    {
        _refreshing = true;
        await JsRuntime.InvokeVoidAsync("localStorage.setItem""currentLanguage""fr-Fr");
        Navigation.Refresh(true);
    }

    private async Task SetEnglish()
    {
        _refreshing = true;
        await JsRuntime.InvokeVoidAsync("localStorage.setItem""currentLanguage""en-GB");
        Navigation.Refresh(true);
    }

    protected async override Task OnInitializedAsync()
    {
        _currentLanguage = await JsRuntime.InvokeAsync<string>("localStorage.getItem""currentLanguage");
        if (string.IsNullOrEmpty(_currentLanguage))
        {
            _currentLanguage = CultureInfo.DefaultThreadCurrentCulture.Name;
        }
    }
}

NB: in the .net8 interactive components, ensure this is @rendermode="new InteractiveWebAssemblyRenderMode(prerenderfalse)" 

Component Resources