Merge pull request #548 from irihitech/localization
Add locale switch helper method
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
@@ -7,32 +8,30 @@ using Ursa.Themes.Semi.Locale;
|
|||||||
namespace Ursa.Themes.Semi;
|
namespace Ursa.Themes.Semi;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class SemiTheme: Styles
|
public class SemiTheme : Styles
|
||||||
{
|
{
|
||||||
public static ThemeVariant Aquatic => new ThemeVariant(nameof(Aquatic), ThemeVariant.Dark);
|
private static readonly Dictionary<CultureInfo, ResourceDictionary> _localeToResource = new()
|
||||||
public static ThemeVariant Desert => new ThemeVariant(nameof(Desert), ThemeVariant.Light);
|
{
|
||||||
public static ThemeVariant Dusk => new ThemeVariant(nameof(Dusk), ThemeVariant.Dark);
|
{ new CultureInfo("zh-CN"), new zh_cn() },
|
||||||
public static ThemeVariant NightSky => new ThemeVariant(nameof(NightSky), ThemeVariant.Dark);
|
{ new CultureInfo("en-US"), new en_us() }
|
||||||
|
};
|
||||||
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 ResourceDictionary _defaultResource = new zh_cn();
|
private static readonly ResourceDictionary _defaultResource = new zh_cn();
|
||||||
|
|
||||||
private readonly IServiceProvider? _sp;
|
private CultureInfo? _locale;
|
||||||
|
|
||||||
public SemiTheme(IServiceProvider? provider = null)
|
public SemiTheme(IServiceProvider? provider = null)
|
||||||
{
|
{
|
||||||
_sp = provider;
|
|
||||||
AvaloniaXamlLoader.Load(provider, this);
|
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
|
public CultureInfo? Locale
|
||||||
{
|
{
|
||||||
get => _locale;
|
get => _locale;
|
||||||
@@ -40,36 +39,59 @@ public class SemiTheme: Styles
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_locale = value;
|
if (TryGetLocaleResource(value, out var resource) && resource is not null)
|
||||||
var resource = TryGetLocaleResource(value);
|
|
||||||
if (resource is null) return;
|
|
||||||
foreach (var kv in resource)
|
|
||||||
{
|
{
|
||||||
this.Resources.Add(kv);
|
_locale = value;
|
||||||
|
foreach (var kv in resource) Resources[kv.Key] = kv.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_locale = new CultureInfo("zh-CN");
|
||||||
|
foreach (var kv in _defaultResource) Resources[kv.Key] = kv.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
_locale = CultureInfo.InvariantCulture;
|
_locale = CultureInfo.InvariantCulture;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ResourceDictionary? TryGetLocaleResource(CultureInfo? locale)
|
private static bool TryGetLocaleResource(CultureInfo? locale, out ResourceDictionary? resourceDictionary)
|
||||||
{
|
{
|
||||||
if (Equals(locale, CultureInfo.InvariantCulture))
|
if (Equals(locale, CultureInfo.InvariantCulture))
|
||||||
{
|
{
|
||||||
return _defaultResource;
|
resourceDictionary = _defaultResource;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (locale is null)
|
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
tests/HeadlessTest.Ursa/Semi/LocalizationTest.cs
Normal file
101
tests/HeadlessTest.Ursa/Semi/LocalizationTest.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user