I have TranslateExtension
:
[ContentProperty(nameof(Name))]
public class TranslateExtension : IMarkupExtension<BindingBase>
{
public required string Name { get; set; }
public BindingBase ProvideValue(IServiceProvider serviceProvider)
{
return new Binding
{
Mode = BindingMode.OneWay,
Path = $"[{Name}]",
Source = LocalizationResourceManager.Instance
};
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return ProvideValue(serviceProvider);
}
}
and LocalizationResourceManager
public class LocalizationResourceManager : INotifyPropertyChanged
{
private LocalizationResourceManager()
{
Strings.Culture = CultureInfo.GetCultureInfo("en");
}
public static LocalizationResourceManager Instance { get; } = new();
public object this[string resourceKey]
=> Strings.ResourceManager.GetObject(resourceKey, Strings.Culture) ??
Array.Empty<byte>();
public event PropertyChangedEventHandler? PropertyChanged;
public string GetString(string resourceKey, CultureInfo? culture)
{
if (culture == null)
{
return Strings.ResourceManager.GetString(resourceKey, Strings.Culture)!;
}
return Strings.ResourceManager.GetString(resourceKey, culture)!;
}
public void SetCulture(CultureInfo culture)
{
Strings.Culture = culture;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
}
and it works fine with this <Label Text={extension:Translate MY_WORD_TO_TRANSLATE}>
But MY_WORD_TO_TRANSLATE
should based on variable in viewmodel. I mean there are many possible implementation with MY_WORD_TO_TRANSLATE
it could be a MY_WORD_TO_TRANSLATE_1
, MY_WORD_TO_TRANSLATE_2
and so on, and I wonder how to handle it when MY_WORD_TO_TRANSLATE
is not hardcoded, it can change. I am looking for a solution like <Label Text={extension:Translate Name={Binding Word}}
where Word
is in viewmodel.
I have TranslateExtension
:
[ContentProperty(nameof(Name))]
public class TranslateExtension : IMarkupExtension<BindingBase>
{
public required string Name { get; set; }
public BindingBase ProvideValue(IServiceProvider serviceProvider)
{
return new Binding
{
Mode = BindingMode.OneWay,
Path = $"[{Name}]",
Source = LocalizationResourceManager.Instance
};
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return ProvideValue(serviceProvider);
}
}
and LocalizationResourceManager
public class LocalizationResourceManager : INotifyPropertyChanged
{
private LocalizationResourceManager()
{
Strings.Culture = CultureInfo.GetCultureInfo("en");
}
public static LocalizationResourceManager Instance { get; } = new();
public object this[string resourceKey]
=> Strings.ResourceManager.GetObject(resourceKey, Strings.Culture) ??
Array.Empty<byte>();
public event PropertyChangedEventHandler? PropertyChanged;
public string GetString(string resourceKey, CultureInfo? culture)
{
if (culture == null)
{
return Strings.ResourceManager.GetString(resourceKey, Strings.Culture)!;
}
return Strings.ResourceManager.GetString(resourceKey, culture)!;
}
public void SetCulture(CultureInfo culture)
{
Strings.Culture = culture;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
}
and it works fine with this <Label Text={extension:Translate MY_WORD_TO_TRANSLATE}>
But MY_WORD_TO_TRANSLATE
should based on variable in viewmodel. I mean there are many possible implementation with MY_WORD_TO_TRANSLATE
it could be a MY_WORD_TO_TRANSLATE_1
, MY_WORD_TO_TRANSLATE_2
and so on, and I wonder how to handle it when MY_WORD_TO_TRANSLATE
is not hardcoded, it can change. I am looking for a solution like <Label Text={extension:Translate Name={Binding Word}}
where Word
is in viewmodel.
- 1 Property in ViewModel could simply provide translated text. You will need to monitor for language changes and rise notification for all such properties. – Sinatr Commented Mar 6 at 13:28
2 Answers
Reset to default 1If you look at recent contributions I made to Gerard Versluis TranslateExtension (https://github/jfversluis/MauiLocalizationSample/blob/main/MauiLocalizationSample/TranslateExtension.cs) we see Name is now a BindableProperty so that it can come from a static string (like before) or from your view model. In addition, it supports X0, X1.
The only caveat is that when you use BindableObject in a markup extension, it will not have access to the visual tree so it's necessary to reset your BindingContext again, e.g.
<Label Text="{i18n:TranslateExtension
BindingContext={Reference your_view_model}
x:DataType=your_view_model_type
Name={Binding NameFromYourViewModel}}" />
[ContentProperty(nameof(Name))]
public class TranslateExtension : BindableObject, IMarkupExtension<BindingBase> {
public static BindableProperty NameProperty = BindableProperty.Create(nameof(Name), typeof(string), typeof(TranslateExtension), null,
propertyChanged: (b, o, n) => ((TranslateExtension)b).OnTranslatedNameChanged());
public string Name
{
get => (string)GetValue(NameProperty);
set => SetValue(NameProperty, value);
}
public static BindableProperty X0Property = BindableProperty.Create(nameof(X0), typeof(object), typeof(TranslateExtension), null,
propertyChanged: (b, o, n) => ((TranslateExtension)b).OnTranslatedNameChanged());
public object X0
{
get => GetValue(X0Property);
set => SetValue(X0Property, value);
}
public static BindableProperty X1Property = BindableProperty.Create(nameof(X1), typeof(object), typeof(TranslateExtension), null,
propertyChanged: (b, o, n) => ((TranslateExtension)b).OnTranslatedNameChanged());
public object X1
{
get => GetValue(X1Property);
set => SetValue(X1Property, value);
}
public string? TranslatedName
=> (Name is string name && LocalizationResourceManager.Instance[name] is string translatedName)
? String.Format(translatedName, new object?[] { X0, X1 })
: null;
public void OnTranslatedNameChanged() => OnPropertyChanged(nameof(TranslatedName));
public TranslateExtension()
{
LocalizationResourceManager.Instance.PropertyChanged += (s, e) => OnTranslatedNameChanged();
}
public BindingBase ProvideValue(IServiceProvider serviceProvider)
=> BindingBase.Create<TranslateExtension, string?>(
static source => source.TranslatedName,
mode: BindingMode.OneWay,
source: this
);
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
=> ProvideValue(serviceProvider);
}
Thanks to Stephen Quan answer I managed this, but my label should looks like:
<Label Text="{extension:Translate BindingContext={Binding Path=BindingContext, Source={x:Reference MyContainerName}, x:DataType=myViewModel:MyViewModel},
Name={Binding MyName}}"/>