WPF Series -6- Globalization of Enums
In the previous blog post of this series I wrote about binding ComboBox or ListBox to an enum. Using just the approach described there exposes the enum values directly in […]
Audit and Consulting of Information Systems and Business Processes
In the previous blog post of this series I wrote about binding ComboBox or ListBox to an enum. Using just the approach described there exposes the enum values directly in […]
In the previous blog post of this series I wrote about binding ComboBox or ListBox to an enum. Using just the approach described there exposes the enum values directly in the corresponding ComboBox or ListBox. Because the enum values do not always have descriptive names or because they are language specific, it’s usually more appropriate to display a localized description of each enum value in the UI instead of exposing enum values directly.
In this blog post I’ll show how to extend the approach of the previous blog post to support internationalization.
First of all a custom attribute has to be implemented to support resource file based translation. Our custom attribute is called ResourceNameAttribute
and inherits from System.Attribute
.
ResourceNameAttribute.cs
/** * Copyright 2017 d-fens GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Diagnostics.Contracts; namespace biz.dfch.CS.ArbitraryWpfApplication.UI.Attributes { /// <summary> /// This attribute is used to define the resource name of a specific enum value /// for resource file based translation /// </summary> [AttributeUsage(AttributeTargets.Field)] public class ResourceNameAttribute : Attribute { /// <summary> /// Holds the name of the resource file entry /// </summary> public string Name { get; protected set; } /// <summary> /// Constructor used to init a ResourceNameAttribute /// </summary> /// <param name="name">Name of the resource file entry</param> public ResourceNameAttribute(string name) { Contract.Requires(!string.IsNullOrWhiteSpace(name)); Name = name; } } }
Furthermore some resource files have to be added to the project. At least the default resource file Resources.resx
has to be created. Culture specific resource files can be added as needed.
Next a custom EnumConverter
has to be implemented that inherits from System.ComponentModel.EnumConverter
. In our case we called it EnumTranslationConverter
. It’s ConvertTo
method relies on the built-in ResourceManager
that uses the culture specific resource file depending on the current culture information. If no culture specific resource file exists for the current culture the Resources.resx
will be used by default. The conversion works as follows.
ResourceNameAttribute
ResourceNameAttribute
of the corresponding enum value, out of the culture specific resource fileRESOURCE-ENTRY-MISSING
, if no resource entry present, that matches the name specified in the ResourceNameAttribute
of the corresponding enum value, in the culture specific resource fileEnumTranslationConverter.cs
/** * Copyright 2017 d-fens GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.ComponentModel; using System.Diagnostics.Contracts; using System.Reflection; using biz.dfch.CS.ArbitraryWpfApplication.UI.Attributes; using biz.dfch.CS.ArbitraryWpfApplication.UI.Properties; namespace biz.dfch.CS.ArbitraryWpfApplication.UI.Converters { public class EnumTranslationCoverter : EnumConverter { internal const string MISSING_RESOURCE_ENTRY = "RESOURCE-ENTRY-MISSING"; public EnumTranslationCoverter(Type type) : base(type) { Contract.Requires(null != type); } public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { Contract.Assert(null != context); Contract.Assert(null != culture); Contract.Assert(null != destinationType); if (destinationType != typeof(string)) { return base.ConvertTo(context, culture, value, destinationType); } if (value != null) { FieldInfo fieldInfo = value.GetType().GetField(value.ToString()); Contract.Assert(null != fieldInfo); var attributes = (ResourceNameAttribute[])fieldInfo.GetCustomAttributes(typeof(ResourceNameAttribute), false); if (attributes.Length < 1) { return value.ToString(); } var resourceName = attributes[0].Name; var localizedText = Resources.ResourceManager.GetString(resourceName, culture); if (string.IsNullOrWhiteSpace(localizedText)) { return MISSING_RESOURCE_ENTRY; } return localizedText; } return string.Empty; } } }
Last but not least the affected enums have to be adjusted as follows.
[TypeConverter(typeof(EnumTranslationCoverter))]
to the enum[ResourceName("NAME_OF_THE_RESOURCE_ENTRY")]
ConnectionMaterial.cs
/** * Copyright 2017 d-fens GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System.ComponentModel; using biz.dfch.CS.ArbitraryWpfApplication.UI.Attributes; using biz.dfch.CS.ArbitraryWpfApplication.UI.Converters; namespace biz.dfch.CS.ArbitraryWpfApplication.UI.Domain.Sewer { [TypeConverter(typeof(EnumTranslationCoverter))] public enum ConnectionMaterial { [ResourceName("ConnectionMaterial_None")] None, [ResourceName("ConnectionMaterial_Kunststoff")] Kunststoff, [ResourceName("ConnectionMaterial_Beton")] Beton, [ResourceName("ConnectionMaterial_Saniert")] Saniert, [ResourceName("ConnectionMaterial_Andere")] Andere, [ResourceName("ConnectionMaterial_Unbekannt")] Unbekannt } }
Don’t forget to add resource entries for every resource name specified using the ResourceNameAttribute
.
That’s all. In the UI the values of the resource entries of the resource file based on the current culture will show up.
To change the current culture create a new instance of type CultureInfo
and assign it to Thread.CurrentThread.CurrentCulture
and Thread.CurrentThread.CurrentUICulture
in OnStartup
method of App.xaml.cs
.
Many thanks to Brian Lagunas for his great and inspiring blog post.
« WPF Series -5- Binding ComboBox or ListBox to Enum | WPF Series -7- ViewModel Validation using DataAnnotations » |
3 Comments »