diff --git a/.github/workflows/mocale-ci.yml b/.github/workflows/mocale-ci.yml index d337dab..821d205 100644 --- a/.github/workflows/mocale-ci.yml +++ b/.github/workflows/mocale-ci.yml @@ -25,6 +25,14 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: 7.0.x + - name: Setup Java JDK + uses: actions/setup-java@v4.0.0 + with: + distribution: 'microsoft' + java-version: '11' + architecture: 'x64' + - name: Install Maui Workloads + run: dotnet workload install maui --source https://api.nuget.org/v3/index.json - name: Restore dependencies run: dotnet restore - name: Build diff --git a/src/Mocale/AppBuilderExtensions.cs b/src/Mocale/AppBuilderExtensions.cs index 2d4f7ba..7e7e847 100644 --- a/src/Mocale/AppBuilderExtensions.cs +++ b/src/Mocale/AppBuilderExtensions.cs @@ -1,6 +1,8 @@ +using System.Globalization; using Mocale.Exceptions; using Mocale.Helper; using Mocale.Managers; +using Mocale.Providers; using Mocale.Wrappers; namespace Mocale; @@ -52,6 +54,11 @@ public static MauiAppBuilder UseMocale( throw new InitializationException($"No external provider was registered when mocale was configured to use one. Please register an external provider or set {nameof(IMocaleConfiguration.UseExternalProvider)} to false."); } + if (!config.UseExternalProvider) + { + mauiAppBuilder.Services.AddTransient(); + } + if (!mocaleBuilder.CacheProviderRegistered) { // TODO: Initialize in memory provider diff --git a/src/Mocale/Managers/LocalizationManager.cs b/src/Mocale/Managers/LocalizationManager.cs index e4b6b13..4dce1db 100644 --- a/src/Mocale/Managers/LocalizationManager.cs +++ b/src/Mocale/Managers/LocalizationManager.cs @@ -6,6 +6,7 @@ public class LocalizationManager : ILocalizationManager { private readonly ICurrentCultureManager currentCultureManager; private readonly ILogger logger; + private readonly IMocaleConfiguration mocaleConfiguration; private readonly ITranslationResolver translationResolver; private readonly ITranslationUpdater translationUpdater; @@ -13,6 +14,7 @@ public class LocalizationManager : ILocalizationManager public LocalizationManager( ICurrentCultureManager currentCultureManager, + IConfigurationManager configurationManager, ILogger logger, ITranslationResolver translationResolver, ITranslationUpdater translationUpdater) @@ -22,6 +24,9 @@ public LocalizationManager( this.translationResolver = Guard.Against.Null(translationResolver, nameof(translationResolver)); this.translationUpdater = Guard.Against.Null(translationUpdater, nameof(this.translationUpdater)); + configurationManager = Guard.Against.Null(configurationManager, nameof(configurationManager)); + this.mocaleConfiguration = configurationManager.Configuration; + CurrentCulture = currentCultureManager.GetActiveCulture(); } @@ -29,12 +34,17 @@ public async Task SetCultureAsync(CultureInfo culture) { try { - var result = await translationResolver.LoadTranslations(culture); - - if (!result.Loaded) + if (mocaleConfiguration.UseExternalProvider) { - logger.LogWarning("Unable to load culture {CultureName}, no localizations found", culture.Name); - return false; + var result = await translationResolver.LoadTranslations(culture); + + if (!result.Loaded) + { + logger.LogWarning("Unable to load culture {CultureName}, no localizations found", culture.Name); + return false; + } + + translationUpdater.UpdateTranslations(result.Localization, result.Source); } var localTranslations = translationResolver.LoadLocalTranslations(culture); @@ -46,9 +56,12 @@ public async Task SetCultureAsync(CultureInfo culture) else { logger.LogInformation("No internal translations found for culture: {CultureName}, consider adding them as a backup", culture.Name); - } - translationUpdater.UpdateTranslations(result.Localization, result.Source); + if (!mocaleConfiguration.UseExternalProvider) + { + return false; + } + } CurrentCulture = culture; @@ -93,7 +106,7 @@ private Task InitializeInternal() logger.LogTrace("Loaded local translations from source: {TranslationSource}", localTranslations.Source); - if (localTranslations.Source is TranslationSource.Internal or TranslationSource.ColdCache) + if ((localTranslations.Source is TranslationSource.Internal or TranslationSource.ColdCache) && mocaleConfiguration.UseExternalProvider) { logger.LogInformation("External translations can be updated, checking for newer copy..."); diff --git a/src/Mocale/Providers/EmbeddedResourceProvider.cs b/src/Mocale/Providers/EmbeddedResourceProvider.cs index 6706a36..18e8a30 100644 --- a/src/Mocale/Providers/EmbeddedResourceProvider.cs +++ b/src/Mocale/Providers/EmbeddedResourceProvider.cs @@ -16,23 +16,17 @@ public EmbeddedResourceProvider( this.logger = logger; } - public Dictionary GetValuesForCulture(CultureInfo cultureInfo) + public Dictionary? GetValuesForCulture(CultureInfo cultureInfo) { // read assembly if (localConfig.ResourcesAssembly is null) { logger.LogWarning("Configured resource assembly was null"); - return new Dictionary(); + return null; } var resources = localConfig.ResourcesAssembly.GetManifestResourceNames(); - if (resources is null) - { - logger.LogWarning("No embedded resources found in assembly {ResourceAssembly}", localConfig.ResourcesAssembly); - return new Dictionary(); - } - // look for the right folder var relativeFolder = localConfig.UseResourceFolder ? $"Resources.{localConfig.ResourcesPath}" @@ -40,12 +34,13 @@ public Dictionary GetValuesForCulture(CultureInfo cultureInfo) var folderPrefix = localConfig.ResourcesAssembly.GetName().Name + "." + relativeFolder; - var localesFolderResources = resources.Where(r => r.StartsWith(folderPrefix, StringComparison.InvariantCultureIgnoreCase)); + var localesFolderResources = resources.Where(r => r.StartsWith(folderPrefix, StringComparison.InvariantCultureIgnoreCase)) + .ToList(); if (!localesFolderResources.Any()) { logger.LogWarning("No assembly resources found with prefix: {FolderPrefix}", folderPrefix); - return new Dictionary(); + return null; } // check if filenames match @@ -59,7 +54,7 @@ public Dictionary GetValuesForCulture(CultureInfo cultureInfo) logger.LogWarning("Unable to find resource for selected culture: {CultureName}", cultureInfo.Name); - return new Dictionary(); + return null; } private static bool FileMatchesCulture(string resourceName, CultureInfo culture) @@ -73,14 +68,14 @@ private static bool FileMatchesCulture(string resourceName, CultureInfo culture) return fileName.Equals(culture.Name, StringComparison.OrdinalIgnoreCase); } - private Dictionary ParseFile(string filePath, Assembly assembly) + private Dictionary? ParseFile(string filePath, Assembly assembly) { using var fileStream = assembly.GetManifestResourceStream(filePath); if (fileStream is null) { logger.LogWarning("File stream was null for assembly resource: {FilePath}", filePath); - return new Dictionary(); + return null; } try @@ -92,7 +87,7 @@ private Dictionary ParseFile(string filePath, Assembly assembly) { logger.LogError(ex, "An exception occurred loading & parsing assembly resource {FilePath}", filePath); - return new Dictionary(); + return null; } } } diff --git a/src/Mocale/Providers/InactiveExternalLocalizationProvider.cs b/src/Mocale/Providers/InactiveExternalLocalizationProvider.cs new file mode 100644 index 0000000..9c97a95 --- /dev/null +++ b/src/Mocale/Providers/InactiveExternalLocalizationProvider.cs @@ -0,0 +1,15 @@ +using System.Globalization; +namespace Mocale.Providers; + +internal sealed class InactiveExternalLocalizationProvider : IExternalLocalizationProvider +{ + public Task GetValuesForCultureAsync(CultureInfo cultureInfo) + { + IExternalLocalizationResult blankResult = new ExternalLocalizationResult() + { + Success = false, + }; + + return Task.FromResult(blankResult); + } +} diff --git a/tests/Mocale.UnitTests/Managers/LocalizationManagerTests.cs b/tests/Mocale.UnitTests/Managers/LocalizationManagerTests.cs index 1559a82..e830d68 100644 --- a/tests/Mocale.UnitTests/Managers/LocalizationManagerTests.cs +++ b/tests/Mocale.UnitTests/Managers/LocalizationManagerTests.cs @@ -13,22 +13,35 @@ public class LocalizationManagerTests : FixtureBase #region Setup private readonly Mock currentCultureManager; + private readonly Mock> configurationManager; private readonly Mock> logger; + private readonly Mock mocaleConfiguration; private readonly Mock translationResolver; private readonly Mock translationUpdater; public LocalizationManagerTests() { currentCultureManager = new Mock(); + configurationManager = new Mock>(); logger = new Mock>(); + mocaleConfiguration = new Mock(); translationResolver = new Mock(); translationUpdater = new Mock(); + + mocaleConfiguration = new Mock(); + + mocaleConfiguration.SetupGet(m => m.UseExternalProvider) + .Returns(true); + + configurationManager.SetupGet(m => m.Configuration) + .Returns(mocaleConfiguration.Object); } public override ILocalizationManager CreateSystemUnderTest() { return new LocalizationManager( currentCultureManager.Object, + configurationManager.Object, logger.Object, translationResolver.Object, translationUpdater.Object @@ -382,6 +395,180 @@ public async Task Initialize_WhenTranslationsCanBeUpdatedAndExternalLoads_Should Times.Never()); } + [Fact] + public async Task Initialize_WhenExternalProviderNotEnabledAndLocalProviderFailsToLoad_ShouldLogAndReturnFalse() + { + // Arrange + mocaleConfiguration.SetupGet(m => m.UseExternalProvider) + .Returns(false); + + var activeCulture = new CultureInfo("fr-FR"); + + currentCultureManager.Setup(m => m.GetActiveCulture()) + .Returns(activeCulture); + + translationResolver.Setup(m => m.LoadLocalTranslations(activeCulture)) + .Returns(new TranslationLoadResult() + { + Loaded = false, + Localization = Localization.Invariant, + }); + + // Act + var initialized = await Sut.Initialize(); + + // Assert + Assert.False(initialized); + + logger.VerifyLog( + log => log.LogWarning("Unable to load translations for culture: {CultureName}", activeCulture.Name), + Times.Once()); + } + + [Fact] + public async Task Initialize_WhenExternalProviderNotEnabledAndLocalProviderLoadsFromInternal_ShouldUpdateAndReturnTrueWithoutUpdatingExternal() + { + // Arrange + mocaleConfiguration.SetupGet(m => m.UseExternalProvider) + .Returns(false); + + var activeCulture = new CultureInfo("it-IT"); + + currentCultureManager.Setup(m => m.GetActiveCulture()) + .Returns(activeCulture); + + var loadResult = new TranslationLoadResult() + { + Loaded = true, + Localization = new Localization() + { + CultureInfo = activeCulture, + Translations = new Dictionary() + { + { "KeyOne", "Ciao mondo!" }, + } + }, + Source = TranslationSource.Internal, + }; + + translationResolver.Setup(m => m.LoadLocalTranslations(activeCulture)) + .Returns(loadResult); + + // Act + var initialized = await Sut.Initialize(); + + // Assert + Assert.True(initialized); + + translationUpdater.Verify( + m => m.UpdateTranslations(loadResult.Localization, TranslationSource.Internal), + Times.Once()); + + logger.VerifyLog( + log => log.LogTrace("Loaded local translations from source: {TranslationSource}", TranslationSource.Internal), + Times.Once()); + + logger.VerifyLog( + log => log.LogInformation("External translations can be updated, checking for newer copy..."), + Times.Never()); + } + + [Fact] + public async Task Initialize_WhenExternalProviderNotEnabledAndLocalProviderLoadsFromColdCache_ShouldUpdateAndReturnTrueWithoutUpdatingExternal() + { + // Arrange + mocaleConfiguration.SetupGet(m => m.UseExternalProvider) + .Returns(false); + + var activeCulture = new CultureInfo("it-IT"); + + currentCultureManager.Setup(m => m.GetActiveCulture()) + .Returns(activeCulture); + + var loadResult = new TranslationLoadResult() + { + Loaded = true, + Localization = new Localization() + { + CultureInfo = activeCulture, + Translations = new Dictionary() + { + { "KeyOne", "Ciao mondo!" }, + } + }, + Source = TranslationSource.ColdCache, + }; + + translationResolver.Setup(m => m.LoadLocalTranslations(activeCulture)) + .Returns(loadResult); + + // Act + var initialized = await Sut.Initialize(); + + // Assert + Assert.True(initialized); + + translationUpdater.Verify( + m => m.UpdateTranslations(loadResult.Localization, TranslationSource.ColdCache), + Times.Once()); + + logger.VerifyLog( + log => log.LogTrace("Loaded local translations from source: {TranslationSource}", TranslationSource.ColdCache), + Times.Once()); + + logger.VerifyLog( + log => log.LogInformation("External translations can be updated, checking for newer copy..."), + Times.Never()); + } + + [Fact] + public async Task Initialize_WhenExternalProviderNotEnabledAndLocalProviderLoadsFromHotCache_ShouldUpdateAndReturnTrueWithoutUpdatingExternal() + { + // Arrange + mocaleConfiguration.SetupGet(m => m.UseExternalProvider) + .Returns(false); + + var activeCulture = new CultureInfo("it-IT"); + + currentCultureManager.Setup(m => m.GetActiveCulture()) + .Returns(activeCulture); + + var loadResult = new TranslationLoadResult() + { + Loaded = true, + Localization = new Localization() + { + CultureInfo = activeCulture, + Translations = new Dictionary() + { + { "KeyOne", "Ciao mondo!" }, + } + }, + Source = TranslationSource.WarmCache, + }; + + translationResolver.Setup(m => m.LoadLocalTranslations(activeCulture)) + .Returns(loadResult); + + // Act + var initialized = await Sut.Initialize(); + + // Assert + Assert.True(initialized); + + translationUpdater.Verify( + m => m.UpdateTranslations(loadResult.Localization, TranslationSource.WarmCache), + Times.Once()); + + logger.VerifyLog( + log => log.LogTrace("Loaded local translations from source: {TranslationSource}", TranslationSource.WarmCache), + Times.Once()); + + logger.VerifyLog( + log => log.LogInformation("External translations can be updated, checking for newer copy..."), + Times.Never()); + } + [Fact] public async Task SetCultureAsync_WhenSomethingThrows_ShouldLogAndReturnFalse() { @@ -574,6 +761,104 @@ public async Task SetCultureAsync_WhenCultureCanBeLoadedAndLocalTranslationsDont Times.Never()); } + [Fact] + public async Task SetCultureAsync_WhenExternalProviderNotEnabledAndLocalTranslationsNotLoaded_ShouldReturnFalseAndNotSetCulture() + { + mocaleConfiguration.SetupGet(m => m.UseExternalProvider) + .Returns(false); + + var activeCulture = new CultureInfo("it-IT"); + + currentCultureManager.Setup(m => m.GetActiveCulture()) + .Returns(activeCulture); + + var newCulture = new CultureInfo("fr-FR"); + + var localLoadResult = new TranslationLoadResult() + { + Loaded = false, + Localization = Localization.Invariant, + Source = TranslationSource.Internal, + }; + + translationResolver.Setup(m => m.LoadLocalTranslations(newCulture)) + .Returns(localLoadResult); + + // Act + var loaded = await Sut.SetCultureAsync(newCulture); + + // Assert + Assert.False(loaded); + Assert.Equal(activeCulture, Sut.CurrentCulture); + Assert.NotEqual(newCulture, Sut.CurrentCulture); + + currentCultureManager.Verify( + m => m.SetActiveCulture(It.IsAny()), + Times.Never); + + logger.VerifyLog( + log => log.LogDebug("Updated localization culture to {CultureName}", newCulture.Name), + Times.Never()); + } + + [Fact] + public async Task SetCultureAsync_WhenExternalProviderNotEnabledAndLocalTranslationsLoaded_ShouldReturnTrue() + { + mocaleConfiguration.SetupGet(m => m.UseExternalProvider) + .Returns(false); + + var activeCulture = new CultureInfo("it-IT"); + + currentCultureManager.Setup(m => m.GetActiveCulture()) + .Returns(activeCulture); + + var newCulture = new CultureInfo("fr-FR"); + + var localLoadResult = new TranslationLoadResult() + { + Loaded = true, + Localization = new Localization() + { + CultureInfo = newCulture, + Translations = new Dictionary() + { + { "KeyOne", "Bonjour le monde" }, + }, + }, + Source = TranslationSource.Internal, + }; + + translationResolver.Setup(m => m.LoadLocalTranslations(newCulture)) + .Returns(localLoadResult); + + // Act + var loaded = await Sut.SetCultureAsync(newCulture); + + // Assert + Assert.True(loaded); + Assert.Equal(newCulture, Sut.CurrentCulture); + + currentCultureManager.Verify( + m => m.SetActiveCulture(newCulture), + Times.Once()); + + logger.VerifyLog( + log => log.LogDebug("Updated localization culture to {CultureName}", newCulture.Name), + Times.Once()); + + logger.VerifyLog( + log => log.LogInformation("No internal translations found for culture: {CultureName}, consider adding them as a backup", newCulture.Name), + Times.Never); + + translationUpdater.Verify( + m => m.UpdateTranslations(It.IsAny(), TranslationSource.External), + Times.Never()); + + translationUpdater.Verify( + m => m.UpdateTranslations(localLoadResult.Localization, TranslationSource.Internal), + Times.Once()); + } + #endregion Tests } diff --git a/tests/Mocale.UnitTests/Mocale.UnitTests.csproj b/tests/Mocale.UnitTests/Mocale.UnitTests.csproj index d9941c9..9cce3e6 100644 --- a/tests/Mocale.UnitTests/Mocale.UnitTests.csproj +++ b/tests/Mocale.UnitTests/Mocale.UnitTests.csproj @@ -33,5 +33,10 @@ + + + + + diff --git a/tests/Mocale.UnitTests/Providers/EmbeddedResourceProviderTests.cs b/tests/Mocale.UnitTests/Providers/EmbeddedResourceProviderTests.cs new file mode 100644 index 0000000..025363b --- /dev/null +++ b/tests/Mocale.UnitTests/Providers/EmbeddedResourceProviderTests.cs @@ -0,0 +1,180 @@ +using System.Globalization; +using Microsoft.Extensions.Logging; +using Mocale.Abstractions; +using Mocale.Providers; +namespace Mocale.UnitTests.Providers; + +public class EmbeddedResourceProviderTests : FixtureBase +{ + #region Setup + + private readonly Mock> configurationManager; + private readonly Mock embeddedResourcesConfig; + private readonly Mock> logger; + + public EmbeddedResourceProviderTests() + { + configurationManager = new Mock>(); + embeddedResourcesConfig = new Mock(); + logger = new Mock>(); + + configurationManager.SetupGet(m => m.Configuration) + .Returns(embeddedResourcesConfig.Object); + } + + public override IInternalLocalizationProvider CreateSystemUnderTest() + { + return new EmbeddedResourceProvider( + configurationManager.Object, + logger.Object); + } + + #endregion Setup + + #region Tests + + [Fact] + public void GetValuesForCulture_WhenResourceAssemblyIsNull_ShouldReturnNull() + { + // Arrange + embeddedResourcesConfig.SetupGet(m => m.ResourcesAssembly) + .Returns(() => null); + + var culture = new CultureInfo("en-GB"); + + // Act + var values = Sut.GetValuesForCulture(culture); + + // Assert + Assert.Null(values); + + logger.VerifyLog(log => log.LogWarning("Configured resource assembly was null"), + Times.Once()); + } + + [Fact] + public void GetValuesForCulture_WhenResourceFolderNotFound_ShouldReturnNull() + { + // Arrange + embeddedResourcesConfig.SetupGet(m => m.ResourcesAssembly) + .Returns(typeof(EmbeddedResourceProviderTests).Assembly); + + embeddedResourcesConfig.SetupGet(m => m.UseResourceFolder) + .Returns(true); + + embeddedResourcesConfig.SetupGet(m => m.ResourcesPath) + .Returns("Locales"); + + var culture = new CultureInfo("en-GB"); + + // Act + var values = Sut.GetValuesForCulture(culture); + + // Assert + Assert.Null(values); + + logger.VerifyLog(log => log.LogWarning("No assembly resources found with prefix: {FolderPrefix}", "Mocale.UnitTests.Resources.Locales"), + Times.Once()); + } + + [Fact] + public void GetValuesForCulture_WhenResourceFolderFoundButNoMatchingFiles_ShouldReturnNull() + { + // Arrange + embeddedResourcesConfig.SetupGet(m => m.ResourcesAssembly) + .Returns(typeof(EmbeddedResourceProviderTests).Assembly); + + embeddedResourcesConfig.SetupGet(m => m.UseResourceFolder) + .Returns(false); + + embeddedResourcesConfig.SetupGet(m => m.ResourcesPath) + .Returns("Resources.Misc"); + + var culture = new CultureInfo("en-GB"); + + // Act + var values = Sut.GetValuesForCulture(culture); + + // Assert + Assert.Null(values); + + logger.VerifyLog(log => log.LogWarning("Unable to find resource for selected culture: {CultureName}", "en-GB"), + Times.Once()); + } + + [Fact] + public void GetValuesForCulture_WhenResourceFolderContainsInvalidCultureFiles_ShouldReturnNull() + { + // Arrange + embeddedResourcesConfig.SetupGet(m => m.ResourcesAssembly) + .Returns(typeof(EmbeddedResourceProviderTests).Assembly); + + embeddedResourcesConfig.SetupGet(m => m.UseResourceFolder) + .Returns(true); + + embeddedResourcesConfig.SetupGet(m => m.ResourcesPath) + .Returns("Invalid"); + + var culture = new CultureInfo("en-GB"); + + // Act + var values = Sut.GetValuesForCulture(culture); + + // Assert + Assert.Null(values); + + logger.VerifyLog( + log => log.LogError( + It.IsAny(), + "An exception occurred loading & parsing assembly resource {FilePath}", + It.IsAny()), + Times.Once()); + } + + [Fact] + public void GetValuesForCulture_WhenResourceFolderContainsValidCultureFiles_ShouldReturnLocalizations() + { + // Arrange + embeddedResourcesConfig.SetupGet(m => m.ResourcesAssembly) + .Returns(typeof(EmbeddedResourceProviderTests).Assembly); + + embeddedResourcesConfig.SetupGet(m => m.UseResourceFolder) + .Returns(true); + + var culture = new CultureInfo("en-GB"); + + // Act + var values = Sut.GetValuesForCulture(culture); + + // Assert + Assert.NotNull(values); + Assert.NotEmpty(values); + + var expectedLocalizations = new Dictionary() + { + { + "CurrentLocaleName", "English" + }, + { + "LocalizationCurrentProviderIs", "The current localization provider is:" + }, + { + "LocalizationProviderName", "Json" + }, + { + "MocaleDescription", "Localization framework for .NET Maui" + }, + { + "MocaleTitle", "Mocale" + }, + { + "ExternalPrefixExplanation", "Strings prefixed with GR_ indicate they have been pulled from the external provider (GitHub.Raw), when the local cache expires if these values change, so will the text displayed!" + }, + }; + + values.Should() + .BeEquivalentTo(expectedLocalizations); + } + + #endregion Tests +} diff --git a/tests/Mocale.UnitTests/Resources/Invalid/en-GB.json b/tests/Mocale.UnitTests/Resources/Invalid/en-GB.json new file mode 100644 index 0000000..385906e --- /dev/null +++ b/tests/Mocale.UnitTests/Resources/Invalid/en-GB.json @@ -0,0 +1,9 @@ + + + English + The current localization provider is: + Json + Localization framework for .NET Maui + Mocale + Strings prefixed with GR_ indicate they have been pulled from the external provider (GitHub.Raw), when the local cache expires if these values change, so will the text displayed! + diff --git a/tests/Mocale.UnitTests/Resources/Misc/RandomFile.txt b/tests/Mocale.UnitTests/Resources/Misc/RandomFile.txt new file mode 100644 index 0000000..7bb0eab --- /dev/null +++ b/tests/Mocale.UnitTests/Resources/Misc/RandomFile.txt @@ -0,0 +1,5 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed facilisis tempus nisl, et faucibus elit gravida eleifend. Aliquam consectetur nibh nec nisl malesuada, nec rhoncus nulla aliquet. Sed dictum tortor id auctor consectetur. Quisque ante nisi, iaculis non euismod et, sodales et nisl. Quisque laoreet eu mauris eget aliquam. Suspendisse nec sem non quam tristique fermentum eu at odio. Nullam pharetra non augue quis egestas. Proin pulvinar et neque id congue. Integer congue, nunc nec elementum vulputate, tortor arcu congue dui, a pulvinar odio ante at orci. + +Pellentesque tellus felis, congue ac mollis sed, venenatis vel erat. Cras blandit lectus sit amet magna euismod tempus. Nullam porttitor sollicitudin eleifend. Nullam scelerisque finibus mollis. Maecenas feugiat, urna condimentum iaculis laoreet, massa ligula venenatis libero, ut sodales nunc turpis vel tortor. Nullam vitae justo porta, congue ipsum vitae, ornare lacus. Sed condimentum interdum nisl, eget commodo arcu pellentesque quis. + +Integer interdum rutrum risus, iaculis condimentum tellus auctor eget. Quisque ut dignissim orci. In sit amet augue magna. Etiam neque orci, lobortis rhoncus sagittis non, aliquam ut nulla. Proin quis ex id lorem cursus rutrum. Ut iaculis lorem dolor, vehicula dictum nunc commodo eget. Maecenas fringilla mauris sit amet molestie viverra. Curabitur pretium lacus leo, at auctor tellus blandit quis. Nam vitae nisi gravida, bibendum nunc vel, venenatis tortor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec vel arcu posuere, congue erat a, finibus tortor. Fusce pellentesque lorem vitae arcu pulvinar, vitae semper est commodo. Duis rhoncus leo ultricies ex gravida, nec elementum odio sollicitudin. Proin sollicitudin diam dui, id gravida sapien imperdiet scelerisque. diff --git a/tests/Mocale.UnitTests/Resources/Misc/TestFile.json b/tests/Mocale.UnitTests/Resources/Misc/TestFile.json new file mode 100644 index 0000000..cfe3476 --- /dev/null +++ b/tests/Mocale.UnitTests/Resources/Misc/TestFile.json @@ -0,0 +1,22 @@ +{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +} diff --git a/tests/Mocale.UnitTests/Resources/en-GB.json b/tests/Mocale.UnitTests/Resources/en-GB.json new file mode 100644 index 0000000..adaaecf --- /dev/null +++ b/tests/Mocale.UnitTests/Resources/en-GB.json @@ -0,0 +1,8 @@ +{ + "CurrentLocaleName": "English", + "LocalizationCurrentProviderIs": "The current localization provider is:", + "LocalizationProviderName": "Json", + "MocaleDescription": "Localization framework for .NET Maui", + "MocaleTitle": "Mocale", + "ExternalPrefixExplanation": "Strings prefixed with GR_ indicate they have been pulled from the external provider (GitHub.Raw), when the local cache expires if these values change, so will the text displayed!" +}