I try to write a globalized app using WPF. I use .resx files to maintain my location resources. I create a customized markup extension to bind location resource to XAML. The problem is when I edit my markup extension in XAML, I can't which resource key could be write down, so I search that problem with ai (gemine, chatgpt...) these ai provide a solution: use TypeConverter can support intellisense in XAML. I followed it. Unfortunately, after a hard work it is unavailable.
I would like to know if this intellisense solution when writing markup extension is feasible. If it is feasible, what is wrong with my implementation? If not, is there any other way to implement it?
My implementation:
ResX Manager (a .resx file manager extension in Visual Studio), 2 .resx files which are auto created by ResX Manager, and a custom markup extension:
[MarkupExtensionReturnType(typeof(object))]
public class LocExtension : MarkupExtension
{
public LocExtension() { }
public LocExtension(LocalizationKey key)
{
Key = key;
}
public LocalizationKey Key { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (DesignerHelper.IsInDesignMode)
{
return $"[{Key}]";
}
if (string.IsNullOrEmpty(Key.Key))
{
return string.Empty;
}
try
{
string? value = language.ResourceManager.GetString(Key, CultureInfo.CurrentUICulture);
return value ?? $"#{Key.Key}#";
}
catch
{
return $"!ERR:{Key.Key}!";
}
}
}
TypeConverter:
public class ResXKeyTypeConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
{
if (destinationType == typeof(string))
{
if (context != null && context.Instance != null)
{
if (!(context.Instance is LocalizationKey localizationKey))
{
throw new ArgumentException("context.Instance is not LocalizationKey", nameof(context));
}
return true;
}
return true;
}
return base.CanConvertTo(context, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value == null)
{
throw GetConvertFromException(value);
}
if (value is string source)
{
return new LocalizationKey(source);
}
return base.ConvertFrom(context, culture, value);
}
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if ( value is LocalizationKey localizationKey)
{
if (destinationType == typeof(string))
{
// When invoked by the serialization engine we can convert to string only for some instances
if (context != null && context.Instance != null)
{
throw new NotSupportedException("null reference in resx key typeConverter ");
}
return localizationKey.ToString();
}
}
// Pass unhandled cases to base class (which will throw exceptions for null value or destinationType.)
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext? context)
{
try
{
var keys = typeof(language)
.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static)
.Where(p => p.PropertyType == typeof(string))
.Select(p => p.Name)
.OrderBy(name => name)
.ToList();
return new StandardValuesCollection(keys);
}
catch
{
string[] empty = ["aaa", "bbb"];
return new StandardValuesCollection(empty);
}
}
}
locationKey
[TypeConverter(typeof(ResXKeyTypeConverter))]
public class LocalizationKey
{
public string Key { get; set; }
public LocalizationKey()
{
}
public LocalizationKey(string key)
{
Key = key;
}
public override string ToString()
{
return Key ?? string.Empty;
}
public static implicit operator string(LocalizationKey localizationKey)
{
return localizationKey?.Key;
}
public static implicit operator LocalizationKey(string key)
{
return new LocalizationKey(key);
}
}
my markupExtension used in xaml
<TextBlock Text="{markupExt:Loc Key=}"/>
<TextBlock Text="{markupExt:Loc}"/>
tow of them inavailable
Keyas an enum should work, but then you'll have to maintain the enumuration to keep it consistent with rex file.