diff --git a/src/OrchardCore/OrchardCore.Localization.Abstractions/ILocalizationManager.cs b/src/OrchardCore/OrchardCore.Localization.Abstractions/ILocalizationManager.cs index fde185debd6..76a842b8b6d 100644 --- a/src/OrchardCore/OrchardCore.Localization.Abstractions/ILocalizationManager.cs +++ b/src/OrchardCore/OrchardCore.Localization.Abstractions/ILocalizationManager.cs @@ -12,5 +12,5 @@ public interface ILocalizationManager /// /// The . /// A for the specified culture. - CultureDictionary GetDictionary(CultureInfo culture); + Task GetDictionaryAsync(CultureInfo culture); } diff --git a/src/OrchardCore/OrchardCore.Localization.Abstractions/ITranslationProvider.cs b/src/OrchardCore/OrchardCore.Localization.Abstractions/ITranslationProvider.cs index 734c9f3e51b..c72a10f7b66 100644 --- a/src/OrchardCore/OrchardCore.Localization.Abstractions/ITranslationProvider.cs +++ b/src/OrchardCore/OrchardCore.Localization.Abstractions/ITranslationProvider.cs @@ -10,5 +10,5 @@ public interface ITranslationProvider /// /// The culture name. /// The that will contains all loaded translations. - void LoadTranslations(string cultureName, CultureDictionary dictionary); + Task LoadTranslationsAsync(string cultureName, CultureDictionary dictionary); } diff --git a/src/OrchardCore/OrchardCore.Localization.Core/LocalizationManager.cs b/src/OrchardCore/OrchardCore.Localization.Core/LocalizationManager.cs index 8fd61b47b66..e64028087de 100644 --- a/src/OrchardCore/OrchardCore.Localization.Core/LocalizationManager.cs +++ b/src/OrchardCore/OrchardCore.Localization.Core/LocalizationManager.cs @@ -33,9 +33,9 @@ public LocalizationManager( } /// - public CultureDictionary GetDictionary(CultureInfo culture) + public async Task GetDictionaryAsync(CultureInfo culture) { - var cachedDictionary = _cache.GetOrCreate(CacheKeyPrefix + culture.Name, k => new Lazy(() => + var cachedDictionary = await _cache.GetOrCreateAsync(CacheKeyPrefix + culture.Name, async (e) => { var rule = _defaultPluralRule; @@ -50,12 +50,12 @@ public CultureDictionary GetDictionary(CultureInfo culture) var dictionary = new CultureDictionary(culture.Name, rule ?? _defaultPluralRule); foreach (var translationProvider in _translationProviders) { - translationProvider.LoadTranslations(culture.Name, dictionary); + await translationProvider.LoadTranslationsAsync(culture.Name, dictionary); } return dictionary; - }, LazyThreadSafetyMode.ExecutionAndPublication)); + }); - return cachedDictionary.Value; + return cachedDictionary; } } diff --git a/src/OrchardCore/OrchardCore.Localization.Core/OrchardCore.Localization.Core.csproj b/src/OrchardCore/OrchardCore.Localization.Core/OrchardCore.Localization.Core.csproj index 252b3fd7c7c..fa9070a6894 100644 --- a/src/OrchardCore/OrchardCore.Localization.Core/OrchardCore.Localization.Core.csproj +++ b/src/OrchardCore/OrchardCore.Localization.Core/OrchardCore.Localization.Core.csproj @@ -14,6 +14,7 @@ + diff --git a/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PoFilesTranslationsProvider.cs b/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PoFilesTranslationsProvider.cs index 73b7be45549..6aa65b5ba43 100644 --- a/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PoFilesTranslationsProvider.cs +++ b/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PoFilesTranslationsProvider.cs @@ -21,21 +21,21 @@ public PoFilesTranslationsProvider(ILocalizationFileLocationProvider poFileLocat } /// - public void LoadTranslations(string cultureName, CultureDictionary dictionary) + public async Task LoadTranslationsAsync(string cultureName, CultureDictionary dictionary) { foreach (var fileInfo in _poFilesLocationProvider.GetLocations(cultureName)) { - LoadFileToDictionary(fileInfo, dictionary); + await LoadFileToDictionaryAsync(fileInfo, dictionary); } } - private void LoadFileToDictionary(IFileInfo fileInfo, CultureDictionary dictionary) + private async Task LoadFileToDictionaryAsync(IFileInfo fileInfo, CultureDictionary dictionary) { if (fileInfo.Exists && !fileInfo.IsDirectory) { using var stream = fileInfo.CreateReadStream(); using var reader = new StreamReader(stream); - dictionary.MergeTranslations(_parser.Parse(reader)); + dictionary.MergeTranslations(await _parser.ParseAsync(reader).ToListAsync()); } } } diff --git a/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PoParser.cs b/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PoParser.cs index 10f5157f6e0..df46d222fc1 100644 --- a/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PoParser.cs +++ b/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PoParser.cs @@ -26,12 +26,12 @@ static PoParser() /// The . /// A list of culture records. #pragma warning disable CA1822 // Mark members as static - public IEnumerable Parse(TextReader reader) + public async IAsyncEnumerable ParseAsync(TextReader reader) #pragma warning restore CA1822 // Mark members as static { var entryBuilder = new DictionaryRecordBuilder(); string line; - while ((line = reader.ReadLine()) != null) + while ((line = await reader.ReadLineAsync()) != null) { (var context, var content) = ParseLine(line); diff --git a/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PortableObjectStringLocalizer.cs b/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PortableObjectStringLocalizer.cs index 9f0085c1eae..a6f238bcf5c 100644 --- a/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PortableObjectStringLocalizer.cs +++ b/src/OrchardCore/OrchardCore.Localization.Core/PortableObject/PortableObjectStringLocalizer.cs @@ -66,10 +66,11 @@ public virtual LocalizedString this[string name] public virtual IEnumerable GetAllStrings(bool includeParentCultures) { var culture = CultureInfo.CurrentUICulture; + var localizedStrings = includeParentCultures + ? GetAllStringsFromCultureHierarchyAsync(culture) + : GetAllStringsAsync(culture); - return includeParentCultures - ? GetAllStringsFromCultureHierarchy(culture) - : GetAllStrings(culture); + return localizedStrings.ToEnumerable(); } /// @@ -106,9 +107,9 @@ public virtual (LocalizedString, object[]) GetTranslation(string name, params ob } } - private IEnumerable GetAllStrings(CultureInfo culture) + private async IAsyncEnumerable GetAllStringsAsync(CultureInfo culture) { - var dictionary = _localizationManager.GetDictionary(culture); + var dictionary = await _localizationManager.GetDictionaryAsync(culture); foreach (var translation in dictionary.Translations) { @@ -116,35 +117,35 @@ private IEnumerable GetAllStrings(CultureInfo culture) } } - private List GetAllStringsFromCultureHierarchy(CultureInfo culture) + private async IAsyncEnumerable GetAllStringsFromCultureHierarchyAsync(CultureInfo culture) { var currentCulture = culture; - var allLocalizedStrings = new List(); + var resourcesNames = new HashSet(); do { - var localizedStrings = GetAllStrings(currentCulture); + var localizedStrings = await GetAllStringsAsync(currentCulture).ToListAsync(); if (localizedStrings != null) { foreach (var localizedString in localizedStrings) { - if (!allLocalizedStrings.Any(ls => ls.Name == localizedString.Name)) + if (!resourcesNames.Contains(localizedString.Name)) { - allLocalizedStrings.Add(localizedString); + resourcesNames.Add(localizedString.Name); + + yield return localizedString; } } } currentCulture = currentCulture.Parent; } while (currentCulture != currentCulture.Parent); - - return allLocalizedStrings; } protected string GetTranslation(string[] pluralForms, CultureInfo culture, int? count) { - var dictionary = _localizationManager.GetDictionary(culture); + var dictionary = _localizationManager.GetDictionaryAsync(culture).GetAwaiter().GetResult(); var pluralForm = count.HasValue ? dictionary.PluralRule(count.Value) : 0; @@ -187,7 +188,7 @@ protected string GetTranslation(string name, string context, CultureInfo culture string ExtractTranslation() { - var dictionary = _localizationManager.GetDictionary(culture); + var dictionary = _localizationManager.GetDictionaryAsync(culture).GetAwaiter().GetResult(); if (dictionary != null) { diff --git a/test/OrchardCore.Tests/Localization/LocalizationManagerTests.cs b/test/OrchardCore.Tests/Localization/LocalizationManagerTests.cs index 83dfede050d..459db02f096 100644 --- a/test/OrchardCore.Tests/Localization/LocalizationManagerTests.cs +++ b/test/OrchardCore.Tests/Localization/LocalizationManagerTests.cs @@ -24,57 +24,57 @@ public LocalizationManagerTests() _memoryCache = new MemoryCache(new MemoryCacheOptions()); } - [Fact] - public void GetDictionaryReturnsDictionaryWithPluralRuleAndCultureIfNoTranslationsExists() - { - _translationProvider.Setup(o => o.LoadTranslations( - It.Is(culture => culture == "cs"), - It.IsAny()) - ); + [Fact] + public async Task GetDictionaryReturnsDictionaryWithPluralRuleAndCultureIfNoTranslationsExists() + { + _translationProvider.Setup(o => o.LoadTranslationsAsync( + It.Is(culture => culture == "cs"), + It.IsAny()) + ); var manager = new LocalizationManager(new[] { _pluralRuleProvider.Object }, new[] { _translationProvider.Object }, _memoryCache); - var dictionary = manager.GetDictionary(CultureInfo.GetCultureInfo("cs")); + var dictionary = await manager.GetDictionaryAsync(CultureInfo.GetCultureInfo("cs")); Assert.Equal("cs", dictionary.CultureName); Assert.Equal(PluralizationRule.Czech, dictionary.PluralRule); } - [Fact] - public void GetDictionaryReturnsDictionaryWithTranslationsFromProvider() - { - var dictionaryRecord = new CultureDictionaryRecord("ball", "míč", "míče", "míčů"); - _translationProvider - .Setup(o => o.LoadTranslations(It.Is(culture => culture == "cs"), It.IsAny())) - .Callback((culture, dictioanry) => dictioanry.MergeTranslations(new[] { dictionaryRecord })); + [Fact] + public async Task GetDictionaryReturnsDictionaryWithTranslationsFromProvider() + { + var dictionaryRecord = new CultureDictionaryRecord("ball", "míč", "míče", "míčů"); + _translationProvider + .Setup(o => o.LoadTranslationsAsync(It.Is(culture => culture == "cs"), It.IsAny())) + .Callback((culture, dictioanry) => dictioanry.MergeTranslations(new[] { dictionaryRecord })); var manager = new LocalizationManager([_pluralRuleProvider.Object], [_translationProvider.Object], _memoryCache); - var dictionary = manager.GetDictionary(CultureInfo.GetCultureInfo("cs")); - var key = new CultureDictionaryRecordKey { MessageId = "ball" }; + var dictionary = await manager.GetDictionaryAsync(CultureInfo.GetCultureInfo("cs")); + var key = new CultureDictionaryRecordKey { MessageId = "ball" }; dictionary.Translations.TryGetValue(key, out var translations); Assert.Equal(translations, dictionaryRecord.Translations); } - [Fact] - public void GetDictionarySelectsPluralRuleFromProviderWithHigherPriority() - { - PluralizationRuleDelegate csPluralRuleOverride = n => ((n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 0); + [Fact] + public async Task GetDictionarySelectsPluralRuleFromProviderWithHigherPriority() + { + PluralizationRuleDelegate csPluralRuleOverride = n => ((n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 0); var highPriorityRuleProvider = new Mock(); highPriorityRuleProvider.SetupGet(o => o.Order).Returns(-1); highPriorityRuleProvider.Setup(o => o.TryGetRule(It.Is(culture => culture.Name == "cs"), out csPluralRuleOverride)).Returns(true); - _translationProvider.Setup(o => o.LoadTranslations( - It.Is(culture => culture == "cs"), - It.IsAny()) - ); + _translationProvider.Setup(o => o.LoadTranslationsAsync( + It.Is(culture => culture == "cs"), + It.IsAny()) + ); var manager = new LocalizationManager([_pluralRuleProvider.Object, highPriorityRuleProvider.Object], [_translationProvider.Object], _memoryCache); - var dictionary = manager.GetDictionary(CultureInfo.GetCultureInfo("cs")); + var dictionary = await manager.GetDictionaryAsync(CultureInfo.GetCultureInfo("cs")); Assert.Equal(dictionary.PluralRule, csPluralRuleOverride); } diff --git a/test/OrchardCore.Tests/Localization/PoParserTests.cs b/test/OrchardCore.Tests/Localization/PoParserTests.cs index 1fa026943f8..e992c26bb62 100644 --- a/test/OrchardCore.Tests/Localization/PoParserTests.cs +++ b/test/OrchardCore.Tests/Localization/PoParserTests.cs @@ -6,28 +6,28 @@ namespace OrchardCore.Tests.Localization; public class PoParserTests { [Fact] - public void ParseRetursSimpleEntry() + public async Task ParseRetursSimpleEntry() { // msgid "Unknown system error" // msgstr "Error desconegut del sistema" - var entries = ParseText("SimpleEntry"); + var entries = await ParseTextAsync("SimpleEntry"); Assert.Equal("Unknown system error", entries[0].Key); Assert.Equal("Error desconegut del sistema", entries[0].Translations[0]); } [Fact] - public void ParseIgnoresEntryWithoutTranslation() + public async Task ParseIgnoresEntryWithoutTranslation() { // "msgid "Unknown system error" // "msgstr "" - var entries = ParseText("EntryWithoutTranslation"); + var entries = await ParseTextAsync("EntryWithoutTranslation"); Assert.Empty(entries); } [Fact] - public void ParseIgnoresPoeditHeader() + public async Task ParseIgnoresPoeditHeader() { // # Translation of kstars.po into Spanish. // # This file is distributed under the same license as the kdeedu package. @@ -48,25 +48,25 @@ public void ParseIgnoresPoeditHeader() // msgid "Unknown system error" // msgstr "Error desconegut del sistema" - var entries = ParseText("PoeditHeader"); + var entries = await ParseTextAsync("PoeditHeader"); Assert.True(entries.Length == 1); Assert.True(entries[0].Translations.Length == 1); } [Fact] - public void ParseSetsContext() + public async Task ParseSetsContext() { // msgctxt "OrchardCore.Localization" // msgid "Unknown system error" // msgstr "Error desconegut del sistema" - var entries = ParseText("EntryWithContext"); + var entries = await ParseTextAsync("EntryWithContext"); Assert.Equal("OrchardCore.Localization|Unknown system error", entries[0].Key, ignoreCase: true); } [Fact] - public void ParseIgnoresComments() + public async Task ParseIgnoresComments() { // # translator-comments // #. extracted-comments @@ -79,38 +79,38 @@ public void ParseIgnoresComments() // msgid "Unknown system error" // msgstr "Error desconegut del sistema" - var entries = ParseText("EntryWithComments"); + var entries = await ParseTextAsync("EntryWithComments"); Assert.Equal("OrchardCore.Localization|Unknown system error", entries[0].Key, ignoreCase: true); Assert.Equal("Error desconegut del sistema", entries[0].Translations[0]); } [Fact] - public void ParseOnlyTrimsLeadingAndTrailingQuotes() + public async Task ParseOnlyTrimsLeadingAndTrailingQuotes() { // msgid "\"{0}\"" // msgstr "\"{0}\"" - var entries = ParseText("EntryWithQuotes"); + var entries = await ParseTextAsync("EntryWithQuotes"); Assert.Equal("\"{0}\"", entries[0].Key); Assert.Equal("\"{0}\"", entries[0].Translations[0]); } [Fact] - public void ParseHandleUnclosedQuote() + public async Task ParseHandleUnclosedQuote() { // msgctxt " // msgid "Foo \"{0}\"" // msgstr "Foo \"{0}\"" - var entries = ParseText("EntryWithUnclosedQuote"); + var entries = await ParseTextAsync("EntryWithUnclosedQuote"); Assert.Equal("Foo \"{0}\"", entries[0].Key); } [Fact] - public void ParseHandlesMultilineEntry() + public async Task ParseHandlesMultilineEntry() { // msgid "" // "Here is an example of how one might continue a very long string\n" @@ -119,26 +119,26 @@ public void ParseHandlesMultilineEntry() // "Here is an example of how one might continue a very long translation\n" // "for the common case the string represents multi-line output." - var entries = ParseText("EntryWithMultilineText"); + var entries = await ParseTextAsync("EntryWithMultilineText"); Assert.Equal("Here is an example of how one might continue a very long string\nfor the common case the string represents multi-line output.", entries[0].Key); Assert.Equal("Here is an example of how one might continue a very long translation\nfor the common case the string represents multi-line output.", entries[0].Translations[0]); } [Fact] - public void ParsePreservesEscapedCharacters() + public async Task ParsePreservesEscapedCharacters() { // msgid "Line:\t\"{0}\"\n" // msgstr "Line:\t\"{0}\"\n" - var entries = ParseText("EntryWithEscapedCharacters"); + var entries = await ParseTextAsync("EntryWithEscapedCharacters"); Assert.Equal("Line:\t\"{0}\"\n", entries[0].Key); Assert.Equal("Line:\t\"{0}\"\n", entries[0].Translations[0]); } [Fact] - public void ParseReadsPluralTranslations() + public async Task ParseReadsPluralTranslations() { // msgid "book" // msgid_plural "books" @@ -146,7 +146,7 @@ public void ParseReadsPluralTranslations() // msgstr[1] "knihy" // msgstr[2] "knih" - var entries = ParseText("EntryWithPlural"); + var entries = await ParseTextAsync("EntryWithPlural"); Assert.Equal("book", entries[0].Key); Assert.Equal("kniha", entries[0].Translations[0]); @@ -155,7 +155,7 @@ public void ParseReadsPluralTranslations() } [Fact] - public void ParseReadsPluralAndMultilineText() + public async Task ParseReadsPluralAndMultilineText() { // msgid "" // "Here is an example of how one might continue a very long string\n" @@ -170,7 +170,7 @@ public void ParseReadsPluralAndMultilineText() // "Here are examples of how one might continue a very long translation\n" // "for the common case the string represents multi-line output." - var entries = ParseText("EntryWithPluralAndMultilineText"); + var entries = await ParseTextAsync("EntryWithPluralAndMultilineText"); Assert.Equal("Here is an example of how one might continue a very long string\nfor the common case the string represents multi-line output.", entries[0].Key); Assert.Equal("Here is an example of how one might continue a very long translation\nfor the common case the string represents multi-line output.", entries[0].Translations[0]); @@ -178,7 +178,7 @@ public void ParseReadsPluralAndMultilineText() } [Fact] - public void ParseReadsMultipleEntries() + public async Task ParseReadsMultipleEntries() { // #. "File {0} does not exist" // msgctxt "OrchardCore.FileSystems.Media.FileSystemStorageProvider" @@ -190,7 +190,7 @@ public void ParseReadsMultipleEntries() // msgid "Directory {0} does not exist" // msgstr "Složka {0} neexistuje" - var entries = ParseText("MultipleEntries"); + var entries = await ParseTextAsync("MultipleEntries"); Assert.Equal(2, entries.Length); @@ -201,13 +201,14 @@ public void ParseReadsMultipleEntries() Assert.Equal("Složka {0} neexistuje", entries[1].Translations[0]); } - private static CultureDictionaryRecord[] ParseText(string resourceName) + private static async Task ParseTextAsync(string resourceName) { var parser = new PoParser(); var testAssembly = typeof(PoParserTests).Assembly; using var resource = testAssembly.GetManifestResourceStream("OrchardCore.Tests.Localization.PoFiles." + resourceName + ".po"); using var reader = new StreamReader(resource); - return parser.Parse(reader).ToArray(); + + return (await parser.ParseAsync(reader).ToListAsync()).ToArray(); } } diff --git a/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerTests.cs b/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerTests.cs index e3d6712c832..4c0fb44d3a1 100644 --- a/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerTests.cs +++ b/test/OrchardCore.Tests/Localization/PortableObjectStringLocalizerTests.cs @@ -342,8 +342,8 @@ public void LocalizerWithContextShouldCallGetDictionaryOncePerCulture(string cul // Act var translation = localizer["Hello"]; - // Assert - _localizationManager.Verify(lm => lm.GetDictionary(It.IsAny()), Times.Exactly(expectedCalls)); + // Assert + _localizationManager.Verify(lm => lm.GetDictionaryAsync(It.IsAny()), Times.Exactly(expectedCalls)); Assert.Equal("Hello", translation); } @@ -359,8 +359,9 @@ private void SetupDictionary(string cultureName, IEnumerable o.GetDictionary(It.Is(c => c.Name == cultureName))).Returns(dictionary); - } + _localizationManager.Setup(o => o.GetDictionaryAsync(It.Is(c => c.Name == cultureName))) + .ReturnsAsync(dictionary); + } public class PortableObjectLocalizationStartup {