diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ByKeyDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ByKeyDictionaryController.cs
index a595296dc3..4e1ece8db3 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ByKeyDictionaryController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ByKeyDictionaryController.cs
@@ -1,40 +1,28 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Core.Models;
-using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Api.Management.ViewModels.Dictionary;
-using Umbraco.New.Cms.Core.Factories;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
-public class ByIdDictionaryController : DictionaryControllerBase
+public class ByKeyDictionaryController : DictionaryControllerBase
{
private readonly ILocalizationService _localizationService;
private readonly IDictionaryFactory _dictionaryFactory;
- public ByIdDictionaryController(
- ILocalizationService localizationService,
- IDictionaryFactory dictionaryFactory)
+ public ByKeyDictionaryController(ILocalizationService localizationService, IDictionaryFactory dictionaryFactory)
{
_localizationService = localizationService;
_dictionaryFactory = dictionaryFactory;
}
- ///
- /// Gets a dictionary item by guid
- ///
- ///
- /// The id.
- ///
- ///
- /// The . Returns a not found response when dictionary item does not exist
- ///
- [HttpGet("{key:guid}")]
+ [HttpGet($"{{{nameof(key)}:guid}}")]
[MapToApiVersion("1.0")]
- [ProducesResponseType(typeof(DictionaryViewModel), StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(NotFoundResult), StatusCodes.Status404NotFound)]
- public async Task> ByKey(Guid key)
+ [ProducesResponseType(typeof(DictionaryItemViewModel), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> ByKey(Guid key)
{
IDictionaryItem? dictionary = _localizationService.GetDictionaryItemById(key);
if (dictionary == null)
@@ -42,6 +30,6 @@ public class ByIdDictionaryController : DictionaryControllerBase
return NotFound();
}
- return await Task.FromResult(_dictionaryFactory.CreateDictionaryViewModel(dictionary));
+ return await Task.FromResult(Ok(_dictionaryFactory.CreateDictionaryItemViewModel(dictionary)));
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs
index ab0cc69625..76e78f4f83 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs
@@ -1,90 +1,52 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Api.Management.ViewModels.Dictionary;
-using Umbraco.Extensions;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
public class CreateDictionaryController : DictionaryControllerBase
{
private readonly ILocalizationService _localizationService;
- private readonly ILocalizedTextService _localizedTextService;
- private readonly GlobalSettings _globalSettings;
- private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
- private readonly ILogger _logger;
+ private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
+ private readonly IDictionaryFactory _dictionaryFactory;
public CreateDictionaryController(
ILocalizationService localizationService,
- ILocalizedTextService localizedTextService,
- IOptionsSnapshot globalSettings,
- IBackOfficeSecurityAccessor backofficeSecurityAccessor,
- ILogger logger)
+ IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
+ IDictionaryFactory dictionaryFactory)
{
_localizationService = localizationService;
- _localizedTextService = localizedTextService;
- _globalSettings = globalSettings.Value;
- _backofficeSecurityAccessor = backofficeSecurityAccessor;
- _logger = logger;
+ _backOfficeSecurityAccessor = backOfficeSecurityAccessor;
+ _dictionaryFactory = dictionaryFactory;
}
- ///
- /// Creates a new dictionary item
- ///
- /// The viewmodel to pass to the action
- ///
- /// The .
- ///
[HttpPost]
[MapToApiVersion("1.0")]
- [ProducesResponseType(typeof(CreatedResult), StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
- public async Task> Create(DictionaryItemViewModel dictionaryViewModel)
+ [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)]
+ public async Task Create(DictionaryItemCreateModel dictionaryItemCreateModel)
{
- if (string.IsNullOrEmpty(dictionaryViewModel.Key.ToString()))
+ IEnumerable translations = _dictionaryFactory.MapTranslations(dictionaryItemCreateModel.Translations);
+
+ Attempt result = _localizationService.Create(
+ dictionaryItemCreateModel.Name,
+ dictionaryItemCreateModel.ParentKey,
+ translations,
+ CurrentUserId(_backOfficeSecurityAccessor));
+
+ if (result.Success)
{
- return ValidationProblem("Key can not be empty."); // TODO: translate
+ return await Task.FromResult(CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key));
}
- if (_localizationService.DictionaryItemExists(dictionaryViewModel.Key.ToString()))
- {
- var message = _localizedTextService.Localize(
- "dictionaryItem",
- "changeKeyError",
- _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.GetUserCulture(_localizedTextService, _globalSettings),
- new Dictionary
- {
- { "0", dictionaryViewModel.Key.ToString() },
- });
- return await Task.FromResult(ValidationProblem(message));
- }
-
- try
- {
- Guid? parentGuid = null;
-
- if (dictionaryViewModel.ParentId.HasValue)
- {
- parentGuid = dictionaryViewModel.ParentId;
- }
-
- IDictionaryItem item = _localizationService.CreateDictionaryItemWithIdentity(
- dictionaryViewModel.Key.ToString(),
- parentGuid,
- string.Empty);
-
-
- return await Task.FromResult(Created($"api/v1.0/dictionary/{item.Key}", item.Key));
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error creating dictionary with {Name} under {ParentId}", dictionaryViewModel.Key, dictionaryViewModel.ParentId);
- return await Task.FromResult(ValidationProblem("Error creating dictionary item"));
- }
+ return DictionaryItemOperationStatusResult(result.Status);
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DeleteDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DeleteDictionaryController.cs
index 0f86cc0e5a..7e86c5743a 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DeleteDictionaryController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DeleteDictionaryController.cs
@@ -1,8 +1,10 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
@@ -16,36 +18,20 @@ public class DeleteDictionaryController : DictionaryControllerBase
_localizationService = localizationService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
- ///
- /// Deletes a data type with a given ID
- ///
- /// The key of the dictionary item to delete
- ///
- ///
- ///
- [HttpDelete("{key}")]
+
+ [HttpDelete($"{{{nameof(key)}:guid}}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(NotFoundResult), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task Delete(Guid key)
{
- IDictionaryItem? foundDictionary = _localizationService.GetDictionaryItemByKey(key.ToString());
-
- if (foundDictionary == null)
+ Attempt result = _localizationService.Delete(key, CurrentUserId(_backOfficeSecurityAccessor));
+ if (result.Success)
{
- return await Task.FromResult(NotFound());
+ return await Task.FromResult(Ok());
}
- IEnumerable foundDictionaryDescendants =
- _localizationService.GetDictionaryItemDescendants(foundDictionary.Key);
-
- foreach (IDictionaryItem dictionaryItem in foundDictionaryDescendants)
- {
- _localizationService.Delete(dictionaryItem, _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1);
- }
-
- _localizationService.Delete(foundDictionary, _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1);
-
- return await Task.FromResult(Ok());
+ return DictionaryItemOperationStatusResult(result.Status);
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DictionaryControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DictionaryControllerBase.cs
index 4914298d14..eb66fa3153 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DictionaryControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DictionaryControllerBase.cs
@@ -1,5 +1,8 @@
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Umbraco.Cms.Api.Common.Builders;
using Umbraco.Cms.Api.Management.Routing;
+using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
@@ -10,4 +13,19 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary;
// TODO: Add authentication
public abstract class DictionaryControllerBase : ManagementApiControllerBase
{
+ protected IActionResult DictionaryItemOperationStatusResult(DictionaryItemOperationStatus status) =>
+ status switch
+ {
+ DictionaryItemOperationStatus.DuplicateItemKey => Conflict(new ProblemDetailsBuilder()
+ .WithTitle("Duplicate dictionary item name detected")
+ .WithDetail("Another dictionary item exists with the same name. Dictionary item names must be unique.")
+ .Build()),
+ DictionaryItemOperationStatus.ItemNotFound => NotFound("The dictionary item could not be found"),
+ DictionaryItemOperationStatus.ParentNotFound => NotFound("The dictionary item parent could not be found"),
+ DictionaryItemOperationStatus.CancelledByNotification => BadRequest(new ProblemDetailsBuilder()
+ .WithTitle("Cancelled by notification")
+ .WithDetail("A notification handler prevented the dictionary item operation.")
+ .Build()),
+ _ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown dictionary operation status")
+ };
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs
index 87fdd2bdde..b2641ddf8d 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs
@@ -32,6 +32,7 @@ public class DictionaryTreeControllerBase : EntityTreeControllerBase Update(Guid id, JsonPatchViewModel[] updateViewModel)
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task Update(Guid key, DictionaryItemUpdateModel dictionaryItemUpdateModel)
{
- IDictionaryItem? dictionaryItem = _localizationService.GetDictionaryItemById(id);
-
- if (dictionaryItem is null)
+ IDictionaryItem? current = _localizationService.GetDictionaryItemById(key);
+ if (current == null)
{
return NotFound();
}
- DictionaryViewModel dictionaryToPatch = _umbracoMapper.Map(dictionaryItem)!;
+ IDictionaryItem updated = _dictionaryFactory.MapUpdateModelToDictionaryItem(current, dictionaryItemUpdateModel);
- PatchResult? result = _jsonPatchService.Patch(updateViewModel, dictionaryToPatch);
+ Attempt result = _localizationService.Update(updated, CurrentUserId(_backOfficeSecurityAccessor));
- if (result?.Result is null)
+ if (result.Success)
{
- throw new JsonException("Could not patch the JsonPatchViewModel");
+ return await Task.FromResult(Ok());
}
- DictionaryViewModel? updatedDictionaryItem = _systemTextJsonSerializer.Deserialize(result.Result.ToJsonString());
- if (updatedDictionaryItem is null)
- {
- throw new JsonException("Could not serialize from PatchResult to DictionaryViewModel");
- }
-
- IDictionaryItem dictionaryToSave = _dictionaryFactory.CreateDictionaryItem(updatedDictionaryItem!);
- _localizationService.Save(dictionaryToSave);
- return await Task.FromResult(Content(_dictionaryService.CalculatePath(dictionaryToSave.ParentId, dictionaryToSave.Id), MediaTypeNames.Text.Plain, Encoding.UTF8));
+ return DictionaryItemOperationStatusResult(result.Status);
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/UploadDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/UploadDictionaryController.cs
index 32156d21c0..e23a2c1566 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/UploadDictionaryController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/UploadDictionaryController.cs
@@ -1,6 +1,7 @@
using System.Xml;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Api.Management.Models;
using Umbraco.Cms.Api.Management.Services;
diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/DictionaryBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/DictionaryBuilderExtensions.cs
index eee1bcd56a..059b8411c7 100644
--- a/src/Umbraco.Cms.Api.Management/DependencyInjection/DictionaryBuilderExtensions.cs
+++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/DictionaryBuilderExtensions.cs
@@ -4,7 +4,6 @@ using Umbraco.Cms.Api.Management.Mapping.Dictionary;
using Umbraco.Cms.Api.Management.Services;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
-using Umbraco.New.Cms.Core.Factories;
namespace Umbraco.Cms.Api.Management.DependencyInjection;
@@ -16,7 +15,7 @@ internal static class DictionaryBuilderExtensions
.AddTransient()
.AddTransient();
- builder.WithCollectionBuilder().Add();
+ builder.WithCollectionBuilder().Add();
return builder;
}
diff --git a/src/Umbraco.Cms.Api.Management/Factories/DictionaryFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DictionaryFactory.cs
index 651a806244..bf629e3139 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/DictionaryFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/DictionaryFactory.cs
@@ -1,11 +1,10 @@
using System.Xml;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
-using Umbraco.Cms.Core.Models.Mapping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Api.Management.Models;
using Umbraco.Cms.Api.Management.ViewModels.Dictionary;
-using Umbraco.New.Cms.Core.Factories;
+using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Factories;
@@ -13,59 +12,50 @@ public class DictionaryFactory : IDictionaryFactory
{
private readonly IUmbracoMapper _umbracoMapper;
private readonly ILocalizationService _localizationService;
- private readonly IDictionaryService _dictionaryService;
- private readonly CommonMapper _commonMapper;
- public DictionaryFactory(
- IUmbracoMapper umbracoMapper,
- ILocalizationService localizationService,
- IDictionaryService dictionaryService,
- CommonMapper commonMapper)
+ public DictionaryFactory(IUmbracoMapper umbracoMapper, ILocalizationService localizationService)
{
_umbracoMapper = umbracoMapper;
_localizationService = localizationService;
- _dictionaryService = dictionaryService;
- _commonMapper = commonMapper;
}
- public IDictionaryItem CreateDictionaryItem(DictionaryViewModel dictionaryViewModel)
+ public DictionaryItemViewModel CreateDictionaryItemViewModel(IDictionaryItem dictionaryItem)
{
- IDictionaryItem mappedItem = _umbracoMapper.Map(dictionaryViewModel)!;
- IDictionaryItem? dictionaryItem = _localizationService.GetDictionaryItemById(dictionaryViewModel.Key);
- mappedItem.Id = dictionaryItem!.Id;
- return mappedItem;
- }
+ DictionaryItemViewModel dictionaryViewModel = _umbracoMapper.Map(dictionaryItem)!;
- public DictionaryViewModel CreateDictionaryViewModel(IDictionaryItem dictionaryItem)
- {
- DictionaryViewModel dictionaryViewModel = _umbracoMapper.Map(dictionaryItem)!;
-
- dictionaryViewModel.ContentApps = _commonMapper.GetContentAppsForEntity(dictionaryItem);
- dictionaryViewModel.Path = _dictionaryService.CalculatePath(dictionaryItem.ParentId, dictionaryItem.Id);
-
- var translations = new List();
- // add all languages and the translations
- foreach (ILanguage lang in _localizationService.GetAllLanguages())
- {
- var langId = lang.Id;
- IDictionaryTranslation? translation = dictionaryItem.Translations?.FirstOrDefault(x => x.LanguageId == langId);
-
- translations.Add(new DictionaryTranslationViewModel
- {
- IsoCode = lang.IsoCode,
- DisplayName = lang.CultureName,
- Translation = translation?.Value ?? string.Empty,
- LanguageId = lang.Id,
- Id = translation?.Id ?? 0,
- Key = translation?.Key ?? Guid.Empty,
- });
- }
-
- dictionaryViewModel.Translations = translations;
+ var validLanguageIds = _localizationService
+ .GetAllLanguages()
+ .Select(language => language.Id)
+ .ToArray();
+ IDictionaryTranslation[] validTranslations = dictionaryItem.Translations
+ .Where(t => validLanguageIds.Contains(t.LanguageId))
+ .ToArray();
+ dictionaryViewModel.Translations = validTranslations
+ .Select(translation => _umbracoMapper.Map(translation))
+ .WhereNotNull()
+ .ToArray();
return dictionaryViewModel;
}
+ public IDictionaryItem MapUpdateModelToDictionaryItem(IDictionaryItem current, DictionaryItemUpdateModel dictionaryItemUpdateModel)
+ {
+ IDictionaryItem updated = _umbracoMapper.Map(dictionaryItemUpdateModel, current);
+
+ MapTranslations(updated, dictionaryItemUpdateModel.Translations);
+
+ return updated;
+ }
+
+ public IEnumerable MapTranslations(IEnumerable translationModels)
+ {
+ var temporaryDictionaryItem = new DictionaryItem(Guid.NewGuid().ToString());
+
+ MapTranslations(temporaryDictionaryItem, translationModels);
+
+ return temporaryDictionaryItem.Translations;
+ }
+
public DictionaryImportViewModel CreateDictionaryImportViewModel(FormFileUploadResult formFileUploadResult)
{
if (formFileUploadResult.CouldLoad is false || formFileUploadResult.XmlDocument is null)
@@ -83,7 +73,7 @@ public class DictionaryFactory : IDictionaryFactory
foreach (XmlNode dictionaryItem in formFileUploadResult.XmlDocument.GetElementsByTagName("DictionaryItem"))
{
var name = dictionaryItem.Attributes?.GetNamedItem("Name")?.Value ?? string.Empty;
- var parentKey = dictionaryItem?.ParentNode?.Attributes?.GetNamedItem("Key")?.Value ?? string.Empty;
+ var parentKey = dictionaryItem.ParentNode?.Attributes?.GetNamedItem("Key")?.Value ?? string.Empty;
if (parentKey != currentParent || level == 1)
{
@@ -96,4 +86,19 @@ public class DictionaryFactory : IDictionaryFactory
return model;
}
+
+ private void MapTranslations(IDictionaryItem dictionaryItem, IEnumerable translationModels)
+ {
+ var languagesByIsoCode = _localizationService
+ .GetAllLanguages()
+ .ToDictionary(l => l.IsoCode);
+ DictionaryItemTranslationModel[] validTranslations = translationModels
+ .Where(translation => languagesByIsoCode.ContainsKey(translation.IsoCode))
+ .ToArray();
+
+ foreach (DictionaryItemTranslationModel translationModel in validTranslations)
+ {
+ _localizationService.AddOrUpdateDictionaryValue(dictionaryItem, languagesByIsoCode[translationModel.IsoCode], translationModel.Translation);
+ }
+ }
}
diff --git a/src/Umbraco.Cms.Api.Management/Factories/IDictionaryFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IDictionaryFactory.cs
index fb86b6aec0..209f9c0981 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/IDictionaryFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/IDictionaryFactory.cs
@@ -1,13 +1,16 @@
-using Umbraco.Cms.Core.Models;
-using Umbraco.Cms.Api.Management.Models;
+using Umbraco.Cms.Api.Management.Models;
using Umbraco.Cms.Api.Management.ViewModels.Dictionary;
+using Umbraco.Cms.Core.Models;
-namespace Umbraco.New.Cms.Core.Factories;
+namespace Umbraco.Cms.Api.Management.Factories;
public interface IDictionaryFactory
{
- IDictionaryItem CreateDictionaryItem(DictionaryViewModel dictionaryViewModel);
- DictionaryViewModel CreateDictionaryViewModel(IDictionaryItem dictionaryItem);
+ IDictionaryItem MapUpdateModelToDictionaryItem(IDictionaryItem current, DictionaryItemUpdateModel dictionaryItemUpdateModel);
+
+ IEnumerable MapTranslations(IEnumerable translationModels);
+
+ DictionaryItemViewModel CreateDictionaryItemViewModel(IDictionaryItem dictionaryItem);
DictionaryImportViewModel CreateDictionaryImportViewModel(FormFileUploadResult formFileUploadResult);
}
diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Dictionary/DictionaryMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Dictionary/DictionaryMapDefinition.cs
new file mode 100644
index 0000000000..7c19b1d877
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/Mapping/Dictionary/DictionaryMapDefinition.cs
@@ -0,0 +1,72 @@
+using Umbraco.Cms.Core.Mapping;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Api.Management.ViewModels.Dictionary;
+
+namespace Umbraco.Cms.Api.Management.Mapping.Dictionary;
+
+public class DictionaryMapDefinition : IMapDefinition
+{
+ private readonly ILocalizationService _localizationService;
+
+ public DictionaryMapDefinition(ILocalizationService localizationService) => _localizationService = localizationService;
+
+ public void DefineMaps(IUmbracoMapper mapper)
+ {
+ mapper.Define((_, _) => new DictionaryItemViewModel(), Map);
+ mapper.Define((_, _) => new DictionaryItemTranslationModel(), Map);
+ mapper.Define((_, _) => new DictionaryItem(string.Empty), Map);
+ mapper.Define((_, _) => new DictionaryItem(string.Empty), Map);
+ mapper.Define((_, _) => new DictionaryOverviewViewModel(), Map);
+ }
+
+ // Umbraco.Code.MapAll -Translations
+ private void Map(IDictionaryItem source, DictionaryItemViewModel target, MapperContext context)
+ {
+ target.Key = source.Key;
+ target.Name = source.ItemKey;
+ }
+
+ // Umbraco.Code.MapAll
+ private void Map(IDictionaryTranslation source, DictionaryItemTranslationModel target, MapperContext context)
+ {
+ target.IsoCode = source.Language?.IsoCode ?? throw new ArgumentException("Translation has no language", nameof(source));
+ target.Translation = source.Value;
+ }
+
+ // Umbraco.Code.MapAll -Id -Key -CreateDate -UpdateDate -ParentId -Translations
+ private void Map(DictionaryItemUpdateModel source, IDictionaryItem target, MapperContext context)
+ {
+ target.ItemKey = source.Name;
+ target.DeleteDate = null;
+ }
+
+ // Umbraco.Code.MapAll -Id -Key -CreateDate -UpdateDate -Translations
+ private void Map(DictionaryItemCreateModel source, IDictionaryItem target, MapperContext context)
+ {
+ target.ItemKey = source.Name;
+ target.ParentId = source.ParentKey;
+ target.DeleteDate = null;
+ }
+
+ // Umbraco.Code.MapAll -Level -Translations
+ private void Map(IDictionaryItem source, DictionaryOverviewViewModel target, MapperContext context)
+ {
+ target.Key = source.Key;
+ target.Name = source.ItemKey;
+
+ // add all languages and the translations
+ foreach (ILanguage lang in _localizationService.GetAllLanguages())
+ {
+ var langId = lang.Id;
+ IDictionaryTranslation? translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId);
+
+ target.Translations.Add(
+ new DictionaryTranslationOverviewViewModel
+ {
+ DisplayName = lang.CultureName,
+ HasTranslation = translation != null && string.IsNullOrEmpty(translation.Value) == false,
+ });
+ }
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Dictionary/DictionaryViewModelMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Dictionary/DictionaryViewModelMapDefinition.cs
deleted file mode 100644
index f0721bb032..0000000000
--- a/src/Umbraco.Cms.Api.Management/Mapping/Dictionary/DictionaryViewModelMapDefinition.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using Umbraco.Cms.Core;
-using Umbraco.Cms.Core.Mapping;
-using Umbraco.Cms.Core.Models;
-using Umbraco.Cms.Core.Models.ContentEditing;
-using Umbraco.Cms.Core.Services;
-using Umbraco.Cms.Api.Management.ViewModels.Dictionary;
-
-namespace Umbraco.Cms.Api.Management.Mapping.Dictionary;
-
-public class DictionaryViewModelMapDefinition : IMapDefinition
-{
- private readonly ILocalizationService _localizationService;
-
- public DictionaryViewModelMapDefinition(ILocalizationService localizationService) => _localizationService = localizationService;
-
- public void DefineMaps(IUmbracoMapper mapper)
- {
- mapper.Define((source, context) => new DictionaryItem(string.Empty), Map);
- mapper.Define((source, context) => new DictionaryViewModel(), Map);
- mapper.Define((source, context) => new DictionaryTranslation(source.LanguageId, string.Empty), Map);
- mapper.Define((source, context) => new DictionaryOverviewViewModel(), Map);
-
- }
-
- // Umbraco.Code.MapAll -Id -CreateDate -UpdateDate
- private void Map(DictionaryViewModel source, IDictionaryItem target, MapperContext context)
- {
- target.ItemKey = source.Name!;
- target.Key = source.Key;
- target.ParentId = source.ParentId;
- target.Translations = context.MapEnumerable(source.Translations);
- target.DeleteDate = null;
- }
-
- // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate -Language
- private void Map(DictionaryTranslationViewModel source, IDictionaryTranslation target, MapperContext context)
- {
- target.Value = source.Translation;
- target.Id = source.Id;
- target.Key = source.Key;
- }
-
- // Umbraco.Code.MapAll -Icon -Trashed -Alias -NameIsDirty -ContentApps -Path -Translations
- private void Map(IDictionaryItem source, DictionaryViewModel target, MapperContext context)
- {
- target.Key = source.Key;
- target.Name = source.ItemKey;
- target.ParentId = source.ParentId ?? null;
- }
-
- // Umbraco.Code.MapAll -Level -Translations
- private void Map(IDictionaryItem source, DictionaryOverviewViewModel target, MapperContext context)
- {
- target.Key = source.Key;
- target.Name = source.ItemKey;
-
- // add all languages and the translations
- foreach (ILanguage lang in _localizationService.GetAllLanguages())
- {
- var langId = lang.Id;
- IDictionaryTranslation? translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId);
-
- target.Translations.Add(
- new DictionaryTranslationOverviewViewModel
- {
- DisplayName = lang.CultureName,
- HasTranslation = translation != null && string.IsNullOrEmpty(translation.Value) == false,
- });
- }
- }
-}
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json
index 3b689525c8..166849c65e 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi.json
+++ b/src/Umbraco.Cms.Api.Management/OpenApi.json
@@ -556,18 +556,21 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/DictionaryItem"
+ "$ref": "#/components/schemas/DictionaryItemCreateModel"
}
}
}
},
"responses": {
"201": {
- "description": "Created",
+ "description": "Created"
+ },
+ "404": {
+ "description": "Not Found",
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/CreatedResult"
+ "$ref": "#/components/schemas/ProblemDetails"
}
}
}
@@ -581,56 +584,13 @@
}
}
}
- }
- }
- }
- },
- "/umbraco/management/api/v1/dictionary/{id}": {
- "patch": {
- "tags": [
- "Dictionary"
- ],
- "operationId": "PatchDictionaryById",
- "parameters": [
- {
- "name": "id",
- "in": "path",
- "required": true,
- "schema": {
- "type": "string",
- "format": "uuid"
- }
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/JsonPatch"
- }
- }
- }
- }
- },
- "responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/ContentResult"
- }
- }
- }
},
- "404": {
- "description": "Not Found",
+ "409": {
+ "description": "Conflict",
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/NotFoundResult"
+ "$ref": "#/components/schemas/ProblemDetails"
}
}
}
@@ -661,7 +621,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Dictionary"
+ "$ref": "#/components/schemas/DictionaryItem"
}
}
}
@@ -671,7 +631,58 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/NotFoundResult"
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ }
+ }
+ },
+ "put": {
+ "tags": [
+ "Dictionary"
+ ],
+ "operationId": "PutDictionaryByKey",
+ "parameters": [
+ {
+ "name": "key",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DictionaryItemUpdateModel"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Success"
+ },
+ "400": {
+ "description": "Bad Request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ProblemDetails"
}
}
}
@@ -703,7 +714,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/NotFoundResult"
+ "$ref": "#/components/schemas/ProblemDetails"
}
}
}
@@ -815,7 +826,7 @@
],
"operationId": "PostDictionaryUpload",
"requestBody": {
- "content": {}
+ "content": { }
},
"responses": {
"200": {
@@ -1216,16 +1227,6 @@
}
],
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/PagedRecycleBinItem"
- }
- }
- }
- },
"401": {
"description": "Unauthorized",
"content": {
@@ -1235,6 +1236,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedRecycleBinItem"
+ }
+ }
+ }
}
}
}
@@ -1266,16 +1277,6 @@
}
],
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/PagedRecycleBinItem"
- }
- }
- }
- },
"401": {
"description": "Unauthorized",
"content": {
@@ -1285,6 +1286,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedRecycleBinItem"
+ }
+ }
+ }
}
}
}
@@ -1515,16 +1526,6 @@
}
],
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HealthCheckGroupWithResult"
- }
- }
- }
- },
"404": {
"description": "Not Found",
"content": {
@@ -1534,6 +1535,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HealthCheckGroupWithResult"
+ }
+ }
+ }
}
}
}
@@ -1554,16 +1565,6 @@
}
},
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HealthCheckResult"
- }
- }
- }
- },
"400": {
"description": "Bad Request",
"content": {
@@ -1573,6 +1574,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HealthCheckResult"
+ }
+ }
+ }
}
}
}
@@ -1624,16 +1635,6 @@
}
],
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/PagedHelpPage"
- }
- }
- }
- },
"400": {
"description": "Bad Request",
"content": {
@@ -1643,6 +1644,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedHelpPage"
+ }
+ }
+ }
}
}
}
@@ -1702,16 +1713,6 @@
}
],
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Index"
- }
- }
- }
- },
"400": {
"description": "Bad Request",
"content": {
@@ -1721,6 +1722,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Index"
+ }
+ }
+ }
}
}
}
@@ -1742,16 +1753,6 @@
}
],
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/OkResult"
- }
- }
- }
- },
"400": {
"description": "Bad Request",
"content": {
@@ -1761,6 +1762,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OkResult"
+ }
+ }
+ }
}
}
}
@@ -1772,16 +1783,6 @@
],
"operationId": "GetInstallSettings",
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/InstallSettings"
- }
- }
- }
- },
"400": {
"description": "Bad Request",
"content": {
@@ -1801,6 +1802,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InstallSettings"
+ }
+ }
+ }
}
}
}
@@ -1821,9 +1832,6 @@
}
},
"responses": {
- "200": {
- "description": "Success"
- },
"400": {
"description": "Bad Request",
"content": {
@@ -1843,6 +1851,9 @@
}
}
}
+ },
+ "200": {
+ "description": "Success"
}
}
}
@@ -1863,9 +1874,6 @@
}
},
"responses": {
- "200": {
- "description": "Success"
- },
"400": {
"description": "Bad Request",
"content": {
@@ -1875,6 +1883,9 @@
}
}
}
+ },
+ "200": {
+ "description": "Success"
}
}
}
@@ -1895,9 +1906,6 @@
}
},
"responses": {
- "201": {
- "description": "Created"
- },
"400": {
"description": "Bad Request",
"content": {
@@ -1907,6 +1915,9 @@
}
}
}
+ },
+ "201": {
+ "description": "Created"
}
}
},
@@ -1965,16 +1976,6 @@
}
],
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Language"
- }
- }
- }
- },
"404": {
"description": "Not Found",
"content": {
@@ -1984,6 +1985,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Language"
+ }
+ }
+ }
}
}
},
@@ -2004,9 +2015,6 @@
}
],
"responses": {
- "200": {
- "description": "Success"
- },
"400": {
"description": "Bad Request",
"content": {
@@ -2026,6 +2034,9 @@
}
}
}
+ },
+ "200": {
+ "description": "Success"
}
}
},
@@ -2055,8 +2066,15 @@
}
},
"responses": {
- "200": {
- "description": "Success"
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/NotFoundResult"
+ }
+ }
+ }
},
"400": {
"description": "Bad Request",
@@ -2068,15 +2086,8 @@
}
}
},
- "404": {
- "description": "Not Found",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/NotFoundResult"
- }
- }
- }
+ "200": {
+ "description": "Success"
}
}
}
@@ -2256,16 +2267,6 @@
}
],
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/PagedRecycleBinItem"
- }
- }
- }
- },
"401": {
"description": "Unauthorized",
"content": {
@@ -2275,6 +2276,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedRecycleBinItem"
+ }
+ }
+ }
}
}
}
@@ -2306,16 +2317,6 @@
}
],
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/PagedRecycleBinItem"
- }
- }
- }
- },
"401": {
"description": "Unauthorized",
"content": {
@@ -2325,6 +2326,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedRecycleBinItem"
+ }
+ }
+ }
}
}
}
@@ -2932,16 +2943,6 @@
}
],
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/PagedRedirectUrl"
- }
- }
- }
- },
"400": {
"description": "Bad Request",
"content": {
@@ -2951,6 +2952,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PagedRedirectUrl"
+ }
+ }
+ }
}
}
}
@@ -3490,16 +3501,6 @@
],
"operationId": "GetServerStatus",
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/ServerStatus"
- }
- }
- }
- },
"400": {
"description": "Bad Request",
"content": {
@@ -3509,6 +3510,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServerStatus"
+ }
+ }
+ }
}
}
}
@@ -3520,16 +3531,6 @@
],
"operationId": "GetServerVersion",
"responses": {
- "200": {
- "description": "Success",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/Version"
- }
- }
- }
- },
"400": {
"description": "Bad Request",
"content": {
@@ -3539,6 +3540,16 @@
}
}
}
+ },
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Version"
+ }
+ }
+ }
}
}
}
@@ -3859,9 +3870,6 @@
}
},
"responses": {
- "200": {
- "description": "Success"
- },
"400": {
"description": "Bad Request",
"content": {
@@ -3871,6 +3879,9 @@
}
}
}
+ },
+ "200": {
+ "description": "Success"
}
}
}
@@ -4556,23 +4567,6 @@
},
"additionalProperties": false
},
- "BackOfficeNotification": {
- "type": "object",
- "properties": {
- "header": {
- "type": "string",
- "nullable": true
- },
- "message": {
- "type": "string",
- "nullable": true
- },
- "notificationType": {
- "$ref": "#/components/schemas/NotificationStyle"
- }
- },
- "additionalProperties": false
- },
"CallingConventions": {
"enum": [
"Standard",
@@ -4726,63 +4720,6 @@
},
"additionalProperties": false
},
- "ContentApp": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true
- },
- "alias": {
- "type": "string",
- "nullable": true
- },
- "weight": {
- "type": "integer",
- "format": "int32"
- },
- "icon": {
- "type": "string",
- "nullable": true
- },
- "view": {
- "type": "string",
- "nullable": true
- },
- "viewModel": {
- "nullable": true
- },
- "active": {
- "type": "boolean"
- },
- "badge": {
- "$ref": "#/components/schemas/ContentAppBadge"
- }
- },
- "additionalProperties": false
- },
- "ContentAppBadge": {
- "type": "object",
- "properties": {
- "count": {
- "type": "integer",
- "format": "int32"
- },
- "type": {
- "$ref": "#/components/schemas/ContentAppBadgeType"
- }
- },
- "additionalProperties": false
- },
- "ContentAppBadgeType": {
- "enum": [
- "default",
- "warning",
- "alert"
- ],
- "type": "integer",
- "format": "int32"
- },
"ContentResult": {
"type": "object",
"properties": {
@@ -5145,50 +5082,6 @@
},
"additionalProperties": false
},
- "Dictionary": {
- "required": [
- "name"
- ],
- "type": "object",
- "properties": {
- "parentId": {
- "type": "string",
- "format": "uuid",
- "nullable": true
- },
- "translations": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/DictionaryTranslation"
- }
- },
- "contentApps": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/ContentApp"
- }
- },
- "notifications": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/BackOfficeNotification"
- },
- "readOnly": true
- },
- "name": {
- "minLength": 1,
- "type": "string"
- },
- "key": {
- "type": "string",
- "format": "uuid"
- },
- "path": {
- "type": "string"
- }
- },
- "additionalProperties": false
- },
"DictionaryImport": {
"type": "object",
"properties": {
@@ -5208,10 +5101,14 @@
"DictionaryItem": {
"type": "object",
"properties": {
- "parentId": {
- "type": "string",
- "format": "uuid",
- "nullable": true
+ "name": {
+ "type": "string"
+ },
+ "translations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DictionaryItemTranslationModel"
+ }
},
"key": {
"type": "string",
@@ -5220,6 +5117,53 @@
},
"additionalProperties": false
},
+ "DictionaryItemCreateModel": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "translations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DictionaryItemTranslationModel"
+ }
+ },
+ "parentKey": {
+ "type": "string",
+ "format": "uuid",
+ "nullable": true
+ }
+ },
+ "additionalProperties": false
+ },
+ "DictionaryItemTranslationModel": {
+ "type": "object",
+ "properties": {
+ "isoCode": {
+ "type": "string"
+ },
+ "translation": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "DictionaryItemUpdateModel": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "translations": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/DictionaryItemTranslationModel"
+ }
+ }
+ },
+ "additionalProperties": false
+ },
"DictionaryItemsImport": {
"type": "object",
"properties": {
@@ -5259,35 +5203,6 @@
},
"additionalProperties": false
},
- "DictionaryTranslation": {
- "type": "object",
- "properties": {
- "id": {
- "type": "integer",
- "format": "int32"
- },
- "key": {
- "type": "string",
- "format": "uuid"
- },
- "displayName": {
- "type": "string",
- "nullable": true
- },
- "isoCode": {
- "type": "string",
- "nullable": true
- },
- "translation": {
- "type": "string"
- },
- "languageId": {
- "type": "integer",
- "format": "int32"
- }
- },
- "additionalProperties": false
- },
"DictionaryTranslationOverview": {
"type": "object",
"properties": {
@@ -5987,7 +5902,7 @@
},
"providerProperties": {
"type": "object",
- "additionalProperties": {},
+ "additionalProperties": { },
"nullable": true
}
},
@@ -6031,19 +5946,6 @@
"type": "object",
"additionalProperties": false
},
- "JsonPatch": {
- "type": "object",
- "properties": {
- "op": {
- "type": "string"
- },
- "path": {
- "type": "string"
- },
- "value": { }
- },
- "additionalProperties": false
- },
"Language": {
"required": [
"isoCode"
@@ -6567,17 +6469,6 @@
},
"additionalProperties": false
},
- "NotificationStyle": {
- "enum": [
- "Save",
- "Info",
- "Error",
- "Success",
- "Warning"
- ],
- "type": "integer",
- "format": "int32"
- },
"OkResult": {
"type": "object",
"properties": {
@@ -7131,7 +7022,7 @@
"nullable": true
}
},
- "additionalProperties": {}
+ "additionalProperties": { }
},
"ProfilingStatus": {
"type": "object",
@@ -8477,7 +8368,7 @@
"authorizationCode": {
"authorizationUrl": "/umbraco/management/api/v1.0/security/back-office/authorize",
"tokenUrl": "/umbraco/management/api/v1.0/security/back-office/token",
- "scopes": {}
+ "scopes": { }
}
}
}
@@ -8485,7 +8376,7 @@
},
"security": [
{
- "OAuth": []
+ "OAuth": [ ]
}
]
}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemCreateModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemCreateModel.cs
new file mode 100644
index 0000000000..b2cbe45f3c
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemCreateModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary;
+
+public class DictionaryItemCreateModel : DictionaryItemModelBase
+{
+ public Guid? ParentKey { get; set; }
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemModelBase.cs
new file mode 100644
index 0000000000..ebdf72fa30
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemModelBase.cs
@@ -0,0 +1,8 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary;
+
+public class DictionaryItemModelBase
+{
+ public string Name { get; set; } = null!;
+
+ public IEnumerable Translations { get; set; } = Array.Empty();
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemTranslationModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemTranslationModel.cs
new file mode 100644
index 0000000000..57d08380b2
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemTranslationModel.cs
@@ -0,0 +1,8 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary;
+
+public class DictionaryItemTranslationModel
+{
+ public string IsoCode { get; set; } = string.Empty;
+
+ public string Translation { get; set; } = string.Empty;
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemUpdateModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemUpdateModel.cs
new file mode 100644
index 0000000000..07835c2c91
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemUpdateModel.cs
@@ -0,0 +1,5 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary;
+
+public class DictionaryItemUpdateModel : DictionaryItemModelBase
+{
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemViewModel.cs
index 5616705c05..a7ee7e5b32 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemViewModel.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemViewModel.cs
@@ -1,8 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary;
-public class DictionaryItemViewModel
+public class DictionaryItemViewModel : DictionaryItemModelBase
{
- public Guid? ParentId { get; set; }
-
public Guid Key { get; set; }
}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryTranslationViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryTranslationViewModel.cs
deleted file mode 100644
index 9d82f93f1e..0000000000
--- a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryTranslationViewModel.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary;
-
-public class DictionaryTranslationViewModel
-{
- public int Id { get; set; }
-
- public Guid Key { get; set; }
-
- ///
- /// Gets or sets the display name.
- ///
- public string? DisplayName { get; set; }
-
- ///
- /// Gets or sets the ISO code.
- ///
- public string? IsoCode { get; set; }
-
- ///
- /// Gets or sets the translation.
- ///
- public string Translation { get; set; } = null!;
-
- ///
- /// Gets or sets the language id.
- ///
- public int LanguageId { get; set; }
-}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryViewModel.cs
deleted file mode 100644
index 03fc307862..0000000000
--- a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryViewModel.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using Umbraco.Cms.Core.Models.ContentEditing;
-using Umbraco.Cms.Core.Models.Validation;
-
-namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary;
-
-///
-/// The dictionary display model
-///
-public class DictionaryViewModel : INotificationModel
-{
- ///
- /// Initializes a new instance of the class.
- ///
- public DictionaryViewModel()
- {
- Notifications = new List();
- Translations = new List();
- ContentApps = new List();
- }
-
- ///
- /// Gets or sets the parent id.
- ///
- public Guid? ParentId { get; set; }
-
- ///
- /// Gets or sets the translations.
- ///
- public IEnumerable Translations { get; set; } = Enumerable.Empty();
-
- ///
- /// Apps for the dictionary item
- ///
- public IEnumerable ContentApps { get; set; }
-
- ///
- ///
- /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes.
- ///
- public List Notifications { get; private set; }
-
- [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")]
- [Required]
- public string Name { get; set; } = null!;
-
- ///
- /// Gets or sets the Key for the object
- ///
- public Guid Key { get; set; }
-
- ///
- /// The path of the entity
- ///
- public string Path { get; set; } = string.Empty;
-}
diff --git a/src/Umbraco.Core/CompatibilitySuppressions.xml b/src/Umbraco.Core/CompatibilitySuppressions.xml
index e6196e4796..77f7c39d35 100644
--- a/src/Umbraco.Core/CompatibilitySuppressions.xml
+++ b/src/Umbraco.Core/CompatibilitySuppressions.xml
@@ -301,6 +301,27 @@
lib/net7.0/Umbraco.Core.dll
true
+
+ CP0006
+ M:Umbraco.Cms.Core.Services.ILocalizationService.Create(System.String,System.Nullable{System.Guid},System.Collections.Generic.IEnumerable{Umbraco.Cms.Core.Models.IDictionaryTranslation},System.Int32)
+ lib/net7.0/Umbraco.Core.dll
+ lib/net7.0/Umbraco.Core.dll
+ true
+
+
+ CP0006
+ M:Umbraco.Cms.Core.Services.ILocalizationService.Delete(System.Guid,System.Int32)
+ lib/net7.0/Umbraco.Core.dll
+ lib/net7.0/Umbraco.Core.dll
+ true
+
+
+ CP0006
+ M:Umbraco.Cms.Core.Services.ILocalizationService.Update(Umbraco.Cms.Core.Models.IDictionaryItem,System.Int32)
+ lib/net7.0/Umbraco.Core.dll
+ lib/net7.0/Umbraco.Core.dll
+ true
+
CP0006
P:Umbraco.Cms.Core.Models.IDataType.ConfigurationData
diff --git a/src/Umbraco.Core/Services/ILocalizationService.cs b/src/Umbraco.Core/Services/ILocalizationService.cs
index dbfb01d3e1..7cd403b4ab 100644
--- a/src/Umbraco.Core/Services/ILocalizationService.cs
+++ b/src/Umbraco.Core/Services/ILocalizationService.cs
@@ -1,4 +1,5 @@
using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.New.Cms.Core.Models;
namespace Umbraco.Cms.Core.Services;
@@ -30,8 +31,23 @@ public interface ILocalizationService : IService
///
///
///
+ [Obsolete("Please use Create. Will be removed in V15")]
IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parentId, string? defaultValue = null);
+ ///
+ /// Creates and saves a new dictionary item and assigns translations to all applicable languages if specified
+ ///
+ ///
+ ///
+ ///
+ /// Optional id of the user saving the dictionary item
+ ///
+ Attempt Create(
+ string key,
+ Guid? parentId,
+ IEnumerable? translations = null,
+ int userId = Constants.Security.SuperUserId);
+
///
/// Gets a by its id
///
@@ -109,16 +125,33 @@ public interface ILocalizationService : IService
///
/// to save
/// Optional id of the user saving the dictionary item
+ [Obsolete("Please use Update. Will be removed in V15")]
void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId);
+ ///
+ /// Updates an existing object
+ ///
+ /// to update
+ /// Optional id of the user saving the dictionary item
+ Attempt Update(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId);
+
///
/// Deletes a object and its related translations
/// as well as its children.
///
/// to delete
/// Optional id of the user deleting the dictionary item
+ [Obsolete("Please use the Delete method that takes an ID and returns an Attempt. Will be removed in V15")]
void Delete(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId);
+ ///
+ /// Deletes a object and its related translations
+ /// as well as its children.
+ ///
+ /// The ID of the to delete
+ /// Optional id of the user deleting the dictionary item
+ Attempt Delete(Guid id, int userId = Constants.Security.SuperUserId);
+
///
/// Gets a by its id
///
diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs
index 824aed8cf5..73faa6c460 100644
--- a/src/Umbraco.Core/Services/LocalizationService.cs
+++ b/src/Umbraco.Core/Services/LocalizationService.cs
@@ -2,9 +2,9 @@
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
-using Umbraco.Cms.Core.Persistence.Querying;
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;
@@ -83,7 +83,27 @@ internal class LocalizationService : RepositoryService, ILocalizationService
///
///
///
+ [Obsolete("Please use Create. Will be removed in V15")]
public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parentId, string? defaultValue = null)
+ {
+ IEnumerable translations = defaultValue.IsNullOrWhiteSpace()
+ ? Array.Empty()
+ : GetAllLanguages()
+ .Select(language => new DictionaryTranslation(language, defaultValue!))
+ .ToArray();
+
+ Attempt result = Create(key, parentId, translations);
+ return result.Success
+ ? result.Result!
+ : throw new ArgumentException($"Could not create a dictionary item with key: {key} under parent: {parentId}");
+ }
+
+ ///
+ public Attempt Create(
+ string key,
+ Guid? parentId,
+ IEnumerable? translations = null,
+ int userId = Constants.Security.SuperUserId)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
@@ -93,20 +113,23 @@ internal class LocalizationService : RepositoryService, ILocalizationService
IDictionaryItem? parent = GetDictionaryItemById(parentId.Value);
if (parent == null)
{
- throw new ArgumentException($"No parent dictionary item was found with id {parentId.Value}.");
+ return Attempt.FailWithStatus(DictionaryItemOperationStatus.ParentNotFound, null);
}
}
var item = new DictionaryItem(parentId, key);
- if (defaultValue.IsNullOrWhiteSpace() == false)
+ // do we have an item key collision (item keys must be unique)?
+ if (HasItemKeyCollision(item))
{
- IEnumerable langs = GetAllLanguages();
- var translations = langs.Select(language => new DictionaryTranslation(language, defaultValue!))
- .Cast()
- .ToList();
+ return Attempt.FailWithStatus(DictionaryItemOperationStatus.DuplicateItemKey, null);
+ }
- item.Translations = translations;
+ IDictionaryTranslation[] translationsAsArray = translations?.ToArray() ?? Array.Empty();
+ if (translationsAsArray.Any())
+ {
+ var allLanguageIds = GetAllLanguages().Select(language => language.Id).ToArray();
+ item.Translations = translationsAsArray.Where(translation => allLanguageIds.Contains(translation.LanguageId)).ToArray();
}
EventMessages eventMessages = EventMessagesFactory.Get();
@@ -115,7 +138,7 @@ internal class LocalizationService : RepositoryService, ILocalizationService
if (scope.Notifications.PublishCancelable(savingNotification))
{
scope.Complete();
- return item;
+ return Attempt.FailWithStatus(DictionaryItemOperationStatus.CancelledByNotification, item);
}
_dictionaryRepository.Save(item);
@@ -126,9 +149,10 @@ internal class LocalizationService : RepositoryService, ILocalizationService
scope.Notifications.Publish(
new DictionaryItemSavedNotification(item, eventMessages).WithStateFrom(savingNotification));
+ Audit(AuditType.New, "Create DictionaryItem", userId, item.Id, "DictionaryItem");
scope.Complete();
- return item;
+ return Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, item);
}
}
@@ -310,6 +334,7 @@ internal class LocalizationService : RepositoryService, ILocalizationService
///
/// to save
/// Optional id of the user saving the dictionary item
+ [Obsolete("Please use Update. Will be removed in V15")]
public void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
@@ -335,22 +360,72 @@ internal class LocalizationService : RepositoryService, ILocalizationService
}
}
+ ///
+ public Attempt Update(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId)
+ {
+ using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+ {
+ // is there an item to update?
+ if (_dictionaryRepository.Exists(dictionaryItem.Id) == false)
+ {
+ return Attempt.FailWithStatus(DictionaryItemOperationStatus.ItemNotFound, dictionaryItem);
+ }
+
+ // do we have an item key collision (item keys must be unique)?
+ if (HasItemKeyCollision(dictionaryItem))
+ {
+ return Attempt.FailWithStatus(DictionaryItemOperationStatus.DuplicateItemKey, dictionaryItem);
+ }
+
+ EventMessages eventMessages = EventMessagesFactory.Get();
+ var savingNotification = new DictionaryItemSavingNotification(dictionaryItem, eventMessages);
+ if (scope.Notifications.PublishCancelable(savingNotification))
+ {
+ scope.Complete();
+ return Attempt.FailWithStatus(DictionaryItemOperationStatus.CancelledByNotification, dictionaryItem);
+ }
+
+ _dictionaryRepository.Save(dictionaryItem);
+
+ // ensure the lazy Language callback is assigned
+ EnsureDictionaryItemLanguageCallback(dictionaryItem);
+ scope.Notifications.Publish(
+ new DictionaryItemSavedNotification(dictionaryItem, eventMessages).WithStateFrom(savingNotification));
+
+ Audit(AuditType.Save, "Update DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem");
+ scope.Complete();
+
+ return Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem);
+ }
+ }
+
///
/// Deletes a object and its related translations
/// as well as its children.
///
/// to delete
/// Optional id of the user deleting the dictionary item
+ [Obsolete("Please use the Delete method that takes an ID and returns an Attempt. Will be removed in V15")]
public void Delete(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId)
+ => Delete(dictionaryItem.Key, userId);
+
+ ///
+ public Attempt Delete(Guid id, int userId = Constants.Security.SuperUserId)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
+ IDictionaryItem? dictionaryItem = _dictionaryRepository.Get(id);
+ if (dictionaryItem == null)
+ {
+ return Attempt.FailWithStatus(DictionaryItemOperationStatus.ItemNotFound, null);
+ }
+
EventMessages eventMessages = EventMessagesFactory.Get();
var deletingNotification = new DictionaryItemDeletingNotification(dictionaryItem, eventMessages);
if (scope.Notifications.PublishCancelable(deletingNotification))
{
scope.Complete();
- return;
+ return Attempt.FailWithStatus(DictionaryItemOperationStatus.CancelledByNotification, dictionaryItem);
}
_dictionaryRepository.Delete(dictionaryItem);
@@ -361,6 +436,7 @@ internal class LocalizationService : RepositoryService, ILocalizationService
Audit(AuditType.Delete, "Delete DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem");
scope.Complete();
+ return Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem);
}
}
@@ -589,4 +665,10 @@ internal class LocalizationService : RepositoryService, ILocalizationService
}
}
}
+
+ private bool HasItemKeyCollision(IDictionaryItem dictionaryItem)
+ {
+ IDictionaryItem? itemKeyCollision = _dictionaryRepository.Get(dictionaryItem.ItemKey);
+ return itemKeyCollision != null && itemKeyCollision.Key != dictionaryItem.Key;
+ }
}
diff --git a/src/Umbraco.Core/Services/OperationStatus/DictionaryItemOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/DictionaryItemOperationStatus.cs
new file mode 100644
index 0000000000..d15e97c7ba
--- /dev/null
+++ b/src/Umbraco.Core/Services/OperationStatus/DictionaryItemOperationStatus.cs
@@ -0,0 +1,10 @@
+namespace Umbraco.Cms.Core.Services.OperationStatus;
+
+public enum DictionaryItemOperationStatus
+{
+ Success,
+ CancelledByNotification,
+ DuplicateItemKey,
+ ItemNotFound,
+ ParentNotFound
+}
diff --git a/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml
new file mode 100644
index 0000000000..e8bace47de
--- /dev/null
+++ b/tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+ CP0002
+ M:Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.LocalizationServiceTests.Can_Create_DictionaryItem_At_Root_With_Identity
+ lib/net7.0/Umbraco.Tests.Integration.dll
+ lib/net7.0/Umbraco.Tests.Integration.dll
+ true
+
+
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs
index 08dddf715d..f526541bbb 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs
@@ -168,16 +168,11 @@ public class CreatedPackagesRepositoryTests : UmbracoIntegrationTest
[Test]
public void GivenNestedDictionaryItems_WhenPackageExported_ThenTheXmlIsNested()
{
- var parent = new DictionaryItem("Parent") { Key = Guid.NewGuid() };
- LocalizationService.Save(parent);
- var child1 = new DictionaryItem(parent.Key, "Child1") { Key = Guid.NewGuid() };
- LocalizationService.Save(child1);
- var child2 = new DictionaryItem(child1.Key, "Child2") { Key = Guid.NewGuid() };
- LocalizationService.Save(child2);
- var child3 = new DictionaryItem(child2.Key, "Child3") { Key = Guid.NewGuid() };
- LocalizationService.Save(child3);
- var child4 = new DictionaryItem(child3.Key, "Child4") { Key = Guid.NewGuid() };
- LocalizationService.Save(child4);
+ var parent = LocalizationService.Create("Parent", null).Result!;
+ var child1 = LocalizationService.Create("Child1", parent.Key).Result!;
+ var child2 = LocalizationService.Create("Child2", child1.Key).Result!;
+ var child3 = LocalizationService.Create("Child3", child2.Key).Result!;
+ var child4 = LocalizationService.Create("Child4", child3.Key).Result!;
var def = new PackageDefinition
{
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs
index 628fa419c2..de1c0b33f0 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs
@@ -404,20 +404,14 @@ public class DictionaryRepositoryTest : UmbracoIntegrationTest
var languageDK = new Language("da-DK", "Danish (Denmark)");
localizationService.Save(languageDK); //Id 2
- var readMore = new DictionaryItem("Read More");
- var translations = new List
+ localizationService.Create("Read More", null, new List
{
new DictionaryTranslation(language, "Read More"), new DictionaryTranslation(languageDK, "Læs mere")
- };
- readMore.Translations = translations;
- localizationService.Save(readMore); // Id 1
+ }); // Id 1
- var article = new DictionaryItem("Article");
- var translations2 = new List
+ localizationService.Create("Article", null, new List
{
new DictionaryTranslation(language, "Article"), new DictionaryTranslation(languageDK, "Artikel")
- };
- article.Translations = translations2;
- localizationService.Save(article); // Id 2
+ }); // Id 2
}
}
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs
index dfc5ee8bb7..b0d3dfcd1a 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs
@@ -240,9 +240,10 @@ public class ScopedRepositoryTests : UmbracoIntegrationTest
var lang = new Language("fr-FR", "French (France)");
service.Save(lang);
- var item = (IDictionaryItem)new DictionaryItem("item-key");
- item.Translations = new IDictionaryTranslation[] { new DictionaryTranslation(lang.Id, "item-value") };
- service.Save(item);
+ var item = service.Create(
+ "item-key",
+ null,
+ new IDictionaryTranslation[] { new DictionaryTranslation(lang.Id, "item-value") }).Result!;
// Refresh the cache manually because we can't unbind
service.GetDictionaryItemById(item.Id);
@@ -266,7 +267,7 @@ public class ScopedRepositoryTests : UmbracoIntegrationTest
Assert.AreNotSame(globalCache, scopedCache);
item.ItemKey = "item-changed";
- service.Save(item);
+ service.Update(item);
// scoped cache contains the "new" entity
var scopeCached = (IDictionaryItem)scopedCache.Get(GetCacheIdKey(item.Id), () => null);
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs
index 2ca4af5fae..b93e7ea77d 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs
@@ -1,13 +1,11 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
-using System;
-using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
@@ -136,26 +134,27 @@ public class LocalizationServiceTests : UmbracoIntegrationTest
for (var i = 0; i < 25; i++)
{
// Create 2 per level
- var desc1 = new DictionaryItem(currParentId, "D1" + i)
- {
- Translations = new List
+ var result = LocalizationService.Create(
+ "D1" + i,
+ currParentId,
+ new List
{
new DictionaryTranslation(en, "ChildValue1 " + i),
new DictionaryTranslation(dk, "BørnVærdi1 " + i)
- }
- };
- var desc2 = new DictionaryItem(currParentId, "D2" + i)
- {
- Translations = new List
+ });
+
+ Assert.IsTrue(result.Success);
+
+ LocalizationService.Create(
+ "D2" + i,
+ currParentId,
+ new List
{
new DictionaryTranslation(en, "ChildValue2 " + i),
new DictionaryTranslation(dk, "BørnVærdi2 " + i)
- }
- };
- LocalizationService.Save(desc1);
- LocalizationService.Save(desc2);
+ });
- currParentId = desc1.Key;
+ currParentId = result.Result!.Key;
}
ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
@@ -242,14 +241,12 @@ public class LocalizationServiceTests : UmbracoIntegrationTest
{
var english = LocalizationService.GetLanguageByIsoCode("en-US");
- var item = (IDictionaryItem)new DictionaryItem("Testing123")
- {
- Translations = new List { new DictionaryTranslation(english, "Hello world") }
- };
- LocalizationService.Save(item);
+ var result = LocalizationService.Create("Testing123", null, new List { new DictionaryTranslation(english, "Hello world") });
+ Assert.True(result.Success);
// re-get
- item = LocalizationService.GetDictionaryItemById(item.Id);
+ var item = LocalizationService.GetDictionaryItemById(result.Result!.Id);
+ Assert.NotNull(item);
Assert.Greater(item.Id, 0);
Assert.IsTrue(item.HasIdentity);
@@ -259,42 +256,75 @@ public class LocalizationServiceTests : UmbracoIntegrationTest
}
[Test]
- public void Can_Create_DictionaryItem_At_Root_With_Identity()
+ public void Can_Create_DictionaryItem_At_Root_With_All_Languages()
{
- var item = LocalizationService.CreateDictionaryItemWithIdentity(
- "Testing12345", null, "Hellooooo");
+ var allLangs = LocalizationService.GetAllLanguages().ToArray();
+ Assert.Greater(allLangs.Length, 0);
+
+ var translations = allLangs.Select(language => new DictionaryTranslation(language, $"Translation for: {language.IsoCode}")).ToArray();
+ var result = LocalizationService.Create("Testing12345", null, translations);
+
+ Assert.IsTrue(result.Success);
+ Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status);
+ Assert.NotNull(result.Result);
// re-get
- item = LocalizationService.GetDictionaryItemById(item.Id);
+ var item = LocalizationService.GetDictionaryItemById(result.Result!.Id);
Assert.IsNotNull(item);
Assert.Greater(item.Id, 0);
Assert.IsTrue(item.HasIdentity);
Assert.IsFalse(item.ParentId.HasValue);
Assert.AreEqual("Testing12345", item.ItemKey);
- var allLangs = LocalizationService.GetAllLanguages();
- Assert.Greater(allLangs.Count(), 0);
foreach (var language in allLangs)
{
- Assert.AreEqual("Hellooooo",
+ Assert.AreEqual($"Translation for: {language.IsoCode}",
item.Translations.Single(x => x.Language.CultureName == language.CultureName).Value);
}
}
+ [Test]
+ public void Can_Create_DictionaryItem_At_Root_With_Some_Languages()
+ {
+ var allLangs = LocalizationService.GetAllLanguages().ToArray();
+ Assert.Greater(allLangs.Length, 1);
+
+ var firstLanguage = allLangs.First();
+ var translations = new[] { new DictionaryTranslation(firstLanguage, $"Translation for: {firstLanguage.IsoCode}") };
+ var result = LocalizationService.Create("Testing12345", null, translations);
+
+ Assert.IsTrue(result.Success);
+ Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status);
+ Assert.NotNull(result.Result);
+
+ // re-get
+ var item = LocalizationService.GetDictionaryItemById(result.Result!.Id);
+
+ Assert.IsNotNull(item);
+ Assert.Greater(item.Id, 0);
+ Assert.IsTrue(item.HasIdentity);
+ Assert.IsFalse(item.ParentId.HasValue);
+ Assert.AreEqual("Testing12345", item.ItemKey);
+ Assert.AreEqual(1, item.Translations.Count());
+ Assert.AreEqual(firstLanguage.Id, item.Translations.First().LanguageId);
+ }
+
[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);
+ var result = LocalizationService.Create("Testing123", null);
+ Assert.True(result.Success);
// re-get
- item = LocalizationService.GetDictionaryItemById(item.Id);
+ var item = LocalizationService.GetDictionaryItemById(result.Result!.Id);
+ Assert.NotNull(item);
item.Translations = new List { new DictionaryTranslation(english, "Hello world") };
- LocalizationService.Save(item);
+ result = LocalizationService.Update(item);
+ Assert.True(result.Success);
Assert.AreEqual(1, item.Translations.Count());
foreach (var translation in item.Translations)
@@ -309,10 +339,12 @@ public class LocalizationServiceTests : UmbracoIntegrationTest
"My new value")
};
- LocalizationService.Save(item);
+ result = LocalizationService.Update(item);
+ Assert.True(result.Success);
// re-get
item = LocalizationService.GetDictionaryItemById(item.Id);
+ Assert.NotNull(item);
Assert.AreEqual(2, item.Translations.Count());
Assert.AreEqual("Hello world", item.Translations.First().Value);
@@ -325,7 +357,9 @@ public class LocalizationServiceTests : UmbracoIntegrationTest
var item = LocalizationService.GetDictionaryItemByKey("Child");
Assert.NotNull(item);
- LocalizationService.Delete(item);
+ var result = LocalizationService.Delete(item.Key);
+ Assert.IsTrue(result.Success);
+ Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status);
var deletedItem = LocalizationService.GetDictionaryItemByKey("Child");
Assert.Null(deletedItem);
@@ -340,7 +374,8 @@ public class LocalizationServiceTests : UmbracoIntegrationTest
translation.Value += "UPDATED";
}
- LocalizationService.Save(item);
+ var result = LocalizationService.Update(item);
+ Assert.True(result.Success);
var updatedItem = LocalizationService.GetDictionaryItemByKey("Child");
Assert.NotNull(updatedItem);
@@ -437,6 +472,78 @@ public class LocalizationServiceTests : UmbracoIntegrationTest
Assert.Null(result);
}
+ [Test]
+ public void Cannot_Add_Duplicate_DictionaryItem_Key()
+ {
+ var item = LocalizationService.GetDictionaryItemByKey("Child");
+ Assert.IsNotNull(item);
+
+ item.ItemKey = "Parent";
+
+ var result = LocalizationService.Update(item);
+ Assert.IsFalse(result.Success);
+ Assert.AreEqual(DictionaryItemOperationStatus.DuplicateItemKey, result.Status);
+
+ var item2 = LocalizationService.GetDictionaryItemByKey("Child");
+ Assert.IsNotNull(item2);
+ Assert.AreEqual(item.Key, item2.Key);
+ }
+
+ [Test]
+ public void Cannot_Create_Child_DictionaryItem_Under_Missing_Parent()
+ {
+ var itemKey = Guid.NewGuid().ToString("N");
+
+ var result = LocalizationService.Create(itemKey, Guid.NewGuid(), Array.Empty());
+ Assert.IsFalse(result.Success);
+ Assert.AreEqual(DictionaryItemOperationStatus.ParentNotFound, result.Status);
+
+ var item = LocalizationService.GetDictionaryItemByKey(itemKey);
+ Assert.IsNull(item);
+ }
+
+ [Test]
+ public void Cannot_Create_Multiple_DictionaryItems_With_Same_ItemKey()
+ {
+ var itemKey = Guid.NewGuid().ToString("N");
+ var result = LocalizationService.Create(itemKey, null, Array.Empty());
+
+ Assert.IsTrue(result.Success);
+
+ result = LocalizationService.Create(itemKey, null, Array.Empty());
+ Assert.IsFalse(result.Success);
+ Assert.AreEqual(DictionaryItemOperationStatus.DuplicateItemKey, result.Status);
+ }
+
+ [Test]
+ public void Cannot_Update_Non_Existant_DictionaryItem()
+ {
+ var result = LocalizationService.Update(new DictionaryItem("NoSuchItemKey"));
+ Assert.False(result.Success);
+ Assert.AreEqual(DictionaryItemOperationStatus.ItemNotFound, result.Status);
+ }
+
+ [Test]
+ public void Cannot_Update_DictionaryItem_With_Empty_Id()
+ {
+ var item = LocalizationService.GetDictionaryItemByKey("Child");
+ Assert.IsNotNull(item);
+
+ item = new DictionaryItem(item.ParentId, item.ItemKey) { Key = item.Key, Translations = item.Translations };
+
+ var result = LocalizationService.Update(item);
+ Assert.False(result.Success);
+ Assert.AreEqual(DictionaryItemOperationStatus.ItemNotFound, result.Status);
+ }
+
+ [Test]
+ public void Cannot_Delete_Non_Existant_DictionaryItem()
+ {
+ var result = LocalizationService.Delete(Guid.NewGuid());
+ Assert.IsFalse(result.Success);
+ Assert.AreEqual(DictionaryItemOperationStatus.ItemNotFound, result.Status);
+ }
+
public void CreateTestData()
{
var languageDaDk = new LanguageBuilder()
@@ -451,27 +558,31 @@ public class LocalizationServiceTests : UmbracoIntegrationTest
_danishLangId = languageDaDk.Id;
_englishLangId = languageEnGb.Id;
- var parentItem = new DictionaryItem("Parent")
- {
- Translations = new List
+ var result = LocalizationService.Create(
+ "Parent",
+ null,
+ new List
{
new DictionaryTranslation(languageEnGb, "ParentValue"),
new DictionaryTranslation(languageDaDk, "ForældreVærdi")
- }
- };
- LocalizationService.Save(parentItem);
+ });
+ Assert.True(result.Success);
+ IDictionaryItem parentItem = result.Result!;
+
_parentItemGuidId = parentItem.Key;
_parentItemIntId = parentItem.Id;
- var childItem = new DictionaryItem(parentItem.Key, "Child")
- {
- Translations = new List
+ result = LocalizationService.Create(
+ "Child",
+ parentItem.Key,
+ new List
{
new DictionaryTranslation(languageEnGb, "ChildValue"),
new DictionaryTranslation(languageDaDk, "BørnVærdi")
- }
- };
- LocalizationService.Save(childItem);
+ });
+ Assert.True(result.Success);
+ IDictionaryItem childItem = result.Result!;
+
_childItemGuidId = childItem.Key;
_childItemIntId = childItem.Id;
}