From d4c854da51cf8f6d31e9990245beedd7b4d324e7 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 2 May 2026 16:23:00 +0200 Subject: [PATCH 1/3] Remove the obsolete ILocalizationService and implementation and update all callers to non-obsolete alternatives. --- src/Umbraco.Core/CLAUDE.md | 3 +- .../DependencyInjection/UmbracoBuilder.cs | 1 - .../Dictionary/DefaultCultureDictionary.cs | 34 +- .../DefaultCultureDictionaryFactory.cs | 15 +- .../PublishedValueFallback.cs | 10 +- src/Umbraco.Core/Models/UserExtensions.cs | 6 +- .../DictionaryItemDeletedNotification.cs | 2 +- .../DictionaryItemDeletingNotification.cs | 2 +- .../DictionaryItemSavedNotification.cs | 2 +- .../DictionaryItemSavingNotification.cs | 2 +- .../LanguageDeletedNotification.cs | 2 +- .../LanguageDeletingNotification.cs | 2 +- .../LanguageSavedNotification.cs | 2 +- .../LanguageSavingNotification.cs | 2 +- .../Packaging/PackagesRepository.cs | 63 +-- .../PublishedCache/DefaultCultureAccessor.cs | 8 +- .../ContentBlueprintEditingService.cs | 5 +- .../Services/ContentEditingService.cs | 5 +- .../Services/ContentEditingServiceBase.cs | 7 +- .../ContentEditingServiceWithSortingBase.cs | 6 +- .../Services/DictionaryService.cs | 39 +- .../Services/ElementEditingService.cs | 4 +- .../Services/EntityXmlSerializer.cs | 14 +- .../Services/IDictionaryService.cs | 2 +- .../Services/ILocalizationService.cs | 106 ------ .../Services/LocalizationService.cs | 184 --------- .../Services/MediaEditingService.cs | 6 +- .../Services/MemberContentEditingService.cs | 5 +- .../Services/NotificationService.cs | 12 +- src/Umbraco.Core/Services/ServiceContext.cs | 30 +- .../UmbracoContentIndex.cs | 6 +- .../UmbracoBuilder.Examine.cs | 2 - .../UmbracoBuilder.Services.cs | 15 +- .../Examine/ContentValueSetBuilder.cs | 6 +- .../Packaging/PackageDataInstallation.cs | 180 +++------ .../Search/UmbracoTreeSearcherFields.cs | 10 +- .../Providers/LanguagesTelemetryProvider.cs | 10 +- ...bleshootingInformationTelemetryProvider.cs | 10 +- .../Templates/TemplateRenderer.cs | 2 - .../CompatibilitySuppressions.xml | 7 - .../CreatedPackagesRepositoryTests.cs | 45 ++- .../UmbracoExamine/IndexInitializer.cs | 16 +- .../DataTypeDefinitionRepositoryTest.cs | 2 - ...stElementLevelVariationTests.Publishing.cs | 2 +- .../Services/EntityXmlSerializerTests.cs | 4 +- .../Services/LocalizationServiceTests.cs | 360 ------------------ .../UrlAndDomains/DomainAndUrlsTests.cs | 2 +- .../Models/UserExtensionsTests.cs | 41 ++ .../Services/DictionaryServiceTests.cs | 78 ++++ .../Services/SystemInformationServiceTests.cs | 12 +- ...ootingInformationTelemetryProviderTests.cs | 2 +- .../Examine/ContentValueSetBuilderTests.cs | 8 - 52 files changed, 412 insertions(+), 989 deletions(-) delete mode 100644 src/Umbraco.Core/Services/ILocalizationService.cs delete mode 100644 src/Umbraco.Core/Services/LocalizationService.cs delete mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs create mode 100644 tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DictionaryServiceTests.cs diff --git a/src/Umbraco.Core/CLAUDE.md b/src/Umbraco.Core/CLAUDE.md index da1b406ed9e9..065a14f7fa1d 100644 --- a/src/Umbraco.Core/CLAUDE.md +++ b/src/Umbraco.Core/CLAUDE.md @@ -263,7 +263,8 @@ public class MyEntityCacheRefresher : CacheRefresherBase - `IMediaService` - Media operations - `IDataTypeService` - Data type configuration - `IUserService` - User management -- `ILocalizationService` - Languages and dictionary +- `ILanguageService` - Languages +- `IDictionaryItemService` - Dictionary items - `IRelationService` - Entity relationships #### Content Models diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index ea7bfc9a7bb8..31753405af27 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -294,7 +294,6 @@ private void AddCoreServices() Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); - Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Dictionary/DefaultCultureDictionary.cs b/src/Umbraco.Core/Dictionary/DefaultCultureDictionary.cs index fa4b25d64d47..40e7915d621a 100644 --- a/src/Umbraco.Core/Dictionary/DefaultCultureDictionary.cs +++ b/src/Umbraco.Core/Dictionary/DefaultCultureDictionary.cs @@ -8,41 +8,39 @@ namespace Umbraco.Cms.Core.Dictionary; /// -/// A culture dictionary that uses the Umbraco ILocalizationService +/// A culture dictionary that uses the Umbraco ILanguageService and IDictionaryItemService. /// /// /// TODO: The ICultureDictionary needs to represent the 'fast' way to do dictionary item retrieval - for front-end and /// back office. -/// The ILocalizationService is the service used for interacting with this data from the database which isn't all that -/// fast -/// (even though there is caching involved, if there's lots of dictionary items the caching is not great) +/// ILanguageService and IDictionaryItemService are the services used for interacting with this data from the database +/// which isn't all that fast (even though there is caching involved, if there's lots of dictionary items the caching is +/// not great). /// internal sealed class DefaultCultureDictionary : ICultureDictionary { - private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + private readonly IDictionaryItemService _dictionaryItemService; private readonly IAppCache _requestCache; private readonly CultureInfo? _specificCulture; /// /// Default constructor which will use the current thread's culture /// - /// - /// - public DefaultCultureDictionary(ILocalizationService localizationService, IAppCache requestCache) + public DefaultCultureDictionary(ILanguageService languageService, IDictionaryItemService dictionaryItemService, IAppCache requestCache) { - _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _languageService = languageService ?? throw new ArgumentNullException(nameof(languageService)); + _dictionaryItemService = dictionaryItemService ?? throw new ArgumentNullException(nameof(dictionaryItemService)); _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); } /// /// Constructor for testing to specify a static culture /// - /// - /// - /// - public DefaultCultureDictionary(CultureInfo specificCulture, ILocalizationService localizationService, IAppCache requestCache) + public DefaultCultureDictionary(CultureInfo specificCulture, ILanguageService languageService, IDictionaryItemService dictionaryItemService, IAppCache requestCache) { - _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _languageService = languageService ?? throw new ArgumentNullException(nameof(languageService)); + _dictionaryItemService = dictionaryItemService ?? throw new ArgumentNullException(nameof(dictionaryItemService)); _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); _specificCulture = specificCulture ?? throw new ArgumentNullException(nameof(specificCulture)); } @@ -64,7 +62,7 @@ public DefaultCultureDictionary(CultureInfo specificCulture, ILocalizationServic CultureInfo culture = Culture; while (culture != CultureInfo.InvariantCulture) { - ILanguage? language = _localizationService.GetLanguageByIsoCode(culture.Name); + ILanguage? language = _languageService.GetAsync(culture.Name).GetAwaiter().GetResult(); if (language != null) { return language; @@ -85,7 +83,7 @@ public string this[string key] { get { - IDictionaryItem? found = _localizationService.GetDictionaryItemByKey(key); + IDictionaryItem? found = _dictionaryItemService.GetAsync(key).GetAwaiter().GetResult(); if (found == null) { return string.Empty; @@ -115,13 +113,13 @@ public IDictionary GetChildren(string key) { var result = new Dictionary(); - IDictionaryItem? found = _localizationService.GetDictionaryItemByKey(key); + IDictionaryItem? found = _dictionaryItemService.GetAsync(key).GetAwaiter().GetResult(); if (found == null) { return result; } - IEnumerable? children = _localizationService.GetDictionaryItemChildren(found.Key); + IEnumerable? children = _dictionaryItemService.GetChildrenAsync(found.Key).GetAwaiter().GetResult(); if (children == null) { return result; diff --git a/src/Umbraco.Core/Dictionary/DefaultCultureDictionaryFactory.cs b/src/Umbraco.Core/Dictionary/DefaultCultureDictionaryFactory.cs index 273aad718619..b3acd971a70a 100644 --- a/src/Umbraco.Core/Dictionary/DefaultCultureDictionaryFactory.cs +++ b/src/Umbraco.Core/Dictionary/DefaultCultureDictionaryFactory.cs @@ -14,24 +14,27 @@ namespace Umbraco.Cms.Core.Dictionary; public class DefaultCultureDictionaryFactory : ICultureDictionaryFactory { private readonly AppCaches _appCaches; - private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + private readonly IDictionaryItemService _dictionaryItemService; /// /// Initializes a new instance of the class. /// - /// The localization service for accessing dictionary items. + /// The language service. + /// The dictionary item service. /// The application caches containing the request cache. - public DefaultCultureDictionaryFactory(ILocalizationService localizationService, AppCaches appCaches) + public DefaultCultureDictionaryFactory(ILanguageService languageService, IDictionaryItemService dictionaryItemService, AppCaches appCaches) { - _localizationService = localizationService; + _languageService = languageService; + _dictionaryItemService = dictionaryItemService; _appCaches = appCaches; } /// public ICultureDictionary CreateDictionary() => - new DefaultCultureDictionary(_localizationService, _appCaches.RequestCache); + new DefaultCultureDictionary(_languageService, _dictionaryItemService, _appCaches.RequestCache); /// public ICultureDictionary CreateDictionary(CultureInfo specificCulture) => - new DefaultCultureDictionary(specificCulture, _localizationService, _appCaches.RequestCache); + new DefaultCultureDictionary(specificCulture, _languageService, _dictionaryItemService, _appCaches.RequestCache); } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs index 425753d4f7ba..a1141f2eaf6d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Core.Models.PublishedContent; /// public class PublishedValueFallback : IPublishedValueFallback { - private readonly ILocalizationService? _localizationService; + private readonly ILanguageService? _languageService; private readonly IVariationContextAccessor _variationContextAccessor; private readonly IPropertyRenderingContextAccessor _propertyRenderingContextAccessor; @@ -23,7 +23,7 @@ public class PublishedValueFallback : IPublishedValueFallback /// The property rendering context accessor. public PublishedValueFallback(ServiceContext serviceContext, IVariationContextAccessor variationContextAccessor, IPropertyRenderingContextAccessor propertyRenderingContextAccessor) { - _localizationService = serviceContext.LocalizationService; + _languageService = serviceContext.LanguageService; _variationContextAccessor = variationContextAccessor; _propertyRenderingContextAccessor = propertyRenderingContextAccessor; } @@ -303,7 +303,7 @@ private bool TryGetValueWithLanguageFallback(TryGetValueForCultureAndSegment< var visited = new HashSet(); - ILanguage? language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; + ILanguage? language = culture is not null ? _languageService?.GetAsync(culture).GetAwaiter().GetResult() : null; if (language == null) { return false; @@ -324,7 +324,7 @@ private bool TryGetValueWithLanguageFallback(TryGetValueForCultureAndSegment< visited.Add(language2IsoCode); - ILanguage? language2 = _localizationService?.GetLanguageByIsoCode(language2IsoCode); + ILanguage? language2 = _languageService?.GetAsync(language2IsoCode).GetAwaiter().GetResult(); if (language2 == null) { return false; @@ -371,7 +371,7 @@ private bool TryGetValueWithDefaultLanguageFallback(TryGetValueForCultureAndS return false; } - var defaultCulture = _localizationService?.GetDefaultLanguageIsoCode(); + var defaultCulture = _languageService?.GetDefaultIsoCodeAsync().GetAwaiter().GetResult(); if (defaultCulture.IsNullOrWhiteSpace()) { return false; diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 11a556d31864..796044c40593 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -224,14 +224,14 @@ public static bool HasAccessToSensitiveData(this IUser user) } /// - /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin + /// Calculate the set of language ids the user is allowed to access, combining group permissions. /// - public static int[] CalculateAllowedLanguageIds(this IUser user, ILocalizationService localizationService) + public static async Task CalculateAllowedLanguageIdsAsync(this IUser user, ILanguageService languageService) { var hasAccessToAllLanguages = user.Groups.Any(x => x.HasAccessToAllLanguages); return hasAccessToAllLanguages - ? localizationService.GetAllLanguages().Select(x => x.Id).ToArray() + ? (await languageService.GetAllAsync()).Select(x => x.Id).ToArray() : user.Groups.SelectMany(x => x.AllowedLanguages).Distinct().ToArray(); } diff --git a/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs index 8c8d9ada8828..7ac409920572 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILocalizationService when the Delete (IDictionaryItem overload) method is called in the API, after the dictionary items has been deleted. +/// A notification that is used to trigger the IDictionaryItemService when the Delete (IDictionaryItem overload) method is called in the API, after the dictionary items has been deleted. /// public class DictionaryItemDeletedNotification : DeletedNotification { diff --git a/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs index 537f0370a322..537cca853612 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILocalizationService when the Delete (IDictionaryItem overload) method is called in the API. +/// A notification that is used to trigger the IDictionaryItemService when the Delete (IDictionaryItem overload) method is called in the API. /// /// /// This notification is cancelable, allowing handlers to prevent the delete operation diff --git a/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs index 9e2d125372c8..f85cc5e8ff0c 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILocalizationService when the Save (IDictionaryItem overload) method is called in the API and the data has been persisted. +/// A notification that is used to trigger the IDictionaryItemService when the Save (IDictionaryItem overload) method is called in the API and the data has been persisted. /// public class DictionaryItemSavedNotification : SavedNotification { diff --git a/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs index 27d2b7647ea8..f7876ddba9f8 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILocalizationService when the Save (IDictionaryItem overload) method is called in the API. +/// A notification that is used to trigger the IDictionaryItemService when the Save (IDictionaryItem overload) method is called in the API. /// /// /// This notification is cancelable, allowing handlers to prevent the save operation diff --git a/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs b/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs index 50cf7d6426cb..edc807d2817f 100644 --- a/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILocalizationService when the Delete (ILanguage overload) method is called in the API, after the languages have been deleted. +/// A notification that is used to trigger the ILanguageService when the Delete (ILanguage overload) method is called in the API, after the languages have been deleted. /// public class LanguageDeletedNotification : DeletedNotification { diff --git a/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs b/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs index 201a5ba4fc0d..07ddad5520c7 100644 --- a/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILocalizationService when the Delete (ILanguage overload) method is called in the API. +/// A notification that is used to trigger the ILanguageService when the Delete (ILanguage overload) method is called in the API. /// public class LanguageDeletingNotification : DeletingNotification { diff --git a/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs b/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs index 0c5342e1686f..514f61799aeb 100644 --- a/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILocalizationService when the Save (ILanguage overload) method is called in the API, after data has been persisted. +/// A notification that is used to trigger the ILanguageService when the Save (ILanguage overload) method is called in the API, after data has been persisted. /// public class LanguageSavedNotification : SavedNotification { diff --git a/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs b/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs index 23eec3cd91be..9b03beca66a2 100644 --- a/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILocalizationService when the Save (ILanguage overload) method is called in the API. +/// A notification that is used to trigger the ILanguageService when the Save (ILanguage overload) method is called in the API. /// public class LanguageSavingNotification : SavingNotification { diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index fe01af794ba5..bbc72c65be16 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -7,6 +7,8 @@ using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; using File = System.IO.File; @@ -27,7 +29,9 @@ public class PackagesRepository : ICreatedPackagesRepository private readonly IFileService _fileService; private readonly FileSystems _fileSystems; private readonly IHostingEnvironment _hostingEnvironment; - private readonly ILocalizationService _languageService; + private readonly ILanguageRepository _languageRepository; + private readonly IDictionaryRepository _dictionaryRepository; + private readonly ICoreScopeProvider _scopeProvider; private readonly MediaFileManager _mediaFileManager; private readonly IMediaService _mediaService; private readonly IMediaTypeService _mediaTypeService; @@ -44,7 +48,9 @@ public class PackagesRepository : ICreatedPackagesRepository /// /// /// - /// + /// + /// + /// /// /// /// @@ -63,7 +69,9 @@ public PackagesRepository( IContentTypeService contentTypeService, IDataTypeService dataTypeService, IFileService fileService, - ILocalizationService languageService, + ILanguageRepository languageRepository, + IDictionaryRepository dictionaryRepository, + ICoreScopeProvider scopeProvider, IHostingEnvironment hostingEnvironment, IEntityXmlSerializer serializer, IOptions globalSettings, @@ -85,7 +93,9 @@ public PackagesRepository( _contentTypeService = contentTypeService; _dataTypeService = dataTypeService; _fileService = fileService; - _languageService = languageService; + _languageRepository = languageRepository; + _dictionaryRepository = dictionaryRepository; + _scopeProvider = scopeProvider; _serializer = serializer; _hostingEnvironment = hostingEnvironment; _packageRepositoryFileName = packageRepositoryFileName; @@ -392,20 +402,23 @@ private void PackageDataTypes(PackageDefinition definition, XContainer root) private void PackageLanguages(PackageDefinition definition, XContainer root) { var languages = new XElement("Languages"); - foreach (var langId in definition.Languages) + using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true)) { - if (!int.TryParse(langId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + foreach (var langId in definition.Languages) { - continue; - } + if (!int.TryParse(langId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } - ILanguage? lang = _languageService.GetLanguageById(outInt); - if (lang == null) - { - continue; - } + ILanguage? lang = _languageRepository.Get(outInt); + if (lang == null) + { + continue; + } - languages.Add(_serializer.Serialize(lang)); + languages.Add(_serializer.Serialize(lang)); + } } root.Add(languages); @@ -421,21 +434,23 @@ private void PackageDictionaryItems(PackageDefinition definition, XContainer roo var rootDictionaryItems = new XElement("DictionaryItems"); var items = new Dictionary(); - foreach (var dictionaryId in definition.DictionaryItems) + using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true)) { - if (!int.TryParse(dictionaryId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + foreach (var dictionaryId in definition.DictionaryItems) { - continue; - } + if (!int.TryParse(dictionaryId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } - IDictionaryItem? di = _languageService.GetDictionaryItemById(outInt); + IDictionaryItem? di = _dictionaryRepository.Get(outInt); + if (di == null) + { + continue; + } - if (di == null) - { - continue; + items[di.Key] = (di, _serializer.Serialize(di, false)); } - - items[di.Key] = (di, _serializer.Serialize(di, false)); } // organize them in hierarchy ... diff --git a/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs b/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs index 4068bc4477f9..7919c2761e90 100644 --- a/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs @@ -9,16 +9,16 @@ namespace Umbraco.Cms.Core.PublishedCache; /// public class DefaultCultureAccessor : IDefaultCultureAccessor { - private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; private readonly IRuntimeState _runtimeState; private GlobalSettings _options; /// /// Initializes a new instance of the class. /// - public DefaultCultureAccessor(ILocalizationService localizationService, IRuntimeState runtimeState, IOptionsMonitor options) + public DefaultCultureAccessor(ILanguageService languageService, IRuntimeState runtimeState, IOptionsMonitor options) { - _localizationService = localizationService; + _languageService = languageService; _runtimeState = runtimeState; _options = options.CurrentValue; options.OnChange(x => _options = x); @@ -26,6 +26,6 @@ public DefaultCultureAccessor(ILocalizationService localizationService, IRuntime /// public string DefaultCulture => _runtimeState.Level == RuntimeLevel.Run - ? _localizationService.GetDefaultLanguageIsoCode() ?? string.Empty // fast + ? _languageService.GetDefaultIsoCodeAsync().GetAwaiter().GetResult() ?? string.Empty // fast : _options.DefaultUILanguage; // default for install and upgrade, when the service is n/a } diff --git a/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs b/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs index e055a02d4c4e..f8cdc6708a5e 100644 --- a/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs +++ b/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs @@ -49,9 +49,8 @@ public ContentBlueprintEditingService( IRelationService relationService, ContentTypeFilterCollection contentTypeFilters, ILanguageService languageService, - IUserService userService, - ILocalizationService localizationService) - : base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, validationService, optionsMonitor, relationService, contentTypeFilters, languageService, userService, localizationService) + IUserService userService) + : base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, validationService, optionsMonitor, relationService, contentTypeFilters, languageService, userService) => _containerService = containerService; /// diff --git a/src/Umbraco.Core/Services/ContentEditingService.cs b/src/Umbraco.Core/Services/ContentEditingService.cs index 807481f5db7a..aa7ee7bdfbba 100644 --- a/src/Umbraco.Core/Services/ContentEditingService.cs +++ b/src/Umbraco.Core/Services/ContentEditingService.cs @@ -33,7 +33,6 @@ internal sealed class ContentEditingService /// The tree entity sorting service. /// The content validation service. /// The user service. - /// The localization service. /// The language service. /// The content settings options monitor. /// The relation service. @@ -50,7 +49,6 @@ public ContentEditingService( ITreeEntitySortingService treeEntitySortingService, IContentValidationService contentValidationService, IUserService userService, - ILocalizationService localizationService, ILanguageService languageService, IOptionsMonitor optionsMonitor, IRelationService relationService, @@ -69,8 +67,7 @@ public ContentEditingService( relationService, contentTypeFilters, languageService, - userService, - localizationService) + userService) { _templateService = templateService; _logger = logger; diff --git a/src/Umbraco.Core/Services/ContentEditingServiceBase.cs b/src/Umbraco.Core/Services/ContentEditingServiceBase.cs index bcb2b4b3ba5c..71fadd257589 100644 --- a/src/Umbraco.Core/Services/ContentEditingServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentEditingServiceBase.cs @@ -37,7 +37,6 @@ internal abstract class ContentEditingServiceBase /// Initializes a new instance of the class. @@ -66,8 +65,7 @@ protected ContentEditingServiceBase( IRelationService relationService, ContentTypeFilterCollection contentTypeFilters, ILanguageService languageService, - IUserService userService, - ILocalizationService localizationService) + IUserService userService) { _propertyEditorCollection = propertyEditorCollection; _dataTypeService = dataTypeService; @@ -87,7 +85,6 @@ protected ContentEditingServiceBase( _contentTypeFilters = contentTypeFilters; _languageService = languageService; _userService = userService; - _localizationService = localizationService; } public abstract Task GetAsync(Guid key); @@ -780,7 +777,7 @@ protected async Task> GetAllowedCulturesForEditingUser(Guid user IUser user = await _userService.GetAsync(userKey) ?? throw new InvalidOperationException($"Could not find user by key {userKey} when editing or validating content."); - var allowedLanguageIds = user.CalculateAllowedLanguageIds(_localizationService)!; + var allowedLanguageIds = (await user.CalculateAllowedLanguageIdsAsync(_languageService))!; return (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet(); } diff --git a/src/Umbraco.Core/Services/ContentEditingServiceWithSortingBase.cs b/src/Umbraco.Core/Services/ContentEditingServiceWithSortingBase.cs index 03d13d961550..237982ee44b0 100644 --- a/src/Umbraco.Core/Services/ContentEditingServiceWithSortingBase.cs +++ b/src/Umbraco.Core/Services/ContentEditingServiceWithSortingBase.cs @@ -56,8 +56,7 @@ protected ContentEditingServiceWithSortingBase( IRelationService relationService, ContentTypeFilterCollection contentTypeFilters, ILanguageService languageService, - IUserService userService, - ILocalizationService localizationService) + IUserService userService) : base( contentService, contentTypeService, @@ -71,8 +70,7 @@ protected ContentEditingServiceWithSortingBase( relationService, contentTypeFilters, languageService, - userService, - localizationService) + userService) { _logger = logger; _treeEntitySortingService = treeEntitySortingService; diff --git a/src/Umbraco.Core/Services/DictionaryService.cs b/src/Umbraco.Core/Services/DictionaryService.cs index 53b9688d056f..16c21c73337d 100644 --- a/src/Umbraco.Core/Services/DictionaryService.cs +++ b/src/Umbraco.Core/Services/DictionaryService.cs @@ -7,41 +7,34 @@ namespace Umbraco.Cms.Core.Services; /// public class DictionaryService : IDictionaryService { - private readonly ILocalizationService _localizationService; + private readonly IDictionaryItemService _dictionaryItemService; /// /// Initializes a new instance of the class. /// - /// The localization service. - public DictionaryService(ILocalizationService localizationService) => _localizationService = localizationService; + /// The dictionary item service. + public DictionaryService(IDictionaryItemService dictionaryItemService) => _dictionaryItemService = dictionaryItemService; /// - public string CalculatePath(Guid? parentId, int sourceId) + public async Task CalculatePathAsync(Guid? parentId, int sourceId) { - string path; - - // TODO: check if there is a better way - if (parentId.HasValue) - { - var ids = new List { -1 }; - var parentIds = new List(); - GetParentId(parentId.Value, parentIds); - parentIds.Reverse(); - ids.AddRange(parentIds); - ids.Add(sourceId); - path = string.Join(",", ids); - } - else + if (parentId.HasValue is false) { - path = "-1," + sourceId; + return "-1," + sourceId; } - return path; + var ids = new List { -1 }; + var parentIds = new List(); + await GetParentIdAsync(parentId.Value, parentIds); + parentIds.Reverse(); + ids.AddRange(parentIds); + ids.Add(sourceId); + return string.Join(",", ids); } - private void GetParentId(Guid parentId, List ids) + private async Task GetParentIdAsync(Guid parentId, List ids) { - IDictionaryItem? dictionary = _localizationService.GetDictionaryItemById(parentId); + IDictionaryItem? dictionary = await _dictionaryItemService.GetAsync(parentId); if (dictionary == null) { return; @@ -51,7 +44,7 @@ private void GetParentId(Guid parentId, List ids) if (dictionary.ParentId.HasValue) { - GetParentId(dictionary.ParentId.Value, ids); + await GetParentIdAsync(dictionary.ParentId.Value, ids); } } } diff --git a/src/Umbraco.Core/Services/ElementEditingService.cs b/src/Umbraco.Core/Services/ElementEditingService.cs index 256a936ef930..818b7baff75f 100644 --- a/src/Umbraco.Core/Services/ElementEditingService.cs +++ b/src/Umbraco.Core/Services/ElementEditingService.cs @@ -45,7 +45,6 @@ public ElementEditingService( IIdKeyMap idKeyMap, ILanguageService languageService, IUserService userService, - ILocalizationService localizationService, IAuditService auditService) : base( elementService, @@ -60,8 +59,7 @@ public ElementEditingService( relationService, contentTypeFilters, languageService, - userService, - localizationService) + userService) { _elementService = elementService; _logger = logger; diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index 4e150f7b5022..86820943d5ec 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -23,7 +23,7 @@ internal sealed class EntityXmlSerializer : IEntityXmlSerializer private readonly IContentService _contentService; private readonly IContentTypeService _contentTypeService; private readonly IDataTypeService _dataTypeService; - private readonly ILocalizationService _localizationService; + private readonly IDictionaryItemService _dictionaryItemService; private readonly IMediaService _mediaService; private readonly PropertyEditorCollection _propertyEditors; private readonly IShortStringHelper _shortStringHelper; @@ -37,7 +37,7 @@ internal sealed class EntityXmlSerializer : IEntityXmlSerializer /// The media service for media operations. /// The data type service for data type operations. /// The user service for user lookups. - /// The localization service for dictionary items. + /// The dictionary item service for dictionary item operations. /// The content type service for content type operations. /// The collection of URL segment providers. /// The helper for string operations. @@ -48,7 +48,7 @@ public EntityXmlSerializer( IMediaService mediaService, IDataTypeService dataTypeService, IUserService userService, - ILocalizationService localizationService, + IDictionaryItemService dictionaryItemService, IContentTypeService contentTypeService, UrlSegmentProviderCollection urlSegmentProviders, IShortStringHelper shortStringHelper, @@ -60,7 +60,7 @@ public EntityXmlSerializer( _contentService = contentService; _dataTypeService = dataTypeService; _userService = userService; - _localizationService = localizationService; + _dictionaryItemService = dictionaryItemService; _urlSegmentProviders = urlSegmentProviders; _shortStringHelper = shortStringHelper; _propertyEditors = propertyEditors; @@ -132,9 +132,9 @@ public XElement Serialize( throw new ArgumentNullException(nameof(_userService)); } - if (_localizationService == null) + if (_dictionaryItemService == null) { - throw new ArgumentNullException(nameof(_localizationService)); + throw new ArgumentNullException(nameof(_dictionaryItemService)); } if (media == null) @@ -264,7 +264,7 @@ public XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren) if (includeChildren) { - IEnumerable? children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key); + IEnumerable? children = _dictionaryItemService.GetChildrenAsync(dictionaryItem.Key).GetAwaiter().GetResult(); if (children is not null) { foreach (IDictionaryItem child in children) diff --git a/src/Umbraco.Core/Services/IDictionaryService.cs b/src/Umbraco.Core/Services/IDictionaryService.cs index 66fefaac4b35..f6c887e6fe53 100644 --- a/src/Umbraco.Core/Services/IDictionaryService.cs +++ b/src/Umbraco.Core/Services/IDictionaryService.cs @@ -11,5 +11,5 @@ public interface IDictionaryService /// The unique identifier of the parent dictionary item, or null for root items. /// The source identifier of the dictionary item. /// The calculated path string. - string CalculatePath(Guid? parentId, int sourceId); + Task CalculatePathAsync(Guid? parentId, int sourceId); } diff --git a/src/Umbraco.Core/Services/ILocalizationService.cs b/src/Umbraco.Core/Services/ILocalizationService.cs deleted file mode 100644 index 3a70fed08a23..000000000000 --- a/src/Umbraco.Core/Services/ILocalizationService.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Services; - -/// -/// Defines the Localization Service, which is an easy access to operations involving Languages and Dictionary -/// -[Obsolete("Please use ILanguageService and IDictionaryItemService for localization. Scheduled for removal in Umbraco 18.")] -public interface ILocalizationService : IService -{ - // Possible to-do list: - // Import DictionaryItem (?) - // RemoveByLanguage (translations) - // Add/Set Text (Insert/Update) - // Remove Text (in translation) - - /// - /// Gets a by its id - /// - /// Id of the - /// - /// - /// - [Obsolete("Please use IDictionaryItemService for dictionary item operations. Scheduled for removal in Umbraco 18.")] - IDictionaryItem? GetDictionaryItemById(int id); - - /// - /// Gets a by its id - /// - /// Id of the - /// - /// - /// - [Obsolete("Please use IDictionaryItemService for dictionary item operations. Scheduled for removal in Umbraco 18.")] - IDictionaryItem? GetDictionaryItemById(Guid id); - - /// - /// Gets a by its key - /// - /// Key of the - /// - /// - /// - [Obsolete("Please use IDictionaryItemService for dictionary item operations. Scheduled for removal in Umbraco 18.")] - IDictionaryItem? GetDictionaryItemByKey(string key); - - /// - /// Gets a list of children for a - /// - /// Id of the parent - /// An enumerable list of objects - [Obsolete("Please use IDictionaryItemService for dictionary item operations. Scheduled for removal in Umbraco 18.")] - IEnumerable GetDictionaryItemChildren(Guid parentId); - - /// - /// Saves a object - /// - /// to save - /// Optional id of the user saving the dictionary item - [Obsolete("Please use IDictionaryItemService for dictionary item operations. Scheduled for removal in Umbraco 18.")] - void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId); - - /// - /// Gets a by its id - /// - /// Id of the - /// - /// - /// - [Obsolete("Please use ILanguageService for language operations. Scheduled for removal in Umbraco 18.")] - ILanguage? GetLanguageById(int id); - - /// - /// Gets a by its iso code - /// - /// Iso Code of the language (ie. en-US) - /// - /// - /// - [Obsolete("Please use ILanguageService for language operations. Scheduled for removal in Umbraco 18.")] - ILanguage? GetLanguageByIsoCode(string? isoCode); - - /// - /// Gets the default language ISO code. - /// - /// - /// This can be optimized and bypass all deep cloning. - /// - [Obsolete("Please use ILanguageService for language operations. Scheduled for removal in Umbraco 18.")] - string GetDefaultLanguageIsoCode(); - - /// - /// Gets all available languages - /// - /// An enumerable list of objects - [Obsolete("Please use ILanguageService for language operations. Scheduled for removal in Umbraco 18.")] - IEnumerable GetAllLanguages(); - - /// - /// Saves a object - /// - /// to save - /// Optional id of the user saving the language - [Obsolete("Please use ILanguageService for language operations. Scheduled for removal in Umbraco 18.")] - void Save(ILanguage language, int userId = Constants.Security.SuperUserId); -} diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs deleted file mode 100644 index 9caa9eedba26..000000000000 --- a/src/Umbraco.Core/Services/LocalizationService.cs +++ /dev/null @@ -1,184 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Services; - -/// -/// Represents the Localization Service, which is an easy access to operations involving and -/// -/// -[Obsolete("Please use ILanguageService and IDictionaryItemService for localization. Scheduled for removal in Umbraco 18.")] -internal class LocalizationService : RepositoryService, ILocalizationService -{ - private readonly IDictionaryRepository _dictionaryRepository; - private readonly ILanguageRepository _languageRepository; - private readonly ILanguageService _languageService; - private readonly IDictionaryItemService _dictionaryItemService; - private readonly IUserIdKeyResolver _userIdKeyResolver; - - /// - /// Initializes a new instance of the class. - /// - /// The core scope provider. - /// The logger factory. - /// The event messages factory. - /// The dictionary repository. - /// The language repository. - /// The language service. - /// The dictionary item service. - /// The user ID key resolver. - [Obsolete("Please use ILanguageService and IDictionaryItemService for localization. Scheduled for removal in Umbraco 18.")] - public LocalizationService( - ICoreScopeProvider provider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IDictionaryRepository dictionaryRepository, - ILanguageRepository languageRepository, - ILanguageService languageService, - IDictionaryItemService dictionaryItemService, - IUserIdKeyResolver userIdKeyResolver) - : base(provider, loggerFactory, eventMessagesFactory) - { - _dictionaryRepository = dictionaryRepository; - _languageRepository = languageRepository; - _languageService = languageService; - _dictionaryItemService = dictionaryItemService; - _userIdKeyResolver = userIdKeyResolver; - } - - /// - /// Gets a by its id - /// - /// Id of the - /// - /// - /// - [Obsolete("Please use IDictionaryItemService for dictionary item operations. Scheduled for removal in Umbraco 18.")] - public IDictionaryItem? GetDictionaryItemById(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _dictionaryRepository.Get(id); - } - } - - /// - /// Gets a by its id - /// - /// Id of the - /// - /// - /// - [Obsolete("Please use IDictionaryItemService for dictionary item operations. Scheduled for removal in Umbraco 18.")] - public IDictionaryItem? GetDictionaryItemById(Guid id) - => _dictionaryItemService.GetAsync(id).GetAwaiter().GetResult(); - - /// - /// Gets a by its key - /// - /// Key of the - /// - /// - /// - [Obsolete("Please use IDictionaryItemService for dictionary item operations. Scheduled for removal in Umbraco 18.")] - public IDictionaryItem? GetDictionaryItemByKey(string key) - => _dictionaryItemService.GetAsync(key).GetAwaiter().GetResult(); - - /// - /// Gets a list of children for a - /// - /// Id of the parent - /// An enumerable list of objects - [Obsolete("Please use IDictionaryItemService for dictionary item operations. Scheduled for removal in Umbraco 18.")] - public IEnumerable GetDictionaryItemChildren(Guid parentId) - => _dictionaryItemService.GetChildrenAsync(parentId).GetAwaiter().GetResult(); - - /// - /// Saves a object - /// - /// to save - /// Optional id of the user saving the dictionary item - [Obsolete("Please use IDictionaryItemService for dictionary item operations. Scheduled for removal in Umbraco 18.")] - public void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId) - { - Guid currentUserKey = _userIdKeyResolver.GetAsync(userId).GetAwaiter().GetResult(); - if (dictionaryItem.Id > 0) - { - _dictionaryItemService.UpdateAsync(dictionaryItem, currentUserKey).GetAwaiter().GetResult(); - } - else - { - _dictionaryItemService.CreateAsync(dictionaryItem, currentUserKey).GetAwaiter().GetResult(); - } - } - - /// - /// Gets a by its id - /// - /// Id of the - /// - /// - /// - [Obsolete("Please use ILanguageService for language operations. Scheduled for removal in Umbraco 18.")] - public ILanguage? GetLanguageById(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _languageRepository.Get(id); - } - } - - /// - /// Gets a by its iso code - /// - /// Iso Code of the language (ie. en-US) - /// - /// - /// - [Obsolete("Please use ILanguageService for language operations. Scheduled for removal in Umbraco 18.")] - public ILanguage? GetLanguageByIsoCode(string? isoCode) - { - ArgumentException.ThrowIfNullOrEmpty(isoCode); - return _languageService.GetAsync(isoCode).GetAwaiter().GetResult(); - } - - /// - [Obsolete("Please use ILanguageService for language operations. Scheduled for removal in Umbraco 18.")] - public string GetDefaultLanguageIsoCode() - => _languageService.GetDefaultIsoCodeAsync().GetAwaiter().GetResult(); - - /// - /// Gets all available languages - /// - /// An enumerable list of objects - [Obsolete("Please use ILanguageService for language operations. Scheduled for removal in Umbraco 18.")] - public IEnumerable GetAllLanguages() - => _languageService.GetAllAsync().GetAwaiter().GetResult(); - - /// - /// Saves a object - /// - /// to save - /// Optional id of the user saving the language - [Obsolete("Please use ILanguageService for language operations. Scheduled for removal in Umbraco 18.")] - public void Save(ILanguage language, int userId = Constants.Security.SuperUserId) - { - Guid currentUserKey = _userIdKeyResolver.GetAsync(userId).GetAwaiter().GetResult(); - Attempt result = language.Id > 0 - ? _languageService.UpdateAsync(language, currentUserKey).GetAwaiter().GetResult() - : _languageService.CreateAsync(language, currentUserKey).GetAwaiter().GetResult(); - - // mimic old Save behavior - if (result.Status == LanguageOperationStatus.InvalidFallback) - { - throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback {language.FallbackIsoCode}."); - } - } -} diff --git a/src/Umbraco.Core/Services/MediaEditingService.cs b/src/Umbraco.Core/Services/MediaEditingService.cs index 7428e4015ce7..9e2dad54a130 100644 --- a/src/Umbraco.Core/Services/MediaEditingService.cs +++ b/src/Umbraco.Core/Services/MediaEditingService.cs @@ -48,8 +48,7 @@ public MediaEditingService( IRelationService relationService, ContentTypeFilterCollection contentTypeFilters, ILanguageService languageService, - IUserService userService, - ILocalizationService localizationService) + IUserService userService) : base( contentService, contentTypeService, @@ -64,8 +63,7 @@ public MediaEditingService( relationService, contentTypeFilters, languageService, - userService, - localizationService) + userService) => _logger = logger; /// diff --git a/src/Umbraco.Core/Services/MemberContentEditingService.cs b/src/Umbraco.Core/Services/MemberContentEditingService.cs index a8fb2eb36446..78fb11661fd3 100644 --- a/src/Umbraco.Core/Services/MemberContentEditingService.cs +++ b/src/Umbraco.Core/Services/MemberContentEditingService.cs @@ -54,9 +54,8 @@ public MemberContentEditingService( IOptionsMonitor optionsMonitor, IRelationService relationService, ContentTypeFilterCollection contentTypeFilters, - ILanguageService languageService, - ILocalizationService localizationService) - : base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, memberValidationService, optionsMonitor, relationService, contentTypeFilters, languageService, userService, localizationService) + ILanguageService languageService) + : base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, memberValidationService, optionsMonitor, relationService, contentTypeFilters, languageService, userService) { _logger = logger; _userService = userService; diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index aaae2b3ea656..fe6b54b50cf0 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -32,7 +32,7 @@ public class NotificationService : INotificationService private readonly IEmailSender _emailSender; private readonly GlobalSettings _globalSettings; private readonly IIOHelper _ioHelper; - private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; private readonly ILogger _logger; private readonly INotificationsRepository _notificationsRepository; private readonly ICoreScopeProvider _uowProvider; @@ -44,7 +44,7 @@ public class NotificationService : INotificationService /// The scope provider for unit of work operations. /// The user service for retrieving user information. /// The content service for accessing content versions. - /// The localization service for language operations. + /// The language service for language operations. /// The logger for diagnostic output. /// The IO helper for resolving paths. /// The repository for notification data access. @@ -55,7 +55,7 @@ public NotificationService( ICoreScopeProvider provider, IUserService userService, IContentService contentService, - ILocalizationService localizationService, + ILanguageService languageService, ILogger logger, IIOHelper ioHelper, INotificationsRepository notificationsRepository, @@ -70,7 +70,7 @@ public NotificationService( _uowProvider = provider ?? throw new ArgumentNullException(nameof(provider)); _userService = userService ?? throw new ArgumentNullException(nameof(userService)); _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService)); - _localizationService = localizationService; + _languageService = languageService; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _ioHelper = ioHelper; } @@ -394,7 +394,7 @@ private NotificationRequest CreateNotificationRequest( // Create the HTML based summary (ul of culture names) IEnumerable? culturesChanged = content.CultureInfos?.Values.Where(x => x.WasDirty()) .Select(x => x.Culture) - .Select(_localizationService.GetLanguageByIsoCode) + .Select(isoCode => _languageService.GetAsync(isoCode).GetAwaiter().GetResult()) .WhereNotNull() .Select(x => x.CultureName); summary.Append("
    "); @@ -415,7 +415,7 @@ private NotificationRequest CreateNotificationRequest( // Create the text based summary (csv of culture names) var culturesChanged = string.Join(", ", content.CultureInfos!.Values.Where(x => x.WasDirty()) .Select(x => x.Culture) - .Select(_localizationService.GetLanguageByIsoCode) + .Select(isoCode => _languageService.GetAsync(isoCode).GetAwaiter().GetResult()) .WhereNotNull() .Select(x => x.CultureName)); diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 15147558287e..5dca8f2d2014 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -16,7 +16,8 @@ public class ServiceContext private readonly Lazy? _externalLoginService; private readonly Lazy? _fileService; private readonly Lazy? _keyValueService; - private readonly Lazy? _localizationService; + private readonly Lazy? _languageService; + private readonly Lazy? _dictionaryItemService; private readonly Lazy? _localizedTextService; private readonly Lazy? _mediaService; private readonly Lazy? _mediaTypeService; @@ -49,7 +50,8 @@ public class ServiceContext /// The media type service. /// The data type service. /// The file service. - /// The localization service. + /// The language service. + /// The dictionary item service. /// The packaging service. /// The server registration service. /// The entity service. @@ -77,7 +79,8 @@ public ServiceContext( Lazy? mediaTypeService, Lazy? dataTypeService, Lazy? fileService, - Lazy? localizationService, + Lazy? languageService, + Lazy? dictionaryItemService, Lazy? packagingService, Lazy? serverRegistrationService, Lazy? entityService, @@ -105,7 +108,8 @@ public ServiceContext( _mediaTypeService = mediaTypeService; _dataTypeService = dataTypeService; _fileService = fileService; - _localizationService = localizationService; + _languageService = languageService; + _dictionaryItemService = dictionaryItemService; _packagingService = packagingService; _serverRegistrationService = serverRegistrationService; _entityService = entityService; @@ -192,9 +196,14 @@ public ServiceContext( public IFileService? FileService => _fileService?.Value; /// - /// Gets the + /// Gets the . /// - public ILocalizationService? LocalizationService => _localizationService?.Value; + public ILanguageService? LanguageService => _languageService?.Value; + + /// + /// Gets the . + /// + public IDictionaryItemService? DictionaryItemService => _dictionaryItemService?.Value; /// /// Gets the @@ -265,7 +274,8 @@ public ServiceContext( /// The media type service. /// The data type service. /// The file service. - /// The localization service. + /// The language service. + /// The dictionary item service. /// The packaging service. /// The entity service. /// The relation service. @@ -297,7 +307,8 @@ public static ServiceContext CreatePartial( IMediaTypeService? mediaTypeService = null, IDataTypeService? dataTypeService = null, IFileService? fileService = null, - ILocalizationService? localizationService = null, + ILanguageService? languageService = null, + IDictionaryItemService? dictionaryItemService = null, IPackagingService? packagingService = null, IEntityService? entityService = null, IRelationService? relationService = null, @@ -338,7 +349,8 @@ public static ServiceContext CreatePartial( Lazy(mediaTypeService), Lazy(dataTypeService), Lazy(fileService), - Lazy(localizationService), + Lazy(languageService), + Lazy(dictionaryItemService), Lazy(packagingService), Lazy(serverRegistrationService), Lazy(entityService), diff --git a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs index db01e40de809..04115490c5f8 100644 --- a/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs @@ -28,7 +28,7 @@ public UmbracoContentIndex( IOptionsMonitor indexOptions, IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState, - ILocalizationService? languageService = null) + ILanguageService? languageService = null) : base(loggerFactory, name, indexOptions, hostingEnvironment, runtimeState) { LanguageService = languageService; @@ -45,9 +45,9 @@ public UmbracoContentIndex( } /// - /// Gets the . + /// Gets the . /// - protected ILocalizationService? LanguageService { get; } + protected ILanguageService? LanguageService { get; } /// /// Explicitly override because we need to do validation differently than the underlying logic diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs index 0324f4efd396..11ec6ac67b8b 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs @@ -53,7 +53,6 @@ public static IUmbracoBuilder AddExamine(this IUmbracoBuilder builder) factory.GetRequiredService(), factory.GetRequiredService(), true, - factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService>(), factory.GetRequiredService(), @@ -66,7 +65,6 @@ public static IUmbracoBuilder AddExamine(this IUmbracoBuilder builder) factory.GetRequiredService(), factory.GetRequiredService(), false, - factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService>(), factory.GetRequiredService(), diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 4719a921ea1e..04773676d462 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -12,7 +12,9 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -103,7 +105,9 @@ private static PackagesRepository CreatePackageRepository(IServiceProvider facto factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), - factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService>(), @@ -119,19 +123,22 @@ private static IPackageDataInstallation CreatePackageDataInstallation(IServicePr factory.GetRequiredService(), factory.GetRequiredService>(), factory.GetRequiredService(), - factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), - factory.GetRequiredService(), + factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), - factory.GetRequiredService()); + factory.GetRequiredService(), + factory.GetRequiredService()); private static LocalizedTextServiceFileSources CreateLocalizedTextServiceFileSourcesFactory( IServiceProvider container) diff --git a/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs b/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs index abced5febe30..e6d0678ad392 100644 --- a/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs @@ -24,7 +24,6 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal private readonly IShortStringHelper _shortStringHelper; private readonly UrlSegmentProviderCollection _urlSegmentProviders; private readonly IUserService _userService; - private readonly ILocalizationService _localizationService; private readonly IContentTypeService _contentTypeService; private readonly ILogger _logger; private readonly IDocumentUrlService _documentUrlService; @@ -39,7 +38,6 @@ public class ContentValueSetBuilder : BaseValueSetBuilder, IContentVal /// The helper used for generating and manipulating short strings. /// The provider for managing database scopes. /// If set to true, only published values will be used. - /// The service used for localization and translations. /// The service used to manage content types. /// The logger used for logging information and errors. /// The service used to generate document URLs. @@ -51,7 +49,6 @@ public ContentValueSetBuilder( IShortStringHelper shortStringHelper, ICoreScopeProvider scopeProvider, bool publishedValuesOnly, - ILocalizationService localizationService, IContentTypeService contentTypeService, ILogger logger, IDocumentUrlService documentUrlService, @@ -62,7 +59,6 @@ public ContentValueSetBuilder( _userService = userService; _shortStringHelper = shortStringHelper; _scopeProvider = scopeProvider; - _localizationService = localizationService; _contentTypeService = contentTypeService; _logger = logger; _documentUrlService = documentUrlService; @@ -167,7 +163,7 @@ private IEnumerable GetValueSetsEnumerable(IContent[] content, Diction var availableCultures = new List(c.AvailableCultures); if (availableCultures.Any() is false) { - availableCultures.Add(_localizationService.GetDefaultLanguageIsoCode()); + availableCultures.Add(defaultCulture); } foreach (IProperty property in c.Properties) diff --git a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs index 1b987a1c7831..723c0832c4a3 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs @@ -17,6 +17,7 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; @@ -31,7 +32,9 @@ public class PackageDataInstallation : IPackageDataInstallation private readonly IDataValueEditorFactory _dataValueEditorFactory; private readonly ILogger _logger; private readonly IFileService _fileService; - private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + private readonly IDictionaryItemService _dictionaryItemService; + private readonly IUserIdKeyResolver _userIdKeyResolver; private readonly IDataTypeService _dataTypeService; private readonly PropertyEditorCollection _propertyEditors; private readonly IScopeProvider _scopeProvider; @@ -53,7 +56,9 @@ public class PackageDataInstallation : IPackageDataInstallation /// Factory for creating data value editors used in property editing. /// The logger used for logging installation events and errors. /// Service for managing files such as templates, stylesheets, and scripts. - /// Service for managing localization and dictionary items. + /// Service for managing languages. + /// Service for managing dictionary items. + /// Resolver for converting user IDs to user keys. /// Service for managing data types within Umbraco. /// Service for managing Umbraco entities generically. /// Service for managing content types (document types, media types, etc.). @@ -71,7 +76,9 @@ public PackageDataInstallation( IDataValueEditorFactory dataValueEditorFactory, ILogger logger, IFileService fileService, - ILocalizationService localizationService, + ILanguageService languageService, + IDictionaryItemService dictionaryItemService, + IUserIdKeyResolver userIdKeyResolver, IDataTypeService dataTypeService, IEntityService entityService, IContentTypeService contentTypeService, @@ -89,7 +96,9 @@ public PackageDataInstallation( _dataValueEditorFactory = dataValueEditorFactory; _logger = logger; _fileService = fileService; - _localizationService = localizationService; + _languageService = languageService; + _dictionaryItemService = dictionaryItemService; + _userIdKeyResolver = userIdKeyResolver; _dataTypeService = dataTypeService; _entityService = entityService; _contentTypeService = contentTypeService; @@ -105,120 +114,6 @@ public PackageDataInstallation( _memberTypeService = memberTypeService; } - /// - /// Initializes a new instance of the class, - /// providing all required services and helpers for package data installation operations. - /// - /// Factory for creating data value editors. - /// The logger used for logging installation events and errors. - /// Service for managing files within the CMS. - /// Service for handling localization and translations. - /// Service for managing data types. - /// Service for managing entities. - /// Service for managing content types. - /// Service for managing content items. - /// A collection of property editors available in the system. - /// Provider for managing database transaction scopes. - /// Helper for handling short string operations. - /// Serializer for configuration editor JSON data. - /// Service for managing media items. - /// Service for managing media types. - /// Service for parsing template content. - /// Service for managing templates. - [Obsolete("Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.")] - public PackageDataInstallation( - IDataValueEditorFactory dataValueEditorFactory, - ILogger logger, - IFileService fileService, - ILocalizationService localizationService, - IDataTypeService dataTypeService, - IEntityService entityService, - IContentTypeService contentTypeService, - IContentService contentService, - PropertyEditorCollection propertyEditors, - IScopeProvider scopeProvider, - IShortStringHelper shortStringHelper, - IConfigurationEditorJsonSerializer serializer, - IMediaService mediaService, - IMediaTypeService mediaTypeService, - ITemplateContentParserService templateContentParserService, - ITemplateService templateService) - : this( - dataValueEditorFactory, - logger, - fileService, - localizationService, - dataTypeService, - entityService, - contentTypeService, - contentService, - propertyEditors, - scopeProvider, - shortStringHelper, - serializer, - mediaService, - mediaTypeService, - templateContentParserService, - templateService, - StaticServiceProvider.Instance.GetRequiredService()) - { } - - /// - /// Initializes a new instance of the class, responsible for handling the installation of package data within Umbraco. - /// - /// Factory for creating data value editors used in property editing. - /// The logger used for logging installation operations and errors. - /// Service for managing files such as templates, stylesheets, and scripts. - /// Service for managing language and dictionary items. - /// Service for managing data types within Umbraco. - /// Service for accessing and managing Umbraco entities. - /// Service for managing content types and media types. - /// Service for managing content items (nodes) in Umbraco. - /// A collection of property editors available in the system. - /// Provides database transaction scopes for data operations. - /// Helper for generating and manipulating short strings, such as aliases. - /// The global settings options for the Umbraco installation. - /// Serializer for configuration editor JSON data. - /// Service for managing media items (files, images, etc.). - /// Service for managing media types. - /// Provides information about the web hosting environment. - [Obsolete("Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.")] - public PackageDataInstallation( - IDataValueEditorFactory dataValueEditorFactory, - ILogger logger, - IFileService fileService, - ILocalizationService localizationService, - IDataTypeService dataTypeService, - IEntityService entityService, - IContentTypeService contentTypeService, - IContentService contentService, - PropertyEditorCollection propertyEditors, - Core.Scoping.IScopeProvider scopeProvider, - IShortStringHelper shortStringHelper, - IOptions globalSettings, - IConfigurationEditorJsonSerializer serializer, - IMediaService mediaService, - IMediaTypeService mediaTypeService, - IHostingEnvironment hostingEnvironment) - : this( - dataValueEditorFactory, - logger, - fileService, - localizationService, - dataTypeService, - entityService, - contentTypeService, - contentService, - propertyEditors, - (IScopeProvider)scopeProvider, - shortStringHelper, - serializer, - mediaService, - mediaTypeService, - StaticServiceProvider.Instance.GetRequiredService(), - StaticServiceProvider.Instance.GetRequiredService()) - { } - #region Install/Uninstall /// @@ -553,7 +448,7 @@ where property.Attribute("isDoc") == null const string nodeNamePrefix = "nodeName-"; // Get the installed culture iso names, we create a localized content node with a culture that does not exist in the project // We have to use Invariant comparisons, because when we get them from ContentBase in EntityXmlSerializer they're all lowercase. - var installedLanguages = _localizationService.GetAllLanguages().Select(l => l.IsoCode).ToArray(); + var installedLanguages = _languageService.GetAllAsync().GetAwaiter().GetResult().Select(l => l.IsoCode).ToArray(); foreach (XAttribute localizedNodeName in element.Attributes() .Where(a => a.Name.LocalName.InvariantStartsWith(nodeNamePrefix))) { @@ -1627,7 +1522,7 @@ public IReadOnlyList ImportDictionaryItems( IEnumerable dictionaryItemElementList, int userId) { - var languages = _localizationService.GetAllLanguages().ToList(); + var languages = _languageService.GetAllAsync().GetAwaiter().GetResult().ToList(); return ImportDictionaryItems(dictionaryItemElementList, languages, null, userId); } @@ -1640,7 +1535,7 @@ public IReadOnlyList ImportDictionaryItems( /// An containing the imported dictionary item(s), including any nested child items. public IEnumerable ImportDictionaryItem(XElement dictionaryItemElement, int userId, Guid? parentId) { - var languages = _localizationService.GetAllLanguages().ToList(); + var languages = _languageService.GetAllAsync().GetAwaiter().GetResult().ToList(); return ImportDictionaryItem(dictionaryItemElement, languages, parentId, userId); } @@ -1671,18 +1566,32 @@ private IEnumerable ImportDictionaryItem( var itemName = dictionaryItemElement.Attribute("Name")?.Value; Guid key = dictionaryItemElement.RequiredAttributeValue("Key"); - dictionaryItem = _localizationService.GetDictionaryItemById(key); - if (dictionaryItem != null) + dictionaryItem = _dictionaryItemService.GetAsync(key).GetAwaiter().GetResult(); + var isUpdate = dictionaryItem != null; + if (isUpdate) { - dictionaryItem = UpdateDictionaryItem(dictionaryItem, dictionaryItemElement, languages); + dictionaryItem = UpdateDictionaryItem(dictionaryItem!, dictionaryItemElement, languages); } else { dictionaryItem = CreateNewDictionaryItem(key, itemName!, dictionaryItemElement, languages, parentId); } - _localizationService.Save(dictionaryItem, userId); - items.Add(dictionaryItem); + Guid currentUserKey = ResolveUserKey(userId); + Attempt saveResult = isUpdate + ? _dictionaryItemService.UpdateAsync(dictionaryItem!, currentUserKey).GetAwaiter().GetResult() + : _dictionaryItemService.CreateAsync(dictionaryItem!, currentUserKey).GetAwaiter().GetResult(); + + if (saveResult.Success is false) + { + _logger.LogWarning( + "Failed to {Operation} dictionary item {Key} during package import: {Status}", + isUpdate ? "update" : "create", + key, + saveResult.Status); + } + + items.Add(dictionaryItem!); items.AddRange(ImportDictionaryItems( dictionaryItemElement.Elements("DictionaryItem"), @@ -1755,6 +1664,13 @@ private static void AddDictionaryTranslation( translations.Add(translation); } + // Resolves an int user id to its Guid key, falling back to SuperUserKey for unknown ids. + private Guid ResolveUserKey(int userId) + { + Attempt attempt = _userIdKeyResolver.TryGetAsync(userId).GetAwaiter().GetResult(); + return attempt.Success ? attempt.Result : Constants.Security.SuperUserKey; + } + #endregion #region Languages @@ -1776,7 +1692,7 @@ public IReadOnlyList ImportLanguages(IEnumerable languageEl continue; } - ILanguage? existingLanguage = _localizationService.GetLanguageByIsoCode(isoCode); + ILanguage? existingLanguage = _languageService.GetAsync(isoCode).GetAwaiter().GetResult(); if (existingLanguage != null) { continue; @@ -1785,7 +1701,15 @@ public IReadOnlyList ImportLanguages(IEnumerable languageEl var cultureName = languageElement.AttributeValue("FriendlyName") ?? isoCode; var langauge = new Language(isoCode, cultureName); - _localizationService.Save(langauge, userId); + Attempt saveResult = _languageService.CreateAsync(langauge, ResolveUserKey(userId)).GetAwaiter().GetResult(); + if (saveResult.Success is false) + { + _logger.LogWarning( + "Failed to create language {IsoCode} during package import: {Status}", + isoCode, + saveResult.Status); + continue; + } list.Add(langauge); } diff --git a/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs b/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs index 5d2143ca1b04..1dd44e429bcc 100644 --- a/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs +++ b/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs @@ -28,7 +28,7 @@ public class UmbracoTreeSearcherFields : IUmbracoTreeSearcherFields new HashSet { UmbracoExamineFieldNames.UmbracoFileFieldName }; private readonly ISet _backOfficeMembersFieldsToLoad = new HashSet { "email", "loginName" }; - private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; private readonly IReadOnlyList _backOfficeFields = new List { @@ -43,9 +43,9 @@ public class UmbracoTreeSearcherFields : IUmbracoTreeSearcherFields /// /// Initializes a new instance of the class. /// - /// The localization service used for localizing field names and values. - public UmbracoTreeSearcherFields(ILocalizationService localizationService) => - _localizationService = localizationService; + /// The language service used for retrieving configured languages. + public UmbracoTreeSearcherFields(ILanguageService languageService) => + _languageService = languageService; /// public virtual IEnumerable GetBackOfficeFields() => _backOfficeFields; @@ -75,7 +75,7 @@ public virtual ISet GetBackOfficeDocumentFieldsToLoad() // We need to load all nodeName_* fields but we won't know those up front so need to get // all langs (this is cached) - foreach (var field in _localizationService.GetAllLanguages() + foreach (var field in _languageService.GetAllAsync().GetAwaiter().GetResult() .Select(x => "nodeName_" + x.IsoCode.ToLowerInvariant())) { fields.Add(field); diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/LanguagesTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/LanguagesTelemetryProvider.cs index 10315a8afaa6..97c30fae0519 100644 --- a/src/Umbraco.Infrastructure/Telemetry/Providers/LanguagesTelemetryProvider.cs +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/LanguagesTelemetryProvider.cs @@ -10,14 +10,14 @@ namespace Umbraco.Cms.Infrastructure.Telemetry.Providers; /// public class LanguagesTelemetryProvider : IDetailedTelemetryProvider { - private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; /// /// Initializes a new instance of the class, which provides telemetry data related to languages. /// - /// An instance of used to retrieve language data. - public LanguagesTelemetryProvider(ILocalizationService localizationService) => - _localizationService = localizationService; + /// An instance of used to retrieve language data. + public LanguagesTelemetryProvider(ILanguageService languageService) => + _languageService = languageService; /// /// Retrieves telemetry information about the number of languages configured in the system. @@ -27,7 +27,7 @@ public LanguagesTelemetryProvider(ILocalizationService localizationService) => /// public IEnumerable GetInformation() { - var languages = _localizationService.GetAllLanguages().Count(); + var languages = _languageService.GetAllAsync().GetAwaiter().GetResult().Count(); yield return new UsageInformation(Constants.Telemetry.LanguageCount, languages); } } diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/SystemTroubleshootingInformationTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/SystemTroubleshootingInformationTelemetryProvider.cs index e402c978ce43..8325584f76c9 100644 --- a/src/Umbraco.Infrastructure/Telemetry/Providers/SystemTroubleshootingInformationTelemetryProvider.cs +++ b/src/Umbraco.Infrastructure/Telemetry/Providers/SystemTroubleshootingInformationTelemetryProvider.cs @@ -18,7 +18,7 @@ internal sealed class SystemTroubleshootingInformationTelemetryProvider : IDetai { private readonly IHostEnvironment _hostEnvironment; private readonly HostingSettings _hostingSettings; - private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; private readonly ModelsBuilderSettings _modelsBuilderSettings; private readonly IUmbracoDatabaseFactory _umbracoDatabaseFactory; private readonly IUmbracoVersion _version; @@ -29,7 +29,7 @@ internal sealed class SystemTroubleshootingInformationTelemetryProvider : IDetai /// Initializes a new instance of the class. /// /// Provides information about the current Umbraco version. - /// Service used for localization and translations. + /// Service used for language operations. /// Monitors the configuration settings for the Models Builder feature. /// Monitors the configuration settings related to hosting. /// Provides information about the web hosting environment. @@ -38,7 +38,7 @@ internal sealed class SystemTroubleshootingInformationTelemetryProvider : IDetai /// Monitors the runtime configuration settings. public SystemTroubleshootingInformationTelemetryProvider( IUmbracoVersion version, - ILocalizationService localizationService, + ILanguageService languageService, IOptionsMonitor modelsBuilderSettings, IOptionsMonitor hostingSettings, IHostEnvironment hostEnvironment, @@ -47,7 +47,7 @@ public SystemTroubleshootingInformationTelemetryProvider( IOptionsMonitor runtimeSettings) { _version = version; - _localizationService = localizationService; + _languageService = languageService; _hostEnvironment = hostEnvironment; _umbracoDatabaseFactory = umbracoDatabaseFactory; _serverRoleAccessor = serverRoleAccessor; @@ -99,7 +99,7 @@ public IDictionary GetTroubleshootingInformation() => { { "Server OS", ServerOs }, { "Server Framework", ServerFramework }, - { "Default Language", _localizationService.GetDefaultLanguageIsoCode() }, + { "Default Language", _languageService.GetDefaultIsoCodeAsync().GetAwaiter().GetResult() }, { "Umbraco Version", _version.SemanticVersion.ToSemanticStringWithoutBuild() }, { "Current Culture", CurrentCulture }, { "Current UI Culture", Thread.CurrentThread.CurrentUICulture.ToString() }, diff --git a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs index a7bbcb788080..0838c834e74d 100644 --- a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs @@ -44,7 +44,6 @@ internal sealed class TemplateRenderer : ITemplateRenderer /// Provides access to the current Umbraco context. /// The published router /// - /// /// /// /// @@ -56,7 +55,6 @@ public TemplateRenderer( IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, IFileService fileService, - ILocalizationService textService, IOptionsMonitor webRoutingSettings, IHttpContextAccessor httpContextAccessor, ICompositeViewEngine viewEngine, diff --git a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml index 98fc351771e0..dbf6238e9d85 100644 --- a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml +++ b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml @@ -78,13 +78,6 @@ lib/net10.0/Umbraco.Tests.Integration.dll true - - CP0002 - M:Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine.IndexInitializer.#ctor(Umbraco.Cms.Core.Strings.IShortStringHelper,Umbraco.Cms.Core.PropertyEditors.PropertyEditorCollection,Umbraco.Cms.Core.PropertyEditors.MediaUrlGeneratorCollection,Umbraco.Cms.Core.Scoping.IScopeProvider,Microsoft.Extensions.Logging.ILoggerFactory,Microsoft.Extensions.Options.IOptions{Umbraco.Cms.Core.Configuration.Models.ContentSettings},Umbraco.Cms.Core.Services.ILocalizationService,Umbraco.Cms.Core.Services.IContentTypeService,Umbraco.Cms.Core.Services.IDocumentUrlService,Umbraco.Cms.Core.Services.ILanguageService) - lib/net10.0/Umbraco.Tests.Integration.dll - lib/net10.0/Umbraco.Tests.Integration.dll - true - CP0002 M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.ContentEditingServiceTests.Can_Validate_Valid_Variant_Content diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs index 1b8b3247450f..dbe55dbfb419 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs @@ -14,9 +14,12 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Tests.Common.Attributes; using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; using Umbraco.Extensions; @@ -47,7 +50,11 @@ public void DeleteTestFolder() => private IDictionaryItemService DictionaryItemService => GetRequiredService(); - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageRepository LanguageRepository => GetRequiredService(); + + private IDictionaryRepository DictionaryRepository => GetRequiredService(); + + private ICoreScopeProvider CoreScopeProvider => GetRequiredService(); private IEntityXmlSerializer EntityXmlSerializer => GetRequiredService(); @@ -63,12 +70,16 @@ public void DeleteTestFolder() => private ITemplateService TemplateService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); + public ICreatedPackagesRepository PackageBuilder => new PackagesRepository( ContentService, ContentTypeService, DataTypeService, FileService, - LocalizationService, + LanguageRepository, + DictionaryRepository, + CoreScopeProvider, HostingEnvironment, EntityXmlSerializer, Options.Create(new GlobalSettings()), @@ -211,6 +222,36 @@ public async Task GivenNestedDictionaryItems_WhenPackageExported_ThenTheXmlIsNes } } + [Test] + public async Task GivenLanguages_WhenPackageExported_ThenTheXmlContainsThem() + { + var daDk = new LanguageBuilder().WithCultureInfo("da-DK").Build(); + await LanguageService.CreateAsync(daDk, Constants.Security.SuperUserKey); + var nbNo = new LanguageBuilder().WithCultureInfo("nb-NO").Build(); + await LanguageService.CreateAsync(nbNo, Constants.Security.SuperUserKey); + + var def = new PackageDefinition + { + Name = "test", + Languages = new List { daDk.Id.ToString(), nbNo.Id.ToString() } + }; + PackageBuilder.SavePackage(def); + + var packageXmlPath = PackageBuilder.ExportPackage(def); + + using (var packageXmlStream = File.OpenRead(packageXmlPath)) + { + var packageXml = XDocument.Load(packageXmlStream); + var languages = packageXml.Root.Element("Languages"); + Assert.IsNotNull(languages); + var languageElements = languages.Elements("Language").ToList(); + Assert.AreEqual(2, languageElements.Count); + Assert.That( + languageElements.Select(l => l.AttributeValue("CultureAlias")), + Is.EquivalentTo(new[] { "da-DK", "nb-NO" })); + } + } + [Test] [LongRunning] public void Export_Zip() diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs index 0b1f6029073e..74b13795f5e1 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/IndexInitializer.cs @@ -30,7 +30,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; public class IndexInitializer { private readonly IOptions _contentSettings; - private readonly ILocalizationService _localizationService; private readonly ILoggerFactory _loggerFactory; private readonly MediaUrlGeneratorCollection _mediaUrlGenerators; private readonly PropertyEditorCollection _propertyEditors; @@ -48,7 +47,6 @@ public IndexInitializer( IScopeProvider scopeProvider, ILoggerFactory loggerFactory, IOptions contentSettings, - ILocalizationService localizationService, IContentTypeService contentTypeService, IDocumentUrlService documentUrlService, ILanguageService languageService, @@ -60,7 +58,6 @@ public IndexInitializer( _scopeProvider = scopeProvider; _loggerFactory = loggerFactory; _contentSettings = contentSettings; - _localizationService = localizationService; _contentTypeService = contentTypeService; _documentUrlService = documentUrlService; _languageService = languageService; @@ -76,7 +73,6 @@ public ContentValueSetBuilder GetContentValueSetBuilder(bool publishedValuesOnly _shortStringHelper, _scopeProvider, publishedValuesOnly, - _localizationService, _contentTypeService, _loggerFactory.CreateLogger(), _documentUrlService, @@ -200,8 +196,12 @@ public static IMediaService GetMockMediaService() return mediaServiceMock.Object; } - public ILocalizationService GetMockLocalizationService() => - Mock.Of(x => x.GetAllLanguages() == Array.Empty()); + public ILanguageService GetMockLanguageService() + { + var mock = new Mock(); + mock.Setup(x => x.GetAllAsync()).ReturnsAsync(Array.Empty()); + return mock.Object; + } public static IMediaTypeService GetMockMediaTypeService(IShortStringHelper shortStringHelper) { @@ -229,12 +229,12 @@ public UmbracoContentIndex GetUmbracoIndexer( IRuntimeState runtimeState, Directory luceneDir, Analyzer analyzer = null, - ILocalizationService languageService = null, + ILanguageService languageService = null, IContentValueSetValidator validator = null) { if (languageService == null) { - languageService = GetMockLocalizationService(); + languageService = GetMockLanguageService(); } if (analyzer == null) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 10c2d8cbc816..354a3801bd46 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -23,8 +23,6 @@ internal sealed class DataTypeDefinitionRepositoryTest : UmbracoIntegrationTest private ILocalizedTextService LocalizedTextService => GetRequiredService(); - private ILocalizationService LocalizationService => GetRequiredService(); - private IContentTypeRepository ContentTypeRepository => GetRequiredService(); private IDataTypeContainerRepository DataTypeContainerRepository => diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Publishing.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Publishing.cs index 056dc1f97dc3..df8188bae33d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Publishing.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockListElementLevelVariationTests.Publishing.cs @@ -2528,7 +2528,7 @@ public async Task Publishing_All_Cultures_Should_Not_Mark_Content_As_Edited() [Test] public async Task Can_Perform_Language_Fallback_At_Block_Property_Level() { - var defaultCulture = GetRequiredService().GetDefaultLanguageIsoCode(); + var defaultCulture = await GetRequiredService().GetDefaultIsoCodeAsync(); Assert.AreEqual("en-US", defaultCulture); var elementType = CreateElementType(ContentVariation.Culture); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs index de3be316fbba..fe616fd7df87 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs @@ -40,8 +40,8 @@ public async Task Can_Export_DictionaryItems() { // Arrange await CreateDictionaryData(); - var localizationService = GetRequiredService(); - var dictionaryItem = localizationService.GetDictionaryItemByKey("Parent"); + var dictionaryItemService = GetRequiredService(); + var dictionaryItem = await dictionaryItemService.GetAsync("Parent"); var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs deleted file mode 100644 index 204a16e8a794..000000000000 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using System.Collections.Generic; -using System.Diagnostics; -using NUnit.Framework; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Tests.Common.Builders; -using Umbraco.Cms.Tests.Common.Builders.Extensions; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Cms.Tests.Integration.Testing; - -namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; - -/// -/// Tests covering all methods in the LocalizationService class. -/// This is more of an integration test as it involves multiple layers -/// as well as configuration. -/// -[TestFixture] -[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] -internal sealed class LocalizationServiceTests : UmbracoIntegrationTest -{ - private ILanguageService LanguageService => GetRequiredService(); - - [SetUp] - public void SetUp() => CreateTestData(); - - private Guid _parentItemGuidId; - private int _parentItemIntId; - private Guid _childItemGuidId; - private int _childItemIntId; - private int _danishLangId; - private int _englishLangId; - - private ILocalizationService LocalizationService => GetRequiredService(); - - [Test] - public void Can_Get_All_Languages() - { - var languages = LocalizationService.GetAllLanguages(); - Assert.NotNull(languages); - Assert.IsTrue(languages.Any()); - Assert.That(languages.Count(), Is.EqualTo(3)); - } - - [Test] - public void Can_Get_Dictionary_Item_By_Int_Id() - { - var parentItem = LocalizationService.GetDictionaryItemById(_parentItemIntId); - Assert.NotNull(parentItem); - - var childItem = LocalizationService.GetDictionaryItemById(_childItemIntId); - Assert.NotNull(childItem); - } - - [Test] - public void Can_Get_Dictionary_Item_By_Guid_Id() - { - var parentItem = LocalizationService.GetDictionaryItemById(_parentItemGuidId); - Assert.NotNull(parentItem); - - var childItem = LocalizationService.GetDictionaryItemById(_childItemGuidId); - Assert.NotNull(childItem); - } - - [Test] - public void Can_Get_Dictionary_Item_By_Key() - { - var parentItem = LocalizationService.GetDictionaryItemByKey("Parent"); - Assert.NotNull(parentItem); - - var childItem = LocalizationService.GetDictionaryItemByKey("Child"); - Assert.NotNull(childItem); - } - - [Test] - public void Can_Get_Dictionary_Item_Children() - { - var item = LocalizationService.GetDictionaryItemChildren(_parentItemGuidId); - Assert.NotNull(item); - Assert.That(item.Count(), Is.EqualTo(1)); - - foreach (var dictionaryItem in item) - { - Assert.AreEqual(_parentItemGuidId, dictionaryItem.ParentId); - Assert.IsFalse(string.IsNullOrEmpty(dictionaryItem.ItemKey)); - } - } - - [Test] - public void Can_GetLanguageById() - { - var danish = LocalizationService.GetLanguageById(_danishLangId); - var english = LocalizationService.GetLanguageById(_englishLangId); - Assert.NotNull(danish); - Assert.NotNull(english); - } - - [Test] - public void Can_GetLanguageByIsoCode() - { - var danish = LocalizationService.GetLanguageByIsoCode("da-DK"); - var english = LocalizationService.GetLanguageByIsoCode("en-GB"); - Assert.NotNull(danish); - Assert.NotNull(english); - } - - [Test] - public void Does_Not_Fail_When_Language_Doesnt_Exist() - { - var language = LocalizationService.GetLanguageByIsoCode("sv-SE"); - Assert.Null(language); - } - - [Test] - public void Does_Not_Fail_When_DictionaryItem_Doesnt_Exist() - { - var item = LocalizationService.GetDictionaryItemByKey("RandomKey"); - Assert.Null(item); - } - - [Test] - public async Task Can_Delete_Language() - { - var languageNbNo = new LanguageBuilder() - .WithCultureInfo("nb-NO") - .Build(); - await LanguageService.CreateAsync(languageNbNo, Constants.Security.SuperUserKey); - Assert.That(languageNbNo.HasIdentity, Is.True); - - await LanguageService.DeleteAsync(languageNbNo.IsoCode, Constants.Security.SuperUserKey); - - var language = await LanguageService.GetAsync(languageNbNo.IsoCode); - Assert.Null(language); - } - - [Test] - public async Task Can_Delete_Language_Used_As_Fallback() - { - var languageDaDk = await LanguageService.GetAsync("da-DK"); - var languageNbNo = new LanguageBuilder() - .WithCultureInfo("nb-NO") - .WithFallbackLanguageIsoCode(languageDaDk.IsoCode) - .Build(); - await LanguageService.CreateAsync(languageNbNo, Constants.Security.SuperUserKey); - - await LanguageService.DeleteAsync(languageDaDk.IsoCode, Constants.Security.SuperUserKey); - - var language = await LanguageService.GetAsync(languageDaDk.IsoCode); - Assert.Null(language); - } - - [Test] - public void Can_Create_DictionaryItem_At_Root() - { - var english = LocalizationService.GetLanguageByIsoCode("en-US"); - - var item = (IDictionaryItem)new DictionaryItem("Testing123") - { - Translations = new List { new DictionaryTranslation(english, "Hello world") } - }; - LocalizationService.Save(item); - - // re-get - item = LocalizationService.GetDictionaryItemById(item.Id); - - Assert.Greater(item.Id, 0); - Assert.IsTrue(item.HasIdentity); - Assert.IsFalse(item.ParentId.HasValue); - Assert.AreEqual("Testing123", item.ItemKey); - Assert.AreEqual(1, item.Translations.Count()); - } - - [Test] - public void Can_Add_Translation_To_Existing_Dictionary_Item() - { - var english = LocalizationService.GetLanguageByIsoCode("en-US"); - - var item = (IDictionaryItem)new DictionaryItem("Testing123"); - LocalizationService.Save(item); - - // re-get - item = LocalizationService.GetDictionaryItemById(item.Id); - - item.Translations = new List { new DictionaryTranslation(english, "Hello world") }; - - LocalizationService.Save(item); - - Assert.AreEqual(1, item.Translations.Count()); - foreach (var translation in item.Translations) - { - Assert.AreEqual("Hello world", translation.Value); - } - - item.Translations = new List(item.Translations) - { - new DictionaryTranslation( - LocalizationService.GetLanguageByIsoCode("en-GB"), - "My new value") - }; - - LocalizationService.Save(item); - - // re-get - item = LocalizationService.GetDictionaryItemById(item.Id); - - Assert.AreEqual(2, item.Translations.Count()); - Assert.AreEqual("Hello world", item.Translations.First().Value); - Assert.AreEqual("My new value", item.Translations.Last().Value); - } - - [Test] - public void Can_Update_Existing_DictionaryItem() - { - var item = LocalizationService.GetDictionaryItemByKey("Child"); - foreach (var translation in item.Translations) - { - translation.Value += "UPDATED"; - } - - LocalizationService.Save(item); - - var updatedItem = LocalizationService.GetDictionaryItemByKey("Child"); - Assert.NotNull(updatedItem); - - foreach (var translation in updatedItem.Translations) - { - Assert.That(translation.Value.EndsWith("UPDATED"), Is.True); - } - } - - [Test] - public void Find_BaseData_Language() - { - // Act - var languages = LocalizationService.GetAllLanguages(); - - // Assert - Assert.That(3, Is.EqualTo(languages.Count())); - } - - [Test] - public async Task Save_Language_And_GetLanguageByIsoCode() - { - // Arrange - var isoCode = "en-AU"; - var languageEnAu = new LanguageBuilder() - .WithCultureInfo(isoCode) - .Build(); - - // Act - await LanguageService.CreateAsync(languageEnAu, Constants.Security.SuperUserKey); - var result = await LanguageService.GetAsync(isoCode); - - // Assert - Assert.NotNull(result); - } - - [Test] - public async Task Save_Language_And_GetLanguageById() - { - // Arrange - var languageEnAu = new LanguageBuilder() - .WithCultureInfo("en-AU") - .Build(); - - // Act - await LanguageService.CreateAsync(languageEnAu, Constants.Security.SuperUserKey); - var result = await LanguageService.GetAsync(languageEnAu.IsoCode); - - // Assert - Assert.NotNull(result); - } - - [Test] - public async Task Set_Default_Language() - { - var languageEnAu = new LanguageBuilder() - .WithCultureInfo("en-AU") - .WithIsDefault(true) - .Build(); - await LanguageService.CreateAsync(languageEnAu, Constants.Security.SuperUserKey); - var result = await LanguageService.GetAsync(languageEnAu.IsoCode); - - Assert.IsTrue(result.IsDefault); - - var languageEnNz = new LanguageBuilder() - .WithCultureInfo("en-NZ") - .WithIsDefault(true) - .Build(); - await LanguageService.CreateAsync(languageEnNz, Constants.Security.SuperUserKey); - var result2 = await LanguageService.GetAsync(languageEnNz.IsoCode); - - // re-get - result = await LanguageService.GetAsync(languageEnAu.IsoCode); - - Assert.IsTrue(result2.IsDefault); - Assert.IsFalse(result.IsDefault); - } - - [Test] - public async Task Deleted_Language_Should_Not_Exist() - { - var isoCode = "en-AU"; - var languageEnAu = new LanguageBuilder() - .WithCultureInfo(isoCode) - .Build(); - await LanguageService.CreateAsync(languageEnAu, Constants.Security.SuperUserKey); - - // Act - await LanguageService.DeleteAsync(languageEnAu.IsoCode, Constants.Security.SuperUserKey); - var result = await LanguageService.GetAsync(isoCode); - - // Assert - Assert.Null(result); - } - - public async Task CreateTestData() - { - var languageDaDk = new LanguageBuilder() - .WithCultureInfo("da-DK") - .Build(); - var languageEnGb = new LanguageBuilder() - .WithCultureInfo("en-GB") - .Build(); - - await LanguageService.CreateAsync(languageDaDk, Constants.Security.SuperUserKey); - await LanguageService.CreateAsync(languageEnGb, Constants.Security.SuperUserKey); - _danishLangId = languageDaDk.Id; - _englishLangId = languageEnGb.Id; - - var parentItem = new DictionaryItem("Parent") - { - Translations = new List - { - new DictionaryTranslation(languageEnGb, "ParentValue"), - new DictionaryTranslation(languageDaDk, "ForældreVærdi") - } - }; - LocalizationService.Save(parentItem); - _parentItemGuidId = parentItem.Key; - _parentItemIntId = parentItem.Id; - - var childItem = new DictionaryItem(parentItem.Key, "Child") - { - Translations = new List - { - new DictionaryTranslation(languageEnGb, "ChildValue"), - new DictionaryTranslation(languageDaDk, "BørnVærdi") - } - }; - LocalizationService.Save(childItem); - _childItemGuidId = childItem.Key; - _childItemIntId = childItem.Id; - } -} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs index 2504a9cba058..fbbbfde5a258 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs @@ -37,7 +37,7 @@ public void Setup() var cultures = new List { - GetRequiredService().GetDefaultLanguageIsoCode() + GetRequiredService().GetDefaultIsoCodeAsync().GetAwaiter().GetResult() }; foreach (var language in InstallationSummary.LanguagesInstalled) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs index 7600695dddf8..4671cc247fe4 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs @@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Tests.Common.Builders; @@ -114,4 +115,44 @@ public void CombineStartNodes(string groupSn, string userSn, string expected) "\"."); } } + + [Test] + public async Task CalculateAllowedLanguageIdsAsync_HasAccessToAllLanguages_ReturnsAllLanguageIds() + { + var group = Mock.Of(g => g.HasAccessToAllLanguages == true); + var user = Mock.Of(u => u.Groups == new[] { group }); + + var languageService = new Mock(); + languageService + .Setup(x => x.GetAllAsync()) + .ReturnsAsync(new[] + { + Mock.Of(l => l.Id == 1), + Mock.Of(l => l.Id == 2), + Mock.Of(l => l.Id == 3), + }); + + var result = await user.CalculateAllowedLanguageIdsAsync(languageService.Object); + + Assert.That(result, Is.EquivalentTo(new[] { 1, 2, 3 })); + } + + [Test] + public async Task CalculateAllowedLanguageIdsAsync_NoAllAccess_ReturnsDistinctGroupLanguages() + { + var group1 = Mock.Of(g => + g.HasAccessToAllLanguages == false && + g.AllowedLanguages == new[] { 1, 2 }); + var group2 = Mock.Of(g => + g.HasAccessToAllLanguages == false && + g.AllowedLanguages == new[] { 2, 3 }); + var user = Mock.Of(u => u.Groups == new[] { group1, group2 }); + + var languageService = new Mock(); + + var result = await user.CalculateAllowedLanguageIdsAsync(languageService.Object); + + Assert.That(result, Is.EquivalentTo(new[] { 1, 2, 3 })); + languageService.Verify(x => x.GetAllAsync(), Times.Never); + } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DictionaryServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DictionaryServiceTests.cs new file mode 100644 index 000000000000..ad7bbcb6d71f --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DictionaryServiceTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services; + +[TestFixture] +public class DictionaryServiceTests +{ + [Test] + public async Task CalculatePathAsync_NullParentId_ReturnsRootPath() + { + var dictionaryItemService = new Mock(); + var sut = new DictionaryService(dictionaryItemService.Object); + + var result = await sut.CalculatePathAsync(null, 42); + + Assert.AreEqual("-1,42", result); + dictionaryItemService.Verify(x => x.GetAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task CalculatePathAsync_RootParent_ReturnsTwoLevelPath() + { + var parentKey = Guid.NewGuid(); + var dictionaryItemService = new Mock(); + dictionaryItemService + .Setup(x => x.GetAsync(parentKey)) + .ReturnsAsync(Mock.Of(d => d.Id == 100 && d.ParentId == null)); + + var sut = new DictionaryService(dictionaryItemService.Object); + + var result = await sut.CalculatePathAsync(parentKey, 42); + + Assert.AreEqual("-1,100,42", result); + } + + [Test] + public async Task CalculatePathAsync_NestedParents_ReturnsFullHierarchy() + { + var grandparentKey = Guid.NewGuid(); + var parentKey = Guid.NewGuid(); + + var dictionaryItemService = new Mock(); + dictionaryItemService + .Setup(x => x.GetAsync(parentKey)) + .ReturnsAsync(Mock.Of(d => d.Id == 100 && d.ParentId == grandparentKey)); + dictionaryItemService + .Setup(x => x.GetAsync(grandparentKey)) + .ReturnsAsync(Mock.Of(d => d.Id == 50 && d.ParentId == null)); + + var sut = new DictionaryService(dictionaryItemService.Object); + + var result = await sut.CalculatePathAsync(parentKey, 42); + + Assert.AreEqual("-1,50,100,42", result); + } + + [Test] + public async Task CalculatePathAsync_ParentNotFound_StopsAtMissingNode() + { + var parentKey = Guid.NewGuid(); + var dictionaryItemService = new Mock(); + dictionaryItemService + .Setup(x => x.GetAsync(parentKey)) + .ReturnsAsync((IDictionaryItem?)null); + + var sut = new DictionaryService(dictionaryItemService.Object); + + var result = await sut.CalculatePathAsync(parentKey, 42); + + Assert.AreEqual("-1,42", result); + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/SystemInformationServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/SystemInformationServiceTests.cs index b0d0f129c705..15e19b100a3d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/SystemInformationServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/SystemInformationServiceTests.cs @@ -139,14 +139,14 @@ private ISystemTroubleshootingInformationService CreateSystemInformationService( bool isDebug = true, RuntimeMode runtimeMode = RuntimeMode.BackofficeDevelopment) { - var localizationService = CreateILocalizationService(culture); + var languageService = CreateILanguageService(culture); var databaseMock = new Mock(); databaseMock.Setup(x => x.DatabaseType.GetProviderName()).Returns("SQL"); return new SystemTroubleshootingInformationTelemetryProvider( _umbracoVersion, - localizationService, + languageService, Mock.Of>(x => x.CurrentValue == new ModelsBuilderSettings { ModelsMode = modelsMode }), Mock.Of>(x => x.CurrentValue == new HostingSettings { Debug = isDebug }), Mock.Of(), @@ -155,11 +155,11 @@ private ISystemTroubleshootingInformationService CreateSystemInformationService( Mock.Of>(x => x.CurrentValue == new RuntimeSettings { Mode = runtimeMode })); } - private ILocalizationService CreateILocalizationService(string culture) + private ILanguageService CreateILanguageService(string culture) { - var localizationService = new Mock(); - localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns(culture); - return localizationService.Object; + var languageService = new Mock(); + languageService.Setup(x => x.GetDefaultIsoCodeAsync()).ReturnsAsync(culture); + return languageService.Object; } private void CreateUmbracoVersion(int major, int minor, int patch) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemTroubleshootingInformationTelemetryProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemTroubleshootingInformationTelemetryProviderTests.cs index 5334127a085f..716ab1054bee 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemTroubleshootingInformationTelemetryProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemTroubleshootingInformationTelemetryProviderTests.cs @@ -107,7 +107,7 @@ private SystemTroubleshootingInformationTelemetryProvider CreateProvider( return new SystemTroubleshootingInformationTelemetryProvider( Mock.Of(), - Mock.Of(), + Mock.Of(), Mock.Of>(x => x.CurrentValue == new ModelsBuilderSettings { ModelsMode = modelsMode }), Mock.Of>(x => x.CurrentValue == new HostingSettings { Debug = isDebug }), hostEnvironment.Object, diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/ContentValueSetBuilderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/ContentValueSetBuilderTests.cs index a47fdfc28d94..a84c49d5316e 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/ContentValueSetBuilderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/ContentValueSetBuilderTests.cs @@ -21,9 +21,6 @@ public class ContentValueSetBuilderTests private const string DefaultCulture = "en-US"; private Mock _userService = null!; private Mock _scopeProvider = null!; -#pragma warning disable CS0618 // Type or member is obsolete - private Mock _localizationService = null!; -#pragma warning restore CS0618 // Type or member is obsolete private Mock _contentTypeService = null!; private Mock _documentUrlService = null!; private Mock _languageService = null!; @@ -47,10 +44,6 @@ public void SetUp() It.IsAny(), It.IsAny())) .Returns(Mock.Of()); -#pragma warning disable CS0618 // Type or member is obsolete - _localizationService = new Mock(); -_localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns(DefaultCulture); -#pragma warning restore CS0618 // Type or member is obsolete _contentTypeService = new Mock(); _contentTypeService.Setup(x => x.GetAll()).Returns(Array.Empty()); _documentUrlService = new Mock(); @@ -84,7 +77,6 @@ private ContentValueSetBuilder CreateBuilder(bool publishedValuesOnly = false) = _shortStringHelper.Object, _scopeProvider.Object, publishedValuesOnly, - _localizationService.Object, _contentTypeService.Object, NullLogger.Instance, _documentUrlService.Object, From a61fcda5ec6fe93e37a64567fbab3fae141bab80 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 2 May 2026 17:05:37 +0200 Subject: [PATCH 2/3] Addressed code review feedback. --- .../DictionaryItemDeletedNotification.cs | 2 +- .../DictionaryItemDeletingNotification.cs | 2 +- .../Notifications/DictionaryItemSavedNotification.cs | 2 +- .../DictionaryItemSavingNotification.cs | 2 +- .../Notifications/LanguageDeletedNotification.cs | 2 +- .../Notifications/LanguageDeletingNotification.cs | 2 +- .../Notifications/LanguageSavedNotification.cs | 2 +- .../Notifications/LanguageSavingNotification.cs | 2 +- .../Packaging/PackageDataInstallation.cs | 12 +++++++----- .../UrlAndDomains/DomainAndUrlsTests.cs | 4 ++-- 10 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs index 7ac409920572..2e25a03aaba6 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the IDictionaryItemService when the Delete (IDictionaryItem overload) method is called in the API, after the dictionary items has been deleted. +/// A notification that is used to trigger the IDictionaryItemService when a dictionary item is deleted via the API, after the dictionary item has been deleted. /// public class DictionaryItemDeletedNotification : DeletedNotification { diff --git a/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs index 537cca853612..c3d32aeef9d7 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the IDictionaryItemService when the Delete (IDictionaryItem overload) method is called in the API. +/// A notification that is used to trigger the IDictionaryItemService when a dictionary item is deleted via the API. /// /// /// This notification is cancelable, allowing handlers to prevent the delete operation diff --git a/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs index f85cc5e8ff0c..39bbe022e083 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the IDictionaryItemService when the Save (IDictionaryItem overload) method is called in the API and the data has been persisted. +/// A notification that is used to trigger the IDictionaryItemService when a dictionary item is created or updated via the API, after data has been persisted. /// public class DictionaryItemSavedNotification : SavedNotification { diff --git a/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs index f7876ddba9f8..e0d45963aa2f 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the IDictionaryItemService when the Save (IDictionaryItem overload) method is called in the API. +/// A notification that is used to trigger the IDictionaryItemService when a dictionary item is created or updated via the API. /// /// /// This notification is cancelable, allowing handlers to prevent the save operation diff --git a/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs b/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs index edc807d2817f..c25942e0d66b 100644 --- a/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILanguageService when the Delete (ILanguage overload) method is called in the API, after the languages have been deleted. +/// A notification that is used to trigger the ILanguageService when a language is deleted via the API, after the language has been deleted. /// public class LanguageDeletedNotification : DeletedNotification { diff --git a/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs b/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs index 07ddad5520c7..3443b71bf64e 100644 --- a/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILanguageService when the Delete (ILanguage overload) method is called in the API. +/// A notification that is used to trigger the ILanguageService when a language is deleted via the API. /// public class LanguageDeletingNotification : DeletingNotification { diff --git a/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs b/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs index 514f61799aeb..6a3eb93c3c9f 100644 --- a/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILanguageService when the Save (ILanguage overload) method is called in the API, after data has been persisted. +/// A notification that is used to trigger the ILanguageService when a language is created or updated via the API, after data has been persisted. /// public class LanguageSavedNotification : SavedNotification { diff --git a/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs b/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs index 9b03beca66a2..fdb5bf491234 100644 --- a/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; /// -/// A notification that is used to trigger the ILanguageService when the Save (ILanguage overload) method is called in the API. +/// A notification that is used to trigger the ILanguageService when a language is created or updated via the API. /// public class LanguageSavingNotification : SavingNotification { diff --git a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs index 723c0832c4a3..d8dc58d39d63 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs @@ -1589,14 +1589,16 @@ private IEnumerable ImportDictionaryItem( isUpdate ? "update" : "create", key, saveResult.Status); + return items; } - items.Add(dictionaryItem!); + IDictionaryItem savedItem = saveResult.Result; + items.Add(savedItem); items.AddRange(ImportDictionaryItems( dictionaryItemElement.Elements("DictionaryItem"), languages, - dictionaryItem.Key, + savedItem.Key, userId)); return items; } @@ -1700,8 +1702,8 @@ public IReadOnlyList ImportLanguages(IEnumerable languageEl var cultureName = languageElement.AttributeValue("FriendlyName") ?? isoCode; - var langauge = new Language(isoCode, cultureName); - Attempt saveResult = _languageService.CreateAsync(langauge, ResolveUserKey(userId)).GetAwaiter().GetResult(); + var language = new Language(isoCode, cultureName); + Attempt saveResult = _languageService.CreateAsync(language, ResolveUserKey(userId)).GetAwaiter().GetResult(); if (saveResult.Success is false) { _logger.LogWarning( @@ -1711,7 +1713,7 @@ public IReadOnlyList ImportLanguages(IEnumerable languageEl continue; } - list.Add(langauge); + list.Add(saveResult.Result); } return list; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs index fbbbfde5a258..3e259a0660d2 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs @@ -26,7 +26,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.UrlAndDomains; internal sealed class DomainAndUrlsTests : UmbracoIntegrationTest { [SetUp] - public void Setup() + public async Task Setup() { var xml = PackageMigrationResource.GetEmbeddedPackageDataManifest(GetType()); var packagingService = GetRequiredService(); @@ -37,7 +37,7 @@ public void Setup() var cultures = new List { - GetRequiredService().GetDefaultIsoCodeAsync().GetAwaiter().GetResult() + await GetRequiredService().GetDefaultIsoCodeAsync() }; foreach (var language in InstallationSummary.LanguagesInstalled) From 0fa78999bfcd61319f1de437daae458fcbb34be9 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 2 May 2026 19:56:47 +0200 Subject: [PATCH 3/3] Fixed failing integration test. --- .../UrlAndDomains/DomainAndUrlsTests.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs index 3e259a0660d2..ce34729655e6 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UrlAndDomains/DomainAndUrlsTests.cs @@ -26,7 +26,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.UrlAndDomains; internal sealed class DomainAndUrlsTests : UmbracoIntegrationTest { [SetUp] - public async Task Setup() + public void Setup() { var xml = PackageMigrationResource.GetEmbeddedPackageDataManifest(GetType()); var packagingService = GetRequiredService(); @@ -35,9 +35,12 @@ public async Task Setup() Root = InstallationSummary.ContentInstalled.First(); ContentService.Publish(Root, Root.AvailableCultures.ToArray()); + // Note: this SetUp must remain synchronous. EnsureUmbracoContext() below writes to an AsyncLocal + // (via HybridUmbracoContextAccessor) and AsyncLocal mutations made inside an awaited Task do not + // flow back to the test method's execution context. var cultures = new List { - await GetRequiredService().GetDefaultIsoCodeAsync() + GetRequiredService().GetDefaultIsoCodeAsync().GetAwaiter().GetResult(), }; foreach (var language in InstallationSummary.LanguagesInstalled)