mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 22:49:53 +08:00
199 lines
6.5 KiB
C#
199 lines
6.5 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using Avalonia.Controls;
|
|
using Avalonia.Data;
|
|
using SharpEmu.GUI;
|
|
using System.Text.Json;
|
|
using Xunit;
|
|
|
|
namespace SharpEmu.Libs.Tests.GUI;
|
|
|
|
public sealed class LocalizationTests : IDisposable
|
|
{
|
|
private readonly string _languagesDirectory = Path.Combine(
|
|
Path.GetTempPath(),
|
|
$"sharpemu-localization-{Guid.NewGuid():N}");
|
|
|
|
public LocalizationTests()
|
|
{
|
|
Directory.CreateDirectory(_languagesDirectory);
|
|
}
|
|
|
|
[Fact]
|
|
public void Load_RaisesNotificationsForCurrentCodeAndIndexer()
|
|
{
|
|
var localization = new Localization(_languagesDirectory);
|
|
var changedProperties = new List<string?>();
|
|
localization.PropertyChanged += (_, args) =>
|
|
changedProperties.Add(args.PropertyName);
|
|
|
|
localization.Load("ru");
|
|
|
|
Assert.Equal("ru", localization.CurrentCode);
|
|
Assert.Contains(nameof(Localization.CurrentCode), changedProperties);
|
|
Assert.Contains("Item", changedProperties);
|
|
}
|
|
|
|
[Fact]
|
|
public void IndexerBinding_UpdatesWhenLanguageChanges()
|
|
{
|
|
var localization = new Localization(_languagesDirectory);
|
|
localization.Load("en");
|
|
var englishLibraryLabel = localization.Get("Page.Library");
|
|
localization.Load("ru");
|
|
var russianLibraryLabel = localization.Get("Page.Library");
|
|
Assert.NotEqual(englishLibraryLabel, russianLibraryLabel);
|
|
|
|
localization.Load("en");
|
|
var text = new TextBlock();
|
|
text.Bind(
|
|
TextBlock.TextProperty,
|
|
new Binding("[Page.Library]") { Source = localization });
|
|
|
|
Assert.Equal(englishLibraryLabel, text.Text);
|
|
|
|
localization.Load("ru");
|
|
|
|
Assert.Equal(russianLibraryLabel, text.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void LocalizedChoice_UpdatesLabelWithoutReplacingStableValue()
|
|
{
|
|
var localization = new Localization(_languagesDirectory);
|
|
var choice = LocalizedChoice.FromKey(
|
|
"Native",
|
|
"Options.CpuEngine.Native");
|
|
var changedProperties = new List<string?>();
|
|
choice.PropertyChanged += (_, args) =>
|
|
changedProperties.Add(args.PropertyName);
|
|
|
|
localization.Load("en");
|
|
choice.Refresh(localization);
|
|
var englishLabel = choice.Label;
|
|
|
|
localization.Load("ru");
|
|
choice.Refresh(localization);
|
|
|
|
Assert.Equal("Native", choice.Value);
|
|
Assert.NotEqual(englishLabel, choice.Label);
|
|
Assert.Equal(localization.Get("Options.CpuEngine.Native"), choice.Label);
|
|
Assert.Contains(nameof(LocalizedChoice.Label), changedProperties);
|
|
}
|
|
|
|
[Fact]
|
|
public void ComboBoxSelection_KeepsLiveLocalizedChoiceAsSelectionBoxItem()
|
|
{
|
|
var localization = new Localization(_languagesDirectory);
|
|
var choice = LocalizedChoice.FromKey(
|
|
"Native",
|
|
"Options.CpuEngine.Native");
|
|
localization.Load("en");
|
|
choice.Refresh(localization);
|
|
var comboBox = new ComboBox
|
|
{
|
|
ItemsSource = new[] { choice },
|
|
SelectedIndex = 0,
|
|
};
|
|
|
|
Assert.Same(choice, comboBox.SelectionBoxItem);
|
|
|
|
localization.Load("ru");
|
|
choice.Refresh(localization);
|
|
|
|
Assert.Same(choice, comboBox.SelectionBoxItem);
|
|
Assert.Equal(localization.Get("Options.CpuEngine.Native"), choice.Label);
|
|
}
|
|
|
|
[Fact]
|
|
public void EmbeddedLanguages_ContainEveryEnglishOptionsKey()
|
|
{
|
|
var assembly = typeof(Localization).Assembly;
|
|
var resourceNames = assembly.GetManifestResourceNames()
|
|
.Where(name =>
|
|
name.StartsWith("Languages.", StringComparison.Ordinal) &&
|
|
name.EndsWith(".json", StringComparison.Ordinal))
|
|
.Order()
|
|
.ToArray();
|
|
var english = ReadEmbeddedLanguage(assembly, "Languages.en.json");
|
|
var optionKeys = english.Keys
|
|
.Where(key => key.StartsWith("Options.", StringComparison.Ordinal))
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
|
|
foreach (var resourceName in resourceNames)
|
|
{
|
|
var language = ReadEmbeddedLanguage(assembly, resourceName);
|
|
var missing = optionKeys
|
|
.Where(key => !language.ContainsKey(key))
|
|
.Order()
|
|
.ToArray();
|
|
var empty = optionKeys
|
|
.Where(key =>
|
|
language.TryGetValue(key, out var value) &&
|
|
string.IsNullOrWhiteSpace(value))
|
|
.Order()
|
|
.ToArray();
|
|
|
|
Assert.True(
|
|
missing.Length == 0,
|
|
$"{resourceName} is missing option keys: {string.Join(", ", missing)}");
|
|
Assert.True(
|
|
empty.Length == 0,
|
|
$"{resourceName} has empty option values: {string.Join(", ", empty)}");
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("en")]
|
|
[InlineData("ru")]
|
|
public void LooseLanguageFile_OverridesEmbeddedValuesWithoutReplacingThem(
|
|
string languageCode)
|
|
{
|
|
var localization = new Localization(_languagesDirectory);
|
|
localization.Load(languageCode);
|
|
var embeddedOptionsLabel = localization.Get("Page.Options");
|
|
File.WriteAllText(
|
|
Path.Combine(_languagesDirectory, $"{languageCode}.json"),
|
|
$$"""
|
|
{
|
|
"_languageName": "Test {{languageCode}}",
|
|
"Page.Library": "Custom library"
|
|
}
|
|
""");
|
|
|
|
localization.Load(languageCode);
|
|
|
|
Assert.Equal("Custom library", localization.Get("Page.Library"));
|
|
Assert.Equal(embeddedOptionsLabel, localization.Get("Page.Options"));
|
|
}
|
|
|
|
[Fact]
|
|
public void MissingLanguage_FallsBackToEnglish()
|
|
{
|
|
var localization = new Localization(_languagesDirectory);
|
|
localization.Load("en");
|
|
var englishLibraryLabel = localization.Get("Page.Library");
|
|
|
|
localization.Load("missing");
|
|
|
|
Assert.Equal("missing", localization.CurrentCode);
|
|
Assert.Equal(englishLibraryLabel, localization.Get("Page.Library"));
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Directory.Delete(_languagesDirectory, recursive: true);
|
|
}
|
|
|
|
private static Dictionary<string, string> ReadEmbeddedLanguage(
|
|
System.Reflection.Assembly assembly,
|
|
string resourceName)
|
|
{
|
|
using var stream = assembly.GetManifestResourceStream(resourceName);
|
|
Assert.NotNull(stream);
|
|
return JsonSerializer.Deserialize<Dictionary<string, string>>(stream)
|
|
?? throw new InvalidDataException($"Could not parse {resourceName}.");
|
|
}
|
|
}
|