一、简单实现篇
1、安装ResXManager
下载地址:ResXManager - Visual Studio Marketplace。安装ResXManager会自动配置VS。
ResXManager是一款开源的可视化工具,.resx资源文件的专属编辑器,适用于多语言场景,便捷、好用。
2、创建resx资源文件
右键项目--属性--资源--创建或打开程序集资源,创建resx资源文件,并设置访问级别为Public。这种方式会默认在Properties目录下创建资源文件。

或者创建Resources文件夹,右击添加--类--资源文件,创建资源文件。
3、添加多语言
点击工具--ResX Manager,打开ResXManager工具,完善语言内容。

在ResXManager工具中,可以任意添加新语言,可以通过excel文件导入翻译资源文件,Money足够的的话也可以采用AI进行自动翻译,官网注册账号并添加key即可。

4、创建语言管理类
这里搭配使用.Net框架里的System.Globalization.CultureInfo类,CultureInfo理解为指定程序语言和地区的语言身份证。
语言管理类继承INotifyPropertyChanged接口,作用是通知界面 "数据变了,赶紧刷新",切换语言后,界面上绑定的文本会自动更新,不用像之前那样重新调用InitializeComponent()。
cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Resources;
using System.Text;
namespace Test0319
{
public class LanguageManager : INotifyPropertyChanged
{
private readonly ResourceManager _resourceManager;
// 懒加载
private static readonly Lazy<LanguageManager> _lazy = new Lazy<LanguageManager>(() => new LanguageManager());
public static LanguageManager Instance => _lazy.Value;
public event PropertyChangedEventHandler PropertyChanged;
public LanguageManager()
{
//获取此命名空间下Resources的Resources资源
_resourceManager = new ResourceManager("Test0319.Resources.Resources", typeof(LanguageManager).Assembly);
}
// 索引器的写法,传入字符串的下标
public string this[string name]
{
get
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
return _resourceManager.GetString(name);
}
}
public void ChangeLanguage(CultureInfo cultureInfo)
{
CultureInfo.CurrentCulture = cultureInfo;
CultureInfo.CurrentUICulture = cultureInfo;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("item[]")); //字符串集合,对应资源的值
}
public void ChangeLanguage(string cultureCode)
{
CultureInfo cultureInfo = new CultureInfo(cultureCode);
ChangeLanguage(cultureInfo);
}
}
}
CultureInfo的CurrentCulture属性指定区域文化,决定数字、日期、货币等格式,比如中文显示 "2026 年 3 月 19 日",法语显示 "19 mars 2026"。
CultureInfo的CurrentUICulture属性指定UI文化,决定程序加载哪个语言的资源文件(.resx),比如 zh-CN 加载Resources.zh-CN.resx中文资源,ja-JP 加载Resources.ja-JP.resx日语资源。
这里采用Lazy + Instance懒加载单例模式。第一次用到Instance时才创建实例,在整个程序运行期间,LanguageManager 只创建一个实例,避免了重复创建浪费资源。在项目任何地方通过LanguageManager.Instance调用这个类的方法 / 属性,不用 new 对象。
PropertyChanged是INotifyPropertyChanged接口要求的事件,用来触发界面刷新。
_resourceManager.GetString(name)根据当前的CurrentUICulture,自动读取对应语言.resx 文件里 Key 为name的文本(比如当前是ja-JP,就读 Resources.ja-JP.resx 里的 BtnHello)。
PropertyChanged?.Invoke(...)触发属性变更通知,"item[]"是索引器的默认属性名,触发通知后,所有绑定了LanguageManager索引器的界面元素(比如按钮文本)自动重新读取最新语言文本,不用重新加载窗口,界面会自动刷新。
5、资源文件引用
在XAML文件中添加相关的UI控件,与资源文件中的资源名称进行绑定,将显示资源文本。
cs
<Button Grid.Row="1" Grid.Column="0" Width="100" Height="50" Content="{Binding [HelloBtn],Source={x:Static local:LanguageManager.Instance}}"/>
<Button Grid.Row="1" Grid.Column="1" Width="100" Height="50" Content="{Binding [SZBtn],Source={x:Static local:LanguageManager.Instance}}"/>
<Button Grid.Row="1" Grid.Column="2" Width="100" Height="50" Content="{Binding [WorldBtn],Source={x:Static local:LanguageManager.Instance}}"/>
6、语言切换功能实现
这里添加了一个combox控件,用于下拉选中语言,通过Tag保存语言code。
cs
<StackPanel Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">
<ComboBox x:Name="CmbLanguage"
Width="100"
SelectionChanged="CmbLanguage_SelectionChanged"
Margin="0 0 0 20">
<ComboBoxItem Tag="zh-CN">中文简体</ComboBoxItem>
<ComboBoxItem Tag="ja-JP">日本語</ComboBoxItem>
<ComboBoxItem Tag="fr-FR">Français</ComboBoxItem>
<ComboBoxItem Tag="en-US">English</ComboBoxItem>
</ComboBox>
</StackPanel>
同步完成C#文件中CmbLanguage_SelectionChanged事件的逻辑:
cs
private void CmbLanguage_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (CmbLanguage.SelectedItem is ComboBoxItem selectedItem)
{
// 获取Tag里的语言代码(zh-CN/ja-JP/fr-FR/en-US)
string cultureCode = selectedItem.Tag.ToString();
LanguageManager.Instance.ChangeLanguage(cultureCode);
}
}
到这里,一个简单的语言切换程序可以运行啦。
二、代码优化篇
1、初次运行语言本地化
初次运行时,app语言跟随系统语言,这里定义默认的语言为"zh-CN"及相关方法,在构造函数中进行调用,这样在初次实例化时将进行语言本地化。
cs
private const string _defaultCulture = "zh-CN";
public LanguageManager()
{
_resourceManager = new ResourceManager("Test0319.Resources.Resources", typeof(LanguageManager).Assembly);
LoadSystemLanguage();
}
private void LoadSystemLanguage()
{
CultureInfo systemCulture = null;
try
{
systemCulture = CultureInfo.CurrentUICulture;
}
catch
{
systemCulture = new CultureInfo(_defaultCulture);
}
ChangeLanguage(systemCulture);
}
2、保存用户语言设置
这里通过应用程序的Properties.Settings属性设置实现。这项设置是.NET内置的本地配置存储机制,适合保存少量简单配置,会在本地用户数据目录或者程序安装目录生成一个XML配置文件,属于本地持久化存储。
2.1、创建Settings
右键项目--属性--设置--创建或打开应用程序设置,这样在解决方案窗口中Properties目录下将可以看到出现一个Settings.settings文件。

2.2、添加设置项
双击打开Settings.settings,然后添加对应的键值用于保存语言选择信息。

2.3、修改CultureInfo相关代码
代码修改如下,首先定义了一个支持的语言列表,在初始加载语言时先读取用户settings,每次用户操作Culture之后保存语言到Properties.Settings。
cs
private readonly List<string> _supportedCultures = new List<string> { "zh-CN", "ja-JP", "fr-FR", "en-US" };
private const string _defaultCulture = "zh-CN";
public void ChangeLanguage(CultureInfo cultureInfo)
{
if (cultureInfo == null) throw new ArgumentNullException(nameof(cultureInfo));
var targetCulture = _supportedCultures.Contains(cultureInfo.Name)
? cultureInfo
: new CultureInfo(_defaultCulture);
CultureInfo.CurrentCulture = targetCulture;
CultureInfo.CurrentUICulture = targetCulture;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("item[]")); //字符串集合,对应资源的值
Properties.Settings.Default.UserSelectedCulture = targetCulture.Name;
Properties.Settings.Default.Save();
}
private void LoadSystemLanguage()
{
// 优先读取用户保存的语言
string _savedCultureCode = Properties.Settings.Default.UserSelectedCulture;
CultureInfo systemCulture = null;
if (!string.IsNullOrEmpty(_savedCultureCode) && _supportedCultures.Contains(_savedCultureCode))
{
systemCulture = new CultureInfo(_savedCultureCode);
}
else
{
try
{
systemCulture = CultureInfo.CurrentUICulture;
}
catch
{
systemCulture = new CultureInfo(_defaultCulture);
}
}
ChangeLanguage(systemCulture);
}
2.4、关于Settings
Settings中的数据有两个范围,一个是User用户,一个是Application应用。前者普通用户可读写无需管理员,保存在用户数据目录,后者默认只读,需要管理员才能写,保存程序安装目录。前者对应的保存路径为:
C:\Users\[Windows用户名]\AppData\Local\[程序公司名]\[程序名].exe_Url_[随机字符]\[程序版本]\user.config
程序运行时,Settings.Default 会把所有配置加载到内存中 ,读写操作默认只操作内存,只有调用 Save() 时才会写入磁盘。
3、Combox控件配置
当前的下拉框Combox写法适用少量数据,如果语言选项比较多时,这种方法显得繁冗,新增或者删除语言选项时候需修改XAML,维护成本及出错概率高,拓展性差,无法批量导入。考虑采用结构化数据+动态加载的方式。
创建 LanguageItem 类管理 "代码 + 名称",在窗口初始化时动态生成 ComboBoxItem,XAML 只保留基础控件。
3.1、定义语言信息类
cs
namespace Test0319
{
// 语言信息模型(存储代码和显示名称)
public class LanguageItem
{
// 语言代码(如 zh-CN、ja-JP)
public string CultureCode { get; set; }
// 显示名称(如 中文简体、日本語)
public string DisplayName { get; set; }
}
}
3.2、动态加载下拉框选项
修改 MainWindow.xaml.cs,在窗口初始化时动态生成下拉框选项。
cs
// 所有语言选项的数据源(集中管理,新增/删除只需改这里)
private readonly List<LanguageItem> _languageList = new List<LanguageItem>()
{
new LanguageItem { CultureCode = "zh-CN", DisplayName = "中文简体" },
new LanguageItem { CultureCode = "ja-JP", DisplayName = "日本語" },
new LanguageItem { CultureCode = "fr-FR", DisplayName = "Français" },
new LanguageItem { CultureCode = "en-US", DisplayName = "English" },
};
public MainWindow()
{
InitializeComponent();
InitLanguageComboBox();
}
private void InitLanguageComboBox()
{
// 清空默认项
LangCombox.Items.Clear();
// 遍历语言列表,动态创建ComboBoxItem
foreach (var lang in _languageList)
{
ComboBoxItem item = new ComboBoxItem
{
// Tag存语言代码(和原有逻辑一致)
Tag = lang.CultureCode,
// Content存显示名称
Content = lang.DisplayName
};
// 添加到下拉框
LangCombox.Items.Add(item);
}
//初始化选中项逻辑
string currentCultureCode = System.Globalization.CultureInfo.CurrentUICulture.Name;
foreach (ComboBoxItem item in LangCombox.Items)
{
if (item.Tag.ToString() == currentCultureCode)
{
LangCombox.SelectedItem = item;
break;
}
}
if (LangCombox.SelectedItem == null)
{
LangCombox.SelectedIndex = 0;
}
}
3.4、简化 XAML
XAML 只需保留下拉框的基础属性,不需要定义 ComboBoxItem。
cs
<StackPanel Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">
<ComboBox x:Name="LangCombox"
Width="100"
SelectionChanged="LangCombox_SelectionChanged"
Margin="0 0 0 20">
</ComboBox>
</StackPanel>
4、采用MVVM模型
M,Model数据模型,V,View界面视图,VM,ViewModel通过定义的数据Model处理View相关的界面逻辑。这三者分工明确,形成的模型不再需要后台代码操作前端View控件。在上面的代码中,在MainWindow.xaml.cs文件中进行Combox组件的定义和初始化,一旦控件增多,传统的模型需要大量的后台代码来完成。这里采用MVVM模型进行优化。
在上面的代码中,已经定义了M数据模型和V界面视图。
4.1、创建ViewModel
创建LanguageCmxViewModel类,用于控制MainWindow.xaml界面中的Combox控件。
cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Test0319.Model;
using Test0319.Services;
namespace Test0319.ViewModel
{
// 主窗口的ViewModel(所有数据逻辑都在这里)
public class LanguageCmxViewModel : NotifyPropertyChangedBase
{
// 1. 语言列表(供下拉框绑定)
private List<LanguageItem> _languageList;
public List<LanguageItem> LanguageList
{
get => _languageList;
set
{
_languageList = value;
OnPropertyChanged(); // 通知UI列表更新
}
}
// 2. 选中的语言代码(双向绑定:UI选了新值,这里会同步;这里改值,UI也会同步)
private string _selectedCultureCode;
public string SelectedCultureCode
{
get => _selectedCultureCode;
set
{
if (_selectedCultureCode != value)
{
_selectedCultureCode = value;
OnPropertyChanged();
LanguageManager.Instance.ChangeLanguage(value);
}
}
}
// 构造函数:初始化语言列表和默认选中项
public LanguageCmxViewModel()
{
// 初始化语言列表(硬编码/从文件加载都可以)
LanguageList = new List<LanguageItem>(){
new LanguageItem { CultureCode = "zh-CN", DisplayName = "中文简体" },
new LanguageItem { CultureCode = "ja-JP", DisplayName = "日本語" },
new LanguageItem { CultureCode = "fr-FR", DisplayName = "Français" },
new LanguageItem { CultureCode = "en-US", DisplayName = "English" }
};
// 初始化选中项:优先读Settings,再赋值给SelectedCultureCode
string savedCultureCode = Properties.Settings.Default.UserSelectedCulture;
_selectedCultureCode = string.IsNullOrEmpty(savedCultureCode)
? System.Globalization.CultureInfo.CurrentUICulture.Name
: savedCultureCode;
}
}
}
4.2、修改View
修改MainWindow.xaml文件,将LanguageCmxViewModel作为窗口的上下文以供引用,修改Combox完成数据绑定。
cs
<Window.DataContext>
<vm:LanguageCmxViewModel />
</Window.DataContext>
<StackPanel Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">
<ComboBox x:Name="LangCombox"
Width="100"
Margin="0 0 0 20"
ItemsSource="{Binding LanguageList}"
DisplayMemberPath="DisplayName"
SelectedValuePath="CultureCode"
SelectedValue="{Binding SelectedCultureCode, Mode=TwoWay}">
</ComboBox>
</StackPanel>
4.3、删除后台代码
删除MainWindow.xaml.cs中有关Combox的定义和初始化。
cs
namespace Test0319
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
到这里,MVVM模型修改完成。
参考资料:
2、WPF本地化全流程解析与完整应用示例_wpflocalizeextension-CSDN博客
3、WPF本地化/国际化,多语言切换_wpf 多语言-CSDN博客
4、WPF国际化 - ResXManager 多语言切换-CSDN博客
5、LanguageDemo/README.md at master · Justin1107-good/LanguageDemo · GitHub
有时候做成一些事情,理解一些事情,或者达到某些目的,就是需要一些时间,时间到了,自然就会了。这一点十分适用于我,不管是学习技术,驾驶,舞蹈,都需要时间停下来,放空,消化,循序渐进,最终的实现过程就很快。