【WPF】 本地化的最佳做法

news2024/7/4 5:17:09

【WPF】 本地化的最佳做法

  • 资源文件
    • 英文资源文件 en-US.xaml
    • 中文资源文件 zh-CN.xaml
  • 资源使用
    • App.xaml
    • 主界面布局
    • cs代码
  • App.config
  • 辅助类
    • 语言切换操作类
    • 资源 binding 解析类
  • 实现效果

  应用程序本地化有很多种方式,选择合适的才是最好的。这里只讨论一种方式,动态资源(DynamicResource) 这种方式可是在不重启应用程序的情况下进行资源的切换,不论是语言切换,还是更上层的主题切换。想要运行时切换不同的资源就必须使用 动态资源(DynamicResource) 这种方式。
图片是可以使用资源字典进行动态 binding 的 不要被误导了

资源文件

确保不同语言环境中资源 Key 是同一个,且对用的资源类型是相同的。
在资源比较多的情况下,可以通过格式化的命名方式来规避 Key 冲突。

资源文件的属性可以设置成:
生成操作:内容
复制到输出目录:如果较新则复制

上面的操作可以在不重新编译程序的情况下编辑语言资源并应用。

如果不想让别人更改语言资源则
生成操作:
复制到输出目录:不复制

英文资源文件 en-US.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib">

    <sys:String x:Key="WindowsTitle">MainWindow</sys:String>
    <ImageSource x:Key="icon">pack://SiteOfOrigin:,,,/Icons/en-USicon.png</ImageSource>
    <sys:String x:Key="LanguageButton">LanguageButton</sys:String>

    <sys:String x:Key="LockTime-OneMinute">1 Minute</sys:String>
    <sys:String x:Key="LockTime-FiveMinute">5 Minute</sys:String>
    <sys:String x:Key="LockTime-TenMinute">10 Minute</sys:String>
    <sys:String x:Key="LockTime-FifteenMinute">15 Minute</sys:String>
    <sys:String x:Key="LockTime-ThirtyMinute">30 Minute</sys:String>
    <sys:String x:Key="LockTime-OneHour">1 Hour</sys:String>
    <sys:String x:Key="LockTime-TwoHour">2 Hour</sys:String>
    <sys:String x:Key="LockTime-ThreeHour">3 Hour</sys:String>
    <sys:String x:Key="LockTime-Never">Never</sys:String>
</ResourceDictionary>

中文资源文件 zh-CN.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib">

    <sys:String x:Key="WindowsTitle">主窗口</sys:String>
    <ImageSource x:Key="icon">pack://SiteOfOrigin:,,,/Icons/zh-CNicon.png</ImageSource>
    <sys:String x:Key="LanguageButton">语言按钮</sys:String>

    <sys:String x:Key="LockTime-OneMinute">1 分钟</sys:String>
    <sys:String x:Key="LockTime-FiveMinute">5 分钟</sys:String>
    <sys:String x:Key="LockTime-TenMinute">10 分钟</sys:String>
    <sys:String x:Key="LockTime-FifteenMinute">15 分钟</sys:String>
    <sys:String x:Key="LockTime-ThirtyMinute">30 分钟</sys:String>
    <sys:String x:Key="LockTime-OneHour">1 小时</sys:String>
    <sys:String x:Key="LockTime-TwoHour">2 小时</sys:String>
    <sys:String x:Key="LockTime-ThreeHour">3 小时</sys:String>
    <sys:String x:Key="LockTime-Never">永不</sys:String>
</ResourceDictionary>

资源使用

App.xaml

设置默认语言资源,用于在设计阶段预览,并利用 VS 智能提示资源的 Key 防止 Key编写出错。

<Application
    x:Class="Localization.Core.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Localization.Core"
    StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/I18nResources/en-US.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

主界面布局

<Window
    x:Class="Localization.Core.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:Localization.Core"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:operate="clr-namespace:Localization.Core.Operates"
    Title="{DynamicResource WindowsTitle}"
    Width="800"
    Height="450"
    Icon="{DynamicResource icon}"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <ComboBox
            x:Name="comLan"
            Width="{Binding SizeToContent.Width}"
            Height="{Binding SizeToContent.Width}"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            SelectedValuePath="Tag"
            SelectionChanged="ComboBox_SelectionChanged">
            <ComboBoxItem Content="中文" Tag="zh-CN" />
            <ComboBoxItem Content="English" Tag="en-US" />
        </ComboBox>

        <StackPanel Grid.Row="1">

            <Button
                Margin="0,50,0,0"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Content="{DynamicResource LanguageButton}" />

            <ComboBox
                x:Name="cmTime"
                Width="120"
                Margin="20"
                VerticalAlignment="Center">
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{operate:ResourceBinding Key}" />
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>
        </StackPanel>

    </Grid>
</Window>

cs代码

public partial class MainWindow : Window
{
    ObservableCollection<KeyValuePair<string, int>>? TimeList { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        var lan = Thread.CurrentThread.CurrentCulture.Name;
        comLan.SelectedValue = lan;

        Loaded += MainWindow_Loaded;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        TimeList = new ObservableCollection<KeyValuePair<string, int>>()
        {
            new KeyValuePair<string, int>("LockTime-OneMinute", 1),
            new KeyValuePair<string, int>("LockTime-FiveMinute", 5),
            new KeyValuePair<string, int>("LockTime-TenMinute", 10),
            new KeyValuePair<string, int>("LockTime-FifteenMinute", 15),
            new KeyValuePair<string, int>("LockTime-ThirtyMinute", 30),
            new KeyValuePair<string, int>("LockTime-OneHour", 60),
            new KeyValuePair<string, int>("LockTime-TwoHour", 120),
            new KeyValuePair<string, int>("LockTime-ThreeHour", 180),
            new KeyValuePair<string, int>("LockTime-Never", 0),
        };

        cmTime.ItemsSource = TimeList;
        cmTime.SelectedValue = TimeList[0];
    }

    private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems is null) return;

        LanguageOperate.SetLanguage((e.AddedItems[0] as ComboBoxItem)!.Tag.ToString()!);
    }
}

App.config

language 配置项用于保存用户选择的语言类型

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<appSettings>
		<add key="language" value=""/>
	</appSettings>
</configuration>

辅助类

语言切换操作类

检查方法入参,有值则切换到指定的资源,无值则读取配置文件中的值,若配置文件中仍无值,则获取当前线程运行的语言环境,切换到与语言环境相匹配的资源,如果没有找到与之匹配的资源则不做操作。

切换资源之后,将配置文件中的 language 的 value 改为当前所选的语言保存并刷新配置文件,直到下次更改。

重写 Application 类的 OnStartup 方法,在 OnStartup 中调用 SetLanguage 方法以实现应用程序启动对语言环境的判别。

/// <summary>
/// 语言操作
/// </summary>
public class LanguageOperate
{
    private const string KEY_OF_LANGUAGE = "language";

    /// <summary>
    /// 语言本地化
    /// </summary>
    /// <param name="language">语言环境</param>
    public static void SetLanguage(string language = "")
    {
        if (string.IsNullOrWhiteSpace(language))
        {
            language = ConfigurationManager.AppSettings[KEY_OF_LANGUAGE]!;

            if (string.IsNullOrWhiteSpace(language))
                language = System.Globalization.CultureInfo.CurrentCulture.ToString();
        }

        string languagePath = $@"I18nResources\{language}.xaml";

        if (!System.IO.File.Exists(languagePath)) return;

        var lanRd = Application.LoadComponent(new Uri(languagePath, UriKind.RelativeOrAbsolute)) as ResourceDictionary;
        var old = Application.Current.Resources.MergedDictionaries.FirstOrDefault(o => o.Contains("WindowsTitle"));

        if (old != null)
            Application.Current.Resources.MergedDictionaries.Remove(old);

        Application.Current.Resources.MergedDictionaries.Add(lanRd);

        Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
        configuration.AppSettings.Settings[KEY_OF_LANGUAGE].Value = language;
        configuration.Save();
        ConfigurationManager.RefreshSection("AppSettings");

        var culture = new System.Globalization.CultureInfo(language);
        System.Globalization.CultureInfo.CurrentCulture = culture;
        System.Globalization.CultureInfo.CurrentUICulture = culture;
    }
}

资源 binding 解析类

ComBox 控件通常是通过 ItemSource 进行绑定,默认情况下是无法对绑定的资源进行翻译的。
通过继承 MarkupExtension 类 重写 ProvideValue 方法来实现,item 绑定 资源的 Key 的解析。

/// <summary>
/// markup extension to allow binding to resourceKey in general case
/// </summary>
public class ResourceBinding : MarkupExtension
{
    #region properties

    private static DependencyObject resourceBindingKey;
    public static DependencyObject ResourceBindingKey
    {
        get => resourceBindingKey;
        set => resourceBindingKey = value;
    }

    // Using a DependencyProperty as the backing store for ResourceBindingKeyHelper.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ResourceBindingKeyHelperProperty =
        DependencyProperty.RegisterAttached(nameof(ResourceBindingKey),
            typeof(object),
            typeof(ResourceBinding),
            new PropertyMetadata(null, ResourceKeyChanged));

    static void ResourceKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is FrameworkElement target) || !(e.NewValue is Tuple<object, DependencyProperty> newVal)) return;

        var dp = newVal.Item2;

        if (newVal.Item1 == null)
        {
            target.SetValue(dp, dp.GetMetadata(target).DefaultValue);
            return;
        }

        target.SetResourceReference(dp, newVal.Item1);
    }
    #endregion

    public ResourceBinding() { }

    public ResourceBinding(string path) => Path = new PropertyPath(path);

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if ((IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)) == null) return null;

        if (((IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget))).TargetObject != null && ((IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget))).TargetObject.GetType().FullName is "System.Windows.SharedDp") return this;


        if (!(((IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget))).TargetObject is FrameworkElement targetObject) || !(((IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget))).TargetProperty is DependencyProperty targetProperty)) return null;

        #region binding
        Binding binding = new Binding
        {
            Path = Path,
            XPath = XPath,
            Mode = Mode,
            UpdateSourceTrigger = UpdateSourceTrigger,
            Converter = Converter,
            ConverterParameter = ConverterParameter,
            ConverterCulture = ConverterCulture,
            FallbackValue = FallbackValue
        };

        if (RelativeSource != null)
            binding.RelativeSource = RelativeSource;

        if (ElementName != null)
            binding.ElementName = ElementName;

        if (Source != null)
            binding.Source = Source;
        #endregion

        var multiBinding = new MultiBinding
        {
            Converter = HelperConverter.Current,
            ConverterParameter = targetProperty
        };

        multiBinding.Bindings.Add(binding);
        multiBinding.NotifyOnSourceUpdated = true;
        targetObject.SetBinding(ResourceBindingKeyHelperProperty, multiBinding);

        return null;
    }

    #region Binding Members
    /// <summary>
    /// The source path (for CLR bindings).
    /// </summary>
    public object Source { get; set; }
    /// <summary>
    /// The source path (for CLR bindings).
    /// </summary>
    public PropertyPath Path { get; set; }
    /// <summary>
    /// The XPath path (for XML bindings).
    /// </summary>
    [DefaultValue(null)]
    public string XPath { get; set; }
    /// <summary>
    /// Binding mode
    /// </summary>
    [DefaultValue(BindingMode.Default)]
    public BindingMode Mode { get; set; }
    /// <summary>
    /// Update type
    /// </summary>
    [DefaultValue(UpdateSourceTrigger.Default)]
    public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
    /// <summary>
    /// The Converter to apply
    /// </summary>
    [DefaultValue(null)]
    public IValueConverter Converter { get; set; }
    /// <summary>
    /// The parameter to pass to converter.
    /// </summary>
    /// <value></value>
    [DefaultValue(null)]
    public object ConverterParameter { get; set; }
    /// <summary>
    /// Culture in which to evaluate the converter
    /// </summary>
    [DefaultValue(null)]
    [TypeConverter(typeof(System.Windows.CultureInfoIetfLanguageTagConverter))]
    public CultureInfo ConverterCulture { get; set; }
    /// <summary>
    /// Description of the object to use as the source, relative to the target element.
    /// </summary>
    [DefaultValue(null)]
    public RelativeSource RelativeSource { get; set; }
    /// <summary>
    /// Name of the element to use as the source
    /// </summary>
    [DefaultValue(null)]
    public string ElementName { get; set; }
    #endregion

    #region BindingBase Members
    /// <summary>
    /// Value to use when source cannot provide a value
    /// </summary>
    /// <remarks>
    /// Initialized to DependencyProperty.UnsetValue; if FallbackValue is not set, BindingExpression
    /// will return target property's default when Binding cannot get a real value.
    /// </remarks>
    public object FallbackValue { get; set; }
    #endregion

    #region Nested types
    private class HelperConverter : IMultiValueConverter
    {
        public static readonly HelperConverter Current = new HelperConverter();

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            return Tuple.Create(values[0], (DependencyProperty)parameter);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    #endregion
}

实现效果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/894296.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信公众平台发布小程序流程

最近因为部署小程序&#xff0c;学习了下如何部署小程序 1. 取消不检验合法域名并上传小程序 建议在小程序上传之前&#xff0c;先取消不校验合法域名并真机调试下。 2. 登录微信公众平台 登录微信公众平台 3. 设置服务器域名 在开放->开发管理->开发设置找到服务器…

Minio知识点+linux下安装+面试总结

一 Minio简介 MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象文件可以是任意大小&…

Word设置只读后,为什么还能编辑?

Word文档设置了只读模式&#xff0c;是可以编辑的&#xff0c;但是当我们进行保存的时候就会发现&#xff0c;word提示需要重命名并选择新路径才能够保存。 这种操作&#xff0c;即使可以编辑文字&#xff0c;但是原文件是不会受到影响的&#xff0c;编辑之后的word文件会保存到…

【Unity小技巧】Unity探究自制对象池和官方内置对象池(ObjectPool)的使用

文章目录 前言不使用对象池使用官方内置对象池应用 自制对象池总结源码参考完结 前言 对象池&#xff08;Object Pool&#xff09;是一种软件设计模式&#xff0c;用于管理和重用已创建的对象。在对象池中&#xff0c;一组预先创建的对象被维护在一个池中&#xff0c;并在需要时…

七夕节送礼物清单,总有一款他/她会喜欢!

马上就要到一年一度的七夕节了&#xff0c;你想好送给对方什么礼物了吗&#xff1f;送礼不一定是贵的好&#xff0c;但一定要表达出自己心意&#xff0c;也有人说&#xff0c;七夕不适合单身狗&#xff0c;其实是错的&#xff0c;单身狗正好可以趁七夕这个浪漫的节日&#xff0…

B站发布财报,正式会员数达2.14亿

KlipC报道&#xff1a;B站公布了截至2023年6月30日的未经审计的财务报告&#xff0c;据数据显示&#xff0c;B站总营收达同比增长8%达53.04亿元人民币&#xff0c;毛利润同比增长66%&#xff0c;其中广告业务收入同比增长36%达16亿人民币。财报发布后&#xff0c;B站美股盘前一…

深度云化时代,什么样的云网络才是企业的“心头好”?

科技云报道原创。 近年来企业上云的快速推进&#xff0c;对云网络提出了更多需求。 最初&#xff0c;云网络只是满足互联网业务公网接入。 随着移动互联网的发展&#xff0c;企业对云上网络安全隔离能力和互访能力、企业数据中心与云上网络互联、构建混合云的能力&#xff0…

骨传导耳机游泳能戴吗?骨传导游泳耳机哪个牌子好?

溽热的夏日&#xff0c;如果能够跳入水中畅游一番&#xff0c;那真的是再好不过了&#xff0c;既能强身健体&#xff0c;又能降温解暑。公共的游泳场馆人声鼎沸&#xff0c;像我这种“社恐”患者&#xff0c;如果在场馆中要待好几个小时&#xff0c;难免会觉得时间漫长&#xf…

韩国半导体巨头库存飙升,存储器市场面临挑战 | 百能云芯

最新财务报告揭示&#xff0c;韩国两大半导体巨头三星和SK海力士面临巨大的库存压力。截至今年6月底&#xff0c;两家公司的半导体库存金额已经飙升至超过50兆韩元&#xff0c;创下历史新高。这不仅显示了存储器市场库存过剩的严峻形势&#xff0c;也暗示着产业复苏步伐不容乐观…

Linux系统调试——valgrind内存泄露检测

代码可能存在内存泄露怎么办&#xff1f; 使用valgrind可以对代码进行内存泄露检测。 valgrind下载安装 下载&#xff1a;https://www.valgrind.org/downloads/ 安装&#xff1a; 1、tar –jxvf valgrind-3.21.0.tar.bz2 2、cd valgrind-3.21.0 3、./configure --prefix/ho…

GBU814-ASEMI逆变器专用整流桥GBU814

编辑&#xff1a;ll GBU814-ASEMI逆变器专用整流桥GBU814 型号&#xff1a;GBU814 品牌&#xff1a;ASEMI 芯片个数&#xff1a;1 封装&#xff1a;GBU-4 恢复时间&#xff1a;&#xff1e;50ns 工作温度&#xff1a;-55C~150C 浪涌电流&#xff1a;200A 正向电流&…

华为AR路由器配置双出口静态IP双链路负载

适用于&#xff1a;有多个以太WAN口的机型。 业务需求&#xff1a; 电信运营商1分配的接口pppoe拨号上网获取地址为116.24.65.19/32。 联通运营商2分配的接口pppoe拨号上网获取地址为10.59.7.238/32。 实现通过pppoe拨号获取动态IP双上行接入Internet&#xff0c;链路1和链…

[USACO1.5] 八皇后 Checker Challenge

题目描述 一个如下的 6 x 6 的跳棋棋盘&#xff0c;有六个棋子被放置在棋盘上&#xff0c;使得每行、每列有且只有一个&#xff0c;每条对角线&#xff08;包括两条主对角线的所有平行线&#xff09;上至多有一个棋子。 上面的布局可以用序列 2 4 6 1 3 5 来描述&#xff0c;第…

由于找不到msvcp120.dll怎么解决?哪个解决方案操作简单

我最近我的电脑遇到了一个问题&#xff0c;运行某些软件的时候出现了一个msvcp120.dll的错误提示。这个错误导致我无法正常运行一些程序&#xff0c;非常影响我的工作和娱乐体验。经过一番搜索和尝试&#xff0c;我最终成功修复了这个问题&#xff0c;现在我把修复方法分享给大…

【学习笔记之vue】使用Nginx运行vue

下载http://nginx.org/en/download.html 下一个稳定版本 加入环境变量 加入path中 在nginx解压路径下输入cmd 或者双击 之后erro显示&#xff1a;80端口冲突 下面我们将80默认启动端口改成90 双击nginx.exe&#xff0c;在浏览器中输入地址http://localhost:90/&#…

物联网设备的分类和功能阐述

物联网是将各种物理设备和传感器通过互联网进行连接和交互的网络&#xff0c;物联网的核心思想是让各种设备能够通过互联网实现智能化、自动化和远程控制。物联网设备是指连接到物联网中的各种设备&#xff0c;可以通过传感器、处理器、通信模块等技术实现与互联网的连接和数据…

中英双语对话大语言模型:ChatGLM-6B

介绍 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费级的显卡上进行本地部署&#xff08;INT4 量化级别下最低只需 6GB 显存&#xff09;。…

iOS16.0:屏幕旋转

此文写于2022年08月03日&#xff0c;距离iOS16正式版推出还有一个多月的时间&#xff0c;iOS16 beta版本有很多API的修改&#xff0c;今天讨论的是屏幕旋转&#xff0c;基于Xcode 14.0 beta4。 之前的屏幕旋转会报错&#xff1a; [Orientation] BUG IN CLIENT OF UIKIT: Settin…

计蒜客T1170——人民币支付

超级水&#xff0c;不解释&#xff0c;代码的处理方式减低了繁琐程度&#xff0c; #include <iostream> using namespace std;int main(int argc, char** argv) {int num0;cin>>num;int money[6]{100,50,20,10,5,1};for(int i0;i<5;i){int count0;countnum/mone…

⛳ Java - IO

目录 ⛳ Java - IO&#x1f3a8; 一、概述&#x1f463; 二、File类2.1、File 对象的构造函数&#xff1a;2.2、分隔符2.3、File类的获取功能2.4、File 类的重命名功能2.5、File类的判断功能2.6、File类的创建功能2.7、File类的删除功能2.8、功能练习代码 &#x1f69c; 三、IO…