Use ISO codes instead of language IDs for fallback languages and translations (#13751)

* Use language ISO code for language fallback instead of language ID

* Remove language and language ID from dictionary item and dictionary item translation

* ADd unit test for dictionary item translation value extension

* Make the internal service implementations sealed

* Rename translation ISO code to be more explicit in its origin (Language)

* Add breaking changes suppression

* Handle save of invalid fallback iso code

* Fixed test

* Only allow non-UserCustomCulture's

* Fixed and added tests

* Rename ISO code validation method

* Fix language telemetry test (create Swedish with the correct ISO code)

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
Kenn Jacobsen
2023-02-01 09:37:37 +01:00
committed by GitHub
parent 641cae7fb5
commit 8caee5297b
52 changed files with 603 additions and 627 deletions

View File

@@ -20,11 +20,15 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>, IDictionaryRepository
{
private readonly ILoggerFactory _loggerFactory;
private readonly ILanguageRepository _languageRepository;
public DictionaryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<DictionaryRepository> logger,
ILoggerFactory loggerFactory)
: base(scopeAccessor, cache, logger) =>
ILoggerFactory loggerFactory, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger)
{
_loggerFactory = loggerFactory;
_languageRepository = languageRepository;
}
public IDictionaryItem? Get(Guid uniqueId)
{
@@ -63,6 +67,8 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
public IEnumerable<IDictionaryItem> GetDictionaryItemDescendants(Guid? parentId)
{
IDictionary<int, ILanguage> languageIsoCodeById = GetLanguagesById();
// This methods will look up children at each level, since we do not store a path for dictionary (ATM), we need to do a recursive
// lookup to get descendants. Currently this is the most efficient way to do it
Func<Guid[], IEnumerable<IEnumerable<IDictionaryItem>>> getItemsFromParents = guids =>
@@ -80,7 +86,7 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
return Database
.FetchOneToMany<DictionaryDto>(x => x.LanguageTextDtos, sql)
.Select(ConvertFromDto);
.Select(dto => ConvertFromDto(dto, languageIsoCodeById));
});
};
@@ -91,7 +97,7 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
.OrderBy<DictionaryDto>(x => x.UniqueId);
return Database
.FetchOneToMany<DictionaryDto>(x => x.LanguageTextDtos, sql)
.Select(ConvertFromDto);
.Select(dto => ConvertFromDto(dto, languageIsoCodeById));
}
return getItemsFromParents(new[] { parentId.Value }).SelectRecursive(items => getItemsFromParents(items.Select(x => x.Key).ToArray())).SelectMany(items => items);
@@ -109,13 +115,16 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
options);
}
protected IDictionaryItem ConvertFromDto(DictionaryDto dto)
private IDictionaryItem ConvertFromDto(DictionaryDto dto, IDictionary<int, ILanguage> languagesById)
{
IDictionaryItem entity = DictionaryItemFactory.BuildEntity(dto);
entity.Translations = dto.LanguageTextDtos.EmptyNull()
.Where(x => x.LanguageId > 0)
.Select(x => DictionaryTranslationFactory.BuildEntity(x, dto.UniqueId))
.Select(x => languagesById.TryGetValue(x.LanguageId, out ILanguage? language)
? DictionaryTranslationFactory.BuildEntity(x, dto.UniqueId, language)
: null)
.WhereNotNull()
.ToList();
return entity;
@@ -138,7 +147,7 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
return null;
}
IDictionaryItem entity = ConvertFromDto(dto);
IDictionaryItem entity = ConvertFromDto(dto, GetLanguagesById());
// reset dirty initial properties (U4-1946)
((EntityBase)entity).ResetDirtyProperties(false);
@@ -162,11 +171,15 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
private class DictionaryByUniqueIdRepository : SimpleGetRepository<Guid, IDictionaryItem, DictionaryDto>
{
private readonly DictionaryRepository _dictionaryRepository;
private readonly IDictionary<int, ILanguage> _languagesById;
public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor,
AppCaches cache, ILogger<DictionaryByUniqueIdRepository> logger)
: base(scopeAccessor, cache, logger) =>
: base(scopeAccessor, cache, logger)
{
_dictionaryRepository = dictionaryRepository;
_languagesById = dictionaryRepository.GetLanguagesById();
}
protected override IEnumerable<DictionaryDto> PerformFetch(Sql sql) =>
Database
@@ -178,7 +191,7 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
"cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " = @id";
protected override IDictionaryItem ConvertToEntity(DictionaryDto dto) =>
_dictionaryRepository.ConvertFromDto(dto);
_dictionaryRepository.ConvertFromDto(dto, _languagesById);
protected override object GetBaseWhereClauseArguments(Guid id) => new { id };
@@ -201,11 +214,15 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
private class DictionaryByKeyRepository : SimpleGetRepository<string, IDictionaryItem, DictionaryDto>
{
private readonly DictionaryRepository _dictionaryRepository;
private readonly IDictionary<int, ILanguage> _languagesById;
public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor,
AppCaches cache, ILogger<DictionaryByKeyRepository> logger)
: base(scopeAccessor, cache, logger) =>
: base(scopeAccessor, cache, logger)
{
_dictionaryRepository = dictionaryRepository;
_languagesById = dictionaryRepository.GetLanguagesById();
}
protected override IEnumerable<DictionaryDto> PerformFetch(Sql sql) =>
Database
@@ -217,7 +234,7 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
"cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " = @id";
protected override IDictionaryItem ConvertToEntity(DictionaryDto dto) =>
_dictionaryRepository.ConvertFromDto(dto);
_dictionaryRepository.ConvertFromDto(dto, _languagesById);
protected override object GetBaseWhereClauseArguments(string? id) => new { id };
@@ -245,9 +262,11 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
sql.WhereIn<DictionaryDto>(x => x.PrimaryKey, ids);
}
IDictionary<int, ILanguage> languageIsoCodeById = GetLanguagesById();
return Database
.FetchOneToMany<DictionaryDto>(x => x.LanguageTextDtos, sql)
.Select(ConvertFromDto);
.Select(dto => ConvertFromDto(dto, languageIsoCodeById));
}
protected override IEnumerable<IDictionaryItem> PerformGetByQuery(IQuery<IDictionaryItem> query)
@@ -257,9 +276,11 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
Sql<ISqlContext> sql = translator.Translate();
sql.OrderBy<DictionaryDto>(x => x.UniqueId);
IDictionary<int, ILanguage> languageIsoCodeById = GetLanguagesById();
return Database
.FetchOneToMany<DictionaryDto>(x => x.LanguageTextDtos, sql)
.Select(ConvertFromDto);
.Select(dto => ConvertFromDto(dto, languageIsoCodeById));
}
#endregion
@@ -309,9 +330,11 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
var id = Convert.ToInt32(Database.Insert(dto));
dictionaryItem.Id = id;
IDictionary<string, ILanguage> languagesByIsoCode = GetLanguagesByIsoCode();
foreach (IDictionaryTranslation translation in dictionaryItem.Translations)
{
LanguageTextDto textDto = DictionaryTranslationFactory.BuildDto(translation, dictionaryItem.Key);
LanguageTextDto textDto = DictionaryTranslationFactory.BuildDto(translation, dictionaryItem.Key, languagesByIsoCode);
translation.Id = Convert.ToInt32(Database.Insert(textDto));
translation.Key = dictionaryItem.Key;
}
@@ -332,9 +355,11 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
Database.Update(dto);
IDictionary<string, ILanguage> languagesByIsoCode = GetLanguagesByIsoCode();
foreach (IDictionaryTranslation translation in entity.Translations)
{
LanguageTextDto textDto = DictionaryTranslationFactory.BuildDto(translation, entity.Key);
LanguageTextDto textDto = DictionaryTranslationFactory.BuildDto(translation, entity.Key, languagesByIsoCode);
if (translation.HasIdentity)
{
Database.Update(textDto);
@@ -384,5 +409,13 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
}
}
private IDictionary<int, ILanguage> GetLanguagesById() => _languageRepository
.GetMany()
.ToDictionary(language => language.Id);
private IDictionary<string, ILanguage> GetLanguagesByIsoCode() => _languageRepository
.GetMany()
.ToDictionary(language => language.IsoCode);
#endregion
}