Introduction
This article presents a project that has a couple of enhancements to the original code presented in Advanced WPF Localization article: It adds the ability to specify capitalization and pluralization.
Background
I was on a project that use this Localization
project to do localization. The problem was that the same text was used in different places, but in some cases the text was capitalized, and not in others. In general the English name was used for the names for the resources. Unfortunately, it is not possible to use the same name for two different resources even it the case is different. The funny thing is that the case has to match the name associated with the resource. To get around this problem, I enhanced the original value to include the ability to specify the case as Property on the ExtensionMethod
. I used and enumeration to specify the different cases allowed (none, upper, lower), which meant that intellesence works. This did a great job at solving our problem.
Later I thought that I could also include pluralization. In our application we did have a method to do pluralization, and I thought about including that code, but later found out about PluralizationServices
, which is in System.Data.Entity.Design
namespace
. There are some limitation with the service, so may actually want to use something different.
While at it I did some cleanup of the code.
New Code
There was only a little bit of code that was needed to add these two capabilities to the existing project. In the derived MarkupExtension
class
LocExtension
needed to add the two properties and have an enumeration for each:
public CaseEnum Case { get; set; }
public enum CaseEnum
{
None = 0, Upper, Lower
}
public PluralizationEnum Pluralization { get; set; }
public enum PluralizationEnum
{
None = 0, Plural, Singular
}
Also needed to add these two properties to the abstract
LocalizedProperty
class
that is used as the base class to for the different TargetProperty
types. Then in the ProvideValue
method of the LocExtension
just needed to set the properties in the LocalizedProperty
class
to match the values in the LocExtension
class
.
Then all that was needed was to modify the string
generated in the GetValue
method of the LocalizedProperty
class
as appropriate before returning the value if the type is actually a string
:
internal object GetValue()
{
var localizedValue = GetLocalizedValue();
var converter = Property.Converter;
if (converter != null)
{
localizedValue = converter.Convert(
localizedValue,
Property.GetValueType(),
Property.ConverterParameter,
Property.GetCulture()
);
}
return AdjustForCase(AdjustForPluralization(localizedValue));
}
private object AdjustForCase(object localizedValue)
{
var strValue = localizedValue as string;
if (strValue == null) return localizedValue;
switch (Property.Case)
{
case LocExtension.CaseEnum.None:
return strValue;
case LocExtension.CaseEnum.Upper:
return strValue.ToUpper();
case LocExtension.CaseEnum.Lower:
return strValue.ToLower();
default:
throw new ArgumentOutOfRangeException();
}
}
private object AdjustForPluralization(object localizedValue)
{
var strValue = localizedValue as string;
if (strValue == null) return localizedValue;
switch (Property.Pluralization)
{
case LocExtension.PluralizationEnum.None:
return strValue;
case LocExtension.PluralizationEnum.Singular:
return strValue.ToSingular();
case LocExtension.PluralizationEnum.Plural:
return strValue.ToPlural();
default:
throw new ArgumentOutOfRangeException();
}
}
It should be noted that use an object
for the localizedValue
since this value may not actually be a string. Each of the two methods used to capitalize and pluralize the value first checks if the localizedValue
is a string
before processing.
The only other thing added to this project was a static class to contain the extension methods for the pluralization:
public static class PluralizationExtensions
{
private static PluralizationService _pluralizationService;
private static PluralizationService PluralizationService => _pluralizationService
?? (_pluralizationService = PluralizationService.CreateService(CultureInfo.CurrentCulture));
public static string ToPlural(this string value)
{
if (String.IsNullOrEmpty(value)) throw new ArgumentException("Value is required", nameof(value));
return PluralizationService.IsPlural(value) ? value : PluralizationService.Pluralize(value);
}
public static string ToSingular(this string value)
{
if (String.IsNullOrEmpty(value)) throw new ArgumentException("Value is required", nameof(value));
return PluralizationService.IsSingular(value) ? value : PluralizationService.Singularize(value);
}
}
This class
could be used on its own.
One More Addition
I actually added one feature to this class. It may not be needed, but I did use it. This was a static
Loc
class that gave access to the same values using the same key string
:
public static class Loc
{
public static object GetValue(string value)
{
var resourceManager = LocalizationManager.DefaultResourceManager;
if (resourceManager == null) return $"[{value}]";
var uiCulture = Thread.CurrentThread.CurrentCulture;
var returnValue = resourceManager.GetObject(value, uiCulture);
return returnValue ?? $"[{value}]";
}
}
I also updated the solution to framework version 4.6.1, and replaced a lot of the code with the conditional operator, and some of the newer C# 6.0 features..
Using the code
The original article can be refered to for informatin about the original features of the code. This article only covers the additions, which are the added elements to specify the captitalization and pluralization of the code.
This snipit shows how to specify that the text should be upper case and pluralized:
<TextBlock Grid.Row="0"
Grid.Column="0"
Style="{StaticResource Label}"
Text="{Loc Label_CodeBehind_Callback, Case=Upper, Pluralization=Plural}" />
Both the Case
and Pluralization
arguments are optional, so can be left out if this feature is not desired. Both are specified as enumerations in the code:
public enum CaseEnum
{
None = 0, Upper, Lower
}
public enum PluralizationEnum
{
None = 0, Plural, Singular
}
The Sample
This is the same sample as in the original code, but with a couple of changes. One is that control that contains "Callback:" has the Case
and Pluralization
specified in the XAML, as can be seen in the code for the TextBlock
above. The second is that the colon (":") at the end of "Callback:" in the Resources.resx
file for Label_CodeBehind_Callback
has been removed. This is because the colon (":") interferes with pluralization as done by the PluralizationService
. The PluralizationService
does not deal with the colon, so to demonstrate the capability, had to remove the color.
History
- 16/06/16: Initial Version
- 16/06/17: Cleanup