Setting the App's Language

~10 min Beginner

BPT ships with eight supported cultures out of the box and gives you five different ways to pick which one is active — from a visual picker your users see, to URL parameters, to programmatic overrides driven by tenant config.

Home / Learning / Setting the App's Language
The whole picture Three pieces collaborate. BptCulture is the catalog of supported cultures. BptCultureState is the cascading service that tracks the currently active one and translates resource keys. BptLanguage is the optional visual picker your users interact with. Each of the five "ways to set the language" below ultimately funnels into the same BptCultureState.

Supported cultures

BPT ships with eight cultures, each with its own resource translations, emoji + SVG flag, currency symbol, and number format defaults:

CodeLanguageCurrency
nb-NONorwegian BokmålNOK (kr)
en-GBEnglish (UK)GBP (£)
en-USEnglish (US)USD ($)
sv-SESwedishSEK (kr)
de-DEGermanEUR (€)
fr-FRFrenchEUR (€)
es-ESSpanishEUR (€)
zh-CNChinese (Simplified)CNY (¥)

The catalog lives in BptCulture.GetSupportedCultures(). Any culture code not in this list falls back to the closest supported neighbor via BptCulture.MapToSupported(...) — for example "en-AU" maps to en-US and "nb" maps to nb-NO.

The five ways to set the active culture

Way 1: Drop in the BptLanguage picker

The simplest path. Add the picker to your MainLayout and users pick their own language. Their selection automatically persists across visits via localStorage.

@using Bpt.Components.Controls <!-- Compact: just a flag icon that opens a dropdown --> <BptLanguage DisplayMode="LanguageDisplayMode.Compact" /> <!-- Full: flag + language name + culture code --> <BptLanguage DisplayMode="LanguageDisplayMode.Full" OnCultureChanged="OnCultureChanged" /> @code { private void OnCultureChanged(string code) { // code = "nb-NO", "en-GB", etc. Optional — the picker already updates BptCultureState. } }
Number-format overrides Some apps need locale-mixed formatting (e.g. Norwegian text but US number separators). Use DecimalSeparator and ThousandsSeparator on either BptLanguage or directly on BptCultureState to override the culture's defaults without changing the language.

Way 2: Pin the culture programmatically

When the active language is decided by application logic (per-tenant config, user profile in DB, A/B bucket), set it once on BptRootComponent at the top of your app:

<!-- App.razor (or MainLayout.razor) --> <BptRootComponent CultureCode="@_tenantCulture"> <Router AppAssembly="typeof(Program).Assembly"> ... </Router> </BptRootComponent> @code { [Inject] ITenantService TenantService { get; set; } = default!; private string _tenantCulture = "en-US"; protected override async Task OnInitializedAsync() { var tenant = await TenantService.GetCurrentAsync(); _tenantCulture = tenant.DefaultCultureCode; // e.g. "nb-NO" } }

For runtime changes (a user toggles their preferred language inside the app), inject BptCultureState and call SetCurrentCulture:

@inject BptCultureState CultureState <button @onclick='() => CultureState.SetCurrentCulture("sv-SE")'> Switch to Swedish </button>

Way 3: URL query parameter

For shareable language links, deep-links from email, or per-tenant subdomain routing, BptRootComponent reads a ?culture=... query parameter on first render:

https://example.com/products?culture=nb-NO https://example.com/products?culture=zh-CN

This takes precedence over the user's saved localStorage preference for that page load. Useful for landing pages where the language is part of the campaign URL.

Way 4: localStorage persistence

By default the BptLanguage picker persists the user's choice to localStorage under the key bpt_current_culture. On subsequent visits, BptLanguage reads that value in OnAfterRenderAsync and restores the language.

You can interact with this key from any other JS code on the page — for instance, syncing the language with a server-side cookie:

// Read what BptLanguage has stored. const culture = localStorage.getItem("bpt_current_culture"); // Mirror to a server-readable cookie so SSR-rendered pages know the culture too. document.cookie = `culture=${culture}; path=/; max-age=31536000`;

Way 5: Auto-detect from the browser

If none of the above apply (no URL param, no localStorage, no programmatic override), BPT falls back to the browser's navigator.language via BptCulture.MapToSupported(...):

Browser sendsBPT resolves to
en-AU, en-CA, en-INen-US
nb, nn, nonb-NO
de-AT, de-CHde-DE
zh-TW, zh-HKzh-CN
anything elseen-US

Reading the active culture in your own components

BptCultureState is registered as a root cascading value, so any component — Server or WebAssembly — can pick it up:

@code { [CascadingParameter] public BptCultureState? CultureState { get; set; } private string Greeting => CultureState?.CurrentCulture switch { "nb-NO" => "Hei!", "sv-SE" => "Hej!", "de-DE" => "Hallo!", "zh-CN" => "你好!", _ => "Hello!" }; // Look up translations from BPT's resx files (Bpt.Components.Common.Resources.BptTranslations). private string L(string key, string fallback) => CultureState?.T(key) ?? fallback; }

Quick comparison

MechanismPersists?Best for
<BptLanguage /> pickerlocalStorageEnd-user choice. The default for most apps.
BptRootComponent CultureCodePer-renderApp-wide lock driven by tenant or user profile.
BptCultureState.SetCurrentCulture(...)In-memory + localStorageRuntime switching from your own UI.
?culture=... query paramPer-requestEmail links, campaign landing pages, tenant subdomains.
Browser auto-detectNone (computed each time)Anonymous first-time visitors who haven't chosen yet.
Cross-circuit synchronization bptCulture.js broadcasts a CustomEvent whenever the culture changes. This means a Server-rendered island and a WebAssembly-rendered island on the same page see culture switches in real time without page reload — no extra wiring needed.

An unhandled error has occurred. Reload 🗙

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please reload the page.