From 5e73a624e1da7e6df7de7c5ec3500a62a56e2a4d Mon Sep 17 00:00:00 2001 From: Dylan Date: Sat, 29 Nov 2025 22:56:23 +0000 Subject: [PATCH] Remove DynamicallyAccessedMembers attribute from localization services --- .../Exports/LocalizationService.cs | 1 - .../Exports/LocalizationService.cs | 1 - .../Exports/TestLocalizationService.cs | 1 - .../Exports/LocalizationService.cs | 1 - .../MarkupExtensions/LocalizeExtension.cs | 54 ++++++++++++------- 5 files changed, 36 insertions(+), 22 deletions(-) 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; } } }