diff --git a/src/AvaloniaXKCD.Browser/Exports/LocalizationService.cs b/src/AvaloniaXKCD.Browser/Exports/LocalizationService.cs
index dd3a69e7..71d45fc0 100644
--- a/src/AvaloniaXKCD.Browser/Exports/LocalizationService.cs
+++ b/src/AvaloniaXKCD.Browser/Exports/LocalizationService.cs
@@ -9,7 +9,6 @@ namespace AvaloniaXKCD.Browser;
///
/// Browser implementation of localization service with JavaScript interop
///
-[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public partial class BrowserLocalizationService : LocalizationService
{
[JSImport("getLocale", "interop")]
diff --git a/src/AvaloniaXKCD.Desktop/Exports/LocalizationService.cs b/src/AvaloniaXKCD.Desktop/Exports/LocalizationService.cs
index e79a6bec..43074303 100644
--- a/src/AvaloniaXKCD.Desktop/Exports/LocalizationService.cs
+++ b/src/AvaloniaXKCD.Desktop/Exports/LocalizationService.cs
@@ -7,7 +7,6 @@ namespace AvaloniaXKCD.Desktop;
///
/// Desktop implementation of localization service
///
-[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class DesktopLocalizationService : LocalizationService
{
public override CultureInfo GetCulture()
diff --git a/src/AvaloniaXKCD.Tests/Exports/TestLocalizationService.cs b/src/AvaloniaXKCD.Tests/Exports/TestLocalizationService.cs
index 4150b5f3..8c22b282 100644
--- a/src/AvaloniaXKCD.Tests/Exports/TestLocalizationService.cs
+++ b/src/AvaloniaXKCD.Tests/Exports/TestLocalizationService.cs
@@ -8,7 +8,6 @@ namespace AvaloniaXKCD.Tests.Exports;
/// Test implementation of LocalizationService for unit tests.
/// This is in the Tests.Exports namespace to be properly registered.
///
-[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public sealed class TestLocalizationService : LocalizationService
{
public override CultureInfo GetCulture()
diff --git a/src/AvaloniaXKCD/Exports/LocalizationService.cs b/src/AvaloniaXKCD/Exports/LocalizationService.cs
index 7aab58b5..50a6d34d 100644
--- a/src/AvaloniaXKCD/Exports/LocalizationService.cs
+++ b/src/AvaloniaXKCD/Exports/LocalizationService.cs
@@ -8,7 +8,6 @@ namespace AvaloniaXKCD.Exports;
///
/// Abstract base service for handling application localization using .resx files
///
-[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public abstract class LocalizationService : ILocalizationService
{
private readonly ResourceManager _resourceManager;
diff --git a/src/AvaloniaXKCD/MarkupExtensions/LocalizeExtension.cs b/src/AvaloniaXKCD/MarkupExtensions/LocalizeExtension.cs
index a3006b96..9895302b 100644
--- a/src/AvaloniaXKCD/MarkupExtensions/LocalizeExtension.cs
+++ b/src/AvaloniaXKCD/MarkupExtensions/LocalizeExtension.cs
@@ -1,8 +1,11 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
using Avalonia.Data;
+using Avalonia.Data.Core;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
+using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using AvaloniaXKCD.Exports;
namespace AvaloniaXKCD.MarkupExtensions;
@@ -14,45 +17,60 @@ public class LocalizeExtension : MarkupExtension
{
public string Key { get; set; } = string.Empty;
- [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
- Justification = "The Binding source is a local private class (LocalizedStringProvider) where the property 'Value' is preserved via code structure or annotation.")]
public override object ProvideValue(IServiceProvider serviceProvider)
{
var localizationService = ExportContainer.Get();
if (localizationService == null)
- {
- return new Binding { Source = Key };
- }
+ return Key;
+
+ var provider = new LocalizedStringProvider(localizationService, Key);
+
+ // Create an IPropertyInfo for the 'Value' CLR property
+ var propInfo = new ClrPropertyInfo(
+ name: nameof(LocalizedStringProvider.Value),
+ getter: obj => ((LocalizedStringProvider)obj!).Value,
+ setter: null,
+ propertyType: typeof(string)
+ );
+
+ // Build a compiled binding path that uses an INPC-aware accessor factory
+ var path = new CompiledBindingPathBuilder()
+ .Property(
+ propInfo,
+ // Use Avalonia's helper to create an INPC-capable property accessor
+ PropertyInfoAccessorFactory.CreateInpcPropertyAccessor
+ )
+ .Build();
- var binding = new Binding
+ // Create and return a CompiledBindingExtension using that compiled path
+ var ext = new CompiledBindingExtension(path)
{
- Source = new LocalizedStringProvider(localizationService, Key),
- Path = nameof(LocalizedStringProvider.Value),
+ Source = provider,
Mode = BindingMode.OneWay
};
- return binding;
+ return ext.ProvideValue(serviceProvider);
}
- private class LocalizedStringProvider : INotifyPropertyChanged
+ private sealed class LocalizedStringProvider : ObservableObject, IDisposable
{
- private readonly ILocalizationService _localizationService;
+ private readonly ILocalizationService _service;
private readonly string _key;
- public LocalizedStringProvider(ILocalizationService localizationService, string key)
+ public LocalizedStringProvider(ILocalizationService service, string key)
{
- _localizationService = localizationService;
+ _service = service;
_key = key;
- _localizationService.CultureChanged += OnCultureChanged;
+ _service.CultureChanged += OnCultureChanged;
}
- public string Value => _localizationService.GetString(_key);
+ public string Value => _service.GetString(_key);
- public event PropertyChangedEventHandler? PropertyChanged;
+ private void OnCultureChanged(object? sender, CultureInfo e) => OnPropertyChanged(nameof(Value));
- private void OnCultureChanged(object? sender, System.Globalization.CultureInfo e)
+ public void Dispose()
{
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+ _service.CultureChanged -= OnCultureChanged;
}
}
}