WPF應(yīng)用程序本地化的最佳方法分享
應(yīng)用程序本地化有很多種方式,選擇合適的才是最好的。這里只討論一種方式,動(dòng)態(tài)資源(DynamicResource) 這種方式可是在不重啟應(yīng)用程序的情況下進(jìn)行資源的切換,不論是語(yǔ)言切換,還是更上層的主題切換。想要運(yùn)行時(shí)切換不同的資源就必須使用 動(dòng)態(tài)資源(DynamicResource) 這種方式。
圖片是可以使用資源字典進(jìn)行動(dòng)態(tài) binding 的 不要被誤導(dǎo)了
資源文件
確保不同語(yǔ)言環(huán)境中資源 Key 是同一個(gè),且對(duì)用的資源類型是相同的。
在資源比較多的情況下,可以通過(guò)格式化的命名方式來(lái)規(guī)避 Key 沖突。
資源文件的屬性可以設(shè)置成:
生成操作:內(nèi)容
復(fù)制到輸出目錄:如果較新則復(fù)制
上面的操作可以在不重新編譯程序的情況下編輯語(yǔ)言資源并應(yīng)用。
如果不想讓別人更改語(yǔ)言資源則
生成操作:頁(yè)
復(fù)制到輸出目錄:不復(fù)制
英文資源文件 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">語(yǔ)言按鈕</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 小時(shí)</sys:String> <sys:String x:Key="LockTime-TwoHour">2 小時(shí)</sys:String> <sys:String x:Key="LockTime-ThreeHour">3 小時(shí)</sys:String> <sys:String x:Key="LockTime-Never">永不</sys:String> </ResourceDictionary>
資源使用
App.xaml
設(shè)置默認(rèn)語(yǔ)言資源,用于在設(shè)計(jì)階段預(yù)覽,并利用 VS 智能提示資源的 Key 防止 Key編寫出錯(cuò)。
<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 配置項(xiàng)用于保存用戶選擇的語(yǔ)言類型
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="language" value=""/> </appSettings> </configuration>
輔助類
語(yǔ)言切換操作類
檢查方法入?yún)ⅲ兄祫t切換到指定的資源,無(wú)值則讀取配置文件中的值,若配置文件中仍無(wú)值,則獲取當(dāng)前線程運(yùn)行的語(yǔ)言環(huán)境,切換到與語(yǔ)言環(huán)境相匹配的資源,如果沒(méi)有找到與之匹配的資源則不做操作。
切換資源之后,將配置文件中的 language 的 value 改為當(dāng)前所選的語(yǔ)言保存并刷新配置文件,直到下次更改。
重寫 Application 類的 OnStartup 方法,在 OnStartup 中調(diào)用 SetLanguage 方法以實(shí)現(xiàn)應(yīng)用程序啟動(dòng)對(duì)語(yǔ)言環(huán)境的判別。
/// <summary> /// 語(yǔ)言操作 /// </summary> public class LanguageOperate { private const string KEY_OF_LANGUAGE = "language"; /// <summary> /// 語(yǔ)言本地化 /// </summary> /// <param name="language">語(yǔ)言環(huán)境</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 控件通常是通過(guò) ItemSource 進(jìn)行綁定,默認(rèn)情況下是無(wú)法對(duì)綁定的資源進(jìn)行翻譯的。
通過(guò)繼承 MarkupExtension 類 重寫 ProvideValue 方法來(lái)實(shí)現(xiàn),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 }
實(shí)現(xiàn)效果
到此這篇關(guān)于WPF應(yīng)用程序本地化的最佳方法分享的文章就介紹到這了,更多相關(guān)WPF本地化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#中的Task.WhenAll和Task.WhenAny方法介紹
這篇文章介紹了C#中的Task.WhenAll和Task.WhenAny方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04C#使用FileStream循環(huán)讀取大文件數(shù)據(jù)的方法示例
這篇文章主要介紹了C#使用FileStream循環(huán)讀取大文件數(shù)據(jù)的方法,結(jié)合實(shí)例形式分析了FileStream文件流的形式循環(huán)讀取大文件的相關(guān)操作技巧,需要的朋友可以參考下2017-05-05C#實(shí)現(xiàn)JWT無(wú)狀態(tài)驗(yàn)證的實(shí)戰(zhàn)應(yīng)用解析
這篇文章主要介紹了C#實(shí)現(xiàn)JWT無(wú)狀態(tài)驗(yàn)證的實(shí)戰(zhàn)應(yīng)用解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03C#實(shí)現(xiàn)軟件開機(jī)自啟動(dòng)功能(不需要管理員權(quán)限)
在本文中,我們探討了如何使用C#語(yǔ)言實(shí)現(xiàn)應(yīng)用程序在系統(tǒng)啟動(dòng)時(shí)自動(dòng)運(yùn)行的功能,同時(shí)避免了對(duì)管理員權(quán)限的需求,文章通過(guò)代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2025-04-04C# newtonsoft.json中文亂碼問(wèn)號(hào)的解決方案
這篇文章主要介紹了C# newtonsoft.json中文亂碼問(wèn)號(hào)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07C#簡(jiǎn)單訪問(wèn)SQLite數(shù)據(jù)庫(kù)的方法(安裝,連接,查詢等)
這篇文章主要介紹了C#簡(jiǎn)單訪問(wèn)SQLite數(shù)據(jù)庫(kù)的方法,涉及SQLite數(shù)據(jù)庫(kù)的下載、安裝及使用C#連接、查詢SQLIte數(shù)據(jù)庫(kù)的相關(guān)技巧,需要的朋友可以參考下2016-07-07將數(shù)組中指定數(shù)量的元素移動(dòng)數(shù)組后面的實(shí)現(xiàn)代碼
本篇文章是對(duì)將數(shù)組中指定數(shù)量的元素移動(dòng)數(shù)組后面的實(shí)現(xiàn)代碼進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06