From bc5fe4e8ab279449742e6ec46d621d769d71bb5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Sat, 15 Jun 2024 16:34:49 +0200 Subject: [PATCH 01/15] Base Setup --- .../LocalisationTestConstants.cs | 24 +++++ ...rsGame.Components.Localisation.Test.csproj | 32 ++++++ .../Attributes/DefaultLanguageAttribute.cs | 34 ++++++ .../OfficiallySupportedLanguageAttribute.cs | 37 +++++++ .../Languages/AlamoLanguageDefinitionBase.cs | 100 ++++++++++++++++++ .../BuiltIn/ChineseAlamoLanguageDefinition.cs | 25 +++++ .../BuiltIn/EnglishAlamoLanguageDefinition.cs | 27 +++++ .../BuiltIn/FrenchAlamoLanguageDefinition.cs | 25 +++++ .../BuiltIn/GermanAlamoLanguageDefinition.cs | 25 +++++ .../BuiltIn/ItalianAlamoLanguageDefinition.cs | 25 +++++ .../JapaneseAlamoLanguageDefinition.cs | 25 +++++ .../BuiltIn/KoreanAlamoLanguageDefinition.cs | 25 +++++ .../BuiltIn/PolishAlamoLanguageDefinition.cs | 25 +++++ .../BuiltIn/RussianAlamoLanguageDefinition.cs | 25 +++++ .../BuiltIn/SpanishAlamoLanguageDefinition.cs | 25 +++++ .../BuiltIn/ThaiAlamoLanguageDefinition.cs | 25 +++++ .../Languages/IAlamoLanguageDefinition.cs | 24 +++++ .../LocalisationServiceContribution.cs | 16 +++ ...tarWarsGame.Components.Localisation.csproj | 37 +++++++ PetroglyphTools.sln | 12 +++ 20 files changed, 593 insertions(+) create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/LocalisationTestConstants.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/DefaultLanguageAttribute.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/OfficiallySupportedLanguageAttribute.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/AlamoLanguageDefinitionBase.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ChineseAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/EnglishAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/FrenchAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/GermanAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ItalianAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/JapaneseAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/KoreanAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/PolishAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/RussianAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/SpanishAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ThaiAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/IAlamoLanguageDefinition.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/LocalisationTestConstants.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/LocalisationTestConstants.cs new file mode 100644 index 00000000..130f2118 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/LocalisationTestConstants.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; +using System.Collections.Generic; +using PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +namespace PG.StarWarsGame.Components.Localisation.Test; + +public sealed class LocalisationTestConstants +{ + 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); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj new file mode 100644 index 00000000..946063f1 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj @@ -0,0 +1,32 @@ + + + false + net8.0 + $(TargetFrameworks);net48 + + + + 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.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/DefaultLanguageAttribute.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/DefaultLanguageAttribute.cs new file mode 100644 index 00000000..57ea11b8 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/DefaultLanguageAttribute.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; + +namespace PG.StarWarsGame.Components.Localisation.Attributes; + +/// +/// Marks the default (fallback) language. +/// +public class DefaultLanguageAttribute : Attribute +{ + /// + /// .ctor + /// + public DefaultLanguageAttribute() + { + IsDefaultLanguage = true; + } + + /// + /// .ctor + /// + /// + public DefaultLanguageAttribute(bool isDefaultLanguage) + { + IsDefaultLanguage = isDefaultLanguage; + } + + /// + /// Returns true for the default language. + /// + public bool IsDefaultLanguage { get; } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/OfficiallySupportedLanguageAttribute.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/OfficiallySupportedLanguageAttribute.cs new file mode 100644 index 00000000..b2c9ce86 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/OfficiallySupportedLanguageAttribute.cs @@ -0,0 +1,37 @@ +// 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.Diagnostics.CodeAnalysis; + +namespace PG.StarWarsGame.Components.Localisation.Attributes; + +/// +/// Marks a language as officially supported. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +[ExcludeFromCodeCoverage] +public sealed class OfficiallySupportedLanguageAttribute : Attribute +{ + /// + /// .ctor + /// + public OfficiallySupportedLanguageAttribute() + { + IsOfficiallySupported = true; + } + + /// + /// .ctor + /// + /// + public OfficiallySupportedLanguageAttribute(bool isOfficiallySupported) + { + IsOfficiallySupported = isOfficiallySupported; + } + + /// + /// Returns true if the language is officially supported. + /// + public bool IsOfficiallySupported { get; } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/AlamoLanguageDefinitionBase.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/AlamoLanguageDefinitionBase.cs new file mode 100644 index 00000000..784553d2 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/AlamoLanguageDefinitionBase.cs @@ -0,0 +1,100 @@ +// 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.Components.Localisation.Languages; + +/// +/// Abstract language definition +/// +public abstract class AlamoLanguageDefinitionBase : IAlamoLanguageDefinition, IComparable +{ + /// + /// A string that is being used to identify the language of the *.DAT file, e.g. a language identifier + /// "english" would produce the file "mastertextfile_english.dat" + /// + protected abstract string ConfiguredLanguageIdentifier { get; } + + /// + /// The .NET Culture that best describes the language. This culture can be used for spell checking, + /// auto-translation between languages, etc. + /// + protected abstract CultureInfo ConfiguredCulture { get; } + + /// + public string LanguageIdentifier => ConfiguredLanguageIdentifier; + + /// + public CultureInfo Culture => ConfiguredCulture; + + /// + public int CompareTo(object obj) + { + if (obj is not IAlamoLanguageDefinition other) + throw new ArgumentException( + $"The type of {obj.GetType()} is not assignable to {typeof(IAlamoLanguageDefinition)}. The two objects cannot be compared."); + + return CompareToInternal(other); + } + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// + /// The specific override differs in its sort order in such a way, + /// that all elements are ordered by their t. + /// + /// + /// + protected virtual int CompareToInternal(IAlamoLanguageDefinition other) + { + return string.Compare(Culture.TwoLetterISOLanguageName, other.Culture.TwoLetterISOLanguageName, + StringComparison.Ordinal); + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// + protected virtual bool EqualsInternal(IAlamoLanguageDefinition other) + { + return CompareTo(other) == 0; + } + + /// + /// Serves as the default hash function. + /// + /// + /// The default implementation does intentionally create hash conflicts between identical language definitions, + /// e.g. using the same and for two different derived classes. + /// + /// + protected virtual int GetHashCodeInternal() + { + unchecked + { + return (ConfiguredLanguageIdentifier.GetHashCode() * 397) ^ Culture.GetHashCode(); + } + } + + /// + public override bool Equals(object? obj) + { + if (obj is null) return false; + + if (ReferenceEquals(this, obj)) return true; + + return obj is IAlamoLanguageDefinition alamoLanguageDefinition && EqualsInternal(alamoLanguageDefinition); + } + + /// + public override int GetHashCode() + { + return GetHashCodeInternal(); + } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ChineseAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ChineseAlamoLanguageDefinition.cs new file mode 100644 index 00000000..80cd3830 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ChineseAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the Chinese language. +/// +/// +/// Officially supported by the Alamo Engine. +/// +[ExcludeFromCodeCoverage] +[OfficiallySupportedLanguage] +public sealed class ChineseAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "CHINESE"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("zh-CN"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/EnglishAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/EnglishAlamoLanguageDefinition.cs new file mode 100644 index 00000000..9f648374 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/EnglishAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the English (US) language. +/// +/// +/// Officially supported by the Alamo Engine.
+/// This language is the default game language and development language of all Alamo Engine games. +///
+[ExcludeFromCodeCoverage] +[DefaultLanguage] +[OfficiallySupportedLanguage] +public sealed class EnglishAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "ENGLISH"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("en-US"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/FrenchAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/FrenchAlamoLanguageDefinition.cs new file mode 100644 index 00000000..b4c070a3 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/FrenchAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the French language. +/// +/// +/// Officially supported by the Alamo Engine. +/// +[ExcludeFromCodeCoverage] +[OfficiallySupportedLanguage] +public sealed class FrenchAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "FRENCH"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("fr-FR"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/GermanAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/GermanAlamoLanguageDefinition.cs new file mode 100644 index 00000000..52a9b483 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/GermanAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the German language. +/// +/// +/// Officially supported by the Alamo Engine. +/// +[ExcludeFromCodeCoverage] +[OfficiallySupportedLanguage] +public sealed class GermanAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "GERMAN"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("de-DE"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ItalianAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ItalianAlamoLanguageDefinition.cs new file mode 100644 index 00000000..4a526bde --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ItalianAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the Italian language. +/// +/// +/// Officially supported by the Alamo Engine. +/// +[ExcludeFromCodeCoverage] +[OfficiallySupportedLanguage] +public sealed class ItalianAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "ITALIAN"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("it-IT"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/JapaneseAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/JapaneseAlamoLanguageDefinition.cs new file mode 100644 index 00000000..908f3f26 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/JapaneseAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the Japanese language. +/// +/// +/// Officially supported by the Alamo Engine. +/// +[ExcludeFromCodeCoverage] +[OfficiallySupportedLanguage] +public sealed class JapaneseAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "JAPANESE"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("ja"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/KoreanAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/KoreanAlamoLanguageDefinition.cs new file mode 100644 index 00000000..743b7ba4 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/KoreanAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the Korean language. +/// +/// +/// Officially supported by the Alamo Engine. +/// +[ExcludeFromCodeCoverage] +[OfficiallySupportedLanguage] +public sealed class KoreanAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "KOREAN"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("ko"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/PolishAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/PolishAlamoLanguageDefinition.cs new file mode 100644 index 00000000..e2a75e20 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/PolishAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the Polish language. +/// +/// +/// Officially supported by the Alamo Engine. +/// +[ExcludeFromCodeCoverage] +[OfficiallySupportedLanguage] +public sealed class PolishAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "POLISH"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("pl"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/RussianAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/RussianAlamoLanguageDefinition.cs new file mode 100644 index 00000000..3645fb14 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/RussianAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the Russian language. +/// +/// +/// Officially supported by the Alamo Engine. +/// +[ExcludeFromCodeCoverage] +[OfficiallySupportedLanguage] +public sealed class RussianAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "RUSSIAN"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("ru"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/SpanishAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/SpanishAlamoLanguageDefinition.cs new file mode 100644 index 00000000..7b019a8d --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/SpanishAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the Spanish language. +/// +/// +/// Officially supported by the Alamo Engine. +/// +[ExcludeFromCodeCoverage] +[OfficiallySupportedLanguage] +public sealed class SpanishAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "SPANISH"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("es-ES"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ThaiAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ThaiAlamoLanguageDefinition.cs new file mode 100644 index 00000000..8a968549 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/BuiltIn/ThaiAlamoLanguageDefinition.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.Diagnostics.CodeAnalysis; +using System.Globalization; +using PG.StarWarsGame.Components.Localisation.Attributes; + +namespace PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; + +/// +/// The language definition for the Thai language. +/// +/// +/// Officially supported by the Alamo Engine. +/// +[ExcludeFromCodeCoverage] +[OfficiallySupportedLanguage] +public sealed class ThaiAlamoLanguageDefinition : AlamoLanguageDefinitionBase +{ + /// + protected override string ConfiguredLanguageIdentifier => "THAI"; + + /// + protected override CultureInfo ConfiguredCulture => CultureInfo.GetCultureInfo("th"); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/IAlamoLanguageDefinition.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/IAlamoLanguageDefinition.cs new file mode 100644 index 00000000..e56dc15d --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/IAlamoLanguageDefinition.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.Globalization; + +namespace PG.StarWarsGame.Components.Localisation.Languages; + +/// +/// An interface exposing all relevant data to describe a language to be used in the Alamo Engine. +/// +public interface IAlamoLanguageDefinition +{ + /// + /// A string that is being used to identify the language of the *.DAT file, e.g. a language identifier + /// "english" would produce the file "mastertextfile_english.dat" + /// + string LanguageIdentifier { get; } + + /// + /// The .NET Culture that best describes the language. This culture can be used for spell checking, + /// auto-translation between languages, etc. + /// + CultureInfo Culture { get; } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs new file mode 100644 index 00000000..c2c1435c --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Attributes; +using PG.Commons.Extensibility; + +namespace PG.StarWarsGame.Components.Localisation; + +/// +[Order(1200)] +public class LocalisationServiceContribution : IServiceContribution +{ + /// + public void ContributeServices(IServiceCollection serviceCollection) + { + // NOP + } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj new file mode 100644 index 00000000..a3175a9f --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj @@ -0,0 +1,37 @@ + + + netstandard2.0;netstandard2.1 + PG.StarWarsGame.Components.Localisation + PG.StarWarsGame.Components.Localisation + AlamoEngineTools.PG.StarWarsGame.Components.Localisation + alamo,petroglyph,glyphx + + + 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/PetroglyphTools.sln b/PetroglyphTools.sln index 863eb544..3ef89cda 100644 --- a/PetroglyphTools.sln +++ b/PetroglyphTools.sln @@ -27,6 +27,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PG.Testing", "PG.Testing\PG EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PG.Benchmarks", "PG.Benchmarks\PG.Benchmarks.csproj", "{469BAFA4-7C54-4736-B349-3D6E84AB33FA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PG.StarWarsGame.Components.Localisation", "PG.StarWarsGame.Components.Localisation\PG.StarWarsGame.Components.Localisation\PG.StarWarsGame.Components.Localisation.csproj", "{C74397F8-9C20-47A0-AD1F-EA276BC9BA63}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PG.StarWarsGame.Components.Localisation.Test", "PG.StarWarsGame.Components.Localisation\PG.StarWarsGame.Components.Localisation.Test\PG.StarWarsGame.Components.Localisation.Test.csproj", "{E825F2AF-978E-41AF-9E14-F1D6984018FA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -77,6 +81,14 @@ Global {469BAFA4-7C54-4736-B349-3D6E84AB33FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {469BAFA4-7C54-4736-B349-3D6E84AB33FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {469BAFA4-7C54-4736-B349-3D6E84AB33FA}.Release|Any CPU.Build.0 = Release|Any CPU + {C74397F8-9C20-47A0-AD1F-EA276BC9BA63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C74397F8-9C20-47A0-AD1F-EA276BC9BA63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C74397F8-9C20-47A0-AD1F-EA276BC9BA63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C74397F8-9C20-47A0-AD1F-EA276BC9BA63}.Release|Any CPU.Build.0 = Release|Any CPU + {E825F2AF-978E-41AF-9E14-F1D6984018FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E825F2AF-978E-41AF-9E14-F1D6984018FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E825F2AF-978E-41AF-9E14-F1D6984018FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E825F2AF-978E-41AF-9E14-F1D6984018FA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From a2246165d2fb67cfe093ddcdb72b979a14da5d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Fri, 4 Oct 2024 15:31:33 +0200 Subject: [PATCH 02/15] Base Interface definition --- .../.idea/projectSettingsUpdater.xml | 6 + .idea/.idea.PetroglyphTools/.idea/vcs.xml | 6 + .../PG.Commons/Data/DataObjectMapperBase.cs | 56 +++++++++ .../PG.Commons/Data/DataObjectMapping.cs | 33 ++++++ .../PG.Commons/Data/DataObjectMappings.cs | 68 +++++++++++ .../PG.Commons/Data/IDataObjectMapping.cs | 8 ++ .../Attributes/DefaultLanguageAttribute.cs | 3 + ...tarWarsGame.Components.Localisation.csproj | 1 + .../Content/IReadOnlyTranslationItem.cs | 10 ++ .../Repository/Content/ITranslationItem.cs | 51 +++++++++ .../Content/ITranslationItemContent.cs | 17 +++ .../Repository/Content/ITranslationItemId.cs | 8 ++ .../Repository/ITranslationDiff.cs | 37 ++++++ .../Repository/ITranslationRepository.cs | 106 ++++++++++++++++++ 14 files changed, 410 insertions(+) create mode 100644 .idea/.idea.PetroglyphTools/.idea/projectSettingsUpdater.xml create mode 100644 .idea/.idea.PetroglyphTools/.idea/vcs.xml create mode 100644 PG.Commons/PG.Commons/Data/DataObjectMapperBase.cs create mode 100644 PG.Commons/PG.Commons/Data/DataObjectMapping.cs create mode 100644 PG.Commons/PG.Commons/Data/DataObjectMappings.cs create mode 100644 PG.Commons/PG.Commons/Data/IDataObjectMapping.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/IReadOnlyTranslationItem.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationDiff.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs diff --git a/.idea/.idea.PetroglyphTools/.idea/projectSettingsUpdater.xml b/.idea/.idea.PetroglyphTools/.idea/projectSettingsUpdater.xml new file mode 100644 index 00000000..86cc6c63 --- /dev/null +++ b/.idea/.idea.PetroglyphTools/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.PetroglyphTools/.idea/vcs.xml b/.idea/.idea.PetroglyphTools/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/.idea.PetroglyphTools/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Data/DataObjectMapperBase.cs b/PG.Commons/PG.Commons/Data/DataObjectMapperBase.cs new file mode 100644 index 00000000..9432ac8a --- /dev/null +++ b/PG.Commons/PG.Commons/Data/DataObjectMapperBase.cs @@ -0,0 +1,56 @@ +using System; + +namespace PG.Commons.Data; + +/// +/// Container class allowing to map from a data objct to a peer and vice versa +/// +/// +/// +public abstract class DataObjectMapperBase +{ + private readonly DataObjectMappings _mappings; + + /// + /// .ctor + /// + protected DataObjectMapperBase() + { + var mappings = new DataObjectMappings(); + // ReSharper disable once VirtualMemberCallInConstructor + InitMappings(mappings); + _mappings = mappings; + } + + /// + /// Method is overridden by subclasses to initialize the mappings. + /// + /// + protected abstract void InitMappings(DataObjectMappings mappings); + + + /// + /// Applies the mapping from the source to the data object. + /// + /// + /// + /// + /// + protected bool ToDataObject(TPeer source, TDataObject dataObject) + { + return _mappings.ToDataObject(source ?? throw new ArgumentNullException(nameof(source)), dataObject); + } + + /// + /// Applies the mapping from the data object to the target. + /// + /// + /// + /// + /// + protected bool FromDataObject(TDataObject dataObject, TPeer target) + { + return _mappings.FromDataObject(dataObject ?? throw new ArgumentNullException(nameof(dataObject)), + target); + } +} \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Data/DataObjectMapping.cs b/PG.Commons/PG.Commons/Data/DataObjectMapping.cs new file mode 100644 index 00000000..a9ebe763 --- /dev/null +++ b/PG.Commons/PG.Commons/Data/DataObjectMapping.cs @@ -0,0 +1,33 @@ +using System; + +namespace PG.Commons.Data; + +internal class DataObjectMapping( + Func dataObjectValueGetter, + Action dataObjectValueSetter, + Func peerValueGetter, + Action peerValueSetter) + : IDataObjectMapping +{ + private Func DataObjectValueGetter { get; } = dataObjectValueGetter; + private Action DataObjectValueSetter { get; } = dataObjectValueSetter; + private Func PeerValueGetter { get; } = peerValueGetter; + private Action PeerValueSetter { get; } = peerValueSetter; + + public void ToDataObject(TPeer source, TDataObject dataObject) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (dataObject == null) throw new ArgumentNullException(nameof(dataObject)); + + var value = PeerValueGetter.Invoke(source); + DataObjectValueSetter.Invoke(dataObject, value); + } + + public void FromDataObject(TDataObject dataObject, TPeer target) + { + if (target == null) throw new ArgumentNullException(nameof(target)); + if (dataObject == null) throw new ArgumentNullException(nameof(dataObject)); + var value = DataObjectValueGetter.Invoke(dataObject); + PeerValueSetter.Invoke(target, value); + } +} \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Data/DataObjectMappings.cs b/PG.Commons/PG.Commons/Data/DataObjectMappings.cs new file mode 100644 index 00000000..dc3d3a6c --- /dev/null +++ b/PG.Commons/PG.Commons/Data/DataObjectMappings.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace PG.Commons.Data; + +/// +/// Data mappings. +/// +/// +/// +public class DataObjectMappings +{ + private List> Mappings { get; } = new(); + + // ReSharper disable once HeapView.ClosureAllocation + internal bool ToDataObject([DisallowNull] TPeer source, TDataObject dataObject) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + try + { + // ReSharper disable once HeapView.DelegateAllocation + Mappings.ForEach(m => m.ToDataObject(source, dataObject)); + } + catch (Exception) + { + return false; + } + + return true; + } + + // ReSharper disable once HeapView.ClosureAllocation + internal bool FromDataObject([DisallowNull] TDataObject dataObject, TPeer target) + { + if (dataObject == null) throw new ArgumentNullException(nameof(dataObject)); + try + { + // ReSharper disable once HeapView.DelegateAllocation + Mappings.ForEach(m => m.FromDataObject(dataObject, target)); + } + catch (Exception) + { + return false; + } + + return true; + } + + /// + /// Mapping builder. + /// + /// + /// + /// + /// + /// + /// + public DataObjectMappings With(Func dataObjectValueGetter, + Action dataObjectValueSetter, + Func peerValueGetter, Action peerValueSetter) + { + // ReSharper disable once HeapView.ObjectAllocation.Evident + Mappings.Add(new DataObjectMapping(dataObjectValueGetter, dataObjectValueSetter, + peerValueGetter, peerValueSetter)); + return this; + } +} \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Data/IDataObjectMapping.cs b/PG.Commons/PG.Commons/Data/IDataObjectMapping.cs new file mode 100644 index 00000000..69b93d95 --- /dev/null +++ b/PG.Commons/PG.Commons/Data/IDataObjectMapping.cs @@ -0,0 +1,8 @@ +namespace PG.Commons.Data; + +internal interface IDataObjectMapping +{ + internal void ToDataObject(TPeer source, TDataObject dataObject); + + internal void FromDataObject(TDataObject dataObject, TPeer target); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/DefaultLanguageAttribute.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/DefaultLanguageAttribute.cs index 57ea11b8..7f826183 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/DefaultLanguageAttribute.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Attributes/DefaultLanguageAttribute.cs @@ -2,12 +2,15 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using System; +using System.Diagnostics.CodeAnalysis; namespace PG.StarWarsGame.Components.Localisation.Attributes; /// /// Marks the default (fallback) language. /// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +[ExcludeFromCodeCoverage] public class DefaultLanguageAttribute : Attribute { /// diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj index a3175a9f..5cb1dbd3 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj @@ -33,5 +33,6 @@ + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/IReadOnlyTranslationItem.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/IReadOnlyTranslationItem.cs new file mode 100644 index 00000000..08b0bdea --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/IReadOnlyTranslationItem.cs @@ -0,0 +1,10 @@ +namespace PG.StarWarsGame.Components.Localisation.Repository.Content; + +/// +/// Readonly representation of an +/// +public interface IReadOnlyTranslationItem : ITranslationItem +{ + /// + new ITranslationItemContent Content { get; } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs new file mode 100644 index 00000000..80c01de5 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs @@ -0,0 +1,51 @@ +namespace PG.StarWarsGame.Components.Localisation.Repository.Content; + +/// +/// The basic representation of a translation item. +/// +public interface ITranslationItem +{ + /// + /// The source of the translation item. + /// + enum TranslationItemSource + { + /// + /// A translation item available in the base game. + /// + BaseGame, + + /// + /// A translation item added by the expansion + /// + Expansion, + + /// + /// A translation item added by a mod. + /// + Mod + } + + /// + /// ID + /// + ITranslationItemId ItemId { get; } + + /// + /// The acutal translation content. + /// + ITranslationItemContent Content { get; set; } + + /// + /// The source of the item. + /// + TranslationItemSource Source { get; } + + /// + /// If an item is originally present in the or the + /// , but also contained in either the + /// or the with a different + /// value the item counts as overwritten. + /// + bool Overwritten { get; } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs new file mode 100644 index 00000000..05f5de81 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs @@ -0,0 +1,17 @@ +namespace PG.StarWarsGame.Components.Localisation.Repository.Content; + +/// +/// The raw content of a translation item. +/// +public interface ITranslationItemContent +{ + /// + /// The string key, usually mapped to + /// + string Key { get; set; } + + /// + /// The string value, usually mapped to + /// + string Value { get; set; } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs new file mode 100644 index 00000000..916339dc --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs @@ -0,0 +1,8 @@ +namespace PG.StarWarsGame.Components.Localisation.Repository.Content; + +/// +/// The unique item ID, could be the string key, or any other unique ID. +/// +public interface ITranslationItemId +{ +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationDiff.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationDiff.cs new file mode 100644 index 00000000..565524b9 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationDiff.cs @@ -0,0 +1,37 @@ +using System.Collections.Immutable; +using PG.StarWarsGame.Components.Localisation.Languages; +using PG.StarWarsGame.Components.Localisation.Repository.Content; + +namespace PG.StarWarsGame.Components.Localisation.Repository; + +/// +/// A Diff between all language contents of a given . +/// +public interface ITranslationDiff +{ + /// + /// The base language for the diff. + /// + IAlamoLanguageDefinition DiffBase { get; } + + /// + /// All languages present in the diff. + /// + IImmutableSet Languages { get; } + + /// + /// Collects all s that are missing from a language when compared to the + /// + /// + /// + /// + IImmutableSet GetMissingItemIdsForLanguage(IAlamoLanguageDefinition language); + + /// + /// Collects all s that are still present for a language when compared to the + /// + /// + /// + /// + IImmutableSet GetDanglingItemIdsForLanguage(IAlamoLanguageDefinition language); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs new file mode 100644 index 00000000..fcb98fce --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using PG.StarWarsGame.Components.Localisation.Languages; +using PG.StarWarsGame.Components.Localisation.Repository.Content; + +namespace PG.StarWarsGame.Components.Localisation.Repository; + +/// +/// The base interface representing a repository containing translations. +/// +public interface ITranslationRepository +{ + /// + /// The merge strategy used when adding a new language to the repository. + /// + enum MergeStrategy + { + /// + /// If the already exists, the new items will replace the existing items. + /// + Replace, + + /// + /// If the already exists, the will be merged, + /// otherwise added. + /// + MergeByKey, + + /// + /// The s will be appended if possible. + /// + Append + } + + /// + /// Readonly dictionary of all contained s separated by language. + /// + IReadOnlyDictionary> Content { get; } + + /// + /// Readonly collection of all contained s. + /// + IReadOnlyCollection GetTranslationItemsByLanguage(IAlamoLanguageDefinition language); + + /// + /// Adds a new language to the repository. + /// + /// + /// True, if the language was added successfully. + bool AddLanguage(IAlamoLanguageDefinition language); + + /// + /// Adds a new language with initial values to the repository. If the language is already present, the translation + /// items will be added to the existing set according to the + /// + /// + /// + /// The merge strategy used if the language already exists. + /// True, if the language was added successfully. + bool AddOrUpdateLanguage(IAlamoLanguageDefinition language, ICollection translationItems, + MergeStrategy strategy = MergeStrategy.MergeByKey); + + /// + /// Removes a language from the repository. + /// + /// + /// + bool RemoveLanguage(IAlamoLanguageDefinition language); + + /// + /// Fetches a given translation item + /// + /// + /// + /// + ITranslationItem GetTranslationItem(IAlamoLanguageDefinition language, ITranslationItemId id); + + /// + /// Removes a given translation item from the repository. This operation applies to ALL languages in the + /// repository. + /// + /// + /// + bool RemoveTranslationItem(ITranslationItemId id); + + /// + /// Adds a translation item to a given language. + /// + /// + /// + void AddOrUpdateTranslationItem(IAlamoLanguageDefinition language, ITranslationItem item); + + /// + /// Creates a diff between the present languages. + /// + /// + /// + ITranslationDiff CreateTranslationDiff(IAlamoLanguageDefinition diffBase); + + /// + /// Applies a diff to th repository. Missing items in languages will be inserted, items not present in the master + /// language will be removed. + /// + /// + /// + bool ApplyTranslationDiff(ITranslationDiff diff); +} \ No newline at end of file From 366db707b485caa010c5843f53169fc963ab2820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Fri, 4 Oct 2024 15:35:15 +0200 Subject: [PATCH 03/15] Fix Dependencies --- .../PG.StarWarsGame.Components.Localisation.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj index 5cb1dbd3..c3fbbe46 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj @@ -29,7 +29,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 944f1368877b8994a86f18e8aee7c0f9c118b293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Sat, 5 Oct 2024 14:00:41 +0200 Subject: [PATCH 04/15] Fix Dependencies again --- ...rsGame.Components.Localisation.Test.csproj | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj index 946063f1..6c523b81 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj @@ -1,32 +1,32 @@  - - false - net8.0 - $(TargetFrameworks);net48 - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + + false + net8.0 + $(TargetFrameworks);net48 + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + From 7d33135823e373a55f5a212c50f41be3ac25f846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Sat, 5 Oct 2024 14:07:02 +0200 Subject: [PATCH 05/15] Fix Dependencies again again --- .../PG.StarWarsGame.Components.Localisation.Test.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj index 6c523b81..ac52baaa 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj @@ -11,7 +11,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive From b61b6c3e2fc05c4e19d4e49df5c6be4ebdea0e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Sat, 5 Oct 2024 18:00:47 +0200 Subject: [PATCH 06/15] DataObject to DTO --- ...ase.cs => DataTransferObjectMapperBase.cs} | 24 ++++++++++--------- ...pings.cs => DataTransferObjectMappings.cs} | 21 ++++++++-------- ...s => DataTransferTransferObjectMapping.cs} | 16 ++++++------- .../PG.Commons/Data/IDataObjectMapping.cs | 8 ------- .../Data/IDataTransferObjectMapping.cs | 8 +++++++ 5 files changed, 40 insertions(+), 37 deletions(-) rename PG.Commons/PG.Commons/Data/{DataObjectMapperBase.cs => DataTransferObjectMapperBase.cs} (58%) rename PG.Commons/PG.Commons/Data/{DataObjectMappings.cs => DataTransferObjectMappings.cs} (65%) rename PG.Commons/PG.Commons/Data/{DataObjectMapping.cs => DataTransferTransferObjectMapping.cs} (61%) delete mode 100644 PG.Commons/PG.Commons/Data/IDataObjectMapping.cs create mode 100644 PG.Commons/PG.Commons/Data/IDataTransferObjectMapping.cs diff --git a/PG.Commons/PG.Commons/Data/DataObjectMapperBase.cs b/PG.Commons/PG.Commons/Data/DataTransferObjectMapperBase.cs similarity index 58% rename from PG.Commons/PG.Commons/Data/DataObjectMapperBase.cs rename to PG.Commons/PG.Commons/Data/DataTransferObjectMapperBase.cs index 9432ac8a..2635483e 100644 --- a/PG.Commons/PG.Commons/Data/DataObjectMapperBase.cs +++ b/PG.Commons/PG.Commons/Data/DataTransferObjectMapperBase.cs @@ -3,20 +3,22 @@ namespace PG.Commons.Data; /// -/// Container class allowing to map from a data objct to a peer and vice versa +/// Container class allowing to map from a data objct to a peer and vice versa. +///
+/// @lgr: check if fully replaceable with AutoMapper ///
-/// +/// /// -public abstract class DataObjectMapperBase +public abstract class DataTransferObjectMapperBase { - private readonly DataObjectMappings _mappings; + private readonly DataTransferObjectMappings _mappings; /// /// .ctor /// - protected DataObjectMapperBase() + protected DataTransferObjectMapperBase() { - var mappings = new DataObjectMappings(); + var mappings = new DataTransferObjectMappings(); // ReSharper disable once VirtualMemberCallInConstructor InitMappings(mappings); _mappings = mappings; @@ -26,7 +28,7 @@ protected DataObjectMapperBase() /// Method is overridden by subclasses to initialize the mappings. ///
/// - protected abstract void InitMappings(DataObjectMappings mappings); + protected abstract void InitMappings(DataTransferObjectMappings mappings); /// @@ -36,9 +38,9 @@ protected DataObjectMapperBase() /// /// /// - protected bool ToDataObject(TPeer source, TDataObject dataObject) + protected bool ToDto(TPeer source, TDto dataObject) { - return _mappings.ToDataObject(source ?? throw new ArgumentNullException(nameof(source)), dataObject); + return _mappings.ToDto(source ?? throw new ArgumentNullException(nameof(source)), dataObject); } /// @@ -48,9 +50,9 @@ protected bool ToDataObject(TPeer source, TDataObject dataObject) /// /// /// - protected bool FromDataObject(TDataObject dataObject, TPeer target) + protected bool FromDto(TDto dataObject, TPeer target) { - return _mappings.FromDataObject(dataObject ?? throw new ArgumentNullException(nameof(dataObject)), + return _mappings.FromDto(dataObject ?? throw new ArgumentNullException(nameof(dataObject)), target); } } \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Data/DataObjectMappings.cs b/PG.Commons/PG.Commons/Data/DataTransferObjectMappings.cs similarity index 65% rename from PG.Commons/PG.Commons/Data/DataObjectMappings.cs rename to PG.Commons/PG.Commons/Data/DataTransferObjectMappings.cs index dc3d3a6c..e66940af 100644 --- a/PG.Commons/PG.Commons/Data/DataObjectMappings.cs +++ b/PG.Commons/PG.Commons/Data/DataTransferObjectMappings.cs @@ -7,20 +7,20 @@ namespace PG.Commons.Data; /// /// Data mappings. /// -/// +/// /// -public class DataObjectMappings +public class DataTransferObjectMappings { - private List> Mappings { get; } = new(); + private List> Mappings { get; } = new(); // ReSharper disable once HeapView.ClosureAllocation - internal bool ToDataObject([DisallowNull] TPeer source, TDataObject dataObject) + internal bool ToDto([DisallowNull] TPeer source, TDto dataObject) { if (source == null) throw new ArgumentNullException(nameof(source)); try { // ReSharper disable once HeapView.DelegateAllocation - Mappings.ForEach(m => m.ToDataObject(source, dataObject)); + Mappings.ForEach(m => m.ToDto(source, dataObject)); } catch (Exception) { @@ -31,13 +31,13 @@ internal bool ToDataObject([DisallowNull] TPeer source, TDataObject dataObject) } // ReSharper disable once HeapView.ClosureAllocation - internal bool FromDataObject([DisallowNull] TDataObject dataObject, TPeer target) + internal bool FromDto([DisallowNull] TDto dataObject, TPeer target) { if (dataObject == null) throw new ArgumentNullException(nameof(dataObject)); try { // ReSharper disable once HeapView.DelegateAllocation - Mappings.ForEach(m => m.FromDataObject(dataObject, target)); + Mappings.ForEach(m => m.FromDto(dataObject, target)); } catch (Exception) { @@ -56,12 +56,13 @@ internal bool FromDataObject([DisallowNull] TDataObject dataObject, TPeer target /// /// /// - public DataObjectMappings With(Func dataObjectValueGetter, - Action dataObjectValueSetter, + public DataTransferObjectMappings With(Func dataObjectValueGetter, + Action dataObjectValueSetter, Func peerValueGetter, Action peerValueSetter) { // ReSharper disable once HeapView.ObjectAllocation.Evident - Mappings.Add(new DataObjectMapping(dataObjectValueGetter, dataObjectValueSetter, + Mappings.Add(new DataTransferTransferObjectMapping(dataObjectValueGetter, + dataObjectValueSetter, peerValueGetter, peerValueSetter)); return this; } diff --git a/PG.Commons/PG.Commons/Data/DataObjectMapping.cs b/PG.Commons/PG.Commons/Data/DataTransferTransferObjectMapping.cs similarity index 61% rename from PG.Commons/PG.Commons/Data/DataObjectMapping.cs rename to PG.Commons/PG.Commons/Data/DataTransferTransferObjectMapping.cs index a9ebe763..0317d1b9 100644 --- a/PG.Commons/PG.Commons/Data/DataObjectMapping.cs +++ b/PG.Commons/PG.Commons/Data/DataTransferTransferObjectMapping.cs @@ -2,19 +2,19 @@ namespace PG.Commons.Data; -internal class DataObjectMapping( - Func dataObjectValueGetter, - Action dataObjectValueSetter, +internal class DataTransferTransferObjectMapping( + Func dataObjectValueGetter, + Action dataObjectValueSetter, Func peerValueGetter, Action peerValueSetter) - : IDataObjectMapping + : IDataTransferObjectMapping { - private Func DataObjectValueGetter { get; } = dataObjectValueGetter; - private Action DataObjectValueSetter { get; } = dataObjectValueSetter; + private Func DataObjectValueGetter { get; } = dataObjectValueGetter; + private Action DataObjectValueSetter { get; } = dataObjectValueSetter; private Func PeerValueGetter { get; } = peerValueGetter; private Action PeerValueSetter { get; } = peerValueSetter; - public void ToDataObject(TPeer source, TDataObject dataObject) + public void ToDto(TPeer source, TDto dataObject) { if (source == null) throw new ArgumentNullException(nameof(source)); if (dataObject == null) throw new ArgumentNullException(nameof(dataObject)); @@ -23,7 +23,7 @@ public void ToDataObject(TPeer source, TDataObject dataObject) DataObjectValueSetter.Invoke(dataObject, value); } - public void FromDataObject(TDataObject dataObject, TPeer target) + public void FromDto(TDto dataObject, TPeer target) { if (target == null) throw new ArgumentNullException(nameof(target)); if (dataObject == null) throw new ArgumentNullException(nameof(dataObject)); diff --git a/PG.Commons/PG.Commons/Data/IDataObjectMapping.cs b/PG.Commons/PG.Commons/Data/IDataObjectMapping.cs deleted file mode 100644 index 69b93d95..00000000 --- a/PG.Commons/PG.Commons/Data/IDataObjectMapping.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PG.Commons.Data; - -internal interface IDataObjectMapping -{ - internal void ToDataObject(TPeer source, TDataObject dataObject); - - internal void FromDataObject(TDataObject dataObject, TPeer target); -} \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Data/IDataTransferObjectMapping.cs b/PG.Commons/PG.Commons/Data/IDataTransferObjectMapping.cs new file mode 100644 index 00000000..546cd54a --- /dev/null +++ b/PG.Commons/PG.Commons/Data/IDataTransferObjectMapping.cs @@ -0,0 +1,8 @@ +namespace PG.Commons.Data; + +internal interface IDataTransferObjectMapping +{ + internal void ToDto(TPeer source, TDataObject dataObject); + + internal void FromDto(TDataObject dataObject, TPeer target); +} \ No newline at end of file From 6b6b489b0d9fa8c68f89204bb89d6134ac370be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Sat, 5 Oct 2024 18:01:06 +0200 Subject: [PATCH 07/15] Continue Repository Base Setup --- PG.Commons/PG.Commons/Data/IId.cs | 20 +++++ PG.Commons/PG.Commons/Data/IdBase.cs | 85 +++++++++++++++++++ PG.Commons/PG.Commons/Data/RootIdBase.cs | 40 +++++++++ .../Exceptions/TypeMismatchException.cs | 25 ++++++ .../Content/ITranslationItemContent.cs | 2 +- .../Repository/Content/ITranslationItemId.cs | 4 +- .../Content/OrderedTranslationItem.cs | 43 ++++++++++ .../Content/OrderedTranslationItemId.cs | 24 ++++++ .../Repository/Content/TranslationItemBase.cs | 33 +++++++ .../Content/TranslationItemContent.cs | 11 +++ 10 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 PG.Commons/PG.Commons/Data/IId.cs create mode 100644 PG.Commons/PG.Commons/Data/IdBase.cs create mode 100644 PG.Commons/PG.Commons/Data/RootIdBase.cs create mode 100644 PG.Commons/PG.Commons/Exceptions/TypeMismatchException.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemContent.cs diff --git a/PG.Commons/PG.Commons/Data/IId.cs b/PG.Commons/PG.Commons/Data/IId.cs new file mode 100644 index 00000000..6edb1812 --- /dev/null +++ b/PG.Commons/PG.Commons/Data/IId.cs @@ -0,0 +1,20 @@ +using System; + +namespace PG.Commons.Data; + +/// +/// A generic ID definition +/// +public interface IId : IEquatable +{ + /// + /// The arity of the ID. + /// + int Arity { get; } + + /// + /// The string representation of the ID + /// + /// + string Unwrap(); +} \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Data/IdBase.cs b/PG.Commons/PG.Commons/Data/IdBase.cs new file mode 100644 index 00000000..b06528da --- /dev/null +++ b/PG.Commons/PG.Commons/Data/IdBase.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using System.Text; +using PG.Commons.Exceptions; + +namespace PG.Commons.Data; + +/// +public abstract class IdBase : IId +{ + /// + /// The ID components. + /// + protected readonly object?[] Components; + + /// + /// .ctor + /// + protected IdBase(params object[] components) + { + if (components.Length != Arity) throw new ArgumentException("Invalid number of components"); + Components = new object[Arity]; + for (var i = 0; i < Components.Length; i++) Components[i] = components[i]; + } + + /// + public bool Equals(IId? other) + { + return other != null && Arity == other.Arity && GetHashCode().Equals(other.GetHashCode()); + } + + /// + public int Arity => GetConfiguredArity(); + + /// + public string Unwrap() + { + var b = new StringBuilder(); + b.Append(GetType().Name).Append('['); + foreach (var idComponent in Components) b.Append(idComponent).Append(';'); + b.Remove(b.Length - 1, 1); // remove the last ";". + b.Append(']'); + return b.ToString(); + } + + /// + /// Convenience method to access components in a type-safe manner. + /// + /// + /// + /// + /// + /// If the provided index is out of bounds. + /// If no is provided. + /// + protected T? GetIdComponent(int idx, Type type) where T : class + { + if (idx > 0 || idx >= Arity) throw new ArgumentException("Invalid component index"); + if (type == null) throw new ArgumentNullException(nameof(type)); + var c = Components[idx]; + if (c?.GetType() != type) throw new TypeMismatchException($"Component {idx - 1} is not of type {type}"); + return c as T; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(GetConfiguredArity(), HashCode.Combine(Components)); + } + + /// + /// Returns true if this ID is equivalent to null. + /// + /// + protected virtual bool IsNullId() + { + return Components.Any(); + } + + /// + /// The arity of this ID type. + /// + /// + protected abstract int GetConfiguredArity(); +} \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Data/RootIdBase.cs b/PG.Commons/PG.Commons/Data/RootIdBase.cs new file mode 100644 index 00000000..4624e3b0 --- /dev/null +++ b/PG.Commons/PG.Commons/Data/RootIdBase.cs @@ -0,0 +1,40 @@ +using System; + +namespace PG.Commons.Data; + +/// +public abstract class RootIdBase : IdBase, IComparable> where T : class, IComparable +{ + /// + protected RootIdBase(T rawId) : base(rawId) + { + } + + /// + public int CompareTo(RootIdBase? other) + { + return other == null ? 1 : Raw().CompareTo(other.Raw()); + } + + /// + protected override int GetConfiguredArity() + { + return 1; + } + + /// + protected override bool IsNullId() + { + return Components[0] == null; + } + + /// + /// Raw value. + /// + /// + /// + public T Raw() + { + return Components[0] as T ?? throw new InvalidOperationException(); + } +} \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Exceptions/TypeMismatchException.cs b/PG.Commons/PG.Commons/Exceptions/TypeMismatchException.cs new file mode 100644 index 00000000..4fc526ed --- /dev/null +++ b/PG.Commons/PG.Commons/Exceptions/TypeMismatchException.cs @@ -0,0 +1,25 @@ +using System; + +namespace PG.Commons.Exceptions; + +/// +/// Thrown if the provided type does not match the requested type. Usually thrown when a generic request for a key +/// component is not logically sound. +/// +public class TypeMismatchException : Exception +{ + /// + public TypeMismatchException() + { + } + + /// + public TypeMismatchException(string message) : base(message) + { + } + + /// + public TypeMismatchException(string message, Exception inner) : base(message, inner) + { + } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs index 05f5de81..1aef72cd 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs @@ -13,5 +13,5 @@ public interface ITranslationItemContent /// /// The string value, usually mapped to /// - string Value { get; set; } + string? Value { get; set; } } \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs index 916339dc..bb426bc1 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs @@ -1,8 +1,10 @@ +using PG.Commons.Data; + namespace PG.StarWarsGame.Components.Localisation.Repository.Content; /// /// The unique item ID, could be the string key, or any other unique ID. /// -public interface ITranslationItemId +public interface ITranslationItemId : IId { } \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs new file mode 100644 index 00000000..418eb0c1 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs @@ -0,0 +1,43 @@ +using System; + +namespace PG.StarWarsGame.Components.Localisation.Repository.Content; + +/// +public class OrderedTranslationItem : TranslationItemBase +{ + /// + protected OrderedTranslationItem(OrderedTranslationItemId itemId, TranslationItemContent content, + ITranslationItem.TranslationItemSource source, bool overwritten) : base(itemId, content, source, overwritten) + { + } + + /// + /// Convenience method to create a new + /// + /// + /// + /// + /// + /// + public static OrderedTranslationItem Of(OrderedTranslationItemId itemId, TranslationItemContent content, + ITranslationItem.TranslationItemSource source, bool overwritten) + { + return new OrderedTranslationItem(itemId, content, source, overwritten); + } + + /// + /// Convenience method to create a new + /// + /// + /// + /// + /// + /// + public static OrderedTranslationItem Of(TranslationItemContent content, + ITranslationItem.TranslationItemSource source = ITranslationItem.TranslationItemSource.Mod, + bool overwritten = false) + { + return Of(OrderedTranslationItemId.Of(content.Key) ?? throw new InvalidOperationException(), content, source, + overwritten); + } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs new file mode 100644 index 00000000..be271d1a --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs @@ -0,0 +1,24 @@ +using PG.Commons.Data; + +namespace PG.StarWarsGame.Components.Localisation.Repository.Content; + +/// +/// Item ID for ordered translation items. Equivalent to the raw string key from a sorted DAT file. +/// +public class OrderedTranslationItemId : RootIdBase, ITranslationItemId +{ + /// + protected OrderedTranslationItemId(string rawId) : base(rawId) + { + } + + /// + /// Convenience method to create a new + /// + /// + /// + public static OrderedTranslationItemId? Of(string rawId) + { + return string.IsNullOrWhiteSpace(rawId) ? null : new OrderedTranslationItemId(rawId); + } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs new file mode 100644 index 00000000..ebd76bce --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs @@ -0,0 +1,33 @@ +namespace PG.StarWarsGame.Components.Localisation.Repository.Content; + +/// +public abstract class TranslationItemBase : ITranslationItem +{ + /// + /// .ctor + /// + /// + /// + /// + /// + protected TranslationItemBase(ITranslationItemId itemId, TranslationItemContent content, + ITranslationItem.TranslationItemSource source, bool overwritten) + { + ItemId = itemId; + Content = content; + Source = source; + Overwritten = overwritten; + } + + /// + public ITranslationItemContent Content { get; set; } + + /// + public ITranslationItemId ItemId { get; } + + /// + public ITranslationItem.TranslationItemSource Source { get; } + + /// + public bool Overwritten { get; } +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemContent.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemContent.cs new file mode 100644 index 00000000..c7d40a5c --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemContent.cs @@ -0,0 +1,11 @@ +namespace PG.StarWarsGame.Components.Localisation.Repository.Content; + +/// +public record TranslationItemContent : ITranslationItemContent +{ + /// + public required string Key { get; set; } + + /// + public string? Value { get; set; } +} \ No newline at end of file From ca93e56dcb779de4496058be1a3a5b2b43333b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Sun, 6 Oct 2024 15:45:50 +0200 Subject: [PATCH 08/15] Update Copyright --- .../Alamo_Engine_Tools_Copyright_Profile.xml | 6 + .../.idea/copyright/profiles_settings.xml | 7 + .../.idea/developer-tools.xml | 13 ++ .../.idea/indexLayout.xml | 8 ++ Directory.Build.props | 23 ++-- PG.Commons/PG.Commons/Attributes/Platform.cs | 40 ++++++ .../Attributes/PlatformAttribute.cs | 24 ++++ .../PG.Commons/PG.Commons.csproj.DotSettings | 12 +- ...rsGame.Components.Localisation.Test.csproj | 7 +- .../IO/IExportHandler.cs | 12 ++ .../IO/IImportHander.cs | 12 ++ .../IO/IInputStrategy.cs | 12 ++ .../IO/IOutputStrategy.cs | 46 +++++++ ...tarWarsGame.Components.Localisation.csproj | 9 +- .../Repository/ITranslationDiff.cs | 3 + .../Repository/ITranslationRepository.cs | 3 + .../Services/IAlamoLanguageSupportService.cs | 41 ++++++ .../Services/ITranslationRepositoryService.cs | 31 +++++ ...WarsGame.Files.DAT.Test.csproj.DotSettings | 12 +- ....StarWarsGame.Files.DAT.csproj.DotSettings | 21 ++- ...WarsGame.Files.MEG.Test.csproj.DotSettings | 18 ++- ....StarWarsGame.Files.MEG.csproj.DotSettings | 50 ++++--- .../PG.StarWarsGame.Files.Xml.Test.csproj | 43 +++--- .../PG.StarWarsGame.Files.Xml.csproj | 57 ++++---- .../Resources/LocalizableTexts.Designer.cs | 1 - .../Resources/LocalizableTexts.de.resx | 13 +- .../Resources/LocalizableTexts.resx | 18 ++- PetroglyphTools.sln.DotSettings | 125 ++++++++++++++++++ 28 files changed, 575 insertions(+), 92 deletions(-) create mode 100644 .idea/.idea.PetroglyphTools/.idea/copyright/Alamo_Engine_Tools_Copyright_Profile.xml create mode 100644 .idea/.idea.PetroglyphTools/.idea/copyright/profiles_settings.xml create mode 100644 .idea/.idea.PetroglyphTools/.idea/developer-tools.xml create mode 100644 .idea/.idea.PetroglyphTools/.idea/indexLayout.xml create mode 100644 PG.Commons/PG.Commons/Attributes/Platform.cs create mode 100644 PG.Commons/PG.Commons/Attributes/PlatformAttribute.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IExportHandler.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHander.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/ITranslationRepositoryService.cs create mode 100644 PetroglyphTools.sln.DotSettings diff --git a/.idea/.idea.PetroglyphTools/.idea/copyright/Alamo_Engine_Tools_Copyright_Profile.xml b/.idea/.idea.PetroglyphTools/.idea/copyright/Alamo_Engine_Tools_Copyright_Profile.xml new file mode 100644 index 00000000..a57a5ce8 --- /dev/null +++ b/.idea/.idea.PetroglyphTools/.idea/copyright/Alamo_Engine_Tools_Copyright_Profile.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.PetroglyphTools/.idea/copyright/profiles_settings.xml b/.idea/.idea.PetroglyphTools/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..690b7667 --- /dev/null +++ b/.idea/.idea.PetroglyphTools/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.PetroglyphTools/.idea/developer-tools.xml b/.idea/.idea.PetroglyphTools/.idea/developer-tools.xml new file mode 100644 index 00000000..37021a5a --- /dev/null +++ b/.idea/.idea.PetroglyphTools/.idea/developer-tools.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.PetroglyphTools/.idea/indexLayout.xml b/.idea/.idea.PetroglyphTools/.idea/indexLayout.xml new file mode 100644 index 00000000..7b08163c --- /dev/null +++ b/.idea/.idea.PetroglyphTools/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index cb972937..0ea7e19c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,3 +1,8 @@ + + $(MSBuildThisFileDirectory) @@ -10,15 +15,15 @@ Alamo Engine Tools and Contributors Copyright © 2023 Alamo Engine Tools and contributors. All rights reserved. https://github.com/AlamoEngine-Tools/PetroglyphTools - $(MSBuildThisFileDirectory)LICENSE - MIT + $(MSBuildThisFileDirectory)LICENSE + MIT https://github.com/AlamoEngine-Tools/PetroglyphTools git Alamo Engine Tools README.md - aet.png - - + aet.png + + latest disable @@ -38,8 +43,8 @@ - - - - + + + + \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Attributes/Platform.cs b/PG.Commons/PG.Commons/Attributes/Platform.cs new file mode 100644 index 00000000..2ce65ac6 --- /dev/null +++ b/PG.Commons/PG.Commons/Attributes/Platform.cs @@ -0,0 +1,40 @@ +using System; + +namespace PG.Commons.Attributes; + +/// +/// The platform a feature/... is supported on. +/// +[Flags] +public enum Platform +{ + /// + /// Disc version of the game / expansion + /// + Disc = 0b00001, + + /// + /// Steam version of the game / expansion + /// + Steam = 0b00010, + + /// + /// Origin version of the game / expansion - yep that abomination exists. + /// + Origin = 0b00100, + + /// + /// GoG.com version of the game. + /// + GoG = 0b01000, + + /// + /// MAC version of the game / expansion - yep that exists as well, only the base game though. + /// + Mac = 0b10000, + + /// + /// Versions that do no longer receive updates. + /// + Outdated = Disc | Origin | Steam | GoG | Mac +} \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Attributes/PlatformAttribute.cs b/PG.Commons/PG.Commons/Attributes/PlatformAttribute.cs new file mode 100644 index 00000000..dd027baf --- /dev/null +++ b/PG.Commons/PG.Commons/Attributes/PlatformAttribute.cs @@ -0,0 +1,24 @@ +using System; + +namespace PG.Commons.Attributes; + +/// +/// Simple annotation to attach platform info. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct)] +public class PlatformAttribute : Attribute +{ + /// + /// .ctor + /// + /// + public PlatformAttribute(Platform platform) + { + Platform = platform; + } + + /// + /// The flags. + /// + public Platform Platform { get; } +} \ No newline at end of file diff --git a/PG.Commons/PG.Commons/PG.Commons.csproj.DotSettings b/PG.Commons/PG.Commons/PG.Commons.csproj.DotSettings index 13d95ad0..589e1da7 100644 --- a/PG.Commons/PG.Commons/PG.Commons.csproj.DotSettings +++ b/PG.Commons/PG.Commons/PG.Commons.csproj.DotSettings @@ -1,2 +1,10 @@ - - True \ No newline at end of file + + + + True \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj index ac52baaa..7041305b 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj @@ -1,4 +1,9 @@ - + + + false net8.0 diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IExportHandler.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IExportHandler.cs new file mode 100644 index 00000000..30df113d --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IExportHandler.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.Components.Localisation.IO; + +/// +/// The export handler associated with a given +/// +/// +public interface IExportHandler where T : IOutputStrategy +{ +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHander.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHander.cs new file mode 100644 index 00000000..cdc9b0cf --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHander.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.Components.Localisation.IO; + +/// +/// The import handler associated with a given +/// +/// +public interface IImportHander where T : IInputStrategy +{ +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs new file mode 100644 index 00000000..9251f37d --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.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.Components.Localisation.IO; + +/// +/// The strategy used to load an +/// +/// +public interface IInputStrategy +{ +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.cs new file mode 100644 index 00000000..01f6d395 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.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. + +namespace PG.StarWarsGame.Components.Localisation.IO; + +/// +/// The strategy used to store an +/// +/// +public interface IOutputStrategy +{ + // TODO: implement DatOutputStrategy, XmlOutputStrategy, CsvOutputStrategy -> this should cover most current community tools. + + /// + /// Determines if the is being + /// exported to a single or multiple files. + /// + enum FileExportGrouping + { + /// + /// Single file export. + /// + Single, + + /// + /// Multiple fles export + /// + Multi + } + + /// + /// The desired export grouping. + /// + FileExportGrouping ExportGrouping { get; } + + /// + /// The export file extension + /// + string Extension { get; } + + /// + /// Creates a fresh instance of a given associated + /// + /// + IExportHandler CreateExportHandler(); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj index c3fbbe46..0145098f 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj @@ -1,4 +1,9 @@ - + + + netstandard2.0;netstandard2.1 PG.StarWarsGame.Components.Localisation @@ -29,7 +34,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationDiff.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationDiff.cs index 565524b9..7dcf01c7 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationDiff.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationDiff.cs @@ -1,3 +1,6 @@ +// 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.Immutable; using PG.StarWarsGame.Components.Localisation.Languages; using PG.StarWarsGame.Components.Localisation.Repository.Content; diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs index fcb98fce..b8c367cb 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs @@ -1,3 +1,6 @@ +// 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.Components.Localisation.Languages; using PG.StarWarsGame.Components.Localisation.Repository.Content; diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs new file mode 100644 index 00000000..0f5ade32 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.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 PG.StarWarsGame.Components.Localisation.Languages; + +namespace PG.StarWarsGame.Components.Localisation.Services; + +/// +/// Helper service for handling s. +/// +public interface IAlamoLanguageSupportService +{ + /// + /// Checks if a given is a builtin languge. + /// + /// + /// + bool IsBuiltInLanguageDefinition(IAlamoLanguageDefinition definition); + + /// + /// Gets the fallback language if none can be resolved. By default, this is the return value of + /// but can be + /// overridden with + /// + /// + IAlamoLanguageDefinition GetFallBackLanguageDefinition(); + + /// + /// Allows for overriding the fallback language. + /// + /// + /// + IAlamoLanguageSupportService WithOverrideFallbackLanguage(IAlamoLanguageDefinition definition); + + /// + /// Returns the default language marked with the + /// + /// + /// + IAlamoLanguageDefinition GetDefaultLanguageDefinition(); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/ITranslationRepositoryService.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/ITranslationRepositoryService.cs new file mode 100644 index 00000000..f780f6ad --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/ITranslationRepositoryService.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.Components.Localisation.IO; +using PG.StarWarsGame.Components.Localisation.Repository; + +namespace PG.StarWarsGame.Components.Localisation.Services; + +/// +/// Base service for handling a +/// +public interface ITranslationRepositoryService +{ + /// + /// Loads a given file or set of files as a singular + /// + /// + /// + /// + ITranslationRepository LoadAs(IInputStrategy inputStrategy, params string[] filePaths); + + /// + /// Stores a as a set of files defined in the in a + /// given base directory. + /// + /// + /// + /// + /// + void StoreAs(string baseFilePath, IOutputStrategy outputStrategy, ITranslationRepository repository); +} \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj.DotSettings b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj.DotSettings index b8c8f99c..2286a6e8 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj.DotSettings +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.Test/PG.StarWarsGame.Files.DAT.Test.csproj.DotSettings @@ -1,2 +1,10 @@ - - True \ No newline at end of file + + + + True \ No newline at end of file diff --git a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj.DotSettings b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj.DotSettings index 1daeeb93..57c0bfa6 100644 --- a/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj.DotSettings +++ b/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT/PG.StarWarsGame.Files.DAT.csproj.DotSettings @@ -1,5 +1,16 @@ - - True - True - True - True \ No newline at end of file + + + + True + True + True + True \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj.DotSettings b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj.DotSettings index 6f52e575..c419e82e 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj.DotSettings +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.Test/PG.StarWarsGame.Files.MEG.Test.csproj.DotSettings @@ -1,4 +1,14 @@ - - True - True - True \ No newline at end of file + + + + True + True + True \ No newline at end of file diff --git a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj.DotSettings b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj.DotSettings index c756b64e..ee7687ed 100644 --- a/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj.DotSettings +++ b/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG/PG.StarWarsGame.Files.MEG.csproj.DotSettings @@ -1,15 +1,35 @@ - - True - True - True - True - True - True - True - True - True - True - True - True - True - True \ No newline at end of file + + + + True + True + True + True + True + True + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj index 7ea1d29a..f11932f7 100644 --- a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj +++ b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.Test/PG.StarWarsGame.Files.Xml.Test.csproj @@ -1,20 +1,25 @@ - - - net8.0 - $(TargetFrameworks);net48 - false - false - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + net8.0 + $(TargetFrameworks);net48 + false + false + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj index 9ff29403..560f2af7 100644 --- a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj +++ b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml.csproj @@ -1,27 +1,32 @@ - - - false - netstandard2.0 - PG.StarWarsGame.Files.Xml - AlamoEngineTools.PG.StarWarsGame.Files.Xml - PG.StarWarsGame.Files.Xml - - - true - true - true - - - true - snupkg - - - - ResXFileCodeGenerator - LocalizableTexts.Designer.cs - - - - - + + + + + false + netstandard2.0 + PG.StarWarsGame.Files.Xml + AlamoEngineTools.PG.StarWarsGame.Files.Xml + PG.StarWarsGame.Files.Xml + + + true + true + true + + + true + snupkg + + + + ResXFileCodeGenerator + LocalizableTexts.Designer.cs + + + + + diff --git a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.Designer.cs b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.Designer.cs index 8f995c05..6bf107c6 100644 --- a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.Designer.cs +++ b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.de.resx b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.de.resx index 91ada13e..2a4977e8 100644 --- a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.de.resx +++ b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.de.resx @@ -1,3 +1,8 @@ + + text/microsoft-resx @@ -6,10 +11,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Keine Beschreibung verfügbar. diff --git a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.resx b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.resx index 2664d848..9e790811 100644 --- a/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.resx +++ b/PG.StarWarsGame.Files.Xml/PG.StarWarsGame.Files.Xml/Resources/LocalizableTexts.resx @@ -1,9 +1,15 @@ + + - + - + @@ -13,10 +19,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + No description available. diff --git a/PetroglyphTools.sln.DotSettings b/PetroglyphTools.sln.DotSettings new file mode 100644 index 00000000..afc58993 --- /dev/null +++ b/PetroglyphTools.sln.DotSettings @@ -0,0 +1,125 @@ + + <?xml version="1.0" encoding="utf-16"?><Profile name="Update Copyright"><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSOptimizeUsings></CSOptimizeUsings><CSUpdateFileHeader>True</CSUpdateFileHeader><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + &lt;option name="myName" value="Update Copyright" /&gt; + &lt;inspection_tool class="ES6ShorthandObjectProperty" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSRemoveUnnecessaryParentheses" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="JSUnnecessarySemicolon" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryContinueJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryLabelJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryLabelOnBreakStatementJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryLabelOnContinueStatementJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="UnnecessaryReturnJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; + &lt;inspection_tool class="WrongPropertyKeyValueDelimiter" enabled="false" level="WEAK WARNING" enabled_by_default="false" /&gt; +&lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; + &lt;Language id="CSS"&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="EditorConfig"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTML"&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; + &lt;/Language&gt; + &lt;Language id="HTTP Request"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Handlebars"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Ini"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JSON"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Jade"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JavaScript"&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; + &lt;/Language&gt; + &lt;Language id="Markdown"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Properties"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="RELAX-NG"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="SQL"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="VueExpr"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="XML"&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; + &lt;/Language&gt; + &lt;Language id="yaml"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;/Language&gt; +&lt;/profile&gt;</RIDER_SETTINGS></Profile> + // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +$HEADER$namespace $NAMESPACE$ +{ + public class $CLASS$ {$END$} +} + // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +$HEADER$namespace $NAMESPACE$ +{ + public partial class $CLASS$ : System.ComponentModel.Component + { + public $CLASS$() + { + InitializeComponent(); + } + + public $CLASS$(System.ComponentModel.IContainer container) + { + container.Add(this); + + InitializeComponent(); + } + } +} + // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +$HEADER$namespace $NAMESPACE$ +{ + public record $RECORD$($END$); +} + // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +$HEADER$namespace $NAMESPACE$ +{ + public interface $INTERFACE$ {$END$} +} + // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +$HEADER$namespace $NAMESPACE$ +{ + public enum $ENUM$ {$END$} +} + // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +$HEADER$namespace $NAMESPACE$ +{ + public struct $STRUCT$ {$END$} +} \ No newline at end of file From cec8cb160a2607d4b714312cf79c8754e8265cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Sun, 6 Oct 2024 16:25:40 +0200 Subject: [PATCH 09/15] Implement AlamoLanguageSupportService --- ...validDefaultLanguageDefinitionException.cs | 25 ++++++++ .../LocalisationServiceContribution.cs | 6 +- .../Services/AlamoLanguageSupportService.cs | 59 +++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Exceptions/InvalidDefaultLanguageDefinitionException.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Exceptions/InvalidDefaultLanguageDefinitionException.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Exceptions/InvalidDefaultLanguageDefinitionException.cs new file mode 100644 index 00000000..91365a88 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Exceptions/InvalidDefaultLanguageDefinitionException.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; + +namespace PG.StarWarsGame.Components.Localisation.Exceptions; + +/// +public class InvalidDefaultLanguageDefinitionException : Exception +{ + /// + public InvalidDefaultLanguageDefinitionException() + { + } + + /// + public InvalidDefaultLanguageDefinitionException(string message) : base(message) + { + } + + /// + public InvalidDefaultLanguageDefinitionException(string message, Exception inner) : base(message, inner) + { + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs index c2c1435c..582b2e60 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using PG.Commons.Attributes; using PG.Commons.Extensibility; +using PG.StarWarsGame.Components.Localisation.Services; namespace PG.StarWarsGame.Components.Localisation; @@ -11,6 +12,7 @@ public class LocalisationServiceContribution : IServiceContribution /// public void ContributeServices(IServiceCollection serviceCollection) { - // NOP + serviceCollection + .AddSingleton(sp => new AlamoLanguageSupportService(sp)); } -} \ No newline at end of file +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs new file mode 100644 index 00000000..771c4857 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.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.Reflection; +using PG.Commons.Services; +using PG.StarWarsGame.Components.Localisation.Attributes; +using PG.StarWarsGame.Components.Localisation.Exceptions; +using PG.StarWarsGame.Components.Localisation.Languages; + +namespace PG.StarWarsGame.Components.Localisation.Services; + +/// +public class AlamoLanguageSupportService : ServiceBase, IAlamoLanguageSupportService +{ + private IAlamoLanguageDefinition _fallbackAlamoLanguageDefinition; + + /// + public AlamoLanguageSupportService(IServiceProvider services) : base(services) + { + _fallbackAlamoLanguageDefinition = GetDefaultLanguageDefinition(); + } + + /// + public bool IsBuiltInLanguageDefinition(IAlamoLanguageDefinition definition) + { + return definition.GetType().GetCustomAttribute(typeof(OfficiallySupportedLanguageAttribute)) != null; + } + + /// + public IAlamoLanguageDefinition GetFallBackLanguageDefinition() + { + return _fallbackAlamoLanguageDefinition; + } + + /// + public IAlamoLanguageSupportService WithOverrideFallbackLanguage(IAlamoLanguageDefinition definition) + { + _fallbackAlamoLanguageDefinition = definition; + return this; + } + + /// + public IAlamoLanguageDefinition GetDefaultLanguageDefinition() + { + var languageDefinitions = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assemblyTypes => assemblyTypes.GetTypes()) + .Where(assemblyType => typeof(IAlamoLanguageDefinition).IsAssignableFrom(assemblyType) && + assemblyType is { IsClass: true, IsAbstract: false }) + .Where(t => t.GetCustomAttribute(typeof(DefaultLanguageAttribute)) != null) + .ToList(); + if (languageDefinitions.Count != 1) + throw new InvalidDefaultLanguageDefinitionException(languageDefinitions.Count > 1 + ? "Multiple default languages have been defined." + : "No default language has been defined."); + return (IAlamoLanguageDefinition)Activator.CreateInstance(languageDefinitions[0]); + } +} From 3de9c1f456d1d1a7da147c51e46e5a9669fd0ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Sun, 6 Oct 2024 17:11:42 +0200 Subject: [PATCH 10/15] LanguageSupportService --- .../AlamoLanguageSupportServiceTest.cs | 89 +++++++++++++++++++ .../Services/AlamoLanguageSupportService.cs | 2 +- .../Services/IAlamoLanguageSupportService.cs | 4 +- 3 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Services/AlamoLanguageSupportServiceTest.cs diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Services/AlamoLanguageSupportServiceTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Services/AlamoLanguageSupportServiceTest.cs new file mode 100644 index 00000000..9a540139 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Services/AlamoLanguageSupportServiceTest.cs @@ -0,0 +1,89 @@ +// 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.Abstractions; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Extensibility; +using PG.StarWarsGame.Components.Localisation.Attributes; +using PG.StarWarsGame.Components.Localisation.Languages; +using PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; +using PG.StarWarsGame.Components.Localisation.Services; +using Testably.Abstractions.Testing; +using Xunit; + +namespace PG.StarWarsGame.Components.Localisation.Test.Services; + +public class AlamoLanguageSupportServiceTest +{ + private readonly MockFileSystem _fileSystem = new(); + private readonly IAlamoLanguageSupportService _service; + + private readonly HashSet builtin_types = new() + { + typeof(ChineseAlamoLanguageDefinition), + typeof(EnglishAlamoLanguageDefinition), + typeof(FrenchAlamoLanguageDefinition), + typeof(GermanAlamoLanguageDefinition), + typeof(ItalianAlamoLanguageDefinition), + typeof(JapaneseAlamoLanguageDefinition), + typeof(KoreanAlamoLanguageDefinition), + typeof(PolishAlamoLanguageDefinition), + typeof(RussianAlamoLanguageDefinition), + typeof(SpanishAlamoLanguageDefinition), + typeof(ThaiAlamoLanguageDefinition) + }; + + public AlamoLanguageSupportServiceTest() + { + var sc = new ServiceCollection(); + sc.AddSingleton(_fileSystem); + sc.CollectPgServiceContributions(); + _service = sc.BuildServiceProvider().GetRequiredService(); + } + + [Fact] + public void Test_DefaultLanguageDefinitionDefinedExactlyOnce() + { + var languageDefinitions = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assemblyTypes => assemblyTypes.GetTypes()) + .Where(assemblyType => typeof(IAlamoLanguageDefinition).IsAssignableFrom(assemblyType) && + assemblyType is { IsClass: true, IsAbstract: false }) + .Where(t => t.GetCustomAttribute(typeof(DefaultLanguageAttribute)) != null) + .ToList(); + Assert.Single(languageDefinitions); + Assert.NotNull(languageDefinitions[0]); + } + + [Fact] + public void Test_GetDefaultLanguageDefinition_ReturnsEnglish() + { + var def = _service.GetDefaultLanguageDefinition(); + Assert.NotNull(def); + Assert.Equal(def, new EnglishAlamoLanguageDefinition()); + } + + [Theory] + [InlineData(null, typeof(EnglishAlamoLanguageDefinition))] + [InlineData(typeof(EnglishAlamoLanguageDefinition), typeof(EnglishAlamoLanguageDefinition))] + [InlineData(typeof(ChineseAlamoLanguageDefinition), typeof(ChineseAlamoLanguageDefinition))] + public void Test_GetFallbackLanguageDefinition_ReturnsDesired(Type? definition, Type expected) + { + var exp = (IAlamoLanguageDefinition)Activator.CreateInstance(expected)!; + if (definition != null) + _service.WithOverrideFallbackLanguage((IAlamoLanguageDefinition)Activator.CreateInstance(definition)!); + else + _service.WithOverrideFallbackLanguage(new EnglishAlamoLanguageDefinition()); + Assert.Equal(exp, _service.GetFallbackLanguageDefinition()); + } + + [Fact] + public void Test_IsBuiltInLanguageDefinition_ReturnsTrue() + { + foreach (var b in builtin_types) + Assert.True(_service.IsBuiltInLanguageDefinition((IAlamoLanguageDefinition)Activator.CreateInstance(b)!)); + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs index 771c4857..e02bc800 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs @@ -29,7 +29,7 @@ public bool IsBuiltInLanguageDefinition(IAlamoLanguageDefinition definition) } /// - public IAlamoLanguageDefinition GetFallBackLanguageDefinition() + public IAlamoLanguageDefinition GetFallbackLanguageDefinition() { return _fallbackAlamoLanguageDefinition; } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs index 0f5ade32..bf7695c0 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs @@ -23,7 +23,7 @@ public interface IAlamoLanguageSupportService /// overridden with /// /// - IAlamoLanguageDefinition GetFallBackLanguageDefinition(); + IAlamoLanguageDefinition GetFallbackLanguageDefinition(); /// /// Allows for overriding the fallback language. @@ -38,4 +38,4 @@ public interface IAlamoLanguageSupportService /// /// IAlamoLanguageDefinition GetDefaultLanguageDefinition(); -} \ No newline at end of file +} From facf507ffe9a67ee02e7983977da89c9d3c8665b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Wed, 23 Oct 2024 14:56:15 +0200 Subject: [PATCH 11/15] XML Support --- .../.idea/projectSettingsUpdater.xml | 1 + PG.Commons/PG.Commons/Data/IId.cs | 2 +- PG.Commons/PG.Commons/Data/IdBase.cs | 15 +- .../IXmlSerializationSupportService.cs | 40 ++++ .../XmlSerializationSupportService.cs | 84 ++++++++ .../PG.Commons/PGServiceContribution.cs | 2 + .../PG.Commons/Utilities/StringUtilities.cs | 47 ++-- .../PG.Commons/Utilities/XmlUtilities.cs | 46 ++++ .../IO/Xml/XmlImportHandlerTest.cs | 92 ++++++++ .../LocalisationTestConstants.cs | 11 +- ...rsGame.Components.Localisation.Test.csproj | 9 +- .../translation_manifest_empty.xml | 8 + .../translation_manifest_multi_keys.xml | 26 +++ .../translation_manifest_single_key.xml | 17 ++ .../AlamoLanguageSupportServiceTest.cs | 53 ++++- ...validDefaultLanguageDefinitionException.cs | 2 + .../IO/IExportHandler.cs | 13 +- .../IO/IImportHander.cs | 12 -- .../IO/IImportHandler.cs | 21 ++ .../IO/IInputStrategy.cs | 54 ++++- .../IO/IOutputStrategy.cs | 14 +- .../IO/Xml/Serializable/v1/Localisation.cs | 35 +++ .../Xml/Serializable/v1/LocalisationData.cs | 29 +++ .../IO/Xml/Serializable/v1/Translation.cs | 63 ++++++ .../IO/Xml/Serializable/v1/TranslationData.cs | 30 +++ .../IO/Xml/XmlExportHandler.cs | 92 ++++++++ .../IO/Xml/XmlImportHandler.cs | 96 +++++++++ .../IO/Xml/XmlInputStrategy.cs | 43 ++++ .../IO/Xml/XmlOutputStrategy.cs | 29 +++ .../Languages/AlamoLanguageDefinitionBase.cs | 6 +- .../LocalisationServiceContribution.cs | 5 +- ...tarWarsGame.Components.Localisation.csproj | 2 +- .../InMemoryOrderedTranslationRepository.cs | 201 ++++++++++++++++++ .../Repository/Content/ITranslationItem.cs | 36 +--- .../Content/ITranslationItemContent.cs | 6 +- .../Content/OrderedTranslationItem.cs | 22 +- .../Repository/Content/TranslationItemBase.cs | 17 +- .../Content/TranslationItemContent.cs | 6 +- .../Repository/ITranslationRepository.cs | 17 +- .../Services/AlamoLanguageSupportService.cs | 12 ++ .../Services/IAlamoLanguageSupportService.cs | 8 + .../Services/ITranslationRepositoryService.cs | 26 ++- PG.Testing/TestUtility.cs | 21 +- 43 files changed, 1225 insertions(+), 146 deletions(-) create mode 100644 PG.Commons/PG.Commons/Data/Serialization/IXmlSerializationSupportService.cs create mode 100644 PG.Commons/PG.Commons/Data/Serialization/XmlSerializationSupportService.cs create mode 100644 PG.Commons/PG.Commons/Utilities/XmlUtilities.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_empty.xml create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_multi_keys.xml create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_single_key.xml delete mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHander.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHandler.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/Localisation.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/LocalisationData.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/Translation.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/TranslationData.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlExportHandler.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlImportHandler.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlOutputStrategy.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Builtin/InMemoryOrderedTranslationRepository.cs diff --git a/.idea/.idea.PetroglyphTools/.idea/projectSettingsUpdater.xml b/.idea/.idea.PetroglyphTools/.idea/projectSettingsUpdater.xml index 86cc6c63..64af657f 100644 --- a/.idea/.idea.PetroglyphTools/.idea/projectSettingsUpdater.xml +++ b/.idea/.idea.PetroglyphTools/.idea/projectSettingsUpdater.xml @@ -1,6 +1,7 @@ + \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Data/IId.cs b/PG.Commons/PG.Commons/Data/IId.cs index 6edb1812..244700ff 100644 --- a/PG.Commons/PG.Commons/Data/IId.cs +++ b/PG.Commons/PG.Commons/Data/IId.cs @@ -17,4 +17,4 @@ public interface IId : IEquatable /// /// string Unwrap(); -} \ No newline at end of file +} diff --git a/PG.Commons/PG.Commons/Data/IdBase.cs b/PG.Commons/PG.Commons/Data/IdBase.cs index b06528da..c84563ce 100644 --- a/PG.Commons/PG.Commons/Data/IdBase.cs +++ b/PG.Commons/PG.Commons/Data/IdBase.cs @@ -43,6 +43,12 @@ public string Unwrap() return b.ToString(); } + /// + public override string ToString() + { + return Unwrap(); + } + /// /// Convenience method to access components in a type-safe manner. /// @@ -65,7 +71,10 @@ public string Unwrap() /// public override int GetHashCode() { - return HashCode.Combine(GetConfiguredArity(), HashCode.Combine(Components)); + var hash = Arity.GetHashCode(); + hash = Components.OfType() + .Aggregate(hash, (current, component) => current * 31 + component.GetHashCode()); + return hash; } /// @@ -74,7 +83,7 @@ public override int GetHashCode() /// protected virtual bool IsNullId() { - return Components.Any(); + return Components.Any(o => o != null); } /// @@ -82,4 +91,4 @@ protected virtual bool IsNullId() /// /// protected abstract int GetConfiguredArity(); -} \ No newline at end of file +} diff --git a/PG.Commons/PG.Commons/Data/Serialization/IXmlSerializationSupportService.cs b/PG.Commons/PG.Commons/Data/Serialization/IXmlSerializationSupportService.cs new file mode 100644 index 00000000..16012ffc --- /dev/null +++ b/PG.Commons/PG.Commons/Data/Serialization/IXmlSerializationSupportService.cs @@ -0,0 +1,40 @@ +namespace PG.Commons.Data.Serialization; + +/// +/// Simple XML serialization helper service. +/// +public interface IXmlSerializationSupportService +{ + /// + /// Serializes an object to XML and stores it to the file path. + /// + /// + /// + /// + /// + bool SerializeObjectAndStoreToDisc(string filePath, T serializableObject, bool overwrite = true) where T : class; + + /// + /// Serializes the given object to an XML string. + /// + /// + /// + /// + string SerializeObject(T serializableObject) where T : class; + + /// + /// Deserializes an XML file read form disc to an object. + /// + /// + /// + /// + T? DeSerializeObjectFromDisc(string filePath) where T : class; + + /// + /// Deserializes an object from a XML string. + /// + /// + /// + /// + T? DeSerializeObject(string xmlString) where T : class; +} diff --git a/PG.Commons/PG.Commons/Data/Serialization/XmlSerializationSupportService.cs b/PG.Commons/PG.Commons/Data/Serialization/XmlSerializationSupportService.cs new file mode 100644 index 00000000..7ac7d2d1 --- /dev/null +++ b/PG.Commons/PG.Commons/Data/Serialization/XmlSerializationSupportService.cs @@ -0,0 +1,84 @@ +// 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 System.Xml; +using System.Xml.Serialization; +using Microsoft.Extensions.Logging; +using PG.Commons.Services; + +namespace PG.Commons.Data.Serialization; + +/// +public sealed class XmlSerializationSupportService : ServiceBase, IXmlSerializationSupportService +{ + /// + public XmlSerializationSupportService(IServiceProvider services) : base(services) + { + } + + /// + public string SerializeObject(T serializableObject) where T : class + { + try + { + var xmlSerializer = new XmlSerializer(typeof(T)); + var stringWriter = new StringWriter(); + using var writer = XmlWriter.Create(stringWriter); + xmlSerializer.Serialize(writer, serializableObject); + return stringWriter.ToString(); + } + catch (Exception e) + { + Logger.LogError("An exception occurred whilst serializing: {}", e); + } + + return string.Empty; + } + + /// + public T? DeSerializeObjectFromDisc(string filePath) where T : class + { + var fileInfo = FileSystem.FileInfo.New(filePath); + if (!fileInfo.Exists) throw new FileNotFoundException("The file {} could not be found!", filePath); + return DeSerializeObject(FileSystem.File.ReadAllText(filePath)); + } + + /// + public T? DeSerializeObject(string xmlString) where T : class + { + if (string.IsNullOrEmpty(xmlString)) return null; + var xmlSerializer = new XmlSerializer(typeof(T)); + var stringReader = new StringReader(xmlString); + using var reader = XmlReader.Create(stringReader); + var obj = xmlSerializer.Deserialize(reader); + return obj as T; + } + + /// + public bool SerializeObjectAndStoreToDisc(string filePath, T serializableObject, bool overwrite = false) + where T : class + { + try + { + var fileInfo = FileSystem.FileInfo.New(filePath); + if (fileInfo.Exists && !overwrite) + throw new UnauthorizedAccessException( + $"The file already exists. Force overwrite with {nameof(overwrite)}=true."); + fileInfo.Delete(); + + var directoyInfo = fileInfo.Directory; + if (directoyInfo is { Exists: false }) FileSystem.Directory.CreateDirectory(directoyInfo.FullName); + + FileSystem.File.WriteAllText(filePath, SerializeObject(serializableObject)); + } + catch (Exception e) + { + Logger.LogError("An exception occurred whilst serializing: {}", e); + return false; + } + + return true; + } +} diff --git a/PG.Commons/PG.Commons/PGServiceContribution.cs b/PG.Commons/PG.Commons/PGServiceContribution.cs index 7ec9b6b8..2e2af95b 100644 --- a/PG.Commons/PG.Commons/PGServiceContribution.cs +++ b/PG.Commons/PG.Commons/PGServiceContribution.cs @@ -1,6 +1,7 @@ using AnakinRaW.CommonUtilities.Hashing; using Microsoft.Extensions.DependencyInjection; using PG.Commons.Attributes; +using PG.Commons.Data.Serialization; using PG.Commons.Extensibility; using PG.Commons.Hashing; @@ -16,5 +17,6 @@ public void ContributeServices(IServiceCollection serviceCollection) { serviceCollection.AddSingleton(sp => new Crc32HashingProvider()); serviceCollection.AddSingleton(sp => new Crc32HashingService(sp)); + serviceCollection.AddSingleton(sp => new XmlSerializationSupportService(sp)); } } \ No newline at end of file diff --git a/PG.Commons/PG.Commons/Utilities/StringUtilities.cs b/PG.Commons/PG.Commons/Utilities/StringUtilities.cs index fe099d4a..8c41a856 100644 --- a/PG.Commons/PG.Commons/Utilities/StringUtilities.cs +++ b/PG.Commons/PG.Commons/Utilities/StringUtilities.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.Runtime.CompilerServices; using System.Text; #if NETSTANDARD2_0 @@ -12,13 +13,14 @@ namespace PG.Commons.Utilities; /// -/// Provides primitive helper methods for strings. +/// Provides primitive helper methods for strings. /// public static class StringUtilities { /// - /// Checks whether a given string, when converted to bytes, is not longer than the max value of an . - /// Throws an if the string is longer. + /// Checks whether a given string, when converted to bytes, is not longer than the max value of an + /// . + /// Throws an if the string is longer. /// /// The string to validate. /// The encoding that shall be used to get the string length. @@ -32,17 +34,18 @@ public static ushort ValidateStringByteSizeUInt16(ReadOnlySpan value, Enco if (encoding == null) throw new ArgumentNullException(nameof(encoding)); - var size = encoding.GetByteCount(value); + var size = encoding.GetByteCount(value); if (size is < 0 or > ushort.MaxValue) - throw new ArgumentException($"The value is longer than the expected {ushort.MaxValue} characters.", nameof(value)); + throw new ArgumentException($"The value is longer than the expected {ushort.MaxValue} characters.", + nameof(value)); return (ushort)size; } /// - /// Checks whether a given character sequence has no more characters than the max value of an . - /// Throws an if the string is longer. + /// Checks whether a given character sequence has no more characters than the max value of an . + /// Throws an if the string is longer. /// /// The string to validate. /// The actual length of the value in characters. @@ -55,13 +58,14 @@ public static ushort ValidateStringCharLengthUInt16(ReadOnlySpan value) var length = value.Length; if (length is < 0 or > ushort.MaxValue) - throw new ArgumentException($"The value is longer that the expected {ushort.MaxValue} characters.", nameof(value)); + throw new ArgumentException($"The value is longer that the expected {ushort.MaxValue} characters.", + nameof(value)); return (ushort)length; } /// - /// Throws an if the given character sequence contains non-ASCII characters. + /// Throws an if the given character sequence contains non-ASCII characters. /// /// The character sequence to validate. /// The character sequence contains non-ASCII characters. @@ -74,7 +78,7 @@ public static void ValidateIsAsciiOnly(ReadOnlySpan value) } /// - /// Throws an if the given character sequence contains non-ASCII characters. + /// Throws an if the given character sequence contains non-ASCII characters. /// /// The character sequence to validate. /// The character sequence contains non-ASCII characters. @@ -85,10 +89,27 @@ public static bool IsAsciiOnly(ReadOnlySpan value) throw new ArgumentNullException(nameof(value)); foreach (var ch in value) - { if ((uint)ch > '\x007f') return false; - } return true; } -} \ No newline at end of file + + /// + /// Basic validation algorithm. + /// + /// + /// + public static string Validate(string? s) + { + var stringBuilder = new StringBuilder(); + + if (string.IsNullOrEmpty(s)) return string.Empty; + + foreach (var t in s.Where(t => t == 0x9 || t == 0xA || t == 0xD || + (t >= 0x20 && t <= 0xD7FF) || + (t >= 0xE000 && t <= 0xFFFD))) + stringBuilder.Append(t); + + return stringBuilder.ToString(); + } +} diff --git a/PG.Commons/PG.Commons/Utilities/XmlUtilities.cs b/PG.Commons/PG.Commons/Utilities/XmlUtilities.cs new file mode 100644 index 00000000..a9b1e2d0 --- /dev/null +++ b/PG.Commons/PG.Commons/Utilities/XmlUtilities.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. + +namespace PG.Commons.Utilities; + +/// +/// XML file utility. +/// +public static class XmlUtilities +{ + /// + /// Escape XML Content. + /// + /// + /// + public static string? EscapeXml(string? s) + { + if (string.IsNullOrEmpty(s)) return s; + + var returnString = s!; + returnString = returnString.Replace("&", "&"); + returnString = returnString.Replace("<", "<"); + returnString = returnString.Replace(">", ">"); + returnString = returnString.Replace("'", "'"); + returnString = returnString.Replace("\"", """); + return returnString; + } + + /// + /// Unescape XML content. + /// + /// + /// + public static string? UnescapeXml(string? s) + { + if (string.IsNullOrEmpty(s)) return s; + + var returnString = s!; + returnString = returnString.Replace("'", "'"); + returnString = returnString.Replace(""", "\""); + returnString = returnString.Replace(">", ">"); + returnString = returnString.Replace("<", "<"); + returnString = returnString.Replace("&", "&"); + return returnString; + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs new file mode 100644 index 00000000..d520907f --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs @@ -0,0 +1,92 @@ +// 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.Abstractions; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Extensibility; +using PG.StarWarsGame.Components.Localisation.IO; +using PG.StarWarsGame.Components.Localisation.IO.Xml; +using PG.StarWarsGame.Components.Localisation.Repository.Builtin; +using PG.StarWarsGame.Components.Localisation.Repository.Content; +using PG.Testing; +using Testably.Abstractions.Testing; +using Xunit; + +namespace PG.StarWarsGame.Components.Localisation.Test.IO.Xml; + +public class XmlImportHandlerTest +{ + private static readonly string EmptyXml = "empty.xml"; + private static readonly string MultiKeysXml = "multi_keys.xml"; + private static readonly string SingleKeyXml = "single_key.xml"; + + private readonly MockFileSystem _fileSystem = new(); + private readonly IImportHandler _handler; + + public XmlImportHandlerTest() + { + var sc = new ServiceCollection(); + sc.AddSingleton(_fileSystem); + sc.CollectPgServiceContributions(); + _handler = sc.BuildServiceProvider().GetRequiredService>(); + } + + private string CreateMockFilePath(string directory, string fileName) + { + return _fileSystem.Path.Combine(_fileSystem.Directory.CreateDirectory(directory).FullName, fileName); + } + + private static string GetResourcePath(string resourceName) + { + return $"IO.Xml.v1.Serializable.translation_manifest_{resourceName}"; + } + + [Fact] + public void Test_Import_WithEmptyXml() + { + var filePath = CreateMockFilePath("./empty_test", EmptyXml); + var resourcePath = GetResourcePath(EmptyXml); + TestUtility.CopyEmbeddedResourceToMockFilesystem(typeof(XmlImportHandlerTest), resourcePath, filePath, + _fileSystem); + var repository = new InMemoryOrderedTranslationRepository(); + _handler.Import(new XmlInputStrategy(filePath), repository); + Assert.Empty(repository.Content); + } + + [Fact] + public void Test_Import_WithSingleKeyXml() + { + var filePath = CreateMockFilePath("./single_key_test", SingleKeyXml); + var resourcePath = GetResourcePath(SingleKeyXml); + TestUtility.CopyEmbeddedResourceToMockFilesystem(typeof(XmlImportHandlerTest), resourcePath, filePath, + _fileSystem); + var repository = new InMemoryOrderedTranslationRepository(); + _handler.Import(new XmlInputStrategy(filePath), repository); + Assert.Equal(5, repository.Content.Keys.Count()); + Assert.Equal(5, repository.Content.Values.Count()); + foreach (var kvp in repository.Content) + { + Assert.Single(kvp.Value); + Assert.NotNull(kvp.Value.First()); + Assert.True(OrderedTranslationItemId.Of("TEST_KEY_00")?.Equals(kvp.Value.First().ItemId)); + Assert.Equal(new TranslationItemContent { Key = "TEST_KEY_00", Value = "Test text for key TEST_KEY_00" }, + kvp.Value.First().Content); + } + } + + [Fact] + public void Test_Import_WithMultiKeyXml() + { + var filePath = CreateMockFilePath("./multi_keys_test", MultiKeysXml); + var resourcePath = GetResourcePath(MultiKeysXml); + TestUtility.CopyEmbeddedResourceToMockFilesystem(typeof(XmlImportHandlerTest), resourcePath, filePath, + _fileSystem); + var repository = new InMemoryOrderedTranslationRepository(); + _handler.Import(new XmlInputStrategy(filePath), repository); + Assert.Equal(5, repository.Content.Keys.Count()); + Assert.Equal(5, repository.Content.Values.Count()); + + foreach (var kvp in repository.Content) Assert.Equal(2, kvp.Value.Count); + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/LocalisationTestConstants.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/LocalisationTestConstants.cs index 130f2118..9ab4d541 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/LocalisationTestConstants.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/LocalisationTestConstants.cs @@ -9,9 +9,9 @@ namespace PG.StarWarsGame.Components.Localisation.Test; public sealed class LocalisationTestConstants { - public static readonly IList REGISTERED_LANGUAGE_DEFINITIONS = new List + public static readonly IList RegisteredLanguageDefinitions = new List { - //[gruenwaldlu, 2021-04-18-12:02:51+2]: All officially supported languages are listed below. + //[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), @@ -20,5 +20,8 @@ public sealed class LocalisationTestConstants typeof(ThaiAlamoLanguageDefinition) }; - public static readonly Type DEFAULT_LANGUAGE = typeof(EnglishAlamoLanguageDefinition); -} \ No newline at end of file + public static readonly Type DefaultLanguage = typeof(EnglishAlamoLanguageDefinition); + + public static readonly string XmlV1ResourcePathBase = + $"{typeof(LocalisationTestConstants).Assembly.Location}.Resources.IO.Xml.v1.Seralizable.translation_manifest_"; +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj index 7041305b..670c5b23 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj @@ -14,8 +14,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + @@ -34,4 +34,9 @@ + + + + + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_empty.xml b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_empty.xml new file mode 100644 index 00000000..e68aace6 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_empty.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_multi_keys.xml b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_multi_keys.xml new file mode 100644 index 00000000..c04d3139 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_multi_keys.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_single_key.xml b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_single_key.xml new file mode 100644 index 00000000..d55f2d96 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_single_key.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Services/AlamoLanguageSupportServiceTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Services/AlamoLanguageSupportServiceTest.cs index 9a540139..16292eb1 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Services/AlamoLanguageSupportServiceTest.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Services/AlamoLanguageSupportServiceTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO.Abstractions; using System.Linq; using System.Reflection; @@ -19,10 +20,7 @@ namespace PG.StarWarsGame.Components.Localisation.Test.Services; public class AlamoLanguageSupportServiceTest { - private readonly MockFileSystem _fileSystem = new(); - private readonly IAlamoLanguageSupportService _service; - - private readonly HashSet builtin_types = new() + private readonly HashSet _builtinTypes = new() { typeof(ChineseAlamoLanguageDefinition), typeof(EnglishAlamoLanguageDefinition), @@ -37,6 +35,9 @@ public class AlamoLanguageSupportServiceTest typeof(ThaiAlamoLanguageDefinition) }; + private readonly MockFileSystem _fileSystem = new(); + private readonly IAlamoLanguageSupportService _service; + public AlamoLanguageSupportServiceTest() { var sc = new ServiceCollection(); @@ -83,7 +84,49 @@ public void Test_GetFallbackLanguageDefinition_ReturnsDesired(Type? definition, [Fact] public void Test_IsBuiltInLanguageDefinition_ReturnsTrue() { - foreach (var b in builtin_types) + foreach (var b in _builtinTypes) Assert.True(_service.IsBuiltInLanguageDefinition((IAlamoLanguageDefinition)Activator.CreateInstance(b)!)); } + + [Fact] + public void Test_IsBuiltInLanguageDefinition_ReturnsFalse() + { + Assert.False(_service.IsBuiltInLanguageDefinition(new TestAlamoLanguageDefinition())); + } + + [Fact] + public void Test_AllPresentLanguageIdentifiersAreValid() + { + var types = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assemblyTypes => assemblyTypes.GetTypes()) + .Where(assemblyType => typeof(IAlamoLanguageDefinition).IsAssignableFrom(assemblyType) && + assemblyType is { IsClass: true, IsAbstract: false }) + .ToList(); + foreach (var type in types) Assert.True(typeof(AlamoLanguageDefinitionBase).IsAssignableFrom(type)); + } + + [Fact] + public void Test_CreateLanguageIdentifierMapping_AllPresent() + { + var defs = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assemblyTypes => assemblyTypes.GetTypes()) + .Where(assemblyType => typeof(IAlamoLanguageDefinition).IsAssignableFrom(assemblyType) && + assemblyType is { IsClass: true, IsAbstract: false }) + .Select(t => (IAlamoLanguageDefinition)Activator.CreateInstance(t)) + .ToList(); + var map = _service.CreateLanguageIdentifierMapping(); + Assert.Equal(defs.Count, map.Count); + foreach (var def in defs) + { + Assert.True(typeof(AlamoLanguageDefinitionBase).IsAssignableFrom(def.GetType())); + Assert.NotNull(map[def.LanguageIdentifier]); + Assert.Equal(def, map[def.LanguageIdentifier]); + } + } + + private class TestAlamoLanguageDefinition : AlamoLanguageDefinitionBase + { + protected override string ConfiguredLanguageIdentifier => "TEST"; + protected override CultureInfo ConfiguredCulture => CultureInfo.CurrentCulture; + } } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Exceptions/InvalidDefaultLanguageDefinitionException.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Exceptions/InvalidDefaultLanguageDefinitionException.cs index 91365a88..60f449d9 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Exceptions/InvalidDefaultLanguageDefinitionException.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Exceptions/InvalidDefaultLanguageDefinitionException.cs @@ -2,10 +2,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using System; +using System.Diagnostics.CodeAnalysis; namespace PG.StarWarsGame.Components.Localisation.Exceptions; /// +[ExcludeFromCodeCoverage] public class InvalidDefaultLanguageDefinitionException : Exception { /// diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IExportHandler.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IExportHandler.cs index 30df113d..c0fca6f5 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IExportHandler.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IExportHandler.cs @@ -1,12 +1,19 @@ // 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.Components.Localisation.Repository; + namespace PG.StarWarsGame.Components.Localisation.IO; /// /// The export handler associated with a given /// -/// -public interface IExportHandler where T : IOutputStrategy +public interface IExportHandler where T : IOutputStrategy { -} \ No newline at end of file + /// + /// Exports the given repository. + /// + /// + /// + void Export(T strategy, ITranslationRepository repository); +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHander.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHander.cs deleted file mode 100644 index cdc9b0cf..00000000 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHander.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for details. - -namespace PG.StarWarsGame.Components.Localisation.IO; - -/// -/// The import handler associated with a given -/// -/// -public interface IImportHander where T : IInputStrategy -{ -} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHandler.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHandler.cs new file mode 100644 index 00000000..ea231c64 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IImportHandler.cs @@ -0,0 +1,21 @@ +// 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.Components.Localisation.Repository; + +namespace PG.StarWarsGame.Components.Localisation.IO; + +/// +/// The import handler associated with a given +/// +/// +public interface IImportHandler where T : IInputStrategy +{ + /// + /// Imports data into a + /// + /// + /// + /// + void Import(T strategy, ITranslationRepository targetRepository); +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs index 9251f37d..b1f8d9d4 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs @@ -1,6 +1,9 @@ // Copyright (c) Alamo Engine Tools and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using System.Collections.Generic; +using System.IO; + namespace PG.StarWarsGame.Components.Localisation.IO; /// @@ -9,4 +12,53 @@ namespace PG.StarWarsGame.Components.Localisation.IO; /// public interface IInputStrategy { -} \ No newline at end of file + // TODO: implement DatInputStrategy, XmlInputStrategy, CsvInputStrategy -> this should cover most current community tools. + + /// + /// Determines if we are reading a single file, multiple files or all files from a base directory with an optional file + /// filter. + /// + enum FileImportGrouping + { + /// + /// Single file import. + /// + Single, + + /// + /// Multiple file import. + /// + Multi, + + /// + /// Imports all files form a directory matching the optional + /// + BaseDirectory + } + + /// + /// The + /// + FileImportGrouping ImportGrouping { get; } + + /// + /// An optional file filter following the established FileDialog.Filter pattern. + /// For more info check + /// + /// FileDialog.Filter + /// Property + /// + /// + string FileFilter { get; } + + /// + /// A set of file paths. Can be empty. + /// + ISet FilePaths { get; } + + /// + /// The base directory. + /// + DirectoryInfo BaseDirectory { get; } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.cs index 01f6d395..8da609b8 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.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.IO; + namespace PG.StarWarsGame.Components.Localisation.IO; /// @@ -39,8 +41,12 @@ enum FileExportGrouping string Extension { get; } /// - /// Creates a fresh instance of a given associated + /// The file name without file extension + /// + string FileName { get; } + + /// + /// The export base path. /// - /// - IExportHandler CreateExportHandler(); -} \ No newline at end of file + DirectoryInfo ExportBasePath { get; } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/Localisation.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/Localisation.cs new file mode 100644 index 00000000..e42ee933 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/Localisation.cs @@ -0,0 +1,35 @@ +// 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; +using System.Xml.Serialization; + +namespace PG.StarWarsGame.Components.Localisation.IO.Xml.Serializable.v1; + +/// +/// XML file definition. +/// +[XmlRoot(ElementName = "LocalisationHolder")] +[ExcludeFromCodeCoverage] +public sealed class Localisation +{ + /// + /// .ctor + /// + public Localisation() + { + TranslationData ??= new TranslationData(); + } + + /// + /// Translation data associated with the key. + /// + [XmlElement(ElementName = "TranslationData")] + public TranslationData TranslationData { get; set; } + + /// + /// Translation Key + /// + [XmlAttribute(AttributeName = "Key")] + public required string Key { get; set; } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/LocalisationData.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/LocalisationData.cs new file mode 100644 index 00000000..cac05e5a --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/LocalisationData.cs @@ -0,0 +1,29 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Xml.Serialization; + +namespace PG.StarWarsGame.Components.Localisation.IO.Xml.Serializable.v1; + +/// +/// +[XmlRoot(ElementName = "LocalisationData")] +[ExcludeFromCodeCoverage] +public sealed class LocalisationData +{ + /// + /// .ctor + /// + public LocalisationData() + { + LocalisationHolder ??= new List(); + } + + /// + /// Holds s + /// + [XmlElement(ElementName = "Localisation")] + public List LocalisationHolder { get; set; } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/Translation.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/Translation.cs new file mode 100644 index 00000000..5ec1315a --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/Translation.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.Diagnostics.CodeAnalysis; +using System.Security; +using System.Xml; +using System.Xml.Serialization; +using PG.Commons.Utilities; + +namespace PG.StarWarsGame.Components.Localisation.IO.Xml.Serializable.v1; + +/// +/// XML file definition. +/// +[XmlRoot(ElementName = "TranslationHolder")] +[ExcludeFromCodeCoverage] +public class Translation +{ + private string _text = null!; + + /// + /// The content's language. + /// + [XmlAttribute(AttributeName = "Language")] + public required string Language { get; set; } + + /// + /// Raw translation string. + /// + [XmlIgnore] + public string? Text + { + get => _text; + set => _text = StringUtilities.Validate(value); + } + + /// + /// Translation content. + /// + /// + [XmlText] + public XmlNode[]? CDataContent + { + get + { + var dummy = new XmlDocument(); + return [dummy.CreateCDataSection(XmlUtilities.EscapeXml(Text))]; + } + set + { + if (value == null) + { + Text = "[MISSING]"; + return; + } + + if (value.Length != 1) throw new InvalidOperationException($"Invalid array length {value.Length}"); + var s = new SecurityElement("a", value[0].Value); + Text = XmlUtilities.UnescapeXml(s.Text); + } + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/TranslationData.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/TranslationData.cs new file mode 100644 index 00000000..b49f4adf --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/Serializable/v1/TranslationData.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 System.Diagnostics.CodeAnalysis; +using System.Xml.Serialization; + +namespace PG.StarWarsGame.Components.Localisation.IO.Xml.Serializable.v1; + +/// +/// XML Element description +/// +[XmlRoot(ElementName = "TranslationData")] +[ExcludeFromCodeCoverage] +public class TranslationData +{ + /// + /// .ctor + /// + public TranslationData() + { + TranslationHolder ??= new List(); + } + + /// + /// Holds s. + /// + [XmlElement(ElementName = "Translation")] + public List TranslationHolder { get; set; } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlExportHandler.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlExportHandler.cs new file mode 100644 index 00000000..a364f010 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlExportHandler.cs @@ -0,0 +1,92 @@ +// 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 Microsoft.Extensions.DependencyInjection; +using PG.Commons.Data.Serialization; +using PG.Commons.Services; +using PG.StarWarsGame.Components.Localisation.IO.Xml.Serializable.v1; +using PG.StarWarsGame.Components.Localisation.Languages; +using PG.StarWarsGame.Components.Localisation.Repository; +using PG.StarWarsGame.Components.Localisation.Repository.Content; + +namespace PG.StarWarsGame.Components.Localisation.IO.Xml; + +/// +public class XmlExportHandler : ServiceBase, IExportHandler +{ + /// + public XmlExportHandler(IServiceProvider services) : base(services) + { + } + + /// + public void Export(XmlOutputStrategy strategy, ITranslationRepository repository) + { + FileSystem.Directory.CreateDirectory(strategy.ExportBasePath.FullName); + var xml = ToXml(repository); + Services.GetService()?.SerializeObjectAndStoreToDisc(strategy.FileName, xml); + } + + /// + /// Converts an to the corresponding XML file representation. + /// + /// + /// + /// + protected LocalisationData ToXml(ITranslationRepository repository) + { + var xml = new LocalisationData(); + var languages = GetLanguages(repository); + var keys = GetKeys(repository); + foreach (var key in keys) + { + var loc = new Serializable.v1.Localisation + { + Key = key.ToUpper(), + TranslationData = new TranslationData() + }; + foreach (var t in languages.Select(lang => new Translation + { + Language = lang.LanguageIdentifier.ToUpper(), + Text = repository.GetTranslationItem(lang, + OrderedTranslationItemId.Of(key) ?? + throw new InvalidOperationException( + $"No valid {nameof(OrderedTranslationItemId)} could be created from {key}")) + .Content + .Value + })) + loc.TranslationData.TranslationHolder.Add(t); + + xml.LocalisationHolder.Add(loc); + } + + return xml; + } + + /// + /// Fetches all text keys from the repository. + /// + /// + /// + protected ISet GetKeys(ITranslationRepository repository) + { + var keySet = new HashSet(); + foreach (var items in repository.Content.Values) + foreach (var item in items) + keySet.Add(item.Content.Key); + return keySet; + } + + /// + /// Returns a set of all contained language definitions. + /// + /// + /// + protected HashSet GetLanguages(ITranslationRepository repository) + { + return new HashSet(repository.Content.Keys); + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlImportHandler.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlImportHandler.cs new file mode 100644 index 00000000..45c6b381 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlImportHandler.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; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using PG.Commons; +using PG.Commons.Data.Serialization; +using PG.Commons.Services; +using PG.StarWarsGame.Components.Localisation.IO.Xml.Serializable.v1; +using PG.StarWarsGame.Components.Localisation.Languages; +using PG.StarWarsGame.Components.Localisation.Repository; +using PG.StarWarsGame.Components.Localisation.Repository.Content; +using PG.StarWarsGame.Components.Localisation.Services; + +namespace PG.StarWarsGame.Components.Localisation.IO.Xml; + +/// +/// The Import handler for the +/// +public class XmlImportHandler : ServiceBase, IImportHandler +{ + /// + public XmlImportHandler(IServiceProvider services) : base(services) + { + } + + /// + public void Import(XmlInputStrategy strategy, ITranslationRepository targetRepository) + { + var data = Services.GetService() + ?.DeSerializeObjectFromDisc(strategy.FilePath); + if (data == null) return; + + FromXml(data, targetRepository); + } + + /// + /// Translates a + /// + /// + /// + /// + protected void FromXml(LocalisationData data, ITranslationRepository targetRepository) + { + var languageMap = + Services.GetService()?.CreateLanguageIdentifierMapping() ?? + throw new LibraryInitialisationException( + $"Required service {nameof(IAlamoLanguageSupportService)} has not bee initialised properly."); + + var contentMap = + languageMap.Values + .ToDictionary>(d => d, + d => new HashSet()); + + foreach (var loc in data.LocalisationHolder) + { + var id = OrderedTranslationItemId.Of(loc.Key); + if (id == null) + { + Logger.LogWarning("Could not create a valid {} from {}. Skipping.", nameof(OrderedTranslationItemId), + loc.Key); + continue; + } + + + foreach (var t in loc.TranslationData.TranslationHolder) + { + if (t == null) + { + Logger.LogWarning("Empty translation for key {} found! Skipping.", loc.Key); + continue; + } + + var lang = languageMap[t.Language]; + if (lang == null) + { + Logger.LogWarning("Could not determine the language for {}. Skipping.", t.Language); + continue; + } + + var c = new TranslationItemContent + { + Key = id.Raw(), + Value = t.Text + }; + contentMap[lang].Add(OrderedTranslationItem.Of(c)); + } + } + + foreach (var kvp in contentMap.Where(kvp => kvp.Value.Count != 0)) + targetRepository.AddOrUpdateLanguage(kvp.Key, kvp.Value); + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs new file mode 100644 index 00000000..58871768 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs @@ -0,0 +1,43 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace PG.StarWarsGame.Components.Localisation.IO.Xml; + +/// +/// The XML input strategy. +/// +public readonly record struct XmlInputStrategy : IInputStrategy +{ + /// + /// .ctor + /// + /// + public XmlInputStrategy(string filePath) + { + FilePaths = new HashSet { filePath }; + } + + /// + /// Convenience method for accessing the only file path required for this strategy. + /// + public string FilePath => FilePaths.Single(); + + /// + public IInputStrategy.FileImportGrouping ImportGrouping => IInputStrategy.FileImportGrouping.Single; + + /// + public string FileFilter => throw new InvalidOperationException( + $"{nameof(XmlInputStrategy)}.{nameof(FileFilter)} is not supported for {nameof(ImportGrouping)}={nameof(IInputStrategy.FileImportGrouping.Single)}"); + + /// + public ISet FilePaths { get; } + + /// + public DirectoryInfo BaseDirectory => throw new InvalidOperationException( + $"{nameof(XmlInputStrategy)}.{nameof(DirectoryInfo)} is not supported for {nameof(ImportGrouping)}={nameof(IInputStrategy.FileImportGrouping.Single)}"); +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlOutputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlOutputStrategy.cs new file mode 100644 index 00000000..782a89e4 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlOutputStrategy.cs @@ -0,0 +1,29 @@ +// 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.Components.Localisation.IO.Xml; + +/// +/// The XML output strategy. +/// +public readonly record struct XmlOutputStrategy : IOutputStrategy +{ + /// + /// Convenience accessor for the full file path as string. + /// + public string FilePath => $"{Path.Combine(ExportBasePath.FullName, FileName)}.{Extension}"; + + /// + public IOutputStrategy.FileExportGrouping ExportGrouping => IOutputStrategy.FileExportGrouping.Single; + + /// + public string Extension => "xml"; + + /// + public required string FileName { get; init; } + + /// + public required DirectoryInfo ExportBasePath { get; init; } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/AlamoLanguageDefinitionBase.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/AlamoLanguageDefinitionBase.cs index 784553d2..f7fb3020 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/AlamoLanguageDefinitionBase.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Languages/AlamoLanguageDefinitionBase.cs @@ -63,7 +63,9 @@ protected virtual int CompareToInternal(IAlamoLanguageDefinition other) /// protected virtual bool EqualsInternal(IAlamoLanguageDefinition other) { - return CompareTo(other) == 0; + return string.Compare(Culture.TwoLetterISOLanguageName, other.Culture.TwoLetterISOLanguageName, + StringComparison.Ordinal) == 0 && string.Compare(LanguageIdentifier, other.LanguageIdentifier, + StringComparison.Ordinal) == 0; } /// @@ -97,4 +99,4 @@ public override int GetHashCode() { return GetHashCodeInternal(); } -} \ No newline at end of file +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs index 582b2e60..db246d9c 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using PG.Commons.Attributes; using PG.Commons.Extensibility; +using PG.StarWarsGame.Components.Localisation.IO; +using PG.StarWarsGame.Components.Localisation.IO.Xml; using PG.StarWarsGame.Components.Localisation.Services; namespace PG.StarWarsGame.Components.Localisation; @@ -13,6 +15,7 @@ public class LocalisationServiceContribution : IServiceContribution public void ContributeServices(IServiceCollection serviceCollection) { serviceCollection - .AddSingleton(sp => new AlamoLanguageSupportService(sp)); + .AddSingleton(sp => new AlamoLanguageSupportService(sp)) + .AddTransient>(sp => new XmlImportHandler(sp)); } } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj index 0145098f..25323b38 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.csproj @@ -33,7 +33,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Builtin/InMemoryOrderedTranslationRepository.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Builtin/InMemoryOrderedTranslationRepository.cs new file mode 100644 index 00000000..f89a5203 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Builtin/InMemoryOrderedTranslationRepository.cs @@ -0,0 +1,201 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using PG.StarWarsGame.Components.Localisation.Languages; +using PG.StarWarsGame.Components.Localisation.Repository.Content; + +namespace PG.StarWarsGame.Components.Localisation.Repository.Builtin; + +/// +/// A simple ordered in-memory translation repository +/// +public class InMemoryOrderedTranslationRepository : ITranslationRepository +{ + private IDictionary> + Repository { get; } = + new Dictionary>(); + + /// + public IReadOnlyDictionary> Content => ToContent(); + + /// + public bool AddLanguage(IAlamoLanguageDefinition language) + { + try + { + Repository.Add(language, new Dictionary()); + return true; + } + catch (ArgumentException) + { + return false; + } + } + + /// + public bool AddOrUpdateLanguage(IAlamoLanguageDefinition language, + ICollection translationItems, + ITranslationRepository.MergeStrategy strategy = ITranslationRepository.MergeStrategy.MergeByKey) + { + return strategy switch + { + ITranslationRepository.MergeStrategy.Replace => AddOrUpdateLanguageReplace(language, translationItems), + ITranslationRepository.MergeStrategy.MergeByKey => AddOrUpdateLanguageMergeByKey(language, + translationItems), + ITranslationRepository.MergeStrategy.Append => AddOrUpdateLanguageAppend(language, translationItems), + _ => throw new ArgumentOutOfRangeException(nameof(strategy), strategy, null) + }; + } + + /// + public bool RemoveLanguage(IAlamoLanguageDefinition language) + { + return Repository.Remove(language); + } + + /// + public ITranslationItem GetTranslationItem(IAlamoLanguageDefinition language, ITranslationItemId id) + { + if (id is not OrderedTranslationItemId key) + throw new ArgumentException( + $"The passed id is of type {id.GetType()} but should be of type {nameof(OrderedTranslationItemId)}", + nameof(id)); + if (!Repository.TryGetValue(language, out var translationItems)) + throw new KeyNotFoundException($"The language {language} was not found"); + if (!translationItems.TryGetValue(key, out var item)) + throw new KeyNotFoundException($"The item {id} was not found"); + return item; + } + + /// + public bool RemoveTranslationItem(ITranslationItemId id) + { + if (id is not OrderedTranslationItemId key) + throw new ArgumentException( + $"The passed id is of type {id.GetType()} but should be of type {nameof(OrderedTranslationItemId)}", + nameof(id)); + return Repository.Values.Where(i => i.ContainsKey(key)) + .Aggregate(false, (current, i) => current | i.Remove(key)); + } + + /// + public void AddOrUpdateTranslationItem(IAlamoLanguageDefinition language, ITranslationItem item) + { + if (item.ItemId is not OrderedTranslationItemId key) + throw new ArgumentException( + $"The passed id is of type {item.ItemId} but should be of type {nameof(OrderedTranslationItemId)}", + nameof(item.ItemId)); + if (!Repository.TryGetValue(language, out var translationItems)) + throw new KeyNotFoundException($"The language {language} was not found"); + translationItems[key] = item; + } + + /// + public ITranslationDiff CreateTranslationDiff(IAlamoLanguageDefinition diffBase) + { + throw new NotImplementedException(); + } + + /// + public bool ApplyTranslationDiff(ITranslationDiff diff) + { + throw new NotImplementedException(); + } + + /// + public IReadOnlyCollection GetTranslationItemsByLanguage( + IAlamoLanguageDefinition language) + { + if (!Repository.TryGetValue(language, out var value)) + throw new KeyNotFoundException($"The provided language {language} could not be found."); + + return value.Values.ToImmutableList(); + } + + private IReadOnlyDictionary> ToContent() + { + return Repository + .ToImmutableDictionary< + KeyValuePair>, + IAlamoLanguageDefinition, ICollection>(pair => pair.Key, + pair => new List(pair.Value.Values)); + } + + private bool AddOrUpdateLanguageAppend(IAlamoLanguageDefinition language, + ICollection translationItems) + { + if (!Repository.TryGetValue(language, out var repositoryItems)) + { + Repository.Add(language, new Dictionary()); + repositoryItems = Repository[language]; + } + + var itemsToAdd = new Dictionary(); + foreach (var item in translationItems) + { + if (item.ItemId is not OrderedTranslationItemId key) + throw new ArgumentException( + $"The passed id is of type {item.ItemId} but should be of type {nameof(OrderedTranslationItemId)}", + nameof(item.ItemId)); + if (!repositoryItems.ContainsKey(key)) + itemsToAdd.Add(key, item); + } + + foreach (var i in itemsToAdd) repositoryItems.Add(i.Key, i.Value); + + return translationItems.Count == itemsToAdd.Count; + } + + private bool AddOrUpdateLanguageMergeByKey(IAlamoLanguageDefinition language, + ICollection translationItems) + { + if (!Repository.TryGetValue(language, out var repositoryItems)) + { + Repository.Add(language, new Dictionary()); + repositoryItems = Repository[language]; + } + + var itemsToAdd = new Dictionary(); + foreach (var item in translationItems) + { + if (item.ItemId is not OrderedTranslationItemId key) + throw new ArgumentException( + $"The passed id is of type {item.ItemId} but should be of type {nameof(OrderedTranslationItemId)}", + nameof(item.ItemId)); + if (repositoryItems.ContainsKey(key)) + repositoryItems[key] = item; + else + itemsToAdd.Add(key, item); + } + + foreach (var i in itemsToAdd) repositoryItems.Add(i.Key, i.Value); + + return true; + } + + private bool AddOrUpdateLanguageReplace(IAlamoLanguageDefinition language, + ICollection translationItems) + { + if (!Repository.TryGetValue(language, out var repositoryItems)) + { + Repository.Add(language, new Dictionary()); + repositoryItems = Repository[language]; + } + + repositoryItems.Clear(); + foreach (var item in translationItems) + { + if (item.ItemId is not OrderedTranslationItemId key) + throw new ArgumentException( + $"The passed id is of type {item.ItemId} but should be of type {nameof(OrderedTranslationItemId)}", + nameof(item.ItemId)); + repositoryItems.Add(key, item); + } + + return true; + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs index 80c01de5..0cc3e056 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs @@ -5,27 +5,6 @@ namespace PG.StarWarsGame.Components.Localisation.Repository.Content; /// public interface ITranslationItem { - /// - /// The source of the translation item. - /// - enum TranslationItemSource - { - /// - /// A translation item available in the base game. - /// - BaseGame, - - /// - /// A translation item added by the expansion - /// - Expansion, - - /// - /// A translation item added by a mod. - /// - Mod - } - /// /// ID /// @@ -35,17 +14,4 @@ enum TranslationItemSource /// The acutal translation content. /// ITranslationItemContent Content { get; set; } - - /// - /// The source of the item. - /// - TranslationItemSource Source { get; } - - /// - /// If an item is originally present in the or the - /// , but also contained in either the - /// or the with a different - /// value the item counts as overwritten. - /// - bool Overwritten { get; } -} \ No newline at end of file +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs index 1aef72cd..40011ee0 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemContent.cs @@ -8,10 +8,10 @@ public interface ITranslationItemContent /// /// The string key, usually mapped to /// - string Key { get; set; } + string Key { get; init; } /// /// The string value, usually mapped to /// - string? Value { get; set; } -} \ No newline at end of file + string? Value { get; init; } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs index 418eb0c1..3c8a78c6 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs @@ -6,8 +6,8 @@ namespace PG.StarWarsGame.Components.Localisation.Repository.Content; public class OrderedTranslationItem : TranslationItemBase { /// - protected OrderedTranslationItem(OrderedTranslationItemId itemId, TranslationItemContent content, - ITranslationItem.TranslationItemSource source, bool overwritten) : base(itemId, content, source, overwritten) + protected OrderedTranslationItem(OrderedTranslationItemId itemId, TranslationItemContent content) : base(itemId, + content) { } @@ -16,28 +16,20 @@ protected OrderedTranslationItem(OrderedTranslationItemId itemId, TranslationIte /// /// /// - /// - /// /// - public static OrderedTranslationItem Of(OrderedTranslationItemId itemId, TranslationItemContent content, - ITranslationItem.TranslationItemSource source, bool overwritten) + public static OrderedTranslationItem Of(OrderedTranslationItemId itemId, TranslationItemContent content) { - return new OrderedTranslationItem(itemId, content, source, overwritten); + return new OrderedTranslationItem(itemId, content); } /// /// Convenience method to create a new /// /// - /// - /// /// /// - public static OrderedTranslationItem Of(TranslationItemContent content, - ITranslationItem.TranslationItemSource source = ITranslationItem.TranslationItemSource.Mod, - bool overwritten = false) + public static OrderedTranslationItem Of(TranslationItemContent content) { - return Of(OrderedTranslationItemId.Of(content.Key) ?? throw new InvalidOperationException(), content, source, - overwritten); + return Of(OrderedTranslationItemId.Of(content.Key) ?? throw new InvalidOperationException(), content); } -} \ No newline at end of file +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs index ebd76bce..0e0334db 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs @@ -8,15 +8,10 @@ public abstract class TranslationItemBase : ITranslationItem /// /// /// - /// - /// - protected TranslationItemBase(ITranslationItemId itemId, TranslationItemContent content, - ITranslationItem.TranslationItemSource source, bool overwritten) + protected TranslationItemBase(ITranslationItemId itemId, TranslationItemContent content) { ItemId = itemId; Content = content; - Source = source; - Overwritten = overwritten; } /// @@ -26,8 +21,8 @@ protected TranslationItemBase(ITranslationItemId itemId, TranslationItemContent public ITranslationItemId ItemId { get; } /// - public ITranslationItem.TranslationItemSource Source { get; } - - /// - public bool Overwritten { get; } -} \ No newline at end of file + public override string ToString() + { + return $"{GetType().Name}[{ItemId}: {Content}]"; + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemContent.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemContent.cs index c7d40a5c..aa7505f8 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemContent.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemContent.cs @@ -4,8 +4,8 @@ namespace PG.StarWarsGame.Components.Localisation.Repository.Content; public record TranslationItemContent : ITranslationItemContent { /// - public required string Key { get; set; } + public required string Key { get; init; } /// - public string? Value { get; set; } -} \ No newline at end of file + public string? Value { get; init; } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs index b8c367cb..1059edcc 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/ITranslationRepository.cs @@ -18,18 +18,23 @@ public interface ITranslationRepository enum MergeStrategy { /// - /// If the already exists, the new items will replace the existing items. + /// If the already exists, the new items will replace ALL existing + /// items. This is equivalent to calling , + /// and + /// for the new items. /// Replace, /// - /// If the already exists, the will be merged, + /// If the already exists, the will be updated, /// otherwise added. /// MergeByKey, /// - /// The s will be appended if possible. + /// The s will be appended if possible. If there is a key already present in the + /// repository, the associated item will NOT be updated, use if you want to update + /// exiting items. /// Append } @@ -37,12 +42,12 @@ enum MergeStrategy /// /// Readonly dictionary of all contained s separated by language. /// - IReadOnlyDictionary> Content { get; } + IReadOnlyDictionary> Content { get; } /// /// Readonly collection of all contained s. /// - IReadOnlyCollection GetTranslationItemsByLanguage(IAlamoLanguageDefinition language); + IReadOnlyCollection GetTranslationItemsByLanguage(IAlamoLanguageDefinition language); /// /// Adds a new language to the repository. @@ -100,7 +105,7 @@ bool AddOrUpdateLanguage(IAlamoLanguageDefinition language, ICollection - /// Applies a diff to th repository. Missing items in languages will be inserted, items not present in the master + /// Applies a diff to the repository. Missing items in languages will be inserted, items not present in the master /// language will be removed. /// /// diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs index e02bc800..27356f1d 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs @@ -2,6 +2,7 @@ // 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.Reflection; using PG.Commons.Services; @@ -56,4 +57,15 @@ public IAlamoLanguageDefinition GetDefaultLanguageDefinition() : "No default language has been defined."); return (IAlamoLanguageDefinition)Activator.CreateInstance(languageDefinitions[0]); } + + /// + public IDictionary CreateLanguageIdentifierMapping() + { + return AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assemblyTypes => assemblyTypes.GetTypes()) + .Where(assemblyType => typeof(IAlamoLanguageDefinition).IsAssignableFrom(assemblyType) && + assemblyType is { IsClass: true, IsAbstract: false }) + .Select(t => (IAlamoLanguageDefinition)Activator.CreateInstance(t)) + .ToDictionary(d => d.LanguageIdentifier, d => d); + } } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs index bf7695c0..63768128 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs @@ -1,6 +1,7 @@ // 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.Components.Localisation.Languages; namespace PG.StarWarsGame.Components.Localisation.Services; @@ -38,4 +39,11 @@ public interface IAlamoLanguageSupportService /// /// IAlamoLanguageDefinition GetDefaultLanguageDefinition(); + + /// + /// Creates a lookup map for s by their + /// + /// + /// + IDictionary CreateLanguageIdentifierMapping(); } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/ITranslationRepositoryService.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/ITranslationRepositoryService.cs index f780f6ad..1e15b3d5 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/ITranslationRepositoryService.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/ITranslationRepositoryService.cs @@ -12,20 +12,24 @@ namespace PG.StarWarsGame.Components.Localisation.Services; public interface ITranslationRepositoryService { /// - /// Loads a given file or set of files as a singular + /// Loads a given file or set of files as a singular based on the + /// /// - /// - /// + /// + /// + /// + /// /// - ITranslationRepository LoadAs(IInputStrategy inputStrategy, params string[] filePaths); + void LoadAs(IImportHandler handler, T strategy, ITranslationRepository repository) + where T : IInputStrategy; // TODO: Handler-factory based on the strategy. /// - /// Stores a as a set of files defined in the in a - /// given base directory. + /// Stores a as defined in the /// - /// - /// + /// + /// /// - /// - void StoreAs(string baseFilePath, IOutputStrategy outputStrategy, ITranslationRepository repository); -} \ No newline at end of file + /// + void StoreAs(IExportHandler handler, T strategy, ITranslationRepository repository) + where T : IOutputStrategy; // TODO: Handler-factory based on the strategy. +} diff --git a/PG.Testing/TestUtility.cs b/PG.Testing/TestUtility.cs index d1685b25..e4bb5e53 100644 --- a/PG.Testing/TestUtility.cs +++ b/PG.Testing/TestUtility.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Abstractions; using Xunit; namespace PG.Testing; @@ -11,14 +12,11 @@ namespace PG.Testing; public static class TestUtility { private static readonly Random RandomGenerator = new(); - + public static void AssertAreBinaryEquivalent(IReadOnlyList expected, IReadOnlyList actual) { Assert.Equal(expected.Count, actual.Count); - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i], actual[i]); - } + for (var i = 0; i < expected.Count; i++) Assert.Equal(expected[i], actual[i]); } public static string GetRandomStringOfLength(int length) @@ -26,10 +24,7 @@ public static string GetRandomStringOfLength(int length) const string allowedChars = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789!@$?_-"; var chars = new char[length]; - for (var i = 0; i < length; i++) - { - chars[i] = allowedChars[RandomGenerator.Next(0, allowedChars.Length)]; - } + for (var i = 0; i < length; i++) chars[i] = allowedChars[RandomGenerator.Next(0, allowedChars.Length)]; return new string(chars); } @@ -49,4 +44,10 @@ public static byte[] GetEmbeddedResourceAsByteArray(Type type, string path) stream.CopyTo(ms); return ms.ToArray(); } -} \ No newline at end of file + + public static void CopyEmbeddedResourceToMockFilesystem(Type type, string resourcePath, string mockFilePath, + IFileSystem fileSystem) + { + fileSystem.File.WriteAllBytes(mockFilePath, GetEmbeddedResourceAsByteArray(type, resourcePath)); + } +} From 26db9187f1e478cebedb852e5e50ed8cd256857e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Thu, 24 Oct 2024 12:32:37 +0200 Subject: [PATCH 12/15] UnitTests: XmlImportHandler --- PG.Commons/PG.Commons/Data/IId.cs | 6 -- PG.Commons/PG.Commons/Data/IdBase.cs | 13 +-- PG.Commons/PG.Commons/Data/RootIdBase.cs | 6 +- .../IO/Xml/XmlImportHandlerTest.cs | 93 ++++++++++++++++++- .../IO/Xml/XmlInputStrategyTest.cs | 36 +++++++ ...rsGame.Components.Localisation.Test.csproj | 3 + .../translation_manifest_invalid_key.xml | 17 ++++ .../translation_manifest_invalid_language.xml | 13 +++ ...tion_manifest_key_without_translations.xml | 20 ++++ .../IO/IInputStrategy.cs | 21 +++++ .../IO/Xml/XmlImportHandler.cs | 26 ++++-- .../IO/Xml/XmlInputStrategy.cs | 11 ++- 12 files changed, 237 insertions(+), 28 deletions(-) create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlInputStrategyTest.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_invalid_key.xml create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_invalid_language.xml create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_key_without_translations.xml diff --git a/PG.Commons/PG.Commons/Data/IId.cs b/PG.Commons/PG.Commons/Data/IId.cs index 244700ff..9909253a 100644 --- a/PG.Commons/PG.Commons/Data/IId.cs +++ b/PG.Commons/PG.Commons/Data/IId.cs @@ -11,10 +11,4 @@ public interface IId : IEquatable /// The arity of the ID. /// int Arity { get; } - - /// - /// The string representation of the ID - /// - /// - string Unwrap(); } diff --git a/PG.Commons/PG.Commons/Data/IdBase.cs b/PG.Commons/PG.Commons/Data/IdBase.cs index c84563ce..13b4c56e 100644 --- a/PG.Commons/PG.Commons/Data/IdBase.cs +++ b/PG.Commons/PG.Commons/Data/IdBase.cs @@ -26,14 +26,17 @@ protected IdBase(params object[] components) /// public bool Equals(IId? other) { - return other != null && Arity == other.Arity && GetHashCode().Equals(other.GetHashCode()); + return other != null + && GetType() == other.GetType() + && Arity == other.Arity + && GetHashCode().Equals(other.GetHashCode()); } /// public int Arity => GetConfiguredArity(); /// - public string Unwrap() + public override string ToString() { var b = new StringBuilder(); b.Append(GetType().Name).Append('['); @@ -43,12 +46,6 @@ public string Unwrap() return b.ToString(); } - /// - public override string ToString() - { - return Unwrap(); - } - /// /// Convenience method to access components in a type-safe manner. /// diff --git a/PG.Commons/PG.Commons/Data/RootIdBase.cs b/PG.Commons/PG.Commons/Data/RootIdBase.cs index 4624e3b0..4d8fa030 100644 --- a/PG.Commons/PG.Commons/Data/RootIdBase.cs +++ b/PG.Commons/PG.Commons/Data/RootIdBase.cs @@ -13,7 +13,7 @@ protected RootIdBase(T rawId) : base(rawId) /// public int CompareTo(RootIdBase? other) { - return other == null ? 1 : Raw().CompareTo(other.Raw()); + return other == null ? 1 : Unwrap().CompareTo(other.Unwrap()); } /// @@ -33,8 +33,8 @@ protected override bool IsNullId() /// /// /// - public T Raw() + public T Unwrap() { return Components[0] as T ?? throw new InvalidOperationException(); } -} \ No newline at end of file +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs index d520907f..22ceafba 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs @@ -1,14 +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. +using System.IO; using System.IO.Abstractions; using System.Linq; using Microsoft.Extensions.DependencyInjection; using PG.Commons.Extensibility; using PG.StarWarsGame.Components.Localisation.IO; using PG.StarWarsGame.Components.Localisation.IO.Xml; +using PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; using PG.StarWarsGame.Components.Localisation.Repository.Builtin; using PG.StarWarsGame.Components.Localisation.Repository.Content; +using PG.StarWarsGame.Files.DAT.Services.Builder.Validation; using PG.Testing; using Testably.Abstractions.Testing; using Xunit; @@ -17,9 +20,12 @@ namespace PG.StarWarsGame.Components.Localisation.Test.IO.Xml; public class XmlImportHandlerTest { - private static readonly string EmptyXml = "empty.xml"; - private static readonly string MultiKeysXml = "multi_keys.xml"; - private static readonly string SingleKeyXml = "single_key.xml"; + private const string EmptyXml = "empty.xml"; + private const string MultiKeysXml = "multi_keys.xml"; + private const string SingleKeyXml = "single_key.xml"; + private const string InvalidLanguage = "invalid_language.xml"; + private const string InvalidKey = "invalid_key.xml"; + private const string KeyWithoutTranslations = "key_without_translations.xml"; private readonly MockFileSystem _fileSystem = new(); private readonly IImportHandler _handler; @@ -28,6 +34,7 @@ public XmlImportHandlerTest() { var sc = new ServiceCollection(); sc.AddSingleton(_fileSystem); + sc.AddSingleton(new EmpireAtWarKeyValidator()); sc.CollectPgServiceContributions(); _handler = sc.BuildServiceProvider().GetRequiredService>(); } @@ -85,8 +92,88 @@ public void Test_Import_WithMultiKeyXml() var repository = new InMemoryOrderedTranslationRepository(); _handler.Import(new XmlInputStrategy(filePath), repository); Assert.Equal(5, repository.Content.Keys.Count()); + + Assert.NotNull(repository.Content[new EnglishAlamoLanguageDefinition()]); + Assert.NotNull(repository.Content[new GermanAlamoLanguageDefinition()]); + Assert.NotNull(repository.Content[new FrenchAlamoLanguageDefinition()]); + Assert.NotNull(repository.Content[new SpanishAlamoLanguageDefinition()]); + Assert.NotNull(repository.Content[new ItalianAlamoLanguageDefinition()]); + Assert.Equal(5, repository.Content.Values.Count()); foreach (var kvp in repository.Content) Assert.Equal(2, kvp.Value.Count); } + + [Fact] + public void Test_Import_WithInvalidLanguageLenient_IsSkipped() + { + var filePath = CreateMockFilePath("./invalid_language_test", InvalidLanguage); + var resourcePath = GetResourcePath(InvalidLanguage); + TestUtility.CopyEmbeddedResourceToMockFilesystem(typeof(XmlImportHandlerTest), resourcePath, filePath, + _fileSystem); + var repository = new InMemoryOrderedTranslationRepository(); + _handler.Import(new XmlInputStrategy(filePath), repository); + Assert.Empty(repository.Content); + Assert.Empty(repository.Content.Keys); + Assert.Empty(repository.Content.Values); + } + + [Fact] + public void Test_Import_WithInvalidLanguageStrict_ThrowsInvalidDataException() + { + var filePath = CreateMockFilePath("./invalid_language_test", InvalidLanguage); + var resourcePath = GetResourcePath(InvalidLanguage); + TestUtility.CopyEmbeddedResourceToMockFilesystem(typeof(XmlImportHandlerTest), resourcePath, filePath, + _fileSystem); + var repository = new InMemoryOrderedTranslationRepository(); + Assert.Throws(() => + _handler.Import(new XmlInputStrategy(filePath, IInputStrategy.ValidationLevel.Strict), repository)); + } + + [Fact] + public void Test_Import_WithInvalidKeyLenient_IsSkipped() + { + var filePath = CreateMockFilePath("./invalid_key_test", InvalidKey); + var resourcePath = GetResourcePath(InvalidKey); + TestUtility.CopyEmbeddedResourceToMockFilesystem(typeof(XmlImportHandlerTest), resourcePath, filePath, + _fileSystem); + var repository = new InMemoryOrderedTranslationRepository(); + _handler.Import(new XmlInputStrategy(filePath), repository); + Assert.Empty(repository.Content); + Assert.Empty(repository.Content.Keys); + Assert.Empty(repository.Content.Values); + } + + [Fact] + public void Test_Import_WithInvalidKeyLenient_ThrowsInvalidDataException() + { + var filePath = CreateMockFilePath("./invalid_key_test", InvalidKey); + var resourcePath = GetResourcePath(InvalidKey); + TestUtility.CopyEmbeddedResourceToMockFilesystem(typeof(XmlImportHandlerTest), resourcePath, filePath, + _fileSystem); + var repository = new InMemoryOrderedTranslationRepository(); + Assert.Throws(() => + _handler.Import(new XmlInputStrategy(filePath, IInputStrategy.ValidationLevel.Strict), repository)); + } + + [Fact] + public void Test_Import_KeyWithoutTranslations_IsSkipped() + { + var filePath = CreateMockFilePath("./key_without_translations_test", KeyWithoutTranslations); + var resourcePath = GetResourcePath(KeyWithoutTranslations); + TestUtility.CopyEmbeddedResourceToMockFilesystem(typeof(XmlImportHandlerTest), resourcePath, filePath, + _fileSystem); + var repository = new InMemoryOrderedTranslationRepository(); + _handler.Import(new XmlInputStrategy(filePath), repository); + Assert.Equal(5, repository.Content.Keys.Count()); + Assert.Equal(5, repository.Content.Values.Count()); + foreach (var kvp in repository.Content) + { + Assert.Single(kvp.Value); + Assert.NotNull(kvp.Value.First()); + Assert.True(OrderedTranslationItemId.Of("TEST_KEY_01")?.Equals(kvp.Value.First().ItemId)); + Assert.Equal(new TranslationItemContent { Key = "TEST_KEY_01", Value = "Test text for key TEST_KEY_01" }, + kvp.Value.First().Content); + } + } } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlInputStrategyTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlInputStrategyTest.cs new file mode 100644 index 00000000..20edc4c4 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlInputStrategyTest.cs @@ -0,0 +1,36 @@ +// 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.Components.Localisation.IO; +using PG.StarWarsGame.Components.Localisation.IO.Xml; +using Xunit; + +namespace PG.StarWarsGame.Components.Localisation.Test.IO.Xml; + +public class XmlInputStrategyTest +{ + [Fact] + public void Test_XmlInputStrategy_InvalidAttributesThrowOnAccess() + { + var strategy = new XmlInputStrategy("test.xml", IInputStrategy.ValidationLevel.Strict); + Assert.Throws(() => strategy.BaseDirectory); + Assert.Throws(() => strategy.FileFilter); + } + + [Fact] + public void Test_XmlInputStrategy_StaticValuesReturnExpectedAttributes() + { + var strategy = new XmlInputStrategy("test.xml", IInputStrategy.ValidationLevel.Strict); + Assert.Equal(IInputStrategy.FileImportGrouping.Single, strategy.ImportGrouping); + Assert.Equal(strategy.FilePath, strategy.FilePaths.First()); + } + + [Fact] + public void Test_XmlInputStrategy_MissingFilePathThrowsIllegalArgumentException() + { + Assert.Throws(() => new XmlInputStrategy(null)); + Assert.Throws(() => new XmlInputStrategy(string.Empty)); + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj index 670c5b23..1632972e 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/PG.StarWarsGame.Components.Localisation.Test.csproj @@ -36,6 +36,9 @@ + + + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_invalid_key.xml b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_invalid_key.xml new file mode 100644 index 00000000..9e20f174 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_invalid_key.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_invalid_language.xml b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_invalid_language.xml new file mode 100644 index 00000000..db27fc63 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_invalid_language.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_key_without_translations.xml b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_key_without_translations.xml new file mode 100644 index 00000000..31dbaaad --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Resources/IO/Xml/v1/Serializable/translation_manifest_key_without_translations.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs index b1f8d9d4..db84dfa6 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IInputStrategy.cs @@ -36,11 +36,32 @@ enum FileImportGrouping BaseDirectory } + /// + /// The validation level used against the provided source. + /// + enum ValidationLevel + { + /// + /// Invalid entires are skipped. + /// + Lenient, + + /// + /// Invalid enrties throw an exception. + /// + Strict + } + /// /// The /// FileImportGrouping ImportGrouping { get; } + /// + /// The validation level used for the source files. + /// + ValidationLevel Validation { get; } + /// /// An optional file filter following the established FileDialog.Filter pattern. /// For more info check diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlImportHandler.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlImportHandler.cs index 45c6b381..bd634e99 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlImportHandler.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlImportHandler.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -14,6 +15,7 @@ using PG.StarWarsGame.Components.Localisation.Repository; using PG.StarWarsGame.Components.Localisation.Repository.Content; using PG.StarWarsGame.Components.Localisation.Services; +using PG.StarWarsGame.Files.DAT.Services.Builder.Validation; namespace PG.StarWarsGame.Components.Localisation.IO.Xml; @@ -34,7 +36,7 @@ public void Import(XmlInputStrategy strategy, ITranslationRepository targetRepos ?.DeSerializeObjectFromDisc(strategy.FilePath); if (data == null) return; - FromXml(data, targetRepository); + FromXml(data, targetRepository, strategy.Validation); } /// @@ -42,8 +44,10 @@ public void Import(XmlInputStrategy strategy, ITranslationRepository targetRepos /// /// /// + /// /// - protected void FromXml(LocalisationData data, ITranslationRepository targetRepository) + protected void FromXml(LocalisationData data, ITranslationRepository targetRepository, + IInputStrategy.ValidationLevel validationLevel) { var languageMap = Services.GetService()?.CreateLanguageIdentifierMapping() ?? @@ -58,9 +62,14 @@ protected void FromXml(LocalisationData data, ITranslationRepository targetRepos foreach (var loc in data.LocalisationHolder) { var id = OrderedTranslationItemId.Of(loc.Key); - if (id == null) + if (id == null || !Services.GetService()!.Validate(loc.Key)) { - Logger.LogWarning("Could not create a valid {} from {}. Skipping.", nameof(OrderedTranslationItemId), + if (validationLevel != IInputStrategy.ValidationLevel.Lenient) + throw new InvalidDataException( + $"Invalid data found: Could not create a valid {nameof(OrderedTranslationItemId)} from {loc.Key}"); + + Logger.LogWarning("Could not create a valid {} from {}. Skipping.", + nameof(OrderedTranslationItemId), loc.Key); continue; } @@ -74,16 +83,19 @@ protected void FromXml(LocalisationData data, ITranslationRepository targetRepos continue; } - var lang = languageMap[t.Language]; - if (lang == null) + if (!languageMap.TryGetValue(t.Language, out var lang)) { + if (validationLevel != IInputStrategy.ValidationLevel.Lenient) + throw new InvalidDataException( + $"Invalid data found: Could not determine the {nameof(IAlamoLanguageDefinition)} for {t.Language}"); + Logger.LogWarning("Could not determine the language for {}. Skipping.", t.Language); continue; } var c = new TranslationItemContent { - Key = id.Raw(), + Key = id.Unwrap(), Value = t.Text }; contentMap[lang].Add(OrderedTranslationItem.Of(c)); diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs index 58871768..8809c5a6 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs @@ -17,9 +17,15 @@ namespace PG.StarWarsGame.Components.Localisation.IO.Xml; /// .ctor /// /// - public XmlInputStrategy(string filePath) + /// + public XmlInputStrategy(string filePath, + IInputStrategy.ValidationLevel validation = IInputStrategy.ValidationLevel.Lenient) { + if (string.IsNullOrWhiteSpace(filePath)) + throw new ArgumentException($"'{filePath}' is not a valid file path.", nameof(filePath)); + FilePaths = new HashSet { filePath }; + Validation = validation; } /// @@ -30,6 +36,9 @@ public XmlInputStrategy(string filePath) /// public IInputStrategy.FileImportGrouping ImportGrouping => IInputStrategy.FileImportGrouping.Single; + /// + public IInputStrategy.ValidationLevel Validation { get; } + /// public string FileFilter => throw new InvalidOperationException( $"{nameof(XmlInputStrategy)}.{nameof(FileFilter)} is not supported for {nameof(ImportGrouping)}={nameof(IInputStrategy.FileImportGrouping.Single)}"); From 0bb1d1fc2e36afde42b7a35ca383c254c23a9222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Thu, 24 Oct 2024 12:34:26 +0200 Subject: [PATCH 13/15] Fix Test Naming --- .../IO/Xml/XmlImportHandlerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs index 22ceafba..90469db3 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlImportHandlerTest.cs @@ -145,7 +145,7 @@ public void Test_Import_WithInvalidKeyLenient_IsSkipped() } [Fact] - public void Test_Import_WithInvalidKeyLenient_ThrowsInvalidDataException() + public void Test_Import_WithInvalidKeyStrict_ThrowsInvalidDataException() { var filePath = CreateMockFilePath("./invalid_key_test", InvalidKey); var resourcePath = GetResourcePath(InvalidKey); From 281b4d011c6f7ba980a559b44ffd64e414c44257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Fri, 25 Oct 2024 12:24:54 +0200 Subject: [PATCH 14/15] XML Export Tests --- PG.Commons/PG.Commons/Data/IdBase.cs | 3 +- PG.Commons/PG.Commons/Data/RootIdBase.cs | 2 +- .../IO/Xml/XmlExportHandlerTest.cs | 76 +++++++++++++++++++ .../IO/Xml/XmlInputStrategyTest.cs | 2 +- .../IO/Xml/XmlOutputStrategyTest.cs | 22 ++++++ .../IO/IOutputStrategy.cs | 4 +- .../IO/Xml/XmlExportHandler.cs | 25 +++--- .../IO/Xml/XmlInputStrategy.cs | 2 + .../IO/Xml/XmlOutputStrategy.cs | 26 ++++++- .../LocalisationServiceContribution.cs | 7 +- .../Content/OrderedTranslationItemId.cs | 4 +- 11 files changed, 152 insertions(+), 21 deletions(-) create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlExportHandlerTest.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlOutputStrategyTest.cs diff --git a/PG.Commons/PG.Commons/Data/IdBase.cs b/PG.Commons/PG.Commons/Data/IdBase.cs index 13b4c56e..d256186d 100644 --- a/PG.Commons/PG.Commons/Data/IdBase.cs +++ b/PG.Commons/PG.Commons/Data/IdBase.cs @@ -6,11 +6,12 @@ namespace PG.Commons.Data; /// -public abstract class IdBase : IId +public abstract record IdBase : IId { /// /// The ID components. /// + // ReSharper disable once TypeWithSuspiciousEqualityIsUsedInRecord.Global protected readonly object?[] Components; /// diff --git a/PG.Commons/PG.Commons/Data/RootIdBase.cs b/PG.Commons/PG.Commons/Data/RootIdBase.cs index 4d8fa030..71238005 100644 --- a/PG.Commons/PG.Commons/Data/RootIdBase.cs +++ b/PG.Commons/PG.Commons/Data/RootIdBase.cs @@ -3,7 +3,7 @@ namespace PG.Commons.Data; /// -public abstract class RootIdBase : IdBase, IComparable> where T : class, IComparable +public abstract record RootIdBase : IdBase, IComparable> where T : class, IComparable { /// protected RootIdBase(T rawId) : base(rawId) diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlExportHandlerTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlExportHandlerTest.cs new file mode 100644 index 00000000..5bcaba8f --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlExportHandlerTest.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.IO; +using System.IO.Abstractions; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Extensibility; +using PG.StarWarsGame.Components.Localisation.IO; +using PG.StarWarsGame.Components.Localisation.IO.Xml; +using PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; +using PG.StarWarsGame.Components.Localisation.Repository.Builtin; +using PG.StarWarsGame.Components.Localisation.Repository.Content; +using Testably.Abstractions.Testing; +using Xunit; + +namespace PG.StarWarsGame.Components.Localisation.Test.IO.Xml; + +public class XmlExportHandlerTest +{ + private readonly MockFileSystem _fileSystem = new(); + private readonly IExportHandler _handler; + + public XmlExportHandlerTest() + { + var sc = new ServiceCollection(); + sc.AddSingleton(_fileSystem); + sc.CollectPgServiceContributions(); + _handler = sc.BuildServiceProvider().GetRequiredService>(); + } + + [Fact] + public void Test_Export_WithEmptyRepository_NoFileCreated() + { + var repository = new InMemoryOrderedTranslationRepository(); + Assert.Empty(repository.Content); + const string testExportEmpty = "./test_export_empty"; + const string testExportEmptyFile = "EMPTY_EXPORT"; + var strategy = new XmlOutputStrategy(_fileSystem.DirectoryInfo.New(testExportEmpty), testExportEmptyFile); + _handler.Export(strategy, repository); + var info = _fileSystem.FileInfo.New(strategy.FilePath); + Assert.False(info.Exists); + } + + [Fact] + public void Test_Export_WithRepositoryWithoutContentBesidesLanguages_NoFileCreated() + { + var repository = new InMemoryOrderedTranslationRepository(); + repository.AddLanguage(new EnglishAlamoLanguageDefinition()); + repository.AddLanguage(new GermanAlamoLanguageDefinition()); + const string testExportEmpty = "./test_export_languages_only"; + const string testExportEmptyFile = "EMPTY_LANGUAGES"; + var strategy = new XmlOutputStrategy(_fileSystem.DirectoryInfo.New(testExportEmpty), testExportEmptyFile); + _handler.Export(strategy, repository); + var info = _fileSystem.FileInfo.New(strategy.FilePath); + Assert.False(info.Exists); + } + + [Fact] + public void Test_Export_WithRepositoryWithSingleEntry_FileCreated() + { + var repository = new InMemoryOrderedTranslationRepository(); + repository.AddLanguage(new EnglishAlamoLanguageDefinition()); + repository.AddOrUpdateTranslationItem(new EnglishAlamoLanguageDefinition(), OrderedTranslationItem.Of( + new TranslationItemContent + { + Key = "TEST_00", + Value = "English translation for TEST_00" + })); + const string testExportEmpty = "./test_export_languages_only"; + const string testExportEmptyFile = "EMPTY_LANGUAGES"; + var strategy = new XmlOutputStrategy(_fileSystem.DirectoryInfo.New(testExportEmpty), testExportEmptyFile); + _handler.Export(strategy, repository); + var info = _fileSystem.FileInfo.New(strategy.FilePath); + Assert.True(info.Exists); + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlInputStrategyTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlInputStrategyTest.cs index 20edc4c4..fe228bd0 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlInputStrategyTest.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlInputStrategyTest.cs @@ -28,7 +28,7 @@ public void Test_XmlInputStrategy_StaticValuesReturnExpectedAttributes() } [Fact] - public void Test_XmlInputStrategy_MissingFilePathThrowsIllegalArgumentException() + public void Test_XmlInputStrategy_InvalidArgumentsThrowIllegalArgumentException() { Assert.Throws(() => new XmlInputStrategy(null)); Assert.Throws(() => new XmlInputStrategy(string.Empty)); diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlOutputStrategyTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlOutputStrategyTest.cs new file mode 100644 index 00000000..f2548abc --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlOutputStrategyTest.cs @@ -0,0 +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 System; +using PG.StarWarsGame.Components.Localisation.IO.Xml; +using Testably.Abstractions.Testing; +using Xunit; + +namespace PG.StarWarsGame.Components.Localisation.Test.IO.Xml; + +public class XmlOutputStrategyTest +{ + private readonly MockFileSystem _fileSystem = new(); + + [Fact] + public void Test_XmlOutputStrategy_InvalidArgumentsThrowIllegalArgumentException() + { + Assert.Throws(() => new XmlOutputStrategy(_fileSystem.DirectoryInfo.New("./"), null)); + Assert.Throws(() => + new XmlOutputStrategy(_fileSystem.DirectoryInfo.New("./"), string.Empty)); + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.cs index 8da609b8..7d5e832c 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/IOutputStrategy.cs @@ -1,7 +1,7 @@ // 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 System.IO.Abstractions; namespace PG.StarWarsGame.Components.Localisation.IO; @@ -48,5 +48,5 @@ enum FileExportGrouping /// /// The export base path. /// - DirectoryInfo ExportBasePath { get; } + IDirectoryInfo ExportBasePath { get; } } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlExportHandler.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlExportHandler.cs index a364f010..cdb5b933 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlExportHandler.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlExportHandler.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using PG.Commons.Data.Serialization; using PG.Commons.Services; using PG.StarWarsGame.Components.Localisation.IO.Xml.Serializable.v1; @@ -25,9 +26,16 @@ public XmlExportHandler(IServiceProvider services) : base(services) /// public void Export(XmlOutputStrategy strategy, ITranslationRepository repository) { + if (!repository.Content.Any()) return; FileSystem.Directory.CreateDirectory(strategy.ExportBasePath.FullName); var xml = ToXml(repository); - Services.GetService()?.SerializeObjectAndStoreToDisc(strategy.FileName, xml); + if (xml.LocalisationHolder.Count == 0) + { + Logger.LogWarning("Empty xml export file, skipping export: {}", xml); + return; + } + + Services.GetService()?.SerializeObjectAndStoreToDisc(strategy.FilePath, xml); } /// @@ -45,16 +53,13 @@ protected LocalisationData ToXml(ITranslationRepository repository) { var loc = new Serializable.v1.Localisation { - Key = key.ToUpper(), + Key = key.Unwrap().ToUpper(), TranslationData = new TranslationData() }; foreach (var t in languages.Select(lang => new Translation { Language = lang.LanguageIdentifier.ToUpper(), - Text = repository.GetTranslationItem(lang, - OrderedTranslationItemId.Of(key) ?? - throw new InvalidOperationException( - $"No valid {nameof(OrderedTranslationItemId)} could be created from {key}")) + Text = repository.GetTranslationItem(lang, key) .Content .Value })) @@ -71,12 +76,14 @@ protected LocalisationData ToXml(ITranslationRepository repository) /// /// /// - protected ISet GetKeys(ITranslationRepository repository) + protected ISet GetKeys(ITranslationRepository repository) { - var keySet = new HashSet(); + var keySet = new HashSet(); foreach (var items in repository.Content.Values) foreach (var item in items) - keySet.Add(item.Content.Key); + keySet.Add(item.ItemId as OrderedTranslationItemId ?? + throw new InvalidOperationException( + $"No valid {nameof(OrderedTranslationItemId)} could be created from {item.ItemId}")); return keySet; } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs index 8809c5a6..71c573d2 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlInputStrategy.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -18,6 +19,7 @@ namespace PG.StarWarsGame.Components.Localisation.IO.Xml; /// /// /// + [ExcludeFromCodeCoverage] public XmlInputStrategy(string filePath, IInputStrategy.ValidationLevel validation = IInputStrategy.ValidationLevel.Lenient) { diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlOutputStrategy.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlOutputStrategy.cs index 782a89e4..50608e3e 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlOutputStrategy.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/IO/Xml/XmlOutputStrategy.cs @@ -1,29 +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 System; +using System.Diagnostics.CodeAnalysis; using System.IO; +using System.IO.Abstractions; namespace PG.StarWarsGame.Components.Localisation.IO.Xml; /// /// The XML output strategy. /// +[ExcludeFromCodeCoverage] public readonly record struct XmlOutputStrategy : IOutputStrategy { + /// + /// .ctor + /// + /// + /// + /// + public XmlOutputStrategy(IDirectoryInfo exportBasePath, string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName)); + FileName = fileName; + ExportBasePath = exportBasePath; + } + /// /// Convenience accessor for the full file path as string. /// public string FilePath => $"{Path.Combine(ExportBasePath.FullName, FileName)}.{Extension}"; /// - public IOutputStrategy.FileExportGrouping ExportGrouping => IOutputStrategy.FileExportGrouping.Single; + public IDirectoryInfo ExportBasePath { get; } /// - public string Extension => "xml"; + public IOutputStrategy.FileExportGrouping ExportGrouping => IOutputStrategy.FileExportGrouping.Single; /// - public required string FileName { get; init; } + public string Extension => "xml"; /// - public required DirectoryInfo ExportBasePath { get; init; } + public string FileName { get; } } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs index db246d9c..75e31e80 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/LocalisationServiceContribution.cs @@ -1,9 +1,11 @@ using Microsoft.Extensions.DependencyInjection; using PG.Commons.Attributes; +using PG.Commons.Data.Serialization; using PG.Commons.Extensibility; using PG.StarWarsGame.Components.Localisation.IO; using PG.StarWarsGame.Components.Localisation.IO.Xml; using PG.StarWarsGame.Components.Localisation.Services; +using PG.StarWarsGame.Files.DAT.Services.Builder.Validation; namespace PG.StarWarsGame.Components.Localisation; @@ -15,7 +17,10 @@ public class LocalisationServiceContribution : IServiceContribution public void ContributeServices(IServiceCollection serviceCollection) { serviceCollection + .AddSingleton(_ => new EmpireAtWarKeyValidator()) + .AddSingleton(sp => new XmlSerializationSupportService(sp)) .AddSingleton(sp => new AlamoLanguageSupportService(sp)) - .AddTransient>(sp => new XmlImportHandler(sp)); + .AddTransient>(sp => new XmlImportHandler(sp)) + .AddTransient>(sp => new XmlExportHandler(sp)); } } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs index be271d1a..8c75a5f0 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs @@ -5,7 +5,7 @@ namespace PG.StarWarsGame.Components.Localisation.Repository.Content; /// /// Item ID for ordered translation items. Equivalent to the raw string key from a sorted DAT file. /// -public class OrderedTranslationItemId : RootIdBase, ITranslationItemId +public record OrderedTranslationItemId : RootIdBase, ITranslationItemId { /// protected OrderedTranslationItemId(string rawId) : base(rawId) @@ -21,4 +21,4 @@ protected OrderedTranslationItemId(string rawId) : base(rawId) { return string.IsNullOrWhiteSpace(rawId) ? null : new OrderedTranslationItemId(rawId); } -} \ No newline at end of file +} From b58cb45e3acfb982a37b7efed2872e6919cac5e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Gr=C3=BCnwald?= Date: Wed, 7 May 2025 21:31:18 +0200 Subject: [PATCH 15/15] WIP --- .../PG.Commons.Test/Data/IIdTestBase.cs | 44 +++++++++++ .../Data/IIdTestCompletenessTestBase.cs | 48 +++++++++++ PG.Commons/PG.Commons/Data/IId.cs | 2 +- PG.Commons/PG.Commons/Data/IdBase.cs | 15 +++- PG.Commons/PG.Commons/Data/RootIdBase.cs | 2 +- .../Data/IIdCompletenessTest.cs | 15 ++++ .../Data/OrderedTranslationItemIdTest.cs | 40 ++++++++++ .../IO/Xml/XmlExportHandlerTest.cs | 19 +++-- ...nMemoryOrderedTranslationRepositoryTest.cs | 79 +++++++++++++++++++ .../InMemoryOrderedTranslationRepository.cs | 7 ++ .../Repository/Content/ITranslationItem.cs | 4 +- .../Repository/Content/ITranslationItemId.cs | 5 +- .../Content/OrderedTranslationItem.cs | 6 +- .../Content/OrderedTranslationItemId.cs | 16 +++- .../Repository/Content/TranslationItemBase.cs | 6 +- .../Services/AlamoLanguageSupportService.cs | 11 ++- .../Services/IAlamoLanguageSupportService.cs | 6 ++ 17 files changed, 298 insertions(+), 27 deletions(-) create mode 100644 PG.Commons/PG.Commons.Test/Data/IIdTestBase.cs create mode 100644 PG.Commons/PG.Commons.Test/Data/IIdTestCompletenessTestBase.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Data/IIdCompletenessTest.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Data/OrderedTranslationItemIdTest.cs create mode 100644 PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Repository/Builtin/InMemoryOrderedTranslationRepositoryTest.cs diff --git a/PG.Commons/PG.Commons.Test/Data/IIdTestBase.cs b/PG.Commons/PG.Commons.Test/Data/IIdTestBase.cs new file mode 100644 index 00000000..e8c10839 --- /dev/null +++ b/PG.Commons/PG.Commons.Test/Data/IIdTestBase.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 System.Linq; +using PG.Commons.Data; +using Xunit; + +namespace PG.Commons.Test.Data; + +// ReSharper disable once InconsistentNaming +public abstract class IIdTestBase where T : IId +{ + [Fact] + public void Test_NullIIdCreationReturnsNull() + { + foreach (var nullKeys in GetConfiguredNullIIds()) Assert.Null(nullKeys); + } + + [Fact] + public void Test_IIdBehavesAsExpectedWithHash() + { + var keyInitialisers = GetConfiguredValidIIdInitialisers(); + var dict = new Dictionary(); + foreach (var key in keyInitialisers.Select(keyInitialiser => CreateId(keyInitialiser))) + { + Assert.NotNull(key); + dict.Add(key, $"{key}: Hash: {key.GetHashCode()}"); + } + + foreach (var key in keyInitialisers.Select(keyInitialiser => CreateId(keyInitialiser))) + { + Assert.NotNull(key); + Assert.Contains(key, dict.Keys); + Assert.NotNull(dict[key]); + } + } + + protected abstract List GetConfiguredNullIIds(); + + protected abstract List GetConfiguredValidIIdInitialisers(); + + protected abstract T CreateId(object[] keyInitialiser); +} diff --git a/PG.Commons/PG.Commons.Test/Data/IIdTestCompletenessTestBase.cs b/PG.Commons/PG.Commons.Test/Data/IIdTestCompletenessTestBase.cs new file mode 100644 index 00000000..e41b8043 --- /dev/null +++ b/PG.Commons/PG.Commons.Test/Data/IIdTestCompletenessTestBase.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 System.Linq; +using System.Text; +using PG.Commons.Data; +using Xunit; + +namespace PG.Commons.Test.Data; + +// ReSharper disable once InconsistentNaming +public abstract class IIdTestCompletenessTestBase +{ + [Fact] + public void Test_IIdTestPresetForAllIIdsOfPackage() + { + var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes()).ToList(); + var iids = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assemblyTypes => assemblyTypes.GetTypes()) + .Where(assemblyType => typeof(IId).IsAssignableFrom(assemblyType) + && assemblyType is { IsClass: true, IsAbstract: false } + && assemblyType.Namespace != null + && assemblyType.Namespace.StartsWith(GetConfiguredNamespaceBase())) + .ToList(); + var present = new Dictionary(); + + + foreach (var iid in iids) + { + present.Add(iid, false); + if (types.Any(type => iid.Name + "Test" == type.Name)) present[iid] = true; + } + + var o = new StringBuilder().Append("The following IIds have no corresponding test:\n"); + var missing = false; + foreach (var kvp in present.Where(kvp => !kvp.Value)) + { + o.Append($"\t{kvp.Key.FullName}\n"); + missing = true; + } + + Assert.False(missing, o.ToString()); + } + + protected abstract string GetConfiguredNamespaceBase(); +} diff --git a/PG.Commons/PG.Commons/Data/IId.cs b/PG.Commons/PG.Commons/Data/IId.cs index 9909253a..d85c34d2 100644 --- a/PG.Commons/PG.Commons/Data/IId.cs +++ b/PG.Commons/PG.Commons/Data/IId.cs @@ -5,7 +5,7 @@ namespace PG.Commons.Data; /// /// A generic ID definition /// -public interface IId : IEquatable +public interface IId : IEquatable, IComparable, IComparable { /// /// The arity of the ID. diff --git a/PG.Commons/PG.Commons/Data/IdBase.cs b/PG.Commons/PG.Commons/Data/IdBase.cs index d256186d..cff090f4 100644 --- a/PG.Commons/PG.Commons/Data/IdBase.cs +++ b/PG.Commons/PG.Commons/Data/IdBase.cs @@ -6,7 +6,7 @@ namespace PG.Commons.Data; /// -public abstract record IdBase : IId +public abstract class IdBase : IId { /// /// The ID components. @@ -36,6 +36,19 @@ public bool Equals(IId? other) /// public int Arity => GetConfiguredArity(); + /// + public int CompareTo(object other) + { + if (other == null || GetType() != other.GetType()) throw new ArgumentException("Object must be of type IdBase"); + return CompareTo(other as IdBase); + } + + /// + public int CompareTo(IId? other) + { + return other == null ? 1 : GetHashCode().CompareTo(other.GetHashCode()); + } + /// public override string ToString() { diff --git a/PG.Commons/PG.Commons/Data/RootIdBase.cs b/PG.Commons/PG.Commons/Data/RootIdBase.cs index 71238005..4d8fa030 100644 --- a/PG.Commons/PG.Commons/Data/RootIdBase.cs +++ b/PG.Commons/PG.Commons/Data/RootIdBase.cs @@ -3,7 +3,7 @@ namespace PG.Commons.Data; /// -public abstract record RootIdBase : IdBase, IComparable> where T : class, IComparable +public abstract class RootIdBase : IdBase, IComparable> where T : class, IComparable { /// protected RootIdBase(T rawId) : base(rawId) diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Data/IIdCompletenessTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Data/IIdCompletenessTest.cs new file mode 100644 index 00000000..41c474b4 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Data/IIdCompletenessTest.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 PG.Commons.Test.Data; + +namespace PG.StarWarsGame.Components.Localisation.Test.Data; + +// ReSharper disable once InconsistentNaming +public class IIdCompletenessTest : IIdTestCompletenessTestBase +{ + protected override string GetConfiguredNamespaceBase() + { + return "PG.StarWarsGame.Components.Localisation"; + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Data/OrderedTranslationItemIdTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Data/OrderedTranslationItemIdTest.cs new file mode 100644 index 00000000..7e7e8a66 --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Data/OrderedTranslationItemIdTest.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; +using System.Collections.Generic; +using PG.Commons.Test.Data; +using PG.StarWarsGame.Components.Localisation.Repository.Content; + +namespace PG.StarWarsGame.Components.Localisation.Test.Data; + +public class OrderedTranslationItemIdTest : IIdTestBase +{ + protected override List GetConfiguredNullIIds() + { + return + [ + OrderedTranslationItemId.Of(string.Empty), + OrderedTranslationItemId.Of(""), + OrderedTranslationItemId.Of("\t") + ]; + } + + protected override List GetConfiguredValidIIdInitialisers() + { + return + [ + new object[] { "TEST_00" }, + new object[] { "TEST_01" }, + new object[] { "TEST_02" }, + new object[] { "TEST_03" }, + new object[] { "TEST_04" }, + new object[] { "TEST_05" } + ]; + } + + protected override OrderedTranslationItemId CreateId(object[] keyInitialiser) + { + return OrderedTranslationItemId.Of((keyInitialiser[0] as string)!) ?? throw new InvalidOperationException(); + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlExportHandlerTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlExportHandlerTest.cs index 5bcaba8f..4eac65b8 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlExportHandlerTest.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/IO/Xml/XmlExportHandlerTest.cs @@ -1,7 +1,6 @@ // 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 System.IO.Abstractions; using Microsoft.Extensions.DependencyInjection; using PG.Commons.Extensibility; @@ -33,9 +32,9 @@ public void Test_Export_WithEmptyRepository_NoFileCreated() { var repository = new InMemoryOrderedTranslationRepository(); Assert.Empty(repository.Content); - const string testExportEmpty = "./test_export_empty"; - const string testExportEmptyFile = "EMPTY_EXPORT"; - var strategy = new XmlOutputStrategy(_fileSystem.DirectoryInfo.New(testExportEmpty), testExportEmptyFile); + const string exportBase = "./test_export_empty"; + const string exportBaseFile = "EMPTY_EXPORT"; + var strategy = new XmlOutputStrategy(_fileSystem.DirectoryInfo.New(exportBase), exportBaseFile); _handler.Export(strategy, repository); var info = _fileSystem.FileInfo.New(strategy.FilePath); Assert.False(info.Exists); @@ -47,9 +46,9 @@ public void Test_Export_WithRepositoryWithoutContentBesidesLanguages_NoFileCreat var repository = new InMemoryOrderedTranslationRepository(); repository.AddLanguage(new EnglishAlamoLanguageDefinition()); repository.AddLanguage(new GermanAlamoLanguageDefinition()); - const string testExportEmpty = "./test_export_languages_only"; - const string testExportEmptyFile = "EMPTY_LANGUAGES"; - var strategy = new XmlOutputStrategy(_fileSystem.DirectoryInfo.New(testExportEmpty), testExportEmptyFile); + const string exportBase = "./test_export_languages_only"; + const string exportBaseFile = "EMPTY_LANGUAGES"; + var strategy = new XmlOutputStrategy(_fileSystem.DirectoryInfo.New(exportBase), exportBaseFile); _handler.Export(strategy, repository); var info = _fileSystem.FileInfo.New(strategy.FilePath); Assert.False(info.Exists); @@ -66,9 +65,9 @@ public void Test_Export_WithRepositoryWithSingleEntry_FileCreated() Key = "TEST_00", Value = "English translation for TEST_00" })); - const string testExportEmpty = "./test_export_languages_only"; - const string testExportEmptyFile = "EMPTY_LANGUAGES"; - var strategy = new XmlOutputStrategy(_fileSystem.DirectoryInfo.New(testExportEmpty), testExportEmptyFile); + const string exportBase = "./test_single_entry"; + const string exportBaseFile = "VALID_FILE"; + var strategy = new XmlOutputStrategy(_fileSystem.DirectoryInfo.New(exportBase), exportBaseFile); _handler.Export(strategy, repository); var info = _fileSystem.FileInfo.New(strategy.FilePath); Assert.True(info.Exists); diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Repository/Builtin/InMemoryOrderedTranslationRepositoryTest.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Repository/Builtin/InMemoryOrderedTranslationRepositoryTest.cs new file mode 100644 index 00000000..e994fe0a --- /dev/null +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation.Test/Repository/Builtin/InMemoryOrderedTranslationRepositoryTest.cs @@ -0,0 +1,79 @@ +// Copyright (c) Alamo Engine Tools and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +using System; +using System.IO.Abstractions; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Extensibility; +using PG.StarWarsGame.Components.Localisation.Languages.BuiltIn; +using PG.StarWarsGame.Components.Localisation.Repository.Builtin; +using PG.StarWarsGame.Components.Localisation.Repository.Content; +using PG.StarWarsGame.Components.Localisation.Services; +using Testably.Abstractions.Testing; +using Xunit; + +namespace PG.StarWarsGame.Components.Localisation.Test.Repository.Builtin; + +public class InMemoryOrderedTranslationRepositoryTest +{ + private readonly MockFileSystem _fileSystem = new(); + private readonly IServiceProvider _serviceProvider; + + public InMemoryOrderedTranslationRepositoryTest() + { + var sc = new ServiceCollection(); + sc.AddSingleton(_fileSystem); + sc.CollectPgServiceContributions(); + _serviceProvider = sc.BuildServiceProvider(); + } + + [Fact] + public void Test_AddLanguage_AllPresent() + { + var repository = new InMemoryOrderedTranslationRepository(); + var service = _serviceProvider.GetService(); + Assert.NotNull(service); + foreach (var l in service.GetRegisteredLanguages()) Assert.True(repository.AddLanguage(l)); + Assert.NotEmpty(repository.Content); + Assert.Equal(service.GetRegisteredLanguages().Count, repository.Content.Count); + } + + [Fact] + public void Test_RemoveLanguage_AllPresentMinusOne() + { + var repository = new InMemoryOrderedTranslationRepository(); + var service = _serviceProvider.GetService(); + Assert.NotNull(service); + foreach (var l in service.GetRegisteredLanguages()) Assert.True(repository.AddLanguage(l)); + Assert.NotEmpty(repository.Content); + Assert.Equal(service.GetRegisteredLanguages().Count, repository.Content.Count); + + repository.RemoveLanguage(service.GetDefaultLanguageDefinition()); + Assert.NotEmpty(repository.Content); + Assert.Equal(service.GetRegisteredLanguages().Count - 1, repository.Content.Count); + } + + [Fact] + public void Test_AddOrUpdateTranslationItem_Add() + { + var repository = new InMemoryOrderedTranslationRepository(); + var service = _serviceProvider.GetService(); + Assert.NotNull(service); + foreach (var l in service.GetRegisteredLanguages()) Assert.True(repository.AddLanguage(l)); + Assert.NotEmpty(repository.Content); + Assert.Equal(service.GetRegisteredLanguages().Count, repository.Content.Count); + + repository.AddOrUpdateTranslationItem(new EnglishAlamoLanguageDefinition(), + OrderedTranslationItem.Of(new TranslationItemContent { Key = "TEST_00", Value = "Test translation" })); + + Assert.Single(repository.Content[new EnglishAlamoLanguageDefinition()]); + foreach (var l in service.GetRegisteredLanguages()) + { + if (l.Equals(new EnglishAlamoLanguageDefinition())) continue; + Assert.Empty(repository[l]); + } + + Assert.NotNull(repository.GetTranslationItem(new EnglishAlamoLanguageDefinition(), + OrderedTranslationItemId.Of("TEST_00")!)); + } +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Builtin/InMemoryOrderedTranslationRepository.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Builtin/InMemoryOrderedTranslationRepository.cs index f89a5203..d85ff882 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Builtin/InMemoryOrderedTranslationRepository.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Builtin/InMemoryOrderedTranslationRepository.cs @@ -2,6 +2,7 @@ // 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.Collections.Immutable; using System.Linq; @@ -19,6 +20,12 @@ private IDictionary>(); + /// + /// Indexer + /// + /// + public IEnumerable this[IAlamoLanguageDefinition alamoLanguageDefinition] => Content[alamoLanguageDefinition]; + /// public IReadOnlyDictionary> Content => ToContent(); diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs index 0cc3e056..5e41269e 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItem.cs @@ -8,10 +8,10 @@ public interface ITranslationItem /// /// ID /// - ITranslationItemId ItemId { get; } + ITranslationItemId? ItemId { get; } /// /// The acutal translation content. /// ITranslationItemContent Content { get; set; } -} +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs index bb426bc1..139e84cf 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/ITranslationItemId.cs @@ -1,3 +1,4 @@ +using System; using PG.Commons.Data; namespace PG.StarWarsGame.Components.Localisation.Repository.Content; @@ -5,6 +6,6 @@ namespace PG.StarWarsGame.Components.Localisation.Repository.Content; /// /// The unique item ID, could be the string key, or any other unique ID. /// -public interface ITranslationItemId : IId +public interface ITranslationItemId : IId, IEquatable, IComparable { -} \ No newline at end of file +} diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs index 3c8a78c6..c5bc8b99 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItem.cs @@ -6,7 +6,7 @@ namespace PG.StarWarsGame.Components.Localisation.Repository.Content; public class OrderedTranslationItem : TranslationItemBase { /// - protected OrderedTranslationItem(OrderedTranslationItemId itemId, TranslationItemContent content) : base(itemId, + protected OrderedTranslationItem(OrderedTranslationItemId? itemId, TranslationItemContent content) : base(itemId, content) { } @@ -17,7 +17,7 @@ protected OrderedTranslationItem(OrderedTranslationItemId itemId, TranslationIte /// /// /// - public static OrderedTranslationItem Of(OrderedTranslationItemId itemId, TranslationItemContent content) + public static OrderedTranslationItem Of(OrderedTranslationItemId? itemId, TranslationItemContent content) { return new OrderedTranslationItem(itemId, content); } @@ -32,4 +32,4 @@ public static OrderedTranslationItem Of(TranslationItemContent content) { return Of(OrderedTranslationItemId.Of(content.Key) ?? throw new InvalidOperationException(), content); } -} +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs index 8c75a5f0..15cf2e3c 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/OrderedTranslationItemId.cs @@ -5,13 +5,27 @@ namespace PG.StarWarsGame.Components.Localisation.Repository.Content; /// /// Item ID for ordered translation items. Equivalent to the raw string key from a sorted DAT file. /// -public record OrderedTranslationItemId : RootIdBase, ITranslationItemId +public class OrderedTranslationItemId : RootIdBase, ITranslationItemId { /// protected OrderedTranslationItemId(string rawId) : base(rawId) { } + /// + public bool Equals(ITranslationItemId? other) + { + if (other == null) return false; + return base.Equals(other); + } + + /// + public int CompareTo(ITranslationItemId? other) + { + if (other == null) return 1; + return base.CompareTo(other); + } + /// /// Convenience method to create a new /// diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs index 0e0334db..28ddd206 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Repository/Content/TranslationItemBase.cs @@ -8,7 +8,7 @@ public abstract class TranslationItemBase : ITranslationItem /// /// /// - protected TranslationItemBase(ITranslationItemId itemId, TranslationItemContent content) + protected TranslationItemBase(ITranslationItemId? itemId, TranslationItemContent content) { ItemId = itemId; Content = content; @@ -18,11 +18,11 @@ protected TranslationItemBase(ITranslationItemId itemId, TranslationItemContent public ITranslationItemContent Content { get; set; } /// - public ITranslationItemId ItemId { get; } + public ITranslationItemId? ItemId { get; } /// public override string ToString() { return $"{GetType().Name}[{ItemId}: {Content}]"; } -} +} \ No newline at end of file diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs index 27356f1d..bf205dd5 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/AlamoLanguageSupportService.cs @@ -61,11 +61,16 @@ public IAlamoLanguageDefinition GetDefaultLanguageDefinition() /// public IDictionary CreateLanguageIdentifierMapping() { - return AppDomain.CurrentDomain.GetAssemblies() + return GetRegisteredLanguages().ToDictionary(d => d.LanguageIdentifier, d => d); + } + + /// + public ISet GetRegisteredLanguages() + { + return new HashSet(AppDomain.CurrentDomain.GetAssemblies() .SelectMany(assemblyTypes => assemblyTypes.GetTypes()) .Where(assemblyType => typeof(IAlamoLanguageDefinition).IsAssignableFrom(assemblyType) && assemblyType is { IsClass: true, IsAbstract: false }) - .Select(t => (IAlamoLanguageDefinition)Activator.CreateInstance(t)) - .ToDictionary(d => d.LanguageIdentifier, d => d); + .Select(t => (IAlamoLanguageDefinition)Activator.CreateInstance(t))); } } diff --git a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs index 63768128..6eb0bfc8 100644 --- a/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs +++ b/PG.StarWarsGame.Components.Localisation/PG.StarWarsGame.Components.Localisation/Services/IAlamoLanguageSupportService.cs @@ -46,4 +46,10 @@ public interface IAlamoLanguageSupportService /// /// IDictionary CreateLanguageIdentifierMapping(); + + /// + /// Collects all registered languages. + /// + /// + ISet GetRegisteredLanguages(); }