-
|
Hi, we have custom markup extensions (code below) used for string localization (inspired by WPFLocalizeExtension) and it works fine on older Avalonia version (11.0.10), but today I tried updating our app to the latest version and suddenly it stops working and I have no clue why... Could you please give me a hint? On older version calling the Here is the implementation: /// <summary>
/// Markup extension for localizing strings in AXAML.
/// </summary>
public class StringLocalizationExtension : MarkupExtension
{
/// <summary>
/// Initializes a new instance of the <see cref="StringLocalizationExtension"/> class.
/// </summary>
public StringLocalizationExtension()
{
Key = new object();
Args = [];
}
/// <summary>
/// Initializes a new instance of the <see cref="StringLocalizationExtension"/> class with a binding key.
/// </summary>
/// <param name="key">The binding key.</param>
public StringLocalizationExtension(BindingBase key)
{
Key = key;
Args = [];
}
/// <summary>
/// Initializes a new instance of the <see cref="StringLocalizationExtension"/> class with a string key.
/// </summary>
/// <param name="key">The string key.</param>
public StringLocalizationExtension(string key)
{
Key = key;
Args = [];
}
/// <summary>
/// Initializes a new instance of the <see cref="StringLocalizationExtension"/> class with a key and one argument binding.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="arg">The argument binding.</param>
public StringLocalizationExtension(object key, BindingBase arg)
{
Key = key;
Args = [arg];
}
/// <summary>
/// Initializes a new instance of the <see cref="StringLocalizationExtension"/> class with a key and two argument bindings.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="arg1">The first argument binding.</param>
/// <param name="arg2">The second argument binding.</param>
public StringLocalizationExtension(object key, BindingBase arg1, BindingBase arg2)
{
Key = key;
Args = [arg1, arg2];
}
/// <summary>
/// Initializes a new instance of the <see cref="StringLocalizationExtension"/> class with a key and three argument bindings.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="arg1">The first argument binding.</param>
/// <param name="arg2">The second argument binding.</param>
/// <param name="arg3">The third argument binding.</param>
public StringLocalizationExtension(object key, BindingBase arg1, BindingBase arg2, BindingBase arg3)
{
Key = key;
Args = [arg1, arg2, arg3];
}
/// <summary>
/// Gets or sets the key used to identify the string resource.
/// </summary>
public object Key { get; set; }
/// <summary>
/// Gets or sets the Binding arguments that will be used to format the resource string using <see cref="string.Format(string, object?[])"/>.
/// </summary>
public BindingBase[] Args { get; }
/// <inheritdoc/>
public override object ProvideValue(IServiceProvider serviceProvider)
{
BindingBase keyBinding;
// This implementation depends on MultiBinding, so when the provided Key is of type string,
// it is easier to just wrap it with a Binding and setting the source directly to the key value itself,
// than to handle this case separately
if (Key is string key)
{
keyBinding = new Binding() { Mode = BindingMode.OneWay, Source = key, };
}
else if (Key is BindingBase binding)
{
keyBinding = binding;
}
else
{
// If the provided Key is not string or Binding, it is returned back to the caller
return Key;
}
var target = (IProvideValueTarget?)serviceProvider.GetService(typeof(IProvideValueTarget));
var targetObject = target?.TargetObject as StyledElement;
var mb = new MultiBinding()
{
// The Binding for StringLocalizationResolver is here,
// because the StringLocalizationResolver is somewhat responsible for notifying the UI for language changes.
// If this Binding wasn't created, all of the keys would be translated only once and wouldn't react for lang changes,
// thus rendering the StringLocalizationExtension kind of pointless...
// Maybe the Binding could be created just once (be static), but I did not test it.
Bindings = [CreateLocalizationBinding(), keyBinding, .. Args],
Converter = new FuncMultiValueConverter<object, string?>(bindings =>
{
var asm = StringLocalizationHelper.GetAssembly(targetObject);
var dic = StringLocalizationHelper.GetDictionaryName(targetObject);
// The convert method is triggered in two or three cases.
// First, the language changed, e.g. the StringLocalizationResolver.Instance.Invalidate() was called
// or a StringLocalizationResolver.Instance.PropertyChanged was invoked.
// Second, the Key was provided via Binding and the bound value changed.
// Third, the Key was provided with arguments and one of the arguments has changed.
var count = bindings.Count();
if (count < 2)
return "Invalid Loc key or binding!";
var key = bindings.SecondOrDefault();
if (key is null or UnsetValueType)
return null;
if (StringLocalizationResolver.Instance is null)
throw new InvalidOperationException("The StringLocalizationResolver.Instance is not set!");
var str = StringLocalizationResolver.Instance.Get(key.ToString()!, dic, asm);
var caseConversion = StringLocalizationHelper.GetCaseConversion(targetObject);
str = StringLocalizationHelper.ConvertCase(str, caseConversion);
if (count == 2)
return str;
var args = bindings.Skip(2).ToArray();
return string.Format(str, args);
}),
};
return mb;
}
static Binding CreateLocalizationBinding() => new Binding("[\"\"]", BindingMode.OneWay) { Source = StringLocalizationResolver.Instance };
}
/// <summary>
/// Proxy class for resolving strings via string localizer implementation
/// </summary>
/// <remarks>
/// The <see cref="Instance"/> property value must be set on startup before resolving first strings!
/// <br/>
/// <br/>
/// The value is set automatically when <see cref="DIExtensions.UseStringLocalizer(Microsoft.Extensions.Hosting.IHost)"/> is called.
/// </remarks>
/// <param name="stringLocalizer">The string localizer implementation</param>
public class StringLocalizationResolver(StringLocalizer stringLocalizer) : INotifyPropertyChanged
{
private readonly StringLocalizer _stringLocalizer = stringLocalizer;
private const string INDEXER_NAME = "Item";
private const string INDEXER_ARRAY_NAME = "Item[]";
/// <summary>
/// Invoked when language changed
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// The instance proxy that is used from <see cref="StringLocalizationExtension"/> to resolve string keys from AXAML or strings from Binding (ViewModels)
/// </summary>
public static StringLocalizationResolver? Instance { get; set; }
public string this[string key] => Get(key, null, null);
public string Get(string key, string? dicName, Assembly? assembly)
{
return _stringLocalizer.Get(key, dicName, assembly);
}
/// <summary>
/// Invalidates current values by invoking <see cref="PropertyChanged"/>
/// </summary>
public void Invalidate()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(INDEXER_NAME));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(INDEXER_ARRAY_NAME));
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
|
Related #16084 |
Beta Was this translation helpful? Give feedback.
-
|
In short, at the moment, because StringLocalizationResolver.Instance[""] likely never changes value, the first binding that your multibinding is connecting to and the PropertyChanged events raised by Invalidate() are being ignored. It looks like long-term this behaviour is being reverted, but short-term you'd need to bind to a property that does change when you need to re-translate the strings like a language ID or similar. Or possibly to Instance[key] instead. |
Beta Was this translation helpful? Give feedback.
In short, at the moment, because StringLocalizationResolver.Instance[""] likely never changes value, the first binding that your multibinding is connecting to and the PropertyChanged events raised by Invalidate() are being ignored. It looks like long-term this behaviour is being reverted, but short-term you'd need to bind to a property that does change when you need to re-translate the strings like a language ID or similar.
Or possibly to Instance[key] instead.