diff --git a/zero/FileStorage/IFileSystem.cs b/zero/FileStorage/IFileSystem.cs index bd8c5238..18e619bc 100644 --- a/zero/FileStorage/IFileSystem.cs +++ b/zero/FileStorage/IFileSystem.cs @@ -57,7 +57,7 @@ public interface IFileSystem /// /// Get all items within a directory /// - IAsyncEnumerable GetDirectoryContent(string path = null, bool recursive = false, CancellationToken cancellationToken = default); + IEnumerable GetDirectoryContent(string path = null, bool recursive = false, CancellationToken cancellationToken = default); /// /// Tries to create a directory at the given path. This method returns if the directory already exists. diff --git a/zero/FileStorage/PhysicalFileSystem.cs b/zero/FileStorage/PhysicalFileSystem.cs index 4248c828..3981c5ac 100644 --- a/zero/FileStorage/PhysicalFileSystem.cs +++ b/zero/FileStorage/PhysicalFileSystem.cs @@ -196,17 +196,17 @@ public class PhysicalFileSystem : IFileSystem /// - public virtual IAsyncEnumerable GetDirectoryContent(string path = null, bool recursive = false, CancellationToken cancellationToken = default) + public virtual IEnumerable GetDirectoryContent(string path = null, bool recursive = false, CancellationToken cancellationToken = default) { try { string resolvedPath = ResolvePath(path); - List results = new(); + List results = []; SearchOption searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; if (!Directory.Exists(resolvedPath)) { - return results.ToAsyncEnumerable(); + return results; } results.AddRange(Directory.GetDirectories(resolvedPath, "*", searchOption).Select(f => @@ -221,7 +221,7 @@ public class PhysicalFileSystem : IFileSystem return new PhysicalFileMeta(fileSystemInfo, ResolvePath(f.Substring(_root.Length))); })); - return results.ToAsyncEnumerable(); + return results; } catch (Exception ex) when (ex is not FileSystemException) { diff --git a/zero/Localization/ConfigurationLocalizer.cs b/zero/Localization/ConfigurationLocalizer.cs index 4fa20668..d749fc5e 100644 --- a/zero/Localization/ConfigurationLocalizer.cs +++ b/zero/Localization/ConfigurationLocalizer.cs @@ -1,32 +1,35 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; +using System.Collections.Concurrent; namespace zero.Localization; public class ConfigurationLocalizer : Localizer { - private IConfiguration _configuration; + protected ConcurrentDictionary FileCache { get; set; } = []; public ConfigurationLocalizer(IConfiguration configuration, ICultureResolver cultureResolver, IOptionsMonitor options) : base(cultureResolver) { - _configuration = configuration; + IConfigurationSection section = configuration.GetSection($"Zero:Localization:{LanguageCode}"); + + if (section != null) + { + foreach (IConfigurationSection child in section.GetChildren()) + { + FileCache.TryAdd(child.Key, new Translation() { Value = child.Value }); + } + } } protected override Translation LoadTranslation(string key) { - IConfigurationSection section = _configuration.GetSection($"Zero:Localization:{LanguageCode}:{key}"); - - if (section == null) + if (!FileCache.TryGetValue(key, out Translation translation)) { return null; } - return new() - { - Key = key, - Value = section.Value - }; + return translation; } } \ No newline at end of file diff --git a/zero/Localization/Models/Translation.cs b/zero/Localization/Models/Translation.cs index 0a635d69..3fc3849e 100644 --- a/zero/Localization/Models/Translation.cs +++ b/zero/Localization/Models/Translation.cs @@ -1,6 +1,6 @@ namespace zero.Localization; -public class Translation +public record Translation { /// /// Key of the translation diff --git a/zero/Localization/ZeroLocalizationModule.cs b/zero/Localization/ZeroLocalizationModule.cs index d6ac58aa..45d3e938 100644 --- a/zero/Localization/ZeroLocalizationModule.cs +++ b/zero/Localization/ZeroLocalizationModule.cs @@ -1,12 +1,8 @@ -using FluentValidation; -using Microsoft.AspNetCore.Mvc.DataAnnotations; -using Microsoft.AspNetCore.Mvc.Localization; +using Microsoft.AspNetCore.Mvc.Localization; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Options; namespace zero.Localization; diff --git a/zero/Mails/MailOptions.cs b/zero/Mails/MailOptions.cs index f198302b..fc54f253 100644 --- a/zero/Mails/MailOptions.cs +++ b/zero/Mails/MailOptions.cs @@ -2,9 +2,20 @@ public class MailOptions { + //public string Host { get; set; } + + //public int Port { get; set; } + + //public string Username { get; set; } + + //public string Password { get; set; } + + //public string To { get; set; } + public string SenderEmail { get; set; } public string SenderName { get; set; } + public Func BuildViewPath { get; set; } } \ No newline at end of file diff --git a/zero/Validation/German/FluentValidationGermanLanguage.cs b/zero/Validation/German/FluentValidationGermanLanguage.cs new file mode 100644 index 00000000..f83bd567 --- /dev/null +++ b/zero/Validation/German/FluentValidationGermanLanguage.cs @@ -0,0 +1,39 @@ +namespace zero.Validation; + +public class FluentValidationGermanLanguage +{ + public static Dictionary Translations = new() + { + { "EmailValidator", "Keine gültige E-Mail-Adresse" }, + { "UrlValidator", "Keine gültige URL" }, + { "HexValidator", "Keine gültige HEX Farbangabe" }, + { "GreaterThanOrEqualValidator", "Der Wert muss grösser oder gleich '{ComparisonValue}' sein." }, + { "GreaterThanValidator", "Der Wert muss grösser sein als '{ComparisonValue}'." }, + { "LengthValidator", "Die Länge muss zwischen {MinLength} und {MaxLength} Zeichen liegen. Es wurden {TotalLength} Zeichen eingetragen." }, + { "MinimumLengthValidator", "Die Länge muss größer oder gleich {MinLength} sein. Sie haben {TotalLength} Zeichen eingegeben." }, + { "MaximumLengthValidator", "Die Länge muss kleiner oder gleich {MaxLength} sein. Sie haben {TotalLength} Zeichen eingegeben." }, + { "LessThanOrEqualValidator", "Der Wert muss kleiner oder gleich '{ComparisonValue}' sein." }, + { "LessThanValidator", "Der Wert muss kleiner sein als '{ComparisonValue}'." }, + { "NotEmptyValidator", "Dieses Feld darf nicht leer sein." }, + { "NotEqualValidator", "Dieser Wert darf nicht '{ComparisonValue}' sein." }, + { "NotNullValidator", "Dieses Feld darf nicht leer sein." }, + { "PredicateValidator", "Der Wert entspricht nicht der festgelegten Bedingung." }, + { "AsyncPredicateValidator", "Der Wert entspricht nicht der festgelegten Bedingung." }, + { "RegularExpressionValidator", "Dieses Feld weist ein ungültiges Format auf." }, + { "EqualValidator", "Der Wert muss gleich '{ComparisonValue}' sein." }, + { "ExactLengthValidator", "Dieses Feld muss genau {MaxLength} lang sein. Es wurden {TotalLength} eingegeben." }, + { "ExclusiveBetweenValidator", "Der Wert muss zwischen {From} und {To} sein (exklusiv). Es wurde {PropertyValue} eingegeben." }, + { "InclusiveBetweenValidator", "Der Wert muss zwischen {From} and {To} sein. Es wurde {PropertyValue} eingegeben." }, + { "CreditCardValidator", "Keine gültige Kreditkartennummer." }, + { "ScalePrecisionValidator", "Das Feld darf insgesamt nicht mehr als {ExpectedPrecision} Ziffern enthalten, mit Berücksichtigung von {ExpectedScale} Dezimalstellen. Es wurden {Digits} Ziffern und {ActualScale} Dezimalstellen gefunden." }, + { "EmptyValidator", "Dieses Feld sollte leer sein." }, + { "NullValidator", "Dieses Feld sollte leer sein." }, + { "EnumValidator", "Dieses Feld hat einen Wertebereich, der '{PropertyValue}' nicht enthält." }, + // Additional fallback messages used by clientside validation integration. + { "Length_Simple", "Die Länge muss zwischen {MinLength} und {MaxLength} Zeichen liegen." }, + { "MinimumLength_Simple", "Die Länge muss größer oder gleich {MinLength} sein." }, + { "MaximumLength_Simple", "Die Länge muss kleiner oder gleich {MaxLength} sein." }, + { "ExactLength_Simple", "Dieses Feld muss genau {MaxLength} lang sein." }, + { "InclusiveBetween_Simple", "Der Wert muss zwischen {From} and {To} sein." } + }; +} \ No newline at end of file diff --git a/zero/Validation/German/GermanIdentityErrorDescriber.cs b/zero/Validation/German/GermanIdentityErrorDescriber.cs new file mode 100644 index 00000000..1cf6a8da --- /dev/null +++ b/zero/Validation/German/GermanIdentityErrorDescriber.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Identity; + +namespace zero.Validation; + +public class GermanIdentityErrorDescriber : IdentityErrorDescriber +{ + public override IdentityError DefaultError() { return new IdentityError { Code = nameof(DefaultError), Description = $"Ein unbekannter Fehler ist aufgetreten" }; } + public override IdentityError ConcurrencyFailure() { return new IdentityError { Code = nameof(ConcurrencyFailure), Description = "Ein unbekannter Fehler ist aufgetreten" }; } + public override IdentityError PasswordMismatch() { return new IdentityError { Code = nameof(PasswordMismatch), Description = "Die Passwörter stimmen nicht überein" }; } + public override IdentityError InvalidToken() { return new IdentityError { Code = nameof(InvalidToken), Description = "Der Token ist ungültig" }; } + public override IdentityError LoginAlreadyAssociated() { return new IdentityError { Code = nameof(LoginAlreadyAssociated), Description = "Ein Benutzer mit diesem Login existiert bereits" }; } + public override IdentityError InvalidUserName(string userName) { return new IdentityError { Code = nameof(InvalidUserName), Description = $"Der Username '{userName}' ist ungültig (nur Buchstaben und Zahlen gültig)" }; } + public override IdentityError InvalidEmail(string email) { return new IdentityError { Code = nameof(InvalidEmail), Description = $"Die Email-Adresse '{email}' ist ungültig" }; } + public override IdentityError DuplicateUserName(string userName) { return new IdentityError { Code = nameof(DuplicateUserName), Description = $"Der Username '{userName}' ist bereits vergeben" }; } + public override IdentityError DuplicateEmail(string email) { return new IdentityError { Code = nameof(DuplicateEmail), Description = $"Die Email-Adresse '{email}' ist bereits vergeben" }; } + //public override IdentityError InvalidRoleName(string role) { return new IdentityError { Code = nameof(InvalidRoleName), Description = $"Role name '{role}' is invalid." }; } + //public override IdentityError DuplicateRoleName(string role) { return new IdentityError { Code = nameof(DuplicateRoleName), Description = $"Role name '{role}' is already taken." }; } + //public override IdentityError UserAlreadyHasPassword() { return new IdentityError { Code = nameof(UserAlreadyHasPassword), Description = "User already has a password set." }; } + //public override IdentityError UserLockoutNotEnabled() { return new IdentityError { Code = nameof(UserLockoutNotEnabled), Description = "Lockout is not enabled for this user." }; } + //public override IdentityError UserAlreadyInRole(string role) { return new IdentityError { Code = nameof(UserAlreadyInRole), Description = $"User already in role '{role}'." }; } + //public override IdentityError UserNotInRole(string role) { return new IdentityError { Code = nameof(UserNotInRole), Description = $"User is not in role '{role}'." }; } + public override IdentityError PasswordTooShort(int length) { return new IdentityError { Code = nameof(PasswordTooShort), Description = $"Das Passwort muss mindestens {length} Zeichen lang sein" }; } + public override IdentityError PasswordRequiresNonAlphanumeric() { return new IdentityError { Code = nameof(PasswordRequiresNonAlphanumeric), Description = "Passwörter müssen zumindest ein alphanumerisches Zeichen (a-z, 0-9) enthalten" }; } + public override IdentityError PasswordRequiresDigit() { return new IdentityError { Code = nameof(PasswordRequiresDigit), Description = "Passwörter müssen zumindest eine Zahl enthalten ('0'-'9')" }; } + public override IdentityError PasswordRequiresLower() { return new IdentityError { Code = nameof(PasswordRequiresLower), Description = "Passwörter müssen zumindest einen Kleinbuchstaben enthalten ('a'-'z')" }; } + public override IdentityError PasswordRequiresUpper() { return new IdentityError { Code = nameof(PasswordRequiresUpper), Description = "Passwörter müssen zumindest einen Großbuchstaben enthalten ('A'-'Z')" }; } + public override IdentityError PasswordRequiresUniqueChars(int uniqueChars) { return new IdentityError { Code = nameof(PasswordRequiresUpper), Description = $"Passwörter müssen zumindest {uniqueChars} verschiedene Zeichen enthalten" }; } + //public override IdentityError RecoveryCodeRedemptionFailed() { return new IdentityError { Code = nameof(PasswordRequiresUpper), Description = "Passwords must have at least one uppercase ('A'-'Z')." }; } +} \ No newline at end of file diff --git a/zero/Validation/German/ServiceCollectionExtensions.cs b/zero/Validation/German/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..cf4c3208 --- /dev/null +++ b/zero/Validation/German/ServiceCollectionExtensions.cs @@ -0,0 +1,30 @@ +using FluentValidation; +using FluentValidation.Resources; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace zero.Validation; + +public static class ServiceCollectionExtensions +{ + public static void AddGermanValidation(this IServiceCollection services) + { + ValidatorOptions.Global.LanguageManager.AddGermanOverrides(); + services.Replace(ServiceLifetime.Scoped); + } + + + static ILanguageManager AddGermanOverrides(this ILanguageManager manager) + { + if (manager is LanguageManager) + { + var lmanager = manager as LanguageManager; + + foreach (var kvp in FluentValidationGermanLanguage.Translations) + { + lmanager.AddTranslation("de", kvp.Key, kvp.Value); + } + } + return manager; + } +} \ No newline at end of file diff --git a/zero/zero.csproj b/zero/zero.csproj index c043943b..f1ed52b4 100644 --- a/zero/zero.csproj +++ b/zero/zero.csproj @@ -18,7 +18,6 @@ - \ No newline at end of file