Skip to content

Commit c01e1e1

Browse files
authored
IPlatformSettings.PreferredApplicationLanguage API (#20108)
* Add new IPlatformSettings APIs * Add control catalog PlatformSettingsPage * Windows PreferredApplicationLanguage implementation * Browser PreferredApplicationLanguage implementation * Run API suppressions for the IPlatformSettings * Rebase PlatformSettingsPage on new ContentPage * Return CultureInfo.InstalledUICulture instead of CultureInfo.CurrentUICulture for default PreferredApplicationLanguage implementation * Android PreferredApplicationLanguage implementation * iOS PreferredApplicationLanguage implementation * Use GetUserPreferredUILanguages for Win32 implementation instead of GetUserDefaultLocaleName * MacOS implementation of PreferredApplicationLanguage * Use GlobalizationPreferences for WIn32 prefered language, GetUserPreferredUILanguages doesn't work as expected with Windows settigns * Avoid AvaloniaLocator in control catalog * Remove unused method * Add value/remarks XML docs on the PreferredApplicationLanguage
1 parent f61bcc3 commit c01e1e1

18 files changed

Lines changed: 561 additions & 14 deletions

File tree

api/Avalonia.nupkg.xml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
33
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
44
<Suppression>
@@ -145,6 +145,12 @@
145145
<Left>baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Left>
146146
<Right>current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Right>
147147
</Suppression>
148+
<Suppression>
149+
<DiagnosticId>CP0006</DiagnosticId>
150+
<Target>E:Avalonia.Platform.IPlatformSettings.PreferredApplicationLanguageChanged</Target>
151+
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
152+
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
153+
</Suppression>
148154
<Suppression>
149155
<DiagnosticId>CP0006</DiagnosticId>
150156
<Target>M:Avalonia.Platform.IBitmapImpl.Save(System.IO.Stream,Avalonia.Media.Imaging.BitmapEncoderOptions)</Target>
@@ -175,6 +181,12 @@
175181
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
176182
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
177183
</Suppression>
184+
<Suppression>
185+
<DiagnosticId>CP0006</DiagnosticId>
186+
<Target>P:Avalonia.Platform.IPlatformSettings.PreferredApplicationLanguage</Target>
187+
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
188+
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
189+
</Suppression>
178190
<Suppression>
179191
<DiagnosticId>CP0006</DiagnosticId>
180192
<Target>M:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl.SetWindowCornerPreference(Avalonia.Controls.Win32Properties.WindowCornerPreference)</Target>
@@ -193,6 +205,12 @@
193205
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
194206
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
195207
</Suppression>
208+
<Suppression>
209+
<DiagnosticId>CP0006</DiagnosticId>
210+
<Target>E:Avalonia.Platform.IPlatformSettings.PreferredApplicationLanguageChanged</Target>
211+
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
212+
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
213+
</Suppression>
196214
<Suppression>
197215
<DiagnosticId>CP0006</DiagnosticId>
198216
<Target>M:Avalonia.Platform.IBitmapImpl.Save(System.IO.Stream,Avalonia.Media.Imaging.BitmapEncoderOptions)</Target>
@@ -223,6 +241,12 @@
223241
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
224242
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
225243
</Suppression>
244+
<Suppression>
245+
<DiagnosticId>CP0006</DiagnosticId>
246+
<Target>P:Avalonia.Platform.IPlatformSettings.PreferredApplicationLanguage</Target>
247+
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
248+
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
249+
</Suppression>
226250
<Suppression>
227251
<DiagnosticId>CP0006</DiagnosticId>
228252
<Target>M:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl.SetWindowCornerPreference(Avalonia.Controls.Win32Properties.WindowCornerPreference)</Target>
@@ -241,4 +265,4 @@
241265
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
242266
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
243267
</Suppression>
244-
</Suppressions>
268+
</Suppressions>

native/Avalonia.Native/src/OSX/PlatformSettings.mm

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
#include "common.h"
2+
#include "AvnString.h"
23

34
@interface CocoaThemeObserver : NSObject
45
-(id)initWithCallback:(IAvnActionCallback *)callback;
56
@end
67

8+
@interface CocoaLocaleObserver : NSObject
9+
-(id)initWithCallback:(IAvnActionCallback *)callback;
10+
-(void)localeDidChange:(NSNotification *)notification;
11+
@end
12+
713
class PlatformSettings : public ComSingleObject<IAvnPlatformSettings, &IID_IAvnPlatformSettings>
814
{
915
CocoaThemeObserver* observer;
16+
CocoaLocaleObserver* localeObserver;
1017

1118
public:
1219
FORWARD_IUNKNOWN()
@@ -58,6 +65,28 @@ virtual void RegisterColorsChange(IAvnActionCallback *callback) override
5865
[[NSApplication sharedApplication] addObserver:observer forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew context:nil];
5966
}
6067
}
68+
69+
virtual HRESULT GetPreferredLanguage(IAvnString** ret) override
70+
{
71+
@autoreleasepool
72+
{
73+
if (ret == nullptr)
74+
return E_POINTER;
75+
76+
auto language = [[NSLocale preferredLanguages] firstObject];
77+
*ret = language == nil ? nullptr : CreateAvnString(language);
78+
return S_OK;
79+
}
80+
}
81+
82+
virtual void RegisterLanguageChange(IAvnActionCallback *callback) override
83+
{
84+
localeObserver = [[CocoaLocaleObserver alloc] initWithCallback: callback];
85+
[[NSNotificationCenter defaultCenter] addObserver:localeObserver
86+
selector:@selector(localeDidChange:)
87+
name:NSCurrentLocaleDidChangeNotification
88+
object:nil];
89+
}
6190

6291
private:
6392
unsigned int to_argb(NSColor* color)
@@ -107,6 +136,23 @@ - (void)observeValueForKeyPath:(NSString *)keyPath
107136
}
108137
@end
109138

139+
@implementation CocoaLocaleObserver
140+
{
141+
ComPtr<IAvnActionCallback> _callback;
142+
}
143+
- (id) initWithCallback:(IAvnActionCallback *)callback {
144+
self = [super init];
145+
if (self) {
146+
_callback = callback;
147+
}
148+
return self;
149+
}
150+
151+
- (void)localeDidChange:(NSNotification *)notification {
152+
_callback->Run();
153+
}
154+
@end
155+
110156
extern IAvnPlatformSettings* CreatePlatformSettings()
111157
{
112158
return new PlatformSettings();
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<ContentPage Theme="{StaticResource ScrollPage}" x:Class="ControlCatalog.Pages.PlatformSettingsPage"
2+
xmlns="https://github.com/avaloniaui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
xmlns:viewModels="using:ControlCatalog.ViewModels"
7+
d:DesignHeight="800"
8+
d:DesignWidth="600"
9+
mc:Ignorable="d"
10+
Header="Platform Settings"
11+
x:DataType="viewModels:PlatformSettingsViewModel">
12+
13+
<StackPanel Spacing="20">
14+
15+
<TextBlock Text="This page displays IPlatformSettings values which may change at runtime based on system settings."
16+
TextWrapping="Wrap"
17+
Opacity="0.7" />
18+
19+
<GroupBox Header="System Color Values">
20+
<StackPanel Spacing="15">
21+
<Grid ColumnDefinitions="180,*" RowDefinitions="Auto,Auto">
22+
<TextBlock Grid.Column="0" Grid.Row="0" Text="Theme Variant:" VerticalAlignment="Center" />
23+
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding ThemeVariant}" FontWeight="Bold" VerticalAlignment="Center" />
24+
25+
<TextBlock Grid.Column="0" Grid.Row="1" Text="Contrast Preference:" VerticalAlignment="Center" Margin="0,10,0,0" />
26+
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding ContrastPreference}" FontWeight="Bold" VerticalAlignment="Center" Margin="0,10,0,0" />
27+
</Grid>
28+
29+
<StackPanel Spacing="10">
30+
<Grid ColumnDefinitions="180,120,*">
31+
<TextBlock Grid.Column="0" Text="Accent Color 1:" VerticalAlignment="Center" />
32+
<Border Grid.Column="1"
33+
Width="100"
34+
Height="40"
35+
BorderBrush="Black"
36+
BorderThickness="1"
37+
CornerRadius="4"
38+
HorizontalAlignment="Left">
39+
<Border.Background>
40+
<SolidColorBrush Color="{Binding AccentColor1}" />
41+
</Border.Background>
42+
</Border>
43+
<TextBlock Grid.Column="2"
44+
Text="{Binding AccentColor1}"
45+
VerticalAlignment="Center"
46+
Margin="10,0,0,0" />
47+
</Grid>
48+
49+
<Grid ColumnDefinitions="180,120,*">
50+
<TextBlock Grid.Column="0" Text="Accent Color 2:" VerticalAlignment="Center" />
51+
<Border Grid.Column="1"
52+
Width="100"
53+
Height="40"
54+
BorderBrush="Black"
55+
BorderThickness="1"
56+
CornerRadius="4"
57+
HorizontalAlignment="Left">
58+
<Border.Background>
59+
<SolidColorBrush Color="{Binding AccentColor2}" />
60+
</Border.Background>
61+
</Border>
62+
<TextBlock Grid.Column="2"
63+
Text="{Binding AccentColor2}"
64+
VerticalAlignment="Center"
65+
Margin="10,0,0,0" />
66+
</Grid>
67+
68+
<Grid ColumnDefinitions="180,120,*">
69+
<TextBlock Grid.Column="0" Text="Accent Color 3:" VerticalAlignment="Center" />
70+
<Border Grid.Column="1"
71+
Width="100"
72+
Height="40"
73+
BorderBrush="Black"
74+
BorderThickness="1"
75+
CornerRadius="4"
76+
HorizontalAlignment="Left">
77+
<Border.Background>
78+
<SolidColorBrush Color="{Binding AccentColor3}" />
79+
</Border.Background>
80+
</Border>
81+
<TextBlock Grid.Column="2"
82+
Text="{Binding AccentColor3}"
83+
VerticalAlignment="Center"
84+
Margin="10,0,0,0" />
85+
</Grid>
86+
</StackPanel>
87+
</StackPanel>
88+
</GroupBox>
89+
90+
<GroupBox Header="Language Settings">
91+
<StackPanel Spacing="10">
92+
<TextBlock Text="This reflects the preferred application language as specified in OS settings."
93+
TextWrapping="Wrap"
94+
Opacity="0.7"
95+
FontSize="12" />
96+
<Grid ColumnDefinitions="180,*">
97+
<TextBlock Grid.Column="0" Text="Preferred Language:" VerticalAlignment="Center" />
98+
<TextBlock Grid.Column="1" Text="{Binding PreferredLanguage}" FontWeight="Bold" VerticalAlignment="Center" />
99+
</Grid>
100+
</StackPanel>
101+
</GroupBox>
102+
103+
<GroupBox Header="Other Platform Settings">
104+
<StackPanel Spacing="10">
105+
<TextBlock Text="These settings are less commonly changed and are platform-specific."
106+
TextWrapping="Wrap"
107+
Opacity="0.7"
108+
FontSize="12" />
109+
110+
<Grid ColumnDefinitions="180,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto">
111+
<TextBlock Grid.Column="0" Grid.Row="0" Text="Hold Wait Duration:" VerticalAlignment="Center" />
112+
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding HoldWaitDuration}" VerticalAlignment="Center" />
113+
114+
<TextBlock Grid.Column="0" Grid.Row="1" Text="Tap Size (Touch):" VerticalAlignment="Center" Margin="0,5,0,0" />
115+
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding TapSizeTouch}" VerticalAlignment="Center" Margin="0,5,0,0" />
116+
117+
<TextBlock Grid.Column="0" Grid.Row="2" Text="Tap Size (Mouse):" VerticalAlignment="Center" Margin="0,5,0,0" />
118+
<TextBlock Grid.Column="1" Grid.Row="2" Text="{Binding TapSizeMouse}" VerticalAlignment="Center" Margin="0,5,0,0" />
119+
120+
<TextBlock Grid.Column="0" Grid.Row="3" Text="Double-Tap Size (Touch):" VerticalAlignment="Center" Margin="0,5,0,0" />
121+
<TextBlock Grid.Column="1" Grid.Row="3" Text="{Binding DoubleTapSizeTouch}" VerticalAlignment="Center" Margin="0,5,0,0" />
122+
123+
<TextBlock Grid.Column="0" Grid.Row="4" Text="Double-Tap Size (Mouse):" VerticalAlignment="Center" Margin="0,5,0,0" />
124+
<TextBlock Grid.Column="1" Grid.Row="4" Text="{Binding DoubleTapSizeMouse}" VerticalAlignment="Center" Margin="0,5,0,0" />
125+
126+
<TextBlock Grid.Column="0" Grid.Row="5" Text="Double-Tap Time (Touch):" VerticalAlignment="Center" Margin="0,5,0,0" />
127+
<TextBlock Grid.Column="1" Grid.Row="5" Text="{Binding DoubleTapTimeTouch}" VerticalAlignment="Center" Margin="0,5,0,0" />
128+
129+
<TextBlock Grid.Column="0" Grid.Row="6" Text="Double-Tap Time (Mouse):" VerticalAlignment="Center" Margin="0,5,0,0" />
130+
<TextBlock Grid.Column="1" Grid.Row="6" Text="{Binding DoubleTapTimeMouse}" VerticalAlignment="Center" Margin="0,5,0,0" />
131+
</Grid>
132+
</StackPanel>
133+
</GroupBox>
134+
135+
</StackPanel>
136+
</ContentPage>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Avalonia;
2+
using Avalonia.Controls;
3+
using Avalonia.VisualTree;
4+
using ControlCatalog.ViewModels;
5+
6+
namespace ControlCatalog.Pages
7+
{
8+
public partial class PlatformSettingsPage : ContentPage
9+
{
10+
private readonly PlatformSettingsViewModel _viewModel = new();
11+
12+
public PlatformSettingsPage()
13+
{
14+
InitializeComponent();
15+
DataContext = _viewModel;
16+
}
17+
18+
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
19+
{
20+
base.OnAttachedToVisualTree(e);
21+
_viewModel.Subscribe(this.GetPlatformSettings() ?? Application.Current?.PlatformSettings);
22+
}
23+
24+
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
25+
{
26+
_viewModel.Unsubscribe();
27+
base.OnDetachedFromVisualTree(e);
28+
}
29+
}
30+
}

samples/ControlCatalog/ViewModels/MainWindowViewModel_PageList.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ partial class MainWindowViewModel
7272
new PageItem("OpenGL Lease",() => new OpenGlLeasePage(), Icons.Cube3D),
7373
new PageItem("PipsPager",() => new PipsPagerPage(), Icons.HorizontalDots),
7474
new PageItem("Platform Information",() => new PlatformInfoPage(), Icons.Info),
75+
new PageItem("Platform Settings",() => new PlatformSettingsPage(), Icons.Tune),
7576
new PageItem("Pointers",() => new PointersPage(), Icons.Cursor),
7677
new PageItem("ProgressBar",() => new ProgressBarPage(), Icons.Progress),
7778
new PageItem("RadioButton",() => new RadioButtonPage(), Icons.Radio),
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using Avalonia.Input;
3+
using Avalonia.Media;
4+
using Avalonia.Platform;
5+
using MiniMvvm;
6+
7+
namespace ControlCatalog.ViewModels;
8+
9+
public class PlatformSettingsViewModel : ViewModelBase
10+
{
11+
private IPlatformSettings? _platformSettings;
12+
private PlatformColorValues? _colorValues;
13+
private string? _preferredLanguage;
14+
15+
public void Subscribe(IPlatformSettings? platformSettings)
16+
{
17+
_platformSettings = platformSettings;
18+
19+
if (platformSettings is not null)
20+
{
21+
OnColorValuesChanged(platformSettings, platformSettings.GetColorValues());
22+
OnPreferredLanguageChanged(platformSettings, EventArgs.Empty);
23+
24+
platformSettings?.ColorValuesChanged += OnColorValuesChanged;
25+
platformSettings?.PreferredApplicationLanguageChanged += OnPreferredLanguageChanged;
26+
}
27+
}
28+
29+
public void Unsubscribe()
30+
{
31+
if (_platformSettings != null)
32+
{
33+
_platformSettings.ColorValuesChanged -= OnColorValuesChanged;
34+
_platformSettings.PreferredApplicationLanguageChanged -= OnPreferredLanguageChanged;
35+
_platformSettings = null;
36+
}
37+
}
38+
39+
private void OnColorValuesChanged(object? sender, PlatformColorValues e)
40+
{
41+
_colorValues = e;
42+
RaisePropertyChanged(nameof(ThemeVariant));
43+
RaisePropertyChanged(nameof(ContrastPreference));
44+
RaisePropertyChanged(nameof(AccentColor1));
45+
RaisePropertyChanged(nameof(AccentColor2));
46+
RaisePropertyChanged(nameof(AccentColor3));
47+
}
48+
49+
private void OnPreferredLanguageChanged(object? sender, EventArgs e)
50+
{
51+
_preferredLanguage = ((IPlatformSettings)sender!).PreferredApplicationLanguage;
52+
RaisePropertyChanged(nameof(PreferredLanguage));
53+
}
54+
55+
public string PreferredLanguage => _preferredLanguage ?? "Not available";
56+
57+
public string ThemeVariant => _colorValues?.ThemeVariant.ToString() ?? "Not available";
58+
59+
public string ContrastPreference => _colorValues?.ContrastPreference.ToString() ?? "Not available";
60+
61+
public Color AccentColor1 => _colorValues?.AccentColor1 ?? Colors.Gray;
62+
63+
public Color AccentColor2 => _colorValues?.AccentColor2 ?? Colors.Gray;
64+
65+
public Color AccentColor3 => _colorValues?.AccentColor3 ?? Colors.Gray;
66+
67+
public string HoldWaitDuration => _platformSettings?.HoldWaitDuration.ToString() ?? "Not available";
68+
69+
public string TapSizeTouch => _platformSettings?.GetTapSize(PointerType.Touch).ToString() ?? "Not available";
70+
71+
public string TapSizeMouse => _platformSettings?.GetTapSize(PointerType.Mouse).ToString() ?? "Not available";
72+
73+
public string DoubleTapSizeTouch => _platformSettings?.GetDoubleTapSize(PointerType.Touch).ToString() ?? "Not available";
74+
75+
public string DoubleTapSizeMouse => _platformSettings?.GetDoubleTapSize(PointerType.Mouse).ToString() ?? "Not available";
76+
77+
public string DoubleTapTimeTouch => _platformSettings?.GetDoubleTapTime(PointerType.Touch).ToString() ?? "Not available";
78+
79+
public string DoubleTapTimeMouse => _platformSettings?.GetDoubleTapTime(PointerType.Mouse).ToString() ?? "Not available";
80+
}

0 commit comments

Comments
 (0)