ASP.NET Core Blazor globalization and localization

Note

This isn't the latest version of this article. To switch to the latest, use the ASP.NET Core version selector at the top of the table of contents.

Version selector

If the selector isn't visible in a narrow browser window, widen the window or select the vertical ellipsis () > Table of contents.

Table of contents selector

This article explains how to render globalized and localized content to users in different cultures and languages in a Blazor Server app. See the Blazor WebAssembly version of this article for guidance on standalone and hosted Blazor WebAssembly apps.

This article explains how to render globalized and localized content to users in different cultures and languages in a standalone or hosted (Client project) Blazor WebAssembly app. See the Blazor Server version of this article for guidance on Blazor Server apps.

For globalization, Blazor provides number and date formatting. For localization, Blazor renders content using the .NET Resources system.

A limited set of ASP.NET Core's localization features are supported:

Supported: IStringLocalizer and IStringLocalizer<T> are supported in Blazor apps.

Not supported: IHtmlLocalizer, IViewLocalizer, and Data Annotations localization are ASP.NET Core MVC features and not supported in Blazor apps.

This article describes how to use Blazor's globalization and localization features based on:

  • The Accept-Language header, which is set by the browser based on a user's language preferences in browser settings.
  • A culture set by the app not based on the value of the Accept-Language header. The setting can be static for all users or dynamic based on app logic. When the setting is based on the user's preference, the setting is usually saved for reload on future visits.

For additional general information, see the following resources:

Note

Often, the terms language and culture are used interchangeably when dealing with globalization and localization concepts.

In this article, language refers to selections made by a user in their browser's settings. The user's language selections are submitted in browser requests in the Accept-Language header. Browser settings usually use the word "language" in the UI.

Culture pertains to members of .NET and Blazor API. For example, a user's request can include the Accept-Language header specifying a language from the user's perspective, but the app ultimately sets the CurrentCulture ("culture") property from the language that the user requested. API usually uses the word "culture" in its member names.

Globalization

The @bind attribute directive applies formats and parses values for display based on the user's first preferred language that the app supports. @bind supports the @bind:culture parameter to provide a System.Globalization.CultureInfo for parsing and formatting a value.

The current culture can be accessed from the System.Globalization.CultureInfo.CurrentCulture property.

CultureInfo.InvariantCulture is used for the following field types (<input type="{TYPE}" />, where the {TYPE} placeholder is the type):

  • date
  • number

The preceding field types:

  • Are displayed using their appropriate browser-based formatting rules.
  • Can't contain free-form text.
  • Provide user interaction characteristics based on the browser's implementation.

When using the date and number field types, specifying a culture with @bind:culture isn't recommended because Blazor provides built-in support to render values in the current culture.

The following field types have specific formatting requirements and aren't currently supported by Blazor because they aren't supported by all of the major browsers:

  • datetime-local
  • month
  • week

For current browser support of the preceding types, see Can I use.

.NET globalization and International Components for Unicode (ICU) support

Blazor WebAssembly uses a reduced globalization API and set of built-in International Components for Unicode (ICU) locales. For more information, see .NET globalization and ICU: ICU on WebAssembly.

To load a custom ICU data file to control the app's locales, see WASM Globalization Icu. Currently, manually building the custom ICU data file is required. .NET tooling to ease the process of creating the file is planned for a future .NET 8.0 preview release.

.NET globalization and International Components for Unicode (ICU) support

Blazor WebAssembly uses a reduced globalization API and set of built-in International Components for Unicode (ICU) locales. For more information, see .NET globalization and ICU: ICU on WebAssembly.

Loading a custom subset of locales in a Blazor WebAssembly app is supported in .NET 8 or later. For more information, access this section for an 8.0 or later version of this article.

Invariant globalization

If the app doesn't require localization, configure the app to support the invariant culture, which is generally based on United States English (en-US). Set the InvariantGlobalization property to true in the app's project file (.csproj):

<PropertyGroup>
  <InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

Alternatively, configure invariant globalization with the following approaches:

  • In runtimeconfig.json:

    {
      "runtimeOptions": {
        "configProperties": {
          "System.Globalization.Invariant": true
        }
      }
    }
    
  • With an environment variable:

    • Key: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
    • Value: true or 1

For more information, see Runtime configuration options for globalization (.NET documentation).

Demonstration component

The following CultureExample1 component can be used to demonstrate Blazor globalization and localization concepts covered by this article.

Pages/CultureExample1.razor:

@page "/culture-example-1"
@using System.Globalization

<h1>Culture Example 1</h1>

<p>
    <b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Rendered values</h2>

<ul>
    <li><b>Date</b>: @dt</li>
    <li><b>Number</b>: @number.ToString("N2")</li>
</ul>

<h2><code>&lt;input&gt;</code> elements that don't set a <code>type</code></h2>

<p>
    The following <code>&lt;input&gt;</code> elements use
    <code>CultureInfo.CurrentCulture</code>.
</p>

<ul>
    <li><label><b>Date:</b> <input @bind="dt" /></label></li>
    <li><label><b>Number:</b> <input @bind="number" /></label></li>
</ul>

<h2><code>&lt;input&gt;</code> elements that set a <code>type</code></h2>

<p>
    The following <code>&lt;input&gt;</code> elements use
    <code>CultureInfo.InvariantCulture</code>.
</p>

<ul>
    <li><label><b>Date:</b> <input type="date" @bind="dt" /></label></li>
    <li><label><b>Number:</b> <input type="number" @bind="number" /></label></li>
</ul>

@code {
    private DateTime dt = DateTime.Now;
    private double number = 1999.69;
}

The number string format (N2) in the preceding example (.ToString("N2")) is a standard .NET numeric format specifier. The N2 format is supported for all numeric types, includes a group separator, and renders up to two decimal places.

Optionally, add a menu item to the navigation in Shared/NavMenu.razor for the CultureExample1 component.

Dynamically set the culture from the Accept-Language header

The Accept-Language header is set by the browser and controlled by the user's language preferences in browser settings. In browser settings, a user sets one or more preferred languages in order of preference. The order of preference is used by the browser to set quality values (q, 0-1) for each language in the header. The following example specifies United States English, English, and Chilean Spanish with a preference for United States English or English:

Accept-Language: en-US,en;q=0.9,es-CL;q=0.8

The app's culture is set by matching the first requested language that matches a supported culture of the app.

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the app's project file (.csproj):

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

Note

If the app's specification requires limiting the supported cultures to an explicit list, see the Dynamically set the culture by user preference section of this article.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Program.cs:

builder.Services.AddLocalization();

Specify the app's supported cultures in Program.cs immediately after Routing Middleware is added to the processing pipeline. The following example configures supported cultures for United States English and Chilean Spanish:

app.UseRequestLocalization(new RequestLocalizationOptions()
    .AddSupportedCultures(new[] { "en-US", "es-CL" })
    .AddSupportedUICultures(new[] { "en-US", "es-CL" }));

For information on ordering the Localization Middleware in the middleware pipeline of Program.cs, see ASP.NET Core Middleware.

Use the CultureExample1 component shown in the Demonstration component section to study how globalization works. Issue a request with United States English (en-US). Switch to Chilean Spanish (es-CL) in the browser's language settings. Request the webpage again.

Note

Some browsers force you to use the default language setting for both requests and the browser's own UI settings. This can make changing the language back to one that you understand difficult because all of the setting UI screens might end up in a language that you can't read. A browser such as Opera is a good choice for testing because it permits you to set a default language for webpage requests but leave the browser's settings UI in your language.

When the culture is United States English (en-US), the rendered component uses month/day date formatting (6/7), 12-hour time (AM/PM), and comma separators in numbers with a dot for the decimal value (1,999.69):

  • Date: 6/7/2021 6:45:22 AM
  • Number: 1,999.69

When the culture is Chilean Spanish (es-CL), the rendered component uses day/month date formatting (7/6), 24-hour time, and period separators in numbers with a comma for the decimal value (1.999,69):

  • Date: 7/6/2021 6:49:38
  • Number: 1.999,69

Statically set the culture

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the app's project file (.csproj):

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

The app's culture can be set in JavaScript when Blazor starts with the applicationCulture Blazor start option. The following example configures the app to launch using the United States English (en-US) culture.

  • In wwwroot/index.html, prevent Blazor autostart by adding autostart="false" to Blazor's <script> tag:

    <script src="_framework/blazor.webassembly.js" autostart="false"></script>
    
  • Add the following <script> block after Blazor's <script> tag and before the closing </body> tag:

    <script>
      Blazor.start({
        applicationCulture: 'en-US'
      });
    </script>
    

The value for applicationCulture must conform to the BCP-47 language tag format. For more information on Blazor startup, see ASP.NET Core Blazor startup.

An alternative to setting the culture Blazor's start option is to set the culture in C# code. Set CultureInfo.DefaultThreadCurrentCulture and CultureInfo.DefaultThreadCurrentUICulture in Program.cs to the same culture.

Add the System.Globalization namespace to Program.cs:

using System.Globalization;

Add the culture settings before the line that builds and runs the WebAssemblyHostBuilder (await builder.Build().RunAsync();):

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");

Important

Always set DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture to the same culture in order to use IStringLocalizer and IStringLocalizer<T>.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Program.cs:

builder.Services.AddLocalization();

Specify the static culture in Program.cs immediately after Routing Middleware is added to the processing pipeline. The following example configures United States English:

app.UseRequestLocalization("en-US");

The culture value for UseRequestLocalization must conform to the BCP-47 language tag format.

For information on ordering the Localization Middleware in the middleware pipeline of Program.cs, see ASP.NET Core Middleware.

Use the CultureExample1 component shown in the Demonstration component section to study how globalization works. Issue a request with United States English (en-US). Switch to Chilean Spanish (es-CL) in the browser's language settings. Request the webpage again. When the requested language is Chilean Spanish, the app's culture remains United States English (en-US).

Dynamically set the culture by user preference

Examples of locations where an app might store a user's preference include in browser local storage (common in Blazor WebAssembly apps), in a localization cookie or database (common in Blazor Server apps), or in an external service attached to an external database and accessed by a web API. The following example demonstrates how to use browser local storage.

Add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the project file:

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

The app's culture in a Blazor WebAssembly app is set using the Blazor framework's API. A user's culture selection can be persisted in browser local storage.

In the wwwroot/index.html file after Blazor's <script> tag and before the closing </body> tag, provide JS functions to get and set the user's culture selection with browser local storage:

<script>
  window.blazorCulture = {
    get: () => window.localStorage['BlazorCulture'],
    set: (value) => window.localStorage['BlazorCulture'] = value
  };
</script>

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Add the namespaces for System.Globalization and Microsoft.JSInterop to the top of Program.cs:

using System.Globalization;
using Microsoft.JSInterop;

Remove the following line from Program.cs:

- await builder.Build().RunAsync();

Replace the preceding line with the following code. The code adds Blazor's localization service to the app's service collection with AddLocalization and uses JS interop to call into JS and retrieve the user's culture selection from local storage. If local storage doesn't contain a culture for the user, the code sets a default value of United States English (en-US).

builder.Services.AddLocalization();

var host = builder.Build();

CultureInfo culture;
var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");

if (result != null)
{
    culture = new CultureInfo(result);
}
else
{
    culture = new CultureInfo("en-US");
    await js.InvokeVoidAsync("blazorCulture.set", "en-US");
}

CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;

await host.RunAsync();

Important

Always set DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture to the same culture in order to use IStringLocalizer and IStringLocalizer<T>.

The following CultureSelector component shows how to perform the following actions:

  • Set the user's culture selection into browser local storage via JS interop.
  • Reload the component that they requested (forceLoad: true), which uses the updated culture.

The CultureSelector component is placed in the Shared folder for use throughout the app.

Shared/CultureSelector.razor:

@using  System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation

<p>
    <label>
        Select your locale:
        <select @bind="Culture">
            @foreach (var culture in supportedCultures)
            {
                <option value="@culture">@culture.DisplayName</option>
            }
        </select>
    </label>
</p>

@code
{
    private CultureInfo[] supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("es-CL"),
    };

    private CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var js = (IJSInProcessRuntime)JS;
                js.InvokeVoid("blazorCulture.set", value.Name);

                Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
            }
        }
    }
}

Inside the closing tag of the </main> element in Shared/MainLayout.razor, add the CultureSelector component:

<article class="bottom-row px-4">
    <CultureSelector />
</article>

Examples of locations where an app might store a user's preference include in browser local storage (common in Blazor WebAssembly apps), in a localization cookie or database (common in Blazor Server apps), or in an external service attached to an external database and accessed by a web API. The following example demonstrates how to use a localization cookie.

Add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Program.cs:

builder.Services.AddLocalization();

Set the app's default and supported cultures with RequestLocalizationOptions.

In Program.cs immediately after Routing Middleware is added to the processing pipeline:

var supportedCultures = new[] { "en-US", "es-CL" };
var localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

For information on ordering the Localization Middleware in the middleware pipeline of Program.cs, see ASP.NET Core Middleware.

The following example shows how to set the current culture in a cookie that can be read by the Localization Middleware.

Modifications to the Pages/_Host.cshtml file require the following namespaces:

Pages/_Host.cshtml:

+ @using System.Globalization
+ @using Microsoft.AspNetCore.Localization
+ @{
+     this.HttpContext.Response.Cookies.Append(
+         CookieRequestCultureProvider.DefaultCookieName,
+         CookieRequestCultureProvider.MakeCookieValue(
+             new RequestCulture(
+                 CultureInfo.CurrentCulture,
+                 CultureInfo.CurrentUICulture)));
+ }

For information on ordering the Localization Middleware in the middleware pipeline of Program.cs, see ASP.NET Core Middleware.

If the app isn't configured to process controller actions:

  • Add MVC services by calling AddControllers on the service collection in Program.cs:

    builder.Services.AddControllers();
    
  • Add controller endpoint routing in Program.cs by calling MapControllers on the IEndpointRouteBuilder:

    app.MapControllers();
    

    The following example shows the call to UseEndpoints after the line is added:

    app.MapControllers();
    app.MapBlazorHub();
    app.MapFallbackToPage("/_Host");
    

To provide UI to allow a user to select a culture, use a redirect-based approach with a localization cookie. 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. The process is similar to what happens in a web app when a user attempts to access a secure resource, where the user is redirected to a sign-in page and then redirected back to the original resource.

Controllers/CultureController.cs:

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

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

        return LocalRedirect(redirectUri);
    }
}

Warning

Use the LocalRedirect action result to prevent open redirect attacks. For more information, see Prevent open redirect attacks in ASP.NET Core.

The following CultureSelector component shows how to call the Set method of the CultureController with the new culture. The component is placed in the Shared folder for use throughout the app.

Shared/CultureSelector.razor:

@using  System.Globalization
@inject NavigationManager Navigation

<p>
    <label>
        Select your locale:
        <select @bind="Culture">
            @foreach (var culture in supportedCultures)
            {
                <option value="@culture">@culture.DisplayName</option>
            }
        </select>
    </label>
</p>

@code
{
    private CultureInfo[] supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("es-CL"),
    };

    protected override void OnInitialized()
    {
        Culture = CultureInfo.CurrentCulture;
    }

    private CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var uri = new Uri(Navigation.Uri)
                    .GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
                var cultureEscaped = Uri.EscapeDataString(value.Name);
                var uriEscaped = Uri.EscapeDataString(uri);

                Navigation.NavigateTo(
                    $"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
                    forceLoad: true);
            }
        }
    }
}

Inside the closing </main> tag in Shared/MainLayout.razor, add the CultureSelector component:

<article class="bottom-row px-4">
    <CultureSelector />
</article>

Use the CultureExample1 component shown in the Demonstration component section to study how the preceding example works.

Localization

If the app doesn't already support culture selection per the Dynamically set the culture by user preference section of this article, add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the app's project file (.csproj):

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

In Program.cs, add namespace the namespace for System.Globalization to the top of the file:

using System.Globalization;

Add Blazor's localization service to the app's service collection with AddLocalization in Program.cs:

builder.Services.AddLocalization();

Use Localization Middleware to set the app's culture.

If the app doesn't already support culture selection per the Dynamically set the culture by user preference section of this article:

  • Add localization services to the app with AddLocalization.
  • Specify the app's default and supported cultures in Program.cs. The following example configures supported cultures for United States English and Chilean Spanish.

In Program.cs:

builder.Services.AddLocalization();

In Program.cs immediately after Routing Middleware is added to the processing pipeline:

var supportedCultures = new[] { "en-US", "es-CL" };
var localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

For information on ordering the Localization Middleware in the middleware pipeline of Program.cs, see ASP.NET Core Middleware.

If the app should localize resources based on storing a user's culture setting, use a localization culture cookie. 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, the recommended approach is to use a localization culture cookie. See the Dynamically set the culture by user preference section of this article to see an example Razor expression that persists the user's culture selection.

The example of localized resources in this section works with the prior examples in this article where the app's supported cultures are English (en) as a default locale and Spanish (es) as a user-selectable or browser-specified alternate locale.

Create resources for each locale. In the following example, resources are created for a default Greeting string:

  • English: Hello, World!
  • Spanish (es): ¡Hola, Mundo!

Note

The following resource file can be added in Visual Studio by right-clicking the project's Pages folder and selecting Add > New Item > Resources File. Name the file CultureExample2.resx. When the editor appears, provide data for a new entry. Set the Name to Greeting and Value to Hello, World!. Save the file.

Pages/CultureExample2.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Greeting" xml:space="preserve">
    <value>Hello, World!</value>
  </data>
</root>

Note

The following resource file can be added in Visual Studio by right-clicking the project's Pages folder and selecting Add > New Item > Resources File. Name the file CultureExample2.es.resx. When the editor appears, provide data for a new entry. Set the Name to Greeting and Value to ¡Hola, Mundo!. Save the file.

Pages/CultureExample2.es.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Greeting" xml:space="preserve">
    <value>¡Hola, Mundo!</value>
  </data>
</root>

The following component demonstrates the use of the localized Greeting string with IStringLocalizer<T>. The Razor markup @Loc["Greeting"] in the following example localizes the string keyed to the Greeting value, which is set in the preceding resource files.

Add the namespace for Microsoft.Extensions.Localization to the app's _Imports.razor file:

@using Microsoft.Extensions.Localization

Pages/CultureExample2.razor:

@page "/culture-example-2"
@using System.Globalization
@inject IStringLocalizer<CultureExample2> Loc

<h1>Culture Example 2</h1>

<p>
    <b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Greeting</h2>

<p>
    @Loc["Greeting"]
</p>

<p>
    @greeting
</p>

@code {
    private string? greeting;

    protected override void OnInitialized()
    {
        greeting = Loc["Greeting"];
    }
}

Optionally, add a menu item to the navigation in Shared/NavMenu.razor for the CultureExample2 component.

Culture provider reference source

To further understand how the Blazor framework processes localization, see the WebAssemblyCultureProvider class in the ASP.NET Core reference source.

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Shared resources

To create localization shared resources, adopt the following approach.

  • Create a dummy class with an arbitrary class name. In the following example:

    • The app uses the BlazorSample namespace, and localization assets use the BlazorSample.Localization namespace.
    • The dummy class is named SharedResource.
    • The class file is placed in a Localization folder at the root of the app.

    Localization/SharedResource.cs:

    namespace BlazorSample.Localization;
    
    public class SharedResource
    {
    }
    
  • Create the shared resource files with a Build Action of Embedded resource. In the following example:

    • The files are placed in the Localization folder with the dummy SharedResource class (Localization/SharedResource.cs).

    • Name the resource files to match the name of the dummy class. The following example files include a default localization file and a file for Spanish (es) localization.

    • Localization/SharedResource.resx

    • Localization/SharedResource.es.resx

    Note

    Localization is resource path that can be set via LocalizationOptions.

  • To reference the dummy class for an injected IStringLocalizer<T> in a Razor component, either place an @using directive for the localization namespace or include the localization namespace in the dummy class reference. In the following examples:

    • The first example states the Localization namespace for the SharedResource dummy class with an @using directive.
    • The second example states the SharedResource dummy class's namespace explicitly.

    In a Razor component, use either of the following approaches:

    @using Localization
    @inject IStringLocalizer<SharedResource> Loc
    
    @inject IStringLocalizer<Localization.SharedResource> Loc
    

For additional guidance, see Globalization and localization in ASP.NET Core.

Additional resources

For globalization, Blazor provides number and date formatting. For localization, Blazor renders content using the .NET Resources system.

A limited set of ASP.NET Core's localization features are supported:

Supported: IStringLocalizer and IStringLocalizer<T> are supported in Blazor apps.

Not supported: IHtmlLocalizer, IViewLocalizer, and Data Annotations localization are ASP.NET Core MVC features and not supported in Blazor apps.

This article describes how to use Blazor's globalization and localization features based on:

  • The Accept-Language header, which is set by the browser based on a user's language preferences in browser settings.
  • A culture set by the app not based on the value of the Accept-Language header. The setting can be static for all users or dynamic based on app logic. When the setting is based on the user's preference, the setting is usually saved for reload on future visits.

For additional general information, see the following resources:

Note

Often, the terms language and culture are used interchangeably when dealing with globalization and localization concepts.

In this article, language refers to selections made by a user in their browser's settings. The user's language selections are submitted in browser requests in the Accept-Language header. Browser settings usually use the word "language" in the UI.

Culture pertains to members of .NET and Blazor API. For example, a user's request can include the Accept-Language header specifying a language from the user's perspective, but the app ultimately sets the CurrentCulture ("culture") property from the language that the user requested. API usually uses the word "culture" in its member names.

Globalization

The @bind attribute directive applies formats and parses values for display based on the user's first preferred language that the app supports. @bind supports the @bind:culture parameter to provide a System.Globalization.CultureInfo for parsing and formatting a value.

The current culture can be accessed from the System.Globalization.CultureInfo.CurrentCulture property.

CultureInfo.InvariantCulture is used for the following field types (<input type="{TYPE}" />, where the {TYPE} placeholder is the type):

  • date
  • number

The preceding field types:

  • Are displayed using their appropriate browser-based formatting rules.
  • Can't contain free-form text.
  • Provide user interaction characteristics based on the browser's implementation.

When using the date and number field types, specifying a culture with @bind:culture isn't recommended because Blazor provides built-in support to render values in the current culture.

The following field types have specific formatting requirements and aren't currently supported by Blazor because they aren't supported by all of the major browsers:

  • datetime-local
  • month
  • week

For current browser support of the preceding types, see Can I use.

.NET globalization and International Components for Unicode (ICU) support

Blazor WebAssembly uses a reduced globalization API and set of built-in International Components for Unicode (ICU) locales. For more information, see .NET globalization and ICU: ICU on WebAssembly.

Loading a custom subset of locales in a Blazor WebAssembly app is supported in .NET 8 or later. For more information, access this section for an 8.0 or later version of this article.

Invariant globalization

If the app doesn't require localization, configure the app to support the invariant culture, which is generally based on United States English (en-US). Set the InvariantGlobalization property to true in the app's project file (.csproj):

<PropertyGroup>
  <InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

Alternatively, configure invariant globalization with the following approaches:

  • In runtimeconfig.json:

    {
      "runtimeOptions": {
        "configProperties": {
          "System.Globalization.Invariant": true
        }
      }
    }
    
  • With an environment variable:

    • Key: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
    • Value: true or 1

For more information, see Runtime configuration options for globalization (.NET documentation).

Demonstration component

The following CultureExample1 component can be used to demonstrate Blazor globalization and localization concepts covered by this article.

Pages/CultureExample1.razor:

@page "/culture-example-1"
@using System.Globalization

<h1>Culture Example 1</h1>

<p>
    <b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Rendered values</h2>

<ul>
    <li><b>Date</b>: @dt</li>
    <li><b>Number</b>: @number.ToString("N2")</li>
</ul>

<h2><code>&lt;input&gt;</code> elements that don't set a <code>type</code></h2>

<p>
    The following <code>&lt;input&gt;</code> elements use
    <code>CultureInfo.CurrentCulture</code>.
</p>

<ul>
    <li><label><b>Date:</b> <input @bind="dt" /></label></li>
    <li><label><b>Number:</b> <input @bind="number" /></label></li>
</ul>

<h2><code>&lt;input&gt;</code> elements that set a <code>type</code></h2>

<p>
    The following <code>&lt;input&gt;</code> elements use
    <code>CultureInfo.InvariantCulture</code>.
</p>

<ul>
    <li><label><b>Date:</b> <input type="date" @bind="dt" /></label></li>
    <li><label><b>Number:</b> <input type="number" @bind="number" /></label></li>
</ul>

@code {
    private DateTime dt = DateTime.Now;
    private double number = 1999.69;
}

The number string format (N2) in the preceding example (.ToString("N2")) is a standard .NET numeric format specifier. The N2 format is supported for all numeric types, includes a group separator, and renders up to two decimal places.

Optionally, add a menu item to the navigation in Shared/NavMenu.razor for the CultureExample1 component.

Dynamically set the culture from the Accept-Language header

The Accept-Language header is set by the browser and controlled by the user's language preferences in browser settings. In browser settings, a user sets one or more preferred languages in order of preference. The order of preference is used by the browser to set quality values (q, 0-1) for each language in the header. The following example specifies United States English, English, and Chilean Spanish with a preference for United States English or English:

Accept-Language: en-US,en;q=0.9,es-CL;q=0.8

The app's culture is set by matching the first requested language that matches a supported culture of the app.

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the app's project file (.csproj):

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

Note

If the app's specification requires limiting the supported cultures to an explicit list, see the Dynamically set the culture by user preference section of this article.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Program.cs:

builder.Services.AddLocalization();

Specify the app's supported cultures in Program.cs immediately after Routing Middleware is added to the processing pipeline. The following example configures supported cultures for United States English and Chilean Spanish:

app.UseRequestLocalization(new RequestLocalizationOptions()
    .AddSupportedCultures(new[] { "en-US", "es-CL" })
    .AddSupportedUICultures(new[] { "en-US", "es-CL" }));

For information on ordering the Localization Middleware in the middleware pipeline of Program.cs, see ASP.NET Core Middleware.

Use the CultureExample1 component shown in the Demonstration component section to study how globalization works. Issue a request with United States English (en-US). Switch to Chilean Spanish (es-CL) in the browser's language settings. Request the webpage again.

Note

Some browsers force you to use the default language setting for both requests and the browser's own UI settings. This can make changing the language back to one that you understand difficult because all of the setting UI screens might end up in a language that you can't read. A browser such as Opera is a good choice for testing because it permits you to set a default language for webpage requests but leave the browser's settings UI in your language.

When the culture is United States English (en-US), the rendered component uses month/day date formatting (6/7), 12-hour time (AM/PM), and comma separators in numbers with a dot for the decimal value (1,999.69):

  • Date: 6/7/2021 6:45:22 AM
  • Number: 1,999.69

When the culture is Chilean Spanish (es-CL), the rendered component uses day/month date formatting (7/6), 24-hour time, and period separators in numbers with a comma for the decimal value (1.999,69):

  • Date: 7/6/2021 6:49:38
  • Number: 1.999,69

Statically set the culture

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the app's project file (.csproj):

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

The app's culture can be set in JavaScript when Blazor starts with the applicationCulture Blazor start option. The following example configures the app to launch using the United States English (en-US) culture.

  • In wwwroot/index.html, prevent Blazor autostart by adding autostart="false" to Blazor's <script> tag:

    <script src="_framework/blazor.webassembly.js" autostart="false"></script>
    
  • Add the following <script> block after Blazor's <script> tag and before the closing </body> tag:

    <script>
      Blazor.start({
        applicationCulture: 'en-US'
      });
    </script>
    

The value for applicationCulture must conform to the BCP-47 language tag format. For more information on Blazor startup, see ASP.NET Core Blazor startup.

An alternative to setting the culture Blazor's start option is to set the culture in C# code. Set CultureInfo.DefaultThreadCurrentCulture and CultureInfo.DefaultThreadCurrentUICulture in Program.cs to the same culture.

Add the System.Globalization namespace to Program.cs:

using System.Globalization;

Add the culture settings before the line that builds and runs the WebAssemblyHostBuilder (await builder.Build().RunAsync();):

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");

Important

Always set DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture to the same culture in order to use IStringLocalizer and IStringLocalizer<T>.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Program.cs:

builder.Services.AddLocalization();

Specify the static culture in Program.cs immediately after Routing Middleware is added to the processing pipeline. The following example configures United States English:

app.UseRequestLocalization("en-US");

The culture value for UseRequestLocalization must conform to the BCP-47 language tag format.

For information on ordering the Localization Middleware in the middleware pipeline of Program.cs, see ASP.NET Core Middleware.

Use the CultureExample1 component shown in the Demonstration component section to study how globalization works. Issue a request with United States English (en-US). Switch to Chilean Spanish (es-CL) in the browser's language settings. Request the webpage again. When the requested language is Chilean Spanish, the app's culture remains United States English (en-US).

Dynamically set the culture by user preference

Examples of locations where an app might store a user's preference include in browser local storage (common in Blazor WebAssembly apps), in a localization cookie or database (common in Blazor Server apps), or in an external service attached to an external database and accessed by a web API. The following example demonstrates how to use browser local storage.

Add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the project file:

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

The app's culture in a Blazor WebAssembly app is set using the Blazor framework's API. A user's culture selection can be persisted in browser local storage.

In the wwwroot/index.html file after Blazor's <script> tag and before the closing </body> tag, provide JS functions to get and set the user's culture selection with browser local storage:

<script>
  window.blazorCulture = {
    get: () => window.localStorage['BlazorCulture'],
    set: (value) => window.localStorage['BlazorCulture'] = value
  };
</script>

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Add the namespaces for System.Globalization and Microsoft.JSInterop to the top of Program.cs:

using System.Globalization;
using Microsoft.JSInterop;

Remove the following line from Program.cs:

- await builder.Build().RunAsync();

Replace the preceding line with the following code. The code adds Blazor's localization service to the app's service collection with AddLocalization and uses JS interop to call into JS and retrieve the user's culture selection from local storage. If local storage doesn't contain a culture for the user, the code sets a default value of United States English (en-US).

builder.Services.AddLocalization();

var host = builder.Build();

CultureInfo culture;
var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");

if (result != null)
{
    culture = new CultureInfo(result);
}
else
{
    culture = new CultureInfo("en-US");
    await js.InvokeVoidAsync("blazorCulture.set", "en-US");
}

CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;

await host.RunAsync();

Important

Always set DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture to the same culture in order to use IStringLocalizer and IStringLocalizer<T>.

The following CultureSelector component shows how to perform the following actions:

  • Set the user's culture selection into browser local storage via JS interop.
  • Reload the component that they requested (forceLoad: true), which uses the updated culture.

The CultureSelector component is placed in the Shared folder for use throughout the app.

Shared/CultureSelector.razor:

@using  System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation

<p>
    <label>
        Select your locale:
        <select @bind="Culture">
            @foreach (var culture in supportedCultures)
            {
                <option value="@culture">@culture.DisplayName</option>
            }
        </select>
    </label>
</p>

@code
{
    private CultureInfo[] supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("es-CL"),
    };

    private CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var js = (IJSInProcessRuntime)JS;
                js.InvokeVoid("blazorCulture.set", value.Name);

                Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
            }
        }
    }
}

Inside the closing tag of the <main> element in Shared/MainLayout.razor, add the CultureSelector component:

<div class="bottom-row px-4">
    <CultureSelector />
</div>

Examples of locations where an app might store a user's preference include in browser local storage (common in Blazor WebAssembly apps), in a localization cookie or database (common in Blazor Server apps), or in an external service attached to an external database and accessed by a web API. The following example demonstrates how to use a localization cookie.

Add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Program.cs:

builder.Services.AddLocalization();

Set the app's default and supported cultures with RequestLocalizationOptions.

In Program.cs immediately after Routing Middleware is added to the processing pipeline:

var supportedCultures = new[] { "en-US", "es-CL" };
var localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

For information on ordering the Localization Middleware in the middleware pipeline of Program.cs, see ASP.NET Core Middleware.

The following example shows how to set the current culture in a cookie that can be read by the Localization Middleware.

Modifications to the Pages/_Host.cshtml file require the following namespaces:

Pages/_Host.cshtml:

@page "/"
@namespace {NAMESPACE}.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@{
    Layout = "_Layout";
    this.HttpContext.Response.Cookies.Append(
        CookieRequestCultureProvider.DefaultCookieName,
        CookieRequestCultureProvider.MakeCookieValue(
            new RequestCulture(
                CultureInfo.CurrentCulture,
                CultureInfo.CurrentUICulture)));
}

<component type="typeof(App)" render-mode="ServerPrerendered" />

In the preceding example, the {NAMESPACE} placeholder is the app's assembly name.

For information on ordering the Localization Middleware in the middleware pipeline of Program.cs, see ASP.NET Core Middleware.

If the app isn't configured to process controller actions:

  • Add MVC services by calling AddControllers on the service collection in Program.cs:

    builder.Services.AddControllers();
    
  • Add controller endpoint routing in Program.cs by calling MapControllers on the IEndpointRouteBuilder:

    app.MapControllers();
    

    The following example shows the call to UseEndpoints after the line is added:

    app.MapControllers();
    app.MapBlazorHub();
    app.MapFallbackToPage("/_Host");
    

To provide UI to allow a user to select a culture, use a redirect-based approach with a localization cookie. 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. The process is similar to what happens in a web app when a user attempts to access a secure resource, where the user is redirected to a sign-in page and then redirected back to the original resource.

Controllers/CultureController.cs:

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

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

        return LocalRedirect(redirectUri);
    }
}

Warning

Use the LocalRedirect action result to prevent open redirect attacks. For more information, see Prevent open redirect attacks in ASP.NET Core.

The following CultureSelector component shows how to call the Set method of the CultureController with the new culture. The component is placed in the Shared folder for use throughout the app.

Shared/CultureSelector.razor:

@using  System.Globalization
@inject NavigationManager Navigation

<p>
    <label>
        Select your locale:
        <select @bind="Culture">
            @foreach (var culture in supportedCultures)
            {
                <option value="@culture">@culture.DisplayName</option>
            }
        </select>
    </label>
</p>

@code
{
    private CultureInfo[] supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("es-CL"),
    };

    protected override void OnInitialized()
    {
        Culture = CultureInfo.CurrentCulture;
    }

    private CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var uri = new Uri(Navigation.Uri)
                    .GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
                var cultureEscaped = Uri.EscapeDataString(value.Name);
                var uriEscaped = Uri.EscapeDataString(uri);

                Navigation.NavigateTo(
                    $"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
                    forceLoad: true);
            }
        }
    }
}

Inside the closing </div> tag of the <main> element in Shared/MainLayout.razor, add the CultureSelector component:

<div class="bottom-row px-4">
    <CultureSelector />
</div>

Use the CultureExample1 component shown in the Demonstration component section to study how the preceding example works.

Localization

If the app doesn't already support culture selection per the Dynamically set the culture by user preference section of this article, add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the app's project file (.csproj):

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

In Program.cs, add namespace the namespace for System.Globalization to the top of the file:

using System.Globalization;

Add Blazor's localization service to the app's service collection with AddLocalization in Program.cs:

builder.Services.AddLocalization();

Use Localization Middleware to set the app's culture.

If the app doesn't already support culture selection per the Dynamically set the culture by user preference section of this article:

  • Add localization services to the app with AddLocalization.
  • Specify the app's default and supported cultures in Program.cs. The following example configures supported cultures for United States English and Chilean Spanish.

In Program.cs:

builder.Services.AddLocalization();

In Program.cs immediately after Routing Middleware is added to the processing pipeline:

var supportedCultures = new[] { "en-US", "es-CL" };
var localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

For information on ordering the Localization Middleware in the middleware pipeline of Program.cs, see ASP.NET Core Middleware.

If the app should localize resources based on storing a user's culture setting, use a localization culture cookie. 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, the recommended approach is to use a localization culture cookie. See the Dynamically set the culture by user preference section of this article to see an example Razor expression that persists the user's culture selection.

The example of localized resources in this section works with the prior examples in this article where the app's supported cultures are English (en) as a default locale and Spanish (es) as a user-selectable or browser-specified alternate locale.

Create resources for each locale. In the following example, resources are created for a default Greeting string:

  • English: Hello, World!
  • Spanish (es): ¡Hola, Mundo!

Note

The following resource file can be added in Visual Studio by right-clicking the project's Pages folder and selecting Add > New Item > Resources File. Name the file CultureExample2.resx. When the editor appears, provide data for a new entry. Set the Name to Greeting and Value to Hello, World!. Save the file.

Pages/CultureExample2.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Greeting" xml:space="preserve">
    <value>Hello, World!</value>
  </data>
</root>

Note

The following resource file can be added in Visual Studio by right-clicking the project's Pages folder and selecting Add > New Item > Resources File. Name the file CultureExample2.es.resx. When the editor appears, provide data for a new entry. Set the Name to Greeting and Value to ¡Hola, Mundo!. Save the file.

Pages/CultureExample2.es.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Greeting" xml:space="preserve">
    <value>¡Hola, Mundo!</value>
  </data>
</root>

The following component demonstrates the use of the localized Greeting string with IStringLocalizer<T>. The Razor markup @Loc["Greeting"] in the following example localizes the string keyed to the Greeting value, which is set in the preceding resource files.

Add the namespace for Microsoft.Extensions.Localization to the app's _Imports.razor file:

@using Microsoft.Extensions.Localization

Pages/CultureExample2.razor:

@page "/culture-example-2"
@using System.Globalization
@inject IStringLocalizer<CultureExample2> Loc

<h1>Culture Example 2</h1>

<p>
    <b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Greeting</h2>

<p>
    @Loc["Greeting"]
</p>

<p>
    @greeting
</p>

@code {
    private string? greeting;

    protected override void OnInitialized()
    {
        greeting = Loc["Greeting"];
    }
}

Optionally, add a menu item to the navigation in Shared/NavMenu.razor for the CultureExample2 component.

Culture provider reference source

To further understand how the Blazor framework processes localization, see the WebAssemblyCultureProvider class in the ASP.NET Core reference source.

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Shared resources

To create localization shared resources, adopt the following approach.

  • Create a dummy class with an arbitrary class name. In the following example:

    • The app uses the BlazorSample namespace, and localization assets use the BlazorSample.Localization namespace.
    • The dummy class is named SharedResource.
    • The class file is placed in a Localization folder at the root of the app.

    Localization/SharedResource.cs:

    namespace BlazorSample.Localization;
    
    public class SharedResource
    {
    }
    
  • Create the shared resource files with a Build Action of Embedded resource. In the following example:

    • The files are placed in the Localization folder with the dummy SharedResource class (Localization/SharedResource.cs).

    • Name the resource files to match the name of the dummy class. The following example files include a default localization file and a file for Spanish (es) localization.

    • Localization/SharedResource.resx

    • Localization/SharedResource.es.resx

    Note

    Localization is resource path that can be set via LocalizationOptions.

  • To reference the dummy class for an injected IStringLocalizer<T> in a Razor component, either place an @using directive for the localization namespace or include the localization namespace in the dummy class reference. In the following examples:

    • The first example states the Localization namespace for the SharedResource dummy class with an @using directive.
    • The second example states the SharedResource dummy class's namespace explicitly.

    In a Razor component, use either of the following approaches:

    @using Localization
    @inject IStringLocalizer<SharedResource> Loc
    
    @inject IStringLocalizer<Localization.SharedResource> Loc
    

For additional guidance, see Globalization and localization in ASP.NET Core.

Additional resources

For globalization, Blazor provides number and date formatting. For localization, Blazor renders content using the .NET Resources system.

A limited set of ASP.NET Core's localization features are supported:

Supported: IStringLocalizer and IStringLocalizer<T> are supported in Blazor apps.

Not supported: IHtmlLocalizer, IViewLocalizer, and Data Annotations localization are ASP.NET Core MVC features and not supported in Blazor apps.

This article describes how to use Blazor's globalization and localization features based on:

  • The Accept-Language header, which is set by the browser based on a user's language preferences in browser settings.
  • A culture set by the app not based on the value of the Accept-Language header. The setting can be static for all users or dynamic based on app logic. When the setting is based on the user's preference, the setting is usually saved for reload on future visits.

For additional general information, see the following resources:

Note

Often, the terms language and culture are used interchangeably when dealing with globalization and localization concepts.

In this article, language refers to selections made by a user in their browser's settings. The user's language selections are submitted in browser requests in the Accept-Language header. Browser settings usually use the word "language" in the UI.

Culture pertains to members of .NET and Blazor API. For example, a user's request can include the Accept-Language header specifying a language from the user's perspective, but the app ultimately sets the CurrentCulture ("culture") property from the language that the user requested. API usually uses the word "culture" in its member names.

Globalization

The @bind attribute directive applies formats and parses values for display based on the user's first preferred language that the app supports. @bind supports the @bind:culture parameter to provide a System.Globalization.CultureInfo for parsing and formatting a value.

The current culture can be accessed from the System.Globalization.CultureInfo.CurrentCulture property.

CultureInfo.InvariantCulture is used for the following field types (<input type="{TYPE}" />, where the {TYPE} placeholder is the type):

  • date
  • number

The preceding field types:

  • Are displayed using their appropriate browser-based formatting rules.
  • Can't contain free-form text.
  • Provide user interaction characteristics based on the browser's implementation.

When using the date and number field types, specifying a culture with @bind:culture isn't recommended because Blazor provides built-in support to render values in the current culture.

The following field types have specific formatting requirements and aren't currently supported by Blazor because they aren't supported by all of the major browsers:

  • datetime-local
  • month
  • week

For current browser support of the preceding types, see Can I use.

.NET globalization and International Components for Unicode (ICU) support

Blazor WebAssembly uses a reduced globalization API and set of built-in International Components for Unicode (ICU) locales. For more information, see .NET globalization and ICU: ICU on WebAssembly.

Loading a custom subset of locales in a Blazor WebAssembly app is supported in .NET 8 or later. For more information, access this section for an 8.0 or later version of this article.

Invariant globalization

If the app doesn't require localization, configure the app to support the invariant culture, which is generally based on United States English (en-US). Set the InvariantGlobalization property to true in the app's project file (.csproj):

<PropertyGroup>
  <InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

Alternatively, configure invariant globalization with the following approaches:

  • In runtimeconfig.json:

    {
      "runtimeOptions": {
        "configProperties": {
          "System.Globalization.Invariant": true
        }
      }
    }
    
  • With an environment variable:

    • Key: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
    • Value: true or 1

For more information, see Runtime configuration options for globalization (.NET documentation).

Demonstration component

The following CultureExample1 component can be used to demonstrate Blazor globalization and localization concepts covered by this article.

Pages/CultureExample1.razor:

@page "/culture-example-1"
@using System.Globalization

<h1>Culture Example 1</h1>

<p>
    <b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Rendered values</h2>

<ul>
    <li><b>Date</b>: @dt</li>
    <li><b>Number</b>: @number.ToString("N2")</li>
</ul>

<h2><code>&lt;input&gt;</code> elements that don't set a <code>type</code></h2>

<p>
    The following <code>&lt;input&gt;</code> elements use
    <code>CultureInfo.CurrentCulture</code>.
</p>

<ul>
    <li><label><b>Date:</b> <input @bind="dt" /></label></li>
    <li><label><b>Number:</b> <input @bind="number" /></label></li>
</ul>

<h2><code>&lt;input&gt;</code> elements that set a <code>type</code></h2>

<p>
    The following <code>&lt;input&gt;</code> elements use
    <code>CultureInfo.InvariantCulture</code>.
</p>

<ul>
    <li><label><b>Date:</b> <input type="date" @bind="dt" /></label></li>
    <li><label><b>Number:</b> <input type="number" @bind="number" /></label></li>
</ul>

@code {
    private DateTime dt = DateTime.Now;
    private double number = 1999.69;
}

The number string format (N2) in the preceding example (.ToString("N2")) is a standard .NET numeric format specifier. The N2 format is supported for all numeric types, includes a group separator, and renders up to two decimal places.

Optionally, add a menu item to the navigation in Shared/NavMenu.razor for the CultureExample1 component.

Dynamically set the culture from the Accept-Language header

The Accept-Language header is set by the browser and controlled by the user's language preferences in browser settings. In browser settings, a user sets one or more preferred languages in order of preference. The order of preference is used by the browser to set quality values (q, 0-1) for each language in the header. The following example specifies United States English, English, and Chilean Spanish with a preference for United States English or English:

Accept-Language: en-US,en;q=0.9,es-CL;q=0.8

The app's culture is set by matching the first requested language that matches a supported culture of the app.

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the app's project file (.csproj):

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

Note

If the app's specification requires limiting the supported cultures to an explicit list, see the Dynamically set the culture by user preference section of this article.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Startup.ConfigureServices (Startup.cs):

services.AddLocalization();

Specify the app's supported cultures in Startup.Configure (Startup.cs) immediately after Routing Middleware is added to the processing pipeline. The following example configures supported cultures for United States English and Chilean Spanish:

app.UseRequestLocalization(new RequestLocalizationOptions()
    .AddSupportedCultures(new[] { "en-US", "es-CL" })
    .AddSupportedUICultures(new[] { "en-US", "es-CL" }));

For information on ordering the Localization Middleware in the middleware pipeline of Startup.Configure, see ASP.NET Core Middleware.

Use the CultureExample1 component shown in the Demonstration component section to study how globalization works. Issue a request with United States English (en-US). Switch to Chilean Spanish (es-CL) in the browser's language settings. Request the webpage again.

Note

Some browsers force you to use the default language setting for both requests and the browser's own UI settings. This can make changing the language back to one that you understand difficult because all of the setting UI screens might end up in a language that you can't read. A browser such as Opera is a good choice for testing because it permits you to set a default language for webpage requests but leave the browser's settings UI in your language.

When the culture is United States English (en-US), the rendered component uses month/day date formatting (6/7), 12-hour time (AM/PM), and comma separators in numbers with a dot for the decimal value (1,999.69):

  • Date: 6/7/2021 6:45:22 AM
  • Number: 1,999.69

When the culture is Chilean Spanish (es-CL), the rendered component uses day/month date formatting (7/6), 24-hour time, and period separators in numbers with a comma for the decimal value (1.999,69):

  • Date: 7/6/2021 6:49:38
  • Number: 1.999,69

Statically set the culture

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the app's project file (.csproj):

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

The app's culture can be set in JavaScript when Blazor starts with the applicationCulture Blazor start option. The following example configures the app to launch using the United States English (en-US) culture.

  • In wwwroot/index.html, prevent Blazor autostart by adding autostart="false" to Blazor's <script> tag:

    <script src="_framework/blazor.webassembly.js" autostart="false"></script>
    
  • Add the following <script> block after Blazor's <script> tag and before the closing </body> tag:

    <script>
      Blazor.start({
        applicationCulture: 'en-US'
      });
    </script>
    

The value for applicationCulture must conform to the BCP-47 language tag format. For more information on Blazor startup, see ASP.NET Core Blazor startup.

An alternative to setting the culture Blazor's start option is to set the culture in C# code. Set CultureInfo.DefaultThreadCurrentCulture and CultureInfo.DefaultThreadCurrentUICulture in Program.cs to the same culture.

Add the System.Globalization namespace to Program.cs:

using System.Globalization;

Add the culture settings before the line that builds and runs the WebAssemblyHostBuilder (await builder.Build().RunAsync();):

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");

Important

Always set DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture to the same culture in order to use IStringLocalizer and IStringLocalizer<T>.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Startup.ConfigureServices (Startup.cs):

services.AddLocalization();

Specify the static culture in Startup.Configure (Startup.cs) immediately after Routing Middleware is added to the processing pipeline. The following example configures United States English:

app.UseRequestLocalization("en-US");

The culture value for UseRequestLocalization must conform to the BCP-47 language tag format.

For information on ordering the Localization Middleware in the middleware pipeline of Startup.Configure, see ASP.NET Core Middleware.

Use the CultureExample1 component shown in the Demonstration component section to study how globalization works. Issue a request with United States English (en-US). Switch to Chilean Spanish (es-CL) in the browser's language settings. Request the webpage again. When the requested language is Chilean Spanish, the app's culture remains United States English (en-US).

Dynamically set the culture by user preference

Examples of locations where an app might store a user's preference include in browser local storage (common in Blazor WebAssembly apps), in a localization cookie or database (common in Blazor Server apps), or in an external service attached to an external database and accessed by a web API. The following example demonstrates how to use browser local storage.

Add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the project file:

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

The app's culture in a Blazor WebAssembly app is set using the Blazor framework's API. A user's culture selection can be persisted in browser local storage.

In the wwwroot/index.html file after Blazor's <script> tag and before the closing </body> tag, provide JS functions to get and set the user's culture selection with browser local storage:

<script>
  window.blazorCulture = {
    get: () => window.localStorage['BlazorCulture'],
    set: (value) => window.localStorage['BlazorCulture'] = value
  };
</script>

Note

The preceding example pollutes the client with global methods. For a better approach in production apps, see JavaScript isolation in JavaScript modules.

Add the namespaces for System.Globalization and Microsoft.JSInterop to the top of Program.cs:

using System.Globalization;
using Microsoft.JSInterop;

Remove the following line from Program.cs:

- await builder.Build().RunAsync();

Replace the preceding line with the following code. The code adds Blazor's localization service to the app's service collection with AddLocalization and uses JS interop to call into JS and retrieve the user's culture selection from local storage. If local storage doesn't contain a culture for the user, the code sets a default value of United States English (en-US).

builder.Services.AddLocalization();

var host = builder.Build();

CultureInfo culture;
var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");

if (result != null)
{
    culture = new CultureInfo(result);
}
else
{
    culture = new CultureInfo("en-US");
    await js.InvokeVoidAsync("blazorCulture.set", "en-US");
}

CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;

await host.RunAsync();

Important

Always set DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture to the same culture in order to use IStringLocalizer and IStringLocalizer<T>.

The following CultureSelector component shows how to set the user's culture selection into browser local storage via JS interop. The component is placed in the Shared folder for use throughout the app.

Shared/CultureSelector.razor:

@using  System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation

<p>
    <label>
        Select your locale:
        <select @bind="Culture">
            @foreach (var culture in supportedCultures)
            {
                <option value="@culture">@culture.DisplayName</option>
            }
        </select>
    </label>
</p>

@code
{
    private CultureInfo[] supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("es-CL"),
    };

    private CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var js = (IJSInProcessRuntime)JS;
                js.InvokeVoid("blazorCulture.set", value.Name);

                Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
            }
        }
    }
}

Inside the closing </div> tag of the <div class="main"> element in Shared/MainLayout.razor, add the CultureSelector component:

<div class="bottom-row px-4">
    <CultureSelector />
</div>

Examples of locations where an app might store a user's preference include in browser local storage (common in Blazor WebAssembly apps), in a localization cookie or database (common in Blazor Server apps), or in an external service attached to an external database and accessed by a web API. The following example demonstrates how to use a localization cookie.

Add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Startup.ConfigureServices (Startup.cs):

services.AddLocalization();

Set the app's default and supported cultures with RequestLocalizationOptions.

In Startup.Configure immediately after Routing Middleware is added to the processing pipeline:

var supportedCultures = new[] { "en-US", "es-CL" };
var localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

For information on ordering the Localization Middleware in the middleware pipeline of Startup.Configure, see ASP.NET Core Middleware.

The following example shows how to set the current culture in a cookie that can be read by the Localization Middleware.

Add the following namespaces to the top of the Pages/_Host.cshtml file:

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

Immediately after the opening <body> tag of Pages/_Host.cshtml, add the following Razor expression:

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

For information on ordering the Localization Middleware in the middleware pipeline of Startup.Configure, see ASP.NET Core Middleware.

If the app isn't configured to process controller actions:

  • Add MVC services by calling AddControllers on the service collection in Startup.ConfigureServices:

    services.AddControllers();
    
  • Add controller endpoint routing in Startup.Configure by calling MapControllers on the IEndpointRouteBuilder:

    endpoints.MapControllers();
    

    The following example shows the call to UseEndpoints after the line is added:

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");
    });
    

To provide UI to allow a user to select a culture, use a redirect-based approach with a localization cookie. 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. The process is similar to what happens in a web app when a user attempts to access a secure resource, where the user is redirected to a sign-in page and then redirected back to the original resource.

Controllers/CultureController.cs:

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

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

        return LocalRedirect(redirectUri);
    }
}

Warning

Use the LocalRedirect action result to prevent open redirect attacks. For more information, see Prevent open redirect attacks in ASP.NET Core.

The following CultureSelector component shows how to perform the initial redirection when the user selects a culture. The component is placed in the Shared folder for use throughout the app.

Shared/CultureSelector.razor:

@using  System.Globalization
@inject NavigationManager Navigation

<p>
    <label>
        Select your locale:
        <select @bind="Culture">
            @foreach (var culture in supportedCultures)
            {
                <option value="@culture">@culture.DisplayName</option>
            }
        </select>
    </label>
</p>

@code
{
    private CultureInfo[] supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("es-CL"),
    };

    protected override void OnInitialized()
    {
        Culture = CultureInfo.CurrentCulture;
    }

    private CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var uri = new Uri(Navigation.Uri)
                    .GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
                var cultureEscaped = Uri.EscapeDataString(value.Name);
                var uriEscaped = Uri.EscapeDataString(uri);

                Navigation.NavigateTo(
                    $"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
                    forceLoad: true);
            }
        }
    }
}

Inside the closing </div> tag of the <div class="main"> element in Shared/MainLayout.razor, add the CultureSelector component:

<div class="bottom-row px-4">
    <CultureSelector />
</div>

Use the CultureExample1 component shown in the Demonstration component section to study how the preceding example works.

Localization

If the app doesn't already support culture selection per the Dynamically set the culture by user preference section of this article, add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Set the BlazorWebAssemblyLoadAllGlobalizationData property to true in the app's project file (.csproj):

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

In Program.cs, add namespace the namespace for System.Globalization to the top of the file:

using System.Globalization;

Add Blazor's localization service to the app's service collection with AddLocalization in Program.cs:

builder.Services.AddLocalization();

Use Localization Middleware to set the app's culture.

If the app doesn't already support culture selection per the Dynamically set the culture by user preference section of this article:

  • Add localization services to the app with AddLocalization.
  • Specify the app's default and supported cultures in Startup.Configure (Startup.cs). The following example configures supported cultures for United States English and Chilean Spanish.

In Startup.ConfigureServices (Startup.cs):

services.AddLocalization();

In Startup.Configure immediately after Routing Middleware is added to the processing pipeline:

var supportedCultures = new[] { "en-US", "es-CL" };
var localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

For information on ordering the Localization Middleware in the middleware pipeline of Startup.Configure, see ASP.NET Core Middleware.

If the app should localize resources based on storing a user's culture setting, use a localization culture cookie. 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, the recommended approach is to use a localization culture cookie. See the Dynamically set the culture by user preference section of this article to see an example Razor expression for the Pages/_Host.cshtml file that persists the user's culture selection.

The example of localized resources in this section works with the prior examples in this article where the app's supported cultures are English (en) as a default locale and Spanish (es) as a user-selectable or browser-specified alternate locale.

Create resources for each locale. In the following example, resources are created for a default Greeting string:

  • English: Hello, World!
  • Spanish (es): ¡Hola, Mundo!

Note

The following resource file can be added in Visual Studio by right-clicking the project's Pages folder and selecting Add > New Item > Resources File. Name the file CultureExample2.resx. When the editor appears, provide data for a new entry. Set the Name to Greeting and Value to Hello, World!. Save the file.

Pages/CultureExample2.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Greeting" xml:space="preserve">
    <value>Hello, World!</value>
  </data>
</root>

Note

The following resource file can be added in Visual Studio by right-clicking the project's Pages folder and selecting Add > New Item > Resources File. Name the file CultureExample2.es.resx. When the editor appears, provide data for a new entry. Set the Name to Greeting and Value to ¡Hola, Mundo!. Save the file.

Pages/CultureExample2.es.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Greeting" xml:space="preserve">
    <value>¡Hola, Mundo!</value>
  </data>
</root>

The following component demonstrates the use of the localized Greeting string with IStringLocalizer<T>. The Razor markup @Loc["Greeting"] in the following example localizes the string keyed to the Greeting value, which is set in the preceding resource files.

Add the namespace for Microsoft.Extensions.Localization to the app's _Imports.razor file:

@using Microsoft.Extensions.Localization

Pages/CultureExample2.razor:

@page "/culture-example-2"
@using System.Globalization
@inject IStringLocalizer<CultureExample2> Loc

<h1>Culture Example 2</h1>

<p>
    <b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Greeting</h2>

<p>
    @Loc["Greeting"]
</p>

<p>
    @greeting
</p>

@code {
    private string greeting;

    protected override void OnInitialized()
    {
        greeting = Loc["Greeting"];
    }
}

Optionally, add a menu item to the navigation in Shared/NavMenu.razor for the CultureExample2 component.

Culture provider reference source

To further understand how the Blazor framework processes localization, see the WebAssemblyCultureProvider class in the ASP.NET Core reference source.

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Shared resources

To create localization shared resources, adopt the following approach.

  • Create a dummy class with an arbitrary class name. In the following example:

    • The app uses the BlazorSample namespace, and localization assets use the BlazorSample.Localization namespace.
    • The dummy class is named SharedResource.
    • The class file is placed in a Localization folder at the root of the app.

    Localization/SharedResource.cs:

    namespace BlazorSample.Localization
    {
        public class SharedResource
        {
        }
    }
    
  • Create the shared resource files with a Build Action of Embedded resource. In the following example:

    • The files are placed in the Localization folder with the dummy SharedResource class (Localization/SharedResource.cs).

    • Name the resource files to match the name of the dummy class. The following example files include a default localization file and a file for Spanish (es) localization.

    • Localization/SharedResource.resx

    • Localization/SharedResource.es.resx

    Note

    Localization is resource path that can be set via LocalizationOptions.

  • To reference the dummy class for an injected IStringLocalizer<T> in a Razor component, either place an @using directive for the localization namespace or include the localization namespace in the dummy class reference. In the following examples:

    • The first example states the Localization namespace for the SharedResource dummy class with an @using directive.
    • The second example states the SharedResource dummy class's namespace explicitly.

    In a Razor component, use either of the following approaches:

    @using Localization
    @inject IStringLocalizer<SharedResource> Loc
    
    @inject IStringLocalizer<Localization.SharedResource> Loc
    

For additional guidance, see Globalization and localization in ASP.NET Core.

Additional resources

For globalization, Blazor provides number and date formatting. For localization, Blazor renders content using the .NET Resources system.

A limited set of ASP.NET Core's localization features are supported:

Supported: IStringLocalizer and IStringLocalizer<T> are supported in Blazor apps.

Not supported: IHtmlLocalizer, IViewLocalizer, and Data Annotations localization are ASP.NET Core MVC features and not supported in Blazor apps.

This article describes how to use Blazor's globalization and localization features based on:

  • The Accept-Language header, which is set by the browser based on a user's language preferences in browser settings.
  • A culture set by the app not based on the value of the Accept-Language header. The setting can be static for all users or dynamic based on app logic. When the setting is based on the user's preference, the setting is usually saved for reload on future visits.

For additional general information, see the following resources:

Note

Often, the terms language and culture are used interchangeably when dealing with globalization and localization concepts.

In this article, language refers to selections made by a user in their browser's settings. The user's language selections are submitted in browser requests in the Accept-Language header. Browser settings usually use the word "language" in the UI.

Culture pertains to members of .NET and Blazor API. For example, a user's request can include the Accept-Language header specifying a language from the user's perspective, but the app ultimately sets the CurrentCulture ("culture") property from the language that the user requested. API usually uses the word "culture" in its member names.

Globalization

The @bind attribute directive applies formats and parses values for display based on the user's first preferred language that the app supports. @bind supports the @bind:culture parameter to provide a System.Globalization.CultureInfo for parsing and formatting a value.

The current culture can be accessed from the System.Globalization.CultureInfo.CurrentCulture property.

CultureInfo.InvariantCulture is used for the following field types (<input type="{TYPE}" />, where the {TYPE} placeholder is the type):

  • date
  • number

The preceding field types:

  • Are displayed using their appropriate browser-based formatting rules.
  • Can't contain free-form text.
  • Provide user interaction characteristics based on the browser's implementation.

When using the date and number field types, specifying a culture with @bind:culture isn't recommended because Blazor provides built-in support to render values in the current culture.

The following field types have specific formatting requirements and aren't currently supported by Blazor because they aren't supported by all of the major browsers:

  • datetime-local
  • month
  • week

For current browser support of the preceding types, see Can I use.

.NET globalization and International Components for Unicode (ICU) support

Blazor WebAssembly uses a reduced globalization API and set of built-in International Components for Unicode (ICU) locales. For more information, see .NET globalization and ICU: ICU on WebAssembly.

Loading a custom subset of locales in a Blazor WebAssembly app is supported in .NET 8 or later. For more information, access this section for an 8.0 or later version of this article.

Invariant globalization

If the app doesn't require localization, configure the app to support the invariant culture, which is generally based on United States English (en-US). Set the InvariantGlobalization property to true in the app's project file (.csproj):

<PropertyGroup>
  <InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

Alternatively, configure invariant globalization with the following approaches:

  • In runtimeconfig.json:

    {
      "runtimeOptions": {
        "configProperties": {
          "System.Globalization.Invariant": true
        }
      }
    }
    
  • With an environment variable:

    • Key: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
    • Value: true or 1

For more information, see Runtime configuration options for globalization (.NET documentation).

Demonstration component

The following CultureExample1 component can be used to demonstrate Blazor globalization and localization concepts covered by this article.

Pages/CultureExample1.razor:

@page "/culture-example-1"
@using System.Globalization

<h1>Culture Example 1</h1>

<p>
    <b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Rendered values</h2>

<ul>
    <li><b>Date</b>: @dt</li>
    <li><b>Number</b>: @number.ToString("N2")</li>
</ul>

<h2><code>&lt;input&gt;</code> elements that don't set a <code>type</code></h2>

<p>
    The following <code>&lt;input&gt;</code> elements use
    <code>CultureInfo.CurrentCulture</code>.
</p>

<ul>
    <li><label><b>Date:</b> <input @bind="dt" /></label></li>
    <li><label><b>Number:</b> <input @bind="number" /></label></li>
</ul>

<h2><code>&lt;input&gt;</code> elements that set a <code>type</code></h2>

<p>
    The following <code>&lt;input&gt;</code> elements use
    <code>CultureInfo.InvariantCulture</code>.
</p>

<ul>
    <li><label><b>Date:</b> <input type="date" @bind="dt" /></label></li>
    <li><label><b>Number:</b> <input type="number" @bind="number" /></label></li>
</ul>

@code {
    private DateTime dt = DateTime.Now;
    private double number = 1999.69;
}

The number string format (N2) in the preceding example (.ToString("N2")) is a standard .NET numeric format specifier. The N2 format is supported for all numeric types, includes a group separator, and renders up to two decimal places.

Optionally, add a menu item to the navigation in Shared/NavMenu.razor for the CultureExample1 component.

Dynamically set the culture from the Accept-Language header

The Accept-Language header is set by the browser and controlled by the user's language preferences in browser settings. In browser settings, a user sets one or more preferred languages in order of preference. The order of preference is used by the browser to set quality values (q, 0-1) for each language in the header. The following example specifies United States English, English, and Chilean Spanish with a preference for United States English or English:

Accept-Language: en-US,en;q=0.9,es-CL;q=0.8

The app's culture is set by matching the first requested language that matches a supported culture of the app.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Startup.ConfigureServices (Startup.cs):

services.AddLocalization();

Specify the app's supported cultures in Startup.Configure (Startup.cs) immediately after Routing Middleware is added to the processing pipeline. The following example configures supported cultures for United States English and Chilean Spanish:

app.UseRequestLocalization(new RequestLocalizationOptions()
    .AddSupportedCultures(new[] { "en-US", "es-CL" })
    .AddSupportedUICultures(new[] { "en-US", "es-CL" }));

For information on ordering the Localization Middleware in the middleware pipeline of Startup.Configure, see ASP.NET Core Middleware.

Use the CultureExample1 component shown in the Demonstration component section to study how globalization works. Issue a request with United States English (en-US). Switch to Chilean Spanish (es-CL) in the browser's language settings. Request the webpage again.

Note

Some browsers force you to use the default language setting for both requests and the browser's own UI settings. This can make changing the language back to one that you understand difficult because all of the setting UI screens might end up in a language that you can't read. A browser such as Opera is a good choice for testing because it permits you to set a default language for webpage requests but leave the browser's settings UI in your language.

When the culture is United States English (en-US), the rendered component uses month/day date formatting (6/7), 12-hour time (AM/PM), and comma separators in numbers with a dot for the decimal value (1,999.69):

  • Date: 6/7/2021 6:45:22 AM
  • Number: 1,999.69

When the culture is Chilean Spanish (es-CL), the rendered component uses day/month date formatting (7/6), 24-hour time, and period separators in numbers with a comma for the decimal value (1.999,69):

  • Date: 7/6/2021 6:49:38
  • Number: 1.999,69

Statically set the culture

By default, the Intermediate Language (IL) Linker configuration for Blazor WebAssembly apps strips out internationalization information except for locales explicitly requested. For more information, see Configure the Linker for ASP.NET Core Blazor.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Startup.ConfigureServices (Startup.cs):

services.AddLocalization();

Specify the static culture in Startup.Configure (Startup.cs) immediately after Routing Middleware is added to the processing pipeline. The following example configures United States English:

app.UseRequestLocalization("en-US");

The culture value for UseRequestLocalization must conform to the BCP-47 language tag format.

For information on ordering the Localization Middleware in the middleware pipeline of Startup.Configure, see ASP.NET Core Middleware.

Use the CultureExample1 component shown in the Demonstration component section to study how globalization works. Issue a request with United States English (en-US). Switch to Chilean Spanish (es-CL) in the browser's language settings. Request the webpage again. When the requested language is Chilean Spanish, the app's culture remains United States English (en-US).

Dynamically set the culture by user preference

Examples of locations where an app might store a user's preference include in browser local storage (common in Blazor WebAssembly apps), in a localization cookie or database (common in Blazor Server apps), or in an external service attached to an external database and accessed by a web API. The following example demonstrates how to use browser local storage.

Add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

The app's culture in a Blazor WebAssembly app is set using the Blazor framework's API. A user's culture selection can be persisted in browser local storage.

In the wwwroot/index.html file after Blazor's <script> tag and before the closing </body> tag, provide JS functions to get and set the user's culture selection with browser local storage:

<script>
  window.blazorCulture = {
    get: () => window.localStorage['BlazorCulture'],
    set: (value) => window.localStorage['BlazorCulture'] = value
  };
</script>

Add the namespaces for System.Globalization and Microsoft.JSInterop to the top of Program.cs:

using System.Globalization;
using Microsoft.JSInterop;

Remove the following line from Program.cs:

- await builder.Build().RunAsync();

Replace the preceding line with the following code. The code adds Blazor's localization service to the app's service collection with AddLocalization and uses JS interop to call into JS and retrieve the user's culture selection from local storage. If local storage doesn't contain a culture for the user, the code sets a default value of United States English (en-US).

builder.Services.AddLocalization();

var host = builder.Build();

CultureInfo culture;
var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");

if (result != null)
{
    culture = new CultureInfo(result);
}
else
{
    culture = new CultureInfo("en-US");
    await js.InvokeVoidAsync("blazorCulture.set", "en-US");
}

CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;

await host.RunAsync();

Important

Always set DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture to the same culture in order to use IStringLocalizer and IStringLocalizer<T>.

The following CultureSelector component shows how to set the user's culture selection into browser local storage via JS interop. The component is placed in the Shared folder for use throughout the app.

Shared/CultureSelector.razor:

@using  System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation

<p>
    <label>
        Select your locale:
        <select @bind="Culture">
            @foreach (var culture in supportedCultures)
            {
                <option value="@culture">@culture.DisplayName</option>
            }
        </select>
    </label>
</p>

@code
{
    private CultureInfo[] supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("es-CL"),
    };

    private CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var js = (IJSInProcessRuntime)JS;
                js.InvokeVoid("blazorCulture.set", value.Name);

                Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
            }
        }
    }
}

Inside the closing </div> tag of the <div class="main"> element in Shared/MainLayout.razor, add the CultureSelector component:

<div class="bottom-row px-4">
    <CultureSelector />
</div>

Examples of locations where an app might store a user's preference include in browser local storage (common in Blazor WebAssembly apps), in a localization cookie or database (common in Blazor Server apps), or in an external service attached to an external database and accessed by a web API. The following example demonstrates how to use a localization cookie.

Add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Blazor Server apps are localized using Localization Middleware. Add localization services to the app with AddLocalization.

In Startup.ConfigureServices (Startup.cs):

services.AddLocalization();

Set the app's default and supported cultures with RequestLocalizationOptions.

In Startup.Configure immediately after Routing Middleware is added to the processing pipeline:

var supportedCultures = new[] { "en-US", "es-CL" };
var localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

For information on ordering the Localization Middleware in the middleware pipeline of Startup.Configure, see ASP.NET Core Middleware.

The following example shows how to set the current culture in a cookie that can be read by the Localization Middleware.

Add the following namespaces to the top of the Pages/_Host.cshtml file:

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

Immediately after the opening <body> tag of Pages/_Host.cshtml, add the following Razor expression:

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

For information on ordering the Localization Middleware in the middleware pipeline of Startup.Configure, see ASP.NET Core Middleware.

If the app isn't configured to process controller actions:

  • Add MVC services by calling AddControllers on the service collection in Startup.ConfigureServices:

    services.AddControllers();
    
  • Add controller endpoint routing in Startup.Configure by calling MapControllers on the IEndpointRouteBuilder:

    endpoints.MapControllers();
    

    The following example shows the call to UseEndpoints after the line is added:

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");
    });
    

To provide UI to allow a user to select a culture, use a redirect-based approach with a localization cookie. 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. The process is similar to what happens in a web app when a user attempts to access a secure resource, where the user is redirected to a sign-in page and then redirected back to the original resource.

Controllers/CultureController.cs:

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

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

        return LocalRedirect(redirectUri);
    }
}

Warning

Use the LocalRedirect action result to prevent open redirect attacks. For more information, see Prevent open redirect attacks in ASP.NET Core.

The following CultureSelector component shows how to perform the initial redirection when the user selects a culture. The component is placed in the Shared folder for use throughout the app.

Shared/CultureSelector.razor:

@using  System.Globalization
@inject NavigationManager Navigation

<p>
    <label>
        Select your locale:
        <select @bind="Culture">
            @foreach (var culture in supportedCultures)
            {
                <option value="@culture">@culture.DisplayName</option>
            }
        </select>
    </label>
</p>

@code
{
    private CultureInfo[] supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("es-CL"),
    };

    protected override void OnInitialized()
    {
        Culture = CultureInfo.CurrentCulture;
    }

    private CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var uri = new Uri(Navigation.Uri)
                    .GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
                var cultureEscaped = Uri.EscapeDataString(value.Name);
                var uriEscaped = Uri.EscapeDataString(uri);

                Navigation.NavigateTo(
                    $"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
                    forceLoad: true);
            }
        }
    }
}

Inside the closing </div> tag of the <div class="main"> element in Shared/MainLayout.razor, add the CultureSelector component:

<div class="bottom-row px-4">
    <CultureSelector />
</div>

Use the CultureExample1 component shown in the Demonstration component section to study how the preceding example works.

Localization

If the app doesn't already support culture selection per the Dynamically set the culture by user preference section of this article, add the Microsoft.Extensions.Localization package to the app.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

By default, the Intermediate Language (IL) Linker configuration for Blazor WebAssembly apps strips out internationalization information except for locales explicitly requested. For more information, see Configure the Linker for ASP.NET Core Blazor.

In Program.cs, add namespace the namespace for System.Globalization to the top of the file:

using System.Globalization;

Add Blazor's localization service to the app's service collection with AddLocalization in Program.cs:

builder.Services.AddLocalization();

Use Localization Middleware to set the app's culture.

If the app doesn't already support culture selection per the Dynamically set the culture by user preference section of this article:

  • Add localization services to the app with AddLocalization.
  • Specify the app's default and supported cultures in Startup.Configure (Startup.cs). The following example configures supported cultures for United States English and Chilean Spanish.

In Startup.ConfigureServices (Startup.cs):

services.AddLocalization();

In Startup.Configure immediately after Routing Middleware is added to the processing pipeline:

var supportedCultures = new[] { "en-US", "es-CL" };
var localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

For information on ordering the Localization Middleware in the middleware pipeline of Startup.Configure, see ASP.NET Core Middleware.

If the app should localize resources based on storing a user's culture setting, use a localization culture cookie. 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, the recommended approach is to use a localization culture cookie. See the Dynamically set the culture by user preference section of this article to see an example Razor expression for the Pages/_Host.cshtml file that persists the user's culture selection.

The example of localized resources in this section works with the prior examples in this article where the app's supported cultures are English (en) as a default locale and Spanish (es) as a user-selectable or browser-specified alternate locale.

Create resources for each locale. In the following example, resources are created for a default Greeting string:

  • English: Hello, World!
  • Spanish (es): ¡Hola, Mundo!

Note

The following resource file can be added in Visual Studio by right-clicking the project's Pages folder and selecting Add > New Item > Resources File. Name the file CultureExample2.resx. When the editor appears, provide data for a new entry. Set the Name to Greeting and Value to Hello, World!. Save the file.

Pages/CultureExample2.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Greeting" xml:space="preserve">
    <value>Hello, World!</value>
  </data>
</root>

Note

The following resource file can be added in Visual Studio by right-clicking the project's Pages folder and selecting Add > New Item > Resources File. Name the file CultureExample2.es.resx. When the editor appears, provide data for a new entry. Set the Name to Greeting and Value to ¡Hola, Mundo!. Save the file.

Pages/CultureExample2.es.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="Greeting" xml:space="preserve">
    <value>¡Hola, Mundo!</value>
  </data>
</root>

The following component demonstrates the use of the localized Greeting string with IStringLocalizer<T>. The Razor markup @Loc["Greeting"] in the following example localizes the string keyed to the Greeting value, which is set in the preceding resource files.

Add the namespace for Microsoft.Extensions.Localization to the app's _Imports.razor file:

@using Microsoft.Extensions.Localization

Pages/CultureExample2.razor:

@page "/culture-example-2"
@using System.Globalization
@inject IStringLocalizer<CultureExample2> Loc

<h1>Culture Example 2</h1>

<p>
    <b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Greeting</h2>

<p>
    @Loc["Greeting"]
</p>

<p>
    @greeting
</p>

@code {
    private string greeting;

    protected override void OnInitialized()
    {
        greeting = Loc["Greeting"];
    }
}

Optionally, add a menu item to the navigation in Shared/NavMenu.razor for the CultureExample2 component.

Shared resources

To create localization shared resources, adopt the following approach.

  • Create a dummy class with an arbitrary class name. In the following example:

    • The app uses the BlazorSample namespace, and localization assets use the BlazorSample.Localization namespace.
    • The dummy class is named SharedResource.
    • The class file is placed in a Localization folder at the root of the app.

    Localization/SharedResource.cs:

    namespace BlazorSample.Localization
    {
        public class SharedResource
        {
        }
    }
    
  • Create the shared resource files with a Build Action of Embedded resource. In the following example:

    • The files are placed in the Localization folder with the dummy SharedResource class (Localization/SharedResource.cs).

    • Name the resource files to match the name of the dummy class. The following example files include a default localization file and a file for Spanish (es) localization.

    • Localization/SharedResource.resx

    • Localization/SharedResource.es.resx

    Note

    Localization is resource path that can be set via LocalizationOptions.

  • To reference the dummy class for an injected IStringLocalizer<T> in a Razor component, either place an @using directive for the localization namespace or include the localization namespace in the dummy class reference. In the following examples:

    • The first example states the Localization namespace for the SharedResource dummy class with an @using directive.
    • The second example states the SharedResource dummy class's namespace explicitly.

    In a Razor component, use either of the following approaches:

    @using Localization
    @inject IStringLocalizer<SharedResource> Loc
    
    @inject IStringLocalizer<Localization.SharedResource> Loc
    

For additional guidance, see Globalization and localization in ASP.NET Core.

Additional resources