From ddbb2e2018b04be71cc18ee052a5e2136bc973f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Wed, 10 Jun 2026 01:02:11 +0200 Subject: [PATCH 1/3] Localisation Layer --- .../BaselineTranslationProviderTest.cs | 71 ++++++ .../CommonBaselineTestBase.cs | 23 ++ ...WarsGame.Localisation.Baseline.Test.csproj | 34 +++ .../BaselineServiceContribution.cs | 33 +++ .../BaselineTranslationProvider.cs | 133 ++++++++++ .../IBaselineTranslationProvider.cs | 41 ++++ ....StarWarsGame.Localisation.Baseline.csproj | 61 +++++ .../Resources}/EaW/de/creditstext_german.dat | Bin .../EaW/de/mastertextfile_german.dat | Bin .../Resources}/EaW/en/creditstext_english.dat | Bin .../EaW/en/mastertextfile_english.dat | Bin .../Resources}/EaW/es/creditstext_spanish.dat | Bin .../EaW/es/mastertextfile_spanish.dat | Bin .../Resources}/EaW/fr/creditstext_french.dat | Bin .../EaW/fr/mastertextfile_french.dat | Bin .../Resources}/EaW/it/creditstext_italian.dat | Bin .../EaW/it/mastertextfile_italian.dat | Bin .../Resources}/FoC/de/creditstext_german.dat | Bin .../FoC/de/mastertextfile_german.dat | Bin .../Resources}/FoC/en/creditstext_english.dat | Bin .../FoC/en/mastertextfile_english.dat | Bin .../Resources}/FoC/es/creditstext_spanish.dat | Bin .../FoC/es/mastertextfile_spanish.dat | Bin .../Resources}/FoC/fr/creditstext_french.dat | Bin .../FoC/fr/mastertextfile_french.dat | Bin .../Resources}/FoC/it/creditstext_italian.dat | Bin .../FoC/it/mastertextfile_italian.dat | Bin .../CommonLocalisationTestBase.cs | 47 ++++ .../Data/KeyedTranslationDatabaseTest.cs | 227 ++++++++++++++++++ .../Data/OrderedTranslationDatabaseTest.cs | 129 ++++++++++ .../Data/TranslationDatabaseBuilderTest.cs | 93 +++++++ .../Data/TranslationProjectDescriptorTest.cs | 51 ++++ .../IO/CsvTranslationAdapterTest.cs | 73 ++++++ .../IO/DatTranslationAdapterTest.cs | 96 ++++++++ .../IO/PropertiesTranslationAdapterTest.cs | 77 ++++++ .../IO/XmlTranslationAdapterTest.cs | 76 ++++++ .../AbstractAlamoLanguageDefinitionTest.cs | 57 +++++ .../AlamoLanguageDefinitionIntegrityTest.cs | 117 +++++---- .../LocalisationServiceContributionTest.cs | 101 ++++++++ .../LocalisationTestConstants.cs | 31 --- .../PG.StarWarsGame.Localisation.Test.csproj | 32 +-- .../Services/LanguageServiceTest.cs | 114 +++++++++ .../Util/LocalisationUtilityTest.cs | 57 ----- ...DefaultAlamoLanguageDefinitionException.cs | 31 --- .../Config/ITranslationProjectDescriptor.cs | 28 +++ .../Config/TranslationProjectDescriptor.cs | 70 ++++++ .../Data/Config/v1/LocalisationDataType.cs | 36 +-- .../Data/Config/v1/LocalisationType.cs | 43 ++-- .../Data/Config/v1/TranslationType.cs | 16 +- .../Data/Config/v2/GameType.cs | 6 + .../Data/Config/v2/OverrideType.cs | 6 + .../Data/Config/v2/TranslationResourceType.cs | 9 + .../Data/IKeyedTranslationDatabase.cs | 26 ++ .../Data/IOrderedTranslationDatabase.cs | 24 ++ .../Data/ITranslationDatabase.cs | 40 +++ .../Data/ITranslationDatabaseBuilder.cs | 44 ++++ .../Data/ITranslationDatabaseFactory.cs | 30 +++ .../Data/ITranslationEntry.cs | 25 ++ .../Data/Internal/KeyedTranslationDatabase.cs | 94 ++++++++ .../Internal/OrderedTranslationDatabase.cs | 80 ++++++ .../Internal/TranslationDatabaseBuilder.cs | 62 +++++ .../Data/Internal/TranslationEntry.cs | 34 +++ .../Data/Localisation/LocalisationBean.cs | 18 -- .../Data/Localisation/LocalisationKey.cs | 15 -- .../Data/Translation/TranslationBean.cs | 15 -- .../Data/Translation/TranslationKey.cs | 14 -- .../Data/TranslationDatabaseFactory.cs | 48 ++++ .../IO/Csv/CsvTranslationExporter.cs | 59 +++++ .../IO/Csv/CsvTranslationImporter.cs | 113 +++++++++ .../IO/Csv/ICsvTranslationExporter.cs | 13 + .../IO/Csv/ICsvTranslationImporter.cs | 15 ++ .../IO/Dat/DatTranslationExporter.cs | 46 ++++ .../IO/Dat/DatTranslationImporter.cs | 27 +++ .../IO/Dat/IDatTranslationExporter.cs | 14 ++ .../IO/Dat/IDatTranslationImporter.cs | 14 ++ .../IO/ITranslationExporter.cs | 31 +++ .../IO/ITranslationImporter.cs | 32 +++ .../IPropertiesTranslationExporter.cs | 12 + .../IPropertiesTranslationImporter.cs | 15 ++ .../PropertiesTranslationExporter.cs | 39 +++ .../PropertiesTranslationImporter.cs | 43 ++++ .../IO/Xml/IXmlTranslationExporter.cs | 14 ++ .../IO/Xml/IXmlTranslationImporter.cs | 14 ++ .../IO/Xml/XmlTranslationExporter.cs | 46 ++++ .../IO/Xml/XmlTranslationImporter.cs | 59 +++++ .../Languages/AlamoLanguageDefinitionBase.cs | 64 +++++ .../Attributes/DefaultLanguageAttribute.cs | 16 ++ .../OfficiallySupportedLanguageAttribute.cs | 15 ++ .../ChineseAlamoLanguageDefinition.cs | 20 +- .../EnglishAlamoLanguageDefinition.cs | 18 +- .../FrenchAlamoLanguageDefinition.cs | 20 +- .../GermanAlamoLanguageDefinition.cs | 20 +- .../Languages/IAlamoLanguageDefinition.cs | 34 +++ .../ItalianAlamoLanguageDefinition.cs | 20 +- .../JapaneseAlamoLanguageDefinition.cs | 20 +- .../KoreanAlamoLanguageDefinition.cs | 20 +- .../PolishAlamoLanguageDefinition.cs | 20 +- .../RussianAlamoLanguageDefinition.cs | 20 +- .../SpanishAlamoLanguageDefinition.cs | 20 +- .../Languages/ThaiAlamoLanguageDefinition.cs | 20 +- .../LocalisationServiceContribution.cs | 55 +++++ .../PG.StarWarsGame.Localisation.csproj | 77 +++--- .../Services/ILanguageService.cs | 42 ++++ .../Services/ILocalisationEditService.cs | 34 --- .../Services/ILocalisationProviderService.cs | 14 -- .../Services/LanguageService.cs | 63 +++++ .../Services/LocalisationEditService.cs | 58 ----- .../Util/LocalisationUtility.cs | 72 ------ PetroglyphTools.slnx | 4 + 109 files changed, 3320 insertions(+), 640 deletions(-) create mode 100644 PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/BaselineTranslationProviderTest.cs create mode 100644 PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/CommonBaselineTestBase.cs create mode 100644 PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/PG.StarWarsGame.Localisation.Baseline.Test.csproj create mode 100644 PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineServiceContribution.cs create mode 100644 PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineTranslationProvider.cs create mode 100644 PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/IBaselineTranslationProvider.cs create mode 100644 PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.csproj rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/EaW/de/creditstext_german.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/EaW/de/mastertextfile_german.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/EaW/en/creditstext_english.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/EaW/en/mastertextfile_english.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/EaW/es/creditstext_spanish.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/EaW/es/mastertextfile_spanish.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/EaW/fr/creditstext_french.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/EaW/fr/mastertextfile_french.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/EaW/it/creditstext_italian.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/EaW/it/mastertextfile_italian.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/FoC/de/creditstext_german.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/FoC/de/mastertextfile_german.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/FoC/en/creditstext_english.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/FoC/en/mastertextfile_english.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/FoC/es/creditstext_spanish.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/FoC/es/mastertextfile_spanish.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/FoC/fr/creditstext_french.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/FoC/fr/mastertextfile_french.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/FoC/it/creditstext_italian.dat (100%) rename {PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Resources/Dat => PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/Resources}/FoC/it/mastertextfile_italian.dat (100%) create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/CommonLocalisationTestBase.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/KeyedTranslationDatabaseTest.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/OrderedTranslationDatabaseTest.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationDatabaseBuilderTest.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationProjectDescriptorTest.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/DatTranslationAdapterTest.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/PropertiesTranslationAdapterTest.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/LocalisationServiceContributionTest.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/LocalisationTestConstants.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Util/LocalisationUtilityTest.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Commons/DefaultAlamoLanguageDefinitionException.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/ITranslationProjectDescriptor.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/TranslationProjectDescriptor.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IKeyedTranslationDatabase.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IOrderedTranslationDatabase.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabase.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseBuilder.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseFactory.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationEntry.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/KeyedTranslationDatabase.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/OrderedTranslationDatabase.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationDatabaseBuilder.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationEntry.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Localisation/LocalisationBean.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Localisation/LocalisationKey.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Translation/TranslationBean.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Translation/TranslationKey.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationDatabaseFactory.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationExporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationImporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/ICsvTranslationExporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/ICsvTranslationImporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationExporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationImporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/IDatTranslationExporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/IDatTranslationImporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/ITranslationExporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/ITranslationImporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/IPropertiesTranslationExporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/IPropertiesTranslationImporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationExporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationImporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/IXmlTranslationExporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/IXmlTranslationImporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationImporter.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/DefaultLanguageAttribute.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/LocalisationServiceContribution.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILocalisationEditService.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILocalisationProviderService.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LocalisationEditService.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Util/LocalisationUtility.cs 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..8e68df6cf --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/BaselineTranslationProviderTest.cs @@ -0,0 +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 System; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Localisation.Data.Config.v2; +using PG.StarWarsGame.Localisation.Languages; +using Xunit; + +namespace PG.StarWarsGame.Localisation.Baseline.Test; + +public class BaselineTranslationProviderTest : CommonBaselineTestBase +{ + private IBaselineTranslationProvider Provider => + ServiceProvider.GetRequiredService(); + + [Theory] + [InlineData(GameType.EaW)] + [InlineData(GameType.FoC)] + public void GetMasterText_SingleLanguage_IsNonEmpty(GameType game) + { + var lang = new EnglishAlamoLanguageDefinition(); + var db = Provider.GetMasterText(game, lang); + Assert.NotEmpty(db); + } + + [Theory] + [InlineData(GameType.EaW)] + [InlineData(GameType.FoC)] + public void GetCreditsText_SingleLanguage_IsNonEmpty(GameType 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(GameType.EaW, langs); + Assert.Equal(2, db.Languages.Count); + } + + [Fact] + public void GetMasterText_EaW_English_ContainsKnownKey() + { + var lang = new EnglishAlamoLanguageDefinition(); + var db = Provider.GetMasterText(GameType.EaW, lang); + // At minimum the database should be populated — spot-check it has entries + Assert.True(db.Count > 100); + } + + [Fact] + public void GetMasterText_UnsupportedGame_Throws() + { + var lang = new EnglishAlamoLanguageDefinition(); + Assert.Throws(() => Provider.GetMasterText(GameType.Mod, lang)); + } + + [Fact] + public void GetCreditsText_UnsupportedGame_Throws() + { + var lang = new EnglishAlamoLanguageDefinition(); + Assert.Throws(() => Provider.GetCreditsText(GameType.Mod, lang)); + } +} diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/CommonBaselineTestBase.cs b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/CommonBaselineTestBase.cs new file mode 100644 index 000000000..f6a2560c6 --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/CommonBaselineTestBase.cs @@ -0,0 +1,23 @@ +// 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.Commons; + +namespace PG.StarWarsGame.Localisation.Baseline.Test; + +public abstract class CommonBaselineTestBase : IDisposable +{ + protected ServiceProvider ServiceProvider { get; } + + protected CommonBaselineTestBase() + { + var sc = new ServiceCollection(); + PetroglyphCommons.ContributeServices(sc); + sc.SupportLocalisationBaseline(); + ServiceProvider = sc.BuildServiceProvider(); + } + + public void Dispose() => ServiceProvider.Dispose(); +} 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..50797bb0c --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineServiceContribution.cs @@ -0,0 +1,33 @@ +// 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 Microsoft.Extensions.DependencyInjection.Extensions; +using PG.StarWarsGame.Files.DAT; +using Testably.Abstractions; + +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) + { + // Register the real file system if none has been provided (allows test overrides). + serviceCollection.TryAddSingleton(new RealFileSystem()); + + 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..c141f01ad --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineTranslationProvider.cs @@ -0,0 +1,133 @@ +// 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.IO.Abstractions; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Files.DAT.Services; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Data.Config.v2; +using PG.StarWarsGame.Localisation.IO.Dat; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Baseline +{ + /// + /// Default implementation of . + /// Loads translation data from embedded EaW and FoC game DAT files. + /// + public sealed class BaselineTranslationProvider : IBaselineTranslationProvider + { + // Only 5 languages have baseline data (the ones that shipped with the original games). + private static readonly IReadOnlyDictionary LanguageMap = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["ENGLISH"] = ("en", "english"), + ["GERMAN"] = ("de", "german"), + ["SPANISH"] = ("es", "spanish"), + ["FRENCH"] = ("fr", "french"), + ["ITALIAN"] = ("it", "italian"), + }; + + private readonly IFileSystem _fileSystem; + private readonly IDatFileService _datFileService; + private readonly IDatTranslationImporter _importer; + private readonly ITranslationDatabaseFactory _factory; + + /// Initialises a new instance using services from the given provider. + public BaselineTranslationProvider(IServiceProvider services) + { + if (services is null) throw new ArgumentNullException(nameof(services)); + _fileSystem = services.GetRequiredService(); + _datFileService = services.GetRequiredService(); + _importer = services.GetRequiredService(); + _factory = services.GetRequiredService(); + } + + /// + public IKeyedTranslationDatabase GetMasterText(GameType game, IAlamoLanguageDefinition language) + { + if (language is null) throw new ArgumentNullException(nameof(language)); + ValidateGame(game); + var db = _factory.CreateKeyed(new[] { language }); + LoadInto(db, game, "mastertextfile", language); + return db; + } + + /// + public IKeyedTranslationDatabase GetMasterText(GameType game, IReadOnlyList languages) + { + if (languages is null) throw new ArgumentNullException(nameof(languages)); + ValidateGame(game); + var db = _factory.CreateKeyed(languages); + foreach (var lang in languages) + LoadInto(db, game, "mastertextfile", lang); + return db; + } + + /// + public IOrderedTranslationDatabase GetCreditsText(GameType game, IAlamoLanguageDefinition language) + { + if (language is null) throw new ArgumentNullException(nameof(language)); + ValidateGame(game); + var db = _factory.CreateOrdered(new[] { language }); + LoadInto(db, game, "creditstext", language); + return db; + } + + /// + public IOrderedTranslationDatabase GetCreditsText(GameType game, IReadOnlyList languages) + { + if (languages is null) throw new ArgumentNullException(nameof(languages)); + ValidateGame(game); + var db = _factory.CreateOrdered(languages); + foreach (var lang in languages) + LoadInto(db, game, "creditstext", lang); + return db; + } + + private void LoadInto(ITranslationDatabase db, GameType 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 + { + if (_fileSystem.File.Exists(tempPath)) + _fileSystem.File.Delete(tempPath); + } + } + + private static string BuildResourceName(GameType game, string langFolder, string fileName) + { + var gameFolder = game == GameType.EaW ? "EaW" : "FoC"; + return $"PG.StarWarsGame.Localisation.Baseline.Resources.{gameFolder}.{langFolder}.{fileName}"; + } + + private static void ValidateGame(GameType game) + { + if (game != GameType.EaW && game != GameType.FoC) + throw new ArgumentException( + $"Baseline data is only available for EaW and FoC, not '{game}'.", nameof(game)); + } + } +} 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..4cb0713b1 --- /dev/null +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/IBaselineTranslationProvider.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.Collections.Generic; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Data.Config.v2; +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. + /// + /// When is null. + /// When is not EaW or FoC. + IKeyedTranslationDatabase GetMasterText(GameType game, IAlamoLanguageDefinition language); + + /// + /// Returns a keyed translation database (MasterText) merged from the given game and languages. + /// + IKeyedTranslationDatabase GetMasterText(GameType game, IReadOnlyList languages); + + /// + /// Returns an ordered translation database (CreditsText) for the given game and single language. + /// + /// When is null. + /// When is not EaW or FoC. + IOrderedTranslationDatabase GetCreditsText(GameType game, IAlamoLanguageDefinition language); + + /// + /// Returns an ordered translation database (CreditsText) merged from the given game and languages. + /// + IOrderedTranslationDatabase GetCreditsText(GameType 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..c90781b4a --- /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).WithActiveLanguage(En).BuildKeyed(); + Assert.Equal(En, db.ActiveLanguage); + } + + [Fact] + public void WithActiveLanguage_NotInLanguages_ThrowsAtBuildTime() + { + Assert.Throws(() => + Builder().WithLanguage(En).WithActiveLanguage(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) + .WithActiveLanguage(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..db4fdb119 --- /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(GameType.EaW, OverrideType.Core, TranslationResourceType.Dat, TwoLangs); + Assert.Equal(GameType.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(GameType.EaW, OverrideType.Core, TranslationResourceType.Dat, null!)); + } + + [Fact] + public void Languages_IsImmutable() + { + var mutable = new List { new EnglishAlamoLanguageDefinition() }; + var d = new TranslationProjectDescriptor(GameType.EaW, OverrideType.Core, TranslationResourceType.Xml, mutable); + mutable.Add(new GermanAlamoLanguageDefinition()); + Assert.Single(d.Languages); + } + + [Fact] + public void Equality_TwoDescriptorsWithSameValues_AreEqual() + { + var a = new TranslationProjectDescriptor(GameType.FoC, OverrideType.Mod, TranslationResourceType.Csv, TwoLangs); + var b = new TranslationProjectDescriptor(GameType.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..5b5a8d45e --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.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.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); + } +} 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..e3f43741a --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/PropertiesTranslationAdapterTest.cs @@ -0,0 +1,77 @@ +// 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); + } +} 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..89645134f --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs @@ -0,0 +1,76 @@ +// 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 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", "http://www.example.org/eaw-translation/"))); + 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); + } +} 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..1d4a9b11e --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs @@ -0,0 +1,57 @@ +// 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 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); + } +} 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..f25509121 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..4d628a20c --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs @@ -0,0 +1,114 @@ +// 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.Languages; +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!)); + } + + private sealed class UnsupportedTestLanguage : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "KLINGON"; + protected override System.Globalization.CultureInfo ConfiguredCulture => + System.Globalization.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/ITranslationProjectDescriptor.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/ITranslationProjectDescriptor.cs new file mode 100644 index 000000000..dc322c84d --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/ITranslationProjectDescriptor.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.Collections.Generic; +using PG.StarWarsGame.Localisation.Data.Config.v2; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data.Config +{ + /// + /// Describes a translation project: which game it targets, its override level, + /// the resource format it uses, and the languages it covers. + /// + public interface ITranslationProjectDescriptor + { + /// Gets the game this project provides translations for. + GameType Game { get; } + + /// Gets whether this project provides core, expansion, or mod text. + OverrideType OverrideType { get; } + + /// Gets the file format used to store translation resources. + TranslationResourceType ResourceType { get; } + + /// Gets the languages covered by this translation project. + IReadOnlyList Languages { get; } + } +} 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..9d8327ebb --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/TranslationProjectDescriptor.cs @@ -0,0 +1,70 @@ +// 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.v2; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.Data.Config +{ + /// + /// Immutable descriptor for a translation project. + /// + public sealed class TranslationProjectDescriptor : ITranslationProjectDescriptor, + IEquatable + { + /// + public GameType Game { get; } + + /// + public OverrideType OverrideType { get; } + + /// + public TranslationResourceType ResourceType { get; } + + /// + public IReadOnlyList Languages { get; } + + /// Initialises a new . + public TranslationProjectDescriptor( + GameType game, + OverrideType overrideType, + TranslationResourceType resourceType, + IReadOnlyList languages) + { + if (languages is null) + throw new ArgumentNullException(nameof(languages)); + + Game = game; + OverrideType = overrideType; + ResourceType = resourceType; + Languages = languages.ToList().AsReadOnly(); + } + + /// + 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 + && Languages.SequenceEqual(other.Languages); + } + + /// + 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 Languages) + 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..62037f122 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 = "http://www.example.org/eaw-translation/")] + [XmlRoot("LocalisationData", Namespace = "http://www.example.org/eaw-translation/", 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..0f357dc9f 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 = "http://www.example.org/eaw-translation/")] + 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..7c2a67334 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 @@ -19,12 +19,12 @@ namespace PG.StarWarsGame.Localisation.Data.Config.v1 [XmlType(Namespace = "http://www.example.org/eaw-translation/")] 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 index 9faa4f68b..31211a941 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/GameType.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/GameType.cs @@ -3,10 +3,16 @@ namespace PG.StarWarsGame.Localisation.Data.Config.v2 { + /// + /// Identifies which Petroglyph Star Wars game a translation project targets. + /// public enum GameType { + /// Empire at War (base game). EaW, + /// Forces of Corruption (expansion). FoC, + /// A community mod. 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..638bfb4fd --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IKeyedTranslationDatabase.cs @@ -0,0 +1,26 @@ +// 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 +{ + /// + /// 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, out ITranslationEntry? entry); + + /// + /// Attempts to retrieve the translation for using . + /// + /// + /// is . + /// + bool TryGetTranslation(string key, 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..23e8e2791 --- /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..f14ebad81 --- /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..5dbc161e6 --- /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. + /// Validation that the language is present in the database's language list occurs at build time. + /// + ITranslationDatabaseBuilder WithActiveLanguage(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..a88d05294 --- /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(ITranslationProjectDescriptor descriptor); + + /// Creates an ordered database (duplicate keys allowed, Credits semantics). + IOrderedTranslationDatabase CreateOrdered(IReadOnlyList languages); + + /// Creates an ordered database using the languages from . + IOrderedTranslationDatabase CreateOrdered(ITranslationProjectDescriptor descriptor); + + /// Returns a fluent builder for constructing a translation database. + ITranslationDatabaseBuilder CreateDatabase(); + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationEntry.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationEntry.cs new file mode 100644 index 000000000..790d1d9c0 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationEntry.cs @@ -0,0 +1,25 @@ +// 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 single translation entry: a string key with zero or more language-specific values. + /// + public interface ITranslationEntry + { + /// Gets the translation key. + string Key { get; } + + /// Gets all available translations, keyed by language definition. + IReadOnlyDictionary Translations { get; } + + /// + /// Attempts to retrieve the translation value for . + /// + bool TryGetTranslation(IAlamoLanguageDefinition language, out string? value); + } +} 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..68259bffd --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/KeyedTranslationDatabase.cs @@ -0,0 +1,94 @@ +// 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 sealed class KeyedTranslationDatabase : IKeyedTranslationDatabase + { + private readonly Dictionary _entries = + new Dictionary(StringComparer.Ordinal); + + private IAlamoLanguageDefinition? _activeLanguage; + + public IReadOnlyList Languages { get; } + + public int Count => _entries.Count; + + public ITranslationEntry this[int index] => _entries.Values.ElementAt(index); + + public IAlamoLanguageDefinition? ActiveLanguage + { + get => _activeLanguage; + set + { + if (value is not null && !Languages.Contains(value)) + throw new ArgumentException( + $"Language '{value.LanguageIdentifier}' is not registered in this database.", nameof(value)); + _activeLanguage = value; + } + } + + internal KeyedTranslationDatabase(IReadOnlyList languages) + { + Languages = languages; + } + + public 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 bool RemoveEntry(string key) => _entries.Remove(key); + + public void Clear() => _entries.Clear(); + + public bool ContainsKey(string key) => _entries.ContainsKey(key); + + public bool TryGetEntry(string key, out ITranslationEntry? entry) + { + if (_entries.TryGetValue(key, out var e)) + { + entry = e; + return true; + } + entry = null; + return false; + } + + public bool TryGetTranslation(string key, 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 IEnumerator GetEnumerator() => + _entries.Values.Cast().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => 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..f372b5da9 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/OrderedTranslationDatabase.cs @@ -0,0 +1,80 @@ +// 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 sealed class OrderedTranslationDatabase : IOrderedTranslationDatabase + { + private readonly List _entries = new List(); + + private IAlamoLanguageDefinition? _activeLanguage; + + public IReadOnlyList Languages { get; } + + public int Count => _entries.Count; + + public ITranslationEntry this[int index] => _entries[index]; + + public IAlamoLanguageDefinition? ActiveLanguage + { + get => _activeLanguage; + set + { + if (value is not null && !Languages.Contains(value)) + throw new ArgumentException( + $"Language '{value.LanguageIdentifier}' is not registered in this database.", nameof(value)); + _activeLanguage = value; + } + } + + internal OrderedTranslationDatabase(IReadOnlyList languages) + { + Languages = languages; + } + + public 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 bool RemoveEntry(string key) + { + var idx = _entries.FindIndex(e => e.Key == key); + if (idx < 0) return false; + _entries.RemoveAt(idx); + return true; + } + + public 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).Cast().ToList().AsReadOnly(); + + public IEnumerator GetEnumerator() => + _entries.Cast().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..d649b10dc --- /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 WithActiveLanguage(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/Internal/TranslationEntry.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationEntry.cs new file mode 100644 index 000000000..0ab2b6136 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationEntry.cs @@ -0,0 +1,34 @@ +// 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.Languages; + +namespace PG.StarWarsGame.Localisation.Data.Internal +{ + internal sealed class TranslationEntry : ITranslationEntry + { + private readonly Dictionary _translations; + + public string Key { get; } + + 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; + } + + public bool TryGetTranslation(IAlamoLanguageDefinition language, out string? value) + { + return _translations.TryGetValue(language, out value); + } + } +} 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..bc5ee176d --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationDatabaseFactory.cs @@ -0,0 +1,48 @@ +// 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.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(ITranslationProjectDescriptor descriptor) + { + if (descriptor is null) throw new ArgumentNullException(nameof(descriptor)); + return CreateKeyed(descriptor.Languages); + } + + /// + public IOrderedTranslationDatabase CreateOrdered(IReadOnlyList languages) + { + if (languages is null) throw new ArgumentNullException(nameof(languages)); + return new OrderedTranslationDatabase(languages); + } + + /// + public IOrderedTranslationDatabase CreateOrdered(ITranslationProjectDescriptor descriptor) + { + if (descriptor is null) throw new ArgumentNullException(nameof(descriptor)); + return CreateOrdered(descriptor.Languages); + } + + /// + public ITranslationDatabaseBuilder CreateDatabase() => new TranslationDatabaseBuilder(); + } +} 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..95c70c9d1 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationExporter.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.Text; +using PG.StarWarsGame.Localisation.Data; +using PG.StarWarsGame.Localisation.Languages; + +namespace PG.StarWarsGame.Localisation.IO.Csv +{ + /// + /// Default implementation of . + /// Produces a CSV with header row: key,LANG1,LANG2,... + /// Values containing commas or quotes are wrapped in double quotes with internal quotes escaped. + /// + public sealed class CsvTranslationExporter : ICsvTranslationExporter + { + /// + public string Export(ITranslationDatabase source) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + + var langs = source.Languages; + var sb = new StringBuilder(); + + // Header + sb.Append("key"); + foreach (var lang in langs) + { + sb.Append(','); + sb.Append(Escape(lang.LanguageIdentifier)); + } + sb.Append('\n'); + + // Rows + foreach (var entry in source) + { + sb.Append(Escape(entry.Key)); + foreach (var lang in langs) + { + sb.Append(','); + if (entry.TryGetTranslation(lang, out var val) && val is not null) + sb.Append(Escape(val)); + } + sb.Append('\n'); + } + + return sb.ToString(); + } + + private static string Escape(string value) + { + if (value.IndexOf(',') >= 0 || value.IndexOf('"') >= 0 || value.IndexOf('\n') >= 0) + return '"' + value.Replace("\"", "\"\"") + '"'; + return value; + } + } +} 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..71d4faa27 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationImporter.cs @@ -0,0 +1,113 @@ +// 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 System.IO; +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 headerLine = source.ReadLine(); + if (headerLine is null) return; + + var headers = ParseCsvLine(headerLine); + if (headers.Count < 2) return; + + var langColumns = new List(); + for (var i = 1; i < headers.Count; i++) + { + _languageService.TryGetByIdentifier(headers[i], out var lang); + langColumns.Add(lang); + } + + string? line; + while ((line = source.ReadLine()) is not null) + { + if (string.IsNullOrWhiteSpace(line)) continue; + var fields = ParseCsvLine(line); + if (fields.Count == 0) continue; + + var key = fields[0]; + for (var i = 1; i < fields.Count && i - 1 < langColumns.Count; i++) + { + var lang = langColumns[i - 1]; + if (lang is null) continue; + if (!target.Languages.Contains(lang)) continue; + target.SetTranslation(key, lang, fields[i]); + } + } + } + + private static List ParseCsvLine(string line) + { + var fields = new List(); + var i = 0; + while (i <= line.Length) + { + if (i == line.Length) + { + fields.Add(string.Empty); + break; + } + + if (line[i] == '"') + { + i++; + var sb = new System.Text.StringBuilder(); + while (i < line.Length) + { + if (line[i] == '"') + { + i++; + if (i < line.Length && line[i] == '"') + { + sb.Append('"'); + i++; + } + else break; + } + else + { + sb.Append(line[i]); + i++; + } + } + fields.Add(sb.ToString()); + if (i < line.Length && line[i] == ',') i++; + } + else + { + var start = i; + while (i < line.Length && line[i] != ',') i++; + fields.Add(line.Substring(start, i - start)); + if (i < line.Length) i++; + } + } + return fields; + } + } +} 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..fcb4a5d40 --- /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) && value is not null) + 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..514e362c8 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationExporter.cs @@ -0,0 +1,39 @@ +// 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.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 sb = new StringBuilder(); + foreach (var entry in source) + { + 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..49adde244 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.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 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 = "http://www.example.org/eaw-translation/"; + + /// + public XDocument Export(ITranslationDatabase source) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + + var root = new XElement(Ns + "LocalisationData"); + + foreach (var entry in source) + { + 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..f91431234 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs @@ -0,0 +1,64 @@ +// 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 + { + /// + public string LanguageIdentifier { get; } + + /// + public CultureInfo Culture { get; } + + /// + public bool IsOfficiallySupported { 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; + IsOfficiallySupported = GetType().IsDefined(typeof(OfficiallySupportedLanguageAttribute), false); + IsDefault = GetType().IsDefined(typeof(DefaultLanguageAttribute), false); + } + + /// + 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..67133386c --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.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; + +namespace PG.StarWarsGame.Localisation.Languages.Attributes +{ + /// + /// Marks a language definition as officially supported by the Alamo engine. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public sealed class OfficiallySupportedLanguageAttribute : Attribute + { + } +} 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/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..aa4e1233d --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs @@ -0,0 +1,34 @@ +// 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; } + + /// + /// Gets a value indicating whether this language is officially supported by the Alamo engine. + /// + bool IsOfficiallySupported { get; } + + /// + /// 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..98f66b4b5 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)] - [ExcludeFromCodeCoverage] + /// The language definition for Japanese (JA). + /// Officially supported by the Alamo Engine. [OfficiallySupportedLanguage] - public sealed class JapaneseAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + 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..e9c1ef051 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)] - [ExcludeFromCodeCoverage] + /// The language definition for Korean (KO). + /// Officially supported by the Alamo Engine. [OfficiallySupportedLanguage] - public sealed class KoreanAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + 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..b9a31d7f3 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)] - [ExcludeFromCodeCoverage] + /// The language definition for Thai (TH). + /// Officially supported by the Alamo Engine. [OfficiallySupportedLanguage] - public sealed class ThaiAlamoLanguageDefinition : AbstractAlamoLanguageDefinition + [ExcludeFromCodeCoverage] + 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..54a3032a5 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,39 @@ - + - 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..3cc398dcd --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs @@ -0,0 +1,42 @@ +// 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.Services +{ + /// + /// Provides lookup and validation operations over the registered Alamo language definitions. + /// + public interface ILanguageService + { + /// Gets all registered language definitions. + IReadOnlyList AllLanguages { get; } + + /// Gets only the officially supported language definitions. + IReadOnlyList OfficiallySupported { get; } + + /// + /// Gets the default game language (English). + /// + /// + /// Thrown when no language definition is marked as default. + /// + IAlamoLanguageDefinition Default { get; } + + /// + /// Returns if is in the officially supported list. + /// + /// + /// Thrown when is . + /// + bool IsOfficiallySupported(IAlamoLanguageDefinition language); + + /// + /// Attempts to find a language definition whose + /// matches (case-insensitive). + /// + bool TryGetByIdentifier(string identifier, 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..ae371ec11 --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs @@ -0,0 +1,63 @@ +// 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.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 IReadOnlyList OfficiallySupported => _officiallySupported; + + /// + 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 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 TryGetByIdentifier(string identifier, 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 @@ + + + + From a47ca38199f811b8e6c76cb015974e9a49dc54cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Wed, 10 Jun 2026 19:01:49 +0200 Subject: [PATCH 2/3] Language Support with Game Distinction --- .../IO/CsvTranslationAdapterTest.cs | 16 +++++ .../IO/PropertiesTranslationAdapterTest.cs | 16 +++++ .../IO/XmlTranslationAdapterTest.cs | 19 ++++++ .../AbstractAlamoLanguageDefinitionTest.cs | 51 +++++++++++++++- .../AlamoLanguageDefinitionIntegrityTest.cs | 2 +- .../Services/LanguageServiceTest.cs | 58 ++++++++++++++++++- .../IO/Csv/CsvTranslationExporter.cs | 8 ++- .../PropertiesTranslationExporter.cs | 7 ++- .../IO/Xml/XmlTranslationExporter.cs | 6 +- .../Languages/AlamoGameContext.cs | 17 ++++++ .../Languages/AlamoLanguageDefinitionBase.cs | 23 ++++++-- .../OfficiallySupportedLanguageAttribute.cs | 15 ++++- .../Languages/IAlamoLanguageDefinition.cs | 11 +++- .../JapaneseAlamoLanguageDefinition.cs | 2 +- .../KoreanAlamoLanguageDefinition.cs | 2 +- .../Languages/ThaiAlamoLanguageDefinition.cs | 2 +- .../Services/ILanguageService.cs | 17 +++++- .../Services/LanguageService.cs | 25 ++++++-- 18 files changed, 270 insertions(+), 27 deletions(-) create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoGameContext.cs diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.cs index 5b5a8d45e..0da9306ad 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.cs @@ -70,4 +70,20 @@ public void Import_EmptyCsv_LeavesDbEmpty() CreateImporter().Import(new StringReader("key,ENGLISH\n"), db); Assert.Empty(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 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/PropertiesTranslationAdapterTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/PropertiesTranslationAdapterTest.cs index e3f43741a..d19c604f0 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/PropertiesTranslationAdapterTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/PropertiesTranslationAdapterTest.cs @@ -74,4 +74,20 @@ public void Import_SkipsCommentLines() 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 index 89645134f..ebb6fcf26 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs @@ -5,6 +5,7 @@ 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; @@ -73,4 +74,22 @@ public void Import_UnknownLanguage_IsSkipped() 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 = "http://www.example.org/eaw-translation/"; + 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 index 1d4a9b11e..23fc182f4 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs @@ -3,6 +3,7 @@ using System.Globalization; using PG.StarWarsGame.Localisation.Languages; +using PG.StarWarsGame.Localisation.Languages.Attributes; using Xunit; namespace PG.StarWarsGame.Localisation.Test.Languages; @@ -27,7 +28,7 @@ public void Culture_ReturnsConfiguredCulture() public void IsOfficiallySupported_IsTrue_WhenMarkedWithAttribute() { var lang = new EnglishAlamoLanguageDefinition(); - Assert.True(lang.IsOfficiallySupported); + Assert.True(lang.IsOfficiallySupported()); } [Fact] @@ -54,4 +55,52 @@ public void Equals_ReturnsFalseForDifferentLanguages() var german = new GermanAlamoLanguageDefinition(); Assert.NotEqual(english, german); } + + [Fact] + public void IsOfficiallySupported_WithContext_ReturnsTrueForBothContexts_WhenNoContextsSpecified() + { + var lang = new EnglishAlamoLanguageDefinition(); + Assert.True(lang.IsOfficiallySupported(AlamoGameContext.BaseGame)); + Assert.True(lang.IsOfficiallySupported(AlamoGameContext.Expansion)); + } + + [Fact] + public void IsOfficiallySupported_WithContext_ReturnsFalse_WhenLanguageHasNoAttribute() + { + var lang = new UnsupportedTestLanguage(); + Assert.False(lang.IsOfficiallySupported(AlamoGameContext.BaseGame)); + Assert.False(lang.IsOfficiallySupported(AlamoGameContext.Expansion)); + } + + [Fact] + public void IsOfficiallySupported_WithContext_ReturnsTrue_OnlyForMatchingContext() + { + var baseOnly = new BaseGameOnlyTestLanguage(); + Assert.True(baseOnly.IsOfficiallySupported(AlamoGameContext.BaseGame)); + Assert.False(baseOnly.IsOfficiallySupported(AlamoGameContext.Expansion)); + + var expOnly = new ExpansionOnlyTestLanguage(); + Assert.False(expOnly.IsOfficiallySupported(AlamoGameContext.BaseGame)); + Assert.True(expOnly.IsOfficiallySupported(AlamoGameContext.Expansion)); + } + + private sealed class UnsupportedTestLanguage : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "KLINGON"; + protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; + } + + [OfficiallySupportedLanguage(AlamoGameContext.BaseGame)] + private sealed class BaseGameOnlyTestLanguage : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "BASE_ONLY"; + protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; + } + + [OfficiallySupportedLanguage(AlamoGameContext.Expansion)] + 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 f25509121..f69bc2ba4 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AlamoLanguageDefinitionIntegrityTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AlamoLanguageDefinitionIntegrityTest.cs @@ -54,7 +54,7 @@ public void AllConcreteLanguagesAreOfficiallySupported() .Select(t => (IAlamoLanguageDefinition)System.Activator.CreateInstance(t)!) .ToList(); - Assert.All(langs, l => Assert.True(l.IsOfficiallySupported)); + Assert.All(langs, l => Assert.True(l.IsOfficiallySupported())); } [Fact] diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs index 4d628a20c..a50097cb0 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs @@ -3,7 +3,9 @@ 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; @@ -41,7 +43,7 @@ public void AllLanguages_ReturnsAllRegistered() public void OfficiallySupported_ReturnsOnlySupported() { var svc = CreateService(); - Assert.All(svc.OfficiallySupported, l => Assert.True(l.IsOfficiallySupported)); + Assert.All(svc.OfficiallySupported(), l => Assert.True(l.IsOfficiallySupported())); } [Fact] @@ -105,10 +107,60 @@ public void IsOfficiallySupported_Throws_OnNull() 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(AlamoGameContext.BaseGame); + var expResult = svc.OfficiallySupported(AlamoGameContext.Expansion); + + 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, AlamoGameContext.BaseGame)); + Assert.False(svc.IsOfficiallySupported(baseOnly, AlamoGameContext.Expansion)); + } + + [Fact] + public void IsOfficiallySupported_WithContext_Throws_OnNull() + { + var svc = CreateService(); + Assert.Throws(() => svc.IsOfficiallySupported(null!, AlamoGameContext.BaseGame)); + } + private sealed class UnsupportedTestLanguage : AlamoLanguageDefinitionBase { protected override string ConfiguredLanguageIdentifier => "KLINGON"; - protected override System.Globalization.CultureInfo ConfiguredCulture => - System.Globalization.CultureInfo.InvariantCulture; + protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; + } + + [OfficiallySupportedLanguage(AlamoGameContext.BaseGame)] + private sealed class BaseGameOnlyTestLanguage : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "BASE_ONLY"; + protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; + } + + [OfficiallySupportedLanguage(AlamoGameContext.Expansion)] + 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/IO/Csv/CsvTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationExporter.cs index 95c70c9d1..523bb6ac5 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationExporter.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationExporter.cs @@ -33,8 +33,12 @@ public string Export(ITranslationDatabase source) } sb.Append('\n'); - // Rows - foreach (var entry in source) + // Rows — keyed databases sort alphabetically for reproducible diffs + var rows = source is IKeyedTranslationDatabase + ? source.OrderBy(e => e.Key, StringComparer.Ordinal).AsEnumerable() + : source.AsEnumerable(); + + foreach (var entry in rows) { sb.Append(Escape(entry.Key)); foreach (var lang in langs) diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationExporter.cs index 514e362c8..6c3d908f4 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationExporter.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Properties/PropertiesTranslationExporter.cs @@ -2,6 +2,7 @@ // 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; @@ -20,8 +21,12 @@ public string Export(ITranslationDatabase source, IAlamoLanguageDefinition langu 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 source) + foreach (var entry in rows) { if (entry.TryGetTranslation(language, out var value) && value is not null) { diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs index 49adde244..3a10c3eba 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs @@ -24,7 +24,11 @@ public XDocument Export(ITranslationDatabase source) var root = new XElement(Ns + "LocalisationData"); - foreach (var entry in source) + 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)); diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoGameContext.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoGameContext.cs new file mode 100644 index 000000000..b85cefeec --- /dev/null +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoGameContext.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 for language-support context. + /// + public enum AlamoGameContext + { + /// Star Wars: Empire at War (base game). + BaseGame, + + /// Star Wars: Empire at War — Forces of Corruption (expansion). + Expansion + } +} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs index f91431234..0d8d5ac98 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs @@ -12,15 +12,15 @@ namespace PG.StarWarsGame.Localisation.Languages /// public abstract class AlamoLanguageDefinitionBase : IAlamoLanguageDefinition { + private readonly bool _isOfficiallySupported; + private readonly AlamoGameContext[] _supportedContexts; + /// public string LanguageIdentifier { get; } /// public CultureInfo Culture { get; } - /// - public bool IsOfficiallySupported { get; } - /// public bool IsDefault { get; } @@ -39,10 +39,25 @@ protected AlamoLanguageDefinitionBase() { LanguageIdentifier = ConfiguredLanguageIdentifier; Culture = ConfiguredCulture; - IsOfficiallySupported = GetType().IsDefined(typeof(OfficiallySupportedLanguageAttribute), false); + + 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(AlamoGameContext context) + { + if (!_isOfficiallySupported) return false; + return _supportedContexts.Length == 0 || Array.IndexOf(_supportedContexts, context) >= 0; + } + /// public bool Equals(IAlamoLanguageDefinition? other) { diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs index 67133386c..fa9b22a24 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs @@ -6,10 +6,23 @@ namespace PG.StarWarsGame.Localisation.Languages.Attributes { /// - /// Marks a language definition as officially supported by the Alamo engine. + /// 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 AlamoGameContext[] SupportedContexts { get; } + + /// + public OfficiallySupportedLanguageAttribute(params AlamoGameContext[] contexts) + { + SupportedContexts = contexts ?? Array.Empty(); + } } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs index aa4e1233d..ca08e6946 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs @@ -22,9 +22,16 @@ public interface IAlamoLanguageDefinition : IEquatable CultureInfo Culture { get; } /// - /// Gets a value indicating whether this language is officially supported by the Alamo engine. + /// Returns if this language is officially supported by the Alamo engine + /// in at least one game context. /// - bool IsOfficiallySupported { get; } + bool IsOfficiallySupported(); + + /// + /// Returns if this language is officially supported within the given + /// . + /// + bool IsOfficiallySupported(AlamoGameContext context); /// /// Gets a value indicating whether this is the default game language (English). diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs index 98f66b4b5..90a582cce 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Localisation.Languages { /// The language definition for Japanese (JA). /// Officially supported by the Alamo Engine. - [OfficiallySupportedLanguage] + [OfficiallySupportedLanguage(AlamoGameContext.BaseGame)] [ExcludeFromCodeCoverage] public sealed class JapaneseAlamoLanguageDefinition : AlamoLanguageDefinitionBase { diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs index e9c1ef051..885f63089 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Localisation.Languages { /// The language definition for Korean (KO). /// Officially supported by the Alamo Engine. - [OfficiallySupportedLanguage] + [OfficiallySupportedLanguage(AlamoGameContext.BaseGame)] [ExcludeFromCodeCoverage] public sealed class KoreanAlamoLanguageDefinition : AlamoLanguageDefinitionBase { diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs index b9a31d7f3..ade72d3d3 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Localisation.Languages { /// The language definition for Thai (TH). /// Officially supported by the Alamo Engine. - [OfficiallySupportedLanguage] + [OfficiallySupportedLanguage(AlamoGameContext.BaseGame)] [ExcludeFromCodeCoverage] public sealed class ThaiAlamoLanguageDefinition : AlamoLanguageDefinitionBase { diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs index 3cc398dcd..136869329 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs @@ -14,8 +14,11 @@ public interface ILanguageService /// Gets all registered language definitions. IReadOnlyList AllLanguages { get; } - /// Gets only the officially supported language definitions. - IReadOnlyList OfficiallySupported { get; } + /// Returns all officially supported language definitions across all game contexts. + IReadOnlyList OfficiallySupported(); + + /// Returns the officially supported language definitions for the given . + IReadOnlyList OfficiallySupported(AlamoGameContext context); /// /// Gets the default game language (English). @@ -26,13 +29,21 @@ public interface ILanguageService IAlamoLanguageDefinition Default { get; } /// - /// Returns if is in the officially supported list. + /// 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, AlamoGameContext context); + /// /// Attempts to find a language definition whose /// matches (case-insensitive). diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs index ae371ec11..b638ba883 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs @@ -20,9 +20,6 @@ public sealed class LanguageService : ILanguageService /// public IReadOnlyList AllLanguages => _all; - /// - public IReadOnlyList OfficiallySupported => _officiallySupported; - /// public IAlamoLanguageDefinition Default => _all.FirstOrDefault(l => l.IsDefault) @@ -38,20 +35,38 @@ public LanguageService(IEnumerable languages) var list = languages.ToList(); _all = list.AsReadOnly(); - _officiallySupported = list.Where(l => l.IsOfficiallySupported).ToList().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(AlamoGameContext 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; + && found.IsOfficiallySupported(); + } + + /// + public bool IsOfficiallySupported(IAlamoLanguageDefinition language, AlamoGameContext context) + { + if (language is null) + throw new ArgumentNullException(nameof(language)); + return _byIdentifier.TryGetValue(language.LanguageIdentifier, out var found) + && found.IsOfficiallySupported(context); } /// From ac73a5c287d23df270782181620689e78ec6a47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Thu, 11 Jun 2026 18:47:57 +0200 Subject: [PATCH 3/3] Address Review --- PG.Commons/PG.Commons/PetroglyphCommons.cs | 10 +- .../DatServiceContribution.cs | 9 +- .../BaselineTranslationProviderTest.cs | 39 ++++---- .../CommonBaselineTestBase.cs | 23 ----- .../BaselineServiceContribution.cs | 5 - .../BaselineTranslationProvider.cs | 75 +++++++-------- .../IBaselineTranslationProvider.cs | 30 ++++-- .../Data/TranslationDatabaseBuilderTest.cs | 6 +- .../Data/TranslationProjectDescriptorTest.cs | 12 +-- .../IO/CsvTranslationAdapterTest.cs | 27 ++++++ .../IO/XmlTranslationAdapterTest.cs | 4 +- .../AbstractAlamoLanguageDefinitionTest.cs | 20 ++-- .../Services/LanguageServiceTest.cs | 14 +-- .../Config/ITranslationProjectDescriptor.cs | 28 ------ .../Config/TranslationProjectDescriptor.cs | 29 +++--- .../Data/Config/v1/LocalisationDataType.cs | 4 +- .../Data/Config/v1/LocalisationType.cs | 2 +- .../Data/Config/v1/TranslationType.cs | 2 +- .../Data/Config/v2/GameType.cs | 18 ---- .../Data/IKeyedTranslationDatabase.cs | 6 +- .../Data/IOrderedTranslationDatabase.cs | 2 +- .../Data/ITranslationDatabase.cs | 4 +- .../Data/ITranslationDatabaseBuilder.cs | 6 +- .../Data/ITranslationDatabaseFactory.cs | 4 +- .../Data/ITranslationEntry.cs | 25 ----- .../Data/Internal/KeyedTranslationDatabase.cs | 59 ++++-------- .../Internal/OrderedTranslationDatabase.cs | 45 +++------ .../Data/Internal/TranslationDatabaseBase.cs | 44 +++++++++ .../Internal/TranslationDatabaseBuilder.cs | 2 +- .../Data/TranslationDatabaseFactory.cs | 9 +- .../Data/{Internal => }/TranslationEntry.cs | 15 ++- .../IO/Csv/CsvTranslationExporter.cs | 47 +++++---- .../IO/Csv/CsvTranslationImporter.cs | 95 ++++++------------- .../IO/Dat/DatTranslationExporter.cs | 4 +- .../IO/Xml/XmlTranslationExporter.cs | 2 +- .../Languages/AlamoLanguageDefinitionBase.cs | 6 +- .../OfficiallySupportedLanguageAttribute.cs | 6 +- .../{AlamoGameContext.cs => GameContext.cs} | 8 +- .../Languages/IAlamoLanguageDefinition.cs | 2 +- .../JapaneseAlamoLanguageDefinition.cs | 2 +- .../KoreanAlamoLanguageDefinition.cs | 2 +- .../Languages/ThaiAlamoLanguageDefinition.cs | 2 +- .../PG.StarWarsGame.Localisation.csproj | 1 + .../Services/ILanguageService.cs | 7 +- .../Services/LanguageService.cs | 7 +- 45 files changed, 340 insertions(+), 429 deletions(-) delete mode 100644 PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/CommonBaselineTestBase.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/ITranslationProjectDescriptor.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/GameType.cs delete mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationEntry.cs create mode 100644 PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationDatabaseBase.cs rename PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/{Internal => }/TranslationEntry.cs (63%) rename PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/{AlamoGameContext.cs => GameContext.cs} (73%) 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 index 8e68df6cf..cab76e729 100644 --- a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/BaselineTranslationProviderTest.cs +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/BaselineTranslationProviderTest.cs @@ -3,21 +3,27 @@ using System; using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Localisation.Data.Config.v2; using PG.StarWarsGame.Localisation.Languages; +using PG.Testing; using Xunit; namespace PG.StarWarsGame.Localisation.Baseline.Test; -public class BaselineTranslationProviderTest : CommonBaselineTestBase +public class BaselineTranslationProviderTest : PGTestBase { + protected override void SetupServices(IServiceCollection serviceCollection) + { + base.SetupServices(serviceCollection); + serviceCollection.SupportLocalisationBaseline(); + } + private IBaselineTranslationProvider Provider => ServiceProvider.GetRequiredService(); [Theory] - [InlineData(GameType.EaW)] - [InlineData(GameType.FoC)] - public void GetMasterText_SingleLanguage_IsNonEmpty(GameType game) + [InlineData(GameContext.EaW)] + [InlineData(GameContext.FoC)] + public void GetMasterText_SingleLanguage_IsNonEmpty(GameContext game) { var lang = new EnglishAlamoLanguageDefinition(); var db = Provider.GetMasterText(game, lang); @@ -25,9 +31,9 @@ public void GetMasterText_SingleLanguage_IsNonEmpty(GameType game) } [Theory] - [InlineData(GameType.EaW)] - [InlineData(GameType.FoC)] - public void GetCreditsText_SingleLanguage_IsNonEmpty(GameType game) + [InlineData(GameContext.EaW)] + [InlineData(GameContext.FoC)] + public void GetCreditsText_SingleLanguage_IsNonEmpty(GameContext game) { var lang = new EnglishAlamoLanguageDefinition(); var db = Provider.GetCreditsText(game, lang); @@ -42,7 +48,7 @@ public void GetMasterText_MultiLanguage_ContainsAllRequestedLanguages() new EnglishAlamoLanguageDefinition(), new GermanAlamoLanguageDefinition(), }; - var db = Provider.GetMasterText(GameType.EaW, langs); + var db = Provider.GetMasterText(GameContext.EaW, langs); Assert.Equal(2, db.Languages.Count); } @@ -50,22 +56,13 @@ public void GetMasterText_MultiLanguage_ContainsAllRequestedLanguages() public void GetMasterText_EaW_English_ContainsKnownKey() { var lang = new EnglishAlamoLanguageDefinition(); - var db = Provider.GetMasterText(GameType.EaW, lang); - // At minimum the database should be populated — spot-check it has entries + var db = Provider.GetMasterText(GameContext.EaW, lang); Assert.True(db.Count > 100); } [Fact] - public void GetMasterText_UnsupportedGame_Throws() + public void GetMasterText_NullLanguage_Throws() { - var lang = new EnglishAlamoLanguageDefinition(); - Assert.Throws(() => Provider.GetMasterText(GameType.Mod, lang)); - } - - [Fact] - public void GetCreditsText_UnsupportedGame_Throws() - { - var lang = new EnglishAlamoLanguageDefinition(); - Assert.Throws(() => Provider.GetCreditsText(GameType.Mod, lang)); + Assert.Throws(() => Provider.GetMasterText(GameContext.EaW, (IAlamoLanguageDefinition)null!)); } } diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/CommonBaselineTestBase.cs b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/CommonBaselineTestBase.cs deleted file mode 100644 index f6a2560c6..000000000 --- a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline.Test/CommonBaselineTestBase.cs +++ /dev/null @@ -1,23 +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 Microsoft.Extensions.DependencyInjection; -using PG.Commons; - -namespace PG.StarWarsGame.Localisation.Baseline.Test; - -public abstract class CommonBaselineTestBase : IDisposable -{ - protected ServiceProvider ServiceProvider { get; } - - protected CommonBaselineTestBase() - { - var sc = new ServiceCollection(); - PetroglyphCommons.ContributeServices(sc); - sc.SupportLocalisationBaseline(); - ServiceProvider = sc.BuildServiceProvider(); - } - - public void Dispose() => ServiceProvider.Dispose(); -} diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineServiceContribution.cs b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineServiceContribution.cs index 50797bb0c..43243cd3c 100644 --- a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineServiceContribution.cs +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineServiceContribution.cs @@ -2,9 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using PG.StarWarsGame.Files.DAT; -using Testably.Abstractions; namespace PG.StarWarsGame.Localisation.Baseline { @@ -18,9 +16,6 @@ public static class BaselineServiceContribution /// public static IServiceCollection SupportLocalisationBaseline(this IServiceCollection serviceCollection) { - // Register the real file system if none has been provided (allows test overrides). - serviceCollection.TryAddSingleton(new RealFileSystem()); - serviceCollection.SupportDAT(); serviceCollection.SupportLocalisation(); diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineTranslationProvider.cs b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineTranslationProvider.cs index c141f01ad..46420396e 100644 --- a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineTranslationProvider.cs +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/BaselineTranslationProvider.cs @@ -4,64 +4,60 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Abstractions; 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.Data.Config.v2; using PG.StarWarsGame.Localisation.IO.Dat; using PG.StarWarsGame.Localisation.Languages; namespace PG.StarWarsGame.Localisation.Baseline { - /// - /// Default implementation of . - /// Loads translation data from embedded EaW and FoC game DAT files. - /// - public sealed class BaselineTranslationProvider : IBaselineTranslationProvider + internal sealed class BaselineTranslationProvider : ServiceBase, IBaselineTranslationProvider { - // Only 5 languages have baseline data (the ones that shipped with the original games). + // 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"), + ["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 IFileSystem _fileSystem; private readonly IDatFileService _datFileService; private readonly IDatTranslationImporter _importer; private readonly ITranslationDatabaseFactory _factory; - /// Initialises a new instance using services from the given provider. - public BaselineTranslationProvider(IServiceProvider services) + public BaselineTranslationProvider(IServiceProvider services) : base(services) { - if (services is null) throw new ArgumentNullException(nameof(services)); - _fileSystem = services.GetRequiredService(); _datFileService = services.GetRequiredService(); _importer = services.GetRequiredService(); _factory = services.GetRequiredService(); } /// - public IKeyedTranslationDatabase GetMasterText(GameType game, IAlamoLanguageDefinition language) + public IKeyedTranslationDatabase GetMasterText(GameContext game, IAlamoLanguageDefinition language) { if (language is null) throw new ArgumentNullException(nameof(language)); - ValidateGame(game); var db = _factory.CreateKeyed(new[] { language }); LoadInto(db, game, "mastertextfile", language); return db; } /// - public IKeyedTranslationDatabase GetMasterText(GameType game, IReadOnlyList languages) + public IKeyedTranslationDatabase GetMasterText(GameContext game, IReadOnlyList languages) { if (languages is null) throw new ArgumentNullException(nameof(languages)); - ValidateGame(game); var db = _factory.CreateKeyed(languages); foreach (var lang in languages) LoadInto(db, game, "mastertextfile", lang); @@ -69,27 +65,25 @@ public IKeyedTranslationDatabase GetMasterText(GameType game, IReadOnlyList - public IOrderedTranslationDatabase GetCreditsText(GameType game, IAlamoLanguageDefinition language) + public IOrderedTranslationDatabase GetCreditsText(GameContext game, IAlamoLanguageDefinition language) { if (language is null) throw new ArgumentNullException(nameof(language)); - ValidateGame(game); var db = _factory.CreateOrdered(new[] { language }); LoadInto(db, game, "creditstext", language); return db; } /// - public IOrderedTranslationDatabase GetCreditsText(GameType game, IReadOnlyList languages) + public IOrderedTranslationDatabase GetCreditsText(GameContext game, IReadOnlyList languages) { if (languages is null) throw new ArgumentNullException(nameof(languages)); - ValidateGame(game); var db = _factory.CreateOrdered(languages); foreach (var lang in languages) LoadInto(db, game, "creditstext", lang); return db; } - private void LoadInto(ITranslationDatabase db, GameType game, string filePrefix, IAlamoLanguageDefinition language) + private void LoadInto(ITranslationDatabase db, GameContext game, string filePrefix, IAlamoLanguageDefinition language) { if (!LanguageMap.TryGetValue(language.LanguageIdentifier, out var map)) return; @@ -98,13 +92,13 @@ private void LoadInto(ITranslationDatabase db, GameType game, string filePrefix, using var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); if (resourceStream is null) return; - var tempPath = _fileSystem.Path.Combine( - _fileSystem.Path.GetTempPath(), + 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)) + using (var fs = FileSystem.FileStream.New(tempPath, FileMode.Create, FileAccess.Write)) resourceStream.CopyTo(fs); var datFile = _datFileService.Load(tempPath); @@ -112,22 +106,21 @@ private void LoadInto(ITranslationDatabase db, GameType game, string filePrefix, } finally { - if (_fileSystem.File.Exists(tempPath)) - _fileSystem.File.Delete(tempPath); + try + { + FileSystem.File.Delete(tempPath); + } + catch + { + // NOP + } } } - private static string BuildResourceName(GameType game, string langFolder, string fileName) + private static string BuildResourceName(GameContext game, string langFolder, string fileName) { - var gameFolder = game == GameType.EaW ? "EaW" : "FoC"; + var gameFolder = game == GameContext.EaW ? "EaW" : "FoC"; return $"PG.StarWarsGame.Localisation.Baseline.Resources.{gameFolder}.{langFolder}.{fileName}"; } - - private static void ValidateGame(GameType game) - { - if (game != GameType.EaW && game != GameType.FoC) - throw new ArgumentException( - $"Baseline data is only available for EaW and FoC, not '{game}'.", nameof(game)); - } } } diff --git a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/IBaselineTranslationProvider.cs b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/IBaselineTranslationProvider.cs index 4cb0713b1..527085373 100644 --- a/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/IBaselineTranslationProvider.cs +++ b/PG.StarWarsGame.Localisation.Baseline/PG.StarWarsGame.Localisation.Baseline/IBaselineTranslationProvider.cs @@ -1,10 +1,8 @@ // 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; -using PG.StarWarsGame.Localisation.Data.Config.v2; using PG.StarWarsGame.Localisation.Languages; namespace PG.StarWarsGame.Localisation.Baseline @@ -17,25 +15,37 @@ public interface IBaselineTranslationProvider /// /// Returns a keyed translation database (MasterText) for the given game and single language. /// - /// When is null. - /// When is not EaW or FoC. - IKeyedTranslationDatabase GetMasterText(GameType game, IAlamoLanguageDefinition 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. /// - IKeyedTranslationDatabase GetMasterText(GameType game, IReadOnlyList 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. /// - /// When is null. - /// When is not EaW or FoC. - IOrderedTranslationDatabase GetCreditsText(GameType game, IAlamoLanguageDefinition 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. /// - IOrderedTranslationDatabase GetCreditsText(GameType game, IReadOnlyList 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/PG.StarWarsGame.Localisation.Test/Data/TranslationDatabaseBuilderTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationDatabaseBuilderTest.cs index c90781b4a..32bb9a00b 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationDatabaseBuilderTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationDatabaseBuilderTest.cs @@ -48,7 +48,7 @@ public void WithLanguages_AddsMultipleLanguages() [Fact] public void WithActiveLanguage_SetsActiveLanguageOnBuiltDatabase() { - var db = Builder().WithLanguage(En).WithActiveLanguage(En).BuildKeyed(); + var db = Builder().WithLanguage(En).SetActiveLanguage(En).BuildKeyed(); Assert.Equal(En, db.ActiveLanguage); } @@ -56,7 +56,7 @@ public void WithActiveLanguage_SetsActiveLanguageOnBuiltDatabase() public void WithActiveLanguage_NotInLanguages_ThrowsAtBuildTime() { Assert.Throws(() => - Builder().WithLanguage(En).WithActiveLanguage(De).BuildKeyed()); + Builder().WithLanguage(En).SetActiveLanguage(De).BuildKeyed()); } [Fact] @@ -72,7 +72,7 @@ public void Builder_IsChainable() var db = Builder() .WithLanguage(En) .WithLanguage(De) - .WithActiveLanguage(En) + .SetActiveLanguage(En) .BuildOrdered(); Assert.Equal(2, db.Languages.Count); diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationProjectDescriptorTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationProjectDescriptorTest.cs index db4fdb119..641fe6917 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationProjectDescriptorTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Data/TranslationProjectDescriptorTest.cs @@ -18,8 +18,8 @@ public class TranslationProjectDescriptorTest [Fact] public void Constructor_SetsAllProperties() { - var d = new TranslationProjectDescriptor(GameType.EaW, OverrideType.Core, TranslationResourceType.Dat, TwoLangs); - Assert.Equal(GameType.EaW, d.Game); + 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); @@ -29,14 +29,14 @@ public void Constructor_SetsAllProperties() public void Constructor_Throws_WhenLanguagesIsNull() { Assert.Throws(() => - new TranslationProjectDescriptor(GameType.EaW, OverrideType.Core, TranslationResourceType.Dat, null!)); + new TranslationProjectDescriptor(GameContext.EaW, OverrideType.Core, TranslationResourceType.Dat, null!)); } [Fact] public void Languages_IsImmutable() { var mutable = new List { new EnglishAlamoLanguageDefinition() }; - var d = new TranslationProjectDescriptor(GameType.EaW, OverrideType.Core, TranslationResourceType.Xml, mutable); + var d = new TranslationProjectDescriptor(GameContext.EaW, OverrideType.Core, TranslationResourceType.Xml, mutable); mutable.Add(new GermanAlamoLanguageDefinition()); Assert.Single(d.Languages); } @@ -44,8 +44,8 @@ public void Languages_IsImmutable() [Fact] public void Equality_TwoDescriptorsWithSameValues_AreEqual() { - var a = new TranslationProjectDescriptor(GameType.FoC, OverrideType.Mod, TranslationResourceType.Csv, TwoLangs); - var b = new TranslationProjectDescriptor(GameType.FoC, OverrideType.Mod, TranslationResourceType.Csv, TwoLangs); + 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 index 0da9306ad..458772d2b 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/CsvTranslationAdapterTest.cs @@ -71,6 +71,33 @@ public void Import_EmptyCsv_LeavesDbEmpty() 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() { diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs index ebb6fcf26..50b9fb61c 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/IO/XmlTranslationAdapterTest.cs @@ -57,7 +57,7 @@ public void Export_ProducesValidXml() public void Import_EmptyXml_LeavesDbEmpty() { var xml = new XDocument(new XElement( - XName.Get("LocalisationData", "http://www.example.org/eaw-translation/"))); + XName.Get("LocalisationData", "urn:alamoenginetools:localisation:v1"))); var db = new TranslationDatabaseFactory().CreateKeyed(new[] { En }); CreateImporter().Import(xml, db); Assert.Empty(db); @@ -84,7 +84,7 @@ public void Export_KeyedDatabase_WritesKeysAlphabetically() db.SetTranslation("MONKEY", En, "m"); var xml = CreateExporter().Export(db); - XNamespace ns = "http://www.example.org/eaw-translation/"; + XNamespace ns = "urn:alamoenginetools:localisation:v1"; var keys = xml.Root! .Elements(ns + "Localisation") .Select(e => e.Attribute("key")!.Value) diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs index 23fc182f4..4e2a18a70 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Languages/AbstractAlamoLanguageDefinitionTest.cs @@ -60,28 +60,28 @@ public void Equals_ReturnsFalseForDifferentLanguages() public void IsOfficiallySupported_WithContext_ReturnsTrueForBothContexts_WhenNoContextsSpecified() { var lang = new EnglishAlamoLanguageDefinition(); - Assert.True(lang.IsOfficiallySupported(AlamoGameContext.BaseGame)); - Assert.True(lang.IsOfficiallySupported(AlamoGameContext.Expansion)); + 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(AlamoGameContext.BaseGame)); - Assert.False(lang.IsOfficiallySupported(AlamoGameContext.Expansion)); + 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(AlamoGameContext.BaseGame)); - Assert.False(baseOnly.IsOfficiallySupported(AlamoGameContext.Expansion)); + Assert.True(baseOnly.IsOfficiallySupported(GameContext.EaW)); + Assert.False(baseOnly.IsOfficiallySupported(GameContext.FoC)); var expOnly = new ExpansionOnlyTestLanguage(); - Assert.False(expOnly.IsOfficiallySupported(AlamoGameContext.BaseGame)); - Assert.True(expOnly.IsOfficiallySupported(AlamoGameContext.Expansion)); + Assert.False(expOnly.IsOfficiallySupported(GameContext.EaW)); + Assert.True(expOnly.IsOfficiallySupported(GameContext.FoC)); } private sealed class UnsupportedTestLanguage : AlamoLanguageDefinitionBase @@ -90,14 +90,14 @@ private sealed class UnsupportedTestLanguage : AlamoLanguageDefinitionBase protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; } - [OfficiallySupportedLanguage(AlamoGameContext.BaseGame)] + [OfficiallySupportedLanguage(GameContext.EaW)] private sealed class BaseGameOnlyTestLanguage : AlamoLanguageDefinitionBase { protected override string ConfiguredLanguageIdentifier => "BASE_ONLY"; protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; } - [OfficiallySupportedLanguage(AlamoGameContext.Expansion)] + [OfficiallySupportedLanguage(GameContext.FoC)] private sealed class ExpansionOnlyTestLanguage : AlamoLanguageDefinitionBase { protected override string ConfiguredLanguageIdentifier => "EXP_ONLY"; diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs index a50097cb0..cb20bdd6a 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.Test/Services/LanguageServiceTest.cs @@ -115,8 +115,8 @@ public void OfficiallySupported_WithContext_ReturnsOnlyContextMatchingLanguages( var both = new EnglishAlamoLanguageDefinition(); var svc = new LanguageService(new IAlamoLanguageDefinition[] { baseOnly, expOnly, both }); - var baseResult = svc.OfficiallySupported(AlamoGameContext.BaseGame); - var expResult = svc.OfficiallySupported(AlamoGameContext.Expansion); + var baseResult = svc.OfficiallySupported(GameContext.EaW); + var expResult = svc.OfficiallySupported(GameContext.FoC); Assert.Contains(baseOnly, baseResult); Assert.Contains(both, baseResult); @@ -133,15 +133,15 @@ public void IsOfficiallySupported_WithContext_ReturnsCorrectResultPerContext() var baseOnly = new BaseGameOnlyTestLanguage(); var svc = new LanguageService(new IAlamoLanguageDefinition[] { baseOnly, new EnglishAlamoLanguageDefinition() }); - Assert.True(svc.IsOfficiallySupported(baseOnly, AlamoGameContext.BaseGame)); - Assert.False(svc.IsOfficiallySupported(baseOnly, AlamoGameContext.Expansion)); + 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!, AlamoGameContext.BaseGame)); + Assert.Throws(() => svc.IsOfficiallySupported(null!, GameContext.EaW)); } private sealed class UnsupportedTestLanguage : AlamoLanguageDefinitionBase @@ -150,14 +150,14 @@ private sealed class UnsupportedTestLanguage : AlamoLanguageDefinitionBase protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; } - [OfficiallySupportedLanguage(AlamoGameContext.BaseGame)] + [OfficiallySupportedLanguage(GameContext.EaW)] private sealed class BaseGameOnlyTestLanguage : AlamoLanguageDefinitionBase { protected override string ConfiguredLanguageIdentifier => "BASE_ONLY"; protected override CultureInfo ConfiguredCulture => CultureInfo.InvariantCulture; } - [OfficiallySupportedLanguage(AlamoGameContext.Expansion)] + [OfficiallySupportedLanguage(GameContext.FoC)] private sealed class ExpansionOnlyTestLanguage : AlamoLanguageDefinitionBase { protected override string ConfiguredLanguageIdentifier => "EXP_ONLY"; diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/ITranslationProjectDescriptor.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/ITranslationProjectDescriptor.cs deleted file mode 100644 index dc322c84d..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/ITranslationProjectDescriptor.cs +++ /dev/null @@ -1,28 +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.Collections.Generic; -using PG.StarWarsGame.Localisation.Data.Config.v2; -using PG.StarWarsGame.Localisation.Languages; - -namespace PG.StarWarsGame.Localisation.Data.Config -{ - /// - /// Describes a translation project: which game it targets, its override level, - /// the resource format it uses, and the languages it covers. - /// - public interface ITranslationProjectDescriptor - { - /// Gets the game this project provides translations for. - GameType Game { get; } - - /// Gets whether this project provides core, expansion, or mod text. - OverrideType OverrideType { get; } - - /// Gets the file format used to store translation resources. - TranslationResourceType ResourceType { get; } - - /// Gets the languages covered by this translation project. - IReadOnlyList Languages { get; } - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/TranslationProjectDescriptor.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/TranslationProjectDescriptor.cs index 9d8327ebb..1de8dd0d2 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/TranslationProjectDescriptor.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/TranslationProjectDescriptor.cs @@ -3,6 +3,7 @@ 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; @@ -12,35 +13,37 @@ namespace PG.StarWarsGame.Localisation.Data.Config /// /// Immutable descriptor for a translation project. /// - public sealed class TranslationProjectDescriptor : ITranslationProjectDescriptor, - IEquatable + public sealed class TranslationProjectDescriptor : IEquatable { - /// - public GameType Game { get; } + 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; } - /// - public IReadOnlyList Languages { get; } + /// Gets the languages covered by this translation project. + public IReadOnlyCollection Languages { get; } /// Initialises a new . public TranslationProjectDescriptor( - GameType game, + GameContext game, OverrideType overrideType, TranslationResourceType resourceType, - IReadOnlyList languages) + 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; - Languages = languages.ToList().AsReadOnly(); } /// @@ -51,7 +54,7 @@ public bool Equals(TranslationProjectDescriptor? other) return Game == other.Game && OverrideType == other.OverrideType && ResourceType == other.ResourceType - && Languages.SequenceEqual(other.Languages); + && _languagesSet.SetEquals(other._languagesSet); } /// @@ -62,7 +65,7 @@ public override bool Equals(object? obj) => public override int GetHashCode() { var hash = HashCode.Combine((int)Game, (int)OverrideType, (int)ResourceType); - foreach (var lang in Languages) + 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 62037f122..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 @@ -16,8 +16,8 @@ namespace PG.StarWarsGame.Localisation.Data.Config.v1 [Serializable] [DebuggerStepThrough] [DesignerCategory("code")] - [XmlType(Namespace = "http://www.example.org/eaw-translation/")] - [XmlRoot("LocalisationData", Namespace = "http://www.example.org/eaw-translation/", IsNullable = false)] + [XmlType(Namespace = "urn:alamoenginetools:localisation:v1")] + [XmlRoot("LocalisationData", Namespace = "urn:alamoenginetools:localisation:v1", IsNullable = false)] public class LocalisationDataType : object, INotifyPropertyChanged { private LocalisationType[]? m_localisationField; 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 0f357dc9f..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 @@ -16,7 +16,7 @@ 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 LocalisationType : object, INotifyPropertyChanged { private TranslationType[]? m_translationDataField; 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 7c2a67334..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,7 +16,7 @@ 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; 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 31211a941..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Config/v2/GameType.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. - -namespace PG.StarWarsGame.Localisation.Data.Config.v2 -{ - /// - /// Identifies which Petroglyph Star Wars game a translation project targets. - /// - public enum GameType - { - /// Empire at War (base game). - EaW, - /// Forces of Corruption (expansion). - FoC, - /// A community mod. - Mod - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IKeyedTranslationDatabase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IKeyedTranslationDatabase.cs index 638bfb4fd..9c5889b11 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IKeyedTranslationDatabase.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IKeyedTranslationDatabase.cs @@ -1,6 +1,8 @@ // 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 { /// @@ -13,7 +15,7 @@ public interface IKeyedTranslationDatabase : ITranslationDatabase bool ContainsKey(string key); /// Attempts to retrieve the entry for . - bool TryGetEntry(string key, out ITranslationEntry? entry); + bool TryGetEntry(string key, [NotNullWhen(true)] out TranslationEntry? entry); /// /// Attempts to retrieve the translation for using . @@ -21,6 +23,6 @@ public interface IKeyedTranslationDatabase : ITranslationDatabase /// /// is . /// - bool TryGetTranslation(string key, out string? value); + 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 index 23e8e2791..2cf806024 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IOrderedTranslationDatabase.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/IOrderedTranslationDatabase.cs @@ -19,6 +19,6 @@ public interface IOrderedTranslationDatabase : ITranslationDatabase void InsertAt(int index, string key, IAlamoLanguageDefinition language, string value); /// Returns all entries whose key equals . - IReadOnlyList GetAllEntriesForKey(string key); + 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 index f14ebad81..8c5e78b90 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabase.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabase.cs @@ -7,10 +7,10 @@ namespace PG.StarWarsGame.Localisation.Data { /// - /// An in-memory store of objects for one or more languages. + /// An in-memory store of objects for one or more languages. /// Changes are fully decoupled from any file format. /// - public interface ITranslationDatabase : IReadOnlyList + public interface ITranslationDatabase : IReadOnlyList { /// Gets the languages tracked by this database. IReadOnlyList Languages { get; } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseBuilder.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseBuilder.cs index 5dbc161e6..777bb2ee8 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseBuilder.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseBuilder.cs @@ -20,10 +20,10 @@ public interface ITranslationDatabaseBuilder ITranslationDatabaseBuilder WithLanguages(IEnumerable languages); /// - /// Sets the active language for the database. - /// Validation that the language is present in the database's language list occurs at build time. + /// 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 WithActiveLanguage(IAlamoLanguageDefinition language); + ITranslationDatabaseBuilder SetActiveLanguage(IAlamoLanguageDefinition language); /// /// Builds a keyed translation database (unique keys, MasterText semantics). diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseFactory.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseFactory.cs index a88d05294..29e17acc5 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseFactory.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationDatabaseFactory.cs @@ -16,13 +16,13 @@ public interface ITranslationDatabaseFactory IKeyedTranslationDatabase CreateKeyed(IReadOnlyList languages); /// Creates a keyed database using the languages from . - IKeyedTranslationDatabase CreateKeyed(ITranslationProjectDescriptor descriptor); + 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(ITranslationProjectDescriptor descriptor); + 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/ITranslationEntry.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationEntry.cs deleted file mode 100644 index 790d1d9c0..000000000 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/ITranslationEntry.cs +++ /dev/null @@ -1,25 +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.Collections.Generic; -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 interface ITranslationEntry - { - /// Gets the translation key. - string Key { get; } - - /// Gets all available translations, keyed by language definition. - IReadOnlyDictionary Translations { get; } - - /// - /// Attempts to retrieve the translation value for . - /// - bool TryGetTranslation(IAlamoLanguageDefinition language, out string? value); - } -} diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/KeyedTranslationDatabase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/KeyedTranslationDatabase.cs index 68259bffd..a6b78b5b2 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/KeyedTranslationDatabase.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/KeyedTranslationDatabase.cs @@ -2,44 +2,25 @@ // 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.Diagnostics.CodeAnalysis; using System.Linq; using PG.StarWarsGame.Localisation.Languages; namespace PG.StarWarsGame.Localisation.Data.Internal { - internal sealed class KeyedTranslationDatabase : IKeyedTranslationDatabase + internal sealed class KeyedTranslationDatabase : TranslationDatabaseBase, IKeyedTranslationDatabase { - private readonly Dictionary _entries = - new Dictionary(StringComparer.Ordinal); + private readonly Dictionary _entries = new(StringComparer.Ordinal); - private IAlamoLanguageDefinition? _activeLanguage; + public override int Count => _entries.Count; - public IReadOnlyList Languages { get; } - - public int Count => _entries.Count; - - public ITranslationEntry this[int index] => _entries.Values.ElementAt(index); - - public IAlamoLanguageDefinition? ActiveLanguage - { - get => _activeLanguage; - set - { - if (value is not null && !Languages.Contains(value)) - throw new ArgumentException( - $"Language '{value.LanguageIdentifier}' is not registered in this database.", nameof(value)); - _activeLanguage = value; - } - } + public override TranslationEntry this[int index] => _entries.Values.ElementAt(index); internal KeyedTranslationDatabase(IReadOnlyList languages) - { - Languages = languages; - } + : base(languages) { } - public bool SetTranslation(string key, IAlamoLanguageDefinition language, string value) + 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)); @@ -56,26 +37,20 @@ public bool SetTranslation(string key, IAlamoLanguageDefinition language, string return true; } - public bool RemoveEntry(string key) => _entries.Remove(key); + public override bool RemoveEntry(string key) => _entries.Remove(key); - public void Clear() => _entries.Clear(); + public override void Clear() => _entries.Clear(); public bool ContainsKey(string key) => _entries.ContainsKey(key); - public bool TryGetEntry(string key, out ITranslationEntry? entry) + public bool TryGetEntry(string key, [NotNullWhen(true)] out TranslationEntry? entry) { - if (_entries.TryGetValue(key, out var e)) - { - entry = e; - return true; - } - entry = null; - return false; + return _entries.TryGetValue(key, out entry); } - public bool TryGetTranslation(string key, out string? value) + public bool TryGetTranslation(string key, [MaybeNullWhen(false)] out string? value) { - if (_activeLanguage is null) + if (ActiveLanguage is null) throw new InvalidOperationException( "ActiveLanguage must be set before calling TryGetTranslation(string, out string?)."); if (!TryGetEntry(key, out var entry)) @@ -83,12 +58,10 @@ public bool TryGetTranslation(string key, out string? value) value = null; return false; } - return entry!.TryGetTranslation(_activeLanguage, out value); + return entry.TryGetTranslation(ActiveLanguage, out value); } - public IEnumerator GetEnumerator() => - _entries.Values.Cast().GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + 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 index f372b5da9..a07f6655e 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/OrderedTranslationDatabase.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/OrderedTranslationDatabase.cs @@ -2,43 +2,24 @@ // 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 sealed class OrderedTranslationDatabase : IOrderedTranslationDatabase + internal sealed class OrderedTranslationDatabase : TranslationDatabaseBase, IOrderedTranslationDatabase { - private readonly List _entries = new List(); + private readonly List _entries = new(); - private IAlamoLanguageDefinition? _activeLanguage; + public override int Count => _entries.Count; - public IReadOnlyList Languages { get; } - - public int Count => _entries.Count; - - public ITranslationEntry this[int index] => _entries[index]; - - public IAlamoLanguageDefinition? ActiveLanguage - { - get => _activeLanguage; - set - { - if (value is not null && !Languages.Contains(value)) - throw new ArgumentException( - $"Language '{value.LanguageIdentifier}' is not registered in this database.", nameof(value)); - _activeLanguage = value; - } - } + public override TranslationEntry this[int index] => _entries[index]; internal OrderedTranslationDatabase(IReadOnlyList languages) - { - Languages = languages; - } + : base(languages) { } - public bool SetTranslation(string key, IAlamoLanguageDefinition language, string value) + 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)); @@ -49,7 +30,7 @@ public bool SetTranslation(string key, IAlamoLanguageDefinition language, string return true; } - public bool RemoveEntry(string key) + public override bool RemoveEntry(string key) { var idx = _entries.FindIndex(e => e.Key == key); if (idx < 0) return false; @@ -57,7 +38,7 @@ public bool RemoveEntry(string key) return true; } - public void Clear() => _entries.Clear(); + public override void Clear() => _entries.Clear(); public void InsertAt(int index, string key, IAlamoLanguageDefinition language, string value) { @@ -69,12 +50,10 @@ public void InsertAt(int index, string key, IAlamoLanguageDefinition language, s _entries.Insert(index, entry); } - public IReadOnlyList GetAllEntriesForKey(string key) => - _entries.Where(e => e.Key == key).Cast().ToList().AsReadOnly(); - - public IEnumerator GetEnumerator() => - _entries.Cast().GetEnumerator(); + public IReadOnlyList GetAllEntriesForKey(string key) => + _entries.Where(e => e.Key == key).ToList().AsReadOnly(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + 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 index d649b10dc..f2dab8da2 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationDatabaseBuilder.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationDatabaseBuilder.cs @@ -27,7 +27,7 @@ public ITranslationDatabaseBuilder WithLanguages(IEnumerable - public IKeyedTranslationDatabase CreateKeyed(ITranslationProjectDescriptor descriptor) + public IKeyedTranslationDatabase CreateKeyed(TranslationProjectDescriptor descriptor) { if (descriptor is null) throw new ArgumentNullException(nameof(descriptor)); - return CreateKeyed(descriptor.Languages); + return CreateKeyed(descriptor.Languages.ToList()); } /// @@ -36,10 +37,10 @@ public IOrderedTranslationDatabase CreateOrdered(IReadOnlyList - public IOrderedTranslationDatabase CreateOrdered(ITranslationProjectDescriptor descriptor) + public IOrderedTranslationDatabase CreateOrdered(TranslationProjectDescriptor descriptor) { if (descriptor is null) throw new ArgumentNullException(nameof(descriptor)); - return CreateOrdered(descriptor.Languages); + return CreateOrdered(descriptor.Languages.ToList()); } /// diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationEntry.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationEntry.cs similarity index 63% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationEntry.cs rename to PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationEntry.cs index 0ab2b6136..4b01965c0 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/Internal/TranslationEntry.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Data/TranslationEntry.cs @@ -3,16 +3,22 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using PG.StarWarsGame.Localisation.Languages; -namespace PG.StarWarsGame.Localisation.Data.Internal +namespace PG.StarWarsGame.Localisation.Data { - internal sealed class TranslationEntry : ITranslationEntry + /// + /// 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) @@ -26,7 +32,10 @@ internal void SetTranslation(IAlamoLanguageDefinition language, string value) _translations[language] = value; } - public bool TryGetTranslation(IAlamoLanguageDefinition language, out string? 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 index 523bb6ac5..c2a0f96f3 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationExporter.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationExporter.cs @@ -2,17 +2,19 @@ // 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; -using PG.StarWarsGame.Localisation.Languages; namespace PG.StarWarsGame.Localisation.IO.Csv { /// /// Default implementation of . /// Produces a CSV with header row: key,LANG1,LANG2,... - /// Values containing commas or quotes are wrapped in double quotes with internal quotes escaped. /// public sealed class CsvTranslationExporter : ICsvTranslationExporter { @@ -21,43 +23,40 @@ public string Export(ITranslationDatabase source) { if (source is null) throw new ArgumentNullException(nameof(source)); - var langs = source.Languages; + 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; - // Header - sb.Append("key"); + csv.WriteField("key"); foreach (var lang in langs) - { - sb.Append(','); - sb.Append(Escape(lang.LanguageIdentifier)); - } - sb.Append('\n'); + csv.WriteField(lang.LanguageIdentifier); + csv.NextRecord(); - // Rows — keyed databases sort alphabetically for reproducible diffs var rows = source is IKeyedTranslationDatabase - ? source.OrderBy(e => e.Key, StringComparer.Ordinal).AsEnumerable() + ? source.OrderBy(e => e.Key, StringComparer.Ordinal) : source.AsEnumerable(); foreach (var entry in rows) { - sb.Append(Escape(entry.Key)); + csv.WriteField(entry.Key); foreach (var lang in langs) { - sb.Append(','); - if (entry.TryGetTranslation(lang, out var val) && val is not null) - sb.Append(Escape(val)); + entry.TryGetTranslation(lang, out var val); + csv.WriteField(val ?? string.Empty); } - sb.Append('\n'); + csv.NextRecord(); } + writer.Flush(); return sb.ToString(); } - - private static string Escape(string value) - { - if (value.IndexOf(',') >= 0 || value.IndexOf('"') >= 0 || value.IndexOf('\n') >= 0) - return '"' + value.Replace("\"", "\"\"") + '"'; - return value; - } } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationImporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationImporter.cs index 71d4faa27..b027a6885 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationImporter.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Csv/CsvTranslationImporter.cs @@ -2,9 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using System; -using System.Collections.Generic; -using System.Linq; +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; @@ -31,83 +33,42 @@ 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 headerLine = source.ReadLine(); - if (headerLine is null) return; + var config = new CsvConfiguration(CultureInfo.InvariantCulture) + { + HasHeaderRecord = true, + MissingFieldFound = null, + }; - var headers = ParseCsvLine(headerLine); - if (headers.Count < 2) return; + using var csv = new CsvReader(source, config); - var langColumns = new List(); - for (var i = 1; i < headers.Count; i++) + 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 var lang); - langColumns.Add(lang); + _languageService.TryGetByIdentifier(headers[i], out langColumns[i - 1]); } - string? line; - while ((line = source.ReadLine()) is not null) + var dbLanguages = target.Languages; + + while (csv.Read()) { - if (string.IsNullOrWhiteSpace(line)) continue; - var fields = ParseCsvLine(line); - if (fields.Count == 0) continue; + var key = csv.GetField(0); + if (key is null) continue; - var key = fields[0]; - for (var i = 1; i < fields.Count && i - 1 < langColumns.Count; i++) + for (var i = 1; i < headers.Length; i++) { var lang = langColumns[i - 1]; if (lang is null) continue; - if (!target.Languages.Contains(lang)) continue; - target.SetTranslation(key, lang, fields[i]); - } - } - } - - private static List ParseCsvLine(string line) - { - var fields = new List(); - var i = 0; - while (i <= line.Length) - { - if (i == line.Length) - { - fields.Add(string.Empty); - break; - } - - if (line[i] == '"') - { - i++; - var sb = new System.Text.StringBuilder(); - while (i < line.Length) - { - if (line[i] == '"') - { - i++; - if (i < line.Length && line[i] == '"') - { - sb.Append('"'); - i++; - } - else break; - } - else - { - sb.Append(line[i]); - i++; - } - } - fields.Add(sb.ToString()); - if (i < line.Length && line[i] == ',') i++; - } - else - { - var start = i; - while (i < line.Length && line[i] != ',') i++; - fields.Add(line.Substring(start, i - start)); - if (i < line.Length) i++; + if (!dbLanguages.Any(l => l.Equals(lang))) continue; + var val = csv.GetField(i); + if (val is null) continue; + target.SetTranslation(key, lang, val); } } - return fields; } } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationExporter.cs index fcb4a5d40..df73dc2d3 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationExporter.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Dat/DatTranslationExporter.cs @@ -36,8 +36,8 @@ public IDatModel Export(ITranslationDatabase source, IAlamoLanguageDefinition la foreach (var entry in source) { - if (entry.TryGetTranslation(language, out var value) && value is not null) - builder.AddEntry(entry.Key, value); + 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/Xml/XmlTranslationExporter.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs index 3a10c3eba..8fafeff57 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/IO/Xml/XmlTranslationExporter.cs @@ -15,7 +15,7 @@ namespace PG.StarWarsGame.Localisation.IO.Xml /// public sealed class XmlTranslationExporter : IXmlTranslationExporter { - internal static readonly XNamespace Ns = "http://www.example.org/eaw-translation/"; + internal static readonly XNamespace Ns = "urn:alamoenginetools:localisation:v1"; /// public XDocument Export(ITranslationDatabase source) diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs index 0d8d5ac98..b5dcb50d7 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoLanguageDefinitionBase.cs @@ -13,7 +13,7 @@ namespace PG.StarWarsGame.Localisation.Languages public abstract class AlamoLanguageDefinitionBase : IAlamoLanguageDefinition { private readonly bool _isOfficiallySupported; - private readonly AlamoGameContext[] _supportedContexts; + private readonly GameContext[] _supportedContexts; /// public string LanguageIdentifier { get; } @@ -44,7 +44,7 @@ protected AlamoLanguageDefinitionBase() GetType(), typeof(OfficiallySupportedLanguageAttribute)); _isOfficiallySupported = attr != null; - _supportedContexts = attr?.SupportedContexts ?? Array.Empty(); + _supportedContexts = attr?.SupportedContexts ?? Array.Empty(); IsDefault = GetType().IsDefined(typeof(DefaultLanguageAttribute), false); } @@ -52,7 +52,7 @@ protected AlamoLanguageDefinitionBase() public bool IsOfficiallySupported() => _isOfficiallySupported; /// - public bool IsOfficiallySupported(AlamoGameContext context) + public bool IsOfficiallySupported(GameContext context) { if (!_isOfficiallySupported) return false; return _supportedContexts.Length == 0 || Array.IndexOf(_supportedContexts, context) >= 0; diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs index fa9b22a24..c56496e10 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/Attributes/OfficiallySupportedLanguageAttribute.cs @@ -17,12 +17,12 @@ 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 AlamoGameContext[] SupportedContexts { get; } + public GameContext[] SupportedContexts { get; } /// - public OfficiallySupportedLanguageAttribute(params AlamoGameContext[] contexts) + public OfficiallySupportedLanguageAttribute(params GameContext[] contexts) { - SupportedContexts = contexts ?? Array.Empty(); + SupportedContexts = contexts ?? Array.Empty(); } } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoGameContext.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/GameContext.cs similarity index 73% rename from PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoGameContext.cs rename to PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/GameContext.cs index b85cefeec..1b37e2046 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/AlamoGameContext.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/GameContext.cs @@ -4,14 +4,14 @@ namespace PG.StarWarsGame.Localisation.Languages { /// - /// Identifies the Petroglyph Star Wars game product for language-support context. + /// Identifies the Petroglyph Star Wars game product. /// - public enum AlamoGameContext + public enum GameContext { /// Star Wars: Empire at War (base game). - BaseGame, + EaW, /// Star Wars: Empire at War — Forces of Corruption (expansion). - Expansion + FoC } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs index ca08e6946..173a50ec9 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/IAlamoLanguageDefinition.cs @@ -31,7 +31,7 @@ public interface IAlamoLanguageDefinition : IEquatable /// Returns if this language is officially supported within the given /// . /// - bool IsOfficiallySupported(AlamoGameContext context); + bool IsOfficiallySupported(GameContext context); /// /// Gets a value indicating whether this is the default game language (English). diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs index 90a582cce..9d7bac474 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/JapaneseAlamoLanguageDefinition.cs @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Localisation.Languages { /// The language definition for Japanese (JA). /// Officially supported by the Alamo Engine. - [OfficiallySupportedLanguage(AlamoGameContext.BaseGame)] + [OfficiallySupportedLanguage(GameContext.EaW)] [ExcludeFromCodeCoverage] public sealed class JapaneseAlamoLanguageDefinition : AlamoLanguageDefinitionBase { diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs index 885f63089..79509396a 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/KoreanAlamoLanguageDefinition.cs @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Localisation.Languages { /// The language definition for Korean (KO). /// Officially supported by the Alamo Engine. - [OfficiallySupportedLanguage(AlamoGameContext.BaseGame)] + [OfficiallySupportedLanguage(GameContext.EaW)] [ExcludeFromCodeCoverage] public sealed class KoreanAlamoLanguageDefinition : AlamoLanguageDefinitionBase { diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs index ade72d3d3..c9f76a303 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Languages/ThaiAlamoLanguageDefinition.cs @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Localisation.Languages { /// The language definition for Thai (TH). /// Officially supported by the Alamo Engine. - [OfficiallySupportedLanguage(AlamoGameContext.BaseGame)] + [OfficiallySupportedLanguage(GameContext.EaW)] [ExcludeFromCodeCoverage] public sealed class ThaiAlamoLanguageDefinition : AlamoLanguageDefinitionBase { 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 54a3032a5..08d058bc3 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.csproj +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation.csproj @@ -29,6 +29,7 @@ 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 index 136869329..7ecfcf0e4 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/ILanguageService.cs @@ -2,6 +2,7 @@ // 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 @@ -18,7 +19,7 @@ public interface ILanguageService IReadOnlyList OfficiallySupported(); /// Returns the officially supported language definitions for the given . - IReadOnlyList OfficiallySupported(AlamoGameContext context); + IReadOnlyList OfficiallySupported(GameContext context); /// /// Gets the default game language (English). @@ -42,12 +43,12 @@ public interface ILanguageService /// /// Thrown when is . /// - bool IsOfficiallySupported(IAlamoLanguageDefinition language, AlamoGameContext context); + bool IsOfficiallySupported(IAlamoLanguageDefinition language, GameContext context); /// /// Attempts to find a language definition whose /// matches (case-insensitive). /// - bool TryGetByIdentifier(string identifier, out IAlamoLanguageDefinition? language); + bool TryGetByIdentifier(string identifier, [NotNullWhen(true)] out IAlamoLanguageDefinition? language); } } diff --git a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs index b638ba883..876a98382 100644 --- a/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs +++ b/PG.StarWarsGame.Localisation/PG.StarWarsGame.Localisation/Services/LanguageService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using PG.StarWarsGame.Localisation.Languages; @@ -46,7 +47,7 @@ public LanguageService(IEnumerable languages) public IReadOnlyList OfficiallySupported() => _officiallySupported; /// - public IReadOnlyList OfficiallySupported(AlamoGameContext context) + public IReadOnlyList OfficiallySupported(GameContext context) { return _all.Where(l => l.IsOfficiallySupported(context)).ToList().AsReadOnly(); } @@ -61,7 +62,7 @@ public bool IsOfficiallySupported(IAlamoLanguageDefinition language) } /// - public bool IsOfficiallySupported(IAlamoLanguageDefinition language, AlamoGameContext context) + public bool IsOfficiallySupported(IAlamoLanguageDefinition language, GameContext context) { if (language is null) throw new ArgumentNullException(nameof(language)); @@ -70,7 +71,7 @@ public bool IsOfficiallySupported(IAlamoLanguageDefinition language, AlamoGameCo } /// - public bool TryGetByIdentifier(string identifier, out IAlamoLanguageDefinition? language) + public bool TryGetByIdentifier(string identifier, [NotNullWhen(true)] out IAlamoLanguageDefinition? language) { return _byIdentifier.TryGetValue(identifier, out language); }