Skip to content

Commit

Permalink
Merge pull request #548 from irihitech/localization
Browse files Browse the repository at this point in the history
Add locale switch helper method
  • Loading branch information
zdpcdt authored Jan 19, 2025
2 parents 1fafcaa + 2a516e4 commit 2822df9
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 31 deletions.
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);
}
}

0 comments on commit 2822df9

Please sign in to comment.