Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add locale switch helper method #548

Merged
merged 3 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 53 additions & 31 deletions src/Ursa.Themes.Semi/Index.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
Expand All @@ -7,69 +8,90 @@
namespace Ursa.Themes.Semi;

/// <summary>
/// Notice: Don't set Locale if your app is in InvariantGlobalization mode.
/// Notice: Don't set Locale if your app is in InvariantGlobalization mode.
/// </summary>
public class SemiTheme: Styles
public class SemiTheme : Styles
{
public static ThemeVariant Aquatic => new ThemeVariant(nameof(Aquatic), ThemeVariant.Dark);
public static ThemeVariant Desert => new ThemeVariant(nameof(Desert), ThemeVariant.Light);
public static ThemeVariant Dusk => new ThemeVariant(nameof(Dusk), ThemeVariant.Dark);
public static ThemeVariant NightSky => new ThemeVariant(nameof(NightSky), ThemeVariant.Dark);

private static readonly Lazy<Dictionary<CultureInfo, ResourceDictionary>> _localeToResource = new Lazy<Dictionary<CultureInfo, ResourceDictionary>>(
() => new Dictionary<CultureInfo, ResourceDictionary>
{
{ new CultureInfo("zh-CN"), new zh_cn() },
{ new CultureInfo("en-US"), new en_us() },
});

private static readonly Dictionary<CultureInfo, ResourceDictionary> _localeToResource = new()
{
{ new CultureInfo("zh-CN"), new zh_cn() },
{ new CultureInfo("en-US"), new en_us() }
};

private static readonly ResourceDictionary _defaultResource = new zh_cn();

private readonly IServiceProvider? _sp;

private CultureInfo? _locale;

public SemiTheme(IServiceProvider? provider = null)
{
_sp = provider;
AvaloniaXamlLoader.Load(provider, this);
}

private CultureInfo? _locale;
public static ThemeVariant Aquatic => new(nameof(Aquatic), ThemeVariant.Dark);
public static ThemeVariant Desert => new(nameof(Desert), ThemeVariant.Light);
public static ThemeVariant Dusk => new(nameof(Dusk), ThemeVariant.Dark);
public static ThemeVariant NightSky => new(nameof(NightSky), ThemeVariant.Dark);

public CultureInfo? Locale
{
get => _locale;
set
{
try
{
_locale = value;
var resource = TryGetLocaleResource(value);
if (resource is null) return;
foreach (var kv in resource)
if (TryGetLocaleResource(value, out var resource) && resource is not null)
{
_locale = value;
foreach (var kv in resource) Resources[kv.Key] = kv.Value;
}
else
{
this.Resources.Add(kv);
_locale = new CultureInfo("zh-CN");
foreach (var kv in _defaultResource) Resources[kv.Key] = kv.Value;
}
}
catch
{
_locale = CultureInfo.InvariantCulture;
}

}
}
private static ResourceDictionary? TryGetLocaleResource(CultureInfo? locale)

private static bool TryGetLocaleResource(CultureInfo? locale, out ResourceDictionary? resourceDictionary)
{
if (Equals(locale, CultureInfo.InvariantCulture))
{
return _defaultResource;
resourceDictionary = _defaultResource;
return true;
}

if (locale is null)
{
return _localeToResource.Value[new CultureInfo("zh-CN")];
resourceDictionary = _defaultResource;
return false;
}
if (_localeToResource.Value.TryGetValue(locale, out var resource))

if (_localeToResource.TryGetValue(locale, out var resource))
{
return resource;
resourceDictionary = resource;
return true;
}
return _localeToResource.Value[new CultureInfo("zh-CN")];

resourceDictionary = _defaultResource;
return false;
}

public static void OverrideLocaleResources(Application application, CultureInfo? culture)
{
if (culture is null) return;
if (!_localeToResource.TryGetValue(culture, out var resources)) return;
foreach (var kv in resources) application.Resources[kv.Key] = kv.Value;
}

public static void OverrideLocaleResources(StyledElement element, CultureInfo? culture)
{
if (culture is null) return;
if (!_localeToResource.TryGetValue(culture, out var resources)) return;
foreach (var kv in resources) element.Resources[kv.Key] = kv.Value;
}
}
101 changes: 101 additions & 0 deletions tests/HeadlessTest.Ursa/Semi/LocalizationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System.Globalization;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless.XUnit;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Semi.Avalonia.Locale;
using Ursa.Controls;
using Ursa.Themes.Semi;

namespace HeadlessTest.Ursa.Semi;

public class LocalizationTest
{
[AvaloniaFact]
public void Default_Locale_Is_Chinese()
{
var window = new UrsaWindow();
window.Show();
MessageBox.ShowOverlayAsync("Hello World", button: MessageBoxButton.YesNo, toplevelHashCode: window.GetHashCode());
Task.Delay(100).Wait();
Dispatcher.UIThread.RunJobs();
var dialog = window.GetVisualDescendants().OfType<MessageBoxControl>().SingleOrDefault();
var yesButton = dialog?.GetVisualDescendants().OfType<Button>().FirstOrDefault(b => b.Name == "PART_YesButton");
Assert.Equal("是", yesButton?.Content?.ToString());
}

[AvaloniaFact]
public void Set_English_Works()
{
var window = new UrsaWindow();
window.Show();
MessageBox.ShowOverlayAsync("Hello World", button: MessageBoxButton.YesNo, toplevelHashCode: window.GetHashCode());
Task.Delay(100).Wait();
Dispatcher.UIThread.RunJobs();
var dialog = window.GetVisualDescendants().OfType<MessageBoxControl>().SingleOrDefault();
var yesButton = dialog?.GetVisualDescendants().OfType<Button>().FirstOrDefault(b => b.Name == "PART_YesButton");
Assert.Equal("是", yesButton?.Content?.ToString());
Assert.NotNull(Application.Current);
SemiTheme.OverrideLocaleResources(Application.Current, new CultureInfo("en-US"));
Assert.Equal("Yes", yesButton?.Content?.ToString());
}

[AvaloniaFact]
public void Set_NonExisting_Culture_Does_Nothing()
{
var window = new UrsaWindow();
window.Show();
MessageBox.ShowOverlayAsync("Hello World", button: MessageBoxButton.YesNo, toplevelHashCode: window.GetHashCode());
Task.Delay(100).Wait();
Dispatcher.UIThread.RunJobs();
var dialog = window.GetVisualDescendants().OfType<MessageBoxControl>().SingleOrDefault();
var yesButton = dialog?.GetVisualDescendants().OfType<Button>().FirstOrDefault(b => b.Name == "PART_YesButton");
Assert.Equal("是", yesButton?.Content?.ToString());
Assert.NotNull(Application.Current);
// We expect there won't be anyone adding Armenian localization... Subject to change.
SemiTheme.OverrideLocaleResources(Application.Current, new CultureInfo("hy-AM"));
Assert.Equal("是", yesButton?.Content?.ToString());
SemiTheme.OverrideLocaleResources(Application.Current, null);
Assert.Equal("是", yesButton?.Content?.ToString());
}

[AvaloniaFact]
public void Set_English_To_Control_Works()
{
var window = new UrsaWindow();
window.Show();
MessageBox.ShowOverlayAsync("Hello World", button: MessageBoxButton.YesNo, toplevelHashCode: window.GetHashCode());
Task.Delay(100).Wait();
Dispatcher.UIThread.RunJobs();
var dialog = window.GetVisualDescendants().OfType<MessageBoxControl>().SingleOrDefault();
var yesButton = dialog?.GetVisualDescendants().OfType<Button>().FirstOrDefault(b => b.Name == "PART_YesButton");
Assert.Equal("是", yesButton?.Content?.ToString());
Assert.NotNull(Application.Current);
SemiTheme.OverrideLocaleResources(window, new CultureInfo("en-US"));
Assert.Equal("Yes", yesButton?.Content?.ToString());
}

[AvaloniaFact]
public void SemiTheme_Localization_Behavior()
{
var theme = new SemiTheme();
Assert.Null(theme.Locale);
theme.Locale = new CultureInfo("en-US");
Assert.Equal(new CultureInfo("en-US"), theme.Locale);
var yesText = theme.Resources["STRING_MENU_DIALOG_YES"];
Assert.Equal("Yes", yesText);
theme.Locale = new CultureInfo("zh-CN");
Assert.Equal(new CultureInfo("zh-CN"), theme.Locale);
yesText = theme.Resources["STRING_MENU_DIALOG_YES"];
Assert.Equal("是", yesText);
theme.Locale = new CultureInfo("hy-AM");
Assert.Equal(new CultureInfo("zh-CN"), theme.Locale);
yesText = theme.Resources["STRING_MENU_DIALOG_YES"];
Assert.Equal("是", yesText);
theme.Locale = null;
Assert.Equal(new CultureInfo("zh-CN"), theme.Locale);
yesText = theme.Resources["STRING_MENU_DIALOG_YES"];
Assert.Equal("是", yesText);
}
}
Loading