using System; using System.IO; using System.Linq; using System.Text.RegularExpressions; using NUnit.Framework; using UnityEditor; using UnityEngine; namespace Ironspore.Tests { public sealed class LocalizationTests { LocalizationCatalog catalog; bool hadPreference; string preference; [SetUp] public void Setup() { hadPreference = PlayerPrefs.HasKey(GameLocalization.PreferenceKey); preference = PlayerPrefs.GetString(GameLocalization.PreferenceKey, ""); catalog = AssetDatabase.LoadAssetAtPath("Assets/Game/Localization/LocalizationCatalog.asset"); Assert.IsNotNull(catalog, "Run Ironspore/Configure Localization first."); GameLocalization.Initialize(catalog); } [TearDown] public void Cleanup() { if (hadPreference) PlayerPrefs.SetString(GameLocalization.PreferenceKey, preference); else PlayerPrefs.DeleteKey(GameLocalization.PreferenceKey); if (catalog) GameLocalization.Initialize(catalog); PlayerPrefs.Save(); } [TestCase("en", "zh-CN", "en")] [TestCase("zh-Hans", "en-US", "zh-Hans")] [TestCase("", "zh-TW", "zh-Hans")] [TestCase("", "en-GB", "en")] [TestCase("obsolete", "zh-HK", "zh-Hans")] [TestCase("", "ja-JP", "en")] [TestCase(null, null, "en")] public void SavedSystemAndFallbackPriority(string saved, string system, string expected) { Assert.AreEqual(expected, GameLocalization.ResolveInitialLocale(saved, system)); } [Test] public void AdditionalLocalesCanUseTheSameSelector() { Assert.AreEqual("fr", GameLocalization.ResolveInitialLocale("fr", "en-US", new[] { "en", "zh-Hans", "fr" })); Assert.AreEqual("fr", GameLocalization.ResolveInitialLocale("", "fr-CA", new[] { "en", "fr" })); } [Test] public void EveryKeyAndFormatArgumentMatchesAndRequiredGlyphsExist() { var english = GameLocalization.ReadTable(catalog.locales.Single(x => x.code == "en").table); foreach (var locale in catalog.locales) { var translated = GameLocalization.ReadTable(locale.table); CollectionAssert.AreEquivalent(english.Keys, translated.Keys, locale.code); foreach (var entry in translated) { var pattern = @"\{(\d+)(?:[^}]*)\}"; string[] expected = Regex.Matches(english[entry.Key], pattern).Cast().Select(x => x.Groups[1].Value).ToArray(); string[] actual = Regex.Matches(entry.Value, pattern).Cast().Select(x => x.Groups[1].Value).ToArray(); CollectionAssert.AreEquivalent(expected, actual, locale.code + ":" + entry.Key); int count = expected.Length == 0 ? 0 : expected.Select(int.Parse).Max() + 1; Assert.DoesNotThrow(() => string.Format(entry.Value, Enumerable.Repeat("sample", count).ToArray()), entry.Key); foreach (char character in entry.Value.Where(c => !char.IsControl(c))) // Dynamic atlases are cleared on build; verify that the source font can supply each glyph. Assert.IsTrue(catalog.uiFont.HasCharacter(character, false, true), "Missing glyph: " + character + " in " + entry.Key); } } } [Test] public void SelectionPersistsAndInvalidSelectionDoesNotChangePreference() { Assert.IsTrue(GameLocalization.SelectLocale("zh-Hans")); GameLocalization.Initialize(catalog); Assert.AreEqual("zh-Hans", GameLocalization.CurrentCode); Assert.IsFalse(GameLocalization.SelectLocale("unavailable")); Assert.AreEqual("zh-Hans", PlayerPrefs.GetString(GameLocalization.PreferenceKey)); Assert.IsTrue(GameLocalization.SelectLocale("en")); GameLocalization.Initialize(catalog); Assert.AreEqual("en", GameLocalization.CurrentCode); } [Test] public void ExistingMessagesAndNamesReformatWithoutMutatingSaveOrIdentifiers() { var state = new GameSave(); state.run.cargo.Add(new CargoStack("blueprint_coil_capacitor", 1)); string before = JsonUtility.ToJson(state); var notice = GameLocalization.Message("notice.recovered", GameLocalization.Message("item.blueprint_coil_capacitor"), 3); GameLocalization.SelectLocale("en", false); StringAssert.Contains("COIL CAPACITOR", notice.ToString()); GameLocalization.SelectLocale("zh-Hans", false); StringAssert.Contains("电磁电容", notice.ToString()); StringAssert.Contains("×3", notice.ToString()); StringAssert.Contains("电磁电容", state.run.cargo[0].Name); Assert.AreEqual(before, JsonUtility.ToJson(state)); Assert.AreEqual(GameSave.CurrentVersion, state.version); } [Test] public void MissingTranslationUsesEnglishFallback() { var temporary = ScriptableObject.CreateInstance(); var english = new UnityEngine.TextAsset("{\"entries\":[{\"key\":\"test.fallback\",\"value\":\"Readable English {0}\"}]}"); var chinese = new UnityEngine.TextAsset("{\"entries\":[]}"); try { temporary.locales = new[] { new GameLocale { code = "en", table = english }, new GameLocale { code = "zh-Hans", table = chinese } }; GameLocalization.Initialize(temporary); GameLocalization.SelectLocale("zh-Hans", false); Assert.AreEqual("Readable English 7", GameLocalization.Text("test.fallback", 7)); } finally { UnityEngine.Object.DestroyImmediate(temporary); UnityEngine.Object.DestroyImmediate(english); UnityEngine.Object.DestroyImmediate(chinese); } } [Test] public void DuplicateKeysAreRejected() { var table = new UnityEngine.TextAsset("{\"entries\":[{\"key\":\"same\",\"value\":\"one\"},{\"key\":\"same\",\"value\":\"two\"}]}"); try { Assert.Throws(() => GameLocalization.ReadTable(table)); } finally { UnityEngine.Object.DestroyImmediate(table); } } } }