[WPF多语言/本地化]通过.resx资源文件实现

语言:C# 

IDE:Microsoft Visual Studio Community 2022 

框架:WPF,.net 8.0

插件:ResXManager,CommunityToolkit.Mvvm


目录

 一、说明

1.1 xaml文件中的静态内容

1.2 后台文件中的内容

1.3 系统控件中的内容

二、 准备工作

2.1 Demo准备

2.2 插件准备

2.3 添加资源文件

2.4 添加语言

2.5 添加语言切换助手

2.6 完善语言切换方法

三、 xaml文件静态内容

3.1 xaml添加引用

3.2 在Resx Manager添加新资源

3.3 修改TextBlock属性

3.4 替换所有静态内容

3.5 添加语言内容

3.5.1 翻译功能

3.5.2 导出功能

3.6 验证

 3.7 代码

四、后台文件中的内容

4.1 添加语言资源

4.2 验证

4.3 代码

五、系统控件中的内容

5.1 切换系统语言

5.2  自定义控件


 一、说明

本文是自己在探索WPF多语言时的总结,主要有以下三种方式:

1.使用locBaml,此方式比较繁琐。

2.使用资源字典文件,此方式相对简单。

3.使用.resx资源文件,采用ResXManager插件可以很便捷的进行开发。

插件安装方式查看

[Visual Stuidio 2022使用技巧]1.插件及风格-CSDN博客

本地化需要处理的情况,个人总结有3种: 

1.1 xaml文件中的静态内容

如TextBlock的Text,Button的Content等

<TextBlock Text="用户名"/>
<Button Content="登录"/>

1.2 后台文件中的内容

.cs文件中的内容,如:

[Required(ErrorMessage ="用户名不能为空")]
string name;

xaml文件中的绑定内容也归入此类 

1.3 系统控件中的内容

如MessageBox的确定按钮

二、 准备工作

2.1 Demo准备

先准备一个只含中文的Demo:

界面:

功能:

1.需要输入三个字段,用户名,手机号,年龄

2.每个字段有一个验证器,验证失败的时候界面会提示错误信息,点击登录提示登录失败

3.全部验证成功后点击登录提示登录成功

4.点击中文后页面语言切换成中文,点击英文后页面语言切换成英文

说明:通过CommunityToolkit.Mvvm插件的ObservableValidator实现,参看:

[CommunityToolkit.Mvvm个人总结]3.Validator-CSDN博客

ViewModel:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.ComponentModel.DataAnnotations;
using System.Windows;

namespace LocalizationDemo.ViewModels
{
    public partial class MainViewModel : ObservableValidator
    {
        [ObservableProperty]
        [Required(ErrorMessage ="用户名不能为空")]
        [NotifyDataErrorInfo]
        [NotifyPropertyChangedFor(nameof(ErrorMessages))]
        string name;

        [ObservableProperty]
        [Required(ErrorMessage = "手机号不能为空")]
        [NotifyDataErrorInfo]
        [NotifyPropertyChangedFor(nameof(ErrorMessages))]
        string phone;

        [ObservableProperty]
        [Range(18, 150,ErrorMessage ="年龄范围18-150")]
        [NotifyDataErrorInfo]
        [NotifyPropertyChangedFor(nameof(ErrorMessages))]
        int age;

        public string ErrorMessages
        {
            get
            {
                return UpdateErrorMessage();
            }
        }

        string UpdateErrorMessage()
        {
            if (HasErrors)
            {
                return string.Join(Environment.NewLine, GetErrors());
            }
            else
            {
                return string.Empty;
            }
        }

        [RelayCommand]
        void LoginClick()
        {
            ValidateAllProperties();
            if (HasErrors)
            {
                MessageBox.Show("注册失败");
            }
            else
            {
                MessageBox.Show("注册成功");
            }
        }

        [RelayCommand]
        void ToChinese()
        {

        }

        [RelayCommand]
        void ToEnglish()
        {

        }

        public MainViewModel()
        {
            ValidateAllProperties();
        }
    }
}

View:

<Window x:Class="LocalizationDemo.Views.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:vm="clr-namespace:LocalizationDemo.ViewModels"
        Width="600"
        SizeToContent="Height"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>
    <Window.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Margin" Value="5" />
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="Margin" Value="5" />
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style TargetType="Button">
            <Setter Property="Margin" Value="5" />
        </Style>
    </Window.Resources>
    <Grid>
        <StackPanel Margin="10" TextElement.FontSize="32">
            <TextBlock Text="用户名" />
            <TextBox Text="{Binding Name, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="手机号" />
            <TextBox Text="{Binding Phone, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="年龄" />
            <TextBox Text="{Binding Age, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
            <Separator Width="500" />
            <TextBlock Text="{Binding ErrorMessages}" Foreground="Red" />
            <Button Content="登录" Command="{Binding LoginClickCommand}" />
            <Button Content="中文" Command="{Binding ToChineseCommand}" />
            <Button Content="英文" Command="{Binding ToEnglishCommand}" />
        </StackPanel>
    </Grid>
</Window>

2.2 插件准备

安装Resx Manager插件,安装成功后,点击工具-Resx Manager,配置默认语言

2.3 添加资源文件

Properties-右键-添加-新建项

搜索资源文件,添加Lang.resx的资源文件

使用Resx Manager打开资源文件,有两种方式:

1.在资源文件的右键菜单中点击Resx Manager

2.工具-Resx Manager 的左侧树形栏中选择资源文件

2.4 添加语言

添加en-US语言

2.5 添加语言切换助手

using System;
using System.ComponentModel;
using System.Globalization;
using System.Resources;

namespace LocalizationDemo
{
    public class LanguageManager : INotifyPropertyChanged
    {
        /// <summary>
        /// 资源
        /// </summary>
        private readonly ResourceManager resourceManager;

        /// <summary>
        /// 懒加载
        /// </summary>
        private static readonly Lazy<LanguageManager> _lazy = new(() => new LanguageManager());
        public static LanguageManager Instance => _lazy.Value;
        public event PropertyChangedEventHandler PropertyChanged;

        public LanguageManager()
        {
            //获取此命名空间下Resources的Lang的资源,Lang可以修改
            resourceManager = new ResourceManager("LocalizationDemo.Properties.Lang", typeof(LanguageManager).Assembly);
        }

        /// <summary>
        /// 索引器的写法,传入字符串的下标
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public string this[string name]
        {
            get
            {
                ArgumentNullException.ThrowIfNull(name);
                return resourceManager.GetString(name);
            }
        }

        public void ChangeLanguage(CultureInfo cultureInfo)
        {
            CultureInfo.CurrentCulture = cultureInfo;
            CultureInfo.CurrentUICulture = cultureInfo;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(""));
        }
    }
}

2.6 完善语言切换方法

[RelayCommand]
void ToChinese()
{
    LanguageManager.Instance.ChangeLanguage(new System.Globalization.CultureInfo("zh-CN"));
}

[RelayCommand]
void ToEnglish()
{
    LanguageManager.Instance.ChangeLanguage(new System.Globalization.CultureInfo("en-US"));
}

三、 xaml文件静态内容

例如:对以下内容进行本地化

<TextBlock Text="用户名" />

3.1 xaml添加引用

xmlns:app="clr-namespace:LocalizationDemo"

3.2 在Resx Manager添加新资源

名称为MainView_UserName,此名称自己定义,考虑到整个App的资源都在此文件中,不能太过简单,否则容易重名。

先添加中文内容:用户名,英文内容先不管,后面统一处理

3.3 修改TextBlock属性

<TextBlock Text="{Binding [MainView_UserName], Source={x:Static app:LanguageManager.Instance}}" />

此处是通过索引器实现的,首先在类LanguageManager中实现了一个索引器,可通过name访问到资源内容

public string this[string name]
{
    get
    {
        ArgumentNullException.ThrowIfNull(name);
        return resourceManager.GetString(name);
    }
}

其次在xmal中,Binding的[]表达式可以访问集合或字典的特定元素(包括索引器)

比如访问集合元素:

<TextBlock Text="{Binding MyList[0]}" />

运行后,可发现界面上的用户名已正确显示,说明已经绑定成功

3.4 替换所有静态内容

重复3.2-3.3替换所有静态内容,资源文件内容为:

View:

<StackPanel Margin="10" TextElement.FontSize="32">
    <TextBlock Text="{Binding [MainView_UserName], Source={x:Static app:LanguageManager.Instance}}" />
    <TextBox Text="{Binding Name, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
    <TextBlock Text="{Binding [MainView_Phone], Source={x:Static app:LanguageManager.Instance}}" />
    <TextBox Text="{Binding Phone, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
    <TextBlock Text="{Binding [MainView_Age], Source={x:Static app:LanguageManager.Instance}}" />
    <TextBox Text="{Binding Age, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
    <Separator Width="500" />
    <TextBlock Text="{Binding ErrorMessages}" Foreground="Red" />
    <Button Content="{Binding [MainView_Login], Source={x:Static app:LanguageManager.Instance}}" Command="{Binding LoginClickCommand}" />
    <Button Content="{Binding [MainView_Chinese], Source={x:Static app:LanguageManager.Instance}}" Command="{Binding ToChineseCommand}" />
    <Button Content="{Binding [MainView_English], Source={x:Static app:LanguageManager.Instance}}" Command="{Binding ToEnglishCommand}" />
</StackPanel>

3.5 添加语言内容

这里进行英文内容的补充,有两种方法

3.5.1 翻译功能

打开ResxManager控件,点击翻译-目标-勾选语言

选择需要翻译的字段,点击开始

等待翻译结束后,校对一遍,因为自动翻译的内容可能有错误,确认后点击应用所有

再返回主要页面,可以看到所有资源都已经翻译完毕

3.5.2 导出功能

点击导出全部,保存为excel文件

 打开excel文件,编辑好英文内容后,再使用导入功能,也可以完成翻译工作

3.6 验证

运行程序,点击中文和英文,可以看到静态内容已经完成了本地化功能

 3.7 代码

此时的ViewModel: 

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.ComponentModel.DataAnnotations;
using System.Windows;

namespace LocalizationDemo.ViewModels
{
    public partial class MainViewModel : ObservableValidator
    {
        [ObservableProperty]
        [Required(ErrorMessage ="用户名不能为空")]
        [NotifyDataErrorInfo]
        [NotifyPropertyChangedFor(nameof(ErrorMessages))]
        string name;

        [ObservableProperty]
        [Required(ErrorMessage = "手机号不能为空")]
        [NotifyDataErrorInfo]
        [NotifyPropertyChangedFor(nameof(ErrorMessages))]
        string phone;

        [ObservableProperty]
        [Range(18, 150,ErrorMessage ="年龄范围18-150")]
        [NotifyDataErrorInfo]
        [NotifyPropertyChangedFor(nameof(ErrorMessages))]
        int age;

        public string ErrorMessages
        {
            get
            {
                return UpdateErrorMessage();
            }
        }

        string UpdateErrorMessage()
        {
            if (HasErrors)
            {
                return string.Join(Environment.NewLine, GetErrors());
            }
            else
            {
                return string.Empty;
            }
        }

        [RelayCommand]
        void LoginClick()
        {
            ValidateAllProperties();
            if (HasErrors)
            {
                MessageBox.Show("注册失败");

            }
            else
            {
                MessageBox.Show("注册成功");
            }
        }

        [RelayCommand]
        void ToChinese()
        {
            LanguageManager.Instance.ChangeLanguage(new System.Globalization.CultureInfo("zh-CN"));
        }

        [RelayCommand]
        void ToEnglish()
        {
            LanguageManager.Instance.ChangeLanguage(new System.Globalization.CultureInfo("en-US"));
        }

        public MainViewModel()
        {
            ValidateAllProperties();
        }
    }
}

View: 

<Window x:Class="LocalizationDemo.Views.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:vm="clr-namespace:LocalizationDemo.ViewModels"
        xmlns:app="clr-namespace:LocalizationDemo"
        Width="600"
        SizeToContent="Height"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>
    <Window.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Margin" Value="5" />
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="Margin" Value="5" />
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style TargetType="Button">
            <Setter Property="Margin" Value="5" />
        </Style>
    </Window.Resources>
    <Grid>
        <StackPanel Margin="10" TextElement.FontSize="32">
            <TextBlock Text="{Binding [MainView_UserName], Source={x:Static app:LanguageManager.Instance}}" />
            <TextBox Text="{Binding Name, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="{Binding [MainView_Phone], Source={x:Static app:LanguageManager.Instance}}" />
            <TextBox Text="{Binding Phone, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="{Binding [MainView_Age], Source={x:Static app:LanguageManager.Instance}}" />
            <TextBox Text="{Binding Age, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
            <Separator Width="500" />
            <TextBlock Text="{Binding ErrorMessages}" Foreground="Red" />
            <Button Content="{Binding [MainView_Login], Source={x:Static app:LanguageManager.Instance}}" Command="{Binding LoginClickCommand}" />
            <Button Content="{Binding [MainView_Chinese], Source={x:Static app:LanguageManager.Instance}}" Command="{Binding ToChineseCommand}" />
            <Button Content="{Binding [MainView_English], Source={x:Static app:LanguageManager.Instance}}" Command="{Binding ToEnglishCommand}" />
        </StackPanel>
    </Grid>
</Window>

四、后台文件中的内容

例如对以下内容进行本地化

[Required(ErrorMessage ="用户名不能为空")]
string name;

4.1 添加语言资源

选择整个字段:ErrorMessage = "用户名不能为空"

在右键菜单中点击移动到资源

弹出以下窗口

资源:存储的资源文件名称

名称:存储的资源字段名称

代码:自动生成的引用代码,可以根据实际场景选择

值:资源内容(即中文内容)

注释:自己添加

勾选在Resx Resource Manager中编辑新项目

点击OK后,将新建一个名称为MainViewModel_UserName_Required的资源,中文内容为:用户名不能为空,并且选中的代码:

ErrorMessage = "用户名不能为空"

也被替换成了:

ErrorMessageResourceType = typeof(Lang), ErrorMessageResourceName = nameof(Lang.MainViewModel_UserName_Required)

按照3.5添加英文内容后,运行程序,可以看到已经实现了后台文件的本地化:

重复此步骤,将ViewModel文件中的其他中文字符串也进行替换。

替换完成后按照3.5的说明添加其英文内容

此时已基本完成了本地化工作,前台的ErrorMessages在语言切换时并不会更新,但是在TextBox输入变化时会正常更新,修改OnChinese和OnEnglish方法:

[RelayCommand]
void ToChinese()
{
    LanguageManager.Instance.ChangeLanguage(new System.Globalization.CultureInfo("zh-CN"));
    ValidateAllProperties();
    OnPropertyChanged(nameof(ErrorMessages));
}

[RelayCommand]
void ToEnglish()
{
    LanguageManager.Instance.ChangeLanguage(new System.Globalization.CultureInfo("en-US"));
    ValidateAllProperties();
    OnPropertyChanged(nameof(ErrorMessages));
}

4.2 验证

4.3 代码

ViewModel:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LocalizationDemo.Properties;
using System;
using System.ComponentModel.DataAnnotations;
using System.Windows;

namespace LocalizationDemo.ViewModels
{
    public partial class MainViewModel : ObservableValidator
    {
        [ObservableProperty]
        [NotifyDataErrorInfo]
        [NotifyPropertyChangedFor(nameof(ErrorMessages))]
        [Required(ErrorMessageResourceType = typeof(Lang), ErrorMessageResourceName = nameof(Lang.MainViewModel_UserName_Required))]
        string name;

        [ObservableProperty]
        [Required(ErrorMessageResourceType = typeof(Lang), ErrorMessageResourceName = nameof(Lang.MainViewModel_Phone_Required))]
        [NotifyDataErrorInfo]
        [NotifyPropertyChangedFor(nameof(ErrorMessages))]
        string phone;

        [ObservableProperty]
        [Range(18, 150,ErrorMessageResourceType = typeof(Lang), ErrorMessageResourceName = nameof(Lang.MainViewModel_Age_Range))]
        [NotifyDataErrorInfo]
        [NotifyPropertyChangedFor(nameof(ErrorMessages))]
        int age;

        public string ErrorMessages
        {
            get
            {
                return UpdateErrorMessage();
            }
        }

        string UpdateErrorMessage()
        {
            if (HasErrors)
            {
                return string.Join(Environment.NewLine, GetErrors());
            }
            else
            {
                return string.Empty;
            }
        }

        [RelayCommand]
        void LoginClick()
        {
            ValidateAllProperties();
            if (HasErrors)
            {
                MessageBox.Show(Lang.LoginClick_Failed);

            }
            else
            {
                MessageBox.Show(Lang.LoginClick_Success);
            }
        }

        [RelayCommand]
        void ToChinese()
        {
            LanguageManager.Instance.ChangeLanguage(new System.Globalization.CultureInfo("zh-CN"));
            ValidateAllProperties();
            OnPropertyChanged(nameof(ErrorMessages));
        }

        [RelayCommand]
        void ToEnglish()
        {
            LanguageManager.Instance.ChangeLanguage(new System.Globalization.CultureInfo("en-US"));
            ValidateAllProperties();
            OnPropertyChanged(nameof(ErrorMessages));
        }

        public MainViewModel()
        {
            ValidateAllProperties();
        }
    }
}

View:

<Window x:Class="LocalizationDemo.Views.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:vm="clr-namespace:LocalizationDemo.ViewModels"
        xmlns:app="clr-namespace:LocalizationDemo"
        Width="600"
        SizeToContent="Height"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>
    <Window.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Margin" Value="5" />
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="Margin" Value="5" />
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style TargetType="Button">
            <Setter Property="Margin" Value="5" />
        </Style>
    </Window.Resources>
    <Grid>
        <StackPanel Margin="10" TextElement.FontSize="32">
            <TextBlock Text="{Binding [MainView_UserName], Source={x:Static app:LanguageManager.Instance}}" />
            <TextBox Text="{Binding Name, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="{Binding [MainView_Phone], Source={x:Static app:LanguageManager.Instance}}" />
            <TextBox Text="{Binding Phone, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="{Binding [MainView_Age], Source={x:Static app:LanguageManager.Instance}}" />
            <TextBox Text="{Binding Age, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
            <Separator Width="500" />
            <TextBlock Text="{Binding ErrorMessages}" Foreground="Red" />
            <Button Content="{Binding [MainView_Login], Source={x:Static app:LanguageManager.Instance}}" Command="{Binding LoginClickCommand}" />
            <Button Content="{Binding [MainView_Chinese], Source={x:Static app:LanguageManager.Instance}}" Command="{Binding ToChineseCommand}" />
            <Button Content="{Binding [MainView_English], Source={x:Static app:LanguageManager.Instance}}" Command="{Binding ToEnglishCommand}" />
        </StackPanel>
    </Grid>
</Window>

五、系统控件中的内容

像MessageBox,OpenFileDialog,SaveFileDialog的本地化有所不同,这些都是调用系统的Win32 API。个人总结有以下几种方法:

5.1 切换系统语言

无需修改代码,完美支持,缺点是使用系统语言使用中文时,即时APP已经切换到不同语言,但是系统控件中的文本还是中文,不会切换。

方法:开始菜单-语言设置

5.2  自定义控件

通过Win32的API接口,修改MessageBox,使得其支持多语言。暂未深入研究,参考以下链接:

MessageBox 按钮显示英文或其他语言_message box怎么设置英文-CSDN博客

Localizing System MessageBox - CodeProject

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值