mirror of https://github.com/Squidex/squidex.git
65 changed files with 1100 additions and 411 deletions
@ -0,0 +1,44 @@ |
|||
// ==========================================================================
|
|||
// LanguageConfig.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
using System.Collections.Immutable; |
|||
|
|||
namespace Squidex.Core |
|||
{ |
|||
public sealed class LanguageConfig |
|||
{ |
|||
public bool IsOptional { get; } |
|||
|
|||
public Language Language { get; } |
|||
|
|||
public ImmutableList<Language> Fallback { get; } |
|||
|
|||
public LanguageConfig(Language language, bool isOptional, params Language[] fallback) |
|||
: this(language, isOptional, fallback?.ToImmutableList()) |
|||
{ |
|||
} |
|||
|
|||
public LanguageConfig(Language language, bool isOptional, IEnumerable<Language> fallback) |
|||
: this(language, isOptional, fallback?.ToImmutableList()) |
|||
{ |
|||
} |
|||
|
|||
public LanguageConfig(Language language, bool isOptional = false, ImmutableList<Language> fallback = null) |
|||
{ |
|||
Guard.NotNull(language, nameof(language)); |
|||
|
|||
Language = language; |
|||
|
|||
IsOptional = isOptional; |
|||
|
|||
Fallback = fallback ?? ImmutableList<Language>.Empty; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,184 @@ |
|||
// ==========================================================================
|
|||
// LanguagesConfig.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using Squidex.Infrastructure; |
|||
|
|||
// ReSharper disable InvertIf
|
|||
|
|||
namespace Squidex.Core |
|||
{ |
|||
public sealed class LanguagesConfig : IEnumerable<LanguageConfig> |
|||
{ |
|||
private readonly ImmutableDictionary<Language, LanguageConfig> languages; |
|||
private readonly LanguageConfig master; |
|||
|
|||
public static readonly LanguagesConfig Empty = Create(); |
|||
public static readonly LanguagesConfig Invariant = Create(Language.Invariant); |
|||
|
|||
public LanguageConfig Master |
|||
{ |
|||
get { return master; } |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return languages.Values.GetEnumerator(); |
|||
} |
|||
|
|||
IEnumerator<LanguageConfig> IEnumerable<LanguageConfig>.GetEnumerator() |
|||
{ |
|||
return languages.Values.GetEnumerator(); |
|||
} |
|||
|
|||
private LanguagesConfig(ImmutableDictionary<Language, LanguageConfig> languages, LanguageConfig master) |
|||
{ |
|||
this.languages = ValidateLanguages(languages); |
|||
|
|||
this.master = master; |
|||
} |
|||
|
|||
public static LanguagesConfig Create(ICollection<LanguageConfig> languageConfigs) |
|||
{ |
|||
Guard.NotNull(languageConfigs, nameof(languageConfigs)); |
|||
|
|||
var validated = ValidateLanguages(languageConfigs.ToImmutableDictionary(c => c.Language)); |
|||
|
|||
return new LanguagesConfig(validated, languageConfigs.FirstOrDefault()); |
|||
} |
|||
|
|||
public static LanguagesConfig Create(params Language[] languages) |
|||
{ |
|||
Guard.NotNull(languages, nameof(languages)); |
|||
|
|||
var languageConfigs = languages.Select(l => new LanguageConfig(l)).ToList(); |
|||
|
|||
return Create(languageConfigs); |
|||
} |
|||
|
|||
public LanguagesConfig MakeMaster(Language language) |
|||
{ |
|||
ThrowIfNotFound(language); |
|||
|
|||
return new LanguagesConfig(languages, languages[language]); |
|||
} |
|||
|
|||
public LanguagesConfig Add(Language language) |
|||
{ |
|||
ThrowIfFound(language, () => $"Cannot add language '{language.Iso2Code}'."); |
|||
|
|||
var newLanguages = languages.Add(language, new LanguageConfig(language)); |
|||
|
|||
return new LanguagesConfig(newLanguages, master ?? newLanguages.Values.First()); |
|||
} |
|||
|
|||
public LanguagesConfig Update(Language language, bool isOptional, IEnumerable<Language> fallback) |
|||
{ |
|||
ThrowIfNotFound(language); |
|||
|
|||
if (isOptional) |
|||
{ |
|||
ThrowIfMaster(language, () => $"Cannot cannot make language '{language.Iso2Code}' optional"); |
|||
} |
|||
|
|||
var newLanguages = ValidateLanguages(languages.SetItem(language, new LanguageConfig(language, isOptional, fallback))); |
|||
|
|||
return new LanguagesConfig(newLanguages, master); |
|||
} |
|||
|
|||
public LanguagesConfig Remove(Language language) |
|||
{ |
|||
ThrowIfNotFound(language); |
|||
ThrowIfMaster(language, () => $"Cannot remove language '{language.Iso2Code}'"); |
|||
|
|||
var newLanguages = languages.Remove(language); |
|||
|
|||
foreach (var languageConfig in newLanguages.Values) |
|||
{ |
|||
if (languageConfig.Fallback.Contains(language)) |
|||
{ |
|||
newLanguages = |
|||
newLanguages.SetItem(languageConfig.Language, |
|||
new LanguageConfig( |
|||
languageConfig.Language, |
|||
languageConfig.IsOptional, |
|||
languageConfig.Fallback.Remove(language))); |
|||
} |
|||
} |
|||
|
|||
return new LanguagesConfig(newLanguages, master); |
|||
} |
|||
|
|||
public bool TryGetConfig(Language language, out LanguageConfig value) |
|||
{ |
|||
return languages.TryGetValue(language, out value); |
|||
} |
|||
|
|||
public bool Contains(Language language) |
|||
{ |
|||
return language != null && languages.ContainsKey(language); |
|||
} |
|||
|
|||
private static ImmutableDictionary<Language, LanguageConfig> ValidateLanguages(ImmutableDictionary<Language, LanguageConfig> languages) |
|||
{ |
|||
var errors = new List<ValidationError>(); |
|||
|
|||
foreach (var languageConfig in languages.Values) |
|||
{ |
|||
foreach (var fallback in languageConfig.Fallback) |
|||
{ |
|||
if (!languages.ContainsKey(fallback)) |
|||
{ |
|||
var message = $"Config for language '{languageConfig.Language.Iso2Code}' contains unsupported fallback language '{fallback.Iso2Code}'"; |
|||
|
|||
errors.Add(new ValidationError(message)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (errors.Count > 0) |
|||
{ |
|||
throw new ValidationException("Cannot configure language", errors); |
|||
} |
|||
|
|||
return languages; |
|||
} |
|||
|
|||
private void ThrowIfNotFound(Language language) |
|||
{ |
|||
if (!Contains(language)) |
|||
{ |
|||
throw new DomainObjectNotFoundException(language, "Languages", typeof(LanguagesConfig)); |
|||
} |
|||
} |
|||
|
|||
private void ThrowIfFound(Language language, Func<string> message) |
|||
{ |
|||
if (Contains(language)) |
|||
{ |
|||
var error = new ValidationError("Language is already part of the app", "Language"); |
|||
|
|||
throw new ValidationException(message(), error); |
|||
} |
|||
} |
|||
|
|||
private void ThrowIfMaster(Language language, Func<string> message) |
|||
{ |
|||
if (master?.Language == language) |
|||
{ |
|||
var error = new ValidationError("Language is the master language", "Language"); |
|||
|
|||
throw new ValidationException(message(), error); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// ==========================================================================
|
|||
// AppLanguageUpdated.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Events.Apps |
|||
{ |
|||
[TypeName("AppLanguageUpdated")] |
|||
public sealed class AppLanguageUpdated : AppEvent |
|||
{ |
|||
public Language Language { get; set; } |
|||
|
|||
public bool IsOptional { get; set; } |
|||
|
|||
public List<Language> Fallback { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// ==========================================================================
|
|||
// MongoAppLanguage.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
|
|||
namespace Squidex.Read.MongoDb.Apps |
|||
{ |
|||
public sealed class MongoAppLanguage |
|||
{ |
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public string Iso2Code { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public bool IsOptional { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public List<string> Fallback { get; set; } |
|||
} |
|||
} |
|||
@ -1,73 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppLanguages.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
|
|||
// ReSharper disable InvertIf
|
|||
|
|||
namespace Squidex.Write.Apps |
|||
{ |
|||
public class AppLanguages |
|||
{ |
|||
private readonly HashSet<Language> languages = new HashSet<Language>(); |
|||
private Language masterLanguage; |
|||
|
|||
public void Add(Language language) |
|||
{ |
|||
ThrowIfFound(language, () => "Cannot add language"); |
|||
|
|||
languages.Add(language); |
|||
} |
|||
|
|||
public void Remove(Language language) |
|||
{ |
|||
ThrowIfNotFound(language); |
|||
ThrowIfMasterLanguage(language, () => "Cannot remove language"); |
|||
|
|||
languages.Remove(language); |
|||
} |
|||
|
|||
public void SetMasterLanguage(Language language) |
|||
{ |
|||
ThrowIfNotFound(language); |
|||
ThrowIfMasterLanguage(language, () => "Cannot set master language"); |
|||
|
|||
masterLanguage = language; |
|||
} |
|||
|
|||
private void ThrowIfNotFound(Language language) |
|||
{ |
|||
if (!languages.Contains(language)) |
|||
{ |
|||
throw new DomainObjectNotFoundException(language.Iso2Code, "Languages", typeof(AppDomainObject)); |
|||
} |
|||
} |
|||
|
|||
private void ThrowIfFound(Language language, Func<string> message) |
|||
{ |
|||
if (languages.Contains(language)) |
|||
{ |
|||
var error = new ValidationError("Language is already part of the app", "Language"); |
|||
|
|||
throw new ValidationException(message(), error); |
|||
} |
|||
} |
|||
|
|||
private void ThrowIfMasterLanguage(Language language, Func<string> message) |
|||
{ |
|||
if (masterLanguage != null && masterLanguage.Equals(language)) |
|||
{ |
|||
var error = new ValidationError("Language is the master language", "Language"); |
|||
|
|||
throw new ValidationException(message(), error); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// ==========================================================================
|
|||
// UpdateLanguage.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Write.Apps.Commands |
|||
{ |
|||
public sealed class UpdateLanguage : AppAggregateCommand, IValidatable |
|||
{ |
|||
public Language Language { get; set; } |
|||
|
|||
public bool IsOptional { get; set; } |
|||
|
|||
public List<Language> Fallback { get; set; } |
|||
|
|||
public void Validate(IList<ValidationError> errors) |
|||
{ |
|||
if (Language == null) |
|||
{ |
|||
errors.Add(new ValidationError("Language cannot be null", nameof(Language))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,213 @@ |
|||
// ==========================================================================
|
|||
// LanguagesConfigTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using FluentAssertions; |
|||
using Squidex.Infrastructure; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Core |
|||
{ |
|||
public class LanguagesConfigTests |
|||
{ |
|||
[Fact] |
|||
public void Should_create_initial_config() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE); |
|||
|
|||
config.ToList().ShouldBeEquivalentTo( |
|||
new List<LanguageConfig> |
|||
{ |
|||
new LanguageConfig(Language.DE) |
|||
}); |
|||
|
|||
Assert.Equal(Language.DE, config.Master.Language); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_create_initial_config_with_multiple_languages() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE, Language.EN, Language.IT); |
|||
|
|||
config.ToList().ShouldBeEquivalentTo( |
|||
new List<LanguageConfig> |
|||
{ |
|||
new LanguageConfig(Language.DE), |
|||
new LanguageConfig(Language.EN), |
|||
new LanguageConfig(Language.IT) |
|||
}); |
|||
|
|||
Assert.Equal(Language.DE, config.Master.Language); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_create_initial_config_with_configs() |
|||
{ |
|||
var configs = new[] |
|||
{ |
|||
new LanguageConfig(Language.DE), |
|||
new LanguageConfig(Language.EN), |
|||
new LanguageConfig(Language.IT), |
|||
}; |
|||
var config = LanguagesConfig.Create(configs); |
|||
|
|||
config.ToList().ShouldBeEquivalentTo(configs); |
|||
|
|||
Assert.Equal(configs[0], config.Master); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_add_language() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE).Add(Language.IT); |
|||
|
|||
config.ToList().ShouldBeEquivalentTo( |
|||
new List<LanguageConfig> |
|||
{ |
|||
new LanguageConfig(Language.DE), |
|||
new LanguageConfig(Language.IT) |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_first_language_to_master() |
|||
{ |
|||
var config = LanguagesConfig.Empty.Add(Language.IT); |
|||
|
|||
Assert.Equal(Language.IT, config.Master.Language); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_if_language_to_add_already_exists() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE); |
|||
|
|||
Assert.Throws<ValidationException>(() => config.Add(Language.DE)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_make_master_language() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE).Add(Language.IT).MakeMaster(Language.IT); |
|||
|
|||
Assert.Equal(Language.IT, config.Master.Language); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_if_language_to_make_master_is_not_found() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE); |
|||
|
|||
Assert.Throws<DomainObjectNotFoundException>(() => config.MakeMaster(Language.EN)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_not_throw_exception_if_language_is_already_master_language() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE); |
|||
|
|||
config.MakeMaster(Language.DE); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_remove_language() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE).Add(Language.IT).Add(Language.RU).Remove(Language.IT); |
|||
|
|||
config.ToList().ShouldBeEquivalentTo( |
|||
new List<LanguageConfig> |
|||
{ |
|||
new LanguageConfig(Language.DE), |
|||
new LanguageConfig(Language.RU) |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_remove_fallbacks_when_removing_language() |
|||
{ |
|||
var config = |
|||
LanguagesConfig.Create(Language.DE) |
|||
.Add(Language.IT) |
|||
.Add(Language.RU) |
|||
.Update(Language.DE, false, new[] { Language.RU, Language.IT }) |
|||
.Update(Language.RU, false, new[] { Language.DE, Language.IT }) |
|||
.Remove(Language.IT); |
|||
|
|||
config.ToList().ShouldBeEquivalentTo( |
|||
new List<LanguageConfig> |
|||
{ |
|||
new LanguageConfig(Language.DE, false, Language.RU), |
|||
new LanguageConfig(Language.RU, false, Language.DE) |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_if_language_to_remove_is_not_found() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE); |
|||
|
|||
Assert.Throws<DomainObjectNotFoundException>(() => config.Remove(Language.EN)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_if_language_to_remove_is_master_language() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE); |
|||
|
|||
Assert.Throws<ValidationException>(() => config.Remove(Language.DE)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_update_language() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE).Add(Language.IT).Update(Language.IT, true, new[] { Language.DE }); |
|||
|
|||
config.ToList().ShouldBeEquivalentTo( |
|||
new List<LanguageConfig> |
|||
{ |
|||
new LanguageConfig(Language.DE), |
|||
new LanguageConfig(Language.IT, true, Language.DE) |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_if_language_to_update_is_not_found() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE); |
|||
|
|||
Assert.Throws<DomainObjectNotFoundException>(() => config.Update(Language.EN, true, null)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_if_fallback_language_is_invalid() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE); |
|||
|
|||
Assert.Throws<ValidationException>(() => config.Update(Language.DE, true, new [] { Language.EN })); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_throw_exception_if_language_to_make_optional_is_master_language() |
|||
{ |
|||
var config = LanguagesConfig.Create(Language.DE); |
|||
|
|||
Assert.Throws<ValidationException>(() => config.Update(Language.DE, true, null)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_provide_enumerators() |
|||
{ |
|||
var config = LanguagesConfig.Create(); |
|||
|
|||
Assert.NotNull(((IEnumerable)config).GetEnumerator()); |
|||
Assert.NotNull(((IEnumerable<LanguageConfig>)config).GetEnumerator()); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue