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

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

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

全球化和本地化

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

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

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

不支持: IHtmlLocalizerIViewLocalizer数据注释本地化是 ASP.NET Core MVC 功能,在 Blazor 应用中不受支持

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

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

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

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

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

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

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

注意

本文中的代码示例采用 .NET 6 或更高版本中的 ASP.NET Core 支持的可为空的引用类型 (NRT) 和 .NET 编译器 Null 状态静态分析。 面向 ASP.NET Core 5.0 或更早版本时,请从文章示例中删除 null 类型指定 (?)。

全球化

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

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

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

  • date
  • number

上述字段类型:

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

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

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

  • datetime-local
  • month
  • week

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

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

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

若要加载自定义 ICU 数据文件来控制应用的区域设置,请参阅 WASM 全球化 ICU。 目前,需要手动生成自定义的 ICU 数据文件。 .NET 工具旨在简化创建文件的过程,计划于 2025 年 11 月针对 .NET 10。

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

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

固定全球化

本部分仅适用于客户端 Blazor 场景。

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

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

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

  • runtimeconfig.json中:

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

    • 键:DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
    • 值:true1

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

时区信息

本部分仅适用于客户端 Blazor 场景。

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

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

注意

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

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

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

演示组件

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

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

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

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

Microsoft.Extensions.Localization 包添加到应用。

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

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

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

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

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

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

说明

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

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

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

builder.Services.AddLocalization();

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

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

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

有关在 Program 文件的中间件管道中对本地化中间件进行排序的信息,请参阅 ASP.NET Core 中间件

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

注意

一些浏览器会强制你在请求和浏览器自身的用户界面设置中使用默认的语言设置。 这可能会导致难以将语言变更回你所能理解的语言,因为所有设置 UI 屏幕可能最终都使用你无法理解的语言。 像 Opera 这样的浏览器是一个很好的测试选择,因为它允许你为网页请求设置一个默认语言,但将浏览器的设置 UI 保留为你的语言。

当区域性为美国英语 (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

静态设置客户端侧区域性

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

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

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

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

通过将 autostart="false" 添加到 Blazor 的 <script> 标记来阻止 Blazor 自动启动:

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

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

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

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 启动

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

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

using System.Globalization;

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

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

注意

目前,Blazor WebAssembly 应用仅基于 DefaultThreadCurrentCulture 加载资源。 有关详细信息,请参阅 Blazor WASM 仅依赖于当前区域性(不遵循当前 UI 区域性)(dotnet/aspnetcore #56824)

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

静态设置服务器侧区域性

服务器侧应用使用本地化中间件进行本地化。 使用 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)。

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

应用可能存储用户首选项的位置的示例包括:在浏览器本地存储中(常见于客户端场景)、在本地化 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();

用下面的代码替换前面的行。 该代码通过 AddLocalization 将 Blazor 的本地化服务添加到应用的服务集合,并使用 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();

注意

目前,Blazor WebAssembly 应用仅基于 DefaultThreadCurrentCulture 加载资源。 有关详细信息,请参阅 Blazor WASM 仅依赖于当前区域性(不遵循当前 UI 区域性)(dotnet/aspnetcore #56824)

以下 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 (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 (CultureInfo.CurrentCulture != selectedCulture)
        {
            await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture!.Name);

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

MainLayout 组件 (MainLayout.razor) 中的 </main> 元素的结束标记内,添加 CultureSelector 组件:

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

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

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

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

注意

以下示例假定应用采用全局交互性,具体方法是在 App 组件(Components/App.razor)中的 Routes 组件上指定交互式服务器端呈现(交互式 SSR):

<Routes @rendermode="InteractiveServer" />

如果应用采用每页/组件交互性,请参阅本节末尾的备注以修改示例组件的呈现模式。

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 组件需要以下命名空间:

将以下内容添加到 App 组件文件 (Components/App.razor) 的顶部:

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

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

@code {
    [CascadingParameter]
    public 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 文件所做的修改需要以下命名空间:

将以下内容添加到文件中:

@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 中间件

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

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

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

    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 组件演示了如何通过新的区域性调用 CultureControllerSet 方法。 该组件位于 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 (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 (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 文件中的结束 </main> 标记中:

CultureSelector 组件添加到 MainLayout 组件。 将以下标记放在 Shared/MainLayout.razor 文件中的结束 </main> 标记中:

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

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

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

<Routes @rendermode="InteractiveServer" />

如果应用采用每页/组件交互性,请进行以下更改

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

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

    <CultureSelector @rendermode="InteractiveServer" />
    

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

本部分适用于采用自动(服务器和 WebAssembly)交互性的 Blazor Web App。

应用可能存储用户偏好设置的位置示例包括浏览器本地存储(常见于客户端场景)、本地化cookie或数据库(常见于服务器端场景)、本地存储和本地化 cookie(带有服务器和 WebAssembly 组件的 Blazor Web App),或者附加到外部数据库并由 Web API 访问的外部服务。 以下示例演示如何对客户端呈现的 (CSR) 组件使用浏览器本地存储,以及对服务器端呈现的 (SSR) 组件使用本地化 cookie。

更新 .Client 项目

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

注意

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

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

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

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

using System.Globalization;
using Microsoft.JSInterop;

删除以下行:

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

用下面的代码替换前面的行。 该代码通过 AddLocalization 将 Blazor 的本地化服务添加到应用的服务集合,并使用 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();

注意

目前,Blazor WebAssembly 应用仅基于 DefaultThreadCurrentCulture 加载资源。 有关详细信息,请参阅 Blazor WASM 仅依赖于当前区域性(不遵循当前 UI 区域性)(dotnet/aspnetcore #56824)

.Client 项目添加以下 CultureSelector 组件。

该组件采用以下方法来处理 SSR 或 CSR 组件:

  • 下拉列表中每个可用区域性的显示名称由字典提供,因为客户端全球化数据包括服务器端全球化数据提供的区域性显示名称的本地化文本。 例如,当 en-US 为区域性时,服务器端本地化显示 English (United States);当使用其他区域性时,显示 Ingles ()。 由于区域性显示名称的本地化不适用于 Blazor WebAssembly 全球化,因此对于任何加载的区域性,客户端上的美国英语显示名称只是 en-US。 使用自定义词典可让组件至少显示完整的英语区域性名称。
  • 当用户更改区域性时,JS 互操作在本地浏览器存储中设置区域性,控制器操作使用相应区域性更新本地化 cookie。 控制器稍后会在服务器项目更新部分中添加到应用。

Pages/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">@cultureDict[culture.Name]</option>
            }
        </select>
    </label>
</p>

@code
{
    private Dictionary<string, string> cultureDict = 
        new()
        {
            { "en-US", "English (United States)" },
            { "es-CR", "Spanish (Costa Rica)" }
        };

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

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

服务器项目更新

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 中设置当前区域性。

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

将以下内容添加到 App 组件文件 (Components/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]
    public HttpContext? HttpContext { get; set; }

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

如果服务器项目未配置为处理控制器操作:

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

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

    app.MapControllers();
    

若要使用户能够为 SSR 组件选择区域性,请将基于重定向的方法与本地化 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 组件添加到 MainLayout 组件。 将以下标记放在 Components/Layout/MainLayout.razor 文件中的结束 </main> 标记中:

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

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

在服务器项目中,放置以下 CultureServer 组件,以研究全球化对 SSR 组件的影响。

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

CultureClientCultureServer 组件添加到 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>

交互式自动组件

本部分中的指南也适用于采用交互式自动呈现模式的组件:

@rendermode InteractiveAuto

本地化

如果应用尚不支持动态区域性选择,请将 Microsoft.Extensions.Localization 包添加到应用。

说明

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

客户端侧本地化

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

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

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

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 文件夹并选择“添加”>“新项”>“资源文件”,添加以下资源文件。 为 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 文件夹并选择“添加”>“新项”>“资源文件”,添加以下资源文件。 为 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)

共享资源

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

  • 创建具有任意类名的虚拟类。 如下示例中:

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

    Localization/SharedResource.cs:

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

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

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

    • Localization/SharedResource.resx

    • Localization/SharedResource.es.resx

    警告

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

  • 若要引用 Razor 组件中已注入 IStringLocalizer<T> 的虚拟类,请为本地化命名空间放置 @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)

其他资源