Blazor Localization in SentenceStudio (Hybrid + WebApp)
Add localized strings to a Razor component that works identically in MAUI Blazor Hybrid and the WebApp, with correct per-user isolation on the server.
Golden rules
- Never read
AppResources.*directly.AppResourcesis internal toSentenceStudio.Shared. Go throughLocalizationManager.GetString(key, culture?)or the injectedBlazorLocalizationService. - Never
AddSingleton<BlazorLocalizationService>. It must be scoped so each Blazor circuit holds its ownCultureInfo. A singleton leaks culture between users on the server. - Never mutate
CultureInfo.DefaultThreadCurrentUICulturefrom Blazor code. TheLocalizationManager.Instance.SetCulturecall is reserved for the MAUI path (single-user process). On WebApp, use the cookie endpoint. - Never write cookies directly from a Blazor Server component. Circuits run over SignalR, the
HttpResponseis unavailable. Redirect through an endpoint.
Pattern: adding localized strings to a Razor page
1. Add keys to both resx files
src/SentenceStudio.Shared/Resources/Strings/AppResources.resx (en) and AppResources.ko-KR.resx (ko). Use a stable prefix for the page (e.g., Profile_Save, Nav_Dashboard). Keep the <comment> human-readable — translators will thank you.
<data name="MyPage_Title" xml:space="preserve">
<value>My Page</value>
<comment>MyPage: page header title</comment>
</data>
2. Inject + consume in the Razor component
@implements IDisposable
@inject SentenceStudio.WebUI.Services.BlazorLocalizationService Localize
<h1>@Localize["MyPage_Title"]</h1>
<button>@Localize["MyPage_Save"]</button>
<p>@Localize.Get("MyPage_Greeting", userName)</p> @* formatted with args *@
@code {
protected override void OnInitialized()
{
Localize.CultureChanged += OnCultureChanged;
}
private void OnCultureChanged() => InvokeAsync(StateHasChanged);
public void Dispose()
{
Localize.CultureChanged -= OnCultureChanged;
}
}
The subscription is how you get live re-render when the user saves a new language without reloading the page.
3. (Rarely) changing the culture from a component
If your component is the one that lets the user pick the language:
var cultureInfo = new System.Globalization.CultureInfo("ko");
Localize.SetCulture(cultureInfo); // flips THIS circuit, raises CultureChanged
var isWeb = !NavManager.BaseUri.StartsWith("app://") && !NavManager.BaseUri.Contains("0.0.0.0");
if (isWeb)
{
// Persist via the account endpoint — circuits can't write cookies.
NavManager.NavigateTo(
$"/account-action/SetCulture?culture=ko&returnUrl=/some/page",
forceLoad: true);
}
else
{
// MAUI: safe to flip the process-wide culture in a single-user client.
SentenceStudio.LocalizationManager.Instance.SetCulture(cultureInfo);
}
Wire-up checklist (already done; reference only)
WebApp/Program.cshasAddLocalization()+Configure<RequestLocalizationOptions>(cookie + accept-language providers) +app.UseRequestLocalization()before auth.BlazorUIServiceExtensions.AddBlazorUIServicesregistersBlazorLocalizationServiceas scoped.AccountEndpoints.MapAccountEndpointsexposesGET /account-action/SetCulture.- MAUI
SentenceStudioAppBuilderregistersLocalizationInitializer(IMauiInitializeService) to applyUserProfile.DisplayLanguageat launch.
Supported cultures
Currently en and ko. To add a new culture:
- Create
AppResources.<tag>.resxwith all keys translated. - Add the tag to
supportedCulturesarray inWebApp/Program.cs. - Add the tag to the whitelist in
AccountEndpoints.SetCulture. - Add the option to Profile's
displayLanguagedropdown +UserProfile.DisplayCultureswitch expression.
Gotchas
AppResourcesisinternal. UseLocalizationManager.GetString. If you genuinely need direct access from another assembly, add a targeted public helper rather thanInternalsVisibleTothe world.- Don't forget
Dispose. If a component subscribes toCultureChangedwithout unsubscribing, the scoped service will hold a reference to the disposed component and the GC can't collect it. Always implementIDisposable. - Don't re-read strings into static fields.
private static readonly string Title = Localize["..."];won't update on culture change. Use properties or inline@Localize["..."]in markup. - Not every string should be a resource. Debug logs, log categories, exception messages to developers, and audit trails should stay in English. Localize only user-facing UI copy.