diff --git a/sample/Directory.Packages.props b/sample/Directory.Packages.props
index 5204db763..ebfb96c1a 100644
--- a/sample/Directory.Packages.props
+++ b/sample/Directory.Packages.props
@@ -8,6 +8,7 @@
+
@@ -28,6 +29,7 @@
+
diff --git a/sample/src/NimblePros.SampleToDo.Core/Localization.cs b/sample/src/NimblePros.SampleToDo.Core/Localization.cs
new file mode 100644
index 000000000..33839718d
--- /dev/null
+++ b/sample/src/NimblePros.SampleToDo.Core/Localization.cs
@@ -0,0 +1,27 @@
+using Microsoft.Extensions.Localization;
+
+///
+/// Exposes the current to static code
+/// (Vogen Validate, domain events, etc.) without pulling the whole
+/// DI container into Core.
+///
+public static class Localization
+{
+ ///
+ /// Set by Program.cs during app startup.
+ ///
+ public static ILocalizationContext? Current { get; set; }
+
+ public sealed class LocalizationContext : ILocalizationContext
+ {
+ public IStringLocalizer Localizer { get; }
+
+ public LocalizationContext(IStringLocalizer localizer) =>
+ Localizer = localizer;
+ }
+}
+
+public interface ILocalizationContext
+{
+ IStringLocalizer Localizer { get; }
+}
diff --git a/sample/src/NimblePros.SampleToDo.Core/NimblePros.SampleToDo.Core.csproj b/sample/src/NimblePros.SampleToDo.Core/NimblePros.SampleToDo.Core.csproj
index 3eb6be895..584d46756 100644
--- a/sample/src/NimblePros.SampleToDo.Core/NimblePros.SampleToDo.Core.csproj
+++ b/sample/src/NimblePros.SampleToDo.Core/NimblePros.SampleToDo.Core.csproj
@@ -12,6 +12,7 @@
+
diff --git a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/Project.cs b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/Project.cs
index ccf4c6939..1884fdf55 100644
--- a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/Project.cs
+++ b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/Project.cs
@@ -1,4 +1,5 @@
-using NimblePros.SampleToDo.Core.ProjectAggregate.Events;
+using NimblePros.SampleToDo.Core.ContributorAggregate.Events;
+using NimblePros.SampleToDo.Core.ProjectAggregate.Events;
namespace NimblePros.SampleToDo.Core.ProjectAggregate;
@@ -27,6 +28,7 @@ public Project AddItem(ToDoItem newItem)
public Project UpdateName(ProjectName newName)
{
+ if (Name.Equals(newName)) return this;
Name = newName;
return this;
}
diff --git a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ProjectErrorMessages.cs b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ProjectErrorMessages.cs
new file mode 100644
index 000000000..002033427
--- /dev/null
+++ b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ProjectErrorMessages.cs
@@ -0,0 +1,53 @@
+// Core/ProjectAggregate/ProjectMessages.cs
+namespace NimblePros.SampleToDo.Core.ProjectAggregate;
+
+///
+/// Strongly-typed, localisable message accessors for the Project aggregate.
+/// The implementation is deliberately **static** so it can be called from Vogen’s
+/// static Validate method.
+///
+public static class ProjectErrorMessages
+{
+ // -----------------------------------------------------------------
+ // 1. Public entry points – these are what you call from the domain
+ // -----------------------------------------------------------------
+ public static string CoreProjectNameEmpty => Get(nameof(CoreProjectNameEmpty));
+ public static string CoreProjectNameTooLong(int maxLength) => Get(nameof(CoreProjectNameTooLong), maxLength);
+ public static string CoreToDoItemDescriptionEmpty => Get(nameof(CoreToDoItemDescriptionEmpty));
+ public static string CoreToDoItemDescriptionTooLong(int maxLength) => Get(nameof(CoreToDoItemDescriptionTooLong), maxLength);
+ public static string CoreToDoItemTitleEmpty => Get(nameof(CoreToDoItemTitleEmpty));
+ public static string CoreToDoItemTitleTooLong(int maxLength) => Get(nameof(CoreToDoItemTitleTooLong), maxLength);
+
+ // -----------------------------------------------------------------
+ // 2. Private helper that forwards to the current localizer
+ // -----------------------------------------------------------------
+ private static string Get(string key, params object[] args)
+ {
+ // The static holder is set once in Program.cs (Web project)
+ var localizer = Localization.Current?.Localizer;
+
+ if (localizer is not null)
+ {
+ // Uses the standard {0}, {1}… placeholders defined in JSON/RESX
+ var localized = localizer[key, args];
+ return localized.ResourceNotFound ? Fallback(key, args) : localized;
+ }
+
+ // No DI container available (e.g. unit-tests) → fallback to English
+ return Fallback(key, args);
+ }
+
+ // -----------------------------------------------------------------
+ // 3. Hard-coded English fallback (never throws, always returns a string)
+ // -----------------------------------------------------------------
+ private static string Fallback(string key, object[] args) => key switch
+ {
+ nameof(CoreProjectNameEmpty) => "Name cannot be empty",
+ nameof(CoreProjectNameTooLong) => FormattableString.Invariant($"Name cannot be longer than {args[0]} characters"),
+ nameof(CoreToDoItemDescriptionEmpty) => "Description cannot be empty",
+ nameof(CoreToDoItemDescriptionTooLong) => FormattableString.Invariant($"Description cannot be longer than {args[0]} characters"),
+ nameof(CoreToDoItemTitleEmpty) => "Title cannot be empty",
+ nameof(CoreToDoItemTitleTooLong) => FormattableString.Invariant($"Title cannot be longer than {args[0]} characters"),
+ _ => $"[{key}]"
+ };
+}
diff --git a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ProjectName.cs b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ProjectName.cs
index ab7df0550..b15a564e4 100644
--- a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ProjectName.cs
+++ b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ProjectName.cs
@@ -13,8 +13,8 @@ public partial struct ProjectName
public const int MaxLength = 100;
private static Validation Validate(in string name) =>
string.IsNullOrEmpty(name)
- ? Validation.Invalid("Name cannot be empty")
+ ? Validation.Invalid(ProjectErrorMessages.CoreProjectNameEmpty)
: name.Length > MaxLength
- ? Validation.Invalid($"Name cannot be longer than {MaxLength} characters")
+ ? Validation.Invalid(ProjectErrorMessages.CoreProjectNameTooLong(MaxLength))
: Validation.Ok;
}
diff --git a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/Specifications/IncompleteItemsSearchSpec.cs b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/Specifications/IncompleteItemsSearchSpec.cs
index 36185587b..3fc78916e 100644
--- a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/Specifications/IncompleteItemsSearchSpec.cs
+++ b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/Specifications/IncompleteItemsSearchSpec.cs
@@ -6,7 +6,7 @@ public IncompleteItemsSearchSpec(string searchString)
{
Query
.Where(item => !item.IsDone &&
- (item.Title.Contains(searchString) ||
- item.Description.Contains(searchString)));
+ (item.Title.Value.Contains(searchString) ||
+ item.Description.Value.Contains(searchString)));
}
}
diff --git a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItem.cs b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItem.cs
index 03dc54b87..e21fe7f29 100644
--- a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItem.cs
+++ b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItem.cs
@@ -1,4 +1,5 @@
-using NimblePros.SampleToDo.Core.ContributorAggregate;
+using System.Xml.Linq;
+using NimblePros.SampleToDo.Core.ContributorAggregate;
using NimblePros.SampleToDo.Core.ProjectAggregate.Events;
namespace NimblePros.SampleToDo.Core.ProjectAggregate;
@@ -8,14 +9,19 @@ public class ToDoItem : EntityBase
public ToDoItem() : this(Priority.Backlog)
{
}
+ public ToDoItem(ToDoItemTitle title, ToDoItemDescription description) : this(Priority.Backlog)
+ {
+ Title = title;
+ Description = description;
+ }
public ToDoItem(Priority priority)
{
Priority = priority;
}
- public string Title { get; set; } = string.Empty; // TODO: Use Value Object
- public string Description { get; set; } = string.Empty; // TODO: Use Value Object
+ public ToDoItemTitle Title { get; private set; }
+ public ToDoItemDescription Description { get; private set; }
public ContributorId? ContributorId { get; private set; } // tasks don't have anyone assigned when first created
public bool IsDone { get; private set; }
@@ -48,9 +54,23 @@ public ToDoItem RemoveContributor()
return this;
}
+ public ToDoItem UpdateTitle(ToDoItemTitle newTitle)
+ {
+ if (Title.Equals(newTitle)) return this;
+ Title = newTitle;
+ return this;
+ }
+
+ public ToDoItem UpdateDescription(ToDoItemDescription newDescription)
+ {
+ if (Description.Equals(newDescription)) return this;
+ Description = newDescription;
+ return this;
+ }
+
public override string ToString()
{
string status = IsDone ? "Done!" : "Not done.";
- return $"{Id}: Status: {status} - {Title} - Priority: {Priority}";
+ return $"{Id}: Status: {status} - {Title.Value} - Priority: {Priority}";
}
}
diff --git a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItemDescription.cs b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItemDescription.cs
new file mode 100644
index 000000000..4460cdd7e
--- /dev/null
+++ b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItemDescription.cs
@@ -0,0 +1,15 @@
+using Vogen;
+
+namespace NimblePros.SampleToDo.Core.ProjectAggregate;
+
+[ValueObject(conversions: Conversions.SystemTextJson)]
+public partial struct ToDoItemDescription
+{
+ public const int MaxLength = 200;
+ private static Validation Validate(in string description) =>
+ string.IsNullOrEmpty(description)
+ ? Validation.Invalid(ProjectErrorMessages.CoreToDoItemDescriptionEmpty)
+ : description.Length > MaxLength
+ ? Validation.Invalid(ProjectErrorMessages.CoreToDoItemDescriptionTooLong(MaxLength))
+ : Validation.Ok;
+}
diff --git a/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItemTitle.cs b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItemTitle.cs
new file mode 100644
index 000000000..e447fa1b0
--- /dev/null
+++ b/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItemTitle.cs
@@ -0,0 +1,15 @@
+using Vogen;
+
+namespace NimblePros.SampleToDo.Core.ProjectAggregate;
+
+[ValueObject(conversions: Conversions.SystemTextJson)]
+public partial struct ToDoItemTitle
+{
+ public const int MaxLength = 100;
+ private static Validation Validate(in string title) =>
+ string.IsNullOrEmpty(title)
+ ? Validation.Invalid(ProjectErrorMessages.CoreToDoItemTitleEmpty)
+ : title.Length > MaxLength
+ ? Validation.Invalid(ProjectErrorMessages.CoreToDoItemTitleTooLong(MaxLength))
+ : Validation.Ok;
+}
diff --git a/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Config/ToDoItemConfiguration.cs b/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Config/ToDoItemConfiguration.cs
index dee312b12..07974d63b 100644
--- a/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Config/ToDoItemConfiguration.cs
+++ b/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Config/ToDoItemConfiguration.cs
@@ -11,11 +11,17 @@ public void Configure(EntityTypeBuilder builder)
.HasValueGenerator>()
.HasVogenConversion()
.IsRequired();
- builder.Property(t => t.Title)
- .HasMaxLength(DataSchemaConstants.DEFAULT_NAME_LENGTH)
+
+ builder.Property(p => p.Title)
+ .HasVogenConversion()
+ .HasMaxLength(ToDoItemTitle.MaxLength)
+ .IsRequired();
+
+ builder.Property(p => p.Description)
+ .HasVogenConversion()
+ .HasMaxLength(ToDoItemDescription.MaxLength)
.IsRequired();
- builder.Property(t => t.Description)
- .HasMaxLength(200);
+
builder.Property(t => t.ContributorId)
.HasConversion(
v => v.HasValue ? v.Value.Value : (int?)null, // to db
diff --git a/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Config/VogenEfCoreConverters.cs b/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Config/VogenEfCoreConverters.cs
index f07a894b2..9643b0ae4 100644
--- a/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Config/VogenEfCoreConverters.cs
+++ b/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Config/VogenEfCoreConverters.cs
@@ -9,4 +9,6 @@ namespace NimblePros.SampleToDo.Infrastructure.Data.Config;
[EfCoreConverter]
[EfCoreConverter]
[EfCoreConverter]
+[EfCoreConverter]
+[EfCoreConverter]
internal partial class VogenEfCoreConverters;
diff --git a/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListIncompleteItemsQueryService.cs b/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListIncompleteItemsQueryService.cs
index 7aa24553b..72c5d4017 100644
--- a/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListIncompleteItemsQueryService.cs
+++ b/sample/src/NimblePros.SampleToDo.Infrastructure/Data/Queries/ListIncompleteItemsQueryService.cs
@@ -17,7 +17,7 @@ public async Task> ListAsync(int projectId)
var projectParameter = new SqlParameter("@projectId", System.Data.SqlDbType.Int);
var result = await _db.ToDoItems.FromSqlRaw("SELECT Id, Title, Description, IsDone, ContributorId FROM ToDoItems WHERE ProjectId = @ProjectId",
projectParameter) // don't fetch other big columns
- .Select(x => new ToDoItemDto(x.Id, x.Title, x.Description, x.IsDone, x.ContributorId))
+ .Select(x => new ToDoItemDto(x.Id, x.Title.Value, x.Description.Value, x.IsDone, x.ContributorId))
.ToListAsync();
return result;
diff --git a/sample/src/NimblePros.SampleToDo.UseCases/Projects/AddToDoItem/AddToDoItemCommand.cs b/sample/src/NimblePros.SampleToDo.UseCases/Projects/AddToDoItem/AddToDoItemCommand.cs
index 129c523d4..6f2f1d541 100644
--- a/sample/src/NimblePros.SampleToDo.UseCases/Projects/AddToDoItem/AddToDoItemCommand.cs
+++ b/sample/src/NimblePros.SampleToDo.UseCases/Projects/AddToDoItem/AddToDoItemCommand.cs
@@ -12,5 +12,5 @@ namespace NimblePros.SampleToDo.UseCases.Projects.AddToDoItem;
///
public record AddToDoItemCommand(ProjectId ProjectId,
ContributorId? ContributorId,
- string Title,
- string Description) : ICommand>;
+ ToDoItemTitle Title,
+ ToDoItemDescription Description) : ICommand>;
diff --git a/sample/src/NimblePros.SampleToDo.UseCases/Projects/AddToDoItem/AddToDoItemHandler.cs b/sample/src/NimblePros.SampleToDo.UseCases/Projects/AddToDoItem/AddToDoItemHandler.cs
index 6d14b45a7..5677022c3 100644
--- a/sample/src/NimblePros.SampleToDo.UseCases/Projects/AddToDoItem/AddToDoItemHandler.cs
+++ b/sample/src/NimblePros.SampleToDo.UseCases/Projects/AddToDoItem/AddToDoItemHandler.cs
@@ -22,13 +22,9 @@ public async ValueTask> Handle(AddToDoItemCommand request,
return Result.NotFound();
}
- var newItem = new ToDoItem()
- {
- Title = request.Title!,
- Description = request.Description!
- };
+ var newItem = new ToDoItem(title: request.Title!, description: request.Description!);
- if(request.ContributorId.HasValue)
+ if (request.ContributorId.HasValue)
{
newItem.AddContributor(request.ContributorId.Value);
}
diff --git a/sample/src/NimblePros.SampleToDo.UseCases/Projects/GetWithAllItems/GetProjectWithAllItemsHandler.cs b/sample/src/NimblePros.SampleToDo.UseCases/Projects/GetWithAllItems/GetProjectWithAllItemsHandler.cs
index 446fb2e05..9f51a1aa4 100644
--- a/sample/src/NimblePros.SampleToDo.UseCases/Projects/GetWithAllItems/GetProjectWithAllItemsHandler.cs
+++ b/sample/src/NimblePros.SampleToDo.UseCases/Projects/GetWithAllItems/GetProjectWithAllItemsHandler.cs
@@ -22,7 +22,7 @@ public async ValueTask> Handle(GetProjectWithAllI
if (project == null) return Result.NotFound();
var items = project.Items
- .Select(i => new ToDoItemDto(i.Id, i.Title, i.Description, i.IsDone, i.ContributorId)).ToList();
+ .Select(i => new ToDoItemDto(i.Id, i.Title.Value, i.Description.Value, i.IsDone, i.ContributorId)).ToList();
return new ProjectWithAllItemsDto(project.Id, project.Name, items, project.Status.ToString())
;
}
diff --git a/sample/src/NimblePros.SampleToDo.Web/NimblePros.SampleToDo.Web.csproj b/sample/src/NimblePros.SampleToDo.Web/NimblePros.SampleToDo.Web.csproj
index 4b55e3716..d39d46d85 100644
--- a/sample/src/NimblePros.SampleToDo.Web/NimblePros.SampleToDo.Web.csproj
+++ b/sample/src/NimblePros.SampleToDo.Web/NimblePros.SampleToDo.Web.csproj
@@ -1,6 +1,6 @@
-
+
true
Exe
@@ -10,11 +10,11 @@
True
-
+
@@ -32,11 +32,11 @@
-
+
-
+
diff --git a/sample/src/NimblePros.SampleToDo.Web/Program.cs b/sample/src/NimblePros.SampleToDo.Web/Program.cs
index 038d51a10..b4d320ec2 100644
--- a/sample/src/NimblePros.SampleToDo.Web/Program.cs
+++ b/sample/src/NimblePros.SampleToDo.Web/Program.cs
@@ -1,5 +1,9 @@
-using FluentValidation;
+using System.Text;
+using AspNetCore.Localizer.Json.Extensions;
+using FluentValidation;
+using Microsoft.Extensions.Localization;
using NimblePros.Metronome;
+using NimblePros.SampleToDo.Core.ProjectAggregate;
using NimblePros.SampleToDo.Infrastructure.Data;
using NimblePros.SampleToDo.Web.Configurations;
using NimblePros.SampleToDo.Web.Projects;
@@ -60,8 +64,39 @@ private static async Task Main(string[] args)
// track db and external service calls
builder.Services.AddMetronome();
+ // Add localization with the JSON path
+ builder.Services.AddLocalization(options => options.ResourcesPath = "i18n");
+
+ // ----- Add JSON-specific localization
+ builder.Services.AddJsonLocalization(options =>
+ {
+ options.ResourcesPath = "i18n"; // Path to the JSON files (e.g., i18n/en/Project.json)
+ options.CacheDuration = TimeSpan.FromHours(1); // Optional: Cache for performance
+ options.FileEncoding = Encoding.UTF8; //Optional: Specify file encoding
+ });
+
+ // ----- Register the typed localizer for the Project aggregate -----
+ builder.Services.AddSingleton(sp =>
+ {
+ var factory = sp.GetRequiredService();
+ // Creates a localizer for 'ProjectAggregate' – loads from i18n/{culture}/Project.json
+ return factory.Create(typeof(ProjectErrorMessages));
+ });
+
var app = builder.Build();
-
+
+ // ----- Set the static holder for Core access -----
+ var localizer = app.Services.GetRequiredService();
+ Localization.Current = new Localization.LocalizationContext(localizer);
+
+ // ----- Add request localization middleware (before app.Run()) -----
+ var supportedCultures = new[] { "en", "fr", "fa" }; // Add your cultures
+ var localizationOptions = new RequestLocalizationOptions()
+ .SetDefaultCulture(supportedCultures[0])
+ .AddSupportedCultures(supportedCultures)
+ .AddSupportedUICultures(supportedCultures);
+ app.UseRequestLocalization(localizationOptions);
+
// Verify validators are registered properly in development
if (app.Environment.IsDevelopment())
{
diff --git a/sample/src/NimblePros.SampleToDo.Web/Projects/CreateToDoItem.cs b/sample/src/NimblePros.SampleToDo.Web/Projects/CreateToDoItem.cs
index 54ab1c0b5..359c70616 100644
--- a/sample/src/NimblePros.SampleToDo.Web/Projects/CreateToDoItem.cs
+++ b/sample/src/NimblePros.SampleToDo.Web/Projects/CreateToDoItem.cs
@@ -1,7 +1,6 @@
using NimblePros.SampleToDo.Core.ContributorAggregate;
using NimblePros.SampleToDo.Core.ProjectAggregate;
using NimblePros.SampleToDo.UseCases.Projects.AddToDoItem;
-using NimblePros.SampleToDo.Web.Extensions;
using NimblePros.SampleToDo.Web.Projects;
namespace NimblePros.SampleToDo.Web.ProjectEndpoints;
@@ -30,17 +29,17 @@ public override void Configure()
Title = "Implement user authentication",
Description = "Add JWT-based authentication to the API"
};
-
+
// Document possible responses
s.Responses[201] = "Todo item created successfully";
s.Responses[404] = "Project or contributor not found";
s.Responses[400] = "Invalid input data";
s.Responses[500] = "Internal server error";
});
-
+
// Add tags for API grouping
Tags("Projects");
-
+
// Add additional metadata
Description(builder => builder
.Accepts("application/json")
@@ -57,7 +56,7 @@ public override async Task>
? ContributorId.From(request.ContributorId.Value)
: null;
var command = new AddToDoItemCommand(ProjectId.From(request.ProjectId), contributorId,
- request.Title, request.Description);
+ ToDoItemTitle.From(request.Title), ToDoItemDescription.From(request.Description));
var result = await _mediator.Send(command);
return result.Status switch
diff --git a/sample/src/NimblePros.SampleToDo.Web/SeedData.cs b/sample/src/NimblePros.SampleToDo.Web/SeedData.cs
index e609c982f..7f74379f3 100644
--- a/sample/src/NimblePros.SampleToDo.Web/SeedData.cs
+++ b/sample/src/NimblePros.SampleToDo.Web/SeedData.cs
@@ -6,33 +6,31 @@ namespace NimblePros.SampleToDo.Web;
public static class SeedData
{
- public static readonly Contributor Contributor1 = new (ContributorName.From("Ardalis"));
- public static readonly Contributor Contributor2 = new (ContributorName.From("Snowfrog"));
+ public static readonly Contributor Contributor1 = new(ContributorName.From("Ardalis"));
+ public static readonly Contributor Contributor2 = new(ContributorName.From("Snowfrog"));
public static readonly Project TestProject1 = new Project(ProjectName.From("Test Project"));
- public static readonly ToDoItem ToDoItem1 = new ToDoItem
- {
- Title = "Get Sample Working",
- Description = "Try to get the sample to build."
- };
- public static readonly ToDoItem ToDoItem2 = new ToDoItem
- {
- Title = "Review Solution",
- Description = "Review the different projects in the solution and how they relate to one another."
- };
- public static readonly ToDoItem ToDoItem3 = new ToDoItem
- {
- Title = "Run and Review Tests",
- Description = "Make sure all the tests run and review what they are doing."
- };
+
+ public static readonly ToDoItem ToDoItem1 =
+ new ToDoItem(title: ToDoItemTitle.From("Get Sample Working"),
+ description: ToDoItemDescription.From("Try to get the sample to build."));
+
+ public static readonly ToDoItem ToDoItem2 =
+ new ToDoItem(title: ToDoItemTitle.From("Review Solution"),
+ description: ToDoItemDescription.From("Review the different projects in the solution and how they relate to one another."));
+
+
+ public static readonly ToDoItem ToDoItem3 =
+new ToDoItem(title: ToDoItemTitle.From("Run and Review Tests"),
+ description: ToDoItemDescription.From("Make sure all the tests run and review what they are doing."));
public static async Task InitializeAsync(AppDbContext dbContext)
{
- if (await dbContext.ToDoItems.AnyAsync())
- {
- return; // DB has been seeded
- }
+ if (await dbContext.ToDoItems.AnyAsync())
+ {
+ return; // DB has been seeded
+ }
- await PopulateTestDataAsync(dbContext);
+ await PopulateTestDataAsync(dbContext);
}
public static async Task PopulateTestDataAsync(AppDbContext dbContext)
diff --git a/sample/src/NimblePros.SampleToDo.Web/i18n/en/Project.json b/sample/src/NimblePros.SampleToDo.Web/i18n/en/Project.json
new file mode 100644
index 000000000..ee15da8bc
--- /dev/null
+++ b/sample/src/NimblePros.SampleToDo.Web/i18n/en/Project.json
@@ -0,0 +1,8 @@
+{
+ "CoreProjectNameEmpty": "Name cannot be empty",
+ "CoreProjectNameTooLong": "Name cannot be longer than {0} characters",
+ "CoreToDoItemDescriptionEmpty": "Description cannot be empty",
+ "CoreToDoItemDescriptionTooLong": "Description cannot be longer than {0} characters",
+ "CoreToDoItemTitleEmpty": "Title cannot be empty",
+ "CoreToDoItemTitleTooLong": "Title cannot be longer than {0} characters"
+}
\ No newline at end of file
diff --git a/sample/src/NimblePros.SampleToDo.Web/i18n/fa/Project.json b/sample/src/NimblePros.SampleToDo.Web/i18n/fa/Project.json
new file mode 100644
index 000000000..ef3d8e37d
--- /dev/null
+++ b/sample/src/NimblePros.SampleToDo.Web/i18n/fa/Project.json
@@ -0,0 +1,8 @@
+{
+ "CoreProjectNameEmpty": "نام نمیتواند خالی باشد",
+ "CoreProjectNameTooLong": "نام نمیتواند بیش از {0} کاراکتر باشد",
+ "CoreToDoItemDescriptionEmpty": "توضیحات نمیتواند خالی باشد",
+ "CoreToDoItemDescriptionTooLong": "توضیحات نمیتواند بیش از {0} کاراکتر باشد",
+ "CoreToDoItemTitleEmpty": "عنوان نمیتواند خالی باشد",
+ "CoreToDoItemTitleTooLong": "عنوان نمیتواند بیش از {0} کاراکتر باشد"
+}
diff --git a/sample/src/NimblePros.SampleToDo.Web/i18n/fr/Project.json b/sample/src/NimblePros.SampleToDo.Web/i18n/fr/Project.json
new file mode 100644
index 000000000..354d1ea08
--- /dev/null
+++ b/sample/src/NimblePros.SampleToDo.Web/i18n/fr/Project.json
@@ -0,0 +1,8 @@
+{
+ "CoreProjectNameEmpty": "Le nom ne peut pas être vide",
+ "CoreProjectNameTooLong": "Le nom ne peut pas dépasser {0} caractères",
+ "CoreToDoItemDescriptionEmpty": "La description ne peut pas être vide",
+ "CoreToDoItemDescriptionTooLong": "La description ne peut pas dépasser {0} caractères",
+ "CoreToDoItemTitleEmpty": "Le titre ne peut pas être vide",
+ "CoreToDoItemTitleTooLong": "Le titre ne peut pas dépasser {0} caractères"
+}
diff --git a/sample/tests/NimblePros.SampleToDo.IntegrationTests/Data/EfRepositoryAdd.cs b/sample/tests/NimblePros.SampleToDo.IntegrationTests/Data/EfRepositoryAdd.cs
index e39eddd32..13ca08b4c 100644
--- a/sample/tests/NimblePros.SampleToDo.IntegrationTests/Data/EfRepositoryAdd.cs
+++ b/sample/tests/NimblePros.SampleToDo.IntegrationTests/Data/EfRepositoryAdd.cs
@@ -12,7 +12,7 @@ public async Task AddsProjectAndSetsId()
var project = new Project(testProjectName);
var item = new ToDoItem();
- item.Title = "test item title";
+ item.UpdateTitle(ToDoItemTitle.From("test item title"));
project.AddItem(item);
await repository.AddAsync(project);
diff --git a/sample/tests/NimblePros.SampleToDo.UnitTests/Core/ProjectAggregate/ProjectConstructor.cs b/sample/tests/NimblePros.SampleToDo.UnitTests/Core/ProjectAggregate/ProjectConstructor.cs
index 278a7d394..2ea990ea7 100644
--- a/sample/tests/NimblePros.SampleToDo.UnitTests/Core/ProjectAggregate/ProjectConstructor.cs
+++ b/sample/tests/NimblePros.SampleToDo.UnitTests/Core/ProjectAggregate/ProjectConstructor.cs
@@ -1,4 +1,5 @@
-using NimblePros.SampleToDo.Core.ProjectAggregate;
+using System.Globalization;
+using NimblePros.SampleToDo.Core.ProjectAggregate;
namespace NimblePros.SampleToDo.UnitTests.Core.ProjectAggregate;
@@ -36,4 +37,16 @@ public void InitializesStatusToInProgress()
Assert.Equal(ProjectStatus.Complete, _testProject.Status);
}
+ [Fact]
+ public void ProjectName_TooLong_ReturnsLocalizedMessage()
+ {
+ // Arrange: Set a French culture for testing
+ Thread.CurrentThread.CurrentUICulture = new CultureInfo("en");
+
+ // Act
+ var message = ProjectErrorMessages.CoreProjectNameTooLong(100);
+
+ // Assert: Should use fallback or loaded JSON (depending on setup)
+ Assert.Equal("Name cannot be longer than 100 characters", message);
+ }
}
diff --git a/sample/tests/NimblePros.SampleToDo.UnitTests/Core/ProjectAggregate/Project_AddItem.cs b/sample/tests/NimblePros.SampleToDo.UnitTests/Core/ProjectAggregate/Project_AddItem.cs
index 8d62c40e3..e623aaf92 100644
--- a/sample/tests/NimblePros.SampleToDo.UnitTests/Core/ProjectAggregate/Project_AddItem.cs
+++ b/sample/tests/NimblePros.SampleToDo.UnitTests/Core/ProjectAggregate/Project_AddItem.cs
@@ -9,11 +9,8 @@ public class Project_AddItem
[Fact]
public void AddsItemToItems()
{
- var _testItem = new ToDoItem
- {
- Title = "title",
- Description = "description"
- };
+ var _testItem = new ToDoItem(title: ToDoItemTitle.From("title"),
+ description: ToDoItemDescription.From("description"));
_testProject.AddItem(_testItem);
diff --git a/sample/tests/NimblePros.SampleToDo.UnitTests/Core/Services/ToDoItemSearchServiceTests.cs b/sample/tests/NimblePros.SampleToDo.UnitTests/Core/Services/ToDoItemSearchServiceTests.cs
index 08004d539..59bcc1e74 100644
--- a/sample/tests/NimblePros.SampleToDo.UnitTests/Core/Services/ToDoItemSearchServiceTests.cs
+++ b/sample/tests/NimblePros.SampleToDo.UnitTests/Core/Services/ToDoItemSearchServiceTests.cs
@@ -1,7 +1,6 @@
using NimblePros.SampleToDo.Core.Interfaces;
using NimblePros.SampleToDo.Core.ProjectAggregate;
using NimblePros.SampleToDo.Core.Services;
-using NimblePros.SharedKernel;
namespace NimblePros.SampleToDo.UnitTests.Core.Services;
@@ -9,21 +8,21 @@ public class ToDoItemSearchServiceTests
{
private readonly IToDoItemSearchService _service;
private readonly IRepository _repo = Substitute.For>();
-
+
public ToDoItemSearchServiceTests()
{
_service = new ToDoItemSearchService(_repo);
-
+
}
[Fact]
public async Task ReturnsValidationErrors()
{
var projects = await _service.GetAllIncompleteItemsAsync(ProjectId.From(0), string.Empty);
-
+
Assert.NotEmpty(projects.ValidationErrors);
}
-
+
[Fact]
public async Task ReturnsProjectNotFound()
{
@@ -31,18 +30,14 @@ public async Task ReturnsProjectNotFound()
Assert.Equal(ResultStatus.NotFound, projects.Status);
}
-
+
[Fact]
public async Task ReturnsAllIncompleteItems()
{
var title = "Some Title";
Project project = new Project(ProjectName.From("Cool Project"));
-
- project.AddItem(new ToDoItem
- {
- Title = title,
- Description = "Some Description"
- });
+
+ project.AddItem(new ToDoItem(title: ToDoItemTitle.From(title), description: ToDoItemDescription.From("Some Description")));
_repo.FirstOrDefaultAsync(Arg.Any>(), Arg.Any())
.Returns(project);
@@ -50,7 +45,7 @@ public async Task ReturnsAllIncompleteItems()
var projects = await _service.GetAllIncompleteItemsAsync(ProjectId.From(1), title);
Assert.Empty(projects.ValidationErrors);
- Assert.Equal(projects.Value.First().Title, title);
+ Assert.Equal(projects.Value.First().Title.Value, title);
Assert.Equal(project.Items.Count(), projects.Value.Count);
}
}
diff --git a/sample/tests/NimblePros.SampleToDo.UnitTests/ToDoItemBuilder.cs b/sample/tests/NimblePros.SampleToDo.UnitTests/ToDoItemBuilder.cs
index 423d33cdc..08f851e14 100644
--- a/sample/tests/NimblePros.SampleToDo.UnitTests/ToDoItemBuilder.cs
+++ b/sample/tests/NimblePros.SampleToDo.UnitTests/ToDoItemBuilder.cs
@@ -14,21 +14,21 @@ public ToDoItemBuilder Id(int id)
return this;
}
- public ToDoItemBuilder Title(string title)
+ public ToDoItemBuilder Title(String title)
{
- _todo.Title = title;
+ _todo.UpdateTitle(ToDoItemTitle.From(title));
return this;
}
- public ToDoItemBuilder Description(string description)
+ public ToDoItemBuilder Description(String description)
{
- _todo.Description = description;
+ _todo.UpdateDescription(ToDoItemDescription.From(description));
return this;
}
public ToDoItemBuilder WithDefaultValues()
{
- _todo = new ToDoItem() { Id = ToDoItemId.From(1), Title = "Test Item", Description = "Test Description" };
+ _todo = new ToDoItem(title: ToDoItemTitle.From("Test Item"), description: ToDoItemDescription.From("Test Description")) { Id = ToDoItemId.From(1) };
return this;
}