diff --git a/PG.Commons/PG.Commons/PetroglyphCommons.cs b/PG.Commons/PG.Commons/PetroglyphCommons.cs index 40cea4e9e..ebfd1c91f 100644 --- a/PG.Commons/PG.Commons/PetroglyphCommons.cs +++ b/PG.Commons/PG.Commons/PetroglyphCommons.cs @@ -3,6 +3,7 @@ using AnakinRaW.CommonUtilities.Hashing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using PG.Commons.Hashing; namespace PG.Commons; @@ -10,7 +11,7 @@ namespace PG.Commons; /// /// Provides initialization routines for this library. /// -public static class PetroglyphCommons +public static class PetroglyphCommons { /// /// Adds all necessary services provided by this library to the specified . @@ -18,7 +19,8 @@ public static class PetroglyphCommons /// The to add services to. public static void ContributeServices(IServiceCollection serviceCollection) { - serviceCollection.AddSingleton(new Crc32HashingProvider()); - serviceCollection.AddSingleton(sp => new Crc32HashingService(sp)); + serviceCollection.TryAddSingleton(new Crc32HashingProvider()); + serviceCollection.TryAddSingleton(sp => new HashingService(sp)); + serviceCollection.TryAddSingleton(sp => new Crc32HashingService(sp)); } -} \ No newline at end of file +} diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/DatServiceContribution.cs b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/DatServiceContribution.cs index 7dd1ad6a9..67a8b8a25 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/DatServiceContribution.cs +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/DatServiceContribution.cs @@ -1,9 +1,13 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using AnakinRaW.CommonUtilities.Hashing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using PG.Commons; using PG.StarWarsGame.Files.DAT.Binary; using PG.StarWarsGame.Files.DAT.Services; + namespace PG.StarWarsGame.Files.DAT; /// @@ -18,10 +22,13 @@ public static class DatServiceContribution /// The to add services to. public static void SupportDAT(this IServiceCollection serviceCollection) { + PetroglyphCommons.ContributeServices(serviceCollection); + serviceCollection.TryAddSingleton(sp => new HashingService(sp)); + serviceCollection .AddSingleton(sp => new DatFileService(sp)) .AddSingleton(sp => new DatModelService(sp)) .AddTransient(sp => new DatFileReader(sp)) .AddTransient(sp => new DatBinaryConverter(sp)); } -} \ No newline at end of file +} diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/BaselineTranslationProviderTest.cs b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/BaselineTranslationProviderTest.cs new file mode 100644 index 000000000..cab76e729 --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/BaselineTranslationProviderTest.cs @@ -0,0 +1,68 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Localisation.Languages; +using PG.Testing; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Baseline.Test; + +public class BaselineTranslationProviderTest : PGTestBase +{ + protected override void SetupServices(IServiceCollection serviceCollection) + { + base.SetupServices(serviceCollection); + serviceCollection.SupportLocalisationBaseline(); + } + + private IBaselineTranslationProvider Provider => + ServiceProvider.GetRequiredService(); + + [Theory] + [InlineData(GameContext.EaW)] + [InlineData(GameContext.FoC)] + public void GetMasterText_SingleLanguage_IsNonEmpty(GameContext game) + { + var lang = new EnglishAlamoLanguageDefinition(); + var db = Provider.GetMasterText(game, lang); + Assert.NotEmpty(db); + } + + [Theory] + [InlineData(GameContext.EaW)] + [InlineData(GameContext.FoC)] + public void GetCreditsText_SingleLanguage_IsNonEmpty(GameContext game) + { + var lang = new EnglishAlamoLanguageDefinition(); + var db = Provider.GetCreditsText(game, lang); + Assert.NotEmpty(db); + } + + [Fact] + public void GetMasterText_MultiLanguage_ContainsAllRequestedLanguages() + { + var langs = new IAlamoLanguageDefinition[] + { + new EnglishAlamoLanguageDefinition(), + new GermanAlamoLanguageDefinition(), + }; + var db = Provider.GetMasterText(GameContext.EaW, langs); + Assert.Equal(2, db.Languages.Count); + } + + [Fact] + public void GetMasterText_EaW_English_ContainsKnownKey() + { + var lang = new EnglishAlamoLanguageDefinition(); + var db = Provider.GetMasterText(GameContext.EaW, lang); + Assert.True(db.Count > 100); + } + + [Fact] + public void GetMasterText_NullLanguage_Throws() + { + Assert.Throws(() => Provider.GetMasterText(GameContext.EaW, (IAlamoLanguageDefinition)null!)); + } +} diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/PG.StarWarsGame.Localisation.Baseline.Test.csproj b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/PG.StarWarsGame.Localisation.Baseline.Test.csproj new file mode 100644 index 000000000..57c5afbaf --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/PG.StarWarsGame.Localisation.Baseline.Test.csproj @@ -0,0 +1,34 @@ + + + net8.0;net10.0 + $(TargetFrameworks);net481 + + + false + true + Exe + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineServiceContribution.cs b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineServiceContribution.cs new file mode 100644 index 000000000..43243cd3c --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineServiceContribution.cs @@ -0,0 +1,28 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Files.DAT; + +namespace PG.StarWarsGame.Localisation.Baseline +{ + /// + /// Provides initialization routines for the PG.StarWarsGame.Localisation.Baseline library. + /// + public static class BaselineServiceContribution + { + /// + /// Adds all services required by this library, including DAT and localisation core services. + /// + public static IServiceCollection SupportLocalisationBaseline(this IServiceCollection serviceCollection) + { + serviceCollection.SupportDAT(); + serviceCollection.SupportLocalisation(); + + serviceCollection.AddSingleton(sp => + new BaselineTranslationProvider(sp)); + + return serviceCollection; + } + } +} diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineTranslationProvider.cs b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineTranslationProvider.cs new file mode 100644 index 000000000..46420396e --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineTranslationProvider.cs @@ -0,0 +1,126 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Services; +using PG.StarWarsGame.Files.DAT.Services; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.IO.Dat; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Baseline +{ + internal sealed class BaselineTranslationProvider : ServiceBase, IBaselineTranslationProvider + { + // The 5 western languages shipped with the original games have embedded baseline data. + // The remaining officially supported languages are mapped for future resource additions. + private static readonly IReadOnlyDictionary LanguageMap = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["ENGLISH"] = ("en", "english"), + ["GERMAN"] = ("de", "german"), + ["SPANISH"] = ("es", "spanish"), + ["FRENCH"] = ("fr", "french"), + ["ITALIAN"] = ("it", "italian"), + ["CHINESE"] = ("zh", "chinese"), + ["POLISH"] = ("pl", "polish"), + ["RUSSIAN"] = ("ru", "russian"), + ["JAPANESE"] = ("ja", "japanese"), + ["KOREAN"] = ("ko", "korean"), + ["THAI"] = ("th", "thai"), + }; + + private readonly IDatFileService _datFileService; + private readonly IDatTranslationImporter _importer; + private readonly ITranslationDatabaseFactory _factory; + + public BaselineTranslationProvider(IServiceProvider services) : base(services) + { + _datFileService = services.GetRequiredService(); + _importer = services.GetRequiredService(); + _factory = services.GetRequiredService(); + } + + /// + public IKeyedTranslationDatabase GetMasterText(GameContext game, IAlamoLanguageDefinition language) + { + if (language is null) throw new ArgumentNullException(nameof(language)); + var db = _factory.CreateKeyed(new[] { language }); + LoadInto(db, game, "mastertextfile", language); + return db; + } + + /// + public IKeyedTranslationDatabase GetMasterText(GameContext game, IReadOnlyList languages) + { + if (languages is null) throw new ArgumentNullException(nameof(languages)); + var db = _factory.CreateKeyed(languages); + foreach (var lang in languages) + LoadInto(db, game, "mastertextfile", lang); + return db; + } + + /// + public IOrderedTranslationDatabase GetCreditsText(GameContext game, IAlamoLanguageDefinition language) + { + if (language is null) throw new ArgumentNullException(nameof(language)); + var db = _factory.CreateOrdered(new[] { language }); + LoadInto(db, game, "creditstext", language); + return db; + } + + /// + public IOrderedTranslationDatabase GetCreditsText(GameContext game, IReadOnlyList languages) + { + if (languages is null) throw new ArgumentNullException(nameof(languages)); + var db = _factory.CreateOrdered(languages); + foreach (var lang in languages) + LoadInto(db, game, "creditstext", lang); + return db; + } + + private void LoadInto(ITranslationDatabase db, GameContext game, string filePrefix, IAlamoLanguageDefinition language) + { + if (!LanguageMap.TryGetValue(language.LanguageIdentifier, out var map)) + return; + + var resourceName = BuildResourceName(game, map.Folder, $"{filePrefix}_{map.NameSuffix}.dat"); + using var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); + if (resourceStream is null) return; + + var tempPath = FileSystem.Path.Combine( + FileSystem.Path.GetTempPath(), + $"pg_baseline_{Guid.NewGuid():N}.dat"); + + try + { + using (var fs = FileSystem.FileStream.New(tempPath, FileMode.Create, FileAccess.Write)) + resourceStream.CopyTo(fs); + + var datFile = _datFileService.Load(tempPath); + _importer.Import(datFile.Content, language, db); + } + finally + { + try + { + FileSystem.File.Delete(tempPath); + } + catch + { + // NOP + } + } + } + + private static string BuildResourceName(GameContext game, string langFolder, string fileName) + { + var gameFolder = game == GameContext.EaW ? "EaW" : "FoC"; + return $"PG.StarWarsGame.Localisation.Baseline.Resources.{gameFolder}.{langFolder}.{fileName}"; + } + } +} diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/IBaselineTranslationProvider.cs b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/IBaselineTranslationProvider.cs new file mode 100644 index 000000000..527085373 --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/IBaselineTranslationProvider.cs @@ -0,0 +1,51 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Collections.Generic; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Baseline +{ + /// + /// Provides pre-loaded translation databases built from the official EaW and FoC game localisations. + /// + public interface IBaselineTranslationProvider + { + /// + /// Returns a keyed translation database (MasterText) for the given game and single language. + /// + /// + /// Languages with no baseline data contribute no entries to the returned database. + /// + /// is . + IKeyedTranslationDatabase GetMasterText(GameContext game, IAlamoLanguageDefinition language); + + /// + /// Returns a keyed translation database (MasterText) merged from the given game and languages. + /// + /// + /// Languages with no baseline data contribute no entries to the returned database. + /// + /// is . + IKeyedTranslationDatabase GetMasterText(GameContext game, IReadOnlyList languages); + + /// + /// Returns an ordered translation database (CreditsText) for the given game and single language. + /// + /// + /// Languages with no baseline data contribute no entries to the returned database. + /// + /// is . + IOrderedTranslationDatabase GetCreditsText(GameContext game, IAlamoLanguageDefinition language); + + /// + /// Returns an ordered translation database (CreditsText) merged from the given game and languages. + /// + /// + /// Languages with no baseline data contribute no entries to the returned database. + /// + /// is . + IOrderedTranslationDatabase GetCreditsText(GameContext game, IReadOnlyList languages); + } +} diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.csproj b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.csproj new file mode 100644 index 000000000..f9f155c35 --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.csproj @@ -0,0 +1,61 @@ + + + netstandard2.0;netstandard2.1;net10.0 + PG.StarWarsGame.Localisation.Baseline + Provides baseline translation databases pre-loaded with official EaW and FoC game localisations. + PG.StarWarsGame.Localisation.Baseline + AlamoEngineTools.PG.StarWarsGame.Localisation.Baseline + alamo;petroglyph;glyphx;localisation;translation;baseline + + + true + true + true + + + true + snupkg + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/de/creditstext_german.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/de/creditstext_german.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/de/creditstext_german.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/de/creditstext_german.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/de/mastertextfile_german.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/de/mastertextfile_german.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/de/mastertextfile_german.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/de/mastertextfile_german.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/en/creditstext_english.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/en/creditstext_english.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/en/creditstext_english.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/en/creditstext_english.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/en/mastertextfile_english.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/en/mastertextfile_english.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/en/mastertextfile_english.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/en/mastertextfile_english.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/es/creditstext_spanish.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/es/creditstext_spanish.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/es/creditstext_spanish.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/es/creditstext_spanish.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/es/mastertextfile_spanish.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/es/mastertextfile_spanish.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/es/mastertextfile_spanish.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/es/mastertextfile_spanish.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/fr/creditstext_french.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/fr/creditstext_french.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/fr/creditstext_french.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/fr/creditstext_french.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/fr/mastertextfile_french.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/fr/mastertextfile_french.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/fr/mastertextfile_french.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/fr/mastertextfile_french.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/it/creditstext_italian.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/it/creditstext_italian.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/it/creditstext_italian.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/it/creditstext_italian.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/it/mastertextfile_italian.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/it/mastertextfile_italian.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/EaW/it/mastertextfile_italian.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/EaW/it/mastertextfile_italian.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/de/creditstext_german.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/de/creditstext_german.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/de/creditstext_german.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/de/creditstext_german.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/de/mastertextfile_german.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/de/mastertextfile_german.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/de/mastertextfile_german.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/de/mastertextfile_german.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/en/creditstext_english.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/en/creditstext_english.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/en/creditstext_english.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/en/creditstext_english.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/en/mastertextfile_english.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/en/mastertextfile_english.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/en/mastertextfile_english.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/en/mastertextfile_english.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/es/creditstext_spanish.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/es/creditstext_spanish.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/es/creditstext_spanish.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/es/creditstext_spanish.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/es/mastertextfile_spanish.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/es/mastertextfile_spanish.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/es/mastertextfile_spanish.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/es/mastertextfile_spanish.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/fr/creditstext_french.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/fr/creditstext_french.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/fr/creditstext_french.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/fr/creditstext_french.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/fr/mastertextfile_french.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/fr/mastertextfile_french.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/fr/mastertextfile_french.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/fr/mastertextfile_french.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/it/creditstext_italian.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/it/creditstext_italian.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/it/creditstext_italian.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/it/creditstext_italian.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/it/mastertextfile_italian.dat b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/it/mastertextfile_italian.dat similarity index 100% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat/FoC/it/mastertextfile_italian.dat rename to PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources/FoC/it/mastertextfile_italian.dat diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/CommonLocalisationTestBase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/CommonLocalisationTestBase.cs new file mode 100644 index 000000000..19fc2ddd7 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/CommonLocalisationTestBase.cs @@ -0,0 +1,47 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Files.DAT; +using PG.StarWarsGame.Localisation.IO.Csv; +using PG.StarWarsGame.Localisation.IO.Dat; +using PG.StarWarsGame.Localisation.IO.Properties; +using PG.StarWarsGame.Localisation.IO.Xml; +using PG.StarWarsGame.Localisation.Languages; +using PG.StarWarsGame.Localisation.Services; +using PG.Testing; + +namespace PG.StarWarsGame.Localisation.Test; + +public abstract class CommonLocalisationTestBase : PGTestBase +{ + protected override void SetupServices(IServiceCollection serviceCollection) + { + base.SetupServices(serviceCollection); + serviceCollection.SupportDAT(); + + serviceCollection.AddSingleton(new EnglishAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new GermanAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new FrenchAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new SpanishAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new ItalianAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new ChineseAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new JapaneseAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new KoreanAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new PolishAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new RussianAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new ThaiAlamoLanguageDefinition()); + + serviceCollection.AddSingleton(sp => + new LanguageService(sp.GetServices())); + + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/KeyedTranslationDatabaseTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/KeyedTranslationDatabaseTest.cs new file mode 100644 index 000000000..6ab783ed2 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/KeyedTranslationDatabaseTest.cs @@ -0,0 +1,227 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Linq; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.Data; + +public class KeyedTranslationDatabaseTest +{ + private static readonly IAlamoLanguageDefinition En = new EnglishAlamoLanguageDefinition(); + private static readonly IAlamoLanguageDefinition De = new GermanAlamoLanguageDefinition(); + + private static IKeyedTranslationDatabase Create(params IAlamoLanguageDefinition[] langs) => + new TranslationDatabaseFactory().CreateKeyed(langs); + + // --- empty state --- + + [Fact] + public void Empty_HasZeroEntries() + { + var db = Create(En); + Assert.Empty(db); + } + + [Fact] + public void Empty_ContainsKey_ReturnsFalse() + { + var db = Create(En); + Assert.False(db.ContainsKey("KEY")); + } + + [Fact] + public void Empty_TryGetEntry_ReturnsFalse() + { + var db = Create(En); + Assert.False(db.TryGetEntry("KEY", out _)); + } + + // --- single-language CRUD --- + + [Fact] + public void SetTranslation_AddsNewEntry() + { + var db = Create(En); + db.SetTranslation("HELLO", En, "Hello"); + Assert.True(db.ContainsKey("HELLO")); + Assert.Single(db); + } + + [Fact] + public void SetTranslation_UpdatesExistingValue() + { + var db = Create(En); + db.SetTranslation("HELLO", En, "Hello"); + db.SetTranslation("HELLO", En, "Hi"); + Assert.Single(db); + Assert.True(db.TryGetEntry("HELLO", out var entry)); + Assert.True(entry!.TryGetTranslation(En, out var val)); + Assert.Equal("Hi", val); + } + + [Fact] + public void RemoveEntry_RemovesKey() + { + var db = Create(En); + db.SetTranslation("HELLO", En, "Hello"); + Assert.True(db.RemoveEntry("HELLO")); + Assert.False(db.ContainsKey("HELLO")); + Assert.Empty(db); + } + + [Fact] + public void RemoveEntry_ReturnsFalse_WhenKeyMissing() + { + var db = Create(En); + Assert.False(db.RemoveEntry("MISSING")); + } + + [Fact] + public void Clear_RemovesAllEntries() + { + var db = Create(En); + db.SetTranslation("A", En, "a"); + db.SetTranslation("B", En, "b"); + db.Clear(); + Assert.Empty(db); + } + + // --- multi-language --- + + [Fact] + public void SetTranslation_MultipleLanguages_StoredPerLanguage() + { + var db = Create(En, De); + db.SetTranslation("HELLO", En, "Hello"); + db.SetTranslation("HELLO", De, "Hallo"); + + Assert.True(db.TryGetEntry("HELLO", out var entry)); + Assert.True(entry!.TryGetTranslation(En, out var en)); + Assert.True(entry.TryGetTranslation(De, out var de)); + Assert.Equal("Hello", en); + Assert.Equal("Hallo", de); + } + + [Fact] + public void TryGetTranslation_ReturnsFalse_ForMissingLanguage() + { + var db = Create(En); + db.SetTranslation("HELLO", En, "Hello"); + Assert.True(db.TryGetEntry("HELLO", out var entry)); + Assert.False(entry!.TryGetTranslation(De, out _)); + } + + // --- uniqueness --- + + [Fact] + public void SetTranslation_SameKey_DoesNotCreateDuplicate() + { + var db = Create(En, De); + db.SetTranslation("KEY", En, "English"); + db.SetTranslation("KEY", De, "Deutsch"); + Assert.Single(db); + } + + // --- Languages list --- + + [Fact] + public void Languages_ReflectsConstructorInput() + { + var db = Create(En, De); + Assert.Equal(2, db.Languages.Count); + Assert.Contains(En, db.Languages); + Assert.Contains(De, db.Languages); + } + + // --- enumeration --- + + [Fact] + public void Enumeration_ReturnsAllEntries() + { + var db = Create(En); + db.SetTranslation("A", En, "a"); + db.SetTranslation("B", En, "b"); + Assert.Equal(2, db.ToList().Count); + } + + // --- null guards --- + + [Fact] + public void SetTranslation_Throws_WhenKeyIsNull() + { + var db = Create(En); + Assert.Throws(() => db.SetTranslation(null!, En, "v")); + } + + [Fact] + public void SetTranslation_Throws_WhenLanguageIsNull() + { + var db = Create(En); + Assert.Throws(() => db.SetTranslation("K", null!, "v")); + } + + // --- ActiveLanguage --- + + [Fact] + public void ActiveLanguage_DefaultsToNull() + { + var db = Create(En); + Assert.Null(db.ActiveLanguage); + } + + [Fact] + public void ActiveLanguage_CanBeSet_WhenRegistered() + { + var db = Create(En); + db.ActiveLanguage = En; + Assert.Equal(En, db.ActiveLanguage); + } + + [Fact] + public void ActiveLanguage_Throws_WhenNotInLanguages() + { + var db = Create(En); + Assert.Throws(() => db.ActiveLanguage = De); + } + + [Fact] + public void ActiveLanguage_CanBeSetToNull() + { + var db = Create(En); + db.ActiveLanguage = En; + db.ActiveLanguage = null; + Assert.Null(db.ActiveLanguage); + } + + // --- TryGetTranslation (active language) --- + + [Fact] + public void TryGetTranslation_ByActiveLanguage_ReturnsValue() + { + var db = Create(En); + db.SetTranslation("HELLO", En, "Hello"); + db.ActiveLanguage = En; + Assert.True(db.TryGetTranslation("HELLO", out var val)); + Assert.Equal("Hello", val); + } + + [Fact] + public void TryGetTranslation_ByActiveLanguage_ReturnsFalse_WhenKeyMissing() + { + var db = Create(En); + db.ActiveLanguage = En; + Assert.False(db.TryGetTranslation("MISSING", out _)); + } + + [Fact] + public void TryGetTranslation_Throws_WhenActiveLanguageIsNull() + { + var db = Create(En); + db.SetTranslation("HELLO", En, "Hello"); + Assert.Throws(() => db.TryGetTranslation("HELLO", out _)); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/OrderedTranslationDatabaseTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/OrderedTranslationDatabaseTest.cs new file mode 100644 index 000000000..c37fab51a --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/OrderedTranslationDatabaseTest.cs @@ -0,0 +1,129 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Linq; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.Data; + +public class OrderedTranslationDatabaseTest +{ + private static readonly IAlamoLanguageDefinition En = new EnglishAlamoLanguageDefinition(); + private static readonly IAlamoLanguageDefinition De = new GermanAlamoLanguageDefinition(); + + private static IOrderedTranslationDatabase Create(params IAlamoLanguageDefinition[] langs) => + new TranslationDatabaseFactory().CreateOrdered(langs); + + [Fact] + public void Empty_HasZeroEntries() + { + var db = Create(En); + Assert.Empty(db); + } + + [Fact] + public void SetTranslation_PreservesInsertionOrder() + { + var db = Create(En); + db.SetTranslation("A", En, "First"); + db.SetTranslation("B", En, "Second"); + db.SetTranslation("C", En, "Third"); + var keys = db.Select(e => e.Key).ToList(); + Assert.Equal(new[] { "A", "B", "C" }, keys); + } + + [Fact] + public void SetTranslation_AllowsDuplicateKeys() + { + var db = Create(En); + db.SetTranslation("CREDITS_LINE", En, "Line 1"); + db.SetTranslation("CREDITS_LINE", En, "Line 2"); + Assert.Equal(2, db.Count); + } + + [Fact] + public void GetAllEntriesForKey_ReturnsAllDuplicates() + { + var db = Create(En); + db.SetTranslation("K", En, "First"); + db.SetTranslation("K", En, "Second"); + var entries = db.GetAllEntriesForKey("K"); + Assert.Equal(2, entries.Count); + } + + [Fact] + public void GetAllEntriesForKey_ReturnsEmpty_WhenKeyMissing() + { + var db = Create(En); + Assert.Empty(db.GetAllEntriesForKey("MISSING")); + } + + [Fact] + public void InsertAt_InsertsAtCorrectPosition() + { + var db = Create(En); + db.SetTranslation("A", En, "First"); + db.SetTranslation("C", En, "Third"); + db.InsertAt(1, "B", En, "Second"); + var keys = db.Select(e => e.Key).ToList(); + Assert.Equal(new[] { "A", "B", "C" }, keys); + } + + [Fact] + public void RemoveEntry_RemovesFirstOccurrence() + { + var db = Create(En); + db.SetTranslation("K", En, "First"); + db.SetTranslation("K", En, "Second"); + db.RemoveEntry("K"); + Assert.Single(db); + } + + [Fact] + public void Clear_RemovesAll() + { + var db = Create(En); + db.SetTranslation("A", En, "a"); + db.SetTranslation("A", En, "a2"); + db.Clear(); + Assert.Empty(db); + } + + [Fact] + public void MultiLanguage_StoredPerLanguagePerEntry() + { + var db = Create(En, De); + db.SetTranslation("K", En, "English"); + db.SetTranslation("K", De, "Deutsch"); + Assert.Equal(2, db.Count); + var allK = db.GetAllEntriesForKey("K"); + Assert.Equal(2, allK.Count); + } + + // --- ActiveLanguage --- + + [Fact] + public void ActiveLanguage_DefaultsToNull() + { + var db = Create(En); + Assert.Null(db.ActiveLanguage); + } + + [Fact] + public void ActiveLanguage_CanBeSet() + { + var db = Create(En); + db.ActiveLanguage = En; + Assert.Equal(En, db.ActiveLanguage); + } + + [Fact] + public void ActiveLanguage_Throws_WhenNotInLanguages() + { + var db = Create(En); + Assert.Throws(() => db.ActiveLanguage = De); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationDatabaseBuilderTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationDatabaseBuilderTest.cs new file mode 100644 index 000000000..32bb9a00b --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationDatabaseBuilderTest.cs @@ -0,0 +1,93 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.Data; + +public class TranslationDatabaseBuilderTest +{ + private static readonly IAlamoLanguageDefinition En = new EnglishAlamoLanguageDefinition(); + private static readonly IAlamoLanguageDefinition De = new GermanAlamoLanguageDefinition(); + + private static ITranslationDatabaseBuilder Builder() => + new TranslationDatabaseFactory().CreateDatabase(); + + [Fact] + public void CreateDatabase_BuildsKeyedDatabase() + { + var db = Builder().WithLanguage(En).BuildKeyed(); + Assert.NotNull(db); + } + + [Fact] + public void CreateDatabase_BuildsOrderedDatabase() + { + var db = Builder().WithLanguage(En).BuildOrdered(); + Assert.NotNull(db); + } + + [Fact] + public void WithLanguage_AddsSingleLanguage() + { + var db = Builder().WithLanguage(En).BuildKeyed(); + Assert.Single(db.Languages); + Assert.Contains(En, db.Languages); + } + + [Fact] + public void WithLanguages_AddsMultipleLanguages() + { + var db = Builder().WithLanguages(new[] { En, De }).BuildKeyed(); + Assert.Equal(2, db.Languages.Count); + } + + [Fact] + public void WithActiveLanguage_SetsActiveLanguageOnBuiltDatabase() + { + var db = Builder().WithLanguage(En).SetActiveLanguage(En).BuildKeyed(); + Assert.Equal(En, db.ActiveLanguage); + } + + [Fact] + public void WithActiveLanguage_NotInLanguages_ThrowsAtBuildTime() + { + Assert.Throws(() => + Builder().WithLanguage(En).SetActiveLanguage(De).BuildKeyed()); + } + + [Fact] + public void WithLanguages_Empty_BuildsEmptyLanguagesDatabase() + { + var db = Builder().BuildKeyed(); + Assert.Empty(db.Languages); + } + + [Fact] + public void Builder_IsChainable() + { + var db = Builder() + .WithLanguage(En) + .WithLanguage(De) + .SetActiveLanguage(En) + .BuildOrdered(); + + Assert.Equal(2, db.Languages.Count); + Assert.Equal(En, db.ActiveLanguage); + } + + [Fact] + public void WithLanguage_Throws_OnNull() + { + Assert.Throws(() => Builder().WithLanguage(null!)); + } + + [Fact] + public void WithLanguages_Throws_OnNull() + { + Assert.Throws(() => Builder().WithLanguages(null!)); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationProjectDescriptorTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationProjectDescriptorTest.cs new file mode 100644 index 000000000..641fe6917 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationProjectDescriptorTest.cs @@ -0,0 +1,51 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using PG.StarWarsGame.Localisation.Data.Config; +using PG.StarWarsGame.Localisation.Data.Config.v2; +using PG.StarWarsGame.Localisation.Languages; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.Data; + +public class TranslationProjectDescriptorTest +{ + private static readonly IReadOnlyList TwoLangs = + new IAlamoLanguageDefinition[] { new EnglishAlamoLanguageDefinition(), new GermanAlamoLanguageDefinition() }; + + [Fact] + public void Constructor_SetsAllProperties() + { + var d = new TranslationProjectDescriptor(GameContext.EaW, OverrideType.Core, TranslationResourceType.Dat, TwoLangs); + Assert.Equal(GameContext.EaW, d.Game); + Assert.Equal(OverrideType.Core, d.OverrideType); + Assert.Equal(TranslationResourceType.Dat, d.ResourceType); + Assert.Equal(2, d.Languages.Count); + } + + [Fact] + public void Constructor_Throws_WhenLanguagesIsNull() + { + Assert.Throws(() => + new TranslationProjectDescriptor(GameContext.EaW, OverrideType.Core, TranslationResourceType.Dat, null!)); + } + + [Fact] + public void Languages_IsImmutable() + { + var mutable = new List { new EnglishAlamoLanguageDefinition() }; + var d = new TranslationProjectDescriptor(GameContext.EaW, OverrideType.Core, TranslationResourceType.Xml, mutable); + mutable.Add(new GermanAlamoLanguageDefinition()); + Assert.Single(d.Languages); + } + + [Fact] + public void Equality_TwoDescriptorsWithSameValues_AreEqual() + { + var a = new TranslationProjectDescriptor(GameContext.FoC, OverrideType.Mod, TranslationResourceType.Csv, TwoLangs); + var b = new TranslationProjectDescriptor(GameContext.FoC, OverrideType.Mod, TranslationResourceType.Csv, TwoLangs); + Assert.Equal(a, b); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.cs new file mode 100644 index 000000000..458772d2b --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.cs @@ -0,0 +1,116 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.IO; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.IO.Csv; +using PG.StarWarsGame.Localisation.Languages; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.IO; + +public class CsvTranslationAdapterTest : CommonLocalisationTestBase +{ + private static readonly IAlamoLanguageDefinition En = new EnglishAlamoLanguageDefinition(); + private static readonly IAlamoLanguageDefinition De = new GermanAlamoLanguageDefinition(); + + private ICsvTranslationImporter CreateImporter() => + ServiceProvider.GetRequiredService(); + + private ICsvTranslationExporter CreateExporter() => + ServiceProvider.GetRequiredService(); + + [Fact] + public void RoundTrip_MultiLanguage_PreservesAllTranslations() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En, De }); + db.SetTranslation("HELLO", En, "Hello"); + db.SetTranslation("HELLO", De, "Hallo"); + + var csv = CreateExporter().Export(db); + var db2 = new TranslationDatabaseFactory().CreateKeyed(new[] { En, De }); + CreateImporter().Import(new StringReader(csv), db2); + + Assert.True(db2.TryGetEntry("HELLO", out var entry)); + Assert.True(entry!.TryGetTranslation(En, out var en)); + Assert.True(entry.TryGetTranslation(De, out var de)); + Assert.Equal("Hello", en); + Assert.Equal("Hallo", de); + } + + [Fact] + public void Export_ContainsHeaderRow() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En, De }); + db.SetTranslation("K", En, "v"); + var csv = CreateExporter().Export(db); + var firstLine = csv.Split('\n')[0]; + Assert.Contains("ENGLISH", firstLine); + Assert.Contains("GERMAN", firstLine); + } + + [Fact] + public void RoundTrip_ValueWithComma_IsPreserved() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("K", En, "Hello, World"); + var csv = CreateExporter().Export(db); + var db2 = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(new StringReader(csv), db2); + Assert.True(db2.TryGetEntry("K", out var entry)); + Assert.True(entry!.TryGetTranslation(En, out var val)); + Assert.Equal("Hello, World", val); + } + + [Fact] + public void Import_EmptyCsv_LeavesDbEmpty() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(new StringReader("key,ENGLISH\n"), db); + Assert.Empty(db); + } + + [Fact] + public void RoundTrip_ValueWithEmbeddedQuotes_IsPreserved() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("K", En, "The \"Resolute\" is a Venator"); + var csv = CreateExporter().Export(db); + var db2 = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(new StringReader(csv), db2); + Assert.True(db2.TryGetEntry("K", out var entry)); + Assert.True(entry!.TryGetTranslation(En, out var val)); + Assert.Equal("The \"Resolute\" is a Venator", val); + } + + [Fact] + public void Import_CrlfLineEndings_AreHandledCorrectly() + { + var csv = "key,ENGLISH\r\nHELLO,Hello\r\nWORLD,World\r\n"; + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(new StringReader(csv), db); + Assert.True(db.TryGetEntry("HELLO", out var e1)); + Assert.True(e1!.TryGetTranslation(En, out var v1)); + Assert.Equal("Hello", v1); + Assert.True(db.TryGetEntry("WORLD", out var e2)); + Assert.True(e2!.TryGetTranslation(En, out var v2)); + Assert.Equal("World", v2); + } + + [Fact] + public void Export_KeyedDatabase_WritesKeysAlphabetically() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("ZEBRA", En, "z"); + db.SetTranslation("APPLE", En, "a"); + db.SetTranslation("MONKEY", En, "m"); + + var csv = CreateExporter().Export(db); + var lines = csv.Split('\n'); + // lines[0] is header; data lines follow + Assert.Equal("APPLE", lines[1].Split(',')[0]); + Assert.Equal("MONKEY", lines[2].Split(',')[0]); + Assert.Equal("ZEBRA", lines[3].Split(',')[0]); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/DatTranslationAdapterTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/DatTranslationAdapterTest.cs new file mode 100644 index 000000000..e3eb1846c --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/DatTranslationAdapterTest.cs @@ -0,0 +1,96 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Files.DAT.Services.Builder; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.IO.Dat; +using PG.StarWarsGame.Localisation.Languages; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.IO; + +public class DatTranslationAdapterTest : CommonLocalisationTestBase +{ + private static readonly IAlamoLanguageDefinition En = new EnglishAlamoLanguageDefinition(); + + private IDatTranslationImporter CreateImporter() => + ServiceProvider.GetRequiredService(); + + private IDatTranslationExporter CreateExporter() => + ServiceProvider.GetRequiredService(); + + [Fact] + public void RoundTrip_KeyedDatabase_PreservesEntriesAndValues() + { + var builder = new EmpireAtWarMasterTextBuilder(false, ServiceProvider); + builder.AddEntry("HELLO", "Hello World"); + builder.AddEntry("GOODBYE", "Goodbye World"); + var model = builder.BuildModel(); + + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(model, En, db); + + Assert.True(db.ContainsKey("HELLO")); + Assert.True(db.ContainsKey("GOODBYE")); + Assert.True(db.TryGetEntry("HELLO", out var entry)); + Assert.True(entry!.TryGetTranslation(En, out var val)); + Assert.Equal("Hello World", val); + } + + [Fact] + public void RoundTrip_OrderedDatabase_PreservesOrder() + { + var builder = new EmpireAtWarCreditsTextBuilder(ServiceProvider); + builder.AddEntry("LINE", "First"); + builder.AddEntry("LINE", "Second"); + var model = builder.BuildModel(); + + var db = new TranslationDatabaseFactory().CreateOrdered(new[] { En }); + CreateImporter().Import(model, En, db); + + Assert.Equal(2, db.Count); + Assert.True(db[0].TryGetTranslation(En, out var first)); + Assert.True(db[1].TryGetTranslation(En, out var second)); + Assert.Equal("First", first); + Assert.Equal("Second", second); + } + + [Fact] + public void Export_KeyedDatabase_ProducesEquivalentDatModel() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("HELLO", En, "Hello"); + db.SetTranslation("BYE", En, "Bye"); + + var exported = CreateExporter().Export(db, En); + Assert.Equal(2, exported.Count); + Assert.Equal("Hello", exported.GetValue("HELLO")); + Assert.Equal("Bye", exported.GetValue("BYE")); + } + + [Fact] + public void Export_OrderedDatabase_PreservesInsertionOrder() + { + var db = new TranslationDatabaseFactory().CreateOrdered(new[] { En }); + db.SetTranslation("LINE", En, "First"); + db.SetTranslation("LINE", En, "Second"); + + var exported = CreateExporter().Export(db, En); + Assert.Equal(2, exported.Count); + var entries = new List(exported); + Assert.Equal("First", entries[0].Value); + Assert.Equal("Second", entries[1].Value); + } + + [Fact] + public void Import_EmptyModel_LeavesDbEmpty() + { + var builder = new EmpireAtWarMasterTextBuilder(false, ServiceProvider); + var model = builder.BuildModel(); + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(model, En, db); + Assert.Empty(db); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/PropertiesTranslationAdapterTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/PropertiesTranslationAdapterTest.cs new file mode 100644 index 000000000..d19c604f0 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/PropertiesTranslationAdapterTest.cs @@ -0,0 +1,93 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.IO; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.IO.Properties; +using PG.StarWarsGame.Localisation.Languages; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.IO; + +public class PropertiesTranslationAdapterTest : CommonLocalisationTestBase +{ + private static readonly IAlamoLanguageDefinition En = new EnglishAlamoLanguageDefinition(); + + private IPropertiesTranslationImporter CreateImporter() => + ServiceProvider.GetRequiredService(); + + private IPropertiesTranslationExporter CreateExporter() => + ServiceProvider.GetRequiredService(); + + [Fact] + public void RoundTrip_PreservesKeyValuePairs() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("HELLO", En, "Hello World"); + db.SetTranslation("BYE", En, "Goodbye"); + + var props = CreateExporter().Export(db, En); + var db2 = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(new StringReader(props), En, db2); + + Assert.True(db2.TryGetEntry("HELLO", out var hello)); + Assert.True(hello!.TryGetTranslation(En, out var val)); + Assert.Equal("Hello World", val); + } + + [Fact] + public void Export_ProducesKeyEqualsValueFormat() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("KEY", En, "value"); + var props = CreateExporter().Export(db, En); + Assert.Contains("KEY=value", props); + } + + [Fact] + public void RoundTrip_ValueWithEqualsSign_IsPreserved() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("EQ", En, "a=b"); + var props = CreateExporter().Export(db, En); + var db2 = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(new StringReader(props), En, db2); + Assert.True(db2.TryGetEntry("EQ", out var entry)); + Assert.True(entry!.TryGetTranslation(En, out var val)); + Assert.Equal("a=b", val); + } + + [Fact] + public void Import_Empty_LeavesDbEmpty() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(new StringReader(""), En, db); + Assert.Empty(db); + } + + [Fact] + public void Import_SkipsCommentLines() + { + const string props = "# This is a comment\nKEY=Value\n"; + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(new StringReader(props), En, db); + Assert.Single(db); + } + + [Fact] + public void Export_KeyedDatabase_WritesKeysAlphabetically() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("ZEBRA", En, "z"); + db.SetTranslation("APPLE", En, "a"); + db.SetTranslation("MONKEY", En, "m"); + + var props = CreateExporter().Export(db, En); + var lines = props.Split('\n'); + var dataLines = System.Array.FindAll(lines, l => l.Contains("=")); + Assert.StartsWith("APPLE=", dataLines[0]); + Assert.StartsWith("MONKEY=", dataLines[1]); + Assert.StartsWith("ZEBRA=", dataLines[2]); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs new file mode 100644 index 000000000..50b9fb61c --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs @@ -0,0 +1,95 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Xml.Linq; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.IO.Xml; +using System.Linq; +using PG.StarWarsGame.Localisation.Languages; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.IO; + +public class XmlTranslationAdapterTest : CommonLocalisationTestBase +{ + private static readonly IAlamoLanguageDefinition En = new EnglishAlamoLanguageDefinition(); + private static readonly IAlamoLanguageDefinition De = new GermanAlamoLanguageDefinition(); + + private IXmlTranslationImporter CreateImporter() => + ServiceProvider.GetRequiredService(); + + private IXmlTranslationExporter CreateExporter() => + ServiceProvider.GetRequiredService(); + + [Fact] + public void RoundTrip_MultiLanguage_PreservesAllTranslations() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En, De }); + db.SetTranslation("HELLO", En, "Hello"); + db.SetTranslation("HELLO", De, "Hallo"); + db.SetTranslation("BYE", En, "Bye"); + db.SetTranslation("BYE", De, "Tschüss"); + + var xml = CreateExporter().Export(db); + + var db2 = new TranslationDatabaseFactory().CreateKeyed(new[] { En, De }); + CreateImporter().Import(xml, db2); + + Assert.True(db2.TryGetEntry("HELLO", out var hello)); + Assert.True(hello!.TryGetTranslation(En, out var en)); + Assert.True(hello.TryGetTranslation(De, out var de)); + Assert.Equal("Hello", en); + Assert.Equal("Hallo", de); + } + + [Fact] + public void Export_ProducesValidXml() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("KEY", En, "Value"); + + var xml = CreateExporter().Export(db); + Assert.NotNull(xml.Root); + } + + [Fact] + public void Import_EmptyXml_LeavesDbEmpty() + { + var xml = new XDocument(new XElement( + XName.Get("LocalisationData", "urn:alamoenginetools:localisation:v1"))); + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + CreateImporter().Import(xml, db); + Assert.Empty(db); + } + + [Fact] + public void Import_UnknownLanguage_IsSkipped() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("K", En, "v"); + var xml = CreateExporter().Export(db); + + var db2 = new TranslationDatabaseFactory().CreateKeyed(new[] { De }); + CreateImporter().Import(xml, db2); + Assert.Empty(db2); + } + + [Fact] + public void Export_KeyedDatabase_WritesKeysAlphabetically() + { + var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); + db.SetTranslation("ZEBRA", En, "z"); + db.SetTranslation("APPLE", En, "a"); + db.SetTranslation("MONKEY", En, "m"); + + var xml = CreateExporter().Export(db); + XNamespace ns = "urn:alamoenginetools:localisation:v1"; + var keys = xml.Root! + .Elements(ns + "Localisation") + .Select(e => e.Attribute("key")!.Value) + .ToList(); + + Assert.Equal(new[] { "APPLE", "MONKEY", "ZEBRA" }, keys); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs new file mode 100644 index 000000000..4e2a18a70 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs @@ -0,0 +1,106 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Globalization; +using PG.StarWarsGame.Localisation.Languages; +using PG.StarWarsGame.Localisation.Languages.Attributes; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.Languages; + +public class AbstractAlamoLanguageDefinitionTest +{ + [Fact] + public void LanguageIdentifier_ReturnsConfiguredValue() + { + var lang = new EnglishAlamoLanguageDefinition(); + Assert.Equal("ENGLISH", lang.LanguageIdentifier); + } + + [Fact] + public void Culture_ReturnsConfiguredCulture() + { + var lang = new EnglishAlamoLanguageDefinition(); + Assert.Equal(CultureInfo.GetCultureInfo("en-US"), lang.Culture); + } + + [Fact] + public void IsOfficiallySupported_IsTrue_WhenMarkedWithAttribute() + { + var lang = new EnglishAlamoLanguageDefinition(); + Assert.True(lang.IsOfficiallySupported()); + } + + [Fact] + public void IsDefault_IsTrue_OnlyForEnglish() + { + var english = new EnglishAlamoLanguageDefinition(); + var german = new GermanAlamoLanguageDefinition(); + Assert.True(english.IsDefault); + Assert.False(german.IsDefault); + } + + [Fact] + public void Equals_ReturnsTrueForSameLanguageIdentifier() + { + var a = new EnglishAlamoLanguageDefinition(); + var b = new EnglishAlamoLanguageDefinition(); + Assert.Equal(a, b); + } + + [Fact] + public void Equals_ReturnsFalseForDifferentLanguages() + { + var english = new EnglishAlamoLanguageDefinition(); + var german = new GermanAlamoLanguageDefinition(); + Assert.NotEqual(english, german); + } + + [Fact] + public void IsOfficiallySupported_WithContext_ReturnsTrueForBothContexts_WhenNoContextsSpecified() + { + var lang = new EnglishAlamoLanguageDefinition(); + Assert.True(lang.IsOfficiallySupported(GameContext.EaW)); + Assert.True(lang.IsOfficiallySupported(GameContext.FoC)); + } + + [Fact] + public void IsOfficiallySupported_WithContext_ReturnsFalse_WhenLanguageHasNoAttribute() + { + var lang = new UnsupportedTestLanguage(); + Assert.False(lang.IsOfficiallySupported(GameContext.EaW)); + Assert.False(lang.IsOfficiallySupported(GameContext.FoC)); + } + + [Fact] + public void IsOfficiallySupported_WithContext_ReturnsTrue_OnlyForMatchingContext() + { + var baseOnly = new BaseGameOnlyTestLanguage(); + Assert.True(baseOnly.IsOfficiallySupported(GameContext.EaW)); + Assert.False(baseOnly.IsOfficiallySupported(GameContext.FoC)); + + var expOnly = new ExpansionOnlyTestLanguage(); + Assert.False(expOnly.IsOfficiallySupported(GameContext.EaW)); + Assert.True(expOnly.IsOfficiallySupported(GameContext.FoC)); + } + + private sealed class UnsupportedTestLanguage : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "KLINGON"; + protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; + } + + [OfficiallySupportedLanguage(GameContext.EaW)] + private sealed class BaseGameOnlyTestLanguage : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "BASE_ONLY"; + protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; + } + + [OfficiallySupportedLanguage(GameContext.FoC)] + private sealed class ExpansionOnlyTestLanguage : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "EXP_ONLY"; + protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AlamoLanguageDefinitionIntegrityTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AlamoLanguageDefinitionIntegrityTest.cs index 013e4ed0d..f69bc2ba4 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AlamoLanguageDefinitionIntegrityTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AlamoLanguageDefinitionIntegrityTest.cs @@ -1,60 +1,71 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; -using PG.Core.Test; -using PG.StarWarsGame.Localisation.Util; -using System.Collections.Generic; - -namespace PG.StarWarsGame.Localisation.Test.Languages +using System.Linq; +using System.Reflection; +using PG.StarWarsGame.Localisation.Languages; +using PG.StarWarsGame.Localisation.Languages.Attributes; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.Languages; + +public class AlamoLanguageDefinitionIntegrityTest { - [TestClass] - [TestCategory(TestConstants.TEST_TYPE_HOLY)] - public class AlamoLanguageDefinitionIntegrityTest + private static readonly Assembly LocalisationAssembly = + typeof(IAlamoLanguageDefinition).Assembly; + + [Fact] + public void ExactlyElevenConcreteLanguagesAreDefined() + { + var count = LocalisationAssembly.GetTypes() + .Count(t => t is { IsClass: true, IsAbstract: false } + && typeof(IAlamoLanguageDefinition).IsAssignableFrom(t)); + Assert.Equal(11, count); + } + + [Fact] + public void ExactlyOneDefaultLanguageIsDefined() + { + var defaults = LocalisationAssembly.GetTypes() + .Where(t => t is { IsClass: true, IsAbstract: false } + && typeof(IAlamoLanguageDefinition).IsAssignableFrom(t) + && t.IsDefined(typeof(DefaultLanguageAttribute), false)) + .ToList(); + Assert.Single(defaults); + } + + [Fact] + public void DefaultLanguageIsEnglish() { - [TestMethod] - public void Test_CorrectNumberOfLanguages() - { - IList l = LocalisationUtility.GetAllAlamoLanguageDefinitions(); - Assert.AreEqual(LocalisationTestConstants.REGISTERED_LANGUAGE_DEFINITIONS.Count, l.Count, "An official language definition has been added or removed. This should never happen - if there is a good reason for this, please update LocalisationTestConstants.REGISTERED_LANGUAGE_DEFINITIONS accordingly."); - } - - [TestMethod] - public void Test_CorrectLanguagesRegistered() - { - IList l = LocalisationUtility.GetAllAlamoLanguageDefinitions(); - foreach (IAlamoLanguageDefinition alamoLanguageDefinition in l) - { - Assert.IsTrue( - LocalisationTestConstants.REGISTERED_LANGUAGE_DEFINITIONS.Contains( - alamoLanguageDefinition.GetType())); - Assert.IsTrue(alamoLanguageDefinition.GetType().GetAttributeValueOrDefault((OfficiallySupportedLanguageAttribute o) => o.IsOfficiallySupported)); - } - } - - [TestMethod] - public void Test_DefaultLanguageIsDefined() - { - IList l = LocalisationUtility.GetAllAlamoLanguageDefinitions(); - bool isDefaultDefined = false; - foreach (IAlamoLanguageDefinition alamoLanguageDefinition in l) - { - if (alamoLanguageDefinition.GetType().GetAttributeValueOrDefault((DefaultAttribute d) => d.IsDefault)) - { - isDefaultDefined = true; - } - } - Assert.IsTrue(isDefaultDefined, "No default language is defined. This should not happen. EnglishAlamoLanguageDefinition should have the Default attribute set."); - } - - [TestMethod] - public void Test_DefaultLanguageIsCorrect() - { - IAlamoLanguageDefinition l = LocalisationUtility.GetDefaultAlamoLanguageDefinition(); - Assert.AreEqual(LocalisationTestConstants.DEFAULT_LANGUAGE, l.GetType(), "The default language is not EnglishAlamoLanguageDefinition. This should never happen. Please ensure EnglishAlamoLanguageDefinition has the Default attribute set."); - } + var defaultType = LocalisationAssembly.GetTypes() + .Single(t => t is { IsClass: true, IsAbstract: false } + && typeof(IAlamoLanguageDefinition).IsAssignableFrom(t) + && t.IsDefined(typeof(DefaultLanguageAttribute), false)); + var instance = (IAlamoLanguageDefinition)System.Activator.CreateInstance(defaultType)!; + Assert.Equal("ENGLISH", instance.LanguageIdentifier); + } + + [Fact] + public void AllConcreteLanguagesAreOfficiallySupported() + { + var langs = LocalisationAssembly.GetTypes() + .Where(t => t is { IsClass: true, IsAbstract: false } + && typeof(IAlamoLanguageDefinition).IsAssignableFrom(t)) + .Select(t => (IAlamoLanguageDefinition)System.Activator.CreateInstance(t)!) + .ToList(); + + Assert.All(langs, l => Assert.True(l.IsOfficiallySupported())); + } + + [Fact] + public void AllConcreteLanguagesHaveUniqueIdentifiers() + { + var identifiers = LocalisationAssembly.GetTypes() + .Where(t => t is { IsClass: true, IsAbstract: false } + && typeof(IAlamoLanguageDefinition).IsAssignableFrom(t)) + .Select(t => ((IAlamoLanguageDefinition)System.Activator.CreateInstance(t)!).LanguageIdentifier) + .ToList(); + + Assert.Equal(identifiers.Count, identifiers.Distinct().Count()); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/LocalisationServiceContributionTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/LocalisationServiceContributionTest.cs new file mode 100644 index 000000000..1a38e3936 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/LocalisationServiceContributionTest.cs @@ -0,0 +1,101 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.IO.Csv; +using PG.StarWarsGame.Localisation.IO.Dat; +using PG.StarWarsGame.Localisation.IO.Properties; +using PG.StarWarsGame.Localisation.IO.Xml; +using PG.StarWarsGame.Localisation.Services; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test; + +public class LocalisationServiceContributionTest +{ + private static ServiceProvider BuildProvider() + { + var sc = new ServiceCollection(); + sc.SupportLocalisation(); + return sc.BuildServiceProvider(); + } + + [Fact] + public void SupportLocalisation_Resolves_ILanguageService() + { + using var sp = BuildProvider(); + Assert.NotNull(sp.GetRequiredService()); + } + + [Fact] + public void SupportLocalisation_Resolves_ITranslationDatabaseFactory() + { + using var sp = BuildProvider(); + Assert.NotNull(sp.GetRequiredService()); + } + + [Fact] + public void SupportLocalisation_Resolves_IDatTranslationImporter() + { + using var sp = BuildProvider(); + Assert.NotNull(sp.GetRequiredService()); + } + + [Fact] + public void SupportLocalisation_Resolves_IDatTranslationExporter() + { + using var sp = BuildProvider(); + Assert.NotNull(sp.GetRequiredService()); + } + + [Fact] + public void SupportLocalisation_Resolves_IXmlTranslationImporter() + { + using var sp = BuildProvider(); + Assert.NotNull(sp.GetRequiredService()); + } + + [Fact] + public void SupportLocalisation_Resolves_IXmlTranslationExporter() + { + using var sp = BuildProvider(); + Assert.NotNull(sp.GetRequiredService()); + } + + [Fact] + public void SupportLocalisation_Resolves_ICsvTranslationImporter() + { + using var sp = BuildProvider(); + Assert.NotNull(sp.GetRequiredService()); + } + + [Fact] + public void SupportLocalisation_Resolves_ICsvTranslationExporter() + { + using var sp = BuildProvider(); + Assert.NotNull(sp.GetRequiredService()); + } + + [Fact] + public void SupportLocalisation_Resolves_IPropertiesTranslationImporter() + { + using var sp = BuildProvider(); + Assert.NotNull(sp.GetRequiredService()); + } + + [Fact] + public void SupportLocalisation_Resolves_IPropertiesTranslationExporter() + { + using var sp = BuildProvider(); + Assert.NotNull(sp.GetRequiredService()); + } + + [Fact] + public void SupportLocalisation_LanguageService_HasElevenLanguages() + { + using var sp = BuildProvider(); + var svc = sp.GetRequiredService(); + Assert.Equal(11, svc.AllLanguages.Count); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/LocalisationTestConstants.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/LocalisationTestConstants.cs deleted file mode 100644 index a4eb8d04c..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/LocalisationTestConstants.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using PG.Core.Test; -using PG.StarWarsGame.Localisation.Languages; -using System; -using System.Collections.Generic; - -namespace PG.StarWarsGame.Localisation.Test -{ - public sealed class LocalisationTestConstants : TestConstants - { - public static readonly IList REGISTERED_LANGUAGE_DEFINITIONS = new List - { - //[gruenwaldlu, 2021-04-18-12:02:51+2]: All officially supported languages are listed below. - typeof(ChineseAlamoLanguageDefinition) - , typeof(EnglishAlamoLanguageDefinition) - , typeof(FrenchAlamoLanguageDefinition) - , typeof(GermanAlamoLanguageDefinition) - , typeof(ItalianAlamoLanguageDefinition) - , typeof(JapaneseAlamoLanguageDefinition) - , typeof(KoreanAlamoLanguageDefinition) - , typeof(PolishAlamoLanguageDefinition) - , typeof(RussianAlamoLanguageDefinition) - , typeof(SpanishAlamoLanguageDefinition) - , typeof(ThaiAlamoLanguageDefinition) - }; - - public static readonly Type DEFAULT_LANGUAGE = typeof(EnglishAlamoLanguageDefinition); - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/PG.StarWarsGame.Localisation.Test.csproj b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/PG.StarWarsGame.Localisation.Test.csproj index b210c6bfb..7455d587b 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/PG.StarWarsGame.Localisation.Test.csproj +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/PG.StarWarsGame.Localisation.Test.csproj @@ -1,30 +1,34 @@ - + + + net8.0;net10.0 + $(TargetFrameworks);net481 + false - net8.0 - $(TargetFrameworks);net48 + true + Exe - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all + runtime; build; native; contentfiles; analyzers; buildtransitive + all - - - + all runtime; build; native; contentfiles; analyzers; buildtransitive + + - \ No newline at end of file + diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs new file mode 100644 index 000000000..cb20bdd6a --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs @@ -0,0 +1,166 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Globalization; +using PG.StarWarsGame.Localisation.Languages; +using PG.StarWarsGame.Localisation.Languages.Attributes; +using PG.StarWarsGame.Localisation.Services; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Test.Services; + +public class LanguageServiceTest +{ + private static ILanguageService CreateService(IEnumerable? langs = null) + { + var all = langs ?? new IAlamoLanguageDefinition[] + { + new EnglishAlamoLanguageDefinition(), + new GermanAlamoLanguageDefinition(), + new FrenchAlamoLanguageDefinition(), + new SpanishAlamoLanguageDefinition(), + new ItalianAlamoLanguageDefinition(), + new ChineseAlamoLanguageDefinition(), + new JapaneseAlamoLanguageDefinition(), + new KoreanAlamoLanguageDefinition(), + new PolishAlamoLanguageDefinition(), + new RussianAlamoLanguageDefinition(), + new ThaiAlamoLanguageDefinition(), + }; + return new LanguageService(all); + } + + [Fact] + public void AllLanguages_ReturnsAllRegistered() + { + var svc = CreateService(); + Assert.Equal(11, svc.AllLanguages.Count); + } + + [Fact] + public void OfficiallySupported_ReturnsOnlySupported() + { + var svc = CreateService(); + Assert.All(svc.OfficiallySupported(), l => Assert.True(l.IsOfficiallySupported())); + } + + [Fact] + public void Default_ReturnsEnglish() + { + var svc = CreateService(); + Assert.Equal("ENGLISH", svc.Default.LanguageIdentifier); + } + + [Fact] + public void Default_Throws_WhenNoDefaultRegistered() + { + var svc = new LanguageService(new[] { new GermanAlamoLanguageDefinition() }); + Assert.Throws(() => _ = svc.Default); + } + + [Fact] + public void TryGetByIdentifier_ReturnsTrue_OnExactMatch() + { + var svc = CreateService(); + Assert.True(svc.TryGetByIdentifier("ENGLISH", out var lang)); + Assert.NotNull(lang); + Assert.Equal("ENGLISH", lang!.LanguageIdentifier); + } + + [Fact] + public void TryGetByIdentifier_IsCaseInsensitive() + { + var svc = CreateService(); + Assert.True(svc.TryGetByIdentifier("english", out var lang)); + Assert.NotNull(lang); + } + + [Fact] + public void TryGetByIdentifier_ReturnsFalse_WhenNotFound() + { + var svc = CreateService(); + Assert.False(svc.TryGetByIdentifier("KLINGON", out var lang)); + Assert.Null(lang); + } + + [Fact] + public void IsOfficiallySupported_ReturnsTrue_ForSupportedLanguage() + { + var svc = CreateService(); + Assert.True(svc.IsOfficiallySupported(new EnglishAlamoLanguageDefinition())); + } + + [Fact] + public void IsOfficiallySupported_ReturnsFalse_ForUnsupportedLanguage() + { + var svc = CreateService(); + var unsupported = new UnsupportedTestLanguage(); + Assert.False(svc.IsOfficiallySupported(unsupported)); + } + + [Fact] + public void IsOfficiallySupported_Throws_OnNull() + { + var svc = CreateService(); + Assert.Throws(() => svc.IsOfficiallySupported(null!)); + } + + [Fact] + public void OfficiallySupported_WithContext_ReturnsOnlyContextMatchingLanguages() + { + var baseOnly = new BaseGameOnlyTestLanguage(); + var expOnly = new ExpansionOnlyTestLanguage(); + var both = new EnglishAlamoLanguageDefinition(); + var svc = new LanguageService(new IAlamoLanguageDefinition[] { baseOnly, expOnly, both }); + + var baseResult = svc.OfficiallySupported(GameContext.EaW); + var expResult = svc.OfficiallySupported(GameContext.FoC); + + Assert.Contains(baseOnly, baseResult); + Assert.Contains(both, baseResult); + Assert.DoesNotContain(expOnly, baseResult); + + Assert.Contains(expOnly, expResult); + Assert.Contains(both, expResult); + Assert.DoesNotContain(baseOnly, expResult); + } + + [Fact] + public void IsOfficiallySupported_WithContext_ReturnsCorrectResultPerContext() + { + var baseOnly = new BaseGameOnlyTestLanguage(); + var svc = new LanguageService(new IAlamoLanguageDefinition[] { baseOnly, new EnglishAlamoLanguageDefinition() }); + + Assert.True(svc.IsOfficiallySupported(baseOnly, GameContext.EaW)); + Assert.False(svc.IsOfficiallySupported(baseOnly, GameContext.FoC)); + } + + [Fact] + public void IsOfficiallySupported_WithContext_Throws_OnNull() + { + var svc = CreateService(); + Assert.Throws(() => svc.IsOfficiallySupported(null!, GameContext.EaW)); + } + + private sealed class UnsupportedTestLanguage : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "KLINGON"; + protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; + } + + [OfficiallySupportedLanguage(GameContext.EaW)] + private sealed class BaseGameOnlyTestLanguage : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "BASE_ONLY"; + protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; + } + + [OfficiallySupportedLanguage(GameContext.FoC)] + private sealed class ExpansionOnlyTestLanguage : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "EXP_ONLY"; + protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Util/LocalisationUtilityTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Util/LocalisationUtilityTest.cs deleted file mode 100644 index 412352319..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Util/LocalisationUtilityTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; -using PG.Core.Test; -using PG.StarWarsGame.Localisation.Util; -using System; -using System.Collections.Generic; - -namespace PG.StarWarsGame.Localisation.Test.Util -{ - [TestClass] - [TestCategory(TestConstants.TEST_TYPE_UTILITY)] - public class LocalisationUtilityTest - { - [TestMethod] - public void Test_GetAllAlamoLanguageDefinitions__ReturnsAtLeastDefaults() - { - IList l = LocalisationUtility.GetAllAlamoLanguageDefinitions(); - Assert.IsTrue(l.Count >= LocalisationTestConstants.REGISTERED_LANGUAGE_DEFINITIONS.Count, - "This function should always at least return the default language definitions."); - int actualNumberOfLanguageDefinitionsMarkedAsOfficial = 0; - int actualDefaultLanguageCount = 0; - foreach (IAlamoLanguageDefinition alamoLanguageDefinition in l) - { - if (alamoLanguageDefinition.GetType() - .GetAttributeValueOrDefault((OfficiallySupportedLanguageAttribute o) => o.IsOfficiallySupported)) - { - actualNumberOfLanguageDefinitionsMarkedAsOfficial++; - } - - if (alamoLanguageDefinition.GetType() - .GetAttributeValueOrDefault((DefaultAttribute d) => d.IsDefault)) - { - actualDefaultLanguageCount++; - } - } - Assert.AreEqual(LocalisationTestConstants.REGISTERED_LANGUAGE_DEFINITIONS.Count, actualNumberOfLanguageDefinitionsMarkedAsOfficial, - "Someone added a language definition marked as official. This should not happen. Please remove the OfficiallySupportedLanguageAttribute from the offending language."); - Assert.AreEqual(1, actualDefaultLanguageCount, - "Someone added a language definition marked as default. This should not happen. Please remove the DefaultAttribute from the offending language."); - - } - - [TestMethod] - public void Test_GetDefaultAlamoLanguageDefinition__ReturnsCorrectLanguage() - { - IAlamoLanguageDefinition l = LocalisationUtility.GetDefaultAlamoLanguageDefinition(); - Assert.AreEqual(LocalisationTestConstants.DEFAULT_LANGUAGE, l.GetType()); - IAlamoLanguageDefinition actual = Activator.CreateInstance(LocalisationTestConstants.DEFAULT_LANGUAGE) as IAlamoLanguageDefinition; - Assert.AreEqual(actual, l); - } - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Commons/DefaultAlamoLanguageDefinitionException.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Commons/DefaultAlamoLanguageDefinitionException.cs deleted file mode 100644 index 79140dd13..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Commons/DefaultAlamoLanguageDefinitionException.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using System; -using System.Runtime.Serialization; - -namespace PG.StarWarsGame.Localisation.Commons -{ - /// - [Serializable] - public sealed class DefaultAlamoLanguageDefinitionException : Exception - { - public DefaultAlamoLanguageDefinitionException() - { - } - - public DefaultAlamoLanguageDefinitionException(string message) : base(message) - { - } - - public DefaultAlamoLanguageDefinitionException(string message, Exception innerException) : base(message, - innerException) - { - } - - public DefaultAlamoLanguageDefinitionException(SerializationInfo info, StreamingContext context) : base(info, - context) - { - } - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/TranslationProjectDescriptor.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/TranslationProjectDescriptor.cs new file mode 100644 index 000000000..1de8dd0d2 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/TranslationProjectDescriptor.cs @@ -0,0 +1,73 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using PG.StarWarsGame.Localisation.Data.Config.v2; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data.Config +{ + /// + /// Immutable descriptor for a translation project. + /// + public sealed class TranslationProjectDescriptor : IEquatable + { + private readonly HashSet _languagesSet; + + /// Gets the game this project provides translations for. + public GameContext Game { get; } + + /// Gets whether this project provides core, expansion, or mod text. + public OverrideType OverrideType { get; } + + /// Gets the file format used to store translation resources. + public TranslationResourceType ResourceType { get; } + + /// Gets the languages covered by this translation project. + public IReadOnlyCollection Languages { get; } + + /// Initialises a new . + public TranslationProjectDescriptor( + GameContext game, + OverrideType overrideType, + TranslationResourceType resourceType, + IEnumerable languages) + { + if (languages is null) + throw new ArgumentNullException(nameof(languages)); + + _languagesSet = new HashSet(languages); + Languages = new ReadOnlyCollection(_languagesSet.ToList()); + Game = game; + OverrideType = overrideType; + ResourceType = resourceType; + } + + /// + public bool Equals(TranslationProjectDescriptor? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Game == other.Game + && OverrideType == other.OverrideType + && ResourceType == other.ResourceType + && _languagesSet.SetEquals(other._languagesSet); + } + + /// + public override bool Equals(object? obj) => + obj is TranslationProjectDescriptor other && Equals(other); + + /// + public override int GetHashCode() + { + var hash = HashCode.Combine((int)Game, (int)OverrideType, (int)ResourceType); + foreach (var lang in _languagesSet.OrderBy(l => l.LanguageIdentifier, StringComparer.Ordinal)) + hash = HashCode.Combine(hash, lang.GetHashCode()); + return hash; + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/LocalisationDataType.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/LocalisationDataType.cs index 79f86b4f1..9fe37d107 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/LocalisationDataType.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/LocalisationDataType.cs @@ -1,22 +1,30 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using System; +using System.CodeDom.Compiler; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Xml.Serialization; + namespace PG.StarWarsGame.Localisation.Data.Config.v1 { /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://www.example.org/eaw-translation/")] - [System.Xml.Serialization.XmlRootAttribute("LocalisationData", Namespace = "http://www.example.org/eaw-translation/", IsNullable = false)] - public class LocalisationDataType : object, System.ComponentModel.INotifyPropertyChanged + [ExcludeFromCodeCoverage] + [GeneratedCode("xsd", "4.8.3928.0")] + [Serializable] + [DebuggerStepThrough] + [DesignerCategory("code")] + [XmlType(Namespace = "urn:alamoenginetools:localisation:v1")] + [XmlRoot("LocalisationData", Namespace = "urn:alamoenginetools:localisation:v1", IsNullable = false)] + public class LocalisationDataType : object, INotifyPropertyChanged { - private LocalisationType[] m_localisationField; + private LocalisationType[]? m_localisationField; /// - [System.Xml.Serialization.XmlElementAttribute("Localisation", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] - public LocalisationType[] Localisation + [XmlElement("Localisation", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + public LocalisationType[]? Localisation { get => m_localisationField; set @@ -26,12 +34,12 @@ public LocalisationType[] Localisation } } - public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + /// + public event PropertyChangedEventHandler? PropertyChanged; - protected void RaisePropertyChanged(string propertyName) + private void RaisePropertyChanged(string propertyName) { - System.ComponentModel.PropertyChangedEventHandler propertyChanged = PropertyChanged; - propertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/LocalisationType.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/LocalisationType.cs index 3d23a9572..f8d203bb7 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/LocalisationType.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/LocalisationType.cs @@ -1,24 +1,31 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using System; +using System.CodeDom.Compiler; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Xml.Serialization; + namespace PG.StarWarsGame.Localisation.Data.Config.v1 { /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://www.example.org/eaw-translation/")] - public class LocalisationType : object, System.ComponentModel.INotifyPropertyChanged + [ExcludeFromCodeCoverage] + [GeneratedCode("xsd", "4.8.3928.0")] + [Serializable] + [DebuggerStepThrough] + [DesignerCategory("code")] + [XmlType(Namespace = "urn:alamoenginetools:localisation:v1")] + public class LocalisationType : object, INotifyPropertyChanged { - private TranslationType[] m_translationDataField; - - private string m_keyField; + private TranslationType[]? m_translationDataField; + private string? m_keyField; /// - [System.Xml.Serialization.XmlArrayAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] - [System.Xml.Serialization.XmlArrayItemAttribute("Translation", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = false)] - public TranslationType[] TranslationData + [XmlArray(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] + [XmlArrayItem("Translation", Form = System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable = false)] + public TranslationType[]? TranslationData { get => m_translationDataField; set @@ -29,8 +36,8 @@ public TranslationType[] TranslationData } /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Key + [XmlAttribute] + public string? Key { get => m_keyField; set @@ -40,12 +47,12 @@ public string Key } } - public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + /// + public event PropertyChangedEventHandler? PropertyChanged; - protected void RaisePropertyChanged(string propertyName) + private void RaisePropertyChanged(string propertyName) { - System.ComponentModel.PropertyChangedEventHandler propertyChanged = PropertyChanged; - propertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/TranslationType.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/TranslationType.cs index 2284d564d..576192ce5 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/TranslationType.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v1/TranslationType.cs @@ -16,15 +16,15 @@ namespace PG.StarWarsGame.Localisation.Data.Config.v1 [Serializable] [DebuggerStepThrough] [DesignerCategory("code")] - [XmlType(Namespace = "http://www.example.org/eaw-translation/")] + [XmlType(Namespace = "urn:alamoenginetools:localisation:v1")] public class TranslationType : object, INotifyPropertyChanged { - private string m_languageField; - private string m_valueField; + private string? m_languageField; + private string? m_valueField; /// [XmlAttribute] - public string Language + public string? Language { get => m_languageField; set @@ -36,7 +36,7 @@ public string Language /// [XmlText] - public string Value + public string? Value { get => m_valueField; set @@ -46,12 +46,12 @@ public string Value } } - public event PropertyChangedEventHandler PropertyChanged; + /// + public event PropertyChangedEventHandler? PropertyChanged; - protected void RaisePropertyChanged(string propertyName) + private void RaisePropertyChanged(string propertyName) { - PropertyChangedEventHandler propertyChanged = PropertyChanged; - propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/GameType.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/GameType.cs deleted file mode 100644 index 9faa4f68b..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/GameType.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -namespace PG.StarWarsGame.Localisation.Data.Config.v2 -{ - public enum GameType - { - EaW, - FoC, - Mod - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/OverrideType.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/OverrideType.cs index 7894872fc..af76ae788 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/OverrideType.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/OverrideType.cs @@ -3,10 +3,16 @@ namespace PG.StarWarsGame.Localisation.Data.Config.v2 { + /// + /// Specifies whether a translation project provides core game text, expansion text, or mod text. + /// public enum OverrideType { + /// Core base-game translation resources. Core, + /// Expansion translation resources. Expansion, + /// Mod-specific translation resources. Mod } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/TranslationResourceType.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/TranslationResourceType.cs index d107cc13a..6b0aa14a9 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/TranslationResourceType.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/TranslationResourceType.cs @@ -3,9 +3,18 @@ namespace PG.StarWarsGame.Localisation.Data.Config.v2 { + /// + /// Specifies the file format used to store translation resources. + /// public enum TranslationResourceType { + /// Binary Petroglyph DAT format. + Dat, + /// XML format conforming to the eaw-translation schema. + Xml, + /// CSV format with a header row of language identifiers. Csv, + /// Java-style .properties format (one file per language). Nls } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IKeyedTranslationDatabase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IKeyedTranslationDatabase.cs new file mode 100644 index 000000000..9c5889b11 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IKeyedTranslationDatabase.cs @@ -0,0 +1,28 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Diagnostics.CodeAnalysis; + +namespace PG.StarWarsGame.Localisation.Data +{ + /// + /// A translation database where keys are unique (MasterText / mastertextfile semantics). + /// upserts by key. + /// + public interface IKeyedTranslationDatabase : ITranslationDatabase + { + /// Returns if an entry with exists. + bool ContainsKey(string key); + + /// Attempts to retrieve the entry for . + bool TryGetEntry(string key, [NotNullWhen(true)] out TranslationEntry? entry); + + /// + /// Attempts to retrieve the translation for using . + /// + /// + /// is . + /// + bool TryGetTranslation(string key, [MaybeNullWhen(false)] out string? value); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IOrderedTranslationDatabase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IOrderedTranslationDatabase.cs new file mode 100644 index 000000000..2cf806024 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IOrderedTranslationDatabase.cs @@ -0,0 +1,24 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Collections.Generic; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data +{ + /// + /// A translation database where insertion order is preserved and duplicate keys are allowed + /// (Credits / creditstext semantics). + /// always appends a new entry. + /// + public interface IOrderedTranslationDatabase : ITranslationDatabase + { + /// + /// Inserts a new entry at with an initial translation. + /// + void InsertAt(int index, string key, IAlamoLanguageDefinition language, string value); + + /// Returns all entries whose key equals . + IReadOnlyList GetAllEntriesForKey(string key); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabase.cs new file mode 100644 index 000000000..8c5e78b90 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabase.cs @@ -0,0 +1,40 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Collections.Generic; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data +{ + /// + /// An in-memory store of objects for one or more languages. + /// Changes are fully decoupled from any file format. + /// + public interface ITranslationDatabase : IReadOnlyList + { + /// Gets the languages tracked by this database. + IReadOnlyList Languages { get; } + + /// + /// Adds or updates the translation value for in . + /// + /// if a new entry was created; if an existing entry was updated. + bool SetTranslation(string key, IAlamoLanguageDefinition language, string value); + + /// Removes the entry identified by . + /// if an entry was removed; otherwise . + bool RemoveEntry(string key); + + /// Removes all entries from the database. + void Clear(); + + /// + /// Gets or sets the language used by single-language convenience operations. + /// Must be one of , or to clear. + /// + /// + /// Set to a language not present in . + /// + IAlamoLanguageDefinition? ActiveLanguage { get; set; } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseBuilder.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseBuilder.cs new file mode 100644 index 000000000..777bb2ee8 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseBuilder.cs @@ -0,0 +1,44 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Collections.Generic; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data +{ + /// + /// A fluent builder for constructing instances. + /// + public interface ITranslationDatabaseBuilder + { + /// Adds a single language to the database. + /// is . + ITranslationDatabaseBuilder WithLanguage(IAlamoLanguageDefinition language); + + /// Adds multiple languages to the database. + /// is . + ITranslationDatabaseBuilder WithLanguages(IEnumerable languages); + + /// + /// Sets the active language for the database without adding it to the language list. + /// Validation that the language is present in the accumulated language list occurs at build time. + /// + ITranslationDatabaseBuilder SetActiveLanguage(IAlamoLanguageDefinition language); + + /// + /// Builds a keyed translation database (unique keys, MasterText semantics). + /// + /// + /// The active language (if set) is not in the accumulated language list. + /// + IKeyedTranslationDatabase BuildKeyed(); + + /// + /// Builds an ordered translation database (duplicate keys allowed, Credits semantics). + /// + /// + /// The active language (if set) is not in the accumulated language list. + /// + IOrderedTranslationDatabase BuildOrdered(); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseFactory.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseFactory.cs new file mode 100644 index 000000000..29e17acc5 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseFactory.cs @@ -0,0 +1,30 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Collections.Generic; +using PG.StarWarsGame.Localisation.Data.Config; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data +{ + /// + /// Creates empty translation databases for a given set of languages. + /// + public interface ITranslationDatabaseFactory + { + /// Creates a keyed database (unique keys, MasterText semantics). + IKeyedTranslationDatabase CreateKeyed(IReadOnlyList languages); + + /// Creates a keyed database using the languages from . + IKeyedTranslationDatabase CreateKeyed(TranslationProjectDescriptor descriptor); + + /// Creates an ordered database (duplicate keys allowed, Credits semantics). + IOrderedTranslationDatabase CreateOrdered(IReadOnlyList languages); + + /// Creates an ordered database using the languages from . + IOrderedTranslationDatabase CreateOrdered(TranslationProjectDescriptor descriptor); + + /// Returns a fluent builder for constructing a translation database. + ITranslationDatabaseBuilder CreateDatabase(); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/KeyedTranslationDatabase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/KeyedTranslationDatabase.cs new file mode 100644 index 000000000..a6b78b5b2 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/KeyedTranslationDatabase.cs @@ -0,0 +1,67 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data.Internal +{ + internal sealed class KeyedTranslationDatabase : TranslationDatabaseBase, IKeyedTranslationDatabase + { + private readonly Dictionary _entries = new(StringComparer.Ordinal); + + public override int Count => _entries.Count; + + public override TranslationEntry this[int index] => _entries.Values.ElementAt(index); + + internal KeyedTranslationDatabase(IReadOnlyList languages) + : base(languages) { } + + public override bool SetTranslation(string key, IAlamoLanguageDefinition language, string value) + { + if (key is null) throw new ArgumentNullException(nameof(key)); + if (language is null) throw new ArgumentNullException(nameof(language)); + + if (_entries.TryGetValue(key, out var existing)) + { + existing.SetTranslation(language, value); + return false; + } + + var entry = new TranslationEntry(key); + entry.SetTranslation(language, value); + _entries[key] = entry; + return true; + } + + public override bool RemoveEntry(string key) => _entries.Remove(key); + + public override void Clear() => _entries.Clear(); + + public bool ContainsKey(string key) => _entries.ContainsKey(key); + + public bool TryGetEntry(string key, [NotNullWhen(true)] out TranslationEntry? entry) + { + return _entries.TryGetValue(key, out entry); + } + + public bool TryGetTranslation(string key, [MaybeNullWhen(false)] out string? value) + { + if (ActiveLanguage is null) + throw new InvalidOperationException( + "ActiveLanguage must be set before calling TryGetTranslation(string, out string?)."); + if (!TryGetEntry(key, out var entry)) + { + value = null; + return false; + } + return entry.TryGetTranslation(ActiveLanguage, out value); + } + + public override IEnumerator GetEnumerator() => + _entries.Values.GetEnumerator(); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/OrderedTranslationDatabase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/OrderedTranslationDatabase.cs new file mode 100644 index 000000000..a07f6655e --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/OrderedTranslationDatabase.cs @@ -0,0 +1,59 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Linq; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data.Internal +{ + internal sealed class OrderedTranslationDatabase : TranslationDatabaseBase, IOrderedTranslationDatabase + { + private readonly List _entries = new(); + + public override int Count => _entries.Count; + + public override TranslationEntry this[int index] => _entries[index]; + + internal OrderedTranslationDatabase(IReadOnlyList languages) + : base(languages) { } + + public override bool SetTranslation(string key, IAlamoLanguageDefinition language, string value) + { + if (key is null) throw new ArgumentNullException(nameof(key)); + if (language is null) throw new ArgumentNullException(nameof(language)); + + var entry = new TranslationEntry(key); + entry.SetTranslation(language, value); + _entries.Add(entry); + return true; + } + + public override bool RemoveEntry(string key) + { + var idx = _entries.FindIndex(e => e.Key == key); + if (idx < 0) return false; + _entries.RemoveAt(idx); + return true; + } + + public override void Clear() => _entries.Clear(); + + public void InsertAt(int index, string key, IAlamoLanguageDefinition language, string value) + { + if (key is null) throw new ArgumentNullException(nameof(key)); + if (language is null) throw new ArgumentNullException(nameof(language)); + + var entry = new TranslationEntry(key); + entry.SetTranslation(language, value); + _entries.Insert(index, entry); + } + + public IReadOnlyList GetAllEntriesForKey(string key) => + _entries.Where(e => e.Key == key).ToList().AsReadOnly(); + + public override IEnumerator GetEnumerator() => + _entries.GetEnumerator(); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationDatabaseBase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationDatabaseBase.cs new file mode 100644 index 000000000..af66b3fb4 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationDatabaseBase.cs @@ -0,0 +1,44 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data.Internal +{ + internal abstract class TranslationDatabaseBase : ITranslationDatabase + { + private IAlamoLanguageDefinition? _activeLanguage; + + public IReadOnlyList Languages { get; } + + public IAlamoLanguageDefinition? ActiveLanguage + { + get => _activeLanguage; + set + { + if (value is not null && !Languages.Any(l => l.Equals(value))) + throw new ArgumentException( + $"Language '{value.LanguageIdentifier}' is not registered in this database.", nameof(value)); + _activeLanguage = value; + } + } + + protected TranslationDatabaseBase(IReadOnlyList languages) + { + Languages = languages; + } + + public abstract int Count { get; } + public abstract TranslationEntry this[int index] { get; } + public abstract bool SetTranslation(string key, IAlamoLanguageDefinition language, string value); + public abstract bool RemoveEntry(string key); + public abstract void Clear(); + public abstract IEnumerator GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationDatabaseBuilder.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationDatabaseBuilder.cs new file mode 100644 index 000000000..f2dab8da2 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationDatabaseBuilder.cs @@ -0,0 +1,62 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Linq; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data.Internal +{ + internal sealed class TranslationDatabaseBuilder : ITranslationDatabaseBuilder + { + private readonly List _languages = new List(); + private IAlamoLanguageDefinition? _activeLanguage; + + public ITranslationDatabaseBuilder WithLanguage(IAlamoLanguageDefinition language) + { + if (language is null) throw new ArgumentNullException(nameof(language)); + _languages.Add(language); + return this; + } + + public ITranslationDatabaseBuilder WithLanguages(IEnumerable languages) + { + if (languages is null) throw new ArgumentNullException(nameof(languages)); + _languages.AddRange(languages); + return this; + } + + public ITranslationDatabaseBuilder SetActiveLanguage(IAlamoLanguageDefinition language) + { + _activeLanguage = language; + return this; + } + + public IKeyedTranslationDatabase BuildKeyed() + { + var langs = _languages.ToList().AsReadOnly(); + var db = new KeyedTranslationDatabase(langs); + ApplyActiveLanguage(db); + return db; + } + + public IOrderedTranslationDatabase BuildOrdered() + { + var langs = _languages.ToList().AsReadOnly(); + var db = new OrderedTranslationDatabase(langs); + ApplyActiveLanguage(db); + return db; + } + + private void ApplyActiveLanguage(ITranslationDatabase db) + { + if (_activeLanguage is null) return; + if (!db.Languages.Contains(_activeLanguage)) + throw new ArgumentException( + $"Active language '{_activeLanguage.LanguageIdentifier}' was not added to this builder.", + nameof(_activeLanguage)); + db.ActiveLanguage = _activeLanguage; + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Localisation/LocalisationBean.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Localisation/LocalisationBean.cs deleted file mode 100644 index 040b84d1e..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Localisation/LocalisationBean.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using PG.Core.Data.Bean; -using System.Diagnostics.CodeAnalysis; - -namespace PG.StarWarsGame.Localisation.Data.Localisation -{ - public class LocalisationBean : AbstractBean - { - public string Localisation { get; set; } - - public LocalisationBean([NotNull] LocalisationKey key, string localisation) : base(key) - { - Localisation = localisation; - } - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Localisation/LocalisationKey.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Localisation/LocalisationKey.cs deleted file mode 100644 index ac08080d6..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Localisation/LocalisationKey.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using PG.Core.Data.Key; -using PG.Core.Localisation; - -namespace PG.StarWarsGame.Localisation.Data.Localisation -{ - public class LocalisationKey : AbstractKey - { - public LocalisationKey(IAlamoLanguageDefinition key) : base(key) - { - } - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Translation/TranslationBean.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Translation/TranslationBean.cs deleted file mode 100644 index 53c462614..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Translation/TranslationBean.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using PG.Core.Data.Bean; -using PG.Core.Data.Repository; -using PG.StarWarsGame.Localisation.Data.Localisation; - -namespace PG.StarWarsGame.Localisation.Data.Translation -{ - public class TranslationBean : AbstractInMemoryKeyValuePairRepository, - IBean - { - public TranslationKey Key { get; } - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Translation/TranslationKey.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Translation/TranslationKey.cs deleted file mode 100644 index d37f0d783..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Translation/TranslationKey.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using PG.Core.Data.Key; - -namespace PG.StarWarsGame.Localisation.Data.Translation -{ - public class TranslationKey : AbstractStringKey - { - public TranslationKey(string key) : base(key) - { - } - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationDatabaseFactory.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationDatabaseFactory.cs new file mode 100644 index 000000000..eed41778b --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationDatabaseFactory.cs @@ -0,0 +1,49 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Linq; +using PG.StarWarsGame.Localisation.Data.Config; +using PG.StarWarsGame.Localisation.Data.Internal; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data +{ + /// + /// Default implementation of . + /// + public sealed class TranslationDatabaseFactory : ITranslationDatabaseFactory + { + /// + public IKeyedTranslationDatabase CreateKeyed(IReadOnlyList languages) + { + if (languages is null) throw new ArgumentNullException(nameof(languages)); + return new KeyedTranslationDatabase(languages); + } + + /// + public IKeyedTranslationDatabase CreateKeyed(TranslationProjectDescriptor descriptor) + { + if (descriptor is null) throw new ArgumentNullException(nameof(descriptor)); + return CreateKeyed(descriptor.Languages.ToList()); + } + + /// + public IOrderedTranslationDatabase CreateOrdered(IReadOnlyList languages) + { + if (languages is null) throw new ArgumentNullException(nameof(languages)); + return new OrderedTranslationDatabase(languages); + } + + /// + public IOrderedTranslationDatabase CreateOrdered(TranslationProjectDescriptor descriptor) + { + if (descriptor is null) throw new ArgumentNullException(nameof(descriptor)); + return CreateOrdered(descriptor.Languages.ToList()); + } + + /// + public ITranslationDatabaseBuilder CreateDatabase() => new TranslationDatabaseBuilder(); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationEntry.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationEntry.cs new file mode 100644 index 000000000..4b01965c0 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationEntry.cs @@ -0,0 +1,43 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data +{ + /// + /// A single translation entry: a string key with zero or more language-specific values. + /// + public sealed class TranslationEntry + { + private readonly Dictionary _translations; + + /// Gets the translation key. + public string Key { get; } + + /// Gets all available translations, keyed by language definition. + public IReadOnlyDictionary Translations => _translations; + + internal TranslationEntry(string key) + { + Key = key ?? throw new ArgumentNullException(nameof(key)); + _translations = new Dictionary(); + } + + internal void SetTranslation(IAlamoLanguageDefinition language, string value) + { + _translations[language] = value; + } + + /// + /// Attempts to retrieve the translation value for . + /// + public bool TryGetTranslation(IAlamoLanguageDefinition language, [MaybeNullWhen(false)] out string? value) + { + return _translations.TryGetValue(language, out value); + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationExporter.cs new file mode 100644 index 000000000..c2a0f96f3 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationExporter.cs @@ -0,0 +1,62 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using CsvHelper; +using CsvHelper.Configuration; +using PG.StarWarsGame.Localisation.Data; + +namespace PG.StarWarsGame.Localisation.IO.Csv +{ + /// + /// Default implementation of . + /// Produces a CSV with header row: key,LANG1,LANG2,... + /// + public sealed class CsvTranslationExporter : ICsvTranslationExporter + { + /// + public string Export(ITranslationDatabase source) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + + var config = new CsvConfiguration(CultureInfo.InvariantCulture) + { + HasHeaderRecord = false, + NewLine = "\n", + }; + + var sb = new StringBuilder(); + using var writer = new StringWriter(sb); + using var csv = new CsvWriter(writer, config); + + var langs = source.Languages; + + csv.WriteField("key"); + foreach (var lang in langs) + csv.WriteField(lang.LanguageIdentifier); + csv.NextRecord(); + + var rows = source is IKeyedTranslationDatabase + ? source.OrderBy(e => e.Key, StringComparer.Ordinal) + : source.AsEnumerable(); + + foreach (var entry in rows) + { + csv.WriteField(entry.Key); + foreach (var lang in langs) + { + entry.TryGetTranslation(lang, out var val); + csv.WriteField(val ?? string.Empty); + } + csv.NextRecord(); + } + + writer.Flush(); + return sb.ToString(); + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationImporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationImporter.cs new file mode 100644 index 000000000..b027a6885 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationImporter.cs @@ -0,0 +1,74 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using CsvHelper; +using CsvHelper.Configuration; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; +using PG.StarWarsGame.Localisation.Services; + +namespace PG.StarWarsGame.Localisation.IO.Csv +{ + /// + /// Default implementation of . + /// Expects a CSV with header row: key,LANG1,LANG2,... + /// + public sealed class CsvTranslationImporter : ICsvTranslationImporter + { + private readonly ILanguageService _languageService; + + /// Initialises a new instance with the given language service. + public CsvTranslationImporter(ILanguageService languageService) + { + _languageService = languageService ?? throw new ArgumentNullException(nameof(languageService)); + } + + /// + public void Import(TextReader source, ITranslationDatabase target) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + if (target is null) throw new ArgumentNullException(nameof(target)); + + var config = new CsvConfiguration(CultureInfo.InvariantCulture) + { + HasHeaderRecord = true, + MissingFieldFound = null, + }; + + using var csv = new CsvReader(source, config); + + csv.Read(); + csv.ReadHeader(); + var headers = csv.HeaderRecord; + if (headers is null || headers.Length < 2) return; + + var langColumns = new IAlamoLanguageDefinition?[headers.Length - 1]; + for (var i = 1; i < headers.Length; i++) + { + _languageService.TryGetByIdentifier(headers[i], out langColumns[i - 1]); + } + + var dbLanguages = target.Languages; + + while (csv.Read()) + { + var key = csv.GetField(0); + if (key is null) continue; + + for (var i = 1; i < headers.Length; i++) + { + var lang = langColumns[i - 1]; + if (lang is null) continue; + if (!dbLanguages.Any(l => l.Equals(lang))) continue; + var val = csv.GetField(i); + if (val is null) continue; + target.SetTranslation(key, lang, val); + } + } + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/ICsvTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/ICsvTranslationExporter.cs new file mode 100644 index 000000000..11a6089f4 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/ICsvTranslationExporter.cs @@ -0,0 +1,13 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +namespace PG.StarWarsGame.Localisation.IO.Csv +{ + /// + /// Exports all languages from a translation database to a CSV string. + /// Schema: first column is key, remaining columns are language identifiers. + /// + public interface ICsvTranslationExporter : IMultiLanguageTranslationExporter + { + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/ICsvTranslationImporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/ICsvTranslationImporter.cs new file mode 100644 index 000000000..2e0f4203a --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/ICsvTranslationImporter.cs @@ -0,0 +1,15 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.IO; + +namespace PG.StarWarsGame.Localisation.IO.Csv +{ + /// + /// Imports multi-language translation data from a CSV text reader. + /// Schema: first column is key, remaining columns are language identifiers. + /// + public interface ICsvTranslationImporter : IMultiLanguageTranslationImporter + { + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationExporter.cs new file mode 100644 index 000000000..df73dc2d3 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationExporter.cs @@ -0,0 +1,46 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using PG.StarWarsGame.Files.DAT.Data; +using PG.StarWarsGame.Files.DAT.Services.Builder; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.IO.Dat +{ + /// + /// Default implementation of . + /// Produces a sorted (CRC32-ordered) model for + /// and an unsorted model for . + /// + public sealed class DatTranslationExporter : IDatTranslationExporter + { + private readonly IServiceProvider _services; + + /// Initialises a new instance with the given service provider. + public DatTranslationExporter(IServiceProvider services) + { + _services = services ?? throw new ArgumentNullException(nameof(services)); + } + + /// + public IDatModel Export(ITranslationDatabase source, IAlamoLanguageDefinition language) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + if (language is null) throw new ArgumentNullException(nameof(language)); + + IDatBuilder builder = source is IKeyedTranslationDatabase + ? new EmpireAtWarMasterTextBuilder(false, _services) + : new EmpireAtWarCreditsTextBuilder(_services); + + foreach (var entry in source) + { + if (entry.TryGetTranslation(language, out var value) && !string.IsNullOrEmpty(value)) + builder.AddEntry(entry.Key, value!); + } + + return builder.BuildModel(); + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationImporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationImporter.cs new file mode 100644 index 000000000..269fbf264 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationImporter.cs @@ -0,0 +1,27 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using PG.StarWarsGame.Files.DAT.Data; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.IO.Dat +{ + /// + /// Default implementation of . + /// + public sealed class DatTranslationImporter : IDatTranslationImporter + { + /// + public void Import(IDatModel source, IAlamoLanguageDefinition language, ITranslationDatabase target) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + if (language is null) throw new ArgumentNullException(nameof(language)); + if (target is null) throw new ArgumentNullException(nameof(target)); + + foreach (var entry in source) + target.SetTranslation(entry.Key, language, entry.Value); + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/IDatTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/IDatTranslationExporter.cs new file mode 100644 index 000000000..bc777d39b --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/IDatTranslationExporter.cs @@ -0,0 +1,14 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using PG.StarWarsGame.Files.DAT.Data; + +namespace PG.StarWarsGame.Localisation.IO.Dat +{ + /// + /// Exports a single language from a translation database to a DAT model. + /// + public interface IDatTranslationExporter : ITranslationExporter + { + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/IDatTranslationImporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/IDatTranslationImporter.cs new file mode 100644 index 000000000..9529eb01a --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/IDatTranslationImporter.cs @@ -0,0 +1,14 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using PG.StarWarsGame.Files.DAT.Data; + +namespace PG.StarWarsGame.Localisation.IO.Dat +{ + /// + /// Imports translation data from a binary DAT model into a translation database. + /// + public interface IDatTranslationImporter : ITranslationImporter + { + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/ITranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/ITranslationExporter.cs new file mode 100644 index 000000000..9c1a33a88 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/ITranslationExporter.cs @@ -0,0 +1,31 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.IO +{ + /// + /// Exports single-language translation data from a database to . + /// + public interface ITranslationExporter + { + /// + /// Serialises the translations for from + /// into . + /// + TResult Export(ITranslationDatabase source, IAlamoLanguageDefinition language); + } + + /// + /// Exports multi-language translation data from a database to . + /// + public interface IMultiLanguageTranslationExporter + { + /// + /// Serialises all languages and entries from into . + /// + TResult Export(ITranslationDatabase source); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/ITranslationImporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/ITranslationImporter.cs new file mode 100644 index 000000000..fd9c7c7be --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/ITranslationImporter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.IO +{ + /// + /// Imports single-language translation data from into a database. + /// + public interface ITranslationImporter + { + /// + /// Reads and adds all entries for + /// into . + /// + void Import(TSource source, IAlamoLanguageDefinition language, ITranslationDatabase target); + } + + /// + /// Imports multi-language translation data from into a database. + /// + public interface IMultiLanguageTranslationImporter + { + /// + /// Reads and adds all languages and entries into . + /// Languages not tracked by are skipped. + /// + void Import(TSource source, ITranslationDatabase target); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/IPropertiesTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/IPropertiesTranslationExporter.cs new file mode 100644 index 000000000..d76ddab5f --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/IPropertiesTranslationExporter.cs @@ -0,0 +1,12 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +namespace PG.StarWarsGame.Localisation.IO.Properties +{ + /// + /// Exports a single language from a translation database to a Java-style .properties string. + /// + public interface IPropertiesTranslationExporter : ITranslationExporter + { + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/IPropertiesTranslationImporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/IPropertiesTranslationImporter.cs new file mode 100644 index 000000000..811b8d8a0 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/IPropertiesTranslationImporter.cs @@ -0,0 +1,15 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.IO; + +namespace PG.StarWarsGame.Localisation.IO.Properties +{ + /// + /// Imports single-language translation data from a Java-style .properties text reader. + /// Format: KEY=Value, lines beginning with # are comments. + /// + public interface IPropertiesTranslationImporter : ITranslationImporter + { + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationExporter.cs new file mode 100644 index 000000000..6c3d908f4 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationExporter.cs @@ -0,0 +1,44 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Linq; +using System.Text; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.IO.Properties +{ + /// + /// Default implementation of . + /// Produces KEY=Value lines, one per entry. + /// + public sealed class PropertiesTranslationExporter : IPropertiesTranslationExporter + { + /// + public string Export(ITranslationDatabase source, IAlamoLanguageDefinition language) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + if (language is null) throw new ArgumentNullException(nameof(language)); + + var rows = source is IKeyedTranslationDatabase + ? source.OrderBy(e => e.Key, StringComparer.Ordinal).AsEnumerable() + : source.AsEnumerable(); + + var sb = new StringBuilder(); + foreach (var entry in rows) + { + if (entry.TryGetTranslation(language, out var value) && value is not null) + { + sb.Append(EscapeKey(entry.Key)); + sb.Append('='); + sb.AppendLine(value); + } + } + return sb.ToString(); + } + + private static string EscapeKey(string key) => + key.Replace("=", "\\=").Replace(":", "\\:").Replace("#", "\\#"); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationImporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationImporter.cs new file mode 100644 index 000000000..40fbfe4ec --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationImporter.cs @@ -0,0 +1,43 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.IO; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.IO.Properties +{ + /// + /// Default implementation of . + /// Reads KEY=Value lines; lines starting with # are comments and are skipped. + /// Only the first = is treated as a separator; the value may contain further = characters. + /// + public sealed class PropertiesTranslationImporter : IPropertiesTranslationImporter + { + /// + public void Import(TextReader source, IAlamoLanguageDefinition language, ITranslationDatabase target) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + if (language is null) throw new ArgumentNullException(nameof(language)); + if (target is null) throw new ArgumentNullException(nameof(target)); + + string? line; + while ((line = source.ReadLine()) is not null) + { + var trimmed = line.TrimStart(); + if (trimmed.Length == 0 || trimmed[0] == '#') continue; + + var separator = trimmed.IndexOf('='); + if (separator < 0) continue; + + var key = UnescapeKey(trimmed.Substring(0, separator)); + var value = trimmed.Substring(separator + 1); + target.SetTranslation(key, language, value); + } + } + + private static string UnescapeKey(string key) => + key.Replace("\\=", "=").Replace("\\:", ":").Replace("\\#", "#"); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/IXmlTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/IXmlTranslationExporter.cs new file mode 100644 index 000000000..42e450f2a --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/IXmlTranslationExporter.cs @@ -0,0 +1,14 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Xml.Linq; + +namespace PG.StarWarsGame.Localisation.IO.Xml +{ + /// + /// Exports all languages from a translation database to an eaw-translation XML document. + /// + public interface IXmlTranslationExporter : IMultiLanguageTranslationExporter + { + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/IXmlTranslationImporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/IXmlTranslationImporter.cs new file mode 100644 index 000000000..701840cee --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/IXmlTranslationImporter.cs @@ -0,0 +1,14 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Xml.Linq; + +namespace PG.StarWarsGame.Localisation.IO.Xml +{ + /// + /// Imports multi-language translation data from an eaw-translation XML document. + /// + public interface IXmlTranslationImporter : IMultiLanguageTranslationImporter + { + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs new file mode 100644 index 000000000..8fafeff57 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs @@ -0,0 +1,50 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Linq; +using System.Xml.Linq; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Services; + +namespace PG.StarWarsGame.Localisation.IO.Xml +{ + /// + /// Default implementation of . + /// Produces an eaw-translation v1 XML document. + /// + public sealed class XmlTranslationExporter : IXmlTranslationExporter + { + internal static readonly XNamespace Ns = "urn:alamoenginetools:localisation:v1"; + + /// + public XDocument Export(ITranslationDatabase source) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + + var root = new XElement(Ns + "LocalisationData"); + + var rows = source is IKeyedTranslationDatabase + ? source.OrderBy(e => e.Key, StringComparer.Ordinal).AsEnumerable() + : source.AsEnumerable(); + + foreach (var entry in rows) + { + var locEl = new XElement(Ns + "Localisation", + new XAttribute("key", entry.Key)); + + foreach (var kv in entry.Translations.OrderBy(kv => kv.Key.LanguageIdentifier)) + { + locEl.Add(new XElement(Ns + "TranslationData", + new XElement(Ns + "Translation", + new XAttribute("Language", kv.Key.LanguageIdentifier), + kv.Value))); + } + + root.Add(locEl); + } + + return new XDocument(root); + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationImporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationImporter.cs new file mode 100644 index 000000000..8c621fa39 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationImporter.cs @@ -0,0 +1,59 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Linq; +using System.Xml.Linq; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Services; + +namespace PG.StarWarsGame.Localisation.IO.Xml +{ + /// + /// Default implementation of . + /// Reads eaw-translation v1 XML documents. + /// + public sealed class XmlTranslationImporter : IXmlTranslationImporter + { + private readonly ILanguageService _languageService; + + /// Initialises a new instance with the given language service. + public XmlTranslationImporter(ILanguageService languageService) + { + _languageService = languageService ?? throw new ArgumentNullException(nameof(languageService)); + } + + /// + public void Import(XDocument source, ITranslationDatabase target) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + if (target is null) throw new ArgumentNullException(nameof(target)); + + var ns = XmlTranslationExporter.Ns; + var root = source.Root; + if (root is null) return; + + foreach (var locEl in root.Elements(ns + "Localisation")) + { + var key = locEl.Attribute("key")?.Value; + if (key is null) continue; + + foreach (var dataEl in locEl.Elements(ns + "TranslationData")) + { + foreach (var transEl in dataEl.Elements(ns + "Translation")) + { + var langId = transEl.Attribute("Language")?.Value; + var value = transEl.Value; + + if (langId is null) continue; + if (!_languageService.TryGetByIdentifier(langId, out var lang) || lang is null) + continue; + if (!target.Languages.Contains(lang)) continue; + + target.SetTranslation(key, lang, value); + } + } + } + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs new file mode 100644 index 000000000..b5dcb50d7 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs @@ -0,0 +1,79 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; + +namespace PG.StarWarsGame.Localisation.Languages +{ + /// + /// Base class for all Alamo language definitions. + /// + public abstract class AlamoLanguageDefinitionBase : IAlamoLanguageDefinition + { + private readonly bool _isOfficiallySupported; + private readonly GameContext[] _supportedContexts; + + /// + public string LanguageIdentifier { get; } + + /// + public CultureInfo Culture { get; } + + /// + public bool IsDefault { get; } + + /// + /// Gets the engine's language identifier string. Subclasses return a constant value. + /// + protected abstract string ConfiguredLanguageIdentifier { get; } + + /// + /// Gets the .NET culture for this language. Subclasses return a constant value. + /// + protected abstract CultureInfo ConfiguredCulture { get; } + + /// Initialises the language definition by reading its attributes. + protected AlamoLanguageDefinitionBase() + { + LanguageIdentifier = ConfiguredLanguageIdentifier; + Culture = ConfiguredCulture; + + var attr = (OfficiallySupportedLanguageAttribute?)Attribute.GetCustomAttribute( + GetType(), typeof(OfficiallySupportedLanguageAttribute)); + + _isOfficiallySupported = attr != null; + _supportedContexts = attr?.SupportedContexts ?? Array.Empty(); + IsDefault = GetType().IsDefined(typeof(DefaultLanguageAttribute), false); + } + + /// + public bool IsOfficiallySupported() => _isOfficiallySupported; + + /// + public bool IsOfficiallySupported(GameContext context) + { + if (!_isOfficiallySupported) return false; + return _supportedContexts.Length == 0 || Array.IndexOf(_supportedContexts, context) >= 0; + } + + /// + public bool Equals(IAlamoLanguageDefinition? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(LanguageIdentifier, other.LanguageIdentifier, StringComparison.OrdinalIgnoreCase); + } + + /// + public override bool Equals(object? obj) => obj is IAlamoLanguageDefinition other && Equals(other); + + /// + public override int GetHashCode() => + StringComparer.OrdinalIgnoreCase.GetHashCode(LanguageIdentifier); + + /// + public override string ToString() => LanguageIdentifier; + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/DefaultLanguageAttribute.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/DefaultLanguageAttribute.cs new file mode 100644 index 000000000..c49c232e0 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/DefaultLanguageAttribute.cs @@ -0,0 +1,16 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; + +namespace PG.StarWarsGame.Localisation.Languages.Attributes +{ + /// + /// Marks a language definition as the default game language. + /// Only one concrete implementation may carry this attribute. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public sealed class DefaultLanguageAttribute : Attribute + { + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs new file mode 100644 index 000000000..c56496e10 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs @@ -0,0 +1,28 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; + +namespace PG.StarWarsGame.Localisation.Languages.Attributes +{ + /// + /// Marks a language definition as officially supported by the Alamo engine, + /// optionally restricting support to specific game contexts. + /// When no contexts are specified the language is considered supported in all game contexts. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public sealed class OfficiallySupportedLanguageAttribute : Attribute + { + /// + /// The game contexts in which this language is officially supported. + /// An empty array means the language is supported in all game contexts. + /// + public GameContext[] SupportedContexts { get; } + + /// + public OfficiallySupportedLanguageAttribute(params GameContext[] contexts) + { + SupportedContexts = contexts ?? Array.Empty(); + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ChineseAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ChineseAlamoLanguageDefinition.cs index 24aaf739e..5d7847cef 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ChineseAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ChineseAlamoLanguageDefinition.cs @@ -1,26 +1,22 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { - /// - /// The language definition for the Chinese language. - /// - /// - /// Officially supported by the Alamo Engine. - /// - [Order(5000)] - [ExcludeFromCodeCoverage] + /// The language definition for Chinese (Simplified, CN). + /// Officially supported by the Alamo Engine. [OfficiallySupportedLanguage] - public sealed class ChineseAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + public sealed class ChineseAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "CHINESE"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("zh-CN"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/EnglishAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/EnglishAlamoLanguageDefinition.cs index b40de6b56..fbbe9b900 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/EnglishAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/EnglishAlamoLanguageDefinition.cs @@ -1,28 +1,28 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { /// - /// The language definition for the English (US) language. + /// The language definition for English (US). /// /// /// Officially supported by the Alamo Engine.
- /// This language is the default game language and development language of all Alamo Engine games. + /// This is the default game language and development language of all Alamo Engine games. ///
- [Default] - [Order(5000)] - [ExcludeFromCodeCoverage] + [DefaultLanguage] [OfficiallySupportedLanguage] - public sealed class EnglishAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + public sealed class EnglishAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "ENGLISH"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("en-US"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/FrenchAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/FrenchAlamoLanguageDefinition.cs index f35f750eb..65d07a2f8 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/FrenchAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/FrenchAlamoLanguageDefinition.cs @@ -1,26 +1,22 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { - /// - /// The language definition for the French language. - /// - /// - /// Officially supported by the Alamo Engine. - /// - [Order(5000)] - [ExcludeFromCodeCoverage] + /// The language definition for French (FR). + /// Officially supported by the Alamo Engine. [OfficiallySupportedLanguage] - public sealed class FrenchAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + public sealed class FrenchAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "FRENCH"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("fr-FR"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/GameContext.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/GameContext.cs new file mode 100644 index 000000000..1b37e2046 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/GameContext.cs @@ -0,0 +1,17 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +namespace PG.StarWarsGame.Localisation.Languages +{ + /// + /// Identifies the Petroglyph Star Wars game product. + /// + public enum GameContext + { + /// Star Wars: Empire at War (base game). + EaW, + + /// Star Wars: Empire at War — Forces of Corruption (expansion). + FoC + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/GermanAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/GermanAlamoLanguageDefinition.cs index a751c334f..56f016351 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/GermanAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/GermanAlamoLanguageDefinition.cs @@ -1,26 +1,22 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { - /// - /// The language definition for the German language. - /// - /// - /// Officially supported by the Alamo Engine. - /// - [Order(5000)] - [ExcludeFromCodeCoverage] + /// The language definition for German (DE). + /// Officially supported by the Alamo Engine. [OfficiallySupportedLanguage] - public sealed class GermanAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + public sealed class GermanAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "GERMAN"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("de-DE"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs new file mode 100644 index 000000000..173a50ec9 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs @@ -0,0 +1,41 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Globalization; + +namespace PG.StarWarsGame.Localisation.Languages +{ + /// + /// Describes a language supported by the Alamo game engine. + /// + public interface IAlamoLanguageDefinition : IEquatable + { + /// + /// Gets the engine's internal language identifier (e.g. "ENGLISH"). + /// + string LanguageIdentifier { get; } + + /// + /// Gets the .NET culture associated with this language. + /// + CultureInfo Culture { get; } + + /// + /// Returns if this language is officially supported by the Alamo engine + /// in at least one game context. + /// + bool IsOfficiallySupported(); + + /// + /// Returns if this language is officially supported within the given + /// . + /// + bool IsOfficiallySupported(GameContext context); + + /// + /// Gets a value indicating whether this is the default game language (English). + /// + bool IsDefault { get; } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ItalianAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ItalianAlamoLanguageDefinition.cs index 05f86ce88..d8936f7e5 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ItalianAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ItalianAlamoLanguageDefinition.cs @@ -1,26 +1,22 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { - /// - /// The language definition for the Italian language. - /// - /// - /// Officially supported by the Alamo Engine. - /// - [Order(5000)] - [ExcludeFromCodeCoverage] + /// The language definition for Italian (IT). + /// Officially supported by the Alamo Engine. [OfficiallySupportedLanguage] - public sealed class ItalianAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + public sealed class ItalianAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "ITALIAN"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("it-IT"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs index d7f572a7c..9d7bac474 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs @@ -1,26 +1,22 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { - /// - /// The language definition for the Japanese language. - /// - /// - /// Officially supported by the Alamo Engine. - /// - [Order(5000)] + /// The language definition for Japanese (JA). + /// Officially supported by the Alamo Engine. + [OfficiallySupportedLanguage(GameContext.EaW)] [ExcludeFromCodeCoverage] - [OfficiallySupportedLanguage] - public sealed class JapaneseAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + public sealed class JapaneseAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "JAPANESE"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("ja"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs index 944ea8be4..79509396a 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs @@ -1,26 +1,22 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { - /// - /// The language definition for the Korean language. - /// - /// - /// Officially supported by the Alamo Engine. - /// - [Order(5000)] + /// The language definition for Korean (KO). + /// Officially supported by the Alamo Engine. + [OfficiallySupportedLanguage(GameContext.EaW)] [ExcludeFromCodeCoverage] - [OfficiallySupportedLanguage] - public sealed class KoreanAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + public sealed class KoreanAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "KOREAN"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("ko"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/PolishAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/PolishAlamoLanguageDefinition.cs index bf175b13d..3f977d2cd 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/PolishAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/PolishAlamoLanguageDefinition.cs @@ -1,26 +1,22 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { - /// - /// The language definition for the Polish language. - /// - /// - /// Officially supported by the Alamo Engine. - /// - [Order(5000)] - [ExcludeFromCodeCoverage] + /// The language definition for Polish (PL). + /// Officially supported by the Alamo Engine. [OfficiallySupportedLanguage] - public sealed class PolishAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + public sealed class PolishAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "POLISH"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("pl"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/RussianAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/RussianAlamoLanguageDefinition.cs index 35e3f4107..16861cdb7 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/RussianAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/RussianAlamoLanguageDefinition.cs @@ -1,26 +1,22 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { - /// - /// The language definition for the Russian language. - /// - /// - /// Officially supported by the Alamo Engine. - /// - [Order(5000)] - [ExcludeFromCodeCoverage] + /// The language definition for Russian (RU). + /// Officially supported by the Alamo Engine. [OfficiallySupportedLanguage] - public sealed class RussianAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + public sealed class RussianAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "RUSSIAN"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("ru"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/SpanishAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/SpanishAlamoLanguageDefinition.cs index 841b80196..e43a8282f 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/SpanishAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/SpanishAlamoLanguageDefinition.cs @@ -1,26 +1,22 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { - /// - /// The language definition for the Spanish language. - /// - /// - /// Officially supported by the Alamo Engine. - /// - [Order(5000)] - [ExcludeFromCodeCoverage] + /// The language definition for Spanish (ES). + /// Officially supported by the Alamo Engine. [OfficiallySupportedLanguage] - public sealed class SpanishAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + public sealed class SpanishAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "SPANISH"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("es-ES"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs index f74773cde..c9f76a303 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs @@ -1,26 +1,22 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.Core.Localisation.Attributes; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using PG.StarWarsGame.Localisation.Languages.Attributes; namespace PG.StarWarsGame.Localisation.Languages { - /// - /// The language definition for the Thai language. - /// - /// - /// Officially supported by the Alamo Engine. - /// - [Order(5000)] + /// The language definition for Thai (TH). + /// Officially supported by the Alamo Engine. + [OfficiallySupportedLanguage(GameContext.EaW)] [ExcludeFromCodeCoverage] - [OfficiallySupportedLanguage] - public sealed class ThaiAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + public sealed class ThaiAlamoLanguageDefinition : AlamoLanguageDefinitionBase { + /// protected override string ConfiguredLanguageIdentifier => "THAI"; + + /// protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("th"); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/LocalisationServiceContribution.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/LocalisationServiceContribution.cs new file mode 100644 index 000000000..8ed0b491a --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/LocalisationServiceContribution.cs @@ -0,0 +1,55 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.IO.Csv; +using PG.StarWarsGame.Localisation.IO.Dat; +using PG.StarWarsGame.Localisation.IO.Properties; +using PG.StarWarsGame.Localisation.IO.Xml; +using PG.StarWarsGame.Localisation.Languages; +using PG.StarWarsGame.Localisation.Services; + +namespace PG.StarWarsGame.Localisation +{ + /// + /// Provides initialization routines for the PG.StarWarsGame.Localisation library. + /// + public static class LocalisationServiceContribution + { + /// + /// Adds all services provided by this library to the specified . + /// + public static IServiceCollection SupportLocalisation(this IServiceCollection serviceCollection) + { + // Register all 11 officially supported language definitions + serviceCollection.AddSingleton(new EnglishAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new GermanAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new FrenchAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new SpanishAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new ItalianAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new ChineseAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new JapaneseAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new KoreanAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new PolishAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new RussianAlamoLanguageDefinition()); + serviceCollection.AddSingleton(new ThaiAlamoLanguageDefinition()); + + serviceCollection.AddSingleton(sp => + new LanguageService(sp.GetServices())); + + serviceCollection.AddSingleton(); + + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + + return serviceCollection; + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.csproj b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.csproj index eabd49fb8..08d058bc3 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.csproj +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.csproj @@ -1,52 +1,40 @@ - + - false - netstandard2.0 + netstandard2.0;netstandard2.1;net10.0 + PG.StarWarsGame.Localisation + Provides language definitions, an in-memory translation database, and format adapters (DAT, XML, CSV, Properties) for Petroglyph Star Wars game localisations. + PG.StarWarsGame.Localisation + AlamoEngineTools.PG.StarWarsGame.Localisation + alamo;petroglyph;glyphx;localisation;translation + + + true + true + true + + + true + snupkg - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs new file mode 100644 index 000000000..7ecfcf0e4 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs @@ -0,0 +1,54 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Services +{ + /// + /// Provides lookup and validation operations over the registered Alamo language definitions. + /// + public interface ILanguageService + { + /// Gets all registered language definitions. + IReadOnlyList AllLanguages { get; } + + /// Returns all officially supported language definitions across all game contexts. + IReadOnlyList OfficiallySupported(); + + /// Returns the officially supported language definitions for the given . + IReadOnlyList OfficiallySupported(GameContext context); + + /// + /// Gets the default game language (English). + /// + /// + /// Thrown when no language definition is marked as default. + /// + IAlamoLanguageDefinition Default { get; } + + /// + /// Returns if is officially supported in at least one game context. + /// + /// + /// Thrown when is . + /// + bool IsOfficiallySupported(IAlamoLanguageDefinition language); + + /// + /// Returns if is officially supported in the given . + /// + /// + /// Thrown when is . + /// + bool IsOfficiallySupported(IAlamoLanguageDefinition language, GameContext context); + + /// + /// Attempts to find a language definition whose + /// matches (case-insensitive). + /// + bool TryGetByIdentifier(string identifier, [NotNullWhen(true)] out IAlamoLanguageDefinition? language); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILocalisationEditService.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILocalisationEditService.cs deleted file mode 100644 index dd5875da0..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILocalisationEditService.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using PG.Core.Attributes; -using PG.Core.Data.Etl.Source; -using PG.Core.Data.Etl.Target; -using PG.Core.Reporting; -using PG.Core.Services.Attributes; -using PG.StarWarsGame.Localisation.Data.Translation; - -namespace PG.StarWarsGame.Localisation.Services -{ - [Order(OrderAttribute.DEFAULT_ORDER)] - [DefaultServiceImplementation(typeof(LocalisationEditService))] - public interface ILocalisationEditService - { - bool IsInitialised { get; } - - ISourceDescriptor SourceDescriptor { get; } - ITargetDescriptor TargetDescriptor { get; } - - IReport LoadAndInitialise(); - - IReport SaveAndDiscard(); - - bool TryGet(TranslationKey key, out TranslationBean bean); - - bool TryAdd(TranslationBean bean); - - bool TryUpdate(TranslationBean bean); - - bool TryRemove(TranslationKey key); - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILocalisationProviderService.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILocalisationProviderService.cs deleted file mode 100644 index 6dbb89bb6..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILocalisationProviderService.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using PG.Core.Attributes; -using PG.Core.Localisation; - -namespace PG.StarWarsGame.Localisation.Services -{ - [Order(OrderAttribute.DEFAULT_ORDER)] - public interface ILocalisationProviderService where T : IAlamoLanguageDefinition - { - bool TryGet(string key, out string value); - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs new file mode 100644 index 000000000..876a98382 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs @@ -0,0 +1,79 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Services +{ + /// + /// Default implementation of . + /// + public sealed class LanguageService : ILanguageService + { + private readonly IReadOnlyList _all; + private readonly IReadOnlyList _officiallySupported; + private readonly Dictionary _byIdentifier; + + /// + public IReadOnlyList AllLanguages => _all; + + /// + public IAlamoLanguageDefinition Default => + _all.FirstOrDefault(l => l.IsDefault) + ?? throw new InvalidOperationException("No default language definition is registered."); + + /// + /// Initialises the service with the given language definitions. + /// + public LanguageService(IEnumerable languages) + { + if (languages is null) + throw new ArgumentNullException(nameof(languages)); + + var list = languages.ToList(); + _all = list.AsReadOnly(); + _officiallySupported = list.Where(l => l.IsOfficiallySupported()).ToList().AsReadOnly(); + _byIdentifier = list.ToDictionary( + l => l.LanguageIdentifier, + l => l, + StringComparer.OrdinalIgnoreCase); + } + + /// + public IReadOnlyList OfficiallySupported() => _officiallySupported; + + /// + public IReadOnlyList OfficiallySupported(GameContext context) + { + return _all.Where(l => l.IsOfficiallySupported(context)).ToList().AsReadOnly(); + } + + /// + public bool IsOfficiallySupported(IAlamoLanguageDefinition language) + { + if (language is null) + throw new ArgumentNullException(nameof(language)); + return _byIdentifier.TryGetValue(language.LanguageIdentifier, out var found) + && found.IsOfficiallySupported(); + } + + /// + public bool IsOfficiallySupported(IAlamoLanguageDefinition language, GameContext context) + { + if (language is null) + throw new ArgumentNullException(nameof(language)); + return _byIdentifier.TryGetValue(language.LanguageIdentifier, out var found) + && found.IsOfficiallySupported(context); + } + + /// + public bool TryGetByIdentifier(string identifier, [NotNullWhen(true)] out IAlamoLanguageDefinition? language) + { + return _byIdentifier.TryGetValue(identifier, out language); + } + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LocalisationEditService.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LocalisationEditService.cs deleted file mode 100644 index 2f293a329..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LocalisationEditService.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using PG.Core.Data.Etl.Source; -using PG.Core.Data.Etl.Target; -using PG.Core.Reporting; -using PG.Core.Services; -using PG.StarWarsGame.Localisation.Data.Translation; -using System; - -namespace PG.StarWarsGame.Localisation.Services -{ - public class LocalisationEditService : AbstractService, ILocalisationEditService - { - private bool m_isInitialised = false; - - public LocalisationEditService(ISourceDescriptor sourceDescriptor, ITargetDescriptor targetDescriptor, IServiceProvider services) : base(services) - { - SourceDescriptor = sourceDescriptor; - TargetDescriptor = targetDescriptor; - } - - public bool IsInitialised => m_isInitialised; - - public ISourceDescriptor SourceDescriptor { get; } - public ITargetDescriptor TargetDescriptor { get; } - - public IReport LoadAndInitialise() - { - throw new System.NotImplementedException(); - } - - public IReport SaveAndDiscard() - { - throw new System.NotImplementedException(); - } - - public bool TryGet(TranslationKey key, out TranslationBean bean) - { - throw new System.NotImplementedException(); - } - - public bool TryAdd(TranslationBean bean) - { - throw new System.NotImplementedException(); - } - - public bool TryUpdate(TranslationBean bean) - { - throw new System.NotImplementedException(); - } - - public bool TryRemove(TranslationKey key) - { - throw new System.NotImplementedException(); - } - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Util/LocalisationUtility.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Util/LocalisationUtility.cs deleted file mode 100644 index b6b6faca7..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Util/LocalisationUtility.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -using PG.Core.Attributes; -using PG.Core.Localisation; -using PG.StarWarsGame.Localisation.Commons; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace PG.StarWarsGame.Localisation.Util -{ - public static class LocalisationUtility - { - private static readonly IEnumerable ALAMO_LANGUAGE_DEFINITION_TYPE_CACHE; - - static LocalisationUtility() - { - ALAMO_LANGUAGE_DEFINITION_TYPE_CACHE = AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()) - .Where(p => - typeof(IAlamoLanguageDefinition).IsAssignableFrom(p) && - typeof(AbstractAlamoLanguageDefinition).IsAssignableFrom(p) && - p.IsClass && - !p.IsAbstract && - !p.IsInterface - ).ToList(); - } - - public static IList GetAllAlamoLanguageDefinitions() - { - List alamoLanguageDefinitions = new(); - foreach (Type type in ALAMO_LANGUAGE_DEFINITION_TYPE_CACHE) - { - try - { - IAlamoLanguageDefinition languageDefinition = - Activator.CreateInstance(type) as IAlamoLanguageDefinition; - alamoLanguageDefinitions.Add(languageDefinition); - } - catch - { - // NOP - this definition is faulty. - } - } - return alamoLanguageDefinitions; - } - - public static IAlamoLanguageDefinition GetDefaultAlamoLanguageDefinition() - { - foreach (Type type in ALAMO_LANGUAGE_DEFINITION_TYPE_CACHE) - { - if (!type.GetAttributeValueOrDefault((DefaultAttribute d) => d.IsDefault)) - { - continue; - } - - try - { - return Activator.CreateInstance(type) as IAlamoLanguageDefinition; - } - catch (Exception e) - { - throw new DefaultAlamoLanguageDefinitionException( - $"Could not create an instance of the default {nameof(IAlamoLanguageDefinition)} specification \"{type}\".", e); - } - } - - throw new DefaultAlamoLanguageDefinitionException( - $"No default {nameof(IAlamoLanguageDefinition)} specified."); - } - } -} diff --git a/PetroglyphTools.slnx b/PetroglyphTools.slnx index cfd0881dd..b7388c1fe 100644 --- a/PetroglyphTools.slnx +++ b/PetroglyphTools.slnx @@ -14,6 +14,10 @@ + + + +