WPF 本地化的最佳做法

news2024/11/23 21:25:36

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/877089.html

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

相关文章

Sencha Ext.NET Crack,构建Blazing快速应用

Sencha Ext.NET Crack,构建Blazing快速应用 Sencha Ext.NET是一个高级的ASP.NET核心组件框架&#xff0c;它包含了强大的跨浏览器Sencha Ext JS库。通过140多个预构建和专业测试的UI组件实现企业级性能和生产效率。Sencha Ext.NET使用尖端的Web技术创建功能强大的Web应用程序&a…

【Spring专题】Bean的生命周期流程图

目录 前言阅读指引 流程图一、之前推测的简单流程图&#xff08;一点点参考&#xff09;*二、Bean生命周期流程图&#xff08;根据Spring源码自结&#xff09;*三、阶段源码流程图&#xff08;不断更新&#xff09; 前言 我向来不主张【通过源码】理解业务&#xff0c;因为每个…

Ubuntu18.04 GitHub提交代码

一、准备工作 1.1安装 git sudo apt-get install git安装完成后&#xff0c;检查git版本&#xff0c;一般自带2.17.1版本 git --version 1.2 注册github SSH keys 1.2.1 github 进入注册github SSH keys 如图&#xff0c;到GitHub上右上角圆形图标进入Settings 左侧点击S…

Vue.js快速入门指南:零基础也能轻松上手,开启前端开发之旅!

目录 MVC设计模式与MVVM设计模式选项式API的编程风格与优势声明式渲染及响应式数据实现原理指令系统与事件方法及传参处理计算属性与侦听器区别与原理条件渲染与列表渲染及注意点class样式与style样式的三种形态表单处理与双向数据绑定原理生命周期钩子函数及原理分析 MVC设计模…

从零实战SLAM-第六课(视觉里程计I)

在七月算法报的班&#xff0c;老师讲的蛮好。好记性不如烂笔头&#xff0c;关键内容还是记录一下吧&#xff0c;课程入口&#xff0c;感兴趣的同学可以学习一下。 --------------------------------------------------------------------------------------------------------…

解决echarts和v-show一起使用canvas宽高改变

本来是想没有数据显示暂无数据的&#xff0c;结果显示成了这样 1.把V-show改成v-if <template><divclass"chart1"ref"chart1"v-if"!nodata"style"width: 100%; height: 100%"></div><el-empty description&quo…

生信豆芽菜-单基因与基因集的关系

网址&#xff1a;http://www.sxdyc.com/panCancerGeneSet 该工具主要用于查看单基因在泛癌中与各个不同基因集的相关性 提交后等待运行成功即可&#xff0c;还可以关注公众号&#xff1a;豆芽数据分析

LeetCode150道面试经典题-- 存在重复元素 II(简单)

1.题目 给你一个整数数组 nums 和一个整数 k &#xff0c;判断数组中是否存在两个 不同的索引 i 和 j &#xff0c;满足 nums[i] nums[j] 且 abs(i - j) < k 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 2.示例 示例 1&#xff1a; 输…

mybatis-plus 根据指定字段 批量 删除/修改

mybatis-plus 提供了根据id批量更新和修改的方法,这个大家都不陌生 但是当表没有id的时候怎么办 方案一: 手写SQL方案二: 手动获取SqlSessionTemplate 就是把mybatis plus 干的事自己干了方案三 : 重写 executeBatch 方法结论: mybatis-plus 提供了根据id批量更新和修改的方法,…

创建百度百科需要什么条件?

随着互联网的发展&#xff0c;人们越来越依赖于搜索引擎获取信息。百度作为中国最大的搜索引擎之一&#xff0c;旗下的百科词条已成为人们获取知识的重要来源。创建百度百科需要什么条件呢&#xff1f;接下来伯乐网络传媒就来给大家讲一讲。 首先&#xff0c;你需要有一个百度…

最小生成树(Kruskal)克鲁斯卡尔算法

算法步骤总共分为两步&#xff0c;由并查集实现 第一步&#xff08;把所有的边按边长的大小进行排序&#xff09; 第二步&#xff08;如果两个点不连通就把两点之间的边加上再把两个点连通&#xff09; 当放入的边数为点数减去一时就代表已经全部连通 例题一&#xff08;859. …

五、Dubbo 启停原理解析

五、Dubbo 启停原理解析 5.1 配置解析 5.1.1 基于 schema 设计解析 Dubbo 配置约束文件在 dubbo-config/dubbo-config-spring/src/main/resources/dubbo.xsd 中&#xff0c;dubbo.xsd 文件用来约束使用 XML 配置时的标签和对应的属性 5.1.2 基于 XML 配置原理解析 主要解…

优维低代码实践:自定义模板

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

threejs中gltf模型出现的问题(黑色,颜色不协调,太小)和解决方案

模型一片漆黑 如下图 可能原因&#xff0c;没有灯光&#xff0c;加下以下代码&#xff1a; // 4、加入灯光 const lightness new THREE.HemisphereLight(0xffffff, 0x444444); lightness.position.set(0, 20, 0); scene.add(lightness); const shadowLight new THREE.Direct…

20230814让惠普(HP)锐14 新AMD锐龙电脑不联网进WIN11进系统

20230814让惠普(HP)锐14 新AMD锐龙电脑不联网进WIN11进系统 2023/8/14 17:19 win11系统无法跳过联网 https://www.xpwin7.com/jiaocheng/28499.html Win11开机联网跳过不了怎么办&#xff1f;Win11开机联网跳过不了解决方法 Win11开机联网跳过不了怎么办&#xff1f;Win11开机…

gearman使用心得

gearman基础 工作原理 部署架构 本质上&#xff0c;gearman可以认为是一个分布式任务队列&#xff0c;client是生产者&#xff0c;worker则是消费者。gearman并不主动分发任务&#xff0c;而是由worker到它那里去取任务执行&#xff0c;所以它采用的是类似kafka的pull消费模式…

【Unity每日一记】向量操作摄像机的移动(向量加减)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

【JavaWeb】实训的长篇笔记(上)

JavaWeb的实训是学校的一门课程&#xff0c;老师先讲解一些基础知识&#xff0c;然后让我们自己开发一个比较简单的Web程序。可涉及的知识何其之多&#xff0c;不是实训课的 3 周时间可以讲得完的&#xff0c;只是快速带过。他说&#xff1a;重点是Web开发的流程。 我的实训草草…

c++虚继承(使用)

class A2:virtual public Grand 1.构造顺序按派生列表顺序&#xff0c;若有虚基类先构造虚基类&#xff0c;销毁顺序和构造顺序相反。 2.虚基类时&#xff0c;孙子C来初始化爷爷Grand。 附&#xff1a;thinking in c 2nd https://www.micc.unifi.it/bertini/download/progr…

【数据结构】 初识集合框架

文章目录 什么是集合框架集合框架的重要性开发中的使用笔试及面试题 数据结构是什么容器背后对应的数据结构相关java知识 什么是算法如何学好数据结构以及算法多画图多思考死磕代码多总结多刷题 总结 什么是集合框架 这里博主将简单介绍一下集合框架&#xff0c;想要详细了解的…