WinForms/WPF应用的多语言化 ── resx、附属程序集与区域文化切换的实务

· · CSharp, .NET, WinForms, WPF, 多语言化, 本地化, 资源, UI, Windows 开发, 技术咨询

“希望海外据点也能使用同一款应用”“工厂现场外语员工增多,希望画面也能显示英文”──Windows桌面应用的多语言化,作为需求本身很简单,但一旦真正着手,就会接连遇到“resx该怎么用”“WPF有好几种方案,不知道哪个才对”“运行中能否切换语言”等难以判断的问题。

本文以WinForms/WPF业务应用为对象,按照实务中最容易犹豫不决的顺序,整理.NET本地化的基础(区域文化与资源回退)、resx与附属程序集的机制、各框架的现实方案、运行时切换,以及字符串以外(格式、排版、RTL)的要点。

1. 先说结论(判断表)

多语言化的方案由框架和需求的组合决定。

情况 推荐方案 理由
WinForms,以画面上的标签/按钮为主 窗体的 Localizable = true + 按语言划分的resx(设计器方式) 可以按语言分别调整控件的字符串以及尺寸、位置,仅用Visual Studio即可完成
WinForms/WPF通用,消息或共享文案较多 共享resx(Resources.resx + Resources.ja.resx 等)+ 自动生成的强类型类 可以从代码中以类型安全的方式引用字符串,画面和逻辑层可以共享同一份文案
WPF 共享resx + x:Static 绑定 LocBaml方式在.NET(Core)的WPF上无法使用1。采用resx方案可以与WinForms共享知识体系
必须支持运行中切换语言 resx + 变更通知机制(或重新生成画面) 在选定方案之前,先确认“是否真的需要无需重启的切换”这一需求本身(第6章)
将翻译外包给第三方 以resx为基础进行设计,通过键与译文的对照表交付 resx是XML格式,本身即可直接交付,但附上上下文信息(是哪个画面的哪个按钮)会直接影响交付质量

先说结论。

  • CurrentCulture(格式)与 CurrentUICulture(资源语言)是两回事。 这一区分是整个多语言化设计的基础(第2章)。
  • 资源的实体是“中性语言资源 + 各语言的附属程序集”,回退是自动进行的。 系统会按照 ja-JPja → 中性语言的顺序查找,因此即使翻译有缺失,应用也不会崩溃,而是显示默认语言的内容2
  • WinForms的实务解法是设计器方式(Localizable = true)与共享resx方式并用。 画面部分采用设计器方式,消息框和通用文案则集中到共享resx中(第4章)。
  • WPF官方演练中提到的LocBaml方式,在.NET(Core)中无法使用1。目前实务上的首选方案是resx + x:Static(或数据绑定)方式(第5章)。
  • 运行时语言切换应以“下次启动时生效”为第一方案。 因为当前显示中的画面资源不会自动替换(第6章)。
  • 字符串翻译只是多语言化工作的一半。 剩下的一半是翻译后字符串长度导致的排版适配、日期与数值的格式、字体,以及从右到左书写的语言(RTL)的支持(第7章)。

2. 基础 ── CurrentCulture 与 CurrentUICulture 的区别

.NET的区域文化(culture)设置中,有两个职责不同的属性3

属性 决定的内容 示例
CultureInfo.CurrentCulture 日期、数值、货币的格式 显示为 2026/07/07 还是 7/7/2026
CultureInfo.CurrentUICulture 加载哪种语言的资源 按钮上显示“保存”还是“Save”

这两者可以独立设置。例如“画面用英文显示,但日期和货币仍按日本格式”这种组合(在国内有外语员工使用的业务应用中很常见的需求),可以通过 CurrentUICulture = enCurrentCulture = ja-JP 自然地实现。

两者的默认值都来自操作系统的设置,因此“什么都不做就跟随操作系统”是出发点。如果要在应用整体范围内明确切换语言,不应逐个线程设置,而应替换默认值本身。

using System.Globalization;

// 替换此后创建的所有线程的默认区域文化
// (需要在UI线程创建之前 = Application.Run之前执行)
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en"); // 资源语言
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("ja-JP"); // 格式

Thread.CurrentThread.CurrentUICulture 赋值只对该线程本身有效。在使用async/await或后台处理、会跨越多个线程的业务应用中,通过 DefaultThreadCurrentUICulture / DefaultThreadCurrentCulture 连同默认值一并更改,才不容易出问题3

另外,日志、文件输出、系统间对接的数据格式不应跟随用户的区域文化变化,而应固定为 CultureInfo.InvariantCulture。关于日期格式设计的完整内容,请参考「业务应用的日期时间与时区设计」。

3. 资源机制 ── resx、附属程序集与回退

.NET的本地化采用的是“中心辐射(hub-and-spoke)”模型。默认语言(中性语言)的资源被嵌入到主程序集中,各语言版本的资源则被分离到名为附属程序集的独立DLL中4

编译后,按语言划分的resx会生成如下的输出结构。

MyApp.exe                     ← 主体(包含中性语言资源)
MyApp.dll
ja/MyApp.resources.dll        ← 日语附属程序集
en/MyApp.resources.dll        ← 英语附属程序集

运行时,资源管理器会以 CurrentUICulture 为起点,按照特定区域文化(ja-JP)→中性区域文化(ja)→默认(主体)的顺序查找资源2。得益于这种自动回退机制,即使缺少一处翻译,应用也不会崩溃,而是显示默认语言的字符串。反过来说,“明明应该是英文画面,却混入了部分日文”正是翻译遗漏的信号,可以作为测试的关注点(第8章)。

实务中需要把握以下两点。

  • 通过 NeutralResourcesLanguage 特性声明默认语言对应的区域文化,这样在默认区域文化下就不会白白去查找附属程序集,回退的终点也会更加明确4
  • 附属程序集只有“与主体一起分发”才能生效,因此需要在安装程序或复制分发的方案中包含语言文件夹(第8章)。

4. WinForms实务 ── 设计器方式与共享resx方式并用

WinForms有两种方式,实务中通常两者并用。

4.1 画面部分采用设计器方式(Localizable = true)

在窗体的属性中将 Localizable 设为 true,将 Language 属性切换为目标语言(例如英语)后再编辑标签、按钮的 Text,Visual Studio就会自动生成类似 Form1.en.resx 这样的语言专属resx5

这种方式的优势在于,不仅是字符串,控件的尺寸和位置也会按语言分别保存。日语的“登录”翻译成英语的“Register”后按钮容纳不下的问题,可以在设计器上按语言分别调整。另一方面,语言数×窗体数会导致resx数量增多,每次改动窗体布局都需要确认所有语言版本,由此产生维护成本。如果预计语言数量会持续增加,建议优先考虑后面第7章介绍的排版层面的对策,构建“无需调整也不会错乱”的画面结构。

4.2 消息、通用文案采用共享resx方式

消息框文案、确认对话框、状态栏显示等从代码中使用的字符串,应集中到项目共享的 Resources.resx 中,并按语言追加 Resources.en.resx 等文件。resx会自动生成强类型类,因此从代码中引用时不会出现键名拼写错误。

// 通过自动生成的强类型类进行引用。
// 返回的字符串会根据CurrentUICulture自动切换
MessageBox.Show(
    Properties.Resources.ConfirmDeleteMessage,
    Properties.Resources.ConfirmTitle,
    MessageBoxButtons.YesNo);

这里最重要的原则是,不要把展示给用户看的字符串硬编码在代码里。多语言化的工作量,往往不是花在引入机制上,而是花在“回收分散各处的硬编码字符串”上。即便目前只运营单一语言,提前采用resx方式也是值得的。

5. WPF实务 ── 首选resx而非LocBaml

WPF的多语言化是信息容易混乱的领域。官方文档的演练中介绍了在项目文件中设置 UICulture 生成BAML资源、再用LocBaml工具插入译文的方式,但LocBaml只是面向.NET Framework版WPF的示例工具,在.NET(Core)的WPF上无法运行1。请不要将其视为当前可选的新建方案。

实务上的首选方案,是像WinForms一样从XAML中引用共享resx。

<!-- 将resx的访问修饰符设为Public后,通过x:Static进行引用 -->
<Window xmlns:res="clr-namespace:MyApp.Properties"
        Title="{x:Static res:Resources.MainWindowTitle}">
    <Button Content="{x:Static res:Resources.SaveButton}" />
</Window>

这种方式的优点在于,资源机制(附属程序集、回退)与WinForms完全通用,即使在混合解决方案中也能统一管理文案。x:Static 是在启动时解析的静态引用,如果需要运行时切换,则需要进一步扩展为数据绑定+变更通知(第6章)。

在XAML一侧,同时设置根元素的 xml:lang,可以让连字符处理、字体回退、数字显示等WPF中依赖语言的功能正常工作1

关于究竟应该用WinForms还是WPF开发的判断本身,请参考「WinForms / WPF / WinUI的使用场景判断表」。

6. 运行时语言切换 ── 首选“下次启动时生效”

在设置画面中让用户选择语言的需求中,生效时机有两种方案可选。

  • 方案A:下次启动时生效(推荐)。 将选定的语言保存到配置文件中,在启动时 Application.Run 之前(WPF则是 Application 启动之前)设置 DefaultThreadCurrentUICulture。实现简单,不会出现因重建画面而导致的问题(例如正在输入的数据丢失、事件处理程序重复注册等),从结构上就不会发生。
  • 方案B:立即生效。 即使更改了 CurrentUICulture已经显示的画面字符串也不会自动替换。若采用WinForms设计器方式,需要用 ComponentResourceManager.ApplyResources 对已打开的窗体重新应用资源;若是WPF,则需要通过数据绑定+变更通知来提供字符串,无论哪种方式,都需要以“所有画面均支持资源重新应用”为前提进行构建。

在业务应用中,切换语言的频率(通常只在导入时发生一次)往往与方案B的实现和测试成本不成正比,因此建议默认采用方案A,只有在确实存在该需求时才实现方案B。语言设置属于按用户区分的设置,保存位置放在 %LOCALAPPDATA% 下比较合适。关于配置保存位置的判断,请参考「Windows业务应用的配置管理实务」中的判断表。

7. 字符串以外的多语言化 ── 排版、格式、字体、RTL

即使翻译已经完成,多语言化仍有一半的工作尚未完成。

  • 排版的伸缩。 即使含义相同,不同语言下文案的长度也会有很大差异(日语“保存”→德语“Speichern”)。固定坐标、固定尺寸的布局每次翻译都会被破坏,因此在WinForms中,官方推荐的做法是结合 TableLayoutPanelAutoSize,让布局能够随字符串长度自动调整6。WPF的布局系统本身就是可变的,只要避免写死宽度,大部分问题都能解决。同时建议与高DPI环境下的错位对策(「WinForms高DPI适配指南」「WPF高DPI适配指南」)一并设计,可以减少返工。
  • 日期、数值、货币的格式。 画面显示应交给 CurrentCulture 处理,尽量减少代码中 ToString("yyyy/MM/dd") 这类写死格式的写法。反过来,文件、日志、对接数据则应固定使用 InvariantCulture(第2章)。
  • 字体。 需要确认目标语言的文字是否包含在字体中。追加中文(简体/繁体)或韩语时,如果沿用面向日语的字体设置,字形可能会显得不自然(即所谓的CJK字形问题)。如果把UI字体的设置集中到一处管理,追加语言时的改动会更轻松。
  • 从右到左书写的语言(RTL)。 支持阿拉伯语、希伯来语时,除了翻译字符串之外,还需要将画面左右翻转。WinForms通过 RightToLeft / RightToLeftLayout 属性控制,WPF则通过 FlowDirection 属性控制。RTL支持如果“事后再加”,就需要重新审视所有画面,因此只要目标语言中可能包含RTL语言,就应该从一开始就纳入需求。
  • 图片、图标中的文字。 嵌入图片中的文字不会随资源机制切换。应避免使用带文字的图片,采用“图片+文本标签”的组合会更安全。

关于UI设计整体的判断,也请参考「Windows应用的UX设计判断表」。

8. 运维 ── 翻译交付、遗漏检测与分发

最后是机制搭建完成之后的运维层面内容。

  • 交付翻译时附上上下文信息。 如果只把resx的键和原文交给译者,“确定”“打开”这类短文案的翻译质量很难稳定。现实的做法是通过对照表(如电子表格)交流键、原文、译文,并附上“显示在哪个画面、用途是什么”的信息,收到译文后再反映到resx中。
  • 通过回退机制发现翻译遗漏。 由于资源回退机制的存在,翻译遗漏会表现为“该语言的画面中混入了默认语言”2。在CI中加入一个简单的检查(脚本或测试代码),将各语言resx的键列表与中性语言resx进行比对,就能在发布前以机械化的方式检测出遗漏。
  • 分发时务必包含附属程序集。 如果没有将 ja/ en/ 等语言文件夹一并分发,该语言就会整体回退,出现“明明翻译了却没有生效”的情况。手动复制时漏掉语言文件夹是常见的事故,因此应将整个目录结构一起纳入安装程序(MSI/MSIX等)。在ClickOnce分发中,默认会将所有语言的附属程序集都包含在部署中7。关于分发方式整体的判断,请参考「Windows应用分发方式的选择」。

结语

桌面应用的多语言化建立在resx与附属程序集这套成熟机制之上,机制本身并不难。真正决定实务成败的是区分CurrentCultureCurrentUICulture的设计、不将面向用户的字符串硬编码的纪律、能够承受翻译后字符串变长的排版,以及“是否真的需要即时切换”这一需求判断。在WPF中,不要被旧有的LocBaml方式相关信息所左右,以resx为基础构建才是当前的实务解法。

既有应用的多语言化方案选型与影响范围评估,以及面向海外据点推广时画面、报表、对接数据的多语言支持,往往需要结合代码库的现状进行判断,如有疑问欢迎咨询我们。

相关文章

参考资料

  1. Microsoft Learn, WPF Globalization and Localization Overview. 介绍了通过项目文件的UICulture设置生成附属程序集、根元素xml:lang的作用,以及LocBaml是仅面向.NET Framework版WPF的示例工具、在.NET的WPF上无法运行这一事实。  2 3 4

  2. Microsoft Learn, Retrieve resources in .NET apps. 介绍了资源回退流程(按特定区域文化→中性区域文化→主程序集默认资源的顺序查找)。  2 3

  3. Microsoft Learn, CultureInfo.CurrentUICulture Property. 介绍了CurrentUICulture如何被资源管理器用于特定区域文化资源的查找、其默认值来自操作系统的用户界面语言,以及通过DefaultThreadCurrentUICulture设置所有线程默认值的方法。  2

  4. Microsoft Learn, Resources in .NET apps. 介绍了resx资源的创建与打包、由主程序集与附属程序集构成的中心辐射模型,以及NeutralResourcesLanguage特性。  2

  5. Microsoft Learn, Walkthrough: Localizing a Hybrid Application. 介绍了在Windows窗体设计器中将Localizable属性设为true并切换Language属性以生成按语言划分的resx(例如Form1.es-ES.resx)的步骤,以及在调用InitializeComponent之前设置CurrentUICulture的示例。 

  6. Microsoft Learn, How to: Design a Windows Forms Layout that Responds Well to Localization. 介绍了使用TableLayoutPanel构建能够随本地化导致的Text属性字符串长度变化而自动调整控件尺寸的布局方法。 

  7. Microsoft Learn, Localize ClickOnce applications. 介绍了将所有附属程序集包含在同一个部署中是Visual Studio的默认方式,以及运行时会根据操作系统的区域文化使用相应附属程序集这一机制。 

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

常见问题

汇总了咨询这一主题时常见的问题。

只用日语开发的既有应用,能不能事后再进行多语言化?
可以,但工作的大头并不是“引入资源机制”,而是“找出并替换代码中硬编码的字符串”。不仅是画面上的文字,消息框、日志、异常信息、报表、保存在数据库中用于显示的字符串都需要盘点,工作量的大小取决于字符串的分散程度,而不是画面数量。如果从新开发阶段起就把面向用户的字符串统一通过resx管理,未来多语言化的成本会大幅降低。
翻译可以直接用机器翻译解决吗?
如果是内部工具或临时应对,从机器翻译入手是现实的做法。但业务应用中有大量按钮、菜单上的短词(例如“登录”“取消”),缺乏上下文时机器翻译的误译率会上升。至少建议安排一轮由实际使用该语言的人来试用画面的评审。而且比翻译质量更重要的是,先通过TableLayoutPanel或AutoSize确保“翻译后变长的字符串不会破坏排版”。
日志和错误消息也需要翻译吗?
实务上的通行做法是:展示给用户看的消息需要翻译,而写入日志文件或事件日志的内容不翻译(固定使用某一种语言)。日志是给开发者和运维人员看的,不是给用户看的,多语言混杂的日志会破坏排查问题时的可检索性。日志中的格式(日期、数值)也应固定使用CultureInfo.InvariantCulture。只要在设计上把CurrentCulture和CurrentUICulture分开处理,这种区分自然就能实现。
除了操作系统的显示语言之外,是否应该让应用本身也能切换语言?
对于要向海外据点推广,或国内有外语员工使用的业务应用来说,让应用拥有独立的语言设置是很有价值的。稳妥的做法是分两层:默认跟随操作系统的显示语言(即CurrentUICulture的默认值),只有在设置画面中明确选择时才覆盖默认值。将切换生效的时机定为“下次启动时”是最安全的方案,实现和运维说明都会更简单。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表