ASP.NET Core Blazor全球化和本地化

注意

此版本不是本文的最新版本。 有关当前发布版本,请参阅此文章的 .NET 10 版本

警告

不再支持此版本的 ASP.NET Core。 有关详细信息,请参阅 .NET 和 .NET 核心支持策略。 有关当前发布版本,请参阅此文章的 .NET 10 版本

本文介绍如何向位于不同区域、使用不同语言的用户呈现全球化和本地化内容。

全球化和本地化

对于全球化,Blazor 提供数字和日期格式。 对于 localization,Blazor使用 .NET Resources 系统 呈现内容。

支持一组有限的 ASP.NET Core本地化功能:

支持:IStringLocalizerIStringLocalizer<T> 在 Blazor 应用中受支持。

不支持:IHtmlLocalizerIViewLocalizer 是 ASP.NET Core MVC 功能,并且在 应用中Blazor。

对于 Blazor 应用,如果实现了 DisplayAttribute.ResourceType,则支持对 ValidationAttribute.ErrorMessageResourceType进行本地化。

对于 Blazor 应用,支持通过两种方式为 使用数据注释的表单验证 提供本地化验证消息:

本文介绍如何使用 Blazor 基于以下内容的全球化和本地化功能:

  • Accept-Language 标头,它由浏览器基于浏览器设置中的用户语言首选项进行设置。
  • 由应用设置的区域性(不基于 Accept-Language 标头的值)。 该设置可以是所有用户的静态设置,也可以是基于应用逻辑的动态设置。 如果设置基于用户的首选项,该设置通常会被保存,以便在未来访问时重载。

有关其他一般信息,请查看以下资源:

通常,在处理全球化和本地化概念时,术语“语言”和“区域性”可以互换使用

在本文中,“语言”指的是用户在其浏览器设置中选择的语言。 用户的语言选择是在 Accept-Language 标头的浏览器请求中提交的。 浏览器设置通常在 UI 中使用“语言”一词。

Culture 与 .NET 和 Blazor API 的成员有关。 例如,用户的请求可以包括从用户角度指定语言的 Accept-Language 标头,但应用最终会根据用户请求的语言设置 CurrentCulture(“区域性”)属性。 API 通常在其成员名称中使用“区域性”一词。

本文中的指导不包括设置页面的 HTML 语言属性 (<html lang="...">),可访问性工具使用该属性。 可以通过将语言分配给 lang 标记的 <html> 属性或 JavaScript 中的 document.documentElement.lang 来静态设置该值。 可以通过 document.documentElement.lang动态设置 JS 的值。

注意

本文中的代码示例采用 可为空引用类型(NRTs)和 .NET 编译器的空状态静态分析,这些特性在 .NET 6 或更高版本的 ASP.NET Core 中受到支持。 当目标为 .NET 5 或更早版本时,请从文章的示例中删除 null 类型标记(?)。

全球化

@bind 属性指令根据应用支持的用户第一首选语言来应用格式并分析显示的值。 @bind 支持 @bind:culture 参数,以提供用于分析值并设置值格式的 System.Globalization.CultureInfo

可从 System.Globalization.CultureInfo.CurrentCulture 属性访问当前区域性。

CultureInfo.InvariantCulture 用于以下字段类型(<input type="{TYPE}" />,其中 {TYPE} 占位符是类型):

  • date
  • number

上述字段类型:

  • 使用其基于浏览器的相应格式规则进行显示。
  • 不能包含自由格式的文本。
  • 基于浏览器的实现提供用户交互特性。

Blazor 提供内置支持来呈现当前区域性中的值。 因此,使用 @bind:culturedate 字段类型时,不建议使用 number 指定区域性。

以下字段类型具有特定的格式要求,并且所有主流浏览器均不支持它们,因此不受 Blazor 支持:

  • datetime-local
  • month
  • week

有关上述类型的当前浏览器支持,请参阅可否使用

默认情况下,Blazor 加载包含应用文化信息的全球化数据的子集。 若要加载所有全球化数据,请在应用的项目文件(<BlazorWebAssemblyLoadAllGlobalizationData>)中将true设置为.csproj

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

.NET 全球化和 Unicode 国际化组件 (ICU) 支持 (Blazor WebAssembly)

Blazor WebAssembly 使用一个简化的全球化 API 和一组内置的 Unicode 国际组件 (ICU) 区域设置。

在 WebAssembly (Wasm) 应用中,当全球化固定模式处于禁用状态时,将加载 ICU 数据文件。 这些文件有四种基本类型:

  • icudt.dat:完整数据
  • icudt_EFIGS.dat:语言环境的数据 en-*fr-FRes-ESit-ITde-DE 的数据。
  • icudt_CJK.dat:语言环境 en-*jakozh-* 的数据。
  • icudt_no_CJK.daticudt.dat 中所有语言环境的数据,但不包括jakozh-*

在应用的项目文件 (<BlazorIcuDataFileName>) 中,指定一个文件以通过 .csproj MSBuild 属性加载。 以下示例加载 icudt_no_CJK.dat 文件:

<PropertyGroup>
  <BlazorIcuDataFileName>icudt_no_CJK.dat</BlazorIcuDataFileName>
</PropertyGroup>

<BlazorIcuDataFileName> 仅接受单个文件。 该文件可以是开发人员创建的自定义文件。 若要创建自定义 ICU 文件,请参阅 WASM 全球化 Icu:自定义 ICU

如果未使用 <BlazorIcuDataFileName> 指定某个文件,则会检查应用的区域性,并为其区域性加载相应的 ICU 文件。 例如,en-US 文化会加载 icudt_EFIGS.dat 文件。 在 zh-CN 中,使用 icudt_CJK.dat 文件。

详细信息,请参考.NET 全球化和 ICU:WebAssembly 上的 ICU

Blazor WebAssembly 使用一个简化的全球化 API 和一组内置的 Unicode 国际组件 (ICU) 区域设置。 详细信息,请参考.NET 全球化和 ICU:WebAssembly 上的 ICU

.NET 8 或更高版本支持在 Blazor WebAssembly 应用中加载自定义区域设置子集。 有关详细信息,请访问此部分以获取本文的 .NET 8 或更高版本。

固定全球化

本部分仅适用于客户端 Blazor 方案。

如果应用不需要本地化,请将应用配置为支持不变区域性,该区域性通常基于美国英语(en-US)。 使用固定全球化可减少应用的下载大小,并加快应用启动速度。 在应用的项目文件 (InvariantGlobalization) 中将 true 属性设置为 .csproj

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

或者,通过以下方法配置固定全球化:

  • runtimeconfig.json中:

    {
      "runtimeOptions": {
        "configProperties": {
          "System.Globalization.Invariant": true
        }
      }
    }
    
  • 使用环境变量:

    • 键:DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
    • 值:true1

有关详细信息,请参阅全球化的Runtime 配置选项(.NET文档)

时区信息

本部分仅适用于客户端 Blazor 方案。

采用固定全球化只会导致使用非本地化的时区名称。 要修剪时区代码和数据,从而减少应用的下载大小并加快应用的启动速度,请在应用的项目文件中应用值为 <InvariantTimezone>true MSBuild 属性:

<PropertyGroup>
  <InvariantTimezone>true</InvariantTimezone>
</PropertyGroup>

注意

<BlazorEnableTimeZoneSupport> 重写以前的 <InvariantTimezone> 设置。 建议删除 <BlazorEnableTimeZoneSupport> 设置。

包含数据文件来确保时区信息正确。 如果应用不需要此功能,请考虑通过将应用项目文件中的 <BlazorEnableTimeZoneSupport> MSBuild 属性设置为 false 来禁用它:

<PropertyGroup>
  <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
</PropertyGroup>

在 Blazor Web App 中进行的客户端预渲染会保留服务器的区域性

默认情况下,服务器端(Blazor Web App 中的 .Client 项目)的客户端预渲染会将服务器的 CurrentCultureCurrentUICulture 持久化到组件状态中,并在附属程序集加载前在客户端应用这些设置。

要求客户端独立于服务器选择区域性的应用,可通过在 Blazor Web App 的 Program 文件中使用 WebAssemblyComponentsOptions.UseCultureFromServer 选择退出:

builder.Services.AddRazorComponents()
    .AddInteractiveWebAssemblyComponents(options =>
    {
        options.UseCultureFromServer = false;
    });

演示组件

以下 CultureExample1 组件可用于演示本文涵盖的 Blazor 全球化和本地化概念。

CultureExample1.razor

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

<h1>Culture Example 1</h1>

<ul>
    <li><b>CurrentCulture</b>: @CultureInfo.CurrentCulture</li>
    <li><b>CurrentUICulture</b>: @CultureInfo.CurrentUICulture</li>
</ul>

<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;
}

上例(N2)中的数字字符串格式(.ToString("N2"))是一个标准.NET数字格式说明符N2 格式支持用于所有数值类型,包括组分隔符,最多可呈现两个小数位。

另外,也可选择将菜单项添加到 NavMenu 组件的 NavMenu.razor 组件 (CultureExample1) 中的导航。

Accept-Language 标头中动态设置区域性

本部分适用于服务器端和客户端 Blazor 应用。

Microsoft.Extensions.Localization添加到应用。

Accept-Language 标头由浏览器设置,在浏览器设置中由用户的语言首选项控制。 在浏览器设置中,用户按优先顺序设置一种或多种首选语言。 浏览器按优先顺序为标头中的每种语言设置质量值(q,0-1)。 以下示例指定美式英语、英语和哥斯达黎加西班牙语,并优先使用美式英语或英语:

接受语言:en-US,en;q=0.9,es-CR;q=0.8

应用的区域性是通过匹配第一个请求的语言来设置的,该语言与应用支持的区域性相匹配。

客户端侧开发中,将客户端侧应用项目文件 (BlazorWebAssemblyLoadAllGlobalizationData) 中的 true 属性设置为 .csproj

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

客户端侧开发中,不支持从 Accept-Language 标头动态设置区域性。

注意

如果应用的规范要求将受支持的区域性限制为显式列表,请参阅本文的按用户首选项动态设置客户端侧区域性部分。

应用使用 本地化中间件进行本地化。 使用 AddLocalization 将本地化服务添加到应用。

将以下行添加到 Program 文件,其中注册了服务:

builder.Services.AddLocalization();

服务器端开发中,在任何可能检查请求区域性的中间件之前,指定应用支持的区域性。 通常,在调用 MapRazorComponents之前立即放置本地化中间件。 下面的示例为美国英语和哥斯达黎加西班牙语配置支持的区域性:

服务器端开发中,在将路由中间件 (UseRouting) 添加到处理管道后立即指定应用支持的区域性。 下面的示例使用以下 API 来配置美式英语和哥斯达黎加西班牙语所支持的区域性:

  • AddSupportedCultures 添加 全球化 支持的文化集(日期、数字和货币格式)。
  • AddSupportedUICultures 添加受支持的 UI 文化集 用于本地化(用于内容呈现的翻译 UI 字符串)。
app.UseRequestLocalization(new RequestLocalizationOptions()
    .AddSupportedCultures(new[] { "en-US", "es-CR" })
    .AddSupportedUICultures(new[] { "en-US", "es-CR" }));

在前面的示例中,同一个受支持的格式化区域性和 UI 区域性被限定在应用仅在美国和哥斯达黎加使用的小范围场景下。 或者,应用可以对日期、数字和货币格式使用更广泛的一组区域性,但仅针对美国和哥斯达黎加提供本地化内容,如下例所示:

var uiCultures = new[] { "en-US", "es-CR" };

var formattingCultures = CultureInfo
    .GetCultures(CultureTypes.SpecificCultures)
    .Select(c => c.Name)
    .ToArray();

var localizationOptions = new RequestLocalizationOptions()
    .SetDefaultCulture(uiCultures[0])
    .AddSupportedCultures(formattingCultures)
    .AddSupportedUICultures(uiCultures);

app.UseRequestLocalization(localizationOptions);

在前面的示例中,CultureTypes.SpecificCultures 仅返回特定于国家或地区的文化,例如 en-USfr-FR。这些文化附带完整、具体的全球化数据(包括日期、数字、日历和其他文化用户界面),.NET可以用于准确地进行格式设置和解析。 中立文化(如 enfr)可能没有完整的全球化数据,因此它们不会包含在此列表中。

有关在文件的中间件管道Program中订购本地化中间件的信息,请参阅 ASP.NET Core中间件

使用CultureExample1部分中显示的 组件来研究全球化的工作方式。 发出一个使用美式英语(en-US)的请求。 在浏览器的语言设置中切换到哥斯达黎加西班牙语 (es-CR)。 再次请求该网页。

当区域性为美国英语 (en-US) 时,呈现的组件使用“月/日”日期格式 (6/7)、12 小时制时间 (AM/PM),并在数值中使用逗号分隔符,在小数值中使用点 (1,999.69):

  • 日期:6/7/2021 6:45:22 AM
  • 编号:1,999.69

当区域性为哥斯达黎加西班牙语 (es-CR) 时,呈现的组件使用“日/月”日期格式 (7/6)、24 小时制时间,并在数值中使用句点分隔符,在小数值中使用逗号(1.999,69):

  • 日期:7/6/2021 6:49:38
  • 编号:1,999.69

静态设置客户端侧区域性

本部分适用于 Blazor WebAssembly 采用交互式 WebAssembly 呈现模式的应用和 Blazor Web App 组件。

本部分适用于 Blazor WebAssembly 应用。

在应用的项目文件 (BlazorWebAssemblyLoadAllGlobalizationData) 中将 true 属性设置为 .csproj

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

客户端呈现的中间语言 (IL) 链接器配置去除国际化信息(显式请求的区域设置除外)。 有关详细信息,请参阅 配置 ASP.NET Core 的链接器Blazor

当 Blazor 使用 applicationCultureBlazor 启动选项启动时,可以在 JavaScript 中设置应用的区域性。 以下示例将应用程序配置为使用美式英语(en-US)文化设置启动。

通过将 Blazor 添加到 Blazor 来阻止 autostart="false" 自动启动:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>

在前面的示例中,{BLAZOR SCRIPT} 占位符是 Blazor 脚本路径和文件名。 有关脚本的位置,请参阅 ASP.NET Core Blazor 项目结构

</body>之后并在结束的<script>标记之前添加以下Blazor块:

Blazor Web App:

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

独立 Blazor WebAssembly:

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

applicationCulture 的值必须符合 BCP-47 语言标记格式。 有关 Blazor 启动的详细信息,请参阅 ASP.NET Core Blazor startup

设置区域性 Blazor 的启动选项的替代方法是在 C# 代码中设置区域性。 将 CultureInfo.DefaultThreadCurrentCulture 文件中的 CultureInfo.DefaultThreadCurrentUICultureProgram 设置为相同的区域性。

System.Globalization 命名空间添加到 Program 文件:

using System.Globalization;

在生成并运行 WebAssemblyHostBuilder (await builder.Build().RunAsync();) 的行之前添加区域性设置:

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

注意

在 .NET 9 或更早版本中,独立Blazor WebAssembly应用基于 CultureInfo.DefaultThreadCurrentCulture加载 UI 全球化资源。 如果要为由 CultureInfo.DefaultThreadCurrentUICulture 定义的本地化区域性额外加载全球化数据,将应用升级到 .NET 10 或更高版本

使用CultureExample1部分中显示的 组件来研究全球化的工作方式。 发出一个使用美式英语(en-US)的请求。 在浏览器的语言设置中切换到哥斯达黎加西班牙语 (es-CR)。 再次请求该网页。 当请求的语言为哥斯达黎加班牙语时,应用的区域性将保留为美国英语 (en-US)。

静态设置服务器侧区域性

本部分适用于 Blazor Web App 采用交互式服务器呈现模式和 Blazor Server 应用的组件。

本部分适用于 Blazor Server 应用。

服务器端应用使用 本地化中间件进行本地化。 使用 AddLocalization 将本地化服务添加到应用。

Program 文件中:

builder.Services.AddLocalization();

必须在可能检查请求区域性的任何中间件之前,在 Program 文件中指定静态区域性。 通常,将本地化中间件紧挨着放在 MapRazorComponents 前面。 以下示例配置了美国英语:

在将路由中间件 (UseRouting) 添加到处理管道后,立即在 Program 文件中指定静态区域性。 以下示例配置了美国英语:

app.UseRequestLocalization("en-US");

UseRequestLocalization 的区域性值必须符合 BCP-47 语言标记格式

有关在文件的中间件管道Program中订购本地化中间件的信息,请参阅 ASP.NET Core中间件

服务器端应用使用 本地化中间件进行本地化。 使用 AddLocalization 将本地化服务添加到应用。

Startup.ConfigureServices (Startup.cs) 中:

services.AddLocalization();

在将路由中间件添加到处理管道后,立即在 Startup.Configure (Startup.cs) 中指定静态区域性。 以下示例配置了美国英语:

app.UseRequestLocalization("en-US");

UseRequestLocalization 的区域性值必须符合 BCP-47 语言标记格式

有关在中间件管道Startup.Configure中订购本地化中间件的信息,请参阅 ASP.NET Core中间件

使用CultureExample1部分中显示的 组件来研究全球化的工作方式。 发出一个使用美式英语(en-US)的请求。 在浏览器的语言设置中切换到哥斯达黎加西班牙语 (es-CR)。 再次请求该网页。 当请求的语言为哥斯达黎加班牙语时,应用的区域性将保留为美国英语 (en-US)。

按用户首选项动态设置客户端侧区域性

本部分适用于 Blazor WebAssembly 采用交互式 WebAssembly 呈现模式的应用和 Blazor Web App 组件。

本部分适用于 Blazor WebAssembly 应用。

应用可能存储用户首选项的位置的示例包括:在浏览器本地存储中(常见于客户端场景)、在本地化 cookie 或数据库中(常见于服务器场景),或在附加到外部数据库并由 Web API 访问的外部服务中。 下面的示例演示如何使用浏览器本地存储。

Microsoft.Extensions.Localization添加到应用。

注意

有关将包添加到 .NET 应用的指南,请参阅 安装和管理包下的文章,位于 包消耗工作流(NuGet 文档)。 在 NuGet.org 中确认正确的包版本。

在项目文件中将 BlazorWebAssemblyLoadAllGlobalizationData 属性设置为 true

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

可使用 Blazor 框架的 API 设置应用用于客户端呈现的区域性。 用户的区域性选择可以保留在浏览器本地存储中。

Blazor<script>标记后提供JS函数,以便通过浏览器本地存储获取和设置用户的区域性选择:

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

注意

前面的示例使用全局函数来污染客户端。 若要在生产应用中获取更好的方法,请参阅 JavaScript 模块中的 JavaScript 隔离

System.GlobalizationMicrosoft.JSInterop 的命名空间添加到 Program 文件的顶部:

using System.Globalization;
using Microsoft.JSInterop;

删除以下行:

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

用下面的代码替换前面的行。 该代码通过 Blazor 将 AddLocalization 的本地化服务添加到应用的服务集合,并使用 JS 互操作调入 JS,然后从本地存储中检索用户的区域性选择。 如果本地存储不包含用户的文化,则代码将设置美国英语(en-US)的默认值。

builder.Services.AddLocalization();

var host = builder.Build();

const string defaultCulture = "en-US";

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

if (result == null)
{
    await js.InvokeVoidAsync("blazorCulture.set", defaultCulture);
}

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

await host.RunAsync();

注意

在 .NET 9 或更早版本中,独立Blazor WebAssembly应用基于 CultureInfo.DefaultThreadCurrentCulture加载 UI 全球化资源。 如果要为由 CultureInfo.DefaultThreadCurrentUICulture 定义的本地化区域性额外加载全球化数据,将应用升级到 .NET 10 或更高版本

以下 CultureSelector 组件显示了如何执行以下操作:

  • 通过 JS 互操作将用户的区域性选择设置到浏览器本地存储中。
  • 重载所请求的组件 (forceLoad: true),这将使用更新的区域性。

CultureSelector.razor

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

<p>
    <label>
        Select your locale:
        <select @bind="selectedCulture" @bind:after="ApplySelectedCultureAsync">
            @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-CR"),
    };

    private CultureInfo? selectedCulture;

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

    private async Task ApplySelectedCultureAsync()
    {
        if (selectedCulture is not null &&
            CultureInfo.CurrentCulture != selectedCulture)
        {
            await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture.Name);

            Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
        }
    }
}
@using System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation

<p>
    <label>
        Select your locale:
        <select value="@selectedCulture" @onchange="HandleSelectedCultureChanged">
            @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-CR"),
    };

    private CultureInfo? selectedCulture;

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

    private async Task HandleSelectedCultureChanged(ChangeEventArgs args)
    {
        selectedCulture = CultureInfo.GetCultureInfo((string)args.Value!);

        if (selectedCulture is not null &&
            CultureInfo.CurrentCulture != selectedCulture)
        {
            await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture.Name);

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

在组件的标头标记 MainLayoutMainLayout.razor)中添加 CultureSelector 组件:

<CultureSelector />

使用CultureExample1部分中显示的 组件来研究前面示例的工作原理。

按用户首选项动态设置服务器侧区域性

本节适用于采用全局交互式服务器渲染模式的 Blazor Web App 和 Blazor Server 应用。 有关包含 Blazor Web App 采用每页/组件交互的指导,请参阅按用户首选项在 Blazor Web App 中动态设置区域性一节。

本部分适用于 Blazor Server 应用。

应用可能存储用户首选项的位置的示例包括:在浏览器本地存储中(常见于客户端场景)、在本地化 cookie 或数据库中(常见于服务器场景),或在附加到外部数据库并由 Web API 访问的外部服务中。 下面的示例展示了如何使用本地化 cookie。

Microsoft.Extensions.Localization添加到应用。

注意

有关将包添加到 .NET 应用的指南,请参阅 安装和管理包下的文章,位于 包消耗工作流(NuGet 文档)。 在 NuGet.org 中确认正确的包版本。

服务器端应用使用 本地化中间件进行本地化。 使用 AddLocalization 将本地化服务添加到应用。

Program 文件中:

builder.Services.AddLocalization();

使用 RequestLocalizationOptions 设置应用的默认且受支持的区域性。

在请求处理管道中调用 MapRazorComponents 之前,请放置以下代码:

将路由中间件 (UseRouting) 添加到请求处理管道后,请放置以下代码:

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

app.UseRequestLocalization(localizationOptions);

有关在中间件管道中订购本地化中间件的信息,请参阅 ASP.NET Core中间件

以下示例演示如何在可由本地化中间件读取的 cookie 中设置当前区域性。

App 组件需要以下命名空间:

System.GlobalizationMicrosoft.AspNetCore.Localization 命名空间添加到组件文件顶部 AppComponents/App.razor):

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

将以下 @code 块添加到 App 组件文件的底部:

@code {
    [CascadingParameter]
    private HttpContext? HttpContext { get; set; }

    protected override void OnInitialized()
    {
        HttpContext?.Response.Cookies.Append(
            CookieRequestCultureProvider.DefaultCookieName,
            CookieRequestCultureProvider.MakeCookieValue(
                new RequestCulture(
                    CultureInfo.CurrentCulture,
                    CultureInfo.CurrentUICulture)));
    }
}

Pages/_Host.cshtml 文件所做的修改需要以下命名空间:

将以下 Razor 标记添加到文件:

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

有关在中间件管道中订购本地化中间件的信息,请参阅 ASP.NET Core中间件

若要提供支持用户选择区域性的 UI,请将基于重定向的方法和本地化 cookie 结合使用。 应用通过重定向到 Minimal API 终结点来持久保存用户所选的区域性。 端点将用户选择的区域性设置到 cookie 中,并将用户重定向回原始 URI。 此过程类似于用户尝试访问安全资源时在 Web 应用中发生的情况,用户会被重定向到登录页,然后重定向回原始资源。

Program 文件顶部,添加以下 using 语句以引入所需命名空间:

using Microsoft.AspNetCore.Localization;

在调用UseRequestLocalization后应用Program文件的请求处理管道中:

app.MapGet("/Culture/Set", (string? culture, string redirectUri = "/",
    HttpContext context) =>
{
    if (!string.IsNullOrWhiteSpace(culture))
    {
        context.Response.Cookies.Append(
            CookieRequestCultureProvider.DefaultCookieName,
            CookieRequestCultureProvider.MakeCookieValue(
                new RequestCulture(culture, culture)));
    }

    return Results.LocalRedirect(redirectUri);
});

警告

如前面的示例所示,请使用 LocalRedirect 结果来防止开放重定向攻击。 有关详细信息,请参阅 防止在 ASP.NET Core 中的公开重定向攻击

如果应用未配置为处理控制器操作:

  • 通过在 AddControllers 文件中对服务集合调用 Program 来添加 MVC 服务:

    builder.Services.AddControllers();
    
  • 通过对 Program (MapControllers) 调用 IEndpointRouteBuilderapp 文件中添加控制器终结点路由:

    app.MapControllers();
    

若要提供支持用户选择区域性的 UI,请将基于重定向的方法和本地化 cookie 结合使用。 应用通过重定向到控制器来保留用户的所选区域性。 控制器将用户选择的区域性设置为 cookie,然后将用户重定向回原始 URI。 此过程类似于用户尝试访问安全资源时在 Web 应用中发生的情况,用户会被重定向到登录页,然后重定向回原始资源。

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);
    }
}

警告

使用 LocalRedirect 操作结果,如前面的示例所示,以防范开放式重定向攻击。 有关详细信息,请参阅 防止在 ASP.NET Core 中的公开重定向攻击

以下 CultureSelector 组件展示了如何在新区域性下调用 Set 端点。 该组件位于 Components 文件夹中,供整个应用使用。

Components/CultureSelector.razor

以下 CultureSelector 组件演示如何使用新的区域性调用 Set 端点。 该组件位于 Shared 文件夹中,供整个应用使用。

Shared/CultureSelector.razor

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

<p>
    <label>
        Select your locale:
        <select @bind="selectedCulture" @bind:after="ApplySelectedCultureAsync">
            @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-CR"),
    };

    private CultureInfo? selectedCulture;

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

    private async Task ApplySelectedCultureAsync()
    {
        if (selectedCulture is not null &&
            CultureInfo.CurrentCulture != selectedCulture)
        {
            var uri = new Uri(Navigation.Uri)
                .GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
            var cultureEscaped = Uri.EscapeDataString(selectedCulture.Name);
            var uriEscaped = Uri.EscapeDataString(uri);

            Navigation.NavigateTo(
                $"Culture/Set?culture={cultureEscaped}&redirectUri={uriEscaped}",
                forceLoad: true);
        }
    }
}
@using System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation

<p>
    <label>
        Select your locale:
        <select value="@selectedCulture" @onchange="HandleSelectedCultureChanged">
            @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-CR"),
    };

    private CultureInfo? selectedCulture;

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

    private async Task HandleSelectedCultureChanged(ChangeEventArgs args)
    {
        selectedCulture = CultureInfo.GetCultureInfo((string)args.Value!);

        if (selectedCulture is not null &&
            CultureInfo.CurrentCulture != selectedCulture)
        {
            var uri = new Uri(Navigation.Uri)
                .GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
            var cultureEscaped = Uri.EscapeDataString(selectedCulture.Name);
            var uriEscaped = Uri.EscapeDataString(uri);

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

CultureSelector 组件添加到 MainLayout 组件。

Components/Layout/MainLayout.razor 的标头标记中:

Shared/MainLayout.razor 的标头标记中:

<CultureSelector />

使用CultureExample1部分中显示的 组件来研究前面示例的工作原理。

前面的示例假定应用采用全局交互性,具体方法是在 组件 () 中的 Routes 组件上指定交互式服务器呈现模式App

<Routes @rendermode="InteractiveServer" />

如果应用采用 按页/组件 交互模式,并且只有服务器端组件提供区域性选择界面,请进行以下更改:

  • CultureExample1 组件文件(Components/Pages/CultureExample1.razor)顶部添加交互式服务器呈现模式:

    @rendermode InteractiveServer
    
  • 在应用的主布局(Components/Layout/MainLayout.razor)中,将交互式服务器呈现模式应用于 CultureSelector 组件:

    <CultureSelector @rendermode="InteractiveServer" />
    

按用户首选项在 Blazor Web App 中动态设置区域性

本部分适用于采用按页面/组件交互的 Blazor Web App。

应用可能存储用户首选项的位置的示例包括:在浏览器本地存储中(常见于客户端方案)、本地化 cookie 或数据库中(常见于服务器端方案),本地存储和本地化 cookie(带有服务器和 WebAssembly 组件的 Blazor Web App),或在附加到外部数据库并由 web API 访问的外部服务中。 以下示例演示如何对客户端呈现的 (CSR) 组件使用浏览器本地存储,以及对服务器端呈现的 (SSR) 组件使用本地化 cookie。 本节中的指导同样适用于采用按页/组件渲染并指定“交互式自动”渲染模式(@rendermode InteractiveAuto)的应用中的组件。

更新 .Client 项目

Microsoft.Extensions.Localization添加到 .Client 项目。

注意

有关将包添加到 .NET 应用的指南,请参阅 安装和管理包下的文章,位于 包消耗工作流(NuGet 文档)。 在 NuGet.org 中确认正确的包版本。

BlazorWebAssemblyLoadAllGlobalizationData 项目文件中将 true 属性设置为 .Client

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

System.GlobalizationMicrosoft.JSInterop 的命名空间添加到 .Client 项目的 Program 文件的顶部:

using System.Globalization;
using Microsoft.JSInterop;

删除以下行:

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

用下面的代码替换前面的行。 该代码通过 Blazor 将 AddLocalization 的本地化服务添加到应用的服务集合,并使用 JS 互操作调入 JS,然后从本地存储中检索用户的区域性选择。 如果本地存储不包含用户的文化,则代码将设置美国英语(en-US)的默认值。

builder.Services.AddLocalization();

var host = builder.Build();

const string defaultCulture = "en-US";

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

if (result == null)
{
    await js.InvokeVoidAsync("blazorCulture.set", defaultCulture);
}

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

await host.RunAsync();

注意

在 .NET 9 或更早版本中,独立Blazor WebAssembly应用基于 CultureInfo.DefaultThreadCurrentCulture加载 UI 全球化资源。 如果要为由 CultureInfo.DefaultThreadCurrentUICulture 定义的本地化区域性额外加载全球化数据,将应用升级到 .NET 10 或更高版本

将以下 CultureSelector 组件添加到 .Client 文件夹中的项目 SharedShared如果项目中不存在.Client文件夹,请创建一个文件夹来保存共享组件。

当用户更改区域性时,JS 互操作会在浏览器本地存储中设置区域性,并且 Minimal API 终结点会使用该区域性更新本地化 cookie。 最小 API 端点将在后面的 服务器项目更新 部分中添加到应用中。

Shared/CultureSelector.razor

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

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

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

    private CultureInfo? selectedCulture;

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

    private async Task ApplySelectedCultureAsync()
    {
        if (selectedCulture is not null &&
            CultureInfo.CurrentCulture != selectedCulture)
        {
            await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture.Name);

            var uri = new Uri(Navigation.Uri)
                .GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
            var cultureEscaped = Uri.EscapeDataString(selectedCulture.Name);
            var uriEscaped = Uri.EscapeDataString(uri);

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

在服务器项目的导入文件(_Imports.razor)中添加项目文件夹的Shared命名空间.Client(更新命名空间以匹配你的应用):

@using BlazorSample.Client.Shared

将以下标记放在文件的标头内容 Components/Layout/MainLayout.razor 中:

<CultureSelector @rendermode="InteractiveAuto" />

.Client 项目中,放置以下 CultureClient 组件,以研究全球化对 CSR 组件的影响。

Pages/CultureClient.razor

@page "/culture-client"
@rendermode InteractiveWebAssembly
@using System.Globalization

<PageTitle>Culture Client</PageTitle>

<h1>Culture Client</h1>

<ul>
    <li><b>CurrentCulture</b>: @CultureInfo.CurrentCulture</li>
    <li><b>CurrentUICulture</b>: @CultureInfo.CurrentUICulture</li>
</ul>

<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;
}

.Client 项目中,放置以下 CultureServer 组件,以研究全球化对 SSR 组件的影响。

Pages/CultureServer.razor

@page "/culture-server"
@rendermode InteractiveServer
@using System.Globalization

<PageTitle>Culture Server</PageTitle>

<h1>Culture Server</h1>

<ul>
    <li><b>CurrentCulture</b>: @CultureInfo.CurrentCulture</li>
    <li><b>CurrentUICulture</b>: @CultureInfo.CurrentUICulture</li>
</ul>

<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;
}

使用CultureExample1部分中所示的 组件来研究全球化如何适用于继承全局自动呈现模式的组件。 将 CultureExample1 组件添加到 .Client 项目的 Pages 文件夹中。 在组件顶部,指定交互式自动呈现模式:

@rendermode InteractiveAuto

CultureClientCultureServerCultureExample1组件添加到服务器项目的边栏导航Components/Layout/NavMenu.razor中:

<div class="nav-item px-3">
    <NavLink class="nav-link" href="culture-server">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Culture (Server)
    </NavLink>
</div>
<div class="nav-item px-3">
    <NavLink class="nav-link" href="culture-client">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Culture (Client)
    </NavLink>
</div>
<div class="nav-item px-3">
    <NavLink class="nav-link" href="culture-example-1">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Culture (Auto)
    </NavLink>
</div>

服务器项目更新

Microsoft.Extensions.Localization添加到服务器项目。

注意

有关将包添加到 .NET 应用的指南,请参阅 安装和管理包下的文章,位于 包消耗工作流(NuGet 文档)。 在 NuGet.org 中确认正确的包版本。

服务器端应用使用 本地化中间件进行本地化。 使用 AddLocalization 将本地化服务添加到应用。

在注册服务的服务器项目的 Program 文件中:

builder.Services.AddLocalization();

使用 RequestLocalizationOptions 设置应用的默认且受支持的区域性。

在请求处理管道中调用 MapRazorComponents 之前,请放置以下代码:

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

app.UseRequestLocalization(localizationOptions);

以下示例演示如何在可由本地化中间件读取的 cookie 中设置当前区域性。

System.GlobalizationMicrosoft.AspNetCore.Localization 命名空间添加到组件顶部 AppComponents/App.razor):

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

可使用 Blazor 框架的 API 设置应用用于客户端呈现的区域性。 用户的区域性选择可以保留在 CSR 组件的浏览器本地存储中。

标记Blazor<script>后,提供JS函数,以便通过浏览器本地存储获取和设置用户的区域性选择:

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

注意

前面的示例使用全局函数来污染客户端。 若要在生产应用中获取更好的方法,请参阅 JavaScript 模块中的 JavaScript 隔离

将以下 @code 块添加到 App 组件文件的底部:

@code {
    [CascadingParameter]
    private HttpContext? HttpContext { get; set; }

    protected override void OnInitialized()
    {
        HttpContext?.Response.Cookies.Append(
            CookieRequestCultureProvider.DefaultCookieName,
            CookieRequestCultureProvider.MakeCookieValue(
                new RequestCulture(
                    CultureInfo.CurrentCulture,
                    CultureInfo.CurrentUICulture)));
    }
}

若要提供支持用户选择区域性的 UI,请将基于重定向的方法和本地化 cookie 结合使用。 应用通过重定向到 Minimal API 终结点来持久保存用户所选的区域性。 终结点将用户选择的区域性设置为 cookie,然后将用户重定向回原始 URI。 此过程类似于用户尝试访问安全资源时在 Web 应用中发生的情况,用户会被重定向到登录页,然后重定向回原始资源。

Program 文件顶部,为所需命名空间添加以下 using 语句:

using Microsoft.AspNetCore.Localization;

在应用的UseRequestLocalization文件中的请求处理管道里,在调用Program之后:

app.MapGet("/Culture/Set", (string? culture, string redirectUri = "/",
    HttpContext context) =>
{
    if (!string.IsNullOrWhiteSpace(culture))
    {
        context.Response.Cookies.Append(
            CookieRequestCultureProvider.DefaultCookieName,
            CookieRequestCultureProvider.MakeCookieValue(
                new RequestCulture(culture, culture)));
    }

    return Results.LocalRedirect(redirectUri);
});

警告

请使用LocalRedirect结果(如前面的示例所示)来防止开放重定向攻击。 有关详细信息,请参阅 防止在 ASP.NET Core 中的公开重定向攻击

本地化

如果应用尚不支持动态文化选择,请将 Microsoft.Extensions.Localization,添加到应用中。

注意

有关将包添加到 .NET 应用的指南,请参阅 安装和管理包下的文章,位于 包消耗工作流(NuGet 文档)。 在 NuGet.org 中确认正确的包版本。

客户端侧本地化

在应用的项目文件 (BlazorWebAssemblyLoadAllGlobalizationData) 中将 true 属性设置为 .csproj

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

Program 文件中,将 System.Globalization 的命名空间添加到文件顶部:

using System.Globalization;

将 Blazor 的本地化服务添加到应用的服务集合中,其中包含 AddLocalization

builder.Services.AddLocalization();

服务器侧本地化

使用 本地化中间件 设置应用的区域性。

如果应用尚不支持动态区域性选择:

  • 使用 AddLocalization 将本地化服务添加到应用。
  • Program 文件中指定应用的默认且受支持的区域性。 下面的示例为美国英语和哥斯达黎加西班牙语配置支持的区域性。
builder.Services.AddLocalization();

将本地化中间件放在可能检查请求区域性的任何中间件之前。 通常,在调用 MapRazorComponents 前立即放置本地化中间件。

在将路由中间件 (UseRouting) 添加到处理管道后立即:

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

app.UseRequestLocalization(localizationOptions);

有关在中间件管道中订购本地化中间件的信息,请参阅 ASP.NET Core中间件

  • 使用 AddLocalization 将本地化服务添加到应用。
  • Startup.Configure (Startup.cs) 中指定应用的默认且受支持的区域性。 下面的示例为美国英语和哥斯达黎加西班牙语配置支持的区域性。

Startup.ConfigureServices (Startup.cs) 中:

services.AddLocalization();

在将路由中间件 (UseRouting) 添加到处理管道后立即在 Startup.Configure 中:

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

app.UseRequestLocalization(localizationOptions);

有关在中间件管道Startup.Configure中订购本地化中间件的信息,请参阅 ASP.NET Core中间件

如果应用应基于存储用户的区域性设置来本地化资源,则使用本地化区域性 cookie。 使用 cookie 可确保 WebSocket 连接可以正确地传播区域性。 如果本地化方案基于 URL 路径或查询字符串,则该方案可能无法与 Websocket 协同使用,因而无法保留区域性。 因此,建议的方法是使用本地化区域性 cookie。 若要查看保留用户区域性选择的示例 表达式,请参阅本文的Razor部分。

本地化资源的示例

本部分中的本地化资源示例与本文前面的示例一样,应用的受支持区域性是将英语 (en) 作为默认区域设置,将西班牙语 (es) 作为用户可选择的或浏览器指定的备用区域设置。

为每个区域设置创建资源文件。 在以下示例中,为英语和西班牙语的 Greeting 字符串创建了资源:

  • 英语 (en):Hello, World!
  • 西班牙语 (es):¡Hola, Mundo!

注意

可以通过在 Visual Studio 中右键单击 Pages 文件夹,然后选择 Add>New Item>Resources File 来添加以下资源文件。 命名文件 CultureExample2.resx。 出现编辑器时,为新项提供数据。 将“名称”设置为 Greeting,并将“值”设置为 Hello, World!。 保存文件。

如果使用 Visual Studio Code,建议安装 Tim Heuer 的 ResX 查看器和编辑器。 将空 CultureExample2.resx 文件添加到 Pages 文件夹。 该扩展会自动接管 UI 中的文件管理。 选择“添加新资源”按钮。 按照说明为 Greeting(键)、Hello, World!(值)和 None(注释)添加条目。 保存文件。 如果关闭并重新打开该文件,可以看到 Greeting 资源。

Tim Heuer 的 ResX 查看器和编辑器不由Microsoft拥有或维护,并且不受任何Microsoft 支持部门协议或许可证保护。

以下内容演示了典型的资源文件。 如果不想将内置工具与集成开发环境(IDE)一起使用(例如Visual Studio的内置资源文件编辑器),或者具有用于创建和编辑资源文件的扩展名的Visual Studio Code,则可以手动将资源文件放入应用的 Pages 文件夹中。

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>

注意

可以通过在 Visual Studio 中右键单击 Pages 文件夹,然后选择 Add>New Item>Resources File 来添加以下资源文件。 命名文件 CultureExample2.es.resx。 出现编辑器时,为新项提供数据。 将“名称”设置为 Greeting,并将“值”设置为 ¡Hola, Mundo!。 保存文件。

如果使用 Visual Studio Code,建议安装 Tim Heuer 的 ResX 查看器和编辑器。 将空 CultureExample2.resx 文件添加到 Pages 文件夹。 该扩展会自动接管 UI 中的文件管理。 选择“添加新资源”按钮。 按照说明为 Greeting(键)、¡Hola, Mundo!(值)和 None(注释)添加条目。 保存文件。 如果关闭并重新打开该文件,可以看到 Greeting 资源。

以下内容演示了典型的资源文件。 如果不想将内置工具与集成开发环境(IDE)一起使用(例如Visual Studio的内置资源文件编辑器),或者具有用于创建和编辑资源文件的扩展名的Visual Studio Code,则可以手动将资源文件放入应用的 Pages 文件夹中。

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>

以下组件演示如何将本地化 Greeting 字符串用于 IStringLocalizer<T>。 以下示例中的 Razor 标记 @Loc["Greeting"] 将键控为 Greeting 值的字符串本地化,该值在前面的资源文件中设置。

将命名空间 Microsoft.Extensions.Localization 添加到应用的导入文件(_Imports.razor):

@using Microsoft.Extensions.Localization

CultureExample2.razor

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

<h1>Culture Example 2</h1>

<ul>
    <li><b>CurrentCulture</b>: @CultureInfo.CurrentCulture</li>
    <li><b>CurrentUICulture</b>: @CultureInfo.CurrentUICulture</li>
</ul>

<h2>Greeting</h2>

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

<p>
    @greeting
</p>

@code {
    private string? greeting;

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

另外,也可选择将 CultureExample2 组件的菜单项添加到 NavMenu 组件 (NavMenu.razor) 中的导航。

WebAssembly 区域性提供程序参考源

若要进一步了解 Blazor 框架如何处理本地化,请参阅 ASP.NET Core引用源中的 WebAssemblyCultureProvider

注意

指向.NET引用源的文档链接通常加载存储库的默认分支,该分支表示下一版本的.NET的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅 如何选择 ASP.NET Core源代码的版本标记(dotnet/AspNetCore.Docs #26205)

共享资源

若要创建本地化共享资源,请采用以下方法。

  • 确认项目引用了 Microsoft.Extensions.Localization

    注意

    有关将包添加到 .NET 应用的指南,请参阅 安装和管理包下的文章,位于 包消耗工作流(NuGet 文档)。 在 NuGet.org 中确认正确的包版本。

  • 通过项目 imports 文件 (_Imports.razor) 中的条目,确认Microsoft.Extensions.Localization命名空间可用于项目的Razor组件:

    @using Microsoft.Extensions.Localization
    
  • 创建具有任意类名的虚拟类。 在以下示例中:

    • 应用使用 BlazorSample 命名空间,本地化资产使用 BlazorSample.Localization 命名空间。
    • 虚拟类名为 SharedResource
    • 类文件位于应用根目录的 Localization 文件夹中。

    注意

    请勿使用自动生成的设计器文件(例如,SharedResources.Designer.cs)。 虚拟类旨在充当共享资源类。 设计器文件的存在会导致命名空间冲突。

    Localization/SharedResource.cs

    namespace BlazorSample.Localization;
    
    public class SharedResource
    {
    }
    
  • 使用 的生成操作Embedded resource创建共享资源文件。 在以下示例中:

    • 文件放置在具有虚拟 Localization 类 (SharedResource) 的 Localization/SharedResource.cs 文件夹中。

    • 将资源文件命名为与虚拟类的名称一致。 以下示例文件包括一个默认本地化文件和一个西班牙语 (es) 本地化文件。

    • Localization/SharedResource.resx

    • Localization/SharedResource.es.resx

    警告

    按照本部分中的方法操作时,不能同时设置 LocalizationOptions.ResourcesPath 并使用 IStringLocalizerFactory.Create 来加载资源。

  • 若要引用 IStringLocalizer<T> 组件中已注入 Razor 的虚拟类,请为本地化命名空间放置 @using 指令,或在虚拟类引用中包含本地化命名空间。 在以下示例中:

    • 第一个示例使用 Localization 指令声明 SharedResource 虚拟类的 @using 命名空间。
    • 第二个示例显式声明 SharedResource 虚拟类的命名空间。

    在 Razor 组件中,使用以下任一方法

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

有关其他指南,请参阅 ASP.NET Core 中的全局均衡化和本地化。

使用开发者工具中“传感器”窗格替代位置

使用 Google Chrome 或Microsoft Edge 开发者工具中的“传感器”窗格来替代位置时,预渲染后备用语言会重置。 在测试时避免使用“传感器”窗格设置语言。 使用浏览器的语言设置来设置语言。

有关详细信息,请参阅Blazor本地化不适用于 InteractiveServer (dotnet/aspnetcore #53707)

其他资源