- Invite administrator
+ @L["RegistryAdmin.InviteAdministrator"]
-
+
- Create invite
+ @L["RegistryAdmin.CreateInvite"]
@if (!string.IsNullOrWhiteSpace(InviteLink))
{
- Invite link:
+ @L["RegistryAdmin.InviteLink"]
@InviteLink
diff --git a/src/BirthList.Web/Components/Pages/RegistryContributionAmount.razor b/src/BirthList.Web/Components/Pages/RegistryContributionAmount.razor
index dc962de..d65d025 100644
--- a/src/BirthList.Web/Components/Pages/RegistryContributionAmount.razor
+++ b/src/BirthList.Web/Components/Pages/RegistryContributionAmount.razor
@@ -3,29 +3,29 @@
@using BirthList.Web.Features.Registries
-Contribution Amount
+@L["RegistryContributionAmount.PageTitle"]
@if (Registry is null || Item is null)
{
- Item not found.
+ @L["RegistryContributionAmount.ItemNotFound"]
}
else if (!IsAuthenticated)
{
- Please log in first.
+ @L["RegistryContributionAmount.LoginFirst"]
}
else
{
-
Partially fulfill: @Item.Name
+
@L["RegistryContributionAmount.PartiallyFulfill", Item.Name]
@if (RepresentableAmounts.Count == 0)
{
-
No representable amount can be formed from configured QR codes up to @GetCurrencySymbol()200.
+
@L["RegistryContributionAmount.NoRepresentableAmount", GetCurrencySymbol()]
}
else
{
-
Select amount: @GetCurrencySymbol()@SelectedAmount
+
@L["RegistryContributionAmount.SelectAmount", GetCurrencySymbol(), SelectedAmount]
No QR combination available for this amount.
+
@L["RegistryContributionAmount.NoQrCombination"]
}
else
{
-
Suggested QR combination:
+
@L["RegistryContributionAmount.SuggestedCombination"]
}
@@ -67,8 +67,8 @@ else
}
- I transferred this amount
- Back
+ @L["RegistryContributionAmount.TransferredAmount"]
+ @L["Common.Back"]
diff --git a/src/BirthList.Web/Components/Pages/RegistryInvite.razor b/src/BirthList.Web/Components/Pages/RegistryInvite.razor
index afe0fa1..82fe542 100644
--- a/src/BirthList.Web/Components/Pages/RegistryInvite.razor
+++ b/src/BirthList.Web/Components/Pages/RegistryInvite.razor
@@ -1,19 +1,19 @@
@page "/registry/{RegistryId:guid}/invite/{Token}"
-Admin Invite
+@L["RegistryInvite.PageTitle"]
-Admin invitation
+@L["RegistryInvite.Title"]
@if (Redeemed is null)
{
- Validating invitation...
+ @L["RegistryInvite.Validating"]
}
else if (Redeemed.Value)
{
- Invitation accepted. You are now an admin.
- Go to admin
+ @L["RegistryInvite.Accepted"]
+ @L["RegistryInvite.GoToAdmin"]
}
else
{
- The invitation is invalid or already used.
+ @L["RegistryInvite.Invalid"]
}
diff --git a/src/BirthList.Web/Components/Pages/RegistryPublic.razor b/src/BirthList.Web/Components/Pages/RegistryPublic.razor
index a4773a3..608e076 100644
--- a/src/BirthList.Web/Components/Pages/RegistryPublic.razor
+++ b/src/BirthList.Web/Components/Pages/RegistryPublic.razor
@@ -3,11 +3,11 @@
@using BirthList.Web.Features.Registries
-Registry
+@L["RegistryPublic.PageTitle"]
@if (Registry is null)
{
- Registry not found.
+ @L["RegistryPublic.NotFound"]
}
else
{
@@ -22,7 +22,7 @@ else
@if (!string.IsNullOrWhiteSpace(Registry.ShippingAddress))
{
- Shipping address
+ @L["RegistryPublic.ShippingAddress"]
@Registry.ShippingAddress
}
@@ -30,18 +30,18 @@ else
@if (Registry.ShowBankAccountName && !string.IsNullOrWhiteSpace(Registry.BankAccountIban))
{
- Bank transfer participation
+ @L["RegistryPublic.BankTransferParticipation"]
@Registry.BankAccountDisplayName
- IBAN: @Registry.BankAccountIban
+ @L["RegistryPublic.IBAN"]: @Registry.BankAccountIban
@if (!string.IsNullOrWhiteSpace(Registry.BankAccountBic))
{
- | BIC: @Registry.BankAccountBic
+ | @L["RegistryPublic.BIC"]: @Registry.BankAccountBic
}
}
- @foreach (var category in Registry.Categories)
+ @foreach (var category in Registry.Categories.Where(x => x.Items.Count > 0))
{
var isCollapsed = IsCategoryCollapsed(category.Id);
@@ -66,11 +66,11 @@ else
@item.Name
@if (item.PreferSecondHand == true)
{
- Second-hand preferred
+ @L["RegistryPublic.SecondHandPreferred"]
}
else if (item.PreferSecondHand == null)
{
- Second-hand optional
+ @L["RegistryPublic.SecondHandOptional"]
}
@if (!string.IsNullOrWhiteSpace(item.Description))
@@ -79,24 +79,24 @@ else
}
@if (item.DesiredQuantity > 1)
{
- Qty: @item.PurchasedQuantity/@item.DesiredQuantity purchased
+ @L["RegistryPublic.Qty"] @item.PurchasedQuantity/@item.DesiredQuantity @L["RegistryPublic.PurchasedSuffix"]
}
@if (item.PriceAmount.HasValue)
{
- Price: @item.PriceAmount.Value.ToString("0.00") @item.CurrencyCode
+ @L["RegistryPublic.Price"] @item.PriceAmount.Value.ToString("0.00") @item.CurrencyCode
}
@if (item.ParticipationAllowed && GetParticipationTotalAmount(item).HasValue)
{
- Participation:
+ @L["RegistryPublic.Participation"]
@item.CurrencyCode@item.MoneyFulfilledAmount.ToString("0.00")
@if (GetParticipationTotalAmount(item)!.Value > 0)
{
- out of @item.CurrencyCode@GetParticipationTotalAmount(item)!.Value.ToString("0.00") fulfilled
+ @L["RegistryPublic.OutOfFulfilled", item.CurrencyCode, GetParticipationTotalAmount(item)!.Value.ToString("0.00")]
}
else
{
- fulfilled
+ @L["RegistryPublic.FulfilledOnly"]
}
}
@@ -109,7 +109,7 @@ else
@if (item.CanViewPurchasers && Registry.IsAdmin)
{
-
Purchased by:
+
@L["RegistryPublic.PurchasedBy"]
@foreach (var purchaser in item.Purchasers)
{
@@ -119,18 +119,19 @@ else
}
else if (item.CanViewPurchasers && item.CurrentUserPurchasedQuantity > 0)
{
-
Purchased by:
+
@L["RegistryPublic.PurchasedBy"]
- You (@item.CurrentUserPurchasedQuantity)
+ @L["RegistryPublic.YouQuantity", item.CurrentUserPurchasedQuantity]
@if (item.Purchasers.Count > 1)
{
- and @(item.Purchasers.Count - 1) other @(item.Purchasers.Count - 1 == 1 ? "person" : "people")
+ var otherCount = item.Purchasers.Count - 1;
+ @L["RegistryPublic.AndOtherPeople", otherCount, otherCount == 1 ? L["RegistryPublic.Person"].Value : L["RegistryPublic.People"].Value]
}
}
else
{
-
Purchased
+
@L["RegistryPublic.Purchased"]
}
}
@@ -139,7 +140,7 @@ else
@if (Registry.IsAdmin)
{
-
Contributed by:
+
@L["RegistryPublic.ContributedBy"]
@foreach (var contributor in item.Contributors)
{
@@ -152,23 +153,23 @@ else
var currentUserContribution = item.Contributors.FirstOrDefault(x => x.UserId == Registry.CurrentUserId);
if (currentUserContribution is not null)
{
-
Contributed by:
+
@L["RegistryPublic.ContributedBy"]
@currentUserContribution.DisplayName (@currentUserContribution.Amount.ToString("0.00") @item.CurrencyCode)
@if (item.Contributors.Count > 1)
{
- and others
+ @L["RegistryPublic.AndOthers"]
}
}
else
{
-
Contributed
+
@L["RegistryPublic.Contributed"]
}
}
else
{
-
Contributed
+
@L["RegistryPublic.Contributed"]
}
}
@@ -177,24 +178,24 @@ else
@if (!IsAuthenticated)
{
-
LoginRedirect()">Login to purchase
+
LoginRedirect()">@L["RegistryPublic.LoginToPurchase"]
}
else
{
@if (!string.IsNullOrWhiteSpace(item.ProductUrl) && item.CurrentUserPurchasedQuantity == 0)
{
- OpenPurchasePrompt(item.Id, openTab: true)">Purchase
+ OpenPurchasePrompt(item.Id, openTab: true)">@L["RegistryPublic.Purchase"]
}
OpenPurchaseManagementPromptAsync(item.Id)">
- @(Registry.IsAdmin ? "Manage purchases" : (item.CurrentUserPurchasedQuantity > 0 ? "Edit purchase" : "Mark purchased"))
+ @(Registry.IsAdmin ? L["RegistryPublic.ManagePurchases"].Value : (item.CurrentUserPurchasedQuantity > 0 ? L["RegistryPublic.EditPurchase"].Value : L["RegistryPublic.MarkPurchased"].Value))
@if (item.ParticipationAllowed)
{
OpenContributionActionAsync(item.Id, item.CurrentUserContributionAmount)">
- @(Registry.IsAdmin ? "Manage participations" : (item.CurrentUserContributionAmount > 0 ? "Edit participation" : "Partially fulfill"))
+ @(Registry.IsAdmin ? L["RegistryPublic.ManageParticipations"].Value : (item.CurrentUserContributionAmount > 0 ? L["RegistryPublic.EditParticipation"].Value : L["RegistryPublic.PartiallyFulfill"].Value))
}
@@ -214,36 +215,20 @@ else
{
-
Mark as purchased
-
How many units did you purchase?
+
@L["RegistryPublic.MarkAsPurchased"]
+
@L["RegistryPublic.HowManyUnitsPurchased"]
@if (!string.IsNullOrWhiteSpace(PurchaseItemUrl))
{
}
- Confirm purchase
- Cancel
-
-
-
-}
-
-@if (ShowContributionPrompt)
-{
-
-
-
Log contribution
-
Transferred amount
-
-
Message: @ContributionMessage
-
- Confirm
- Cancel
+ @L["RegistryPublic.ConfirmPurchase"]
+ @L["Common.Cancel"]
@@ -253,20 +238,20 @@ else
{
-
Select purchaser to unmark
-
Multiple users have purchased this item. Choose which purchase to unmark:
+
@L["RegistryPublic.SelectPurchaserToUnmark"]
+
@L["RegistryPublic.MultipleUsersPurchased"]
@foreach (var purchaser in PurchasersToUnmark)
{
@purchaser.DisplayName (@purchaser.Quantity)
- UnmarkPurchaserAsync(purchaser.UserId)">Unmark
+ UnmarkPurchaserAsync(purchaser.UserId)">@L["RegistryPublic.Unmark"]
}
- Unmark all
- Cancel
+ @L["RegistryPublic.UnmarkAll"]
+ @L["Common.Cancel"]
@@ -276,29 +261,29 @@ else
{
-
Partially fulfill item
+
@L["RegistryPublic.PartiallyFulfillItem"]
@if (PartialFulfillStep == 1)
{
-
Select how you want to donate:
+
@L["RegistryPublic.SelectDonateMethod"]
@if (HasIbanPaymentOption())
{
SelectPaymentMethod(ContributionPaymentMethodType.Iban)">
- IBAN transfer
+ @L["RegistryPublic.IbanTransfer"]
}
@if (HasSingleQrPaymentOption())
{
SelectPaymentMethod(ContributionPaymentMethodType.SingleQrCode)">
- Single QR code
+ @L["RegistryPublic.SingleQrCode"]
}
@if (HasAmountSpecificQrPaymentOption())
{
SelectPaymentMethod(ContributionPaymentMethodType.AmountSpecificQrCode)">
- QR code per amount
+ @L["RegistryPublic.QrCodePerAmount"]
}
@@ -306,10 +291,10 @@ else
@if (SelectedPaymentMethod == ContributionPaymentMethodType.Iban && !string.IsNullOrWhiteSpace(Registry?.BankAccountIban))
{
- IBAN: @Registry.BankAccountIban
+ @L["RegistryPublic.IBAN"]: @Registry.BankAccountIban
@if (!string.IsNullOrWhiteSpace(Registry.BankAccountBic))
{
- | BIC: @Registry.BankAccountBic
+ | @L["RegistryPublic.BIC"]: @Registry.BankAccountBic
}
}
@@ -319,25 +304,25 @@ else
}
- Next
- Cancel
+ @L["Common.Next"]
+ @L["Common.Cancel"]
}
else if (PartialFulfillStep == 2)
{
-
How much did you add for this item?
+
@L["RegistryPublic.HowMuchAdded"]
-
Message: @ContributionMessage
+
@L["Common.Message"]: @ContributionMessage
@if (SelectedPaymentMethod == ContributionPaymentMethodType.AmountSpecificQrCode && ContributionAmount > 0 && string.IsNullOrWhiteSpace(SelectedPaymentQrCodeUrl))
{
-
No QR code configured for this exact amount.
+
@L["RegistryPublic.NoQrForAmount"]
}
@if (!string.IsNullOrWhiteSpace(SelectedPaymentQrCodeUrl))
@@ -345,15 +330,15 @@ else
}
- Confirm
- Back
- Cancel
+ @L["RegistryPublic.Confirm"]
+ @L["Common.Back"]
+ @L["Common.Cancel"]
}
@@ -364,14 +349,14 @@ else
{
-
Manage purchase
+
@L["RegistryPublic.ManagePurchase"]
@if (Registry?.IsAdmin == true)
{
-
User
-
+
@L["RegistryPublic.User"]
+
- Select user
+ @L["Common.SelectUser"]
@foreach (var user in FilteredSelectableUsers)
{
@user.DisplayName
@@ -379,13 +364,13 @@ else
}
-
Quantity
+
@L["Common.Quantity"]
- Save
- Remove
- Cancel
+ @L["Common.Save"]
+ @L["Common.Remove"]
+ @L["Common.Cancel"]
@@ -395,14 +380,14 @@ else
{
-
Manage participation
+
@L["RegistryPublic.ManageParticipation"]
@if (Registry?.IsAdmin == true)
{
-
User
-
+
@L["RegistryPublic.User"]
+
- Select user
+ @L["Common.SelectUser"]
@foreach (var user in FilteredSelectableUsers)
{
@user.DisplayName
@@ -410,16 +395,16 @@ else
}
-
Amount
+
@L["Common.Amount"]
-
Message
+
@L["Common.Message"]
- Save
- Remove
- Cancel
+ @L["Common.Save"]
+ @L["Common.Remove"]
+ @L["Common.Cancel"]
diff --git a/src/BirthList.Web/Components/_Imports.razor b/src/BirthList.Web/Components/_Imports.razor
index 5217567..f162ea3 100644
--- a/src/BirthList.Web/Components/_Imports.razor
+++ b/src/BirthList.Web/Components/_Imports.razor
@@ -4,8 +4,11 @@
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.Extensions.Localization
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BirthList.Web
@using BirthList.Web.Components
+
+@inject IStringLocalizer
L
diff --git a/src/BirthList.Web/Data/ApplicationUser.cs b/src/BirthList.Web/Data/ApplicationUser.cs
index f2d6af7..9a8efd3 100644
--- a/src/BirthList.Web/Data/ApplicationUser.cs
+++ b/src/BirthList.Web/Data/ApplicationUser.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Identity;
namespace BirthList.Web.Data;
@@ -8,5 +9,8 @@ public class ApplicationUser : IdentityUser
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Address { get; set; }
+
+ [MaxLength(10)]
+ public string? PreferredCulture { get; set; }
}
diff --git a/src/BirthList.Web/Data/Migrations/20260519000100_AddPreferredCultureToApplicationUser.Designer.cs b/src/BirthList.Web/Data/Migrations/20260519000100_AddPreferredCultureToApplicationUser.Designer.cs
new file mode 100644
index 0000000..5356a4e
--- /dev/null
+++ b/src/BirthList.Web/Data/Migrations/20260519000100_AddPreferredCultureToApplicationUser.Designer.cs
@@ -0,0 +1,292 @@
+//
+using System;
+using BirthList.Web.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace BirthList.Web.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20260519000100_AddPreferredCultureToApplicationUser")]
+ partial class AddPreferredCultureToApplicationUser
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.26")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("BirthList.Web.Data.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("Address")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("FirstName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LastName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("PreferredCulture")
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("bit");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex")
+ .HasFilter("[NormalizedName] IS NOT NULL");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("RoleId")
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Value")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("BirthList.Web.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("BirthList.Web.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("BirthList.Web.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("BirthList.Web.Data.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/BirthList.Web/Data/Migrations/20260519000100_AddPreferredCultureToApplicationUser.cs b/src/BirthList.Web/Data/Migrations/20260519000100_AddPreferredCultureToApplicationUser.cs
new file mode 100644
index 0000000..45b2d37
--- /dev/null
+++ b/src/BirthList.Web/Data/Migrations/20260519000100_AddPreferredCultureToApplicationUser.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace BirthList.Web.Migrations
+{
+ ///
+ public partial class AddPreferredCultureToApplicationUser : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "PreferredCulture",
+ table: "AspNetUsers",
+ type: "nvarchar(10)",
+ maxLength: 10,
+ nullable: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "PreferredCulture",
+ table: "AspNetUsers");
+ }
+ }
+}
diff --git a/src/BirthList.Web/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/src/BirthList.Web/Data/Migrations/ApplicationDbContextModelSnapshot.cs
index 9e5de3d..4e5147d 100644
--- a/src/BirthList.Web/Data/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/src/BirthList.Web/Data/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -73,6 +73,10 @@ namespace BirthList.Web.Migrations
b.Property("PhoneNumberConfirmed")
.HasColumnType("bit");
+ b.Property("PreferredCulture")
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
b.Property("SecurityStamp")
.HasColumnType("nvarchar(max)");
diff --git a/src/BirthList.Web/Features/Localization/LocalizationService.cs b/src/BirthList.Web/Features/Localization/LocalizationService.cs
new file mode 100644
index 0000000..afdf762
--- /dev/null
+++ b/src/BirthList.Web/Features/Localization/LocalizationService.cs
@@ -0,0 +1,56 @@
+using BirthList.Web.Data;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Localization;
+
+namespace BirthList.Web.Features.Localization;
+
+internal sealed class LocalizationService(UserManager userManager)
+{
+ private static readonly HashSet SupportedCultureNames =
+ [
+ "en",
+ "nl-NL",
+ "nl-BE",
+ "fr-FR",
+ "qps-Ploc"
+ ];
+
+ public static IReadOnlyList GetSupportedCultures()
+ {
+ return SupportedCultureNames.OrderBy(x => x).ToList();
+ }
+
+ public bool IsSupportedCulture(string? culture)
+ {
+ return !string.IsNullOrWhiteSpace(culture) && SupportedCultureNames.Contains(culture);
+ }
+
+ public async Task SetPreferredCultureAsync(ApplicationUser user, string culture, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(user);
+
+ if (!IsSupportedCulture(culture))
+ {
+ throw new ArgumentException("Unsupported culture.", nameof(culture));
+ }
+
+ if (string.Equals(user.PreferredCulture, culture, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ user.PreferredCulture = culture;
+ var result = await userManager.UpdateAsync(user).ConfigureAwait(false);
+ if (!result.Succeeded)
+ {
+ var error = result.Errors.FirstOrDefault()?.Description ?? "Could not persist preferred culture.";
+ throw new InvalidOperationException(error);
+ }
+ }
+
+ public static string BuildCultureCookieValue(string culture)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(culture);
+ return CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
+ }
+}
diff --git a/src/BirthList.Web/Program.cs b/src/BirthList.Web/Program.cs
index 8c39d69..0991f1b 100644
--- a/src/BirthList.Web/Program.cs
+++ b/src/BirthList.Web/Program.cs
@@ -1,18 +1,22 @@
using System.Data;
+using System.Globalization;
using BirthList.Infrastructure.Persistence;
using BirthList.Web.Authorization;
using BirthList.Web.Components;
using BirthList.Web.Components.Account;
using BirthList.Web.Configuration;
using BirthList.Web.Data;
+using BirthList.Web.Features.Localization;
using BirthList.Web.Features.Registries;
using BirthList.Web.Services;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
@@ -44,6 +48,7 @@ builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
+builder.Services.AddScoped();
var googleClientId = builder.Configuration["Authentication:Google:ClientId"];
var googleClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];
@@ -126,6 +131,32 @@ builder.Services.AddScoped();
builder.Services.AddScoped>(serviceProvider => serviceProvider.GetRequiredService());
builder.Services.AddScoped();
+builder.Services.AddLocalization(options =>
+{
+ options.ResourcesPath = "Resources";
+});
+
+var supportedCultures = new[]
+{
+ new CultureInfo("en"),
+ new CultureInfo("nl-NL"),
+ new CultureInfo("nl-BE"),
+ new CultureInfo("fr-FR"),
+ new CultureInfo("qps-Ploc")
+};
+
+builder.Services.Configure(options =>
+{
+ options.DefaultRequestCulture = new RequestCulture("en");
+ options.SupportedCultures = supportedCultures;
+ options.SupportedUICultures = supportedCultures;
+ options.RequestCultureProviders =
+ [
+ new CookieRequestCultureProvider(),
+ new AcceptLanguageHeaderRequestCultureProvider()
+ ];
+});
+
var app = builder.Build();
using (var scope = app.Services.CreateScope())
@@ -190,9 +221,50 @@ app.UseAuthorization();
app.UseHttpsRedirection();
+app.UseRequestLocalization();
+
app.UseStaticFiles();
app.UseAntiforgery();
+app.MapPost("/set-language", async (
+ HttpContext context,
+ UserManager userManager,
+ LocalizationService localizationService,
+ [FromForm] string culture,
+ [FromForm] string? returnUrl) =>
+{
+ if (!localizationService.IsSupportedCulture(culture))
+ {
+ return Results.BadRequest("Unsupported culture.");
+ }
+
+ context.Response.Cookies.Append(
+ CookieRequestCultureProvider.DefaultCookieName,
+ LocalizationService.BuildCultureCookieValue(culture),
+ new CookieOptions
+ {
+ IsEssential = true,
+ Expires = DateTimeOffset.UtcNow.AddYears(1)
+ });
+
+ if (context.User.Identity?.IsAuthenticated == true)
+ {
+ var user = await userManager.GetUserAsync(context.User).ConfigureAwait(false);
+ if (user is not null)
+ {
+ await localizationService.SetPreferredCultureAsync(user, culture, context.RequestAborted).ConfigureAwait(false);
+ }
+ }
+
+ var targetUrl = string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl;
+ if (!Uri.TryCreate(targetUrl, UriKind.Relative, out _))
+ {
+ targetUrl = "/";
+ }
+
+ return Results.LocalRedirect(targetUrl);
+});
+
app.MapRazorComponents()
.AddInteractiveServerRenderMode();
diff --git a/src/BirthList.Web/Resources/SharedResources.fr-FR.resx b/src/BirthList.Web/Resources/SharedResources.fr-FR.resx
new file mode 100644
index 0000000..8063576
--- /dev/null
+++ b/src/BirthList.Web/Resources/SharedResources.fr-FR.resx
@@ -0,0 +1,239 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms, ...
+ System.Resources.ResXResourceWriter, System.Windows.Forms, ...
+
+ Oui
+ Non
+ Enregistrer
+ Annuler
+ Supprimer
+ Modifier
+ Retour
+ Suivant
+ Chargement...
+ Accès refusé.
+ Sélectionner un utilisateur
+ Rechercher un utilisateur
+ Quantité
+ Montant
+ Message
+
+ Langue
+ Gift List
+ Veuillez compléter votre profil (prénom, nom et adresse).
+ Compléter le profil
+ Paramètres du compte
+ Se connecter
+ Se déconnecter
+
+ Liste de naissance
+ Bienvenue sur Gift List
+ Listes que vous gérez
+ Créer
+ Aucune liste pour le moment.
+ Voir
+ Gérer
+ Listes visitées
+ Aucune liste visitée pour le moment.
+ Créer une nouvelle liste
+ Titre
+ Type
+ Thème
+ Par défaut
+ Doux
+ Moderne
+ Naissance
+ Mariage
+ Anniversaire
+ Vous devez être connecté pour créer une liste.
+ Le titre est obligatoire.
+ Veuillez vous connecter pour créer et gérer des listes.
+ Veuillez
+ vous connecter
+ pour créer et gérer des listes.
+
+ Liste
+ Liste introuvable.
+ Adresse de livraison
+ Participation par virement
+ IBAN
+ BIC
+ Seconde main privilégiée
+ Seconde main possible
+ Qté :
+ achetés
+ Prix :
+ Participation :
+ sur {0}{1} atteints
+ atteints
+ Acheté par :
+ Contribué par :
+ Vous ({0})
+ et {0} autre{1}
+ personne
+ s personnes
+ et d'autres
+ Acheté
+ Contribué
+ Connectez-vous pour acheter
+ Acheter
+ Gérer les achats
+ Modifier l'achat
+ Marquer comme acheté
+ Gérer les participations
+ Modifier la participation
+ Participer partiellement
+ Marquer comme acheté
+ Combien d'unités avez-vous achetées ?
+ Ouvrir le lien du produit
+ Enregistrer la contribution
+ Montant transféré
+ Confirmer
+ Confirmer l'achat
+ Sélectionner l'acheteur à annuler
+ Plusieurs utilisateurs ont acheté cet article. Choisissez l'achat à annuler :
+ Annuler
+ Tout annuler
+ Participer partiellement à l'article
+ Choisissez votre mode de participation :
+ Virement IBAN
+ QR code unique
+ QR code par montant
+ Ouvrir le lien de paiement
+ Quel montant avez-vous ajouté pour cet article ?
+ Aucun QR code configuré pour ce montant exact.
+ Gérer l'achat
+ Gérer la participation
+ Utilisateur
+
+ Montant de participation
+ Article introuvable.
+ Veuillez d'abord vous connecter.
+ Participation partielle : {0}
+ Aucun montant représentable ne peut être formé avec les QR codes configurés jusqu'à {0}200.
+ Sélectionnez un montant : {0}{1}
+ Aucune combinaison QR disponible pour ce montant.
+ Combinaison QR suggérée :
+ Ouvrir le lien de paiement
+ J'ai transféré ce montant
+
+ Invitation admin
+ Invitation administrateur
+ Validation de l'invitation...
+ Invitation acceptée. Vous êtes maintenant administrateur.
+ Aller à l'administration
+ L'invitation est invalide ou déjà utilisée.
+
+ Administration de la liste
+ Administration de la liste
+ SMTP n'est pas configuré. Les fonctionnalités e-mail (Identity et invitations admin) sont désactivées. Configurez la section Smtp dans appsettings ou user secrets.
+ Articles
+ Paramètres
+ Administrateurs
+ Adresses
+ Journal d'actions
+ Ajouter ou modifier un article
+ Nom
+ URL du produit
+ Remplissage auto
+ URL de l'image
+ Description
+ Prix
+ Symbole monétaire
+ Quantité souhaitée
+ Participation
+ Objectif de participation
+ Préférence seconde main
+ Seconde main possible
+ Préférer la seconde main
+ Neuf uniquement
+ Donné
+ Catégorie
+ Enregistrer l'article
+ Catégories et articles
+ Nouvelle catégorie
+ Ajouter une catégorie
+ Faites glisser les catégories ou les articles pour réorganiser. Déposez les articles dans une autre catégorie pour les regrouper.
+ Renommer
+ Déposez des articles ici.
+ Nom
+ Qté souhaitée
+ Participation
+ Acheté par / Contribué par
+ Acheté :
+ Contribué :
+ Paramètres de la liste
+ Prénom du bébé
+ Date de naissance
+ Thème
+ Par défaut
+ Doux
+ Moderne
+ Adresse de livraison
+ Les sauts de ligne seront conservés
+ Contenu d'en-tête
+ Texte d'accueil
+ Paramètres du compte bancaire
+ Nom du compte bancaire
+ Afficher le nom du compte bancaire
+ Options de paiement des participations
+ URL du QR code unique
+ Optionnel : un QR code unique que les donateurs peuvent scanner pour n'importe quel montant.
+ QR codes par montant
+ Ajouter un montant QR
+ Aucun QR code par montant configuré.
+ URL du QR code
+ Enregistrer les paramètres
+ Adresses des utilisateurs
+ Aucun utilisateur trouvé pour le moment.
+ E-mail
+ Adresse
+ Administrateurs actuels
+ Aucun administrateur assigné pour le moment.
+ E-mail / Nom
+ Inviter un administrateur
+ e-mail optionnel
+ Créer une invitation
+ Lien d'invitation :
+
+ Journal d'actions - Admin liste
+ Journal d'actions de la liste
+ Ce journal montre toutes les actions des utilisateurs sur cette liste : achats, participations et autres interactions.
+ Aucune action enregistrée pour le moment.
+ Date/Heure
+ Utilisateur
+ Action
+ Article
+ Quantité
+ Montant
+ Détails
+ Liste ouverte
+ Lien article ouvert
+ Achat marqué
+ Achat annulé
+ Achat partiel
+ Participation enregistrée
+
+ Erreur
+ Erreur.
+ Une erreur est survenue lors du traitement de votre demande.
+ ID de requête :
+ Mode Développement
+ Passer en environnement Development affichera des informations plus détaillées sur l'erreur.
+ L'environnement Development ne devrait pas être activé sur une application déployée.
+ Cela peut afficher des informations sensibles issues des exceptions aux utilisateurs finaux.
+ Pour le débogage local, activez l'environnement Development en définissant ASPNETCORE_ENVIRONMENT à Development puis redémarrez l'application.
+
+ Une erreur non gérée est survenue.
+ Recharger
+
+ BirthList
+ Menu de navigation
+ Accueil
+ Se déconnecter
+ S'inscrire
+ Se connecter
+
\ No newline at end of file
diff --git a/src/BirthList.Web/Resources/SharedResources.nl-BE.resx b/src/BirthList.Web/Resources/SharedResources.nl-BE.resx
new file mode 100644
index 0000000..023606d
--- /dev/null
+++ b/src/BirthList.Web/Resources/SharedResources.nl-BE.resx
@@ -0,0 +1,239 @@
+
+
+ text/microsoft-resx
+ 2.0
+ System.Resources.ResXResourceReader, System.Windows.Forms, ...
+ System.Resources.ResXResourceWriter, System.Windows.Forms, ...
+
+ Ja
+ Nee
+ Opslaan
+ Annuleren
+ Verwijderen
+ Bewerken
+ Terug
+ Volgende
+ Laden...
+ Toegang geweigerd.
+ Selecteer gebruiker
+ Zoek gebruiker
+ Aantal
+ Bedrag
+ Bericht
+
+ Taal
+ Gift List
+ Vul je profiel aan (voornaam, achternaam en adres).
+ Profiel aanvullen
+ Accountinstellingen
+ Aanmelden
+ Afmelden
+
+ Geboortelijst
+ Welkom bij Gift List
+ Lijsten die jij beheert
+ Nieuwe maken
+ Nog geen lijsten.
+ Bekijken
+ Beheren
+ Bezochte lijsten
+ Nog geen bezochte lijsten.
+ Nieuwe lijst aanmaken
+ Titel
+ Type
+ Thema
+ Standaard
+ Zacht
+ Modern
+ Geboorte
+ Huwelijk
+ Verjaardag
+ Je moet aangemeld zijn om een lijst aan te maken.
+ Titel is verplicht.
+ Meld je aan om lijsten aan te maken en te beheren.
+ Gelieve
+ aan te melden
+ om lijsten aan te maken en te beheren.
+
+ Lijst
+ Lijst niet gevonden.
+ Leveringsadres
+ Deelname via overschrijving
+ IBAN
+ BIC
+ Tweedehands heeft de voorkeur
+ Tweedehands is mogelijk
+ Aantal:
+ gekocht
+ Prijs:
+ Deelname:
+ van {0}{1} behaald
+ behaald
+ Gekocht door:
+ Bijgedragen door:
+ Jij ({0})
+ en {0} andere {1}
+ persoon
+ personen
+ en anderen
+ Gekocht
+ Bijgedragen
+ Meld je aan om te kopen
+ Kopen
+ Aankopen beheren
+ Aankoop bewerken
+ Markeer als gekocht
+ Deelnames beheren
+ Deelname bewerken
+ Gedeeltelijk vervullen
+ Markeer als gekocht
+ Hoeveel stuks heb je gekocht?
+ Open productlink
+ Bijdrage registreren
+ Overgeschreven bedrag
+ Bevestigen
+ Aankoop bevestigen
+ Selecteer koper om te verwijderen
+ Meerdere gebruikers hebben dit item gekocht. Kies welke aankoop je wil verwijderen:
+ Verwijderen
+ Alles verwijderen
+ Item gedeeltelijk vervullen
+ Kies hoe je wil bijdragen:
+ IBAN-overschrijving
+ Enkele QR-code
+ QR-code per bedrag
+ Open betaallink
+ Hoeveel heb je toegevoegd voor dit item?
+ Geen QR-code ingesteld voor dit exacte bedrag.
+ Aankoop beheren
+ Deelname beheren
+ Gebruiker
+
+ Deelnamebedrag
+ Item niet gevonden.
+ Meld je eerst aan.
+ Gedeeltelijk vervullen: {0}
+ Er kan geen voorstelbaar bedrag gevormd worden met ingestelde QR-codes tot {0}200.
+ Selecteer bedrag: {0}{1}
+ Geen QR-combinatie beschikbaar voor dit bedrag.
+ Voorgestelde QR-combinatie:
+ Open betaallink
+ Ik heb dit bedrag overgeschreven
+
+ Admin-uitnodiging
+ Uitnodiging voor beheerder
+ Uitnodiging valideren...
+ Uitnodiging geaccepteerd. Je bent nu beheerder.
+ Ga naar beheer
+ De uitnodiging is ongeldig of al gebruikt.
+
+ Lijstbeheer
+ Lijstbeheer
+ SMTP is niet geconfigureerd. E-mailfuncties (Identity-mails en admin-uitnodigingen) zijn uitgeschakeld. Configureer de sectie Smtp in appsettings of user secrets.
+ Items
+ Instellingen
+ Beheerders
+ Adressen
+ Actielog
+ Item toevoegen of bewerken
+ Naam
+ Product-URL
+ Automatisch ophalen
+ Afbeeldings-URL
+ Beschrijving
+ Prijs
+ Valutasymbool
+ Gewenst aantal
+ Deelname
+ Doelbedrag deelname
+ Voorkeur tweedehands
+ Tweedehands mogelijk
+ Tweedehands verkiezen
+ Enkel nieuw
+ Gegeven
+ Categorie
+ Item opslaan
+ Categorieën en items
+ Nieuwe categorie
+ Categorie toevoegen
+ Sleep categorieën of items om te herschikken. Zet items in een andere categorie om ze te groeperen.
+ Hernoemen
+ Sleep items hierheen.
+ Naam
+ Gewenst aantal
+ Deelname
+ Gekocht door / Bijgedragen door
+ Gekocht:
+ Bijgedragen:
+ Lijstinstellingen
+ Naam baby
+ Geboortedatum
+ Thema
+ Standaard
+ Zacht
+ Modern
+ Leveringsadres
+ Regeleinden blijven behouden
+ Bovenste inhoud
+ Welkomsttekst
+ Bankrekeninginstellingen
+ Naam bankrekening
+ Naam bankrekening tonen
+ Betaalopties voor deelname
+ URL van enkele QR-code
+ Optioneel: één QR-code die schenkers voor eender welk bedrag kunnen scannen.
+ Bedragsspecifieke QR-codes
+ QR-bedrag toevoegen
+ Geen bedragsspecifieke QR-codes ingesteld.
+ QR-code-URL
+ Instellingen opslaan
+ Gebruikersadressen
+ Nog geen gebruikers gevonden.
+ E-mail
+ Adres
+ Huidige beheerders
+ Nog geen beheerders toegewezen.
+ E-mail / Naam
+ Beheerder uitnodigen
+ optionele e-mail
+ Uitnodiging aanmaken
+ Uitnodigingslink:
+
+ Actielog - Lijstbeheer
+ Actielog van de lijst
+ Dit log toont alle gebruikersacties op deze lijst: aankopen, deelnames en andere interacties.
+ Nog geen acties geregistreerd.
+ Datum/tijd
+ Gebruiker
+ Actie
+ Item
+ Aantal
+ Bedrag
+ Details
+ Lijst geopend
+ Itemlink geopend
+ Aankoop gemarkeerd
+ Aankoop verwijderd
+ Gedeeltelijke aankoop
+ Deelname geregistreerd
+
+ Fout
+ Fout.
+ Er is een fout opgetreden bij het verwerken van je aanvraag.
+ Aanvraag-ID:
+ Ontwikkelmodus
+ Wanneer je naar de Development-omgeving schakelt, zie je meer gedetailleerde foutinformatie.
+ De Development-omgeving mag niet ingeschakeld zijn op gedeployde toepassingen.
+ Dat kan gevoelige informatie uit uitzonderingen tonen aan eindgebruikers.
+ Voor lokaal debuggen: zet ASPNETCORE_ENVIRONMENT op Development en herstart de app.
+
+ Er is een onverwachte fout opgetreden.
+ Herladen
+
+ BirthList
+ Navigatiemenu
+ Start
+ Afmelden
+ Registreren
+ Aanmelden
+
\ No newline at end of file
diff --git a/src/BirthList.Web/Resources/SharedResources.qps-Ploc.resx b/src/BirthList.Web/Resources/SharedResources.qps-Ploc.resx
new file mode 100644
index 0000000..2307729
--- /dev/null
+++ b/src/BirthList.Web/Resources/SharedResources.qps-Ploc.resx
@@ -0,0 +1,675 @@
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, ...
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, ...
+
+
+ [[Yes]]
+
+
+ [[No]]
+
+
+ [[Save]]
+
+
+ [[Cancel]]
+
+
+ [[Remove]]
+
+
+ [[Edit]]
+
+
+ [[Back]]
+
+
+ [[Next]]
+
+
+ [[Loading...]]
+
+
+ [[Access denied.]]
+
+
+ [[Select user]]
+
+
+ [[Search user]]
+
+
+ [[Quantity]]
+
+
+ [[Amount]]
+
+
+ [[Message]]
+
+
+ [[Language]]
+
+
+ [[Gift List]]
+
+
+ [[Please complete your profile (first name, last name, and address).]]
+
+
+ [[Complete profile]]
+
+
+ [[Account settings]]
+
+
+ [[Sign in]]
+
+
+ [[Sign out]]
+
+
+ [[Birth Registry]]
+
+
+ [[Welcome to Gift List]]
+
+
+ [[Registries you manage]]
+
+
+ [[Create new]]
+
+
+ [[No registries yet.]]
+
+
+ [[View]]
+
+
+ [[Manage]]
+
+
+ [[Visited registries]]
+
+
+ [[No visited registries yet.]]
+
+
+ [[Create new registry]]
+
+
+ [[Title]]
+
+
+ [[Type]]
+
+
+ [[Theme]]
+
+
+ [[Default]]
+
+
+ [[Soft]]
+
+
+ [[Modern]]
+
+
+ [[Birth]]
+
+
+ [[Wedding]]
+
+
+ [[Birthday]]
+
+
+ [[You must be logged in to create a registry.]]
+
+
+ [[Title is required.]]
+
+
+ [[Please ]]
+
+
+ [[log in]]
+
+
+ [[ to create and manage registries.]]
+
+
+ [[Registry]]
+
+
+ [[Registry not found.]]
+
+
+ [[Shipping address]]
+
+
+ [[Bank transfer participation]]
+
+
+ [[IBAN]]
+
+
+ [[BIC]]
+
+
+ [[Second-hand preferred]]
+
+
+ [[Second-hand optional]]
+
+
+ [[Qty:]]
+
+
+ [[purchased]]
+
+
+ [[Price:]]
+
+
+ [[Participation:]]
+
+
+ [[out of {0}{1} fulfilled]]
+
+
+ [[fulfilled]]
+
+
+ [[Purchased by:]]
+
+
+ [[Contributed by:]]
+
+
+ [[You ({0})]]
+
+
+ [[and {0} other {1}]]
+
+
+ [[person]]
+
+
+ [[people]]
+
+
+ [[and others]]
+
+
+ [[Purchased]]
+
+
+ [[Contributed]]
+
+
+ [[Login to purchase]]
+
+
+ [[Purchase]]
+
+
+ [[Manage purchases]]
+
+
+ [[Edit purchase]]
+
+
+ [[Mark purchased]]
+
+
+ [[Manage participations]]
+
+
+ [[Edit participation]]
+
+
+ [[Partially fulfill]]
+
+
+ [[Mark as purchased]]
+
+
+ [[How many units did you purchase?]]
+
+
+ [[Open product link]]
+
+
+ [[Confirm purchase]]
+
+
+ [[Log contribution]]
+
+
+ [[Transferred amount]]
+
+
+ [[Confirm]]
+
+
+ [[Select purchaser to unmark]]
+
+
+ [[Multiple users have purchased this item. Choose which purchase to unmark:]]
+
+
+ [[Unmark]]
+
+
+ [[Unmark all]]
+
+
+ [[Partially fulfill item]]
+
+
+ [[Select how you want to donate:]]
+
+
+ [[IBAN transfer]]
+
+
+ [[Single QR code]]
+
+
+ [[QR code per amount]]
+
+
+ [[Open payment link]]
+
+
+ [[How much did you add for this item?]]
+
+
+ [[No QR code configured for this exact amount.]]
+
+
+ [[Manage purchase]]
+
+
+ [[Manage participation]]
+
+
+ [[User]]
+
+
+ [[Contribution Amount]]
+
+
+ [[Item not found.]]
+
+
+ [[Please log in first.]]
+
+
+ [[Partially fulfill: {0}]]
+
+
+ [[No representable amount can be formed from configured QR codes up to {0}200.]]
+
+
+ [[Select amount: {0}{1}]]
+
+
+ [[No QR combination available for this amount.]]
+
+
+ [[Suggested QR combination:]]
+
+
+ [[Open payment link]]
+
+
+ [[I transferred this amount]]
+
+
+ [[Admin Invite]]
+
+
+ [[Admin invitation]]
+
+
+ [[Validating invitation...]]
+
+
+ [[Invitation accepted. You are now an admin.]]
+
+
+ [[Go to admin]]
+
+
+ [[The invitation is invalid or already used.]]
+
+
+ [[Registry Admin]]
+
+
+ [[Registry Admin]]
+
+
+ [[SMTP is not configured. Email features (identity emails and admin invite emails) are disabled. Configure the Smtp section in appsettings or user secrets.]]
+
+
+ [[Items]]
+
+
+ [[Settings]]
+
+
+ [[Administrators]]
+
+
+ [[Addresses]]
+
+
+ [[Action Log]]
+
+
+ [[Add or edit item]]
+
+
+ [[Name]]
+
+
+ [[Product URL]]
+
+
+ [[Auto fetch]]
+
+
+ [[Picture URL]]
+
+
+ [[Description]]
+
+
+ [[Price]]
+
+
+ [[Currency symbol]]
+
+
+ [[Desired qty]]
+
+
+ [[Participation]]
+
+
+ [[Participation target]]
+
+
+ [[Second hand preference]]
+
+
+ [[Second hand optional]]
+
+
+ [[Prefer second hand]]
+
+
+ [[New only]]
+
+
+ [[Given]]
+
+
+ [[Category]]
+
+
+ [[Save item]]
+
+
+ [[Categories and items]]
+
+
+ [[New category]]
+
+
+ [[Add category]]
+
+
+ [[Drag categories or items to reorder. Drop items into another category to regroup them.]]
+
+
+ [[Rename]]
+
+
+ [[Drop items here.]]
+
+
+ [[Name]]
+
+
+ [[Desired Qty]]
+
+
+ [[Participation]]
+
+
+ [[Purchased by / Contributed by]]
+
+
+ [[Purchased:]]
+
+
+ [[Contributed:]]
+
+
+ [[Registry Settings]]
+
+
+ [[Baby name]]
+
+
+ [[Birth date]]
+
+
+ [[Theme]]
+
+
+ [[Default]]
+
+
+ [[Soft]]
+
+
+ [[Modern]]
+
+
+ [[Shipping address]]
+
+
+ [[Line breaks will be preserved]]
+
+
+ [[Top content]]
+
+
+ [[Welcome text]]
+
+
+ [[Bank Account Settings]]
+
+
+ [[Bank account name]]
+
+
+ [[Display bank account name]]
+
+
+ [[Contribution Payment Options]]
+
+
+ [[Single QR code URL]]
+
+
+ [[Optional: one QR code that donors can scan for any amount.]]
+
+
+ [[Amount-specific QR codes]]
+
+
+ [[Add QR amount]]
+
+
+ [[No amount-specific QR codes configured.]]
+
+
+ [[QR code URL]]
+
+
+ [[Save settings]]
+
+
+ [[User addresses]]
+
+
+ [[No users found yet.]]
+
+
+ [[Email]]
+
+
+ [[Address]]
+
+
+ [[Current administrators]]
+
+
+ [[No admins assigned yet.]]
+
+
+ [[Email / Name]]
+
+
+ [[Invite administrator]]
+
+
+ [[optional email]]
+
+
+ [[Create invite]]
+
+
+ [[Invite link:]]
+
+
+ [[Action Log - Registry Admin]]
+
+
+ [[Registry Action Log]]
+
+
+ [[This log shows all user actions on this registry: purchases, contributions, and other interactions.]]
+
+
+ [[No actions recorded yet.]]
+
+
+ [[Date/Time]]
+
+
+ [[User]]
+
+
+ [[Action]]
+
+
+ [[Item]]
+
+
+ [[Quantity]]
+
+
+ [[Amount]]
+
+
+ [[Details]]
+
+
+ [[Registry opened]]
+
+
+ [[Item link opened]]
+
+
+ [[Purchase marked]]
+
+
+ [[Purchase unmarked]]
+
+
+ [[Partial purchase]]
+
+
+ [[Contribution logged]]
+
+
+ [[Error]]
+
+
+ [[Error.]]
+
+
+ [[An error occurred while processing your request.]]
+
+
+ [[Request ID:]]
+
+
+ [[Development Mode]]
+
+
+ [[Swapping to Development environment will display more detailed information about the error that occurred.]]
+
+
+ [[The Development environment shouldn't be enabled for deployed applications.]]
+
+
+ [[It can result in displaying sensitive information from exceptions to end users.]]
+
+
+ [[For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app.]]
+
+
+ [[An unhandled error has occurred.]]
+
+
+ [[Reload]]
+
+
+ [[BirthList]]
+
+
+ [[Navigation menu]]
+
+
+ [[Home]]
+
+
+ [[Logout]]
+
+
+ [[Register]]
+
+
+ [[Login]]
+
+
\ No newline at end of file
diff --git a/src/BirthList.Web/Resources/SharedResources.resx b/src/BirthList.Web/Resources/SharedResources.resx
new file mode 100644
index 0000000..ba4d4a3
--- /dev/null
+++ b/src/BirthList.Web/Resources/SharedResources.resx
@@ -0,0 +1,247 @@
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, ...
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, ...
+
+
+ Yes
+ No
+ Save
+ Cancel
+ Remove
+ Edit
+ Back
+ Next
+ Loading...
+ Access denied.
+ Select user
+ Search user
+ Quantity
+ Amount
+ Message
+
+ Language
+ Gift List
+ Please complete your profile (first name, last name, and address).
+ Complete profile
+ Account settings
+ Sign in
+ Sign out
+
+ Birth Registry
+ Welcome to Gift List
+ Registries you manage
+ Create new
+ No registries yet.
+ View
+ Manage
+ Visited registries
+ No visited registries yet.
+ Create new registry
+ Title
+ Type
+ Theme
+ Default
+ Soft
+ Modern
+ Birth
+ Wedding
+ Birthday
+ You must be logged in to create a registry.
+ Title is required.
+ Please
+ log in
+ to create and manage registries.
+
+ Registry
+ Registry not found.
+ Shipping address
+ Bank transfer participation
+ IBAN
+ BIC
+ Second-hand preferred
+ Second-hand optional
+ Qty:
+ purchased
+ Price:
+ Participation:
+ out of {0}{1} fulfilled
+ fulfilled
+ Purchased by:
+ Contributed by:
+ You ({0})
+ and {0} other {1}
+ person
+ people
+ and others
+ Purchased
+ Contributed
+ Login to purchase
+ Purchase
+ Manage purchases
+ Edit purchase
+ Mark purchased
+ Manage participations
+ Edit participation
+ Partially fulfill
+ Mark as purchased
+ How many units did you purchase?
+ Open product link
+ Confirm purchase
+ Log contribution
+ Transferred amount
+ Confirm
+ Select purchaser to unmark
+ Multiple users have purchased this item. Choose which purchase to unmark:
+ Unmark
+ Unmark all
+ Partially fulfill item
+ Select how you want to donate:
+ IBAN transfer
+ Single QR code
+ QR code per amount
+ Open payment link
+ How much did you add for this item?
+ No QR code configured for this exact amount.
+ Manage purchase
+ Manage participation
+ User
+
+ Contribution Amount
+ Item not found.
+ Please log in first.
+ Partially fulfill: {0}
+ No representable amount can be formed from configured QR codes up to {0}200.
+ Select amount: {0}{1}
+ No QR combination available for this amount.
+ Suggested QR combination:
+ Open payment link
+ I transferred this amount
+
+ Admin Invite
+ Admin invitation
+ Validating invitation...
+ Invitation accepted. You are now an admin.
+ Go to admin
+ The invitation is invalid or already used.
+
+ Registry Admin
+ Registry Admin
+ SMTP is not configured. Email features (identity emails and admin invite emails) are disabled. Configure the Smtp section in appsettings or user secrets.
+ Items
+ Settings
+ Administrators
+ Addresses
+ Action Log
+ Add or edit item
+ Name
+ Product URL
+ Auto fetch
+ Picture URL
+ Description
+ Price
+ Currency symbol
+ Desired qty
+ Participation
+ Participation target
+ Second hand preference
+ Second hand optional
+ Prefer second hand
+ New only
+ Given
+ Category
+ Save item
+ Categories and items
+ New category
+ Add category
+ Drag categories or items to reorder. Drop items into another category to regroup them.
+ Rename
+ Drop items here.
+ Name
+ Desired Qty
+ Participation
+ Purchased by / Contributed by
+ Purchased:
+ Contributed:
+ Registry Settings
+ Baby name
+ Birth date
+ Theme
+ Default
+ Soft
+ Modern
+ Shipping address
+ Line breaks will be preserved
+ Top content
+ Welcome text
+ Bank Account Settings
+ Bank account name
+ Display bank account name
+ Contribution Payment Options
+ Single QR code URL
+ Optional: one QR code that donors can scan for any amount.
+ Amount-specific QR codes
+ Add QR amount
+ No amount-specific QR codes configured.
+ QR code URL
+ Save settings
+ User addresses
+ No users found yet.
+ Email
+ Address
+ Current administrators
+ No admins assigned yet.
+ Email / Name
+ Invite administrator
+ optional email
+ Create invite
+ Invite link:
+
+ Action Log - Registry Admin
+ Registry Action Log
+ This log shows all user actions on this registry: purchases, contributions, and other interactions.
+ No actions recorded yet.
+ Date/Time
+ User
+ Action
+ Item
+ Quantity
+ Amount
+ Details
+ Registry opened
+ Item link opened
+ Purchase marked
+ Purchase unmarked
+ Partial purchase
+ Contribution logged
+
+ Error
+ Error.
+ An error occurred while processing your request.
+ Request ID:
+ Development Mode
+ Swapping to Development environment will display more detailed information about the error that occurred.
+ The Development environment shouldn't be enabled for deployed applications.
+ It can result in displaying sensitive information from exceptions to end users.
+ For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app.
+
+ An unhandled error has occurred.
+ Reload
+
+ BirthList
+ Navigation menu
+ Home
+ Logout
+ Register
+ Login
+
+
diff --git a/src/BirthList.Web/SharedResources.cs b/src/BirthList.Web/SharedResources.cs
new file mode 100644
index 0000000..382296c
--- /dev/null
+++ b/src/BirthList.Web/SharedResources.cs
@@ -0,0 +1,5 @@
+namespace BirthList.Web;
+
+public sealed class SharedResources
+{
+}