diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs index 238ab6f4d..915edb8e8 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs @@ -30,13 +30,9 @@ namespace Squidex.Domain.Apps.Core.Comments Guard.NotNull(text, nameof(text)); Id = id; - Text = text; - Time = time; - User = user; - Url = url; } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs index 4917faf80..4a1a94ac4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -20,6 +20,7 @@ using Squidex.Domain.Apps.Core.Scripting.ContentWrapper; using Squidex.Domain.Apps.Core.Scripting.Internal; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Core.Scripting @@ -179,15 +180,15 @@ namespace Squidex.Domain.Apps.Core.Scripting } catch (ArgumentException ex) { - throw new ValidationException($"Failed to execute script with javascript syntax error: {ex.Message}", new ValidationError(ex.Message)); + throw new ValidationException(T.Get("common.jsParseError", new { error = ex.Message })); } catch (JavaScriptException ex) { - throw new ValidationException($"Failed to execute script with javascript error: {ex.Message}", new ValidationError(ex.Message)); + throw new ValidationException(T.Get("common.jsError", new { error = ex.Message })); } catch (ParserException ex) { - throw new ValidationException($"Failed to execute script with javascript error: {ex.Message}", new ValidationError(ex.Message)); + throw new ValidationException(T.Get("common.jsError", new { error = ex.Message })); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs index de3d6643b..6f8b684d8 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -7,6 +7,7 @@ using Jint; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Core.Scripting @@ -17,16 +18,16 @@ namespace Squidex.Domain.Apps.Core.Scripting private static readonly MessageDelegate Disallow = new MessageDelegate(message => { - message = !string.IsNullOrWhiteSpace(message) ? message : "Not allowed"; + message = !string.IsNullOrWhiteSpace(message) ? message : T.Get("common.jsNotAlloweed"); throw new DomainForbiddenException(message); }); private static readonly MessageDelegate Reject = new MessageDelegate(message => { - var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null; + message = !string.IsNullOrWhiteSpace(message) ? message : T.Get("common.jsRejected"); - throw new ValidationException("Script rejected the operation.", errors); + throw new ValidationException(message); }); public static Engine AddDisallow(this Engine engine) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/TemplateParseException.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/TemplateParseException.cs index 0fe18d3ad..071624eb0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/TemplateParseException.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/TemplateParseException.cs @@ -18,8 +18,8 @@ namespace Squidex.Domain.Apps.Core.Templates { public IReadOnlyList Errors { get; } - public TemplateParseException(string template, IEnumerable errors) - : base(BuildErrorMessage(errors, template)) + public TemplateParseException(string template, IEnumerable errors, Exception? inner = null) + : base(BuildErrorMessage(errors, template), inner) { Errors = errors.ToList(); } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs index ee50150f4..18cdb4919 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -10,6 +10,7 @@ using System.Collections.Generic; using NodaTime.Text; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Json.Objects; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Core.ValidateContent @@ -55,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent return (b.Value, null); } - return (null, new JsonError("Invalid json type, expected boolean.")); + return (null, new JsonError(T.Get("contents.invalidBoolean"))); } public (object? Result, JsonError? Error) Visit(IField field) @@ -65,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent return (n.Value, null); } - return (null, new JsonError("Invalid json type, expected number.")); + return (null, new JsonError(T.Get("contents.invalidNumber"))); } public (object? Result, JsonError? Error) Visit(IField field) @@ -75,7 +76,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent return (s.Value, null); } - return (null, new JsonError("Invalid json type, expected string.")); + return (null, new JsonError(T.Get("contents.invalidString"))); } public (object? Result, JsonError? Error) Visit(IField field) @@ -97,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent return (parseResult.Value, null); } - return (null, new JsonError("Invalid json type, expected string.")); + return (null, new JsonError(T.Get("contents.invalidString"))); } public (object? Result, JsonError? Error) Visit(IField field) @@ -109,7 +110,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent if (!string.Equals(propertyName, "latitude", StringComparison.OrdinalIgnoreCase) && !string.Equals(propertyName, "longitude", StringComparison.OrdinalIgnoreCase)) { - return (null, new JsonError("Geolocation can only have latitude and longitude property.")); + return (null, new JsonError(T.Get("contents.invalidGeolocationMoreProperties"))); } } @@ -119,12 +120,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent if (!lat.IsBetween(-90, 90)) { - return (null, new JsonError("Latitude must be between -90 and 90.")); + return (null, new JsonError(T.Get("contents.invalidGeolocationLatitude"))); } } else { - return (null, new JsonError("Invalid json type, expected latitude/longitude object.")); + return (null, new JsonError(T.Get("contents.invalidGeolocation"))); } if (geolocation.TryGetValue("longitude", out var lonValue) && lonValue is JsonNumber lonNumber) @@ -133,18 +134,18 @@ namespace Squidex.Domain.Apps.Core.ValidateContent if (!lon.IsBetween(-180, 180)) { - return (null, new JsonError("Longitude must be between -180 and 180.")); + return (null, new JsonError(T.Get("contents.invalidGeolocationLongitude"))); } } else { - return (null, new JsonError("Invalid json type, expected latitude/longitude object.")); + return (null, new JsonError(T.Get("contents.invalidGeolocation"))); } return (value, null); } - return (null, new JsonError("Invalid json type, expected latitude/longitude object.")); + return (null, new JsonError(T.Get("contents.invalidGeolocation"))); } public (object? Result, JsonError? Error) Visit(IField field) @@ -166,14 +167,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent } else { - return (null, new JsonError("Invalid json type, expected array of guid strings.")); + return (null, new JsonError(T.Get("contents.invalidArrayOfIds"))); } } return (result, null); } - return (null, new JsonError("Invalid json type, expected array of guid strings.")); + return (null, new JsonError(T.Get("contents.invalidArrayOfIds"))); } private (object? Result, JsonError? Error) ConvertToStringList() @@ -194,14 +195,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent } else { - return (null, new JsonError("Invalid json type, expected array of strings.")); + return (null, new JsonError(T.Get("contents.invalidArrayOfStrings"))); } } return (result, null); } - return (null, new JsonError("Invalid json type, expected array of strings.")); + return (null, new JsonError(T.Get("contents.invalidArrayOfStrings"))); } private (object? Result, JsonError? Error) ConvertToObjectList() @@ -218,14 +219,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent } else { - return (null, new JsonError("Invalid json type, expected array of objects.")); + return (null, new JsonError(T.Get("contents.invalidArrayOfObjects"))); } } return (result, null); } - return (null, new JsonError("Invalid json type, expected array of objects.")); + return (null, new JsonError(T.Get("contents.invalidArrayOfObjects"))); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs index 6d506df4c..ca15f797c 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs @@ -9,19 +9,20 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { - public sealed class AllowedValuesValidator : IValidator + public sealed class AllowedValuesValidator : IValidator { - private readonly IEnumerable allowedValues; + private readonly IEnumerable allowedValues; - public AllowedValuesValidator(params T[] allowedValues) - : this((IEnumerable)allowedValues) + public AllowedValuesValidator(params TValue[] allowedValues) + : this((IEnumerable)allowedValues) { } - public AllowedValuesValidator(IEnumerable allowedValues) + public AllowedValuesValidator(IEnumerable allowedValues) { Guard.NotNull(allowedValues, nameof(allowedValues)); @@ -30,9 +31,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators public Task ValidateAsync(object? value, ValidationContext context, AddError addError) { - if (value != null && value is T typedValue && !allowedValues.Contains(typedValue)) + if (value != null && value is TValue typedValue && !allowedValues.Contains(typedValue)) { - addError(context.Path, "Not an allowed value."); + addError(context.Path, T.Get("contents.validation.notAllowed")); } return Task.CompletedTask; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs index e3fd8e414..e14187078 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -54,32 +55,32 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (asset == null) { - addError(path, $"Id '{assetId}' not found."); + addError(path, T.Get("contents.validation.assetNotFound", new { id = assetId })); continue; } if (properties.MinSize.HasValue && asset.FileSize < properties.MinSize) { - addError(path, $"'{asset.FileSize.ToReadableSize()}' less than minimum of '{properties.MinSize.Value.ToReadableSize()}'."); + addError(path, T.Get("contents.validation.minimumSize", new { size = asset.FileSize.ToReadableSize(), min = properties.MinSize.Value.ToReadableSize() })); } if (properties.MaxSize.HasValue && asset.FileSize > properties.MaxSize) { - addError(path, $"'{asset.FileSize.ToReadableSize()}' greater than maximum of '{properties.MaxSize.Value.ToReadableSize()}'."); + addError(path, T.Get("contents.validation.maximumSize", new { size = asset.FileSize.ToReadableSize(), max = properties.MaxSize.Value.ToReadableSize() })); } if (properties.AllowedExtensions != null && properties.AllowedExtensions.Count > 0 && !properties.AllowedExtensions.Any(x => asset.FileName.EndsWith("." + x, StringComparison.OrdinalIgnoreCase))) { - addError(path, "Invalid file extension."); + addError(path, T.Get("contents.validation.extension")); } if (asset.Type != AssetType.Image) { if (properties.MustBeImage) { - addError(path, "Not an image."); + addError(path, T.Get("contents.validation.image")); } continue; @@ -97,22 +98,22 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (properties.MinWidth.HasValue && w < properties.MinWidth) { - addError(path, $"Width '{w}px' less than minimum of '{properties.MinWidth}px'."); + addError(path, T.Get("contents.validation.minimumWidth", new { width = w, min = properties.MinWidth })); } if (properties.MaxWidth.HasValue && w > properties.MaxWidth) { - addError(path, $"Width '{w}px' greater than maximum of '{properties.MaxWidth}px'."); + addError(path, T.Get("contents.validation.maximumWidth", new { width = w, max = properties.MaxWidth })); } if (properties.MinHeight.HasValue && h < properties.MinHeight) { - addError(path, $"Height '{h}px' less than minimum of '{properties.MinHeight}px'."); + addError(path, T.Get("contents.validation.minimumHeight", new { height = h, min = properties.MinHeight })); } if (properties.MaxHeight.HasValue && h > properties.MaxHeight) { - addError(path, $"Height '{h}px' greater than maximum of '{properties.MaxHeight}px'."); + addError(path, T.Get("contents.validation.maximumHeight", new { height = h, max = properties.MaxHeight })); } if (properties.AspectHeight.HasValue && properties.AspectWidth.HasValue) @@ -121,7 +122,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (Math.Abs(expectedRatio - actualRatio) > double.Epsilon) { - addError(path, $"Aspect ratio not '{properties.AspectWidth}:{properties.AspectHeight}'."); + addError(path, T.Get("contents.validation.aspectRatio", new { width = properties.AspectWidth, height = properties.AspectHeight })); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs index 3c2d16a7c..6d7719181 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -8,6 +8,7 @@ using System; using System.Collections; using System.Threading.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -35,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (isRequired && !context.IsOptional) { - addError(context.Path, "Field is required."); + addError(context.Path, T.Get("contents.validation.required")); } return Task.CompletedTask; @@ -45,23 +46,23 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (minItems == maxItems && minItems != items.Count) { - addError(context.Path, $"Must have exactly {maxItems} item(s)."); + addError(context.Path, T.Get("contents.validation.itemCount", new { count = minItems })); } else if (items.Count < minItems || items.Count > maxItems) { - addError(context.Path, $"Must have between {minItems} and {maxItems} item(s)."); + addError(context.Path, T.Get("contents.validation.itemCountBetween", new { min = minItems, max = maxItems })); } } else { if (minItems.HasValue && items.Count < minItems.Value) { - addError(context.Path, $"Must have at least {minItems} item(s)."); + addError(context.Path, T.Get("contents.validation.minItems", new { min = minItems })); } if (maxItems.HasValue && items.Count > maxItems.Value) { - addError(context.Path, $"Must not have more than {maxItems} item(s)."); + addError(context.Path, T.Get("contents.validation.maxItems", new { max = maxItems })); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs index fd9129962..892d69394 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -69,7 +70,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } catch { - addError(context.Path, "Not a valid value."); + addError(context.Path, T.Get("contents.validation.invalid")); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs index 6e0907836..99e0feea8 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -6,6 +6,7 @@ // ========================================================================== using System.Threading.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -21,7 +22,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (!value.IsUndefined()) { - addError(context.Path, "Value must not be defined."); + addError(context.Path, T.Get("contents.validation.mustBeEmpty")); } return Task.CompletedTask; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs index 2a432c396..b201d2047 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (!schema.ContainsKey(name)) { - addError(context.Path.Enqueue(name), $"Not a known {fieldType}."); + addError(context.Path.Enqueue(name), T.Get("contents.validation.unknownField", new { fieldType })); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs index a11336c00..6c8279128 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -9,6 +9,7 @@ using System; using System.Text.RegularExpressions; using System.Threading.Tasks; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (string.IsNullOrWhiteSpace(errorMessage)) { - addError(context.Path, "Does not match to the pattern."); + addError(context.Path, T.Get("contents.validation.pattern")); } else { @@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } catch { - addError(context.Path, "Regex is too slow."); + addError(context.Path, T.Get("contents.validation.regexTooSlow")); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs index d733ff856..fdfb79c7a 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -7,15 +7,16 @@ using System; using System.Threading.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { - public sealed class RangeValidator : IValidator where T : struct, IComparable + public sealed class RangeValidator : IValidator where TValue : struct, IComparable { - private readonly T? min; - private readonly T? max; + private readonly TValue? min; + private readonly TValue? max; - public RangeValidator(T? min, T? max) + public RangeValidator(TValue? min, TValue? max) { if (min.HasValue && max.HasValue && min.Value.CompareTo(max.Value) > 0) { @@ -28,29 +29,29 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators public Task ValidateAsync(object? value, ValidationContext context, AddError addError) { - if (value != null && value is T typedValue) + if (value != null && value is TValue typedValue) { if (min.HasValue && max.HasValue) { if (Equals(min, max) && Equals(min.Value, max.Value)) { - addError(context.Path, $"Must be exactly '{max}'."); + addError(context.Path, T.Get("contents.validation.exactValue", new { value = max.Value })); } else if (typedValue.CompareTo(min.Value) < 0 || typedValue.CompareTo(max.Value) > 0) { - addError(context.Path, $"Must be between '{min}' and '{max}'."); + addError(context.Path, T.Get("contents.validation.between", new { min, max })); } } else { if (min.HasValue && typedValue.CompareTo(min.Value) < 0) { - addError(context.Path, $"Must be greater or equal to '{min}'."); + addError(context.Path, T.Get("contents.validation.min", new { min })); } if (max.HasValue && typedValue.CompareTo(max.Value) > 0) { - addError(context.Path, $"Must be less or equal to '{max}'."); + addError(context.Path, T.Get("contents.validation.max", new { max })); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs index 8551d0f56..f606f6bd6 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -46,11 +47,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (schemaId == Guid.Empty) { - addError(context.Path, $"Contains invalid reference '{id}'."); + addError(context.Path, T.Get("common.referenceNotFound", new { id })); } else if (schemaIds?.Any() == true && !schemaIds.Contains(schemaId)) { - addError(context.Path, $"Contains reference '{id}' to invalid schema."); + addError(context.Path, T.Get("common.referenceToInvalidSchema", new { id })); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs index d169d0258..c3efd156d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -6,6 +6,7 @@ // ========================================================================== using System.Threading.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -27,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (value.IsNullOrUndefined() || IsEmptyString(value)) { - addError(context.Path, "Field is required."); + addError(context.Path, T.Get("contents.validation.required")); } return Task.CompletedTask; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs index 8e9440cf7..dcda4f4d0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -6,6 +6,7 @@ // ========================================================================== using System.Threading.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -15,7 +16,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (value.IsNullOrUndefined() && !context.IsOptional) { - addError(context.Path, "Field is required."); + addError(context.Path, T.Get("contents.validation.required")); } return Task.CompletedTask; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs index ad33e9de0..553c39376 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -7,6 +7,7 @@ using System; using System.Threading.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -34,23 +35,23 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (minLength == maxLength && minLength != stringValue.Length) { - addError(context.Path, $"Must have exactly {maxLength} character(s)."); + addError(context.Path, T.Get("contents.validation.characterCount", new { count = minLength })); } else if (stringValue.Length < minLength || stringValue.Length > maxLength) { - addError(context.Path, $"Must have between {minLength} and {maxLength} character(s)."); + addError(context.Path, T.Get("contents.validation.charactersBetween", new { min = minLength, max = maxLength })); } } else { if (minLength.HasValue && stringValue.Length < minLength.Value) { - addError(context.Path, $"Must have at least {minLength} character(s)."); + addError(context.Path, T.Get("contents.validation.minLength", new { min = minLength })); } if (maxLength.HasValue && stringValue.Length > maxLength.Value) { - addError(context.Path, $"Must not have more than {maxLength} character(s)."); + addError(context.Path, T.Get("contents.validation.maxLength", new { max = maxLength })); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs index e16194f6e..4e4770803 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Squidex.Infrastructure.Queries; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -52,7 +53,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators if (found.Any(x => x.Id != context.ContentId)) { - addError(context.Path, "Another content with the same value exists."); + addError(context.Path, T.Get("contents.validation.unique")); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs index 266be2748..d4857ab7f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -8,20 +8,21 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { - public sealed class UniqueValuesValidator : IValidator + public sealed class UniqueValuesValidator : IValidator { public Task ValidateAsync(object? value, ValidationContext context, AddError addError) { - if (value is IEnumerable items && items.Any()) + if (value is IEnumerable items && items.Any()) { var itemsArray = items.ToArray(); if (itemsArray.Length != itemsArray.Distinct().Count()) { - addError(context.Path, "Must not contain duplicate values."); + addError(context.Path, T.Get("contents.validation.duplicates")); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs index 5833e3337..0eddf64c9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -21,6 +21,7 @@ using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { @@ -91,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets } catch (MongoQueryException ex) when (ex.Message.Contains("17406")) { - throw new DomainException("Result set is too large to be retrieved. Use $take parameter to reduce the number of items."); + throw new DomainException(T.Get("common.resultTooLarge")); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs index c44e26ee5..f6508ba0f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -21,6 +21,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations { @@ -99,11 +100,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations } catch (MongoCommandException ex) when (ex.Code == 96) { - throw new DomainException("Result set is too large to be retrieved. Use $take parameter to reduce the number of items."); + throw new DomainException(T.Get("common.resultTooLarge")); } catch (MongoQueryException ex) when (ex.Message.Contains("17406")) { - throw new DomainException("Result set is too large to be retrieved. Use $take parameter to reduce the number of items."); + throw new DomainException(T.Get("common.resultTooLarge")); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs index 5d59c836a..866673c18 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Threading; +using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using Squidex.Domain.Apps.Core.Contents; @@ -14,14 +14,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { public sealed class StatusSerializer : SerializerBase { - private static volatile int isRegistered; - public static void Register() { - if (Interlocked.Increment(ref isRegistered) == 1) + try { BsonSerializer.RegisterSerializer(new StatusSerializer()); } + catch (BsonSerializationException) + { + return; + } } public override Status Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs index 1a0f8c866..332c4657b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Apps @@ -64,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.Apps if (image == null) { - throw new ValidationException("File is not an image."); + throw new ValidationException(T.Get("apps.notImage")); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs index f26b10311..98e933472 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -21,6 +21,7 @@ using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; +using Squidex.Infrastructure.Translations; using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Apps @@ -462,7 +463,7 @@ namespace Squidex.Domain.Apps.Entities.Apps { if (Snapshot.IsArchived) { - throw new DomainException("App has already been archived."); + throw new DomainException(T.Get("apps.alreadyArchieved")); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs index 39eaf5c36..8eed269f1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Events.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Apps { @@ -21,55 +22,55 @@ namespace Squidex.Domain.Apps.Entities.Apps : base(typeNameRegistry) { AddEventMessage( - "assigned {user:[Contributor]} as {[Role]}"); + T.Get("history.apps.contributoreAssigned")); AddEventMessage( - "removed {user:[Contributor]} from app"); + T.Get("history.apps.contributoreRemoved")); AddEventMessage( - "added client {[Id]} to app"); + T.Get("history.apps.clientAdded")); AddEventMessage( - "revoked client {[Id]}"); + T.Get("history.apps.clientRevoked")); AddEventMessage( - "updated client {[Id]}"); + T.Get("history.apps.clientUpdated")); AddEventMessage( - "changed plan to {[Plan]}"); + T.Get("history.apps.planChanged")); AddEventMessage( - "resetted plan"); + T.Get("history.apps.planReset")); AddEventMessage( - "added language {[Language]}"); + T.Get("history.apps.languagedAdded")); AddEventMessage( - "removed language {[Language]}"); + T.Get("history.apps.languagedRemoved")); AddEventMessage( - "updated language {[Language]}"); + T.Get("history.apps.languagedUpdated")); AddEventMessage( - "changed master language to {[Language]}"); + T.Get("history.apps.languagedSetToMaster")); AddEventMessage( - "added pattern {[Name]}"); + T.Get("history.apps.patternAdded")); AddEventMessage( - "deleted pattern {[PatternId]}"); + T.Get("history.apps.patternDeleted")); AddEventMessage( - "updated pattern {[Name]}"); + T.Get("history.apps.patternUpdated")); AddEventMessage( - "added role {[Name]}"); + T.Get("history.apps.roleAdded")); AddEventMessage( - "deleted role {[Name]}"); + T.Get("history.apps.roleDeleted")); AddEventMessage( - "updated role {[Name]}"); + T.Get("history.apps.roleUpdated")); } private HistoryEvent? CreateEvent(IEvent @event) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs index 08ae96613..fa2bcdfd5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -9,6 +9,7 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Plans; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Apps.Guards @@ -19,11 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot create app.", e => + Validate.It(e => { if (!command.Name.IsSlug()) { - e(Not.ValidSlug("Name"), nameof(command.Name)); + e(Not.ValidSlug(nameof(command.Name)), nameof(command.Name)); } }); } @@ -32,11 +33,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot upload image.", e => + Validate.It(e => { if (command.File == null) { - e(Not.Defined("File"), nameof(command.File)); + e(Not.Defined(nameof(command.File)), nameof(command.File)); } }); } @@ -55,22 +56,22 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot change plan.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.PlanId)) { - e(Not.Defined("Plan id"), nameof(command.PlanId)); + e(Not.Defined(nameof(command.PlanId)), nameof(command.PlanId)); return; } if (appPlans.GetPlan(command.PlanId) == null) { - e("A plan with this id does not exist.", nameof(command.PlanId)); + e(T.Get("apps.plans.notFound"), nameof(command.PlanId)); } if (!string.IsNullOrWhiteSpace(command.PlanId) && plan != null && !plan.Owner.Equals(command.Actor)) { - e("Plan can only changed from the user who configured the plan initially."); + e(T.Get("apps.plans.notPlanOwner")); } }); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs index 1bebbfac7..d34cd27a2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -8,6 +8,7 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Apps.Guards @@ -18,15 +19,15 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot attach client.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Id)) { - e(Not.Defined("Client id"), nameof(command.Id)); + e(Not.Defined("ClientId"), nameof(command.Id)); } else if (clients.ContainsKey(command.Id)) { - e("A client with the same id already exists."); + e(T.Get("apps.clients.idAlreadyExists")); } }); } @@ -37,11 +38,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards GetClientOrThrow(clients, command.Id); - Validate.It(() => "Cannot revoke client.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Id)) { - e(Not.Defined("Client id"), nameof(command.Id)); + e(Not.Defined("ClientId"), nameof(command.Id)); } }); } @@ -52,16 +53,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards GetClientOrThrow(clients, command.Id); - Validate.It(() => "Cannot update client.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Id)) { - e(Not.Defined("Client id"), nameof(command.Id)); + e(Not.Defined("Clientd"), nameof(command.Id)); } if (command.Role != null && !roles.Contains(command.Role)) { - e(Not.Valid("role"), nameof(command.Role)); + e(Not.Valid("Role"), nameof(command.Role)); } }); } @@ -75,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (!clients.TryGetValue(id, out var client)) { - throw new DomainObjectNotFoundException(id, "Clients", typeof(IAppEntity)); + throw new DomainObjectNotFoundException(id); } return client; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs index f6cb10e23..8fb63148c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Plans; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; using Squidex.Shared.Users; @@ -23,16 +24,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - return Validate.It(() => "Cannot assign contributor.", async e => + return Validate.It(async e => { if (!roles.Contains(command.Role)) { - e(Not.Valid("role"), nameof(command.Role)); + e(Not.Valid(nameof(command.Role)), nameof(command.Role)); } if (string.IsNullOrWhiteSpace(command.ContributorId)) { - e(Not.Defined("Contributor id"), nameof(command.ContributorId)); + e(Not.Defined(nameof(command.ContributorId)), nameof(command.ContributorId)); } else { @@ -40,21 +41,21 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (user == null) { - throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(IAppEntity)); + throw new DomainObjectNotFoundException(command.ContributorId); } if (!command.Restoring) { if (string.Equals(command.ContributorId, command.Actor?.Identifier, StringComparison.OrdinalIgnoreCase)) { - throw new DomainForbiddenException("You cannot change your own role."); + throw new DomainForbiddenException(T.Get("apps.contributors.cannotChangeYourself")); } if (!contributors.TryGetValue(command.ContributorId, out var role)) { if (plan != null && plan.MaxContributors > 0 && contributors.Count >= plan.MaxContributors) { - e("You have reached the maximum number of contributors for your plan."); + e(T.Get("apps.contributors.maxReached")); } } } @@ -66,24 +67,24 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot remove contributor.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.ContributorId)) { - e(Not.Defined("Contributor id"), nameof(command.ContributorId)); + e(Not.Defined(nameof(command.ContributorId)), nameof(command.ContributorId)); } var ownerIds = contributors.Where(x => x.Value == Role.Owner).Select(x => x.Key).ToList(); if (ownerIds.Count == 1 && ownerIds.Contains(command.ContributorId)) { - e("Cannot remove the only owner."); + e(T.Get("apps.contributors.onlyOneOwner")); } }); if (!contributors.ContainsKey(command.ContributorId)) { - throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(IAppEntity)); + throw new DomainObjectNotFoundException(command.ContributorId); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs index 66ff0fe99..90f5ea3a8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -8,6 +8,7 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Apps.Guards @@ -18,17 +19,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot add language.", e => + Validate.It(e => { var language = command.Language; if (language == null) { - e(Not.Defined("Language code"), nameof(command.Language)); + e(Not.Defined(nameof(command.Language)), nameof(command.Language)); } else if (languages.Contains(language)) { - e("Language has already been added."); + e(T.Get("apps.languages.languageAlreadyAdded")); } }); } @@ -37,13 +38,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot remove language.", e => + Validate.It(e => { var language = command.Language; if (language == null) { - e(Not.Defined("Language code"), nameof(command.Language)); + e(Not.Defined(nameof(command.Language)), nameof(command.Language)); } else { @@ -51,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (languages.IsMaster(language)) { - e("Master language cannot be removed."); + e(T.Get("apps.languages.masterLanguageNotRemovable")); } } }); @@ -61,13 +62,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot update language.", e => + Validate.It(e => { var language = command.Language; if (language == null) { - e(Not.Defined("Language code"), nameof(command.Language)); + e(Not.Defined(nameof(command.Language)), nameof(command.Language)); } else { @@ -77,12 +78,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (command.IsOptional) { - e("Master language cannot be made optional.", nameof(command.IsMaster)); + e(T.Get("apps.languages.masterLanguageNotOptional"), nameof(command.IsMaster)); } if (command.Fallback?.Count > 0) { - e("Master language cannot have fallback languages.", nameof(command.Fallback)); + e(T.Get("apps.languages.masterLanguageNoFallbacks"), nameof(command.Fallback)); } } @@ -95,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (!languages.Contains(fallback)) { - e($"App does not have fallback language '{fallback}'.", nameof(command.Fallback)); + e(T.Get("apps.languages.fallbackNotFound", new { fallback }), nameof(command.Fallback)); } } } @@ -106,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (!languages.Contains(language)) { - throw new DomainObjectNotFoundException(language, "Languages", typeof(IAppEntity)); + throw new DomainObjectNotFoundException(language); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs index d5c0437d9..7d63347a9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -9,6 +9,7 @@ using System.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Apps.Guards @@ -19,35 +20,35 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot add pattern.", e => + Validate.It(e => { if (command.PatternId == Guid.Empty) { - e(Not.Defined("Id"), nameof(command.PatternId)); + e(Not.Defined(nameof(command.PatternId)), nameof(command.PatternId)); } if (string.IsNullOrWhiteSpace(command.Name)) { - e(Not.Defined("Name"), nameof(command.Name)); + e(Not.Defined(nameof(command.Name)), nameof(command.Name)); } if (patterns.Values.Any(x => x.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) { - e("A pattern with the same name already exists."); + e(T.Get("apps.patterns.nameAlreadyExists")); } if (string.IsNullOrWhiteSpace(command.Pattern)) { - e(Not.Defined("Pattern"), nameof(command.Pattern)); + e(Not.Defined(nameof(command.Pattern)), nameof(command.Pattern)); } else if (!command.Pattern.IsValidRegex()) { - e(Not.Valid("Pattern"), nameof(command.Pattern)); + e(Not.Valid(nameof(command.Pattern)), nameof(command.Pattern)); } if (patterns.Values.Any(x => x.Pattern == command.Pattern)) { - e("This pattern already exists but with another name."); + e(T.Get("apps.patterns.patternAlreadyExists")); } }); } @@ -58,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (!patterns.ContainsKey(command.PatternId)) { - throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern)); + throw new DomainObjectNotFoundException(command.PatternId.ToString()); } } @@ -68,33 +69,33 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (!patterns.ContainsKey(command.PatternId)) { - throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern)); + throw new DomainObjectNotFoundException(command.PatternId.ToString()); } - Validate.It(() => "Cannot update pattern.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Name)) { - e(Not.Defined("Name"), nameof(command.Name)); + e(Not.Defined(nameof(command.Name)), nameof(command.Name)); } if (patterns.Any(x => x.Key != command.PatternId && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) { - e("A pattern with the same name already exists."); + e(T.Get("apps.patterns.nameAlreadyExists")); } if (string.IsNullOrWhiteSpace(command.Pattern)) { - e(Not.Defined("Pattern"), nameof(command.Pattern)); + e(Not.Defined(nameof(command.Pattern)), nameof(command.Pattern)); } else if (!command.Pattern.IsValidRegex()) { - e(Not.Valid("Pattern"), nameof(command.Pattern)); + e(Not.Valid(nameof(command.Pattern)), nameof(command.Pattern)); } if (patterns.Any(x => x.Key != command.PatternId && x.Value.Pattern == command.Pattern)) { - e("This pattern already exists but with another name."); + e(T.Get("apps.patterns.patternAlreadyExists")); } }); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs index bd75bc92e..69c203cfd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -10,6 +10,7 @@ using System.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Apps.Guards @@ -20,15 +21,15 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot add role.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Name)) { - e(Not.Defined("Name"), nameof(command.Name)); + e(Not.Defined(nameof(command.Name)), nameof(command.Name)); } else if (roles.Contains(command.Name)) { - e("A role with the same name already exists."); + e(T.Get("apps.roles.nameAlreadyExists")); } }); } @@ -39,25 +40,25 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards CheckRoleExists(roles, command.Name); - Validate.It(() => "Cannot delete role.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Name)) { - e(Not.Defined("Name"), nameof(command.Name)); + e(Not.Defined(nameof(command.Name)), nameof(command.Name)); } else if (Roles.IsDefault(command.Name)) { - e("Cannot delete a default role."); + e(T.Get("apps.roles.defaultRoleNotRemovable")); } if (clients.Values.Any(x => string.Equals(x.Role, command.Name, StringComparison.OrdinalIgnoreCase))) { - e("Cannot remove a role when a client is assigned."); + e(T.Get("apps.roles.usedRoleByClientsNotRemovable")); } if (contributors.Values.Any(x => string.Equals(x, command.Name, StringComparison.OrdinalIgnoreCase))) { - e("Cannot remove a role when a contributor is assigned."); + e(T.Get("apps.roles.usedRoleByContributorsNotRemovable")); } }); } @@ -68,20 +69,20 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards CheckRoleExists(roles, command.Name); - Validate.It(() => "Cannot delete role.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Name)) { - e(Not.Defined("Name"), nameof(command.Name)); + e(Not.Defined(nameof(command.Name)), nameof(command.Name)); } else if (Roles.IsDefault(command.Name)) { - e("Cannot update a default role."); + e(T.Get("apps.roles.defaultRoleNotUpdateable")); } if (command.Permissions == null) { - e(Not.Defined("Permissions"), nameof(command.Permissions)); + e(Not.Defined(nameof(command.Permissions)), nameof(command.Permissions)); } }); } @@ -95,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (!roles.ContainsCustom(name)) { - throw new DomainObjectNotFoundException(name, "Roles", typeof(IAppEntity)); + throw new DomainObjectNotFoundException(name); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs index bbff8ca5d..48b4553b0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -9,6 +9,7 @@ using System; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Apps.Guards @@ -19,11 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot add workflow.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Name)) { - e(Not.Defined("Name"), nameof(command.Name)); + e(Not.Defined(nameof(command.Name)), nameof(command.Name)); } }); } @@ -34,11 +35,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards CheckWorkflowExists(workflows, command.WorkflowId); - Validate.It(() => "Cannot update workflow.", e => + Validate.It(e => { if (command.Workflow == null) { - e(Not.Defined("Workflow"), nameof(command.Workflow)); + e(Not.Defined(nameof(command.Workflow)), nameof(command.Workflow)); return; } @@ -46,19 +47,19 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (!workflow.Steps.ContainsKey(workflow.Initial)) { - e(Not.Defined("Initial step"), $"{nameof(command.Workflow)}.{nameof(workflow.Initial)}"); + e(Not.Defined("InitialStep"), $"{nameof(command.Workflow)}.{nameof(workflow.Initial)}"); } if (workflow.Initial == Status.Published) { - e("Initial step cannot be published step.", $"{nameof(command.Workflow)}.{nameof(workflow.Initial)}"); + e(T.Get("workflows.initialNotPublished"), $"{nameof(command.Workflow)}.{nameof(workflow.Initial)}"); } var stepsPrefix = $"{nameof(command.Workflow)}.{nameof(workflow.Steps)}"; if (!workflow.Steps.ContainsKey(Status.Published)) { - e("Workflow must have a published step.", stepsPrefix); + e(T.Get("apps.workflows.initialNotPublished"), stepsPrefix); } foreach (var step in workflow.Steps) @@ -67,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (step.Value == null) { - e(Not.Defined("Step"), stepPrefix); + e(Not.Defined("WorkflowStep"), stepPrefix); } else { @@ -77,12 +78,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (!workflow.Steps.ContainsKey(status)) { - e("Transition has an invalid target.", transitionPrefix); + e(T.Get("apps.workflows.publishedStepNotFound"), transitionPrefix); } if (transition == null) { - e(Not.Defined("Transition"), transitionPrefix); + e(Not.Defined("WorkflowTransition"), transitionPrefix); } } } @@ -101,7 +102,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (!workflows.ContainsKey(id)) { - throw new DomainObjectNotFoundException(id.ToString(), "Workflows", typeof(IAppEntity)); + throw new DomainObjectNotFoundException(id.ToString()); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs index 6df748be1..456220b92 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -17,6 +17,7 @@ using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Security; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; using Squidex.Shared; @@ -237,7 +238,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes await RemoveContributorAsync(removeContributor); break; - case ArchiveApp archiveApp: + case ArchiveApp _: await ArchiveAppAsync(app); break; } @@ -256,9 +257,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes if (token == null) { - var error = new ValidationError("An app with this already exists."); - - throw new ValidationException("Cannot create app.", error); + throw new ValidationException(T.Get("apps.nameAlreadyExists")); } return token; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs index 6b3ec885f..6bcbe0628 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase); } - private static Task CreateAuthenticationSchemeSchemaAsync(Func publish) + private static async Task> CreateAuthenticationSchemeSchemaAsync(Func publish) { var schema = SchemaBuilder.Create("Authentication Schemes") @@ -71,7 +71,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates .Hints("Additional scopes you want from the provider.")) .Build(); - return publish(schema); + await publish(schema); + + return NamedId.Of(schema.SchemaId, schema.Name); } private static Task CreateClientsSchemaAsync(Func publish) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs index 146c59a7e..504b8efe9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -20,6 +20,7 @@ using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Assets { @@ -172,7 +173,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { if (Snapshot.IsDeleted) { - throw new DomainException("Asset has already been deleted"); + throw new DomainException(T.Get("assets.assetAlreadyDeleted")); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs index 30ca258ed..33aeaff18 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -18,6 +18,7 @@ using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Assets { @@ -112,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { if (Snapshot.IsDeleted) { - throw new DomainException("Asset folder has already been deleted"); + throw new DomainException(T.Get("assets.assetFolderAlreadyDeleted")); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetHistoryEventsCreator.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetHistoryEventsCreator.cs index 7fb827a3d..1c46183cd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetHistoryEventsCreator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetHistoryEventsCreator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -10,6 +10,7 @@ using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Events.Assets; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Assets { @@ -19,13 +20,13 @@ namespace Squidex.Domain.Apps.Entities.Assets : base(typeNameRegistry) { AddEventMessage( - "uploaded asset."); + T.Get("history.assets.uploaded")); AddEventMessage( - "replaced asset."); + T.Get("history.assets.replaced")); AddEventMessage( - "updated asset."); + T.Get("history.assets.updated")); } protected override Task CreateEventCoreAsync(Envelope @event) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs index ad157b704..183372389 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -9,6 +9,7 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Assets.Guards @@ -18,30 +19,13 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards public static void CanAnnotate(AnnotateAsset command) { Guard.NotNull(command, nameof(command)); - - Validate.It(() => "Cannot annotate asset.", e => - { - if (string.IsNullOrWhiteSpace(command.FileName) && - string.IsNullOrWhiteSpace(command.Slug) && - command.IsProtected == null && - command.Metadata == null && - command.Tags == null) - { - e("At least one property must be defined.", - nameof(command.FileName), - nameof(command.IsProtected), - nameof(command.Metadata), - nameof(command.Slug), - nameof(command.Tags)); - } - }); } public static Task CanCreate(CreateAsset command, IAssetQueryService assetQuery) { Guard.NotNull(command, nameof(command)); - return Validate.It(() => "Cannot upload asset.", async e => + return Validate.It(async e => { await CheckPathAsync(command.ParentId, assetQuery, e); }); @@ -51,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards { Guard.NotNull(command, nameof(command)); - return Validate.It(() => "Cannot move asset.", async e => + return Validate.It(async e => { if (command.ParentId != oldParentId) { @@ -78,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards if (path.Count == 0) { - e("Asset folder does not exist.", nameof(MoveAsset.ParentId)); + e(T.Get("assets.folderNotFound"), nameof(MoveAsset.ParentId)); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs index b615d065b..df4b818f0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -9,6 +9,7 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Assets.Guards @@ -19,11 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards { Guard.NotNull(command, nameof(command)); - return Validate.It(() => "Cannot upload asset.", async e => + return Validate.It(async e => { if (string.IsNullOrWhiteSpace(command.FolderName)) { - e(Not.Defined("Folder name"), nameof(command.FolderName)); + e(Not.Defined(nameof(command.FolderName)), nameof(command.FolderName)); } await CheckPathAsync(command.ParentId, assetQuery, Guid.Empty, e); @@ -34,11 +35,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot rename asset.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.FolderName)) { - e(Not.Defined("Folder name"), nameof(command.FolderName)); + e(Not.Defined(nameof(command.FolderName)), nameof(command.FolderName)); } }); } @@ -47,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards { Guard.NotNull(command, nameof(command)); - return Validate.It(() => "Cannot move asset.", async e => + return Validate.It(async e => { if (command.ParentId != oldParentId) { @@ -69,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards if (path.Count == 0) { - e("Asset folder does not exist.", nameof(MoveAssetFolder.ParentId)); + e(T.Get("assets.folderNotFound"), nameof(MoveAssetFolder.ParentId)); } else if (id != default) { @@ -78,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards if (indexOfThis >= 0 && indexOfParent > indexOfThis) { - e("Cannot add folder to its own child.", nameof(MoveAssetFolder.ParentId)); + e(T.Get("assets.folderRecursion"), nameof(MoveAssetFolder.ParentId)); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs index fdd1d867f..87c763bd3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using SixLabors.ImageSharp; using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs index cfdaf9cec..97e90c08f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs @@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries if (asset == null || asset.Version <= EtagVersion.Empty || (version > EtagVersion.Any && asset.Version != version)) { - throw new DomainObjectNotFoundException(id.ToString(), typeof(IAssetEntity)); + throw new DomainObjectNotFoundException(id.ToString()); } return asset; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs index a7aa7658d..33fa44763 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -19,6 +19,7 @@ using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries.Json; using Squidex.Infrastructure.Queries.OData; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Assets.Queries @@ -106,11 +107,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries } catch (NotSupportedException) { - throw new ValidationException("OData operation is not supported."); + throw new ValidationException(T.Get("common.odataNotSupported")); } catch (ODataException ex) { - throw new ValidationException($"Failed to parse query: {ex.Message}", ex); + throw new ValidationException(T.Get("common.odataFailure", new { message = ex.Message }), ex); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs index 60434cddf..1d2fa4b7f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -20,6 +20,7 @@ using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Tasks; +using Squidex.Infrastructure.Translations; using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Backup @@ -92,12 +93,12 @@ namespace Squidex.Domain.Apps.Entities.Backup { if (currentJobToken != null) { - throw new DomainException("Another backup process is already running."); + throw new DomainException(T.Get("backups.alreadyRunning")); } if (state.Value.Jobs.Count >= MaxBackups) { - throw new DomainException($"You cannot have more than {MaxBackups} backups."); + throw new DomainException(T.Get("backups.maxReached", new { max = MaxBackups })); } var job = new BackupJob @@ -231,7 +232,7 @@ namespace Squidex.Domain.Apps.Entities.Backup if (job == null) { - throw new DomainObjectNotFoundException(id.ToString(), typeof(IBackupJob)); + throw new DomainObjectNotFoundException(id.ToString()); } if (currentJob == job) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupRestoreException.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupRestoreException.cs index f7fec5453..d6c116a60 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupRestoreException.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupRestoreException.cs @@ -13,12 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Backup [Serializable] public class BackupRestoreException : Exception { - public BackupRestoreException(string message) - : base(message) - { - } - - public BackupRestoreException(string message, Exception inner) + public BackupRestoreException(string message, Exception? inner = null) : base(message, inner) { } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs index 297e051e1..a9cf736a5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -23,6 +23,7 @@ using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.States; using Squidex.Infrastructure.Tasks; +using Squidex.Infrastructure.Translations; using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Backup @@ -112,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Backup if (CurrentJob?.Status == JobStatus.Started) { - throw new DomainException("A restore operation is already running."); + throw new DomainException(T.Get("backups.restoreRunning")); } state.Value.Job = new RestoreJob diff --git a/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs index c06f79f56..c7e0b9217 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs @@ -112,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Comments if (command.ExpectedVersion > EtagVersion.Any && command.ExpectedVersion != Version) { - throw new DomainObjectVersionException(Key, GetType(), Version, command.ExpectedVersion); + throw new DomainObjectVersionException(Key, Version, command.ExpectedVersion); } var prevVersion = version; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs b/backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs index ddde50c91..e268de016 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Entities.Comments.Commands; using Squidex.Domain.Apps.Events.Comments; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Comments.Guards @@ -21,11 +22,11 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot create comment.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Text)) { - e(Not.Defined("Text"), nameof(command.Text)); + e(Not.Defined("Text"), nameof(command.Text)); } }); } @@ -38,14 +39,14 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards if (!string.Equals(commentsId, command.Actor.Identifier) && !comment.Payload.Actor.Equals(command.Actor)) { - throw new DomainException("Comment is created by another user."); + throw new DomainException(T.Get("comments.notUserComment")); } - Validate.It(() => "Cannot update comment.", e => + Validate.It(e => { if (string.IsNullOrWhiteSpace(command.Text)) { - e(Not.Defined("Text"), nameof(command.Text)); + e(Not.Defined("Text"), nameof(command.Text)); } }); } @@ -58,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards if (!string.Equals(commentsId, command.Actor.Identifier) && !comment.Payload.Actor.Equals(command.Actor)) { - throw new DomainException("Comment is created by another user."); + throw new DomainException(T.Get("comments.notUserComment")); } } @@ -80,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards if (result == null) { - throw new DomainObjectNotFoundException(commentId.ToString(), "Comments", typeof(CommentsGrain)); + throw new DomainObjectNotFoundException(commentId.ToString()); } return result; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs index 11d5b0a3c..eaabaecb8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Contents { @@ -89,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { if (id == null || id == default) { - throw new DomainObjectNotFoundException("NOT DEFINED", typeof(IContentEntity)); + throw new DomainObjectNotFoundException("undefined"); } var command = SimpleMapper.Map(bulkUpdates, new ChangeContentStatus { ContentId = id.Value }); @@ -107,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { if (id == null || id == default) { - throw new DomainObjectNotFoundException("NOT DEFINED", typeof(IContentEntity)); + throw new DomainObjectNotFoundException("undefined"); } var command = SimpleMapper.Map(bulkUpdates, new DeleteContent { ContentId = id.Value }); @@ -162,7 +163,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (existing.Total > 1) { - throw new DomainException("More than one content matches to the query."); + throw new DomainException(T.Get("contents.bulkInsertQueryNotUnique")); } id = existing.FirstOrDefault()?.Id; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs index 27683b69f..a720b1bc7 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -21,6 +21,7 @@ using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Contents { @@ -48,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents case CreateContent createContent: return CreateReturnAsync(createContent, async c => { - await LoadContext(c.AppId, c.SchemaId, c, () => "Failed to create content.", c.OptimizeValidation); + await LoadContext(c.AppId, c.SchemaId, c, c.OptimizeValidation); await GuardContent.CanCreate(context.Schema, contentWorkflow, c); @@ -98,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Contents case CreateContentDraft createContentDraft: return UpdateReturnAsync(createContentDraft, async c => { - await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c, () => "Failed to create draft."); + await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c); GuardContent.CanCreateDraft(c, Snapshot); @@ -112,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Contents case DeleteContentDraft deleteContentDraft: return UpdateReturnAsync(deleteContentDraft, async c => { - await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c, () => "Failed to delete draft."); + await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c); GuardContent.CanDeleteDraft(c, Snapshot); @@ -142,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { try { - await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c, () => "Failed to change content."); + await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c); await GuardContent.CanChangeStatus(context.Schema, Snapshot, contentWorkflow, c); @@ -187,7 +188,7 @@ namespace Squidex.Domain.Apps.Entities.Contents case DeleteContent deleteContent: return UpdateAsync(deleteContent, async c => { - await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c, () => "Failed to delete content."); + await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c); GuardContent.CanDelete(context.Schema, c); @@ -219,7 +220,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (!currentData!.Equals(newData)) { - await LoadContext(Snapshot.AppId, Snapshot.SchemaId, command, () => "Failed to update content.", command.OptimizeValidation); + await LoadContext(Snapshot.AppId, Snapshot.SchemaId, command, command.OptimizeValidation); if (!command.DoNotValidate) { @@ -337,13 +338,13 @@ namespace Squidex.Domain.Apps.Entities.Contents { if (Snapshot.IsDeleted) { - throw new DomainException("Content has already been deleted."); + throw new DomainException(T.Get("contents.alreadyDeleted")); } } - private Task LoadContext(NamedId appId, NamedId schemaId, ContentCommand command, Func message, bool optimized = false) + private Task LoadContext(NamedId appId, NamedId schemaId, ContentCommand command, bool optimized = false) { - return context.LoadAsync(appId, schemaId, command, message, optimized); + return context.LoadAsync(appId, schemaId, command, optimized); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs index 4dc41f3a5..fc16538ad 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Contents { @@ -20,28 +21,28 @@ namespace Squidex.Domain.Apps.Entities.Contents : base(typeNameRegistry) { AddEventMessage( - "created {[Schema]} content."); + T.Get("history.contents.created")); AddEventMessage( - "updated {[Schema]} content."); + T.Get("history.contents.updated")); AddEventMessage( - "deleted {[Schema]} content."); + T.Get("history.contents.deleted")); AddEventMessage( - "created new draft."); + T.Get("history.contents.draftCreated")); AddEventMessage( - "deleted draft."); + T.Get("history.contents.draftDeleted")); AddEventMessage( - "failed to schedule status change for {[Schema]} content."); + T.Get("history.contents.scheduleFailed")); AddEventMessage( - "changed status of {[Schema]} content to {[Status]}."); + T.Get("history.statusChanged")); AddEventMessage( - "scheduled to change status of {[Schemra]} content to {[Status]}."); + T.Get("history.contents.scheduleCompleted")); } protected override Task CreateEventCoreAsync(Envelope @event) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs index c2a07ae70..e5c07d056 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs @@ -40,7 +40,6 @@ namespace Squidex.Domain.Apps.Entities.Contents private IAppEntity app; private ContentCommand command; private ValidationContext validationContext; - private Func message; public ContentOperationContext(IAppProvider appProvider, IEnumerable factories, IScriptEngine scriptEngine) { @@ -54,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Contents get { return schema; } } - public async Task LoadAsync(NamedId appId, NamedId schemaId, ContentCommand command, Func message, bool optimized) + public async Task LoadAsync(NamedId appId, NamedId schemaId, ContentCommand command, bool optimized) { var (app, schema) = await appProvider.GetAppWithSchemaAsync(appId.Id, schemaId.Id); @@ -71,7 +70,6 @@ namespace Squidex.Domain.Apps.Entities.Contents this.app = app; this.schema = schema; this.command = command; - this.message = message; validationContext = new ValidationContext(appId, schemaId, schema.SchemaDef, command.ContentId).Optimized(optimized); } @@ -114,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { if (validator.Errors.Count > 0) { - throw new ValidationException(message(), validator.Errors.ToList()); + throw new ValidationException(validator.Errors.ToList()); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs index fc4553e6c..e65cdaadf 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -11,6 +11,7 @@ using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Contents { @@ -33,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (workflows.Values.Count(x => x.SchemaIds.Count == 0) > 1) { - errors.Add("Multiple workflows cover all schemas."); + errors.Add(T.Get("workflows.overlap")); } var uniqueSchemaIds = workflows.Values.SelectMany(x => x.SchemaIds).Distinct().ToList(); @@ -46,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (schema != null) { - errors.Add($"The schema `{schema.SchemaDef.Name}` is covered by multiple workflows."); + errors.Add(T.Get("workflows.schemaOverlap", new { schema = schema.SchemaDef.Name })); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs index 9388e7165..1fb0ed895 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Contents.Guards @@ -25,15 +26,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards if (schema.SchemaDef.IsSingleton && command.ContentId != schema.Id) { - throw new DomainException("Singleton content cannot be created."); + throw new DomainException(T.Get("contents.singletonNotCreatable")); } if (command.Publish && !await contentWorkflow.CanPublishOnCreateAsync(schema, command.Data, command.User)) { - throw new DomainException("Content workflow prevents publishing."); + throw new DomainException(T.Get("contents.workflowErorPublishing")); } - Validate.It(() => "Cannot created content.", e => + Validate.It(e => { ValidateData(command, e); }); @@ -43,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot update content.", e => + Validate.It(e => { ValidateData(command, e); }); @@ -55,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot patch content.", e => + Validate.It(e => { ValidateData(command, e); }); @@ -69,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards if (content.NewStatus == null) { - throw new DomainException("There is nothing to delete."); + throw new DomainException(T.Get("contents.draftToDeleteNotFound")); } } @@ -79,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards if (content.Status != Status.Published) { - throw new DomainException("You can only create a new version when the content is published."); + throw new DomainException(T.Get("contents.draftNotCreateForUnpublished")); } } @@ -91,22 +92,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { if (content.NewVersion == null || command.Status != Status.Published) { - throw new DomainException("Singleton content cannot be updated."); + throw new DomainException(T.Get("contents.singletonNotChangeable")); } return Task.CompletedTask; } - return Validate.It(() => "Cannot change status.", async e => + return Validate.It(async e => { if (!await contentWorkflow.CanMoveToAsync(content, content.EditingStatus, command.Status, command.User)) { - e($"Cannot change status from {content.Status} to {command.Status}.", nameof(command.Status)); + e(T.Get("contents.statusTransitionNotAllowed", new { oldStatus = content.EditingStatus, newStatus = command.Status }), nameof(command.Status)); } if (command.DueTime.HasValue && command.DueTime.Value < SystemClock.Instance.GetCurrentInstant()) { - e("Due time must be in the future.", nameof(command.DueTime)); + e(T.Get("contents.statusSchedulingNotInFuture"), nameof(command.DueTime)); } }); } @@ -117,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards if (schema.SchemaDef.IsSingleton) { - throw new DomainException("Singleton content cannot be deleted."); + throw new DomainException(T.Get("contents.singletonNotDeletable")); } } @@ -125,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { if (command.Data == null) { - e(Not.Defined("Data"), nameof(command.Data)); + e(Not.Defined(nameof(command.Data)), nameof(command.Data)); } } @@ -133,7 +134,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { if (!await contentWorkflow.CanUpdateAsync(content, content.EditingStatus, user)) { - throw new DomainException($"The workflow does not allow updates at status {content.Status}"); + throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status = content.EditingStatus })); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs index c29116b5d..0cfb5f4d2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs @@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries if (content == null || content.Version <= EtagVersion.Empty || (version > EtagVersion.Any && content.Version != version)) { - throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity)); + throw new DomainObjectNotFoundException(id.ToString()); } return content; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs index 16f5b69bc..90d58b3b5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs @@ -27,6 +27,7 @@ using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries.Json; using Squidex.Infrastructure.Queries.OData; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Contents.Queries @@ -119,11 +120,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries } catch (NotSupportedException) { - throw new ValidationException("OData operation is not supported."); + throw new ValidationException(T.Get("common.odataNotSupported")); } catch (ODataException ex) { - throw new ValidationException($"Failed to parse query: {ex.Message}", ex); + throw new ValidationException(T.Get("common.odataFailure", new { message = ex.Message }), ex); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs index 0fe9fa259..9836c1ea5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Queries; +using Squidex.Infrastructure.Translations; using Squidex.Shared; #pragma warning disable RECS0147 @@ -73,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries if (content == null || content.SchemaId.Id != schema.Id) { - throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity)); + throw new DomainObjectNotFoundException(id.ToString()); } return await TransformAsync(context, content); @@ -170,7 +171,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries if (schema == null) { - throw new DomainObjectNotFoundException(schemaIdOrName, typeof(ISchemaEntity)); + throw new DomainObjectNotFoundException(schemaIdOrName); } return schema; @@ -182,7 +183,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries { if (!HasPermission(context, schema)) { - throw new DomainForbiddenException("You do not have permission for this schema."); + throw new DomainForbiddenException(T.Get("schemas.noPermission")); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs index d81b3cca7..d2ed3f649 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -16,6 +16,7 @@ using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Json.Objects; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps { @@ -128,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps private static JsonObject CreateFallback(Context context, List referencedContents) { - var text = $"{referencedContents.Count} Reference(s)"; + var text = T.Get("contents.listReferences", new { count = referencedContents.Count }); var value = JsonValue.Object(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs index 66a7ae204..e987c05ec 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs @@ -19,28 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { Guard.NotNull(command, nameof(command)); - return Validate.It(() => "Cannot create rule.", async e => + return Validate.It(async e => { if (command.Trigger == null) { - e(Not.Defined("Trigger"), nameof(command.Trigger)); + e(Not.Defined(nameof(command.Trigger)), nameof(command.Trigger)); } else { var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Id, command.Trigger, appProvider); - errors.Foreach(x => x.AddTo(e)); + errors.Foreach((x, _) => x.AddTo(e)); } if (command.Action == null) { - e(Not.Defined("Action"), nameof(command.Action)); + e(Not.Defined(nameof(command.Action)), nameof(command.Action)); } else { var errors = command.Action.Validate(); - errors.Foreach(x => x.AddTo(e)); + errors.Foreach((x, _) => x.AddTo(e)); } }); } @@ -49,25 +49,20 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { Guard.NotNull(command, nameof(command)); - return Validate.It(() => "Cannot update rule.", async e => + return Validate.It(async e => { - if (command.Trigger == null && command.Action == null && command.Name == null) - { - e(Not.Defined("Either trigger, action or name"), nameof(command.Trigger), nameof(command.Action)); - } - if (command.Trigger != null) { var errors = await RuleTriggerValidator.ValidateAsync(appId, command.Trigger, appProvider); - errors.Foreach(x => x.AddTo(e)); + errors.Foreach((x, _) => x.AddTo(e)); } if (command.Action != null) { var errors = command.Action.Validate(); - errors.Foreach(x => x.AddTo(e)); + errors.Foreach((x, _) => x.AddTo(e)); } }); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs index d0a1f4290..df153c9a6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Rules.Guards @@ -62,7 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards if (trigger.NumDays.HasValue && (trigger.NumDays < 1 || trigger.NumDays > 30)) { - errors.Add(new ValidationError(Not.Between("Num days", 1, 30), nameof(trigger.NumDays))); + errors.Add(new ValidationError(Not.Between(nameof(trigger.NumDays), 1, 30), nameof(trigger.NumDays))); } return Task.FromResult>(errors); @@ -80,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { if (schema.SchemaId == Guid.Empty) { - errors.Add(new ValidationError(Not.Defined("Schema id"), nameof(trigger.Schemas))); + errors.Add(new ValidationError(Not.Defined("SchemaId"), nameof(trigger.Schemas))); } else { @@ -100,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { if (await SchemaProvider(schema.SchemaId) == null) { - return new ValidationError($"Schema {schema.SchemaId} does not exist.", nameof(ContentChangedTriggerV2.Schemas)); + return new ValidationError(T.Get("schemas.notFoundId", new { id = schema.SchemaId }), nameof(ContentChangedTriggerV2.Schemas)); } return null; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs index 3f14da2dd..b5310a537 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -18,6 +18,7 @@ using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Rules { @@ -143,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Rules { if (Snapshot.IsDeleted) { - throw new DomainException("Rule has already been deleted."); + throw new DomainException(T.Get("rules.alreadyDeleted")); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs index 105f02b87..45c139699 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -20,6 +20,7 @@ using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; using Squidex.Infrastructure.Tasks; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Rules.Runner { @@ -109,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner { if (currentJobToken != null) { - throw new DomainException("Another rule is already running."); + throw new DomainException(T.Get("rules.ruleAlreadyRunning")); } state.Value = new State diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs index bbc7279af..1e13eb3e3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -7,6 +7,7 @@ using System.Collections.Generic; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Schemas.Guards @@ -34,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) { - yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), + yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxItems), nameof(properties.MinItems)), nameof(properties.MinItems), nameof(properties.MaxItems)); } @@ -44,35 +45,35 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) { - yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), + yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxItems), nameof(properties.MinItems)), nameof(properties.MinItems), nameof(properties.MaxItems)); } if (properties.MaxHeight.HasValue && properties.MinHeight.HasValue && properties.MinHeight.Value > properties.MaxHeight.Value) { - yield return new ValidationError(Not.GreaterEquals("Max height", "min height"), + yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxHeight), nameof(properties.MinHeight)), nameof(properties.MaxHeight), nameof(properties.MinHeight)); } if (properties.MaxWidth.HasValue && properties.MinWidth.HasValue && properties.MinWidth.Value > properties.MaxWidth.Value) { - yield return new ValidationError(Not.GreaterEquals("Max width", "min width"), + yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxWidth), nameof(properties.MinWidth)), nameof(properties.MaxWidth), nameof(properties.MinWidth)); } if (properties.MaxSize.HasValue && properties.MinSize.HasValue && properties.MinSize.Value >= properties.MaxSize.Value) { - yield return new ValidationError(Not.GreaterThan("Max size", "min size"), + yield return new ValidationError(Not.GreaterThan(nameof(properties.MaxSize), nameof(properties.MinSize)), nameof(properties.MaxSize), nameof(properties.MinSize)); } if (properties.AspectWidth.HasValue != properties.AspectHeight.HasValue) { - yield return new ValidationError(Not.Defined2("Aspect width", "aspect height"), + yield return new ValidationError(Not.BothDefined(nameof(properties.AspectWidth), nameof(properties.AspectHeight)), nameof(properties.AspectWidth), nameof(properties.AspectHeight)); } @@ -82,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError(Not.Valid("Editor"), + yield return new ValidationError(Not.Valid(nameof(properties.Editor)), nameof(properties.Editor)); } } @@ -91,25 +92,25 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError(Not.Valid("Editor"), + yield return new ValidationError(Not.Valid(nameof(properties.Editor)), nameof(properties.Editor)); } if (properties.DefaultValue.HasValue && properties.MinValue.HasValue && properties.DefaultValue.Value < properties.MinValue.Value) { - yield return new ValidationError(Not.GreaterEquals("Default value", "min value"), + yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.DefaultValue), nameof(properties.MinValue)), nameof(properties.DefaultValue)); } if (properties.DefaultValue.HasValue && properties.MaxValue.HasValue && properties.DefaultValue.Value > properties.MaxValue.Value) { - yield return new ValidationError(Not.LessEquals("Default value", "max value"), + yield return new ValidationError(Not.LessEqualsThan(nameof(properties.DefaultValue), nameof(properties.MaxValue)), nameof(properties.DefaultValue)); } if (properties.MaxValue.HasValue && properties.MinValue.HasValue && properties.MinValue.Value >= properties.MaxValue.Value) { - yield return new ValidationError(Not.GreaterThan("Max value", "min value"), + yield return new ValidationError(Not.GreaterThan(nameof(properties.MaxValue), nameof(properties.MinValue)), nameof(properties.MinValue), nameof(properties.MaxValue)); } @@ -118,13 +119,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.CalculatedDefaultValue.Value.IsEnumValue()) { - yield return new ValidationError(Not.Valid("Calculated default value"), + yield return new ValidationError(Not.Valid(nameof(properties.CalculatedDefaultValue)), nameof(properties.CalculatedDefaultValue)); } if (properties.DefaultValue.HasValue) { - yield return new ValidationError("Calculated default value and default value cannot be used together.", + yield return new ValidationError(T.Get("schemas.dateTimeCalculatedDefaultAndDefaultError"), nameof(properties.CalculatedDefaultValue), nameof(properties.DefaultValue)); } @@ -135,7 +136,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError(Not.Valid("Editor"), + yield return new ValidationError(Not.Valid(nameof(properties.Editor)), nameof(properties.Editor)); } } @@ -149,38 +150,38 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError(Not.Valid("Editor"), + yield return new ValidationError(Not.Valid(nameof(properties.Editor)), nameof(properties.Editor)); } if ((properties.Editor == NumberFieldEditor.Radio || properties.Editor == NumberFieldEditor.Dropdown) && (properties.AllowedValues == null || properties.AllowedValues.Count == 0)) { - yield return new ValidationError("Radio buttons or dropdown list need allowed values.", + yield return new ValidationError(T.Get("schemas.stringEditorsNeedAllowedValuesError"), nameof(properties.AllowedValues)); } if (properties.DefaultValue.HasValue && properties.MinValue.HasValue && properties.DefaultValue.Value < properties.MinValue.Value) { - yield return new ValidationError(Not.GreaterEquals("Default value", "min value"), + yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.DefaultValue), nameof(properties.MinValue)), nameof(properties.DefaultValue)); } if (properties.DefaultValue.HasValue && properties.MaxValue.HasValue && properties.DefaultValue.Value > properties.MaxValue.Value) { - yield return new ValidationError(Not.LessEquals("Default value", "max value"), + yield return new ValidationError(Not.LessEqualsThan(nameof(properties.DefaultValue), nameof(properties.MaxValue)), nameof(properties.DefaultValue)); } if (properties.MaxValue.HasValue && properties.MinValue.HasValue && properties.MinValue.Value >= properties.MaxValue.Value) { - yield return new ValidationError(Not.GreaterThan("Max value", "min value"), + yield return new ValidationError(Not.GreaterThan(nameof(properties.MaxValue), nameof(properties.MinValue)), nameof(properties.MinValue), nameof(properties.MaxValue)); } if (properties.AllowedValues != null && properties.AllowedValues.Count > 0 && (properties.MinValue.HasValue || properties.MaxValue.HasValue)) { - yield return new ValidationError("Either allowed values or min and max value can be defined.", + yield return new ValidationError(T.Get("schemas.string.eitherMinMaxOrAllowedValuesError"), nameof(properties.AllowedValues), nameof(properties.MinValue), nameof(properties.MaxValue)); @@ -188,7 +189,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (properties.InlineEditable && properties.Editor == NumberFieldEditor.Radio) { - yield return new ValidationError("Inline editing is not allowed for Radio editor.", + yield return new ValidationError(T.Get("schemas.number.inlineEditorError"), nameof(properties.InlineEditable), nameof(properties.Editor)); } @@ -198,20 +199,20 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError(Not.Valid("Editor"), + yield return new ValidationError(Not.Valid(nameof(properties.Editor)), nameof(properties.Editor)); } if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) { - yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), + yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxItems), nameof(properties.MinItems)), nameof(properties.MinItems), nameof(properties.MaxItems)); } if (properties.ResolveReference && properties.MaxItems != 1) { - yield return new ValidationError("Can only resolve references when MaxItems is 1.", + yield return new ValidationError(T.Get("schemas.references.resolveError"), nameof(properties.ResolveReference), nameof(properties.MaxItems)); } @@ -221,32 +222,32 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError(Not.Valid("Editor"), + yield return new ValidationError(Not.Valid(nameof(properties.Editor)), nameof(properties.Editor)); } if ((properties.Editor == StringFieldEditor.Radio || properties.Editor == StringFieldEditor.Dropdown) && (properties.AllowedValues == null || properties.AllowedValues.Count == 0)) { - yield return new ValidationError("Radio buttons or dropdown list need allowed values.", + yield return new ValidationError(T.Get("schemas.stringEditorsNeedAllowedValuesError"), nameof(properties.AllowedValues)); } if (properties.Pattern != null && !properties.Pattern.IsValidRegex()) { - yield return new ValidationError(Not.Valid("Pattern"), + yield return new ValidationError(Not.Valid(nameof(properties.Pattern)), nameof(properties.Pattern)); } if (properties.MaxLength.HasValue && properties.MinLength.HasValue && properties.MinLength.Value > properties.MaxLength.Value) { - yield return new ValidationError(Not.GreaterEquals("Max length", "min length"), + yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxLength), nameof(properties.MinLength)), nameof(properties.MinLength), nameof(properties.MaxLength)); } if (properties.AllowedValues != null && properties.AllowedValues.Count > 0 && (properties.MinLength.HasValue || properties.MaxLength.HasValue)) { - yield return new ValidationError("Either allowed values or min and max length can be defined.", + yield return new ValidationError(T.Get("schemas.number.eitherMinMaxOrAllowedValuesError"), nameof(properties.AllowedValues), nameof(properties.MinLength), nameof(properties.MaxLength)); @@ -254,7 +255,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (properties.InlineEditable && properties.Editor != StringFieldEditor.Dropdown && properties.Editor != StringFieldEditor.Input && properties.Editor != StringFieldEditor.Slug) { - yield return new ValidationError("Inline editing is only allowed for dropdowns, slugs and input fields.", + yield return new ValidationError(T.Get("schemas.string.inlineEditorError"), nameof(properties.InlineEditable), nameof(properties.Editor)); } @@ -264,19 +265,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError(Not.Valid("Editor"), + yield return new ValidationError(Not.Valid(nameof(properties.Editor)), nameof(properties.Editor)); } if ((properties.Editor == TagsFieldEditor.Checkboxes || properties.Editor == TagsFieldEditor.Dropdown) && (properties.AllowedValues == null || properties.AllowedValues.Count == 0)) { - yield return new ValidationError("Checkboxes or dropdown list need allowed values.", + yield return new ValidationError(T.Get("schemas.tags.editorNeedsAllowedValues"), nameof(properties.AllowedValues)); } if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) { - yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), + yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxItems), nameof(properties.MinItems)), nameof(properties.MinItems), nameof(properties.MaxItems)); } @@ -286,7 +287,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError(Not.Valid("Editor"), + yield return new ValidationError(Not.Valid(nameof(properties.Editor)), nameof(properties.Editor)); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs index 760a6086e..6da17d86d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -7,6 +7,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Schemas.Guards { @@ -16,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!schema.FieldsById.TryGetValue(parentId, out var rootField) || !(rootField is IArrayField arrayField)) { - throw new DomainObjectNotFoundException(parentId.ToString(), "Fields", typeof(Schema)); + throw new DomainObjectNotFoundException(parentId.ToString()); } if (!allowLocked) @@ -35,7 +36,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (!arrayField.FieldsById.TryGetValue(fieldId, out var nestedField)) { - throw new DomainObjectNotFoundException(fieldId.ToString(), $"Fields[{parentId}].Fields", typeof(Schema)); + throw new DomainObjectNotFoundException(fieldId.ToString()); } return nestedField; @@ -43,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (!schema.FieldsById.TryGetValue(fieldId, out var field)) { - throw new DomainObjectNotFoundException(fieldId.ToString(), "Fields", typeof(Schema)); + throw new DomainObjectNotFoundException(fieldId.ToString()); } if (!allowLocked) @@ -58,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (field.IsLocked) { - throw new DomainException("Schema field is locked."); + throw new DomainException(T.Get("schemas.fieldIsLocked")); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index f90c38201..ce8a2e618 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; #pragma warning disable IDE0060 // Remove unused parameter @@ -25,11 +26,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot create schema.", e => + Validate.It(e => { if (!command.Name.IsSlug()) { - e(Not.ValidSlug("Name"), nameof(command.Name)); + e(Not.ValidSlug(nameof(command.Name)), nameof(command.Name)); } ValidateUpsert(command, e); @@ -40,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot synchronize schema.", e => + Validate.It(e => { ValidateUpsert(command, e); }); @@ -57,11 +58,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards arrayField = GuardHelper.GetArrayFieldOrThrow(schema, command.ParentFieldId.Value, false); } - Validate.It(() => "Cannot reorder schema fields.", e => + Validate.It(e => { if (command.FieldIds == null) { - e(Not.Defined("Field ids"), nameof(command.FieldIds)); + e(Not.Defined(nameof(command.FieldIds)), nameof(command.FieldIds)); } if (arrayField == null) @@ -79,11 +80,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot configure preview urls.", e => + Validate.It(e => { if (command.PreviewUrls == null) { - e(Not.Defined("Preview Urls"), nameof(command.PreviewUrls)); + e(Not.Defined(nameof(command.PreviewUrls)), nameof(command.PreviewUrls)); } }); } @@ -92,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot configure UI fields.", e => + Validate.It(e => { ValidateFieldNames(schema, command.FieldsInLists, nameof(command.FieldsInLists), e, IsMetaField); ValidateFieldNames(schema, command.FieldsInReferences, nameof(command.FieldsInReferences), e, IsNotAllowed); @@ -103,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot configure field rules.", e => + Validate.It(e => { ValidateFieldRules(command.FieldRules, nameof(command.FieldRules), e); }); @@ -143,22 +144,18 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (command.Fields?.Count > 0) { - var fieldIndex = 0; - var fieldPrefix = string.Empty; - - foreach (var field in command.Fields) + command.Fields.Foreach((field, fieldIndex) => { - fieldIndex++; - fieldPrefix = $"Fields[{fieldIndex}]"; + var fieldPrefix = $"Fields[{fieldIndex}]"; ValidateRootField(field, fieldPrefix, e); - } + }); foreach (var fieldName in command.Fields.Duplicates(x => x.Name)) { if (fieldName.IsPropertyName()) { - e($"Field '{fieldName}' has been added twice.", nameof(command.Fields)); + e(T.Get("schemas.duplicateFieldName", new { field = fieldName }), nameof(command.Fields)); } } } @@ -179,7 +176,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!field.Partitioning.IsValidPartitioning()) { - e(Not.Valid("Partitioning"), $"{prefix}.{nameof(field.Partitioning)}"); + e(Not.Valid(nameof(field.Partitioning)), $"{prefix}.{nameof(field.Partitioning)}"); } ValidateField(field, prefix, e); @@ -188,27 +185,23 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (field.Properties is ArrayFieldProperties) { - var nestedIndex = 0; - var nestedPrefix = string.Empty; - - foreach (var nestedField in field.Nested) + field.Nested.Foreach((nestedField, nestedIndex) => { - nestedIndex++; - nestedPrefix = $"{prefix}.Nested[{nestedIndex}]"; + var nestedPrefix = $"{prefix}.Nested[{nestedIndex}]"; ValidateNestedField(nestedField, nestedPrefix, e); - } + }); } else if (field.Nested.Count > 0) { - e("Only array fields can have nested fields.", $"{prefix}.{nameof(field.Partitioning)}"); + e(T.Get("schemas.onlyArraysHaveNested"), $"{prefix}.{nameof(field.Partitioning)}"); } foreach (var fieldName in field.Nested.Duplicates(x => x.Name)) { if (fieldName.IsPropertyName()) { - e($"Field '{fieldName}' has been added twice.", $"{prefix}.Nested"); + e(T.Get("schemas.duplicateFieldName", new { field = fieldName }), $"{prefix}.Nested"); } } } @@ -225,7 +218,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (nestedField.Properties is ArrayFieldProperties) { - e("Nested field cannot be array fields.", $"{prefix}.{nameof(nestedField.Properties)}"); + e(T.Get("schemas.onylArraysInRoot"), $"{prefix}.{nameof(nestedField.Properties)}"); } ValidateField(nestedField, prefix, e); @@ -236,12 +229,12 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!field.Name.IsPropertyName()) { - e(Not.ValidPropertyName("Field name"), $"{prefix}.{nameof(field.Name)}"); + e(Not.ValidJavascriptName(nameof(field.Name)), $"{prefix}.{nameof(field.Name)}"); } if (field.Properties == null) { - e(Not.Defined("Field properties"), $"{prefix}.{nameof(field.Properties)}"); + e(Not.Defined(nameof(field.Properties)), $"{prefix}.{nameof(field.Properties)}"); } else { @@ -249,18 +242,18 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (field.IsHidden) { - e("UI field cannot be hidden.", $"{prefix}.{nameof(field.IsHidden)}"); + e(T.Get("schemas.uiFieldCannotBeHidden"), $"{prefix}.{nameof(field.IsHidden)}"); } if (field.IsDisabled) { - e("UI field cannot be disabled.", $"{prefix}.{nameof(field.IsDisabled)}"); + e(T.Get("schemas.uiFieldCannotBeDisabled"), $"{prefix}.{nameof(field.IsDisabled)}"); } } var errors = FieldPropertiesValidator.Validate(field.Properties); - errors.Foreach(x => x.WithPrefix($"{prefix}.{nameof(field.Properties)}").AddTo(e)); + errors.Foreach((x, _) => x.WithPrefix($"{prefix}.{nameof(field.Properties)}").AddTo(e)); } } @@ -268,13 +261,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (fields != null) { - var fieldIndex = 0; - var fieldPrefix = string.Empty; - - foreach (var fieldName in fields) + fields.Foreach((fieldName, fieldIndex) => { - fieldIndex++; - fieldPrefix = $"{path}[{fieldIndex}]"; + var fieldPrefix = $"{path}[{fieldIndex}]"; var field = schema.FieldsByName.GetOrDefault(fieldName ?? string.Empty); @@ -284,19 +273,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } else if (field == null && !isAllowed(fieldName)) { - e("Field is not part of the schema.", fieldPrefix); + e(T.Get("schemas.fieldNotInSchema"), fieldPrefix); } else if (field?.IsUI() == true) { - e("Field cannot be an UI field.", fieldPrefix); + e(T.Get("schemas.fieldCannotBeUIField"), fieldPrefix); } - } + }); foreach (var duplicate in fields.Duplicates()) { if (!string.IsNullOrWhiteSpace(duplicate)) { - e($"Field '{duplicate}' has been added twice.", path); + e(T.Get("schemas.duplicateFieldName", new { field = duplicate }), path); } } } @@ -304,40 +293,29 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards private static void ValidateFieldRules(List? fieldRules, string path, AddValidation e) { - if (fieldRules != null) + fieldRules?.Foreach((rule, ruleIndex) => { - var ruleIndex = 0; - var rulePrefix = string.Empty; + var rulePrefix = $"{path}[{ruleIndex}]"; - foreach (var fieldRule in fieldRules) + if (string.IsNullOrWhiteSpace(rule.Field)) { - ruleIndex++; - rulePrefix = $"{path}[{ruleIndex}]"; - - if (string.IsNullOrWhiteSpace(fieldRule.Field)) - { - e(Not.Defined(nameof(fieldRule.Field)), $"{rulePrefix}.{nameof(fieldRule.Field)}"); - } + e(Not.Defined(nameof(rule.Field)), $"{rulePrefix}.{nameof(rule.Field)}"); + } - if (!fieldRule.Action.IsEnumValue()) - { - e(Not.Valid(nameof(fieldRule.Action)), $"{rulePrefix}.{nameof(fieldRule.Action)}"); - } + if (!rule.Action.IsEnumValue()) + { + e(Not.Valid(nameof(rule.Action)), $"{rulePrefix}.{nameof(rule.Action)}"); } - } + }); } private static void ValidateFieldNames(UpsertCommand command, FieldNames? fields, string path, AddValidation e, Func isAllowed) { if (fields != null) { - var fieldIndex = 0; - var fieldPrefix = string.Empty; - - foreach (var fieldName in fields) + fields.Foreach((fieldName, fieldIndex) => { - fieldIndex++; - fieldPrefix = $"{path}[{fieldIndex}]"; + var fieldPrefix = $"{path}[{fieldIndex}]"; var field = command?.Fields?.Find(x => x.Name == fieldName); @@ -347,19 +325,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } else if (field == null && !isAllowed(fieldName)) { - e("Field is not part of the schema.", fieldPrefix); + e(T.Get("schemas.fieldNotInSchema"), fieldPrefix); } else if (field?.Properties?.IsUIProperty() == true) { - e("Field cannot be an UI field.", fieldPrefix); + e(T.Get("schemas.fieldCannotBeUIField"), fieldPrefix); } - } + }); foreach (var duplicate in fields.Duplicates()) { if (!string.IsNullOrWhiteSpace(duplicate)) { - e($"Field '{duplicate}' has been added twice.", path); + e(T.Get("schemas.duplicateFieldName", new { field = duplicate }), path); } } } @@ -375,11 +353,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards return false; } - private static void ValidateFieldIds(ReorderFields c, IReadOnlyDictionary fields, AddValidation e) + private static void ValidateFieldIds(ReorderFields c, IReadOnlyDictionary fields, AddValidation e) { if (c.FieldIds != null && (c.FieldIds.Count != fields.Count || c.FieldIds.Any(x => !fields.ContainsKey(x)))) { - e("Field ids do not cover all fields.", nameof(c.FieldIds)); + e(T.Get("schemas.fieldsNotCovered"), nameof(c.FieldIds)); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs index 2788f0387..c307c7651 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -9,6 +9,7 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Schemas.Guards @@ -19,22 +20,22 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot add a new field.", e => + Validate.It(e => { if (!command.Name.IsPropertyName()) { - e(Not.ValidPropertyName("Name"), nameof(command.Name)); + e(Not.ValidJavascriptName(nameof(command.Name)), nameof(command.Name)); } if (command.Properties == null) { - e(Not.Defined("Properties"), nameof(command.Properties)); + e(Not.Defined(nameof(command.Properties)), nameof(command.Properties)); } else { var errors = FieldPropertiesValidator.Validate(command.Properties); - errors.Foreach(x => x.WithPrefix(nameof(command.Properties)).AddTo(e)); + errors.Foreach((x, _) => x.WithPrefix(nameof(command.Properties)).AddTo(e)); } if (command.ParentFieldId.HasValue) @@ -43,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (arrayField.FieldsByName.ContainsKey(command.Name)) { - e("A field with the same name already exists."); + e(T.Get("schemas.fieldNameAlreadyExists")); } } else @@ -55,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (schema.FieldsByName.ContainsKey(command.Name)) { - e("A field with the same name already exists."); + e(T.Get("schemas.fieldNameAlreadyExists")); } } }); @@ -67,17 +68,17 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId, false); - Validate.It(() => "Cannot update field.", e => + Validate.It(e => { if (command.Properties == null) { - e(Not.Defined("Properties"), nameof(command.Properties)); + e(Not.Defined("Properties"), nameof(command.Properties)); } else { var errors = FieldPropertiesValidator.Validate(command.Properties); - errors.Foreach(x => x.WithPrefix(nameof(command.Properties)).AddTo(e)); + errors.Foreach((x, _) => x.WithPrefix(nameof(command.Properties)).AddTo(e)); } }); } @@ -90,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (!field.IsForApi(true)) { - throw new DomainException("UI field cannot be hidden."); + throw new DomainException(T.Get("schemas.uiFieldCannotBeHidden")); } } @@ -102,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (!field.IsForApi(true)) { - throw new DomainException("UI field cannot be diabled."); + throw new DomainException(T.Get("schemas.uiFieldCannotBeDisabled")); } } @@ -114,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (!field.IsForApi(true)) { - throw new DomainException("UI field cannot be shown."); + throw new DomainException(T.Get("schemas.uiFieldCannotBeShown")); } } @@ -126,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (!field.IsForApi(true)) { - throw new DomainException("UI field cannot be enabled."); + throw new DomainException(T.Get("schemas.uiFieldCannotBeEnabled")); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs index bc22580e6..98d159cdd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -15,6 +15,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Schemas.Indexes @@ -178,9 +179,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes if (token == null) { - var error = new ValidationError("A schema with this name already exists."); - - throw new ValidationException("Cannot create schema.", error); + throw new ValidationException(T.Get("schemas.nameAlreadyExists")); } return token; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs index efd84de50..56557a005 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -21,6 +21,7 @@ using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Schemas { @@ -429,7 +430,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas { if (Snapshot.IsDeleted) { - throw new DomainException("Schema has already been deleted."); + throw new DomainException(T.Get("schemas.alreadyDeleted")); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs index 3fb4ae135..b7c3a72ea 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Schemas; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.Schemas { @@ -20,55 +21,55 @@ namespace Squidex.Domain.Apps.Entities.Schemas : base(typeNameRegistry) { AddEventMessage( - "reordered fields of schema {[Name]}."); + T.Get("history.schemas.fieldsReordered")); AddEventMessage( - "created schema {[Name]}."); + T.Get("history.schemas.created")); AddEventMessage( - "updated schema {[Name]}."); + T.Get("history.schemas.updated")); AddEventMessage( - "deleted schema {[Name]}."); + T.Get("history.schemas.deleted")); AddEventMessage( - "published schema {[Name]}."); + T.Get("history.schemas.published")); AddEventMessage( - "unpublished schema {[Name]}."); + T.Get("history.schemas.unpublished")); AddEventMessage( - "reordered fields of schema {[Name]}."); + T.Get("history.schemas.fieldsReordered")); AddEventMessage( - "configured script of schema {[Name]}."); + T.Get("history.schemas.scriptsConfigured")); AddEventMessage( - "added field {[Field]} to schema {[Name]}."); + T.Get("history.schemas.fieldAdded")); AddEventMessage( - "deleted field {[Field]} from schema {[Name]}."); + T.Get("history.schemas.fieldDeleted")); AddEventMessage( - "has locked field {[Field]} of schema {[Name]}."); + T.Get("history.schemas.fieldLocked")); AddEventMessage( - "has hidden field {[Field]} of schema {[Name]}."); + T.Get("history.schemas.fieldHidden")); AddEventMessage( - "has shown field {[Field]} of schema {[Name]}."); + T.Get("history.schemas.fieldShown")); AddEventMessage( - "disabled field {[Field]} of schema {[Name]}."); + T.Get("history.schemas.fieldDisabled")); AddEventMessage( - "disabled field {[Field]} of schema {[Name]}."); + T.Get("history.schemas.fieldDisabled")); AddEventMessage( - "has updated field {[Field]} of schema {[Name]}."); + T.Get("history.schemas.fieldUpdated")); AddEventMessage( - "deleted field {[Field]} of schema {[Name]}."); + T.Get("history.schemas.fieldDeleted")); } protected override Task CreateEventCoreAsync(Envelope @event) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs index 81eddcffc..33be088b0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Search; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Shared; namespace Squidex.Domain.Apps.Entities.Schemas @@ -66,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas { var schemaUrl = urlGenerator.SchemaUI(appId, schemaId); - result.Add($"{name} Schema", SearchResultType.Schema, schemaUrl); + result.Add(T.Get("search.schemaResult", new { name }), SearchResultType.Schema, schemaUrl); } private void AddContentsUrl(SearchResults result, NamedId appId, ISchemaEntity schema, NamedId schemaId, string name) @@ -75,13 +76,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas { var contentUrl = urlGenerator.ContentUI(appId, schemaId, schemaId.Id); - result.Add($"{name} Content", SearchResultType.Content, contentUrl, name); + result.Add(T.Get("search.contentResult", new { name }), SearchResultType.Content, contentUrl, name); } else { var contentUrl = urlGenerator.ContentsUI(appId, schemaId); - result.Add($"{name} Contents", SearchResultType.Content, contentUrl, name); + result.Add(T.Get("search.contentsResult", new { name }), SearchResultType.Content, contentUrl, name); } } diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs b/backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs index 6a8bfbdd9..47aab16e9 100644 --- a/backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs +++ b/backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs @@ -50,7 +50,7 @@ namespace Squidex.Domain.Users.MongoDb internal void AddClaims(IEnumerable claims) { - claims.Foreach(AddClaim); + claims.Foreach((x, _) => AddClaim(x)); } internal void RemoveClaim(Claim claim) @@ -60,7 +60,7 @@ namespace Squidex.Domain.Users.MongoDb internal void RemoveClaims(IEnumerable claims) { - claims.Foreach(RemoveClaim); + claims.Foreach((x, _) => RemoveClaim(x)); } internal string? GetToken(string loginProvider, string name) diff --git a/backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs b/backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs index 08a2f8320..9f34fdf65 100644 --- a/backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs +++ b/backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -11,15 +11,12 @@ using Microsoft.AspNetCore.Identity; using SharpPwned.NET; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Users { public sealed class PwnedPasswordValidator : IPasswordValidator { - private const string ErrorCode = "PwnedError"; - private const string ErrorText = "This password has previously appeared in a data breach and should never be used. If you've ever used it anywhere before, change it!"; - private static readonly IdentityResult Error = IdentityResult.Failed(new IdentityError { Code = ErrorCode, Description = ErrorText }); - private readonly HaveIBeenPwnedRestClient client = new HaveIBeenPwnedRestClient(); private readonly ISemanticLog log; @@ -38,7 +35,9 @@ namespace Squidex.Domain.Users if (isBreached) { - return Error; + var errorText = T.Get("security.passwordStolen"); + + return IdentityResult.Failed(new IdentityError { Code = "PwnedError", Description = errorText }); } } catch (Exception ex) diff --git a/backend/src/Squidex.Domain.Users/UserManagerExtensions.cs b/backend/src/Squidex.Domain.Users/UserManagerExtensions.cs index eee722702..8214d8286 100644 --- a/backend/src/Squidex.Domain.Users/UserManagerExtensions.cs +++ b/backend/src/Squidex.Domain.Users/UserManagerExtensions.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -131,12 +131,12 @@ namespace Squidex.Domain.Users try { - await DoChecked(() => userManager.CreateAsync(user), "Cannot create user."); - await DoChecked(() => values.SyncClaims(userManager, user), "Cannot add user."); + await DoChecked(() => userManager.CreateAsync(user)); + await DoChecked(() => values.SyncClaims(userManager, user)); if (!string.IsNullOrWhiteSpace(values.Password)) { - await DoChecked(() => userManager.AddPasswordAsync(user, values.Password), "Cannot create user."); + await DoChecked(() => userManager.AddPasswordAsync(user, values.Password)); } } catch @@ -155,7 +155,7 @@ namespace Squidex.Domain.Users if (user == null) { - throw new DomainObjectNotFoundException(id, typeof(IdentityUser)); + throw new DomainObjectNotFoundException(id); } await UpdateAsync(userManager, user, values); @@ -189,23 +189,21 @@ namespace Squidex.Domain.Users public static async Task UpdateAsync(this UserManager userManager, IdentityUser user, UserValues values) { - if (user == null) - { - throw new DomainObjectNotFoundException("Id", typeof(IdentityUser)); - } + Guard.NotNull(user, nameof(user)); + Guard.NotNull(values, nameof(values)); if (!string.IsNullOrWhiteSpace(values.Email) && values.Email != user.Email) { - await DoChecked(() => userManager.SetEmailAsync(user, values.Email), "Cannot update email."); - await DoChecked(() => userManager.SetUserNameAsync(user, values.Email), "Cannot update email."); + await DoChecked(() => userManager.SetEmailAsync(user, values.Email)); + await DoChecked(() => userManager.SetUserNameAsync(user, values.Email)); } - await DoChecked(() => values.SyncClaims(userManager, user), "Cannot update user."); + await DoChecked(() => values.SyncClaims(userManager, user)); if (!string.IsNullOrWhiteSpace(values.Password)) { - await DoChecked(() => userManager.RemovePasswordAsync(user), "Cannot replace password."); - await DoChecked(() => userManager.AddPasswordAsync(user, values.Password), "Cannot replace password."); + await DoChecked(() => userManager.RemovePasswordAsync(user)); + await DoChecked(() => userManager.AddPasswordAsync(user, values.Password)); } } @@ -215,10 +213,10 @@ namespace Squidex.Domain.Users if (user == null) { - throw new DomainObjectNotFoundException(id, typeof(IdentityUser)); + throw new DomainObjectNotFoundException(id); } - await DoChecked(() => userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddYears(100)), "Cannot lock user."); + await DoChecked(() => userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddYears(100))); return (await userManager.ResolveUserAsync(user))!; } @@ -229,21 +227,21 @@ namespace Squidex.Domain.Users if (user == null) { - throw new DomainObjectNotFoundException(id, typeof(IdentityUser)); + throw new DomainObjectNotFoundException(id); } - await DoChecked(() => userManager.SetLockoutEndDateAsync(user, null), "Cannot unlock user."); + await DoChecked(() => userManager.SetLockoutEndDateAsync(user, null)); return (await userManager.ResolveUserAsync(user))!; } - private static async Task DoChecked(Func> action, string message) + private static async Task DoChecked(Func> action) { var result = await action(); if (!result.Succeeded) { - throw new ValidationException(message, result.Errors.Select(x => new ValidationError(x.Description)).ToArray()); + throw new ValidationException(result.Errors.Select(x => new ValidationError(x.Description)).ToList()); } } diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs index 671b2caba..d4490f84b 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs @@ -8,7 +8,7 @@ using System; using System.Linq; using System.Reflection; -using System.Threading; +using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; using Newtonsoft.Json; @@ -18,11 +18,9 @@ namespace Squidex.Infrastructure.MongoDb { public static class BsonJsonConvention { - private static volatile int isRegistered; - public static void Register(JsonSerializer serializer) { - if (Interlocked.Increment(ref isRegistered) == 1) + try { var pack = new ConventionPack(); @@ -53,6 +51,10 @@ namespace Squidex.Infrastructure.MongoDb ConventionRegistry.Register("json", pack, t => true); } + catch (BsonSerializationException) + { + return; + } } } } \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/InstantSerializer.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/InstantSerializer.cs index 6979cafe3..63c871406 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/InstantSerializer.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/InstantSerializer.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Threading; +using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using NodaTime; @@ -14,14 +14,16 @@ namespace Squidex.Infrastructure.MongoDb { public sealed class InstantSerializer : SerializerBase, IBsonPolymorphicSerializer { - private static volatile int isRegistered; - public static void Register() { - if (Interlocked.Increment(ref isRegistered) == 1) + try { BsonSerializer.RegisterSerializer(new InstantSerializer()); } + catch (BsonSerializationException) + { + return; + } } public bool IsDiscriminatorCompatibleWithObjectSerializer diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs index 995af9d52..acc0e592c 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -7,27 +7,28 @@ using MongoDB.Driver; using Squidex.Infrastructure.Queries; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Infrastructure.MongoDb.Queries { public static class FilterBuilder { - public static (FilterDefinition? Filter, bool Last) BuildFilter(this ClrQuery query, bool supportsSearch = true) + public static (FilterDefinition? Filter, bool Last) BuildFilter(this ClrQuery query, bool supportsSearch = true) { if (query.FullText != null) { if (!supportsSearch) { - throw new ValidationException("Query $search clause not supported."); + throw new ValidationException(T.Get("common.fullTextNotSupported")); } - return (Builders.Filter.Text(query.FullText), false); + return (Builders.Filter.Text(query.FullText), false); } if (query.Filter != null) { - return (query.Filter.BuildFilter(), true); + return (query.Filter.BuildFilter(), true); } return (null, false); diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs index b4c45f945..890bade4a 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Threading; +using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; @@ -13,14 +13,16 @@ namespace Squidex.Infrastructure.MongoDb { public class RefTokenSerializer : ClassSerializerBase { - private static volatile int isRegistered; - public static void Register() { - if (Interlocked.Increment(ref isRegistered) == 1) + try { BsonSerializer.RegisterSerializer(new RefTokenSerializer()); } + catch (BsonSerializationException) + { + return; + } } protected override RefToken DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args) diff --git a/backend/src/Squidex.Infrastructure/Assets/AssetAlreadyExistsException.cs b/backend/src/Squidex.Infrastructure/Assets/AssetAlreadyExistsException.cs index ddf8465e0..6e1815dba 100644 --- a/backend/src/Squidex.Infrastructure/Assets/AssetAlreadyExistsException.cs +++ b/backend/src/Squidex.Infrastructure/Assets/AssetAlreadyExistsException.cs @@ -13,12 +13,7 @@ namespace Squidex.Infrastructure.Assets [Serializable] public class AssetAlreadyExistsException : Exception { - public AssetAlreadyExistsException(string fileName) - : base(FormatMessage(fileName)) - { - } - - public AssetAlreadyExistsException(string fileName, Exception inner) + public AssetAlreadyExistsException(string fileName, Exception? inner = null) : base(FormatMessage(fileName), inner) { } diff --git a/backend/src/Squidex.Infrastructure/Assets/AssetNotFoundException.cs b/backend/src/Squidex.Infrastructure/Assets/AssetNotFoundException.cs index 1691a8bbf..f618b69dc 100644 --- a/backend/src/Squidex.Infrastructure/Assets/AssetNotFoundException.cs +++ b/backend/src/Squidex.Infrastructure/Assets/AssetNotFoundException.cs @@ -13,12 +13,7 @@ namespace Squidex.Infrastructure.Assets [Serializable] public class AssetNotFoundException : Exception { - public AssetNotFoundException(string fileName) - : base(FormatMessage(fileName)) - { - } - - public AssetNotFoundException(string fileName, Exception inner) + public AssetNotFoundException(string fileName, Exception? inner = null) : base(FormatMessage(fileName), inner) { } diff --git a/backend/src/Squidex.Infrastructure/CollectionExtensions.cs b/backend/src/Squidex.Infrastructure/CollectionExtensions.cs index 51fe2ca6a..a69c38716 100644 --- a/backend/src/Squidex.Infrastructure/CollectionExtensions.cs +++ b/backend/src/Squidex.Infrastructure/CollectionExtensions.cs @@ -258,11 +258,15 @@ namespace Squidex.Infrastructure return result; } - public static void Foreach(this IEnumerable collection, Action action) + public static void Foreach(this IEnumerable collection, Action action) { + var index = 0; + foreach (var item in collection) { - action(item); + action(item, index); + + index++; } } diff --git a/backend/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs b/backend/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs index 07e6670c0..d9e5d84a6 100644 --- a/backend/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs +++ b/backend/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs @@ -153,12 +153,12 @@ namespace Squidex.Infrastructure.Commands if (command.ExpectedVersion > EtagVersion.Any && command.ExpectedVersion != Version) { - throw new DomainObjectVersionException(id.ToString(), GetType(), Version, command.ExpectedVersion); + throw new DomainObjectVersionException(id.ToString(), Version, command.ExpectedVersion); } if (isUpdate && Version < 0) { - throw new DomainObjectNotFoundException(id.ToString(), GetType()); + throw new DomainObjectNotFoundException(id.ToString()); } var previousSnapshot = Snapshot; diff --git a/backend/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs b/backend/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs index d475d02da..ee780bf4b 100644 --- a/backend/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs +++ b/backend/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure.Orleans; namespace Squidex.Infrastructure.Commands { - public abstract class DomainObjectGrain : GrainOfGuid where T : DomainObjectBase where TState : class, IDomainState, new() + public abstract class DomainObjectGrain : GrainOfGuid, IDomainObjectGrain where T : DomainObjectBase where TState : class, IDomainState, new() { private readonly T domainObject; @@ -40,8 +40,10 @@ namespace Squidex.Infrastructure.Commands return base.OnActivateAsync(key); } - public async Task> ExecuteAsync(J command) + public async Task> ExecuteAsync(J command, GrainContext context) { + context?.Use(); + var result = await domainObject.ExecuteAsync(command.Value); return result; diff --git a/backend/src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs b/backend/src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs index cf49ccf0e..b3897d03d 100644 --- a/backend/src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs +++ b/backend/src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs @@ -20,7 +20,7 @@ namespace Squidex.Infrastructure.Commands } if (string.Equals(context.InterfaceMethod.Name, nameof(IDomainObjectGrain.ExecuteAsync), StringComparison.CurrentCultureIgnoreCase) && - context.Arguments?.Length == 1 && + context.Arguments?.Length > 0 && context.Arguments[0] != null) { var argumentFullName = context.Arguments[0].ToString(); diff --git a/backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs b/backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs index 8b49b1683..60fd43e3f 100644 --- a/backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs +++ b/backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Orleans; +using Squidex.Infrastructure.Orleans; namespace Squidex.Infrastructure.Commands { @@ -42,7 +43,7 @@ namespace Squidex.Infrastructure.Commands { var grain = grainFactory.GetGrain(typedCommand.AggregateId); - var result = await grain.ExecuteAsync(typedCommand); + var result = await grain.ExecuteAsync(typedCommand, GrainContext.Create()); return result.Value; } diff --git a/backend/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs b/backend/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs index ea10c037b..3a9f26e5d 100644 --- a/backend/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs +++ b/backend/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs @@ -13,6 +13,6 @@ namespace Squidex.Infrastructure.Commands { public interface IDomainObjectGrain : IGrainWithGuidKey { - Task> ExecuteAsync(J command); + Task> ExecuteAsync(J command, GrainContext context); } } diff --git a/backend/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs b/backend/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs index 2a51820cf..b0ef7fa10 100644 --- a/backend/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs +++ b/backend/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Options; +using Squidex.Infrastructure.Translations; namespace Squidex.Infrastructure.Commands { @@ -25,7 +26,7 @@ namespace Squidex.Infrastructure.Commands { if (options.IsReadonly) { - throw new DomainException("Application is in readonly mode at the moment."); + throw new DomainException(T.Get("common.readonlyMode")); } return next(context); diff --git a/backend/src/Squidex.Infrastructure/ConfigurationException.cs b/backend/src/Squidex.Infrastructure/ConfigurationException.cs index 8f378a684..14e6d5eb5 100644 --- a/backend/src/Squidex.Infrastructure/ConfigurationException.cs +++ b/backend/src/Squidex.Infrastructure/ConfigurationException.cs @@ -13,16 +13,7 @@ namespace Squidex.Infrastructure [Serializable] public class ConfigurationException : Exception { - public ConfigurationException() - { - } - - public ConfigurationException(string message) - : base(message) - { - } - - public ConfigurationException(string message, Exception inner) + public ConfigurationException(string message, Exception? inner = null) : base(message, inner) { } diff --git a/backend/src/Squidex.Infrastructure/DomainException.cs b/backend/src/Squidex.Infrastructure/DomainException.cs index 666f0f65c..2fdbd1c65 100644 --- a/backend/src/Squidex.Infrastructure/DomainException.cs +++ b/backend/src/Squidex.Infrastructure/DomainException.cs @@ -13,12 +13,7 @@ namespace Squidex.Infrastructure [Serializable] public class DomainException : Exception { - public DomainException(string message) - : base(message) - { - } - - public DomainException(string message, Exception? inner) + public DomainException(string message, Exception? inner = null) : base(message, inner) { } diff --git a/backend/src/Squidex.Infrastructure/DomainForbiddenException.cs b/backend/src/Squidex.Infrastructure/DomainForbiddenException.cs index 6baa3c64f..31f29336e 100644 --- a/backend/src/Squidex.Infrastructure/DomainForbiddenException.cs +++ b/backend/src/Squidex.Infrastructure/DomainForbiddenException.cs @@ -13,12 +13,7 @@ namespace Squidex.Infrastructure [Serializable] public class DomainForbiddenException : DomainException { - public DomainForbiddenException(string message) - : base(message) - { - } - - public DomainForbiddenException(string message, Exception inner) + public DomainForbiddenException(string message, Exception? inner = null) : base(message, inner) { } diff --git a/backend/src/Squidex.Infrastructure/DomainObjectDeletedException.cs b/backend/src/Squidex.Infrastructure/DomainObjectDeletedException.cs index 38758b038..90305a3ba 100644 --- a/backend/src/Squidex.Infrastructure/DomainObjectDeletedException.cs +++ b/backend/src/Squidex.Infrastructure/DomainObjectDeletedException.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -7,14 +7,15 @@ using System; using System.Runtime.Serialization; +using Squidex.Infrastructure.Translations; namespace Squidex.Infrastructure { [Serializable] public class DomainObjectDeletedException : DomainObjectException { - public DomainObjectDeletedException(string id, Type type) - : base(FormatMessage(id, type), id, type) + public DomainObjectDeletedException(string id, Exception? inner = null) + : base(FormatMessage(id), id, inner) { } @@ -23,9 +24,9 @@ namespace Squidex.Infrastructure { } - private static string FormatMessage(string id, Type type) + private static string FormatMessage(string id) { - return $"Domain object \'{id}\' (type {type}) already deleted."; + return T.Get("exceptions.domainObjectDeleted", new { id }); } } } diff --git a/backend/src/Squidex.Infrastructure/DomainObjectException.cs b/backend/src/Squidex.Infrastructure/DomainObjectException.cs index 4742bcc33..18d6069c7 100644 --- a/backend/src/Squidex.Infrastructure/DomainObjectException.cs +++ b/backend/src/Squidex.Infrastructure/DomainObjectException.cs @@ -13,30 +13,25 @@ namespace Squidex.Infrastructure [Serializable] public class DomainObjectException : Exception { - public string? TypeName { get; } - public string Id { get; } - protected DomainObjectException(string message, string id, Type type, Exception? inner = null) + public DomainObjectException(string message, string id, Exception? inner = null) : base(message, inner) { - Id = id; + Guard.NotNullOrEmpty(id, nameof(id)); - TypeName = type?.Name; + Id = id; } - protected DomainObjectException(SerializationInfo info, StreamingContext context) + public DomainObjectException(SerializationInfo info, StreamingContext context) : base(info, context) { Id = info.GetString(nameof(Id))!; - - TypeName = info.GetString(nameof(TypeName))!; } public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue(nameof(Id), Id); - info.AddValue(nameof(TypeName), TypeName); base.GetObjectData(info, context); } diff --git a/backend/src/Squidex.Infrastructure/DomainObjectNotFoundException.cs b/backend/src/Squidex.Infrastructure/DomainObjectNotFoundException.cs index 5dfbe786e..d6a8ada4d 100644 --- a/backend/src/Squidex.Infrastructure/DomainObjectNotFoundException.cs +++ b/backend/src/Squidex.Infrastructure/DomainObjectNotFoundException.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -7,19 +7,15 @@ using System; using System.Runtime.Serialization; +using Squidex.Infrastructure.Translations; namespace Squidex.Infrastructure { [Serializable] public class DomainObjectNotFoundException : DomainObjectException { - public DomainObjectNotFoundException(string id, Type type) - : base(FormatMessage(id, type), id, type) - { - } - - public DomainObjectNotFoundException(string id, string collection, Type type) - : base(FormatMessage(id, collection, type), id, type) + public DomainObjectNotFoundException(string id, Exception? inner = null) + : base(FormatMessage(id), id, inner) { } @@ -28,14 +24,9 @@ namespace Squidex.Infrastructure { } - private static string FormatMessage(string id, Type type) - { - return $"Domain object \'{id}\' (type {type}) is not found."; - } - - private static string FormatMessage(string id, string collection, Type type) + private static string FormatMessage(string id) { - return $"Domain object \'{id}\' not found on {type}.{collection}"; + return T.Get("exceptions.domainObjectNotFound", new { id }); } } } diff --git a/backend/src/Squidex.Infrastructure/DomainObjectVersionException.cs b/backend/src/Squidex.Infrastructure/DomainObjectVersionException.cs index 3b6d50aee..37b686e71 100644 --- a/backend/src/Squidex.Infrastructure/DomainObjectVersionException.cs +++ b/backend/src/Squidex.Infrastructure/DomainObjectVersionException.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -7,6 +7,7 @@ using System; using System.Runtime.Serialization; +using Squidex.Infrastructure.Translations; namespace Squidex.Infrastructure { @@ -17,8 +18,8 @@ namespace Squidex.Infrastructure public long ExpectedVersion { get; } - public DomainObjectVersionException(string id, Type type, long currentVersion, long expectedVersion) - : base(FormatMessage(id, type, currentVersion, expectedVersion), id, type) + public DomainObjectVersionException(string id, long currentVersion, long expectedVersion, Exception? inner = null) + : base(FormatMessage(id, currentVersion, expectedVersion), id, inner) { CurrentVersion = currentVersion; @@ -41,9 +42,9 @@ namespace Squidex.Infrastructure base.GetObjectData(info, context); } - private static string FormatMessage(string id, Type type, long currentVersion, long expectedVersion) + private static string FormatMessage(string id, long currentVersion, long expectedVersion) { - return $"Requested version {expectedVersion} for object '{id}' (type {type}), but found {currentVersion}."; + return T.Get("exceptions.domainObjectVersion", new { id, currentVersion, expectedVersion }); } } } diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs b/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs index f526caee8..fb5443617 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs @@ -17,8 +17,8 @@ namespace Squidex.Infrastructure.EventSourcing public long ExpectedVersion { get; } - public WrongEventVersionException(long currentVersion, long expectedVersion) - : base(FormatMessage(currentVersion, expectedVersion)) + public WrongEventVersionException(long currentVersion, long expectedVersion, Exception? inner = null) + : base(FormatMessage(currentVersion, expectedVersion), inner) { CurrentVersion = currentVersion; diff --git a/backend/src/Squidex.Infrastructure/Guard.cs b/backend/src/Squidex.Infrastructure/Guard.cs index 7ed6bdbd5..282e1a0aa 100644 --- a/backend/src/Squidex.Infrastructure/Guard.cs +++ b/backend/src/Squidex.Infrastructure/Guard.cs @@ -205,14 +205,5 @@ namespace Squidex.Infrastructure throw new ArgumentException("Value contains an invalid character.", parameterName); } } - - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Valid(IValidatable? target, [CallerArgumentExpression("target")] string parameterName, Func message) - { - NotNull(target, parameterName); - - target?.Validate(message); - } } } diff --git a/backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs b/backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs index f7eba3978..4b02db019 100644 --- a/backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs +++ b/backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) diff --git a/backend/src/Squidex.Infrastructure/Log/Internal/FileLogProcessor.cs b/backend/src/Squidex.Infrastructure/Log/Internal/FileLogProcessor.cs index f14b8b070..8c7ad4541 100644 --- a/backend/src/Squidex.Infrastructure/Log/Internal/FileLogProcessor.cs +++ b/backend/src/Squidex.Infrastructure/Log/Internal/FileLogProcessor.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -32,7 +32,8 @@ namespace Squidex.Infrastructure.Log.Internal outputThread = new Thread(ProcessLogQueue) { - IsBackground = true, Name = "Logging" + IsBackground = true, + Name = "Logging" }; } diff --git a/backend/src/Squidex.Infrastructure/Migrations/MigrationFailedException.cs b/backend/src/Squidex.Infrastructure/Migrations/MigrationFailedException.cs index 9767b903a..1a53d0be8 100644 --- a/backend/src/Squidex.Infrastructure/Migrations/MigrationFailedException.cs +++ b/backend/src/Squidex.Infrastructure/Migrations/MigrationFailedException.cs @@ -15,13 +15,7 @@ namespace Squidex.Infrastructure.Migrations { public string Name { get; } - public MigrationFailedException(string name) - : base(FormatException(name)) - { - Name = name; - } - - public MigrationFailedException(string name, Exception inner) + public MigrationFailedException(string name, Exception? inner = null) : base(FormatException(name), inner) { Name = name; diff --git a/backend/src/Squidex.Infrastructure/Orleans/CultureFilter.cs b/backend/src/Squidex.Infrastructure/Orleans/CultureFilter.cs new file mode 100644 index 000000000..f5fdb93d8 --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Orleans/CultureFilter.cs @@ -0,0 +1,40 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Globalization; +using System.Threading.Tasks; +using Orleans; +using Orleans.Runtime; + +namespace Squidex.Infrastructure.Orleans +{ + public sealed class CultureFilter : IIncomingGrainCallFilter, IOutgoingGrainCallFilter + { + public Task Invoke(IOutgoingGrainCallContext context) + { + RequestContext.Set("Culture", CultureInfo.CurrentCulture.Name); + RequestContext.Set("CultureUI", CultureInfo.CurrentUICulture.Name); + + return context.Invoke(); + } + + public Task Invoke(IIncomingGrainCallContext context) + { + if (RequestContext.Get("Culture") is string culture) + { + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(culture); + } + + if (RequestContext.Get("CultureUI") is string cultureUI) + { + CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(cultureUI); + } + + return context.Invoke(); + } + } +} diff --git a/backend/src/Squidex.Infrastructure/Orleans/GrainContext.cs b/backend/src/Squidex.Infrastructure/Orleans/GrainContext.cs new file mode 100644 index 000000000..0b13cd809 --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Orleans/GrainContext.cs @@ -0,0 +1,55 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Globalization; +using System.Runtime.Serialization; + +namespace Squidex.Infrastructure.Orleans +{ + [Serializable] + public class GrainContext : ISerializable + { + public CultureInfo Culture { get; private set; } + + public CultureInfo CultureUI { get; private set; } + + private GrainContext() + { + } + + protected GrainContext(SerializationInfo info, StreamingContext context) + { + Culture = CultureInfo.GetCultureInfo(info.GetString(nameof(Culture))!); + CultureUI = CultureInfo.GetCultureInfo(info.GetString(nameof(CultureUI))!); + } + + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(nameof(Culture), Culture.Name); + info.AddValue(nameof(CultureUI), CultureUI.Name); + } + + public static GrainContext Create() + { + return new GrainContext + { + Culture = CultureInfo.CurrentCulture, + CultureUI = CultureInfo.CurrentUICulture + }; + } + + public void Use() + { + if (Culture != null) + { + CultureInfo.CurrentCulture = Culture; + CultureInfo.CurrentUICulture = CultureUI; + } + } + } +} diff --git a/backend/src/Squidex.Infrastructure/Queries/Json/QueryParser.cs b/backend/src/Squidex.Infrastructure/Queries/Json/QueryParser.cs index 8544b89cc..df4366e64 100644 --- a/backend/src/Squidex.Infrastructure/Queries/Json/QueryParser.cs +++ b/backend/src/Squidex.Infrastructure/Queries/Json/QueryParser.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -12,6 +12,7 @@ using NJsonSchema; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Infrastructure.Queries.Json @@ -46,12 +47,19 @@ namespace Squidex.Infrastructure.Queries.Json if (errors.Count > 0) { - throw new ValidationException("Failed to parse json query", errors.Select(x => new ValidationError(x)).ToArray()); + throw new ValidationException(errors.Select(BuildError).ToList()); } return result; } + private static ValidationError BuildError(string message) + { + var error = T.Get("exception.invalidJsonQuery", new { message }); + + return new ValidationError(error); + } + private static void ConvertFilters(JsonSchema schema, ClrQuery result, List errors, Query query) { if (query.Filter != null) @@ -84,7 +92,9 @@ namespace Squidex.Infrastructure.Queries.Json } catch (JsonException ex) { - throw new ValidationException("Failed to parse json query.", new ValidationError(ex.Message)); + var error = T.Get("exception.invalidJsonQueryJson", new { message = ex.Message }); + + throw new ValidationException(error); } } } diff --git a/backend/src/Squidex.Infrastructure/Queries/OData/FilterBuilder.cs b/backend/src/Squidex.Infrastructure/Queries/OData/FilterBuilder.cs index 661289113..9919b11e3 100644 --- a/backend/src/Squidex.Infrastructure/Queries/OData/FilterBuilder.cs +++ b/backend/src/Squidex.Infrastructure/Queries/OData/FilterBuilder.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -7,6 +7,7 @@ using Microsoft.OData; using Microsoft.OData.UriParser; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Infrastructure.Queries.OData @@ -22,7 +23,9 @@ namespace Squidex.Infrastructure.Queries.OData } catch (ODataException ex) { - throw new ValidationException("Query $search clause not valid.", new ValidationError(ex.Message)); + var error = T.Get("common.odataSearchNotValid", new { message = ex.Message }); + + throw new ValidationException(error, ex); } if (searchClause != null) @@ -37,7 +40,9 @@ namespace Squidex.Infrastructure.Queries.OData } catch (ODataException ex) { - throw new ValidationException("Query $filter clause not valid.", new ValidationError(ex.Message)); + var error = T.Get("common.odataFilterNotValid", new { message = ex.Message }); + + throw new ValidationException(error, ex); } if (filterClause != null) diff --git a/backend/src/Squidex.Infrastructure/Reflection/TypeNameNotFoundException.cs b/backend/src/Squidex.Infrastructure/Reflection/TypeNameNotFoundException.cs index 465385f3f..6d9a4437b 100644 --- a/backend/src/Squidex.Infrastructure/Reflection/TypeNameNotFoundException.cs +++ b/backend/src/Squidex.Infrastructure/Reflection/TypeNameNotFoundException.cs @@ -13,16 +13,7 @@ namespace Squidex.Infrastructure.Reflection [Serializable] public class TypeNameNotFoundException : Exception { - public TypeNameNotFoundException() - { - } - - public TypeNameNotFoundException(string message) - : base(message) - { - } - - public TypeNameNotFoundException(string message, Exception inner) + public TypeNameNotFoundException(string? message = null, Exception? inner = null) : base(message, inner) { } diff --git a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index 9cb38209d..41f669756 100644 --- a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -8,10 +8,14 @@ full True + + + + + - all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs b/backend/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs index d4d4d6dc9..3d728c76e 100644 --- a/backend/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs +++ b/backend/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs @@ -73,7 +73,7 @@ namespace Squidex.Infrastructure.States { if (version == EtagVersion.Empty) { - throw new DomainObjectNotFoundException(ownerKey.ToString()!, ownerType); + throw new DomainObjectNotFoundException(ownerKey.ToString()!); } else { diff --git a/backend/src/Squidex.Infrastructure/Translations/ILocalizer.cs b/backend/src/Squidex.Infrastructure/Translations/ILocalizer.cs new file mode 100644 index 000000000..ef586e37d --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Translations/ILocalizer.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Globalization; + +namespace Squidex.Infrastructure.Translations +{ + public interface ILocalizer + { + (string Result, bool NotFound) Get(CultureInfo culture, string key, string fallback, object? args = null); + } +} diff --git a/backend/src/Squidex.Infrastructure/Translations/LocalizedCompareAttribute.cs b/backend/src/Squidex.Infrastructure/Translations/LocalizedCompareAttribute.cs new file mode 100644 index 000000000..e3ac39ebd --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Translations/LocalizedCompareAttribute.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.ComponentModel.DataAnnotations; + +namespace Squidex.Infrastructure.Translations +{ + public sealed class LocalizedCompareAttribute : CompareAttribute + { + public LocalizedCompareAttribute(string otherProperty) + : base(otherProperty) + { + } + + public override string FormatErrorMessage(string name) + { + var property = T.Get($"common.{name.ToCamelCase()}", name); + + var other = T.Get($"common.{OtherProperty.ToCamelCase()}", OtherProperty); + + return T.Get("annotations_Compare", new { property, other }); + } + } +} diff --git a/backend/src/Squidex.Infrastructure/Translations/LocalizedRequired.cs b/backend/src/Squidex.Infrastructure/Translations/LocalizedRequired.cs new file mode 100644 index 000000000..c5bb084ef --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Translations/LocalizedRequired.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.ComponentModel.DataAnnotations; + +namespace Squidex.Infrastructure.Translations +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public class LocalizedRequired : RequiredAttribute + { + public override string FormatErrorMessage(string name) + { + var property = T.Get($"common.{name.ToCamelCase()}", name); + + return T.Get("annotations_Required", new { property }); + } + } +} diff --git a/backend/src/Squidex.Infrastructure/Translations/ResourcesLocalizer.cs b/backend/src/Squidex.Infrastructure/Translations/ResourcesLocalizer.cs new file mode 100644 index 000000000..e6150f01d --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Translations/ResourcesLocalizer.cs @@ -0,0 +1,182 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Resources; +using System.Text; + +namespace Squidex.Infrastructure.Translations +{ + public sealed class ResourcesLocalizer : ILocalizer + { + private const string MissingFileName = "__missing.txt"; + + private static readonly object LockObject = new object(); + private readonly ResourceManager resourceManager; + private readonly HashSet missingTranslations; + + public ResourcesLocalizer(ResourceManager resourceManager) + { + Guard.NotNull(resourceManager, nameof(resourceManager)); + + this.resourceManager = resourceManager; +#if DEBUG + if (File.Exists(MissingFileName)) + { + var missing = File.ReadAllLines(MissingFileName); + + missingTranslations = new HashSet(missing); + } + else + { + missingTranslations = new HashSet(); + } +#endif + } + + public (string Result, bool NotFound) Get(CultureInfo culture, string key, string fallback, object? args = null) + { + Guard.NotNull(culture, nameof(culture)); + Guard.NotNullOrEmpty(key, nameof(key)); + Guard.NotNull(fallback, nameof(fallback)); + + var translation = GetCore(culture, key); + + if (translation == null) + { + return (fallback, true); + } + + if (args != null) + { + var argsType = args.GetType(); + + var sb = new StringBuilder(translation.Length); + + var span = translation.AsSpan(); + + while (span.Length > 0) + { + var indexOfStart = span.IndexOf('{'); + + if (indexOfStart < 0) + { + break; + } + + indexOfStart++; + + var indexOfEnd = span.Slice(indexOfStart).IndexOf('}'); + + if (indexOfEnd < 0) + { + break; + } + + indexOfEnd += indexOfStart; + + sb.Append(span.Slice(0, indexOfStart - 1)); + + var variable = span[indexOfStart..indexOfEnd]; + + var shouldLower = false; + var shouldUpper = false; + + if (variable.Length > 0) + { + if (variable.EndsWith("|lower")) + { + variable = variable[0..^6]; + shouldLower = true; + } + + if (variable.EndsWith("|upper")) + { + variable = variable[0..^6]; + shouldUpper = true; + } + } + + var variableName = variable.ToString(); + var variableValue = variableName; + + var property = argsType.GetProperty(variableName); + + if (property != null) + { + try + { + variableValue = Convert.ToString(property.GetValue(args), culture); + } + catch + { + variableValue = null; + } + } + + if (variableValue == null) + { + variableValue = variableName; + } + + variableValue ??= variableName; + + if (variableValue!.Length > 0) + { + if (shouldLower && !char.IsLower(variableValue[0])) + { + sb.Append(char.ToLower(variableValue[0])); + + sb.Append(variableValue.AsSpan().Slice(1)); + } + else if (shouldUpper && !char.IsUpper(variableValue[0])) + { + sb.Append(char.ToUpper(variableValue[0])); + + sb.Append(variableValue.AsSpan().Slice(1)); + } + else + { + sb.Append(variableValue); + } + } + + span = span.Slice(indexOfEnd + 1); + } + + sb.Append(span); + + return (sb.ToString(), false); + } + + return (translation, false); + } + + private string? GetCore(CultureInfo culture, string key) + { + var translation = resourceManager.GetString(key, culture); + + if (translation == null) + { +#if DEBUG + lock (LockObject) + { + if (!missingTranslations.Add(key)) + { + File.AppendAllLines(MissingFileName, new string[] { key }); + } + } +#endif + } + + return translation; + } + } +} diff --git a/backend/src/Squidex.Infrastructure/Translations/T.cs b/backend/src/Squidex.Infrastructure/Translations/T.cs new file mode 100644 index 000000000..5da6cf894 --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Translations/T.cs @@ -0,0 +1,40 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Globalization; + +namespace Squidex.Infrastructure.Translations +{ + public static class T + { + private static ILocalizer? localizer; + + public static void Setup(ILocalizer newLocalizer) + { + localizer = newLocalizer; + } + + public static string Get(string key, object? args = null) + { + return Get(key, key, args); + } + + public static string Get(string key, string fallback, object? args = null) + { + Guard.NotNullOrEmpty(key, nameof(key)); + + if (localizer == null) + { + return key; + } + + var (result, _) = localizer.Get(CultureInfo.CurrentUICulture, key, fallback, args); + + return result; + } + } +} diff --git a/backend/src/Squidex.Infrastructure/Validation/AbsoluteUrlAttribute.cs b/backend/src/Squidex.Infrastructure/Validation/AbsoluteUrlAttribute.cs index fa99cd730..a00f74188 100644 --- a/backend/src/Squidex.Infrastructure/Validation/AbsoluteUrlAttribute.cs +++ b/backend/src/Squidex.Infrastructure/Validation/AbsoluteUrlAttribute.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -7,14 +7,17 @@ using System; using System.ComponentModel.DataAnnotations; +using Squidex.Infrastructure.Translations; namespace Squidex.Infrastructure.Validation { public sealed class AbsoluteUrlAttribute : ValidationAttribute { - public AbsoluteUrlAttribute() - : base(() => "The {0} field must be an absolute URL.") + public override string FormatErrorMessage(string name) { + var property = T.Get($"common.{name.ToCamelCase()}", name); + + return T.Get("annotations_absoluteUrl", new { property }); } public override bool IsValid(object value) diff --git a/backend/src/Squidex.Infrastructure/Validation/Not.cs b/backend/src/Squidex.Infrastructure/Validation/Not.cs index a24767ca0..e861eddf9 100644 --- a/backend/src/Squidex.Infrastructure/Validation/Not.cs +++ b/backend/src/Squidex.Infrastructure/Validation/Not.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -6,89 +6,105 @@ // ========================================================================== using System.Runtime.CompilerServices; +using Squidex.Infrastructure.Translations; namespace Squidex.Infrastructure.Validation { public static class Not { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Defined(string property) + public static string Defined() { - return $"{Upper(property)} is required."; + return T.Get("validation.requiredValue"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Defined2(string property1, string property2) + public static string Defined(string propertyName) { - return $"If {Lower(property1)} or {Lower(property2)} is used both must be defined."; - } + var property = T.Get($"common.{propertyName.ToCamelCase()}", propertyName); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ValidSlug(string property) - { - return $"{Upper(property)} is not a valid slug."; + return T.Get("validation.required", new { property }); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ValidPropertyName(string property) + public static string BothDefined(string propertyName1, string propertyName2) { - return $"{Upper(property)} is not a Javascript property name."; + var property1 = T.Get($"common.{propertyName1.ToCamelCase()}", propertyName1); + var property2 = T.Get($"common.{propertyName2.ToCamelCase()}", propertyName2); + + return T.Get("validation.requiredBoth", new { property1, property2 }); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GreaterThan(string property, string other) + public static string ValidSlug(string propertyName) { - return $"{Upper(property)} must be greater than {Lower(other)}."; + var property = T.Get($"common.{propertyName.ToCamelCase()}", propertyName); + + return T.Get("validation.slug", new { property }); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GreaterEquals(string property, string other) + public static string ValidJavascriptName(string propertyName) { - return $"{Upper(property)} must be greater or equal to {Lower(other)}."; + var property = T.Get($"common.{propertyName.ToCamelCase()}", propertyName); + + return T.Get("validation.javascriptProperty", new { property }); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string LessThan(string property, string other) + public static string GreaterThan(string propertyName, string otherName) { - return $"{Upper(property)} must be less than {Lower(other)}."; + var property = T.Get($"common.{propertyName.ToCamelCase()}", propertyName); + + var other = T.Get($"common.{otherName.ToCamelCase()}", otherName); + + return T.Get("validation.greaterThan", new { property, other }); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string LessEquals(string property, string other) + public static string GreaterEqualsThan(string propertyName, string otherName) { - return $"{Upper(property)} must be less or equal to {Lower(other)}."; + var property = T.Get($"common.{propertyName.ToCamelCase()}", propertyName); + + var other = T.Get($"common.{otherName.ToCamelCase()}", otherName); + + return T.Get("validation.greaterEqualsThan", new { property, other }); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Between(string property, T min, T max) + public static string LessThan(string propertyName, string otherName) { - return $"{Upper(property)} must be between {min} and {max}."; + var property = T.Get($"common.{propertyName.ToCamelCase()}", propertyName); + + var other = T.Get($"common.{otherName.ToCamelCase()}", otherName); + + return T.Get("validation.lessThan", new { property, other }); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Valid(string property) + public static string LessEqualsThan(string propertyName, string otherName) { - return $"{Upper(property)} is not a valid value."; + var property = T.Get($"common.{propertyName.ToCamelCase()}", propertyName); + + var other = T.Get($"common.{otherName.ToCamelCase()}", otherName); + + return T.Get("validation.lessEqualsThan", new { property, other }); } - private static string Lower(string property) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Between(string propertyName, TField min, TField max) { - if (char.IsUpper(property[0])) - { - return char.ToLower(property[0]) + property.Substring(1); - } + var property = T.Get($"common.{propertyName.ToCamelCase()}", propertyName); - return property; + return T.Get("validation.between", new { property, min, max }); } - private static string Upper(string property) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Valid(string propertyName) { - if (char.IsLower(property[0])) - { - return char.ToUpper(property[0]) + property.Substring(1); - } + var property = T.Get($"common.{propertyName.ToCamelCase()}", propertyName); - return property; + return T.Get("validation.valid", new { property }); } } } diff --git a/backend/src/Squidex.Infrastructure/Validation/Validate.cs b/backend/src/Squidex.Infrastructure/Validation/Validate.cs index 95e3a8db5..28bbf4d29 100644 --- a/backend/src/Squidex.Infrastructure/Validation/Validate.cs +++ b/backend/src/Squidex.Infrastructure/Validation/Validate.cs @@ -13,7 +13,7 @@ namespace Squidex.Infrastructure.Validation { public static class Validate { - public static void It(Func message, Action action) + public static void It(Action action) { List? errors = null; @@ -31,11 +31,11 @@ namespace Squidex.Infrastructure.Validation if (errors != null) { - throw new ValidationException(message(), errors); + throw new ValidationException(errors); } } - public static async Task It(Func message, Func action) + public static async Task It(Func action) { List? errors = null; @@ -53,7 +53,7 @@ namespace Squidex.Infrastructure.Validation if (errors != null) { - throw new ValidationException(message(), errors); + throw new ValidationException(errors); } } } diff --git a/backend/src/Squidex.Infrastructure/Validation/ValidationException.cs b/backend/src/Squidex.Infrastructure/Validation/ValidationException.cs index 27edc3de9..c97024271 100644 --- a/backend/src/Squidex.Infrastructure/Validation/ValidationException.cs +++ b/backend/src/Squidex.Infrastructure/Validation/ValidationException.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; using System.Text; @@ -16,90 +15,62 @@ namespace Squidex.Infrastructure.Validation [Serializable] public class ValidationException : DomainException { - private static readonly char[] TrimChars = { ' ', '.', ':' }; - private static readonly List FallbackErrors = new List(); - private readonly IReadOnlyList errors; + public IReadOnlyList Errors { get; } - public IReadOnlyList Errors + public ValidationException(string error, Exception? inner = null) + : this(new ValidationError(error), inner) { - get { return errors ?? FallbackErrors; } } - public string Summary { get; } - - public ValidationException(string summary, params ValidationError[]? errors) - : this(summary, errors?.ToList()) - { - } - - public ValidationException(string summary, IReadOnlyList? errors) - : this(summary, null, errors) + public ValidationException(ValidationError error, Exception? inner = null) + : this(new List { error }, inner) { } - public ValidationException(string summary, Exception? inner, params ValidationError[]? errors) - : this(summary, inner, errors?.ToList()) + public ValidationException(IReadOnlyList errors, Exception? inner = null) + : base(FormatMessage(errors), inner) { - } - - public ValidationException(string summary, Exception? inner, IReadOnlyList? errors) - : base(FormatMessage(summary, errors), inner!) - { - Summary = summary; - - this.errors = errors ?? FallbackErrors; + Errors = errors; } protected ValidationException(SerializationInfo info, StreamingContext context) : base(info, context) { - Summary = info.GetString(nameof(Summary))!; - - errors = (List)info.GetValue(nameof(errors), typeof(List))!; + Errors = (List)info.GetValue(nameof(Errors), typeof(List))!; } public override void GetObjectData(SerializationInfo info, StreamingContext context) { - info.AddValue(nameof(Summary), Summary); - info.AddValue(nameof(errors), errors.ToList()); + info.AddValue(nameof(Errors), Errors); base.GetObjectData(info, context); } - private static string FormatMessage(string summary, IReadOnlyList? errors) + private static string FormatMessage(IReadOnlyList errors) { - var sb = new StringBuilder(); + Guard.NotNull(errors, nameof(errors)); - sb.Append(summary.TrimEnd(TrimChars)); + var sb = new StringBuilder(); - if (errors?.Count > 0) + for (var i = 0; i < errors.Count; i++) { - sb.Append(": "); + var error = errors[i]?.Message; - for (var i = 0; i < errors.Count; i++) + if (!string.IsNullOrWhiteSpace(error)) { - var error = errors[i]?.Message; + sb.Append(error); - if (!string.IsNullOrWhiteSpace(error)) + if (!error.EndsWith(".", StringComparison.OrdinalIgnoreCase)) { - sb.Append(error); - - if (!error.EndsWith(".", StringComparison.OrdinalIgnoreCase)) - { - sb.Append("."); - } + sb.Append("."); + } - if (i < errors.Count - 1) - { - sb.Append(" "); - } + if (i < errors.Count - 1) + { + sb.Append(" "); } } } - else - { - sb.Append("."); - } return sb.ToString(); } diff --git a/backend/src/Squidex.Infrastructure/Validation/ValidationExtensions.cs b/backend/src/Squidex.Infrastructure/Validation/ValidationExtensions.cs index 5d6967395..d22f7336b 100644 --- a/backend/src/Squidex.Infrastructure/Validation/ValidationExtensions.cs +++ b/backend/src/Squidex.Infrastructure/Validation/ValidationExtensions.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; #pragma warning disable RECS0026 // Possible unassigned object created by 'new' @@ -46,17 +45,5 @@ namespace Squidex.Infrastructure.Validation return false; } } - - public static void Validate(this IValidatable target, Func message) - { - var errors = new List(); - - target.Validate(errors); - - if (errors.Any()) - { - throw new ValidationException(message(), errors); - } - } } } diff --git a/backend/src/Squidex.Shared/Squidex.Shared.csproj b/backend/src/Squidex.Shared/Squidex.Shared.csproj index eee0ea477..f0bec00de 100644 --- a/backend/src/Squidex.Shared/Squidex.Shared.csproj +++ b/backend/src/Squidex.Shared/Squidex.Shared.csproj @@ -22,4 +22,9 @@ + + + + + \ No newline at end of file diff --git a/backend/src/Squidex.Shared/Texts.cs b/backend/src/Squidex.Shared/Texts.cs new file mode 100644 index 000000000..da5f068c4 --- /dev/null +++ b/backend/src/Squidex.Shared/Texts.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Resources; + +namespace Squidex.Shared +{ + public static class Texts + { + private static ResourceManager resourceManager; + + public static ResourceManager ResourceManager + { + get + { + if (resourceManager == null) + { + resourceManager = new ResourceManager("Squidex.Shared.Texts", typeof(Texts).Assembly); + } + + return resourceManager; + } + } + } +} diff --git a/backend/src/Squidex.Shared/Texts.resx b/backend/src/Squidex.Shared/Texts.resx new file mode 100644 index 000000000..845ecd193 --- /dev/null +++ b/backend/src/Squidex.Shared/Texts.resx @@ -0,0 +1,1030 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The field {name|lower} must be an absolute URL. + + + The field {name|lower} must be the same as {other|lower}. + + + The field {name|lower} is required. + + + App has already been archived. + + + A client with the same id already exists. + + + You cannot change your own role. + + + You have reached the maximum number of contributors for your plan. + + + Cannot remove the only owner. + + + App does not have fallback language '{fallback}'. + + + Language has already been added. + + + Master language cannot have fallback languages. + + + Master language cannot be made optional. + + + Master language cannot be removed. + + + An app with the same name already exists. + + + File is not an image + + + A pattern with the same name already exists. + + + This pattern already exists but with another name. + + + A plan with this id does not exist. + + + Plan can only changed from the user who configured the plan initially. + + + Cannot delete a default role. + + + Cannot update a default role. + + + A role with the same name already exists. + + + Cannot remove a role when a client is assigned. + + + Cannot remove a role when a contributor is assigned. + + + Workflow must have a published step. + + + Transition has an invalid target. + + + The field {0} must be an absolute URL. + + + The field {0} must be the same as {1}. + + + The field {0} is required. + + + Asset has already been deleted + + + Asset folder has already been deleted + + + Asset folder does not exist. + + + Cannot add folder to its own child. + + + You have reached your max asset size. + + + Another backup process is already running. + + + You cannot have more than {max} backups. + + + A restore operation is already running. + + + You can only access your notifications. + + + Comment is created by another user. + + + Action + + + Aspect height + + + Aspect width + + + Calculated default value + + + Client ID + + + Client ID + + + Client Secret + + + Contributor ID or email + + + Data + + + Default value + + + Display name + + + Editor + + + Email + + + Field + + + Field IDs + + + File + + + Folder name + + + Query search clause not supported. + + + File content-type is not defined. + + + File name is not defined. + + + The model is not valid. + + + Request body has an invalid format. + + + Not allowed for clients. + + + Validation error + + + Initial step + + + Failed to execute script with Javascript error: {message} + + + Script has forbidden the operation. + + + Failed to execute script with Javascript syntax error: {message} + + + Script rejected the operation. + + + Language code + + + Login + + + Logout + + + Max height + + + Max items + + + Max length + + + Max size + + + Max value + + + Max width + + + Min height + + + Min items + + + Min length + + + Min size + + + Min value + + + Min width + + + Name + + + - not found - + + + Num days + + + Failed to parse query: {message} + + + OData $filter clause not valid: {ex.Message} + + + OData operation is not supported. + + + OData $search clause not valid: {ex.Message} + + + Old password + + + Other + + + Partitioning + + + Password + + + Confirm + + + Pattern + + + Permissions + + + Plan ID + + + Preview URLs + + + Squidex Headless CMS + + + Properties + + + Property + + + Application is in readonly mode at the moment. + + + Contains invalid reference '{id}'. + + + Contains reference '{id}' to invalid schema. + + + Remove + + + Result set is too large to be retrieved. Use $take parameter to reduce the number of items. + + + Role + + + Save + + + Schema ID + + + Signup + + + Text + + + Trigger + + + Workflow + + + Step + + + Transition + + + Content has already been deleted. + + + More than one content matches to the query. + + + You can only create a new version when the content is published. + + + There is nothing to delete. + + + Invalid json type, expected array of guid strings. + + + Invalid json type, expected array of objects. + + + Invalid json type, expected array of strings. + + + Invalid json type, expected boolean. + + + Invalid json type, expected latitude/longitude object. + + + Latitude must be between -90 and 90. + + + Longitude must be between -180 and 180. + + + Geolocation can only have latitude and longitude property. + + + Invalid json type, expected number. + + + Invalid json type, expected string. + + + {count} Reference(s) + + + Singleton content cannot be updated. + + + Singleton content cannot be created. + + + Singleton content cannot be deleted. + + + Due time must be in the future. + + + Cannot change status from {oldStatus} to {newStatus}. + + + Must have aspect ratio {width}:{height}. + + + Id {id} not found. + + + Must be between {min} and {max}. + + + Must have exactly {count} character(s). + + + Must have between {min} and {max} character(s). + + + Must not contain duplicate values. + + + Must be exactly {value}. + + + Must be an allowed extension. + + + Not an image. + + + Not a valid value. + + + Must have exactly {count} item(s). + + + Must have between {min} and {max} item(s). + + + Must be less or equal to {max}. + + + Height {height}px must be less than {max}px. + + + Size of {size} must be less than {max}. + + + Width {width}px must be less than {max}px. + + + Must not have more than {max} item(s). + + + Must not have more than {max} character(s). + + + Must be greater or equal to {min}. + + + Height {height}px must be greater than {min}px. + + + Size of {size} must be greater than {min}. + + + Width {width}px must be greater than {min}px. + + + Must have at least {min} item(s). + + + Must have at least {min} character(s). + + + Value must not be defined. + + + Not an allowed value. + + + Must follow the pattern. + + + Regex is too slow. + + + Field is required. + + + Another content with the same value exists. + + + Not a known {fieldType}. + + + Content workflow prevents publishing. + + + The workflow does not allow updates at status {status} + + + Json query not valid: {message} + + + Json query not valid json: {message} + + + Entity ({id}) has been deleted. + + + Entity ({id}) does not exist. + + + Entity ({id}) requested version {expectedVersion}, but found {currentVersion}. + + + added client {[Id]} to app + + + revoked client {[Id]} + + + updated client {[Id]} + + + assigned {user:[Contributor]} as {[Role]} + + + removed {user:[Contributor]} from app + + + added language {[Language]} + + + removed language {[Language]} + + + changed master language to {[Language]} + + + updated language {[Language]} + + + added pattern {[Name]} + + + deleted pattern {[PatternId]} + + + updated pattern {[Name]} + + + changed plan to {[Plan]} + + + resetted plan + + + added role {[Name]} + + + deleted role {[Name]} + + + updated role {[Name]} + + + replaced asset. + + + updated asset. + + + uploaded asset. + + + created {[Schema]} content. + + + deleted {[Schema]} content. + + + created new draft. + + + deleted draft. + + + scheduled to change status of {[Schemra]} content to {[Status]}. + + + failed to schedule status change for {[Schema]} content. + + + updated {[Schema]} content. + + + created schema {[Name]}. + + + deleted schema {[Name]}. + + + added field {[Field]} to schema {[Name]}. + + + deleted field {[Field]} from schema {[Name]}. + + + disabled field {[Field]} of schema {[Name]}. + + + has hidden field {[Field]} of schema {[Name]}. + + + has locked field {[Field]} of schema {[Name]}. + + + has shown field {[Field]} of schema {[Name]}. + + + reordered fields of schema {[Name]}. + + + has updated field {[Field]} of schema {[Name]}. + + + published schema {[Name]}. + + + configured script of schema {[Name]}. + + + unpublished schema {[Name]}. + + + updated schema {[Name]}. + + + changed status of {[Schema]} content to {[Status]}. + + + Your email address is set to private in Github. Please set it to public to use Github login. + + + Rule has already been deleted. + + + Another rule is already running. + + + Schema has already been deleted. + + + Calculated default value and default value cannot be used together. + + + Field '{field}' has been added twice. + + + Field cannot be an UI field. + + + Schema field is locked. + + + Field name + + + A field with the same name already exists. + + + Field is not part of the schema. + + + Field ids do not cover all fields. + + + A schema with the same name already exists. + + + You do not have permission for this schema. + + + Schema {id} does not exist. + + + Either allowed values or min and max length can be defined. + + + Inline editing is not allowed for Radio editor. + + + Only array fields can have nested fields. + + + Nested field cannot be array fields. + + + Can only resolve references when MaxItems is 1. + + + Either allowed values or min and max value can be defined. + + + Inline editing is only allowed for dropdowns, slugs and input fields. + + + Radio buttons or dropdown list need allowed values. + + + Checkboxes or dropdown list need allowed values. + + + UI field cannot be disabled. + + + UI field cannot be enabled. + + + UI field cannot be hidden. + + + UI field cannot be shown. + + + {name} Content + + + {name} Contents + + + {name} Schema + + + This password has previously appeared in a data breach and should never be used. If you have ever used it anywhere before, change it! + + + This operation is not allowed, your account might be locked. + + + Access denied + + + I agree! + + + Cookies & Analytics + + + <p> I understand and agree that Squidex uses cookies to ensure you get the best experience on our platform and to store your login status. </p><p> I understand and agree that Squidex has integrated Google Analytics (with the anonymizer function). Google Analytics is a web analytics service to gather and analyse data about the behavior of users. </p><p> I accept the <a href="{privacyUrl}" target="_blank" rel="noopener">privacy policies</a>.</p> + + + Automated E-Mails (Optional) + + + I understand and agree that Squidex sends Emails to inform me about new features, breaking changes and down times. + + + We need your consent + + + You have to give consent. + + + Personal Information + + + I understand and agree that Squidex collects the following private information that are retrieved from external authentication providers such as Google, Microsoft or Github. <ul class="personal-information"> <li> Basic personal information (first name, last name and picture) are provided to all other users so that they can add you to their working space. </li><li> At anytime you have the option to change these information to anonymize your account. </li><li> Your user account has an unique identifier and for all your changes we track, that you made these changes and provide this information to other users. </li></ul> + + + Consent + + + Operation failed + + + We are really sorry that something went wrong. + + + Error + + + An unexpected exception occurred. + + + Your account is locked, please contact the administrator. + + + Account locked + + + You cannot lock yourself. + + + + + + Enter Email + + + Email or password not correct + + + {action} with <strong>{provider}</strong> + + + Click here to login + + + Already registered? + + + Click here to signup + + + No account yet? + + + Enter Password + + + OR + + + Logged out! + + + !Please close this popup. + + + Logout + + + Login added successfully. + + + Change Password + + + Password changed successfully. + + + Use the client credentials to access the API with your profile information and permissions + + + Client + + + Confirm + + + Generate + + + Client secret generated successfully. + + + Edit Profile + + + Do not show my profile to other users + + + Logins + + + Password + + + Personal Information + + + Use custom properties for rules and scripts. + + + Properties + + + Add Property + + + Login provider removed successfully. + + + Set Password + + + Password set successfully. + + + Profile + + + Account updated successfully. + + + Account updated successfully. + + + Upload Picture + + + Picture uploaded successfully. + + + You cannot unlock yourself. + + + User is not allowed to login. + + + Cannot find user. + + + {property|upper} must be between {min} and {max}. + + + {property|upper} must be greater or equal to {other|lower}. + + + {property|upper} must be greater than {other|lower}. + + + {property|upper} is not a Javascript property name. + + + {property|upper} must be less or equal to {other|lower}. + + + {property|upper} must be less than {other|lower}. + + + Can only upload one file. + + + {property|upper} is required. + + + If {property1|lower} or {property2|lower} is used both must be defined. + + + Value must be defined. + + + {property|upper} is not a valid slug. + + + {property|upper} is not a valid value. + + + Picture is not a valid image. + + + Initial step cannot be published step. + + + Multiple workflows cover all schemas. + + + The schema '{schema}' is covered by multiple workflows. + + \ No newline at end of file diff --git a/backend/src/Squidex.Web/ApiExceptionConverter.cs b/backend/src/Squidex.Web/ApiExceptionConverter.cs index a9fadd03f..605d7633c 100644 --- a/backend/src/Squidex.Web/ApiExceptionConverter.cs +++ b/backend/src/Squidex.Web/ApiExceptionConverter.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -14,6 +14,7 @@ using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Web @@ -82,7 +83,7 @@ namespace Squidex.Web switch (exception) { case ValidationException ex: - return (CreateError(400, ex.Summary, ToDetails(ex)), true); + return (CreateError(400, T.Get("common.httpValidationError"), ToDetails(ex)), true); case DomainObjectNotFoundException _: return (CreateError(404), true); @@ -116,11 +117,43 @@ namespace Squidex.Web private static string[] ToDetails(ValidationException ex) { + static string FixPropertyName(string property) + { + property = property.Trim(); + + if (property.Length == 0) + { + return property; + } + + var prevChar = 0; + + var builder = new StringBuilder(property.Length); + + builder.Append(char.ToLower(property[0])); + + foreach (var character in property.Skip(1)) + { + if (prevChar == '.') + { + builder.Append(char.ToLower(character)); + } + else + { + builder.Append(character); + } + + prevChar = character; + } + + return builder.ToString(); + } + return ex.Errors.Select(e => { if (e.PropertyNames?.Any() == true) { - return $"{string.Join(", ", e.PropertyNames)}: {e.Message}"; + return $"{string.Join(", ", e.PropertyNames.Select(FixPropertyName))}: {e.Message}"; } else { diff --git a/backend/src/Squidex.Web/ApiExceptionFilterAttribute.cs b/backend/src/Squidex.Web/ApiExceptionFilterAttribute.cs index 63813ed55..0300af0b9 100644 --- a/backend/src/Squidex.Web/ApiExceptionFilterAttribute.cs +++ b/backend/src/Squidex.Web/ApiExceptionFilterAttribute.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -35,7 +35,7 @@ namespace Squidex.Web { var log = context.HttpContext.RequestServices.GetService(); - log.LogError(context.Exception, w => w.WriteProperty("messag", "An unexpected exception has occurred.")); + log.LogError(context.Exception, w => w.WriteProperty("message", "An unexpected exception has occurred.")); } context.Result = GetResult(error); diff --git a/backend/src/Squidex.Web/ApiModelValidationAttribute.cs b/backend/src/Squidex.Web/ApiModelValidationAttribute.cs index f3bc81b79..bebf46230 100644 --- a/backend/src/Squidex.Web/ApiModelValidationAttribute.cs +++ b/backend/src/Squidex.Web/ApiModelValidationAttribute.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -9,6 +9,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Newtonsoft.Json; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; namespace Squidex.Web @@ -34,7 +35,7 @@ namespace Squidex.Web { if (string.IsNullOrWhiteSpace(key)) { - errors.Add(new ValidationError("Request body has an invalid format.")); + errors.Add(new ValidationError(T.Get("common.httpInvalidRequestFormat"))); } else { @@ -55,7 +56,7 @@ namespace Squidex.Web if (errors.Count > 0) { - throw new ValidationException("The model is not valid.", errors); + throw new ValidationException(errors); } } } diff --git a/backend/src/Squidex.Web/FileExtensions.cs b/backend/src/Squidex.Web/FileExtensions.cs index 76e34b849..bc30d7e96 100644 --- a/backend/src/Squidex.Web/FileExtensions.cs +++ b/backend/src/Squidex.Web/FileExtensions.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -8,6 +8,7 @@ using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Http; using Squidex.Infrastructure.Assets; +using Squidex.Infrastructure.Translations; namespace Squidex.Web { @@ -17,12 +18,12 @@ namespace Squidex.Web { if (string.IsNullOrWhiteSpace(formFile.ContentType)) { - throw new ValidationException("File content-type is not defined."); + throw new ValidationException(T.Get("common.httpContentTypeNotDefined")); } if (string.IsNullOrWhiteSpace(formFile.FileName)) { - throw new ValidationException("File name is not defined."); + throw new ValidationException(T.Get("common.httpFileNameNotDefined")); } return new DelegateAssetFile(formFile.FileName, formFile.ContentType, formFile.Length, formFile.OpenReadStream); diff --git a/backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs b/backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs index 4c2d94b63..7ca0d8cad 100644 --- a/backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs +++ b/backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) diff --git a/backend/src/Squidex.Web/Services/StringLocalizer.cs b/backend/src/Squidex.Web/Services/StringLocalizer.cs new file mode 100644 index 000000000..40d4afffc --- /dev/null +++ b/backend/src/Squidex.Web/Services/StringLocalizer.cs @@ -0,0 +1,106 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.Extensions.Localization; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Translations; + +namespace Squidex.Web.Services +{ + public sealed class StringLocalizer : IStringLocalizer, IStringLocalizerFactory + { + private readonly ILocalizer translationService; + private readonly CultureInfo? culture; + + public LocalizedString this[string name] + { + get { return this[name, null!]; } + } + + public LocalizedString this[string name, params object[] arguments] + { + get + { + if (string.IsNullOrWhiteSpace(name)) + { + return new LocalizedString(name, name, false); + } + + var currentCulture = culture ?? CultureInfo.CurrentUICulture; + + TranslateProperty(name, arguments, currentCulture); + + var (result, notFound) = translationService.Get(currentCulture, $"aspnet_{name}", name); + + if (arguments != null && !notFound) + { + result = string.Format(currentCulture, result, arguments); + } + + return new LocalizedString(name, result, notFound); + } + } + + private void TranslateProperty(string name, object[] arguments, CultureInfo currentCulture) + { + if (arguments?.Length == 1 && IsValidationError(name)) + { + var key = $"common.{arguments[0].ToString()?.ToCamelCase()}"; + + var (result, notFound) = translationService.Get(currentCulture, key, name); + + if (!notFound) + { + arguments[0] = result; + } + } + } + + public StringLocalizer(ILocalizer translationService) + : this(translationService, null) + { + } + + private StringLocalizer(ILocalizer translationService, CultureInfo? culture) + { + Guard.NotNull(translationService, nameof(translationService)); + + this.translationService = translationService; + + this.culture = culture; + } + + public IEnumerable GetAllStrings(bool includeParentCultures) + { + return Enumerable.Empty(); + } + + public IStringLocalizer WithCulture(CultureInfo culture) + { + return new StringLocalizer(translationService, culture); + } + + public IStringLocalizer Create(string baseName, string location) + { + return this; + } + + public IStringLocalizer Create(Type resourceSource) + { + return this; + } + + private static bool IsValidationError(string name) + { + return name.Contains("annotations_", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs index 4595058b8..bc8daa29b 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs @@ -14,7 +14,6 @@ using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Validation; using Squidex.Shared; using Squidex.Web; @@ -152,7 +151,7 @@ namespace Squidex.Areas.Api.Controllers.Apps } catch (NotSupportedException) { - throw new ValidationException($"Language '{language}' is not valid."); + throw new DomainObjectNotFoundException(language); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 45afd435c..4bef79fd9 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -21,6 +21,7 @@ using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Security; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; using Squidex.Shared; using Squidex.Web; @@ -308,9 +309,9 @@ namespace Squidex.Areas.Api.Controllers.Apps { if (file == null || Request.Form.Files.Count != 1) { - var error = new ValidationError($"Can only upload one file, found {Request.Form.Files.Count} files."); + var error = T.Get("validation.onlyOneFile"); - throw new ValidationException("Cannot upload image.", error); + throw new ValidationException(error); } return new UploadAppImage { File = file.ToAssetFile() }; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs index 56f4714a0..30392e186 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Squidex.Infrastructure.Translations; using Squidex.Shared.Users; using Squidex.Web; @@ -14,8 +15,6 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models { public sealed class ContributorDto : Resource { - private const string NotFound = "- not found -"; - /// /// The id of the user that contributes to the app. /// @@ -55,7 +54,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models } else { - ContributorName = NotFound; + ContributorName = T.Get("common.notFoundValue"); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 6f86b3d40..a4d3f16bf 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -19,6 +19,7 @@ using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; using Squidex.Shared; using Squidex.Web; @@ -316,9 +317,9 @@ namespace Squidex.Areas.Api.Controllers.Assets { if (file == null || Request.Form.Files.Count != 1) { - var error = new ValidationError($"Can only upload one file, found {Request.Form.Files.Count} files."); + var error = T.Get("validation.onlyOneFile"); - throw new ValidationException("Cannot create asset.", error); + throw new ValidationException(error); } var (plan, _) = appPlansProvider.GetPlanForApp(App); @@ -327,9 +328,9 @@ namespace Squidex.Areas.Api.Controllers.Assets if (plan.MaxAssetSize > 0 && plan.MaxAssetSize < currentSize + file.Length) { - var error = new ValidationError("You have reached your max asset size."); + var error = new ValidationError(T.Get("assets.maxSizeReached")); - throw new ValidationException("Cannot create asset.", error); + throw new ValidationException(error); } return file.ToAssetFile(); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Comments/Notifications/UserNotificationsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Comments/Notifications/UserNotificationsController.cs index 8b55ee330..6da86b76a 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Comments/Notifications/UserNotificationsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Comments/Notifications/UserNotificationsController.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -15,6 +15,7 @@ using Squidex.Domain.Apps.Entities.Comments.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Security; +using Squidex.Infrastructure.Translations; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Comments.Notifications @@ -95,7 +96,7 @@ namespace Squidex.Areas.Api.Controllers.Comments.Notifications { if (!string.Equals(userId, User.OpenIdSubject())) { - throw new DomainForbiddenException("You can only access your notifications."); + throw new DomainForbiddenException(T.Get("comments.noPermissions")); } } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/UI/UIController.cs b/backend/src/Squidex/Areas/Api/Controllers/UI/UIController.cs index 7257f70f6..8a4b825e3 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/UI/UIController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/UI/UIController.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Security; +using Squidex.Infrastructure.Translations; using Squidex.Shared; using Squidex.Web; @@ -175,7 +176,7 @@ namespace Squidex.Areas.Api.Controllers.UI if (string.IsNullOrWhiteSpace(subject)) { - throw new DomainForbiddenException("Not allowed for clients."); + throw new DomainForbiddenException(T.Get("common.httpOnlyAsUser")); } return subject; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs b/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs index 331ff8254..76c00fd50 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -10,9 +10,10 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.Users.Models; using Squidex.Domain.Users; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Tasks; -using Squidex.Infrastructure.Validation; +using Squidex.Infrastructure.Translations; using Squidex.Shared; using Squidex.Web; @@ -104,7 +105,7 @@ namespace Squidex.Areas.Api.Controllers.Users { if (this.IsUser(id)) { - throw new ValidationException("Locking user failed.", new ValidationError("You cannot lock yourself.")); + throw new DomainForbiddenException(T.Get("users.lockYourselfError")); } var user = await userManager.LockAsync(id); @@ -122,7 +123,7 @@ namespace Squidex.Areas.Api.Controllers.Users { if (this.IsUser(id)) { - throw new ValidationException("Unlocking user failed.", new ValidationError("You cannot unlock yourself.")); + throw new DomainForbiddenException(T.Get("users.unlockYourselfError")); } var user = await userManager.UnlockAsync(id); diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs index 4812207e0..7e4f6b9a1 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -25,6 +25,7 @@ using Squidex.Domain.Users; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Security; +using Squidex.Infrastructure.Translations; using Squidex.Shared; using Squidex.Shared.Identity; using Squidex.Shared.Users; @@ -74,7 +75,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account [Route("account/forbidden/")] public IActionResult Forbidden() { - throw new DomainForbiddenException("User is not allowed to login."); + throw new DomainForbiddenException(T.Get("users.userLocked")); } [HttpGet] @@ -111,12 +112,12 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account { if (!model.ConsentToCookies) { - ModelState.AddModelError(nameof(model.ConsentToCookies), "You have to give consent."); + ModelState.AddModelError(nameof(model.ConsentToCookies), T.Get("users.consent.needed")); } if (!model.ConsentToPersonalInformation) { - ModelState.AddModelError(nameof(model.ConsentToPersonalInformation), "You have to give consent."); + ModelState.AddModelError(nameof(model.ConsentToPersonalInformation), T.Get("users.consent.needed")); } if (!ModelState.IsValid) @@ -130,7 +131,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account if (user == null) { - throw new DomainException("Cannot find user."); + throw new DomainException(T.Get("users.userNotFound")); } var update = new UserValues diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/LoginModel.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/LoginModel.cs index d37c907bc..69abd354e 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/LoginModel.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/LoginModel.cs @@ -5,16 +5,16 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.ComponentModel.DataAnnotations; +using Squidex.Infrastructure.Translations; namespace Squidex.Areas.IdentityServer.Controllers.Account { public sealed class LoginModel { - [Required] + [LocalizedRequired] public string Email { get; set; } - [Required] + [LocalizedRequired] public string Password { get; set; } } } diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ChangePasswordModel.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ChangePasswordModel.cs index 5a2a55f43..9ef52234a 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ChangePasswordModel.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ChangePasswordModel.cs @@ -1,23 +1,23 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.ComponentModel.DataAnnotations; +using Squidex.Infrastructure.Translations; namespace Squidex.Areas.IdentityServer.Controllers.Profile { public class ChangePasswordModel { - [Required(ErrorMessage = "Old Password is required.")] + [LocalizedRequired] public string OldPassword { get; set; } - [Required(ErrorMessage = "Password is required.")] + [LocalizedRequired] public string Password { get; set; } - [Compare(nameof(Password), ErrorMessage = "Passwords must be identitical.")] + [LocalizedCompare(nameof(Password))] public string PasswordConfirm { get; set; } } } diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ChangeProfileModel.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ChangeProfileModel.cs index 869323308..67bac754e 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ChangeProfileModel.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ChangeProfileModel.cs @@ -1,21 +1,21 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.ComponentModel.DataAnnotations; using Squidex.Domain.Users; +using Squidex.Infrastructure.Translations; namespace Squidex.Areas.IdentityServer.Controllers.Profile { public class ChangeProfileModel { - [Required(ErrorMessage = "Email is required.")] + [LocalizedRequired] public string Email { get; set; } - [Required(ErrorMessage = "DisplayName is required.")] + [LocalizedRequired] public string DisplayName { get; set; } public bool IsHidden { get; set; } diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs index 5dec1f7b9..4aa400bc4 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -22,6 +22,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Tasks; +using Squidex.Infrastructure.Translations; using Squidex.Shared.Identity; using Squidex.Shared.Users; @@ -81,7 +82,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile public Task AddLoginCallback() { return MakeChangeAsync(u => AddLoginAsync(u), - "Login added successfully."); + T.Get("users.profile.addLoginDone")); } [HttpPost] @@ -89,7 +90,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile public Task UpdateProfile(ChangeProfileModel model) { return MakeChangeAsync(u => UpdateAsync(u, model.ToValues()), - "Account updated successfully.", model); + T.Get("users.profile.updateProfileDone"), model); } [HttpPost] @@ -97,7 +98,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile public Task UpdateProperties(ChangePropertiesModel model) { return MakeChangeAsync(u => UpdateAsync(u, model.ToValues()), - "Account updated successfully.", model); + T.Get("users.profile.updatePropertiesDone"), model); } [HttpPost] @@ -105,7 +106,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile public Task RemoveLogin(RemoveLoginModel model) { return MakeChangeAsync(u => userManager.RemoveLoginAsync(u, model.LoginProvider, model.ProviderKey), - "Login provider removed successfully.", model); + T.Get("users.profile.removeLoginDone"), model); } [HttpPost] @@ -113,7 +114,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile public Task SetPassword(SetPasswordModel model) { return MakeChangeAsync(u => userManager.AddPasswordAsync(u, model.Password), - "Password set successfully.", model); + T.Get("users.profile.setPasswordDone"), model); } [HttpPost] @@ -121,7 +122,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile public Task ChangePassword(ChangePasswordModel model) { return MakeChangeAsync(u => userManager.ChangePasswordAsync(u, model.OldPassword, model.Password), - "Password changed successfully.", model); + T.Get("users.profile.changePasswordDone"), model); } [HttpPost] @@ -129,7 +130,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile public Task GenerateClientSecret() { return MakeChangeAsync(user => userManager.GenerateClientSecretAsync(user), - "Client secret generated successfully."); + T.Get("users.profile.generateClientDone")); } [HttpPost] @@ -137,7 +138,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile public Task UploadPicture(List file) { return MakeChangeAsync(user => UpdatePictureAsync(file, user), - "Picture uploaded successfully."); + T.Get("users.profile.uploadPictureDone")); } private async Task AddLoginAsync(IdentityUser user) @@ -168,7 +169,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile { if (file.Count != 1) { - return IdentityResult.Failed(new IdentityError { Description = "Please upload a single file." }); + return IdentityResult.Failed(new IdentityError { Description = T.Get("validation.onlyOneFile") }); } using (var thumbnailStream = new MemoryStream()) @@ -181,7 +182,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile } catch { - return IdentityResult.Failed(new IdentityError { Description = "Picture is not a valid image." }); + return IdentityResult.Failed(new IdentityError { Description = T.Get("valiodation.notAnImage") }); } await userPictureStore.UploadAsync(user.Id, thumbnailStream); @@ -190,13 +191,13 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile return await userManager.UpdateSafeAsync(user, new UserValues { PictureUrl = SquidexClaimTypes.PictureUrlStore }); } - private async Task MakeChangeAsync(Func> action, string successMessage, T? model = null) where T : class + private async Task MakeChangeAsync(Func> action, string successMessage, TModel? model = null) where TModel : class { var user = await userManager.GetUserWithClaimsAsync(User); if (user == null) { - throw new DomainException("Cannot find user."); + throw new DomainException(T.Get("users.userNotFound")); } if (!ModelState.IsValid) @@ -220,17 +221,17 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile } catch { - errorMessage = "An unexpected exception occurred."; + errorMessage = T.Get("users.errorHappened"); } return View(nameof(Profile), await GetProfileVM(user, model, errorMessage)); } - private async Task GetProfileVM(UserWithClaims? user, T? model = null, string? errorMessage = null, string? successMessage = null) where T : class + private async Task GetProfileVM(UserWithClaims? user, TModel? model = null, string? errorMessage = null, string? successMessage = null) where TModel : class { if (user == null) { - throw new DomainException("Cannot find user."); + throw new DomainException(T.Get("users.userNotFound")); } var (providers, hasPassword, logins) = await AsyncHelper.WhenAll( diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/RemoveLoginModel.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/RemoveLoginModel.cs index 081b96451..2d17d96d8 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/RemoveLoginModel.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/RemoveLoginModel.cs @@ -1,20 +1,20 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.ComponentModel.DataAnnotations; +using Squidex.Infrastructure.Translations; namespace Squidex.Areas.IdentityServer.Controllers.Profile { public class RemoveLoginModel { - [Required(ErrorMessage = "Login provider is required.")] + [LocalizedRequired] public string LoginProvider { get; set; } - [Required(ErrorMessage = "Provider key.")] + [LocalizedRequired] public string ProviderKey { get; set; } } } diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/SetPasswordModel.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/SetPasswordModel.cs index 6e8e13009..568ca4c1d 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/SetPasswordModel.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/SetPasswordModel.cs @@ -1,20 +1,20 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.ComponentModel.DataAnnotations; +using Squidex.Infrastructure.Translations; namespace Squidex.Areas.IdentityServer.Controllers.Profile { public class SetPasswordModel { - [Required(ErrorMessage = "Password is required.")] + [LocalizedRequired] public string Password { get; set; } - [Compare(nameof(Password), ErrorMessage = "Passwords must be identitical.")] + [LocalizedRequired] public string PasswordConfirm { get; set; } } } diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/UserProperty.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/UserProperty.cs index 8d5b6e80e..d1dce20a3 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/UserProperty.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/UserProperty.cs @@ -5,16 +5,16 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.ComponentModel.DataAnnotations; +using Squidex.Infrastructure.Translations; namespace Squidex.Areas.IdentityServer.Controllers.Profile { public sealed class UserProperty { - [Required(ErrorMessage = "Name is required.")] + [LocalizedRequired] public string Name { get; set; } - [Required(ErrorMessage = "Value is required.")] + [LocalizedRequired] public string Value { get; set; } public (string Name, string Value) ToTuple() diff --git a/backend/src/Squidex/Areas/IdentityServer/Views/Account/AccessDenied.cshtml b/backend/src/Squidex/Areas/IdentityServer/Views/Account/AccessDenied.cshtml index 16aef5862..07cf868e1 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Views/Account/AccessDenied.cshtml +++ b/backend/src/Squidex/Areas/IdentityServer/Views/Account/AccessDenied.cshtml @@ -1,16 +1,17 @@ @{ ViewBag.Theme = "white"; - ViewBag.Title = "Account locked"; + + ViewBag.Title = T.Get("users.accessDenied.title"); } -

Access denied

+

@T.Get("users.accessDenied.title")

- This operation is not allowed, your account might be locked. + @T.Get("users.accessDenied.text")

- Logout + @T.Get("common.logout")

\ No newline at end of file diff --git a/backend/src/Squidex/Areas/IdentityServer/Views/Account/Consent.cshtml b/backend/src/Squidex/Areas/IdentityServer/Views/Account/Consent.cshtml index 472546230..319f720f0 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Views/Account/Consent.cshtml +++ b/backend/src/Squidex/Areas/IdentityServer/Views/Account/Consent.cshtml @@ -3,7 +3,7 @@ @{ ViewBag.Class = "profile-lg"; - ViewBag.Title = "Consent"; + ViewBag.Title = T.Get("users.consent.title"); } @functions { @@ -14,19 +14,19 @@ }
-

We need your consent

+

@T.Get("users.consent.headline")