关于 dotnet-localization
面向多语言应用开发者的 .NET 本地化技能,介绍资源格式、字符串查找、区域性格式化、从右向左布局、复数规则,以及多种 .NET UI 框架的集成方式。
主要功能
- 01使用 .resx 和 JSON 资源
- 02通过 IStringLocalizer 本地化字符串
- 03按区域性格式化日期、数字和货币
- 04支持从右向左布局
- 05使用本地化库处理复数形式
- 06集成多种 .NET UI 框架
适用场景
- 01为 .NET 应用添加翻译文本
- 02按指定区域性格式化价格和日期
- 03为从右向左书写的语言准备界面
- 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 支持的IStringLocalizerEmbedded.Json.Localization——嵌入式 JSON 资源
JSON 资源在 ASP.NET Core 项目中很常见,但缺少 .resx 所具备的内置工具支持(Visual Studio 设计器、附属程序集编译)。
用于 AOT 兼容性的源生成器
传统的 .resx 配合 ResourceManager 会在运行时使用反射,这对 Native AOT 和修剪不利。源生成器可在编译时生成强类型访问器类,从而避免运行时反射。
推荐的源生成器:
| 生成器 | 说明 | AOT 安全 |
|---|---|---|
| ResXGenerator (ycanardeau) | 支持 IStringLocalizer 和 DI 注册的强类型类 | 是 |
| VocaDb.ResXFileCodeGenerator | 原始的强类型 .resx 源生成器 | 是 |
内置 ResXFileCodeGenerator | Visual 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 安全 | Blazor | MVC |
|---|---|---|---|---|
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/2026 | 14.02.2026 |
"D" | 长日期 | 星期五,February 14, 2026 | Freitag, 14. Februar 2026 |
"C" | 货币 | $1,234.56 | 1.234,56 EUR |
"N2" | 数字 | 1,234.57 | 1.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 / SSR | RequestLocalizationMiddleware(服务器端) |
| WebAssembly | CultureInfo.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 常见问题
- 不要在 Blazor 中使用
IHtmlLocalizer或IViewLocalizer。 这些是仅适用于 MVC 的功能。在 Blazor 组件中使用IStringLocalizer<T>。 - 不要在服务端代码中依赖
CultureInfo.CurrentCulture的线程默认值。 始终向格式化方法显式传入CultureInfo。服务端线程区域性可能与请求区域性不一致。 - 不要硬编码复数形式。 英语的“单数/复数”规则不适用于阿拉伯语(有 6 种形式)、波兰语或其他语言。使用 MessageFormat.NET 或 SmartFormat.NET 实现正确的 CLDR 复数规则。
- 不要在 .NET 8 及更高版本的 WPF 中使用 LocBaml。 LocBaml 是仅适用于 .NET Framework 的示例工具。现代 WPF 应使用
.resx文件或资源字典。 - 不要忘记为 Blazor WASM 设置
BlazorWebAssemblyLoadAllGlobalizationData。 如果未设置,只会加载部分 ICU 数据,导致许多区域性的日期/数字格式不正确。 - 不要添加默认
.resx文件中不存在的翻译键。 默认资源是唯一可信来源;卫星程序集必须是其子集。 - 不要在 AOT/裁剪应用中直接使用
ResourceManager。 它依赖反射。使用源生成器(ResXGenerator)在编译时访问资源。 - 不要忘记 MAUI 的特定平台设置。 iOS/Mac Catalyst 需要在
Info.plist中设置CFBundleLocalizations;Windows 需要Resource Language条目。