WPF基于resx资源文件的多语言实现

一、简单实现篇

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模型修改完成。

参考资料:

1、WPF利用Resx的多语言支持 - 代码先锋网

2、WPF本地化全流程解析与完整应用示例_wpflocalizeextension-CSDN博客

3、WPF本地化/国际化,多语言切换_wpf 多语言-CSDN博客

4、WPF国际化 - ResXManager 多语言切换-CSDN博客

5、LanguageDemo/README.md at master · Justin1107-good/LanguageDemo · GitHub

有时候做成一些事情,理解一些事情,或者达到某些目的,就是需要一些时间,时间到了,自然就会了。这一点十分适用于我,不管是学习技术,驾驶,舞蹈,都需要时间停下来,放空,消化,循序渐进,最终的实现过程就很快。

相关推荐
量子物理学2 小时前
WPF 标签预览可以显示图片运行后不显示
c#·wpf
△曉風殘月〆2 小时前
WPF Prism中的依赖注入详解
wpf·mvvm
△曉風殘月〆2 小时前
WPF Prism创建Bootstrapper/启动器
wpf·mvvm
小曹要微笑3 小时前
WPF的依赖与附加属性
wpf·依赖属性·附加属性
chushiyunen3 小时前
BM25稀疏检索算法笔记
笔记·算法·c#
玩泥巴的12 小时前
存储那么贵,何不白嫖飞书云文件空间
c#·.net·二次开发·飞书
脑电信号要分类1 天前
将多张图片拼接成一个pdf文件输出
pdf·c#·apache
njsgcs1 天前
c# solidworks 折弯系数检查
开发语言·c#
格林威1 天前
工业相机图像采集:Grab Timeout 设置建议——拒绝“假死”与“丢帧”的黄金法则
开发语言·人工智能·数码相机·计算机视觉·c#·机器视觉·工业相机