mcpField
D
dotnet-localization

dotnet-localization

github.com/wshaddix开发8675 次安装35 周前更新
代码仓库

关于 dotnet-localization

面向多语言应用开发者的 .NET 本地化技能,介绍资源格式、字符串查找、区域性格式化、从右向左布局、复数规则,以及多种 .NET UI 框架的集成方式。

主要功能

  1. 01使用 .resx 和 JSON 资源
  2. 02通过 IStringLocalizer 本地化字符串
  3. 03按区域性格式化日期、数字和货币
  4. 04支持从右向左布局
  5. 05使用本地化库处理复数形式
  6. 06集成多种 .NET UI 框架

适用场景

  1. 01为 .NET 应用添加翻译文本
  2. 02按指定区域性格式化价格和日期
  3. 03为从右向左书写的语言准备界面
  4. 04为多种区域性选择复数处理方案

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-localization

安装后在 Claude Code 中输入/dotnet-localization确认已加载。

详细介绍

dotnet-localization

全面的 .NET 国际化与本地化:.resx 资源文件和附属程序集、现代替代方案(JSON 资源、用于 AOT 的源生成器)、IStringLocalizer 模式、使用 CultureInfo 格式化日期、数字和货币、RTL 布局支持、复数化引擎,以及针对 Blazor、MAUI、Uno Platform 和 WPF 的框架级本地化集成。

**版本假设:**以 .NET 8.0+ 为基准。IStringLocalizer 自 .NET Core 1.0 起保持稳定;本地化 API 自 .NET 5 起保持稳定。.NET 9+ 的功能会明确标注。

**范围界定:**本技能涵盖所有跨领域的本地化问题:资源格式、IStringLocalizer、格式化、RTL 和复数化。UI 框架小节提供架构概览,并交叉引用框架专属技能以了解深入的实现模式。

**不在范围内:**深入的 Blazor 组件模式——参见 [skill:dotnet-blazor-components]。深入的 MAUI 开发模式——参见 [skill:dotnet-maui-development]。Uno Platform 项目结构和 Extensions 生态系统——参见 [skill:dotnet-uno-platform]。WPF Host builder 和 MVVM 模式——参见 [skill:dotnet-wpf-modern]。源生成器编写(Roslyn API)——参见 [skill:dotnet-csharp-source-generators]。

交叉引用:[skill:dotnet-blazor-components] 了解 Blazor 组件生命周期,[skill:dotnet-maui-development] 了解 MAUI 应用结构,[skill:dotnet-uno-platform] 了解 Uno Extensions 和 x:Uid,[skill:dotnet-wpf-modern] 了解现代 .NET 上的 WPF。


.resx 资源文件

概述

资源文件(.resx)是标准的 .NET 本地化格式。它们会编译成附属程序集,由 ResourceManager 解析,并自动执行区域性回退。

区域性回退链

资源按特定程度从高到低依次解析,直到找到匹配项:

sr-Cyrl-RS.resx -> sr-Cyrl.resx -> sr.resx -> Resources.resx (default/neutral)

默认 .resx 文件(不带区域性后缀)是唯一事实来源。翻译文件不得包含默认文件中不存在的键。

项目设置

<PropertyGroup>
  <NeutralLanguage>en-US</NeutralLanguage>
</PropertyGroup>

<ItemGroup>
  
  <EmbeddedResource Include="Resources\Messages.resx" />
  
  <EmbeddedResource Include="Resources\Messages.fr-FR.resx" />
  <EmbeddedResource Include="Resources\Messages.de-DE.resx" />
</ItemGroup>
资源文件结构

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="Welcome" xml:space="preserve">
    <value>Welcome to the application</value>
    <comment>Shown on the home page</comment>
  </data>
  <data name="ItemCount" xml:space="preserve">
    <value>You have {0} item(s)</value>
    <comment>{0} = number of items</comment>
  </data>
</root>
访问资源
// Via generated strongly-typed class (ResXFileCodeGenerator custom tool)
string welcome = Messages.Welcome;

// Via ResourceManager directly
var rm = new ResourceManager("MyApp.Resources.Messages",
    typeof(Messages).Assembly);
string welcome = rm.GetString("Welcome", CultureInfo.CurrentUICulture);

现代替代方案

基于 JSON 的资源

适用于已使用 JSON 配置的项目的轻量级替代方案。相关库提供由 JSON 文件支持的 IStringLocalizer 实现。

// Resources/en-US.json
{
  "Welcome": "Welcome to the application",
  "ItemCount": "You have {0} item(s)"
}

库:

  • Senlin.Mo.Localization——由 JSON 支持的 IStringLocalizer
  • Embedded.Json.Localization——嵌入式 JSON 资源

JSON 资源在 ASP.NET Core 项目中很常见,但缺少 .resx 所具备的内置工具支持(Visual Studio 设计器、附属程序集编译)。

用于 AOT 兼容性的源生成器

传统的 .resx 配合 ResourceManager 会在运行时使用反射,这对 Native AOT 和修剪不利。源生成器可在编译时生成强类型访问器类,从而避免运行时反射。

推荐的源生成器:

生成器说明AOT 安全
ResXGenerator (ycanardeau)支持 IStringLocalizer 和 DI 注册的强类型类是
VocaDb.ResXFileCodeGenerator原始的强类型 .resx 源生成器是
内置 ResXFileCodeGeneratorVisual Studio 自定义工具(不是 Roslyn 源生成器)否——生成静态属性,但仍使用 ResourceManager

<ItemGroup>
  <PackageReference Include="ResXGenerator" Version="1.*"
                    PrivateAssets="all" />
</ItemGroup>
// Generated at compile time -- no runtime reflection
string welcome = Messages.Welcome;

// With DI registration (ResXGenerator)
services.AddResXLocalization();

**建议:**使用 .resx 文件作为资源格式(工具支持最广泛),并在 AOT/修剪场景中使用源生成器。仅在轻量级或配置繁多的项目中使用 JSON 资源。


IStringLocalizer 模式

注册
var builder = WebApplication.CreateBuilder(args);

// Register localization services
builder.Services.AddLocalization(options =>
    options.ResourcesPath = "Resources");

var app = builder.Build();

// Configure request localization middleware
var supportedCultures = new[] { "en-US", "fr-FR", "de-DE", "ja-JP" };
app.UseRequestLocalization(options =>
{
    options.SetDefaultCulture(supportedCultures[0])
           .AddSupportedCultures(supportedCultures)
           .AddSupportedUICultures(supportedCultures);
});
IStringLocalizer

主要的本地化接口。可通过 DI 注入。可在各处使用:服务、控制器、Blazor 组件和中间件。

public class OrderService
{
    private readonly IStringLocalizer<OrderService> _localizer;

    public OrderService(IStringLocalizer<OrderService> localizer)
    {
        _localizer = localizer;
    }

    public string GetConfirmation(int orderId)
    {
        // Indexer returns LocalizedString with implicit string conversion
        return _localizer["OrderConfirmed", orderId];
        // Resolves: "Order {0} confirmed" with orderId substituted
    }

    public bool IsTranslated(string key)
    {
        LocalizedString result = _localizer[key];
        return !result.ResourceNotFound;
    }
}
IViewLocalizer(仅限 MVC Razor 视图)

自动解析与视图路径匹配的资源文件。Blazor 不支持。

@* Views/Home/Index.cshtml *@
@inject IViewLocalizer Localizer

<h1>@Localizer["Welcome"]</h1>
<p>@Localizer["ItemCount", Model.Count]</p>

资源文件位置:Resources/Views/Home/Index.en-US.resx

IHtmlLocalizer(仅限 MVC)

支持 HTML 的变体,会对格式参数进行 HTML 编码,但保留资源字符串本身中的 HTML。Blazor 不支持。

@inject IHtmlLocalizer<SharedResource> HtmlLocalizer

@* Resource: "Read our <a href='/terms'>terms</a>, {0}" *@
@* {0} is HTML-encoded, the <a> tag is preserved *@
<p>@HtmlLocalizer["TermsNotice", Model.UserName]</p>
何时使用各接口
接口范围HTML 安全BlazorMVC
IStringLocalizer<T>全局适用否(纯文本)是是
IViewLocalizer视图局部字符串否否是
IHtmlLocalizer<T>资源中的 HTML是否是
命名空间解析

如果资源查找失败,请检查命名空间是否匹配。IStringLocalizer<T> 会根据 ResourcesPath,使用 T 的完整类型名称解析资源。使用 RootNamespaceAttribute 修复命名空间与程序集不匹配的问题:

[assembly: RootNamespace("MyApp")]

日期、数字和货币格式化

CultureInfo

CultureInfo 是用于区域性特定格式化的核心类。以下两个不同的属性用于控制行为:

  • CultureInfo.CurrentCulture —— 控制格式化(日期、数字、货币)
  • CultureInfo.CurrentUICulture —— 控制资源查找(要使用哪个 .resx 文件)
// Always pass explicit CultureInfo -- never rely on thread defaults in server code
var date = DateTime.Now.ToString("D", new CultureInfo("fr-FR"));
// "vendredi 14 fevrier 2026"

var price = 1234.56m.ToString("C", new CultureInfo("de-DE"));
// "1.234,56 EUR" (uses NumberFormatInfo.CurrencySymbol)

var number = 1234567.89.ToString("N2", new CultureInfo("ja-JP"));
// "1,234,567.89"
服务端最佳实践
// Use useUserOverride: false in server scenarios to avoid
// picking up user-customized formats
var culture = new CultureInfo("en-US", useUserOverride: false);

// Set culture per-request (ASP.NET Core middleware handles this)
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
格式说明符
说明符类型示例(en-US)示例(de-DE)
"d"短日期2/14/202614.02.2026
"D"长日期星期五,February 14, 2026Freitag, 14. Februar 2026
"C"货币$1,234.561.234,56 EUR
"N2"数字1,234.571.234,57
"P1"百分比85.5%85,5 %

RTL 支持

检测 RTL 区域性
bool isRtl = CultureInfo.CurrentCulture.TextInfo.IsRightToLeft;
// true for: ar-*, he-*, fa-*, ur-*, etc.
各框架中的 RTL 模式

**Blazor:**没有原生的 FlowDirection——请使用 CSS dir 属性:

// wwwroot/js/app.js
window.setDocumentDirection = (dir) => document.documentElement.dir = dir;
// Set via named JS function (avoid eval -- causes CSP unsafe-eval violations)
await JSRuntime.InvokeVoidAsync("setDocumentDirection",
    isRtl ? "rtl" : "ltr");

如需了解深入的 Blazor 组件模式,请参阅 [skill:dotnet-blazor-components]。

MAUI:VisualElement 和 Window 上的 FlowDirection 属性:

// Set at window level -- cascades to all children
window.FlowDirection = isRtl
    ? FlowDirection.RightToLeft
    : FlowDirection.LeftToRight;

Android 需要在 AndroidManifest.xml 中设置 android:supportsRtl="true"(MAUI 默认已设置)。如需了解深入的 MAUI 模式,请参阅 [skill:dotnet-maui-development]。

**Uno Platform:**继承 WinUI 的 FlowDirection 模型:

<Page FlowDirection="RightToLeft">
  
</Page>

如需了解 Uno Extensions 和 x:Uid 绑定,请参阅 [skill:dotnet-uno-platform]。

WPF:FrameworkElement 上的 FlowDirection 属性:

<Window FlowDirection="RightToLeft">
  
</Window>

如需了解现代 .NET 中的 WPF 模式,请参阅 [skill:dotnet-wpf-modern]。


复数形式

问题

简单的字符串插值无法处理跨语言的复数形式:

// WRONG: English-only, breaks in languages with complex plural rules
$"You have {count} item{(count != 1 ? "s" : "")}"

阿拉伯语等语言有六种复数形式(零、单数、双数、少数、多数、其他)。波兰语则根据数字范围区分“少数”和“多数”。

ICU MessageFormat(MessageFormat.NET)

使用 ICU 复数类别实现符合 CLDR 标准的复数处理。推荐用于以国际化为先的项目。

// Package: jeffijoe/messageformat.net (v5.0+, ships CLDR pluralizers)
var formatter = new MessageFormatter();

string pattern = "{count, plural, " +
    "=0 {No items}" +
    "one {# item}" +
    "other {# items}}";

formatter.Format(pattern, new { count = 0 });  // "No items"
formatter.Format(pattern, new { count = 1 });  // "1 item"
formatter.Format(pattern, new { count = 42 }); // "42 items"
SmartFormat.NET

灵活的文本模板,内置复数处理。适合希望获得最大灵活性的项目。

// Package: axuno/SmartFormat (v3.6.1+)
using SmartFormat;

Smart.Format("{count:plural:No items|# item|# items}",
    new { count = 0 });  // "No items"
Smart.Format("{count:plural:No items|# item|# items}",
    new { count = 1 });  // "1 item"
Smart.Format("{count:plural:No items|# item|# items}",
    new { count = 5 });  // "5 items"
选择复数处理引擎
引擎CLDR 合规性API 风格最适用于
MessageFormat.NET完全支持(CLDR 类别)ICU 模式字符串需要符合标准的多区域应用
SmartFormat.NET部分支持(可扩展).NET 格式字符串扩展支持复数处理的灵活模板
手动条件判断无string.Format + 分支判断仅支持英语简单双数形式的场景

UI 框架集成

Blazor 本地化

Blazor 仅支持 IStringLocalizer——不支持 IHtmlLocalizer 和 IViewLocalizer。

组件注入:

@inject IStringLocalizer<MyComponent> Loc

<h1>@Loc["Welcome"]</h1>
<p>@Loc["ItemCount", items.Count]</p>

按呈现模式配置区域性:

呈现模式区域性来源
Server / SSRRequestLocalizationMiddleware(服务器端)
WebAssemblyCultureInfo.DefaultThreadCurrentCulture + Blazor 启动选项 applicationCulture
Auto两者——初始加载使用服务器中间件,客户端使用 WASM 区域性

WASM 全球化数据:


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

如果不设置此属性,Blazor WASM 只会加载部分 ICU 数据。若要将下载大小降至最低,请使用 InvariantGlobalization=true(这会完全禁用本地化)。

动态切换区域性:

// CultureSelector component pattern:
// 1. Store selected culture in browser local storage
// 2. Set culture cookie via controller redirect (server-side)
// 3. Read cookie in RequestLocalizationMiddleware

如需了解 Blazor 组件的深入模式(生命周期、状态管理、JS 互操作),请参阅 [skill:dotnet-blazor-components]。

MAUI 本地化

MAUI 使用 .resx 文件,并生成强类型属性。

资源设置:

Resources/
  Strings/
    AppResources.resx              # Default (neutral) culture
    AppResources.fr-FR.resx        # French
    AppResources.ja-JP.resx        # Japanese

XAML 绑定:


<ContentPage xmlns:strings="clr-namespace:MyApp.Resources.Strings">

  
  <Label Text="{x:Static strings:AppResources.Welcome}" />
  <Button Text="{x:Static strings:AppResources.LoginButton}" />
</ContentPage>

代码访问:

string welcome = AppResources.Welcome;

平台要求:

  • iOS/Mac Catalyst:在 Info.plist 中添加 CFBundleLocalizations
  • Windows:在 Package.appxmanifest 中添加 <Resource Language="..."> 条目
  • 所有平台:在 csproj 中设置 <NeutralLanguage>en-US</NeutralLanguage>

如需了解 MAUI 开发的深入模式(控件、导航、平台 API),请参阅 [skill:dotnet-maui-development]。

Uno Platform 本地化

Uno 使用 .resw 文件(Windows 资源格式),并通过 x:Uid 自动绑定 XAML 资源。

资源结构:

Strings/
  en/Resources.resw
  fr-FR/Resources.resw
  ja-JP/Resources.resw

注册:

// In Host builder configuration
.UseLocalization()

使用 x:Uid 进行 XAML 绑定:


<TextBlock x:Uid="MainPage_Title" />
<Button x:Uid="LoginButton" />

运行时切换区域性:

var localizationService = serviceProvider
    .GetRequiredService<ILocalizationService>();
await localizationService.SetCurrentCultureAsync(
    new CultureInfo("fr-FR"));
// Note: XAML x:Uid bindings retain old culture until app restart

**已知限制:**基于 x:Uid 的本地化会一直保留旧区域性,直到应用重启,即使调用了 SetCurrentCultureAsync 也是如此。基于代码的 IStringLocalizer 会立即更新。

如需了解 Uno Extensions 生态系统配置和 MVUX 模式,请参阅 [skill:dotnet-uno-platform]。

WPF 本地化

**.NET 8+ 的推荐方案:**使用 .resx 文件和 DynamicResource 绑定,以便在运行时切换区域设置。避免使用 LocBaml(仅适用于 .NET Framework)。

资源字典方案:


<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:sys="clr-namespace:System;assembly=System.Runtime">
  <sys:String x:Key="Welcome">Welcome</sys:String>
  <sys:String x:Key="LoginButton">Log In</sys:String>
</ResourceDictionary>

<TextBlock Text="{DynamicResource Welcome}" />
<Button Content="{DynamicResource LoginButton}" />

运行时切换区域设置:

// Swap resource dictionary at runtime
var dict = new ResourceDictionary
{
    Source = new Uri($"Resources/Strings.{cultureName}.xaml",
                     UriKind.Relative)
};
Application.Current.Resources.MergedDictionaries.Clear();
Application.Current.Resources.MergedDictionaries.Add(dict);

ResX 方案(更简单,适用于所有 .NET 版本):

// Standard .resx with generated class
string welcome = Strings.Welcome;

// Runtime switch
Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR");
// Re-read after culture change
string welcomeFr = Strings.Welcome; // Now returns French

社区方案:

  • WPF Localization Extensions——使用 RESX 文件和 XAML 标记扩展实现声明式本地化
  • LocBamlCore(h3xds1nz)——非官方移植版本,支持 .NET 9,可在现代 .NET 上进行 BAML 本地化

如需了解 WPF Host builder、MVVM Toolkit 和主题模式,请参阅 [skill:dotnet-wpf-modern]。


Agent 常见问题

  1. 不要在 Blazor 中使用 IHtmlLocalizer 或 IViewLocalizer。 这些是仅适用于 MVC 的功能。在 Blazor 组件中使用 IStringLocalizer<T>。
  2. 不要在服务端代码中依赖 CultureInfo.CurrentCulture 的线程默认值。 始终向格式化方法显式传入 CultureInfo。服务端线程区域性可能与请求区域性不一致。
  3. 不要硬编码复数形式。 英语的“单数/复数”规则不适用于阿拉伯语(有 6 种形式)、波兰语或其他语言。使用 MessageFormat.NET 或 SmartFormat.NET 实现正确的 CLDR 复数规则。
  4. 不要在 .NET 8 及更高版本的 WPF 中使用 LocBaml。 LocBaml 是仅适用于 .NET Framework 的示例工具。现代 WPF 应使用 .resx 文件或资源字典。
  5. 不要忘记为 Blazor WASM 设置 BlazorWebAssemblyLoadAllGlobalizationData。 如果未设置,只会加载部分 ICU 数据,导致许多区域性的日期/数字格式不正确。
  6. 不要添加默认 .resx 文件中不存在的翻译键。 默认资源是唯一可信来源;卫星程序集必须是其子集。
  7. 不要在 AOT/裁剪应用中直接使用 ResourceManager。 它依赖反射。使用源生成器(ResXGenerator)在编译时访问资源。
  8. 不要忘记 MAUI 的特定平台设置。 iOS/Mac Catalyst 需要在 Info.plist 中设置 CFBundleLocalizations;Windows 需要 Resource Language 条目。

评论

支持 Markdown