Add localization support and UI enhancements
Build and Push Docker Image / build-and-push (push) Successful in 27s

Introduced centralized RESX localization with hierarchical keys for multi-language support. Added resource files for English, French, Dutch (Belgium), and pseudo-localization.

Replaced hardcoded strings in Razor components with localized string references. Added a `PreferredCulture` property to `ApplicationUser` with a migration to store user-specific culture preferences.

Implemented `LocalizationService` to manage supported cultures and user preferences. Added a language picker in `TopBar.razor` and configured `RequestLocalizationOptions` for culture detection.

Localized registry-related components and error messages. Enhanced UI elements to dynamically display localized content. Added pseudo-localization to identify hardcoded strings during development.
This commit is contained in:
Arne Moerman
2026-05-19 23:30:30 +02:00
parent 9f3ae1051c
commit b59ad6e5ab
23 changed files with 2155 additions and 261 deletions
+2 -1
View File
@@ -6,7 +6,7 @@
- Authentication must support local accounts plus Microsoft and Google login; admin invite links are single-use and not email-locked. - Authentication must support local accounts plus Microsoft and Google login; admin invite links are single-use and not email-locked.
- Money participation defaults to EUR and tracks fulfilled amount per item via bank-transfer contributions with item-linked messages. - Money participation defaults to EUR and tracks fulfilled amount per item via bank-transfer contributions with item-linked messages.
- Registry visibility is public-by-link initially, but logged-in users who visited a registry should be able to rediscover it in-app. - Registry visibility is public-by-link initially, but logged-in users who visited a registry should be able to rediscover it in-app.
- Identities must be hidden from non-admin users for privacy, only admins and the user who purchased/contributed can see that he did so. Other users may see a generic indication that a contrubution/purchase was made but never a name/email of another user. - Identities must be hidden from non-admin users for privacy; only admins and the user who purchased/contributed can see that he did so. Other users may see a generic indication that a contribution/purchase was made but never a name/email of another user.
- Use SQL Server as the default provider and set `RequireConfirmedAccount=false` for MVP testing; the first registered account should be the owner with full access. - Use SQL Server as the default provider and set `RequireConfirmedAccount=false` for MVP testing; the first registered account should be the owner with full access.
## Technical Specifications ## Technical Specifications
@@ -14,3 +14,4 @@
- Support registry type theming from day one to enhance user experience. - Support registry type theming from day one to enhance user experience.
- URL autofetch should utilize OpenGraph/meta tags for better link previews. - URL autofetch should utilize OpenGraph/meta tags for better link previews.
- Use Blazored.TextEditor for rich text editing. - Use Blazored.TextEditor for rich text editing.
- Use centralized RESX localization with hierarchical keys (not sentence keys); prefer a shared resource file over per-component files, and use pseudo-localization (qps-Ploc) to catch hardcoded strings.
@@ -9,7 +9,7 @@
</main> </main>
<div id="blazor-error-ui"> <div id="blazor-error-ui">
An unhandled error has occurred. @L["MainLayout.UnhandledError"]
<a href="" class="reload">Reload</a> <a href="" class="reload">@L["MainLayout.Reload"]</a>
<a class="dismiss">🗙</a> <a class="dismiss">🗙</a>
</div> </div>
@@ -4,17 +4,17 @@
<div class="top-row ps-3 navbar navbar-dark"> <div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="">BirthList</a> <a class="navbar-brand" href="">@L["NavMenu.Brand"]</a>
</div> </div>
</div> </div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" /> <input type="checkbox" title="@L["NavMenu.NavigationMenu"]" class="navbar-toggler" />
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()"> <div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column"> <nav class="flex-column">
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home <span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> @L["NavMenu.Home"]
</NavLink> </NavLink>
</div> </div>
@@ -30,7 +30,7 @@
<AntiforgeryToken /> <AntiforgeryToken />
<input type="hidden" name="ReturnUrl" value="@currentUrl" /> <input type="hidden" name="ReturnUrl" value="@currentUrl" />
<button type="submit" class="nav-link"> <button type="submit" class="nav-link">
<span class="bi bi-arrow-bar-left-nav-menu" aria-hidden="true"></span> Logout <span class="bi bi-arrow-bar-left-nav-menu" aria-hidden="true"></span> @L["NavMenu.Logout"]
</button> </button>
</form> </form>
</div> </div>
@@ -38,12 +38,12 @@
<NotAuthorized> <NotAuthorized>
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link" href="Account/Register"> <NavLink class="nav-link" href="Account/Register">
<span class="bi bi-person-nav-menu" aria-hidden="true"></span> Register <span class="bi bi-person-nav-menu" aria-hidden="true"></span> @L["NavMenu.Register"]
</NavLink> </NavLink>
</div> </div>
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link" href="Account/Login"> <NavLink class="nav-link" href="Account/Login">
<span class="bi bi-person-badge-nav-menu" aria-hidden="true"></span> Login <span class="bi bi-person-badge-nav-menu" aria-hidden="true"></span> @L["NavMenu.Login"]
</NavLink> </NavLink>
</div> </div>
</NotAuthorized> </NotAuthorized>
@@ -1,3 +1,5 @@
@using System.Globalization
@using BirthList.Web.Features.Localization
@using BirthList.Web.Features.Registries @using BirthList.Web.Features.Registries
@using BirthList.Web.Services @using BirthList.Web.Services
@implements IDisposable @implements IDisposable
@@ -10,39 +12,51 @@
@if (ShowProfileCompletionPrompt) @if (ShowProfileCompletionPrompt)
{ {
<div class="profile-completion-banner"> <div class="profile-completion-banner">
<span>Please complete your profile (first name, last name, and address).</span> <span>@L["TopBar.ProfilePrompt"]</span>
<a href="/Account/Manage" class="profile-completion-link">Complete profile</a> <a href="/Account/Manage" class="profile-completion-link">@L["TopBar.CompleteProfile"]</a>
</div> </div>
} }
<nav class="top-bar"> <nav class="top-bar">
<div class="top-bar-left"> <div class="top-bar-left">
<a href="/" class="top-bar-brand" title="Gift List"> <a href="/" class="top-bar-brand" title="@L["TopBar.Brand"]">
<i class="bi bi-gift" aria-hidden="true"></i> <i class="bi bi-gift" aria-hidden="true"></i>
<span class="brand-text">Gift List</span> <span class="brand-text">@L["TopBar.Brand"]</span>
</a> </a>
</div> </div>
<div class="top-bar-right"> <div class="top-bar-right">
<form action="/set-language" method="post" class="d-inline-flex align-items-center me-2">
<AntiforgeryToken />
<input type="hidden" name="returnUrl" value="@currentAbsolutePath" />
<label for="language-picker" class="visually-hidden">@L["TopBar.Language"]</label>
<select id="language-picker" name="culture" class="form-select form-select-sm" onchange="this.form.submit()">
@foreach (var option in LanguageOptions)
{
<option value="@option.Culture" selected="@(string.Equals(option.Culture, CurrentCulture, StringComparison.OrdinalIgnoreCase))">@option.DisplayName</option>
}
</select>
</form>
<AuthorizeView> <AuthorizeView>
<Authorized> <Authorized>
<div class="user-info"> <div class="user-info">
<i class="bi bi-person-circle" aria-hidden="true"></i> <i class="bi bi-person-circle" aria-hidden="true"></i>
<span class="user-email">@context.User.Identity?.Name</span> <span class="user-email">@context.User.Identity?.Name</span>
</div> </div>
<a href="Account/Manage" class="top-bar-link" title="Account settings"> <a href="Account/Manage" class="top-bar-link" title="@L["TopBar.AccountSettings"]">
<i class="bi bi-gear" aria-hidden="true"></i> <i class="bi bi-gear" aria-hidden="true"></i>
</a> </a>
<form action="Account/Logout" method="post" class="logout-form"> <form action="Account/Logout" method="post" class="logout-form">
<AntiforgeryToken /> <AntiforgeryToken />
<input type="hidden" name="ReturnUrl" value="@currentUrl" /> <input type="hidden" name="ReturnUrl" value="@currentUrl" />
<button type="submit" class="top-bar-link logout-btn" title="Sign out"> <button type="submit" class="top-bar-link logout-btn" title="@L["TopBar.SignOut"]">
<i class="bi bi-box-arrow-right" aria-hidden="true"></i> <i class="bi bi-box-arrow-right" aria-hidden="true"></i>
</button> </button>
</form> </form>
</Authorized> </Authorized>
<NotAuthorized> <NotAuthorized>
<a href="Account/Login" class="top-bar-link" title="Sign in"> <a href="Account/Login" class="top-bar-link" title="@L["TopBar.SignIn"]">
<i class="bi bi-box-arrow-right" aria-hidden="true"></i> <i class="bi bi-box-arrow-right" aria-hidden="true"></i>
</a> </a>
</NotAuthorized> </NotAuthorized>
@@ -52,11 +66,24 @@
@code { @code {
private string currentUrl = ""; private string currentUrl = "";
private string currentAbsolutePath = "/";
private bool ShowProfileCompletionPrompt { get; set; } private bool ShowProfileCompletionPrompt { get; set; }
private string CurrentCulture { get; set; } = "en";
private static readonly IReadOnlyList<LanguageOption> LanguageOptions =
[
new("en", "English"),
new("nl-NL", "Nederlands (NL)"),
new("nl-BE", "Nederlands (BE)"),
new("fr-FR", "Français"),
new("qps-Ploc", "Pseudo")
];
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
currentUrl = GetRelativePath(NavigationManager.Uri); currentUrl = GetRelativePath(NavigationManager.Uri);
currentAbsolutePath = BuildAbsolutePath(NavigationManager.Uri);
CurrentCulture = CultureInfo.CurrentUICulture.Name;
NavigationManager.LocationChanged += OnLocationChanged; NavigationManager.LocationChanged += OnLocationChanged;
await RefreshProfileCompletionPromptAsync().ConfigureAwait(false); await RefreshProfileCompletionPromptAsync().ConfigureAwait(false);
@@ -65,6 +92,8 @@
private async void OnLocationChanged(object? sender, LocationChangedEventArgs e) private async void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{ {
currentUrl = GetRelativePath(e.Location); currentUrl = GetRelativePath(e.Location);
currentAbsolutePath = BuildAbsolutePath(e.Location);
CurrentCulture = CultureInfo.CurrentUICulture.Name;
await RefreshProfileCompletionPromptAsync().ConfigureAwait(false); await RefreshProfileCompletionPromptAsync().ConfigureAwait(false);
await InvokeAsync(StateHasChanged).ConfigureAwait(false); await InvokeAsync(StateHasChanged).ConfigureAwait(false);
} }
@@ -99,6 +128,19 @@
return relativePath; return relativePath;
} }
private string BuildAbsolutePath(string absoluteUri)
{
if (!Uri.TryCreate(absoluteUri, UriKind.Absolute, out var uri))
{
return "/";
}
var pathAndQuery = uri.PathAndQuery;
return string.IsNullOrWhiteSpace(pathAndQuery) ? "/" : pathAndQuery;
}
private sealed record LanguageOption(string Culture, string DisplayName);
void IDisposable.Dispose() void IDisposable.Dispose()
{ {
NavigationManager.LocationChanged -= OnLocationChanged; NavigationManager.LocationChanged -= OnLocationChanged;
+9 -10
View File
@@ -1,27 +1,26 @@
@page "/Error" @page "/Error"
@using System.Diagnostics @using System.Diagnostics
<PageTitle>Error</PageTitle> <PageTitle>@L["Error.PageTitle"]</PageTitle>
<h1 class="text-danger">Error.</h1> <h1 class="text-danger">@L["Error.Title"]</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2> <h2 class="text-danger">@L["Error.Subtitle"]</h2>
@if (ShowRequestId) @if (ShowRequestId)
{ {
<p> <p>
<strong>Request ID:</strong> <code>@RequestId</code> <strong>@L["Error.RequestId"]</strong> <code>@RequestId</code>
</p> </p>
} }
<h3>Development Mode</h3> <h3>@L["Error.DevMode"]</h3>
<p> <p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred. @L["Error.DevHint1"]
</p> </p>
<p> <p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong> <strong>@L["Error.DevHint2"]</strong>
It can result in displaying sensitive information from exceptions to end users. @L["Error.DevHint3"]
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> @L["Error.DevHint4"]
and restarting the app.
</p> </p>
@code{ @code{
+27 -25
View File
@@ -2,23 +2,23 @@
@using BirthList.Web.Features.Registries @using BirthList.Web.Features.Registries
<PageTitle>Birth Registry</PageTitle> <PageTitle>@L["Home.PageTitle"]</PageTitle>
<h1>Welcome to Gift List</h1> <h1>@L["Home.Welcome"]</h1>
<AuthorizeView> <AuthorizeView>
<Authorized Context="authState"> <Authorized Context="authState">
<div class="registry-sections"> <div class="registry-sections">
<div class="mb-4"> <div class="mb-4">
<div class="section-header"> <div class="section-header">
<h2>Registries you manage</h2> <h2>@L["Home.ManagedRegistries"]</h2>
<button class="btn btn-primary btn-sm" @onclick="() => ShowCreateForm = !ShowCreateForm"> <button class="btn btn-primary btn-sm" @onclick="() => ShowCreateForm = !ShowCreateForm">
<span class="bi bi-plus"></span> Create new <span class="bi bi-plus"></span> @L["Home.CreateNew"]
</button> </button>
</div> </div>
@if (MyRegistries.Count == 0) @if (MyRegistries.Count == 0)
{ {
<p class="text-muted">No registries yet.</p> <p class="text-muted">@L["Home.NoRegistries"]</p>
} }
else else
{ {
@@ -28,8 +28,8 @@
<div class="registry-card"> <div class="registry-card">
<h3>@registry.Title</h3> <h3>@registry.Title</h3>
<div class="registry-actions"> <div class="registry-actions">
<a href="/registry/@registry.PublicLinkCode" class="btn btn-outline-primary btn-sm">View</a> <a href="/registry/@registry.PublicLinkCode" class="btn btn-outline-primary btn-sm">@L["Home.View"]</a>
<a href="/registry/@registry.Id/admin" class="btn btn-outline-secondary btn-sm">Manage</a> <a href="/registry/@registry.Id/admin" class="btn btn-outline-secondary btn-sm">@L["Home.Manage"]</a>
</div> </div>
</div> </div>
} }
@@ -38,10 +38,10 @@
</div> </div>
<div class="mb-4"> <div class="mb-4">
<h2>Visited registries</h2> <h2>@L["Home.VisitedRegistries"]</h2>
@if (VisitedRegistries.Count == 0) @if (VisitedRegistries.Count == 0)
{ {
<p class="text-muted">No visited registries yet.</p> <p class="text-muted">@L["Home.NoVisitedRegistries"]</p>
} }
else else
{ {
@@ -51,7 +51,7 @@
<div class="registry-card"> <div class="registry-card">
<h3>@registry.Title</h3> <h3>@registry.Title</h3>
<div class="registry-actions"> <div class="registry-actions">
<a href="/registry/@registry.PublicLinkCode" class="btn btn-outline-primary btn-sm">View</a> <a href="/registry/@registry.PublicLinkCode" class="btn btn-outline-primary btn-sm">@L["Home.View"]</a>
</div> </div>
</div> </div>
} }
@@ -65,30 +65,30 @@
<div class="create-registry-modal-overlay" @onclick="() => ShowCreateForm = false"> <div class="create-registry-modal-overlay" @onclick="() => ShowCreateForm = false">
<div class="create-registry-modal" @onclick:stopPropagation="true"> <div class="create-registry-modal" @onclick:stopPropagation="true">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title">Create new registry</h3> <h3 class="modal-title">@L["Home.CreateRegistryTitle"]</h3>
<button type="button" class="btn-close" @onclick="() => ShowCreateForm = false"></button> <button type="button" class="btn-close" @onclick="() => ShowCreateForm = false"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<EditForm Model="Model" OnValidSubmit="CreateRegistryAsync" Context="formContext" FormName="create-registry-form"> <EditForm Model="Model" OnValidSubmit="CreateRegistryAsync" Context="formContext" FormName="create-registry-form">
<DataAnnotationsValidator /> <DataAnnotationsValidator />
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Title</label> <label class="form-label">@L["Home.Title"]</label>
<InputText class="form-control" @bind-Value="Model.Title" /> <InputText class="form-control" @bind-Value="Model.Title" />
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Type</label> <label class="form-label">@L["Home.Type"]</label>
<InputSelect class="form-select" @bind-Value="Model.RegistryType"> <InputSelect class="form-select" @bind-Value="Model.RegistryType">
<option value="@BirthList.Domain.Entities.RegistryType.Birth">Birth</option> <option value="@BirthList.Domain.Entities.RegistryType.Birth">@L["Home.RegistryType.Birth"]</option>
<option value="@BirthList.Domain.Entities.RegistryType.Wedding">Wedding</option> <option value="@BirthList.Domain.Entities.RegistryType.Wedding">@L["Home.RegistryType.Wedding"]</option>
<option value="@BirthList.Domain.Entities.RegistryType.Birthday">Birthday</option> <option value="@BirthList.Domain.Entities.RegistryType.Birthday">@L["Home.RegistryType.Birthday"]</option>
</InputSelect> </InputSelect>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Theme</label> <label class="form-label">@L["Home.Theme"]</label>
<InputSelect class="form-select" @bind-Value="Model.ThemeKey"> <InputSelect class="form-select" @bind-Value="Model.ThemeKey">
<option value="default">Default</option> <option value="default">@L["Home.Theme.Default"]</option>
<option value="soft">Soft</option> <option value="soft">@L["Home.Theme.Soft"]</option>
<option value="modern">Modern</option> <option value="modern">@L["Home.Theme.Modern"]</option>
</InputSelect> </InputSelect>
</div> </div>
@if (!string.IsNullOrWhiteSpace(ErrorMessage)) @if (!string.IsNullOrWhiteSpace(ErrorMessage))
@@ -96,8 +96,8 @@
<div class="alert alert-danger mb-3">@ErrorMessage</div> <div class="alert alert-danger mb-3">@ErrorMessage</div>
} }
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" @onclick="() => ShowCreateForm = false">Cancel</button> <button type="button" class="btn btn-outline-secondary" @onclick="() => ShowCreateForm = false">@L["Common.Cancel"]</button>
<button class="btn btn-primary" type="submit">Create</button> <button class="btn btn-primary" type="submit">@L["Home.CreateNew"]</button>
</div> </div>
</EditForm> </EditForm>
</div> </div>
@@ -107,7 +107,9 @@
</Authorized> </Authorized>
<NotAuthorized> <NotAuthorized>
<div class="alert alert-info mt-4"> <div class="alert alert-info mt-4">
<p>Please <a href="Account/Login">log in</a> to create and manage registries.</p> <p>
@L["Home.LoginPromptPrefix"]<a href="Account/Login">@L["Home.LoginPromptLinkText"]</a>@L["Home.LoginPromptSuffix"]
</p>
</div> </div>
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
@@ -136,13 +138,13 @@
var userId = await RegistryUserContext.GetUserIdAsync(CancellationToken.None).ConfigureAwait(false); var userId = await RegistryUserContext.GetUserIdAsync(CancellationToken.None).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(userId)) if (string.IsNullOrWhiteSpace(userId))
{ {
ErrorMessage = "You must be logged in to create a registry."; ErrorMessage = L["Home.MustBeLoggedIn"];
return; return;
} }
if (string.IsNullOrWhiteSpace(Model.Title)) if (string.IsNullOrWhiteSpace(Model.Title))
{ {
ErrorMessage = "Title is required."; ErrorMessage = L["Home.TitleRequired"];
return; return;
} }
@@ -4,24 +4,24 @@
@using BirthList.Web.Features.Registries @using BirthList.Web.Features.Registries
@using BirthList.Web.Authorization @using BirthList.Web.Authorization
<PageTitle>Action Log - Registry Admin</PageTitle> <PageTitle>@L["RegistryActionLog.PageTitle"]</PageTitle>
@if (!IsAuthorized) @if (!IsAuthorized)
{ {
<p>Access denied.</p> <p>@L["Common.AccessDenied"]</p>
} }
else else
{ {
<h1>Registry Action Log</h1> <h1>@L["RegistryActionLog.Title"]</h1>
<p class="text-muted">This log shows all user actions on this registry: purchases, contributions, and other interactions.</p> <p class="text-muted">@L["RegistryActionLog.Description"]</p>
@if (ActionLogs is null) @if (ActionLogs is null)
{ {
<p>Loading...</p> <p>@L["Common.Loading"]</p>
} }
else if (ActionLogs.Count == 0) else if (ActionLogs.Count == 0)
{ {
<p class="text-muted">No actions recorded yet.</p> <p class="text-muted">@L["RegistryActionLog.NoActions"]</p>
} }
else else
{ {
@@ -29,13 +29,13 @@ else
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>Date/Time</th> <th>@L["RegistryActionLog.DateTime"]</th>
<th>User</th> <th>@L["RegistryActionLog.User"]</th>
<th>Action</th> <th>@L["RegistryActionLog.Action"]</th>
<th>Item</th> <th>@L["RegistryActionLog.Item"]</th>
<th>Quantity</th> <th>@L["RegistryActionLog.Quantity"]</th>
<th>Amount</th> <th>@L["RegistryActionLog.Amount"]</th>
<th>Details</th> <th>@L["RegistryActionLog.Details"]</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -48,22 +48,22 @@ else
@switch (log.ActionType) @switch (log.ActionType)
{ {
case "RegistryLinkOpened": case "RegistryLinkOpened":
<span class="badge bg-info">Registry opened</span> <span class="badge bg-info">@L["RegistryActionLog.Badge.RegistryOpened"]</span>
break; break;
case "ItemLinkOpened": case "ItemLinkOpened":
<span class="badge bg-info">Item link opened</span> <span class="badge bg-info">@L["RegistryActionLog.Badge.ItemLinkOpened"]</span>
break; break;
case "MarkPurchased": case "MarkPurchased":
<span class="badge bg-success">Purchase marked</span> <span class="badge bg-success">@L["RegistryActionLog.Badge.PurchaseMarked"]</span>
break; break;
case "UnmarkPurchased": case "UnmarkPurchased":
<span class="badge bg-warning">Purchase unmarked</span> <span class="badge bg-warning">@L["RegistryActionLog.Badge.PurchaseUnmarked"]</span>
break; break;
case "MarkPartialPurchase": case "MarkPartialPurchase":
<span class="badge bg-primary">Partial purchase</span> <span class="badge bg-primary">@L["RegistryActionLog.Badge.PartialPurchase"]</span>
break; break;
case "LogContribution": case "LogContribution":
<span class="badge bg-secondary">Contribution logged</span> <span class="badge bg-secondary">@L["RegistryActionLog.Badge.ContributionLogged"]</span>
break; break;
default: default:
<span class="badge bg-dark">@log.ActionType</span> <span class="badge bg-dark">@log.ActionType</span>
@@ -3,20 +3,20 @@
@using Blazored.TextEditor @using Blazored.TextEditor
<PageTitle>Registry Admin</PageTitle> <PageTitle>@L["RegistryAdmin.PageTitle"]</PageTitle>
@if (!IsAuthorized) @if (!IsAuthorized)
{ {
<p>Access denied.</p> <p>@L["Common.AccessDenied"]</p>
} }
else else
{ {
<h1>Registry Admin</h1> <h1>@L["RegistryAdmin.Title"]</h1>
@if (!IsSmtpConfigured) @if (!IsSmtpConfigured)
{ {
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
SMTP is not configured. Email features (identity emails and admin invite emails) are disabled. Configure the Smtp section in appsettings or user secrets. @L["RegistryAdmin.SmtpNotConfigured"]
</div> </div>
} }
@@ -29,7 +29,7 @@ else
role="tab" role="tab"
aria-controls="items-content" aria-controls="items-content"
aria-selected="@(ActiveTab == "items" ? "true" : "false")"> aria-selected="@(ActiveTab == "items" ? "true" : "false")">
<span class="bi bi-box-seam"></span> Items <span class="bi bi-box-seam"></span> @L["RegistryAdmin.Tab.Items"]
</button> </button>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
@@ -40,7 +40,7 @@ else
role="tab" role="tab"
aria-controls="settings-content" aria-controls="settings-content"
aria-selected="@(ActiveTab == "settings" ? "true" : "false")"> aria-selected="@(ActiveTab == "settings" ? "true" : "false")">
<span class="bi bi-gear"></span> Settings <span class="bi bi-gear"></span> @L["RegistryAdmin.Tab.Settings"]
</button> </button>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
@@ -51,7 +51,7 @@ else
role="tab" role="tab"
aria-controls="admins-content" aria-controls="admins-content"
aria-selected="@(ActiveTab == "admins" ? "true" : "false")"> aria-selected="@(ActiveTab == "admins" ? "true" : "false")">
<span class="bi bi-people"></span> Administrators <span class="bi bi-people"></span> @L["RegistryAdmin.Tab.Administrators"]
</button> </button>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
@@ -62,12 +62,12 @@ else
role="tab" role="tab"
aria-controls="addresses-content" aria-controls="addresses-content"
aria-selected="@(ActiveTab == "addresses" ? "true" : "false")"> aria-selected="@(ActiveTab == "addresses" ? "true" : "false")">
<span class="bi bi-house"></span> Addresses <span class="bi bi-house"></span> @L["RegistryAdmin.Tab.Addresses"]
</button> </button>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a href="/registry/@RegistryId/admin/action-log" class="nav-link"> <a href="/registry/@RegistryId/admin/action-log" class="nav-link">
<span class="bi bi-clock-history"></span> Action Log <span class="bi bi-clock-history"></span> @L["RegistryAdmin.Tab.ActionLog"]
</a> </a>
</li> </li>
</ul> </ul>
@@ -76,62 +76,62 @@ else
<!-- Items Tab --> <!-- Items Tab -->
<div class="tab-pane fade @(GetTabPaneClass("items"))" id="items-content" role="tabpanel" aria-labelledby="items-tab"> <div class="tab-pane fade @(GetTabPaneClass("items"))" id="items-content" role="tabpanel" aria-labelledby="items-tab">
<section class="mb-4"> <section class="mb-4">
<h2>Add or edit item</h2> <h2>@L["RegistryAdmin.AddOrEditItem"]</h2>
<EditForm Model="ItemModel" OnValidSubmit="SaveItemAsync" FormName="registry-item-form"> <EditForm Model="ItemModel" OnValidSubmit="SaveItemAsync" FormName="registry-item-form">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Name</label> <label class="form-label">@L["RegistryAdmin.Name"]</label>
<InputText class="form-control" @bind-Value="ItemModel.Name" /> <InputText class="form-control" @bind-Value="ItemModel.Name" />
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Product URL</label> <label class="form-label">@L["RegistryAdmin.ProductUrl"]</label>
<div class="input-group"> <div class="input-group">
<InputText class="form-control" @bind-Value="ItemModel.ProductUrl" /> <InputText class="form-control" @bind-Value="ItemModel.ProductUrl" />
<button type="button" class="btn btn-outline-secondary" @onclick="FetchMetadataAsync">Auto fetch</button> <button type="button" class="btn btn-outline-secondary" @onclick="FetchMetadataAsync">@L["RegistryAdmin.AutoFetch"]</button>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Picture URL</label> <label class="form-label">@L["RegistryAdmin.PictureUrl"]</label>
<InputText class="form-control" @bind-Value="ItemModel.PictureUrl" /> <InputText class="form-control" @bind-Value="ItemModel.PictureUrl" />
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Description</label> <label class="form-label">@L["RegistryAdmin.Description"]</label>
<InputTextArea class="form-control" @bind-Value="ItemModel.Description" rows="2" /> <InputTextArea class="form-control" @bind-Value="ItemModel.Description" rows="2" />
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<label class="form-label">Price</label> <label class="form-label">@L["RegistryAdmin.Price"]</label>
<InputNumber class="form-control" @bind-Value="ItemModel.PriceAmount" /> <InputNumber class="form-control" @bind-Value="ItemModel.PriceAmount" />
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<label class="form-label">Currency symbol</label> <label class="form-label">@L["RegistryAdmin.CurrencySymbol"]</label>
<InputText class="form-control" @bind-Value="ItemModel.CurrencyCode" /> <InputText class="form-control" @bind-Value="ItemModel.CurrencyCode" />
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<label class="form-label">Desired qty</label> <label class="form-label">@L["RegistryAdmin.DesiredQty"]</label>
<InputNumber class="form-control" @bind-Value="ItemModel.DesiredQuantity" /> <InputNumber class="form-control" @bind-Value="ItemModel.DesiredQuantity" />
</div> </div>
<div class="col-md-3 d-flex align-items-center gap-2"> <div class="col-md-3 d-flex align-items-center gap-2">
<InputCheckbox @bind-Value="ItemModel.ParticipationAllowed" /> <InputCheckbox @bind-Value="ItemModel.ParticipationAllowed" />
<label>Participation</label> <label>@L["RegistryAdmin.Participation"]</label>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label">Participation target</label> <label class="form-label">@L["RegistryAdmin.ParticipationTarget"]</label>
<InputNumber class="form-control" @bind-Value="ItemModel.ParticipationTargetAmount" /> <InputNumber class="form-control" @bind-Value="ItemModel.ParticipationTargetAmount" />
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label">Second hand preference</label> <label class="form-label">@L["RegistryAdmin.SecondHandPreference"]</label>
<InputSelect class="form-select" @bind-Value="ItemModel.PreferSecondHand"> <InputSelect class="form-select" @bind-Value="ItemModel.PreferSecondHand">
<option value="">Second hand optional</option> <option value="">@L["RegistryAdmin.SecondHandOptional"]</option>
<option value="true">Prefer second hand</option> <option value="true">@L["RegistryAdmin.PreferSecondHand"]</option>
<option value="false">New only</option> <option value="false">@L["RegistryAdmin.NewOnly"]</option>
</InputSelect> </InputSelect>
</div> </div>
<div class="col-md-3 d-flex align-items-center gap-2"> <div class="col-md-3 d-flex align-items-center gap-2">
<InputCheckbox @bind-Value="ItemModel.IsGiven" /> <InputCheckbox @bind-Value="ItemModel.IsGiven" />
<label>Given</label> <label>@L["RegistryAdmin.Given"]</label>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Category</label> <label class="form-label">@L["RegistryAdmin.Category"]</label>
<InputSelect class="form-select" @bind-Value="ItemModel.CategoryId"> <InputSelect class="form-select" @bind-Value="ItemModel.CategoryId">
@foreach (var category in ItemCategories) @foreach (var category in ItemCategories)
{ {
@@ -140,16 +140,16 @@ else
</InputSelect> </InputSelect>
</div> </div>
</div> </div>
<button class="btn btn-primary mt-3" type="submit">Save item</button> <button class="btn btn-primary mt-3" type="submit">@L["RegistryAdmin.SaveItem"]</button>
</EditForm> </EditForm>
</section> </section>
<section> <section>
<div class="d-flex justify-content-between align-items-end flex-wrap gap-3"> <div class="d-flex justify-content-between align-items-end flex-wrap gap-3">
<h2 class="mb-0">Categories and items</h2> <h2 class="mb-0">@L["RegistryAdmin.CategoriesAndItems"]</h2>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<InputText class="form-control" @bind-Value="NewCategoryName" placeholder="New category" /> <InputText class="form-control" @bind-Value="NewCategoryName" placeholder="@L["RegistryAdmin.NewCategory"]" />
<button type="button" class="btn btn-outline-primary" @onclick="AddCategoryAsync">Add category</button> <button type="button" class="btn btn-outline-primary" @onclick="AddCategoryAsync">@L["RegistryAdmin.AddCategory"]</button>
</div> </div>
</div> </div>
@@ -158,7 +158,7 @@ else
<div class="alert alert-warning mt-3 mb-0" role="alert">@ItemManagementMessage</div> <div class="alert alert-warning mt-3 mb-0" role="alert">@ItemManagementMessage</div>
} }
<p class="text-muted mt-3 mb-2">Drag categories or items to reorder. Drop items into another category to regroup them.</p> <p class="text-muted mt-3 mb-2">@L["RegistryAdmin.DragHint"]</p>
<div class="category-groups mt-3"> <div class="category-groups mt-3">
@foreach (var category in ItemCategories) @foreach (var category in ItemCategories)
@@ -178,8 +178,8 @@ else
{ {
<div class="d-flex gap-2 align-items-center w-100"> <div class="d-flex gap-2 align-items-center w-100">
<InputText class="form-control form-control-sm" @bind-Value="CategoryRenameName" /> <InputText class="form-control form-control-sm" @bind-Value="CategoryRenameName" />
<button type="button" class="btn btn-sm btn-primary" @onclick="() => SaveCategoryRenameAsync(category.Id)">Save</button> <button type="button" class="btn btn-sm btn-primary" @onclick="() => SaveCategoryRenameAsync(category.Id)">@L["Common.Save"]</button>
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="CancelCategoryRename">Cancel</button> <button type="button" class="btn btn-sm btn-outline-secondary" @onclick="CancelCategoryRename">@L["Common.Cancel"]</button>
</div> </div>
} }
else else
@@ -189,13 +189,13 @@ else
<button type="button" <button type="button"
class="btn btn-sm btn-outline-secondary" class="btn btn-sm btn-outline-secondary"
@onclick="() => StartCategoryRename(category)"> @onclick="() => StartCategoryRename(category)">
Rename @L["RegistryAdmin.Rename"]
</button> </button>
<button type="button" <button type="button"
class="btn btn-sm btn-outline-danger" class="btn btn-sm btn-outline-danger"
@onclick="() => RemoveCategoryAsync(category.Id)" @onclick="() => RemoveCategoryAsync(category.Id)"
disabled="@(ItemCategories.Count <= 1)"> disabled="@(ItemCategories.Count <= 1)">
Remove @L["Common.Remove"]
</button> </button>
</div> </div>
} }
@@ -208,17 +208,17 @@ else
@ondrop="() => OnCategoryItemsDropAsync(category.Id)"> @ondrop="() => OnCategoryItemsDropAsync(category.Id)">
@if (category.Items.Count == 0) @if (category.Items.Count == 0)
{ {
<div class="p-3 text-muted">Drop items here.</div> <div class="p-3 text-muted">@L["RegistryAdmin.DropItemsHere"]</div>
} }
else else
{ {
<table class="table table-striped mb-0"> <table class="table table-striped mb-0">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>@L["RegistryAdmin.Header.Name"]</th>
<th>Desired Qty</th> <th>@L["RegistryAdmin.Header.DesiredQty"]</th>
<th>Participation</th> <th>@L["RegistryAdmin.Header.Participation"]</th>
<th>Purchased by / Contributed by</th> <th>@L["RegistryAdmin.Header.PurchasedContributedBy"]</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@@ -239,12 +239,12 @@ else
@ondrop="() => OnItemDropAsync(category.Id, item.Id!.Value)"> @ondrop="() => OnItemDropAsync(category.Id, item.Id!.Value)">
<td>@item.Name</td> <td>@item.Name</td>
<td>@item.DesiredQuantity</td> <td>@item.DesiredQuantity</td>
<td>@(item.ParticipationAllowed ? "Yes" : "No")</td> <td>@(item.ParticipationAllowed ? L["Common.Yes"].Value : L["Common.No"].Value)</td>
<td> <td>
@if (item.Purchasers.Count > 0) @if (item.Purchasers.Count > 0)
{ {
<div class="mb-1"> <div class="mb-1">
<small><strong>Purchased:</strong></small> <small><strong>@L["RegistryAdmin.Purchased"]</strong></small>
<div class="small"> <div class="small">
@foreach (var purchaser in item.Purchasers) @foreach (var purchaser in item.Purchasers)
{ {
@@ -256,7 +256,7 @@ else
@if (item.Contributors.Count > 0) @if (item.Contributors.Count > 0)
{ {
<div> <div>
<small><strong>Contributed:</strong></small> <small><strong>@L["RegistryAdmin.Contributed"]</strong></small>
<div class="small"> <div class="small">
@foreach (var contributor in item.Contributors) @foreach (var contributor in item.Contributors)
{ {
@@ -267,8 +267,8 @@ else
} }
</td> </td>
<td> <td>
<button class="btn btn-sm btn-outline-secondary me-2" @onclick="() => EditItem(item)">Edit</button> <button class="btn btn-sm btn-outline-secondary me-2" @onclick="() => EditItem(item)">@L["Common.Edit"]</button>
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteItemAsync(item.Id)">Delete</button> <button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteItemAsync(item.Id)">@L["Common.Remove"]</button>
</td> </td>
</tr> </tr>
} }
@@ -292,57 +292,57 @@ else
<!-- Settings Tab --> <!-- Settings Tab -->
<div class="tab-pane fade @(GetTabPaneClass("settings"))" id="settings-content" role="tabpanel" aria-labelledby="settings-tab"> <div class="tab-pane fade @(GetTabPaneClass("settings"))" id="settings-content" role="tabpanel" aria-labelledby="settings-tab">
<section class="mb-4"> <section class="mb-4">
<h2>Registry Settings</h2> <h2>@L["RegistryAdmin.RegistrySettings"]</h2>
<EditForm Model="SettingsModel" OnValidSubmit="SaveSettingsAsync" FormName="registry-settings-form"> <EditForm Model="SettingsModel" OnValidSubmit="SaveSettingsAsync" FormName="registry-settings-form">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Baby name</label> <label class="form-label">@L["RegistryAdmin.BabyName"]</label>
<InputText class="form-control" @bind-Value="SettingsModel.BabyName" /> <InputText class="form-control" @bind-Value="SettingsModel.BabyName" />
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label">Birth date</label> <label class="form-label">@L["RegistryAdmin.BirthDate"]</label>
<InputDate class="form-control" @bind-Value="SettingsModel.BirthDate" /> <InputDate class="form-control" @bind-Value="SettingsModel.BirthDate" />
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<label class="form-label">Currency symbol</label> <label class="form-label">@L["RegistryAdmin.CurrencySymbol"]</label>
<InputText class="form-control" @bind-Value="SettingsModel.CurrencyCode" /> <InputText class="form-control" @bind-Value="SettingsModel.CurrencyCode" />
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label">Theme</label> <label class="form-label">@L["RegistryAdmin.Theme"]</label>
<InputSelect class="form-select" @bind-Value="SettingsModel.ThemeKey"> <InputSelect class="form-select" @bind-Value="SettingsModel.ThemeKey">
<option value="default">Default</option> <option value="default">@L["RegistryAdmin.Theme.Default"]</option>
<option value="soft">Soft</option> <option value="soft">@L["RegistryAdmin.Theme.Soft"]</option>
<option value="modern">Modern</option> <option value="modern">@L["RegistryAdmin.Theme.Modern"]</option>
</InputSelect> </InputSelect>
</div> </div>
</div> </div>
<div class="mt-3"> <div class="mt-3">
<label class="form-label">Shipping address</label> <label class="form-label">@L["RegistryAdmin.ShippingAddress"]</label>
<InputTextArea class="form-control" @bind-Value="SettingsModel.ShippingAddress" rows="4" /> <InputTextArea class="form-control" @bind-Value="SettingsModel.ShippingAddress" rows="4" />
<small class="form-text text-muted">Line breaks will be preserved</small> <small class="form-text text-muted">@L["RegistryAdmin.LineBreaksPreserved"]</small>
</div> </div>
<div class="mt-3"> <div class="mt-3">
<label class="form-label">Top content</label> <label class="form-label">@L["RegistryAdmin.TopContent"]</label>
<div class="editor-wrapper"> <div class="editor-wrapper">
<BlazoredTextEditor @ref="TextEditor" Placeholder="Welcome text" Theme="snow" ToolbarContent="@ToolbarContent" /> <BlazoredTextEditor @ref="TextEditor" Placeholder="@L["RegistryAdmin.WelcomeText"]" Theme="snow" ToolbarContent="@ToolbarContent" />
</div> </div>
</div> </div>
<div class="mt-3"> <div class="mt-3">
<h3>Bank Account Settings</h3> <h3>@L["RegistryAdmin.BankAccountSettings"]</h3>
<div class="row g-3 mt-2"> <div class="row g-3 mt-2">
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Bank account name</label> <label class="form-label">@L["RegistryAdmin.BankAccountName"]</label>
<InputText class="form-control" @bind-Value="SettingsModel.BankAccountDisplayName" /> <InputText class="form-control" @bind-Value="SettingsModel.BankAccountDisplayName" />
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">IBAN</label> <label class="form-label">@L["RegistryPublic.IBAN"]</label>
<InputText class="form-control" @bind-Value="SettingsModel.BankAccountIban" /> <InputText class="form-control" @bind-Value="SettingsModel.BankAccountIban" />
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">BIC</label> <label class="form-label">@L["RegistryPublic.BIC"]</label>
<InputText class="form-control" @bind-Value="SettingsModel.BankAccountBic" /> <InputText class="form-control" @bind-Value="SettingsModel.BankAccountBic" />
</div> </div>
</div> </div>
@@ -350,31 +350,31 @@ else
<div class="form-check"> <div class="form-check">
<InputCheckbox class="form-check-input" @bind-Value="SettingsModel.ShowBankAccountName" id="showBankName" /> <InputCheckbox class="form-check-input" @bind-Value="SettingsModel.ShowBankAccountName" id="showBankName" />
<label class="form-check-label" for="showBankName"> <label class="form-check-label" for="showBankName">
Display bank account name @L["RegistryAdmin.DisplayBankAccountName"]
</label> </label>
</div> </div>
</div> </div>
</div> </div>
<div class="mt-3"> <div class="mt-3">
<h3>Contribution Payment Options</h3> <h3>@L["RegistryAdmin.ContributionPaymentOptions"]</h3>
<div class="row g-3 mt-2"> <div class="row g-3 mt-2">
<div class="col-md-12"> <div class="col-md-12">
<label class="form-label">Single QR code URL</label> <label class="form-label">@L["RegistryAdmin.SingleQrCodeUrl"]</label>
<InputText class="form-control" @bind-Value="SettingsModel.ContributionQrCodeUrl" /> <InputText class="form-control" @bind-Value="SettingsModel.ContributionQrCodeUrl" />
<small class="form-text text-muted">Optional: one QR code that donors can scan for any amount.</small> <small class="form-text text-muted">@L["RegistryAdmin.SingleQrHelp"]</small>
</div> </div>
</div> </div>
<div class="mt-3"> <div class="mt-3">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<h4 class="mb-0">Amount-specific QR codes</h4> <h4 class="mb-0">@L["RegistryAdmin.AmountSpecificQrCodes"]</h4>
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="AddContributionAmountQrCode">Add QR amount</button> <button type="button" class="btn btn-outline-secondary btn-sm" @onclick="AddContributionAmountQrCode">@L["RegistryAdmin.AddQrAmount"]</button>
</div> </div>
@if (SettingsModel.ContributionAmountQrCodes.Count == 0) @if (SettingsModel.ContributionAmountQrCodes.Count == 0)
{ {
<p class="text-muted mt-2 mb-0">No amount-specific QR codes configured.</p> <p class="text-muted mt-2 mb-0">@L["RegistryAdmin.NoAmountSpecificQrCodes"]</p>
} }
else else
{ {
@@ -383,15 +383,15 @@ else
{ {
<div class="row g-2 align-items-end"> <div class="row g-2 align-items-end">
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label">Amount</label> <label class="form-label">@L["Common.Amount"]</label>
<InputNumber class="form-control" @bind-Value="amountQr.Amount" /> <InputNumber class="form-control" @bind-Value="amountQr.Amount" />
</div> </div>
<div class="col-md-7"> <div class="col-md-7">
<label class="form-label">QR code URL</label> <label class="form-label">@L["RegistryAdmin.QrCodeUrl"]</label>
<InputText class="form-control" @bind-Value="amountQr.QrCodeUrl" /> <InputText class="form-control" @bind-Value="amountQr.QrCodeUrl" />
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<button type="button" class="btn btn-outline-danger w-100" @onclick="() => RemoveContributionAmountQrCode(amountQr)">Remove</button> <button type="button" class="btn btn-outline-danger w-100" @onclick="() => RemoveContributionAmountQrCode(amountQr)">@L["Common.Remove"]</button>
</div> </div>
</div> </div>
} }
@@ -400,7 +400,7 @@ else
</div> </div>
</div> </div>
<button class="btn btn-primary mt-4" type="submit">Save settings</button> <button class="btn btn-primary mt-4" type="submit">@L["RegistryAdmin.SaveSettings"]</button>
</EditForm> </EditForm>
</section> </section>
</div> </div>
@@ -408,19 +408,19 @@ else
<!-- Addresses Tab --> <!-- Addresses Tab -->
<div class="tab-pane fade @(GetTabPaneClass("addresses"))" id="addresses-content" role="tabpanel" aria-labelledby="addresses-tab"> <div class="tab-pane fade @(GetTabPaneClass("addresses"))" id="addresses-content" role="tabpanel" aria-labelledby="addresses-tab">
<section class="mb-4"> <section class="mb-4">
<h2>User addresses</h2> <h2>@L["RegistryAdmin.UserAddresses"]</h2>
@if (AccessibleUserAddresses.Count == 0) @if (AccessibleUserAddresses.Count == 0)
{ {
<p class="text-muted">No users found yet.</p> <p class="text-muted">@L["RegistryAdmin.NoUsersFound"]</p>
} }
else else
{ {
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>@L["RegistryAdmin.Name"]</th>
<th>Email</th> <th>@L["RegistryAdmin.Email"]</th>
<th>Address</th> <th>@L["RegistryAdmin.Address"]</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -441,17 +441,17 @@ else
<!-- Admins Tab --> <!-- Admins Tab -->
<div class="tab-pane fade @(GetTabPaneClass("admins"))" id="admins-content" role="tabpanel" aria-labelledby="admins-tab"> <div class="tab-pane fade @(GetTabPaneClass("admins"))" id="admins-content" role="tabpanel" aria-labelledby="admins-tab">
<section class="mb-4"> <section class="mb-4">
<h2>Current administrators</h2> <h2>@L["RegistryAdmin.CurrentAdministrators"]</h2>
@if (Admins.Count == 0) @if (Admins.Count == 0)
{ {
<p class="text-muted">No admins assigned yet.</p> <p class="text-muted">@L["RegistryAdmin.NoAdmins"]</p>
} }
else else
{ {
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>Email / Name</th> <th>@L["RegistryAdmin.EmailOrName"]</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@@ -461,7 +461,7 @@ else
<tr> <tr>
<td>@admin.DisplayName</td> <td>@admin.DisplayName</td>
<td class="text-end"> <td class="text-end">
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteAdminAsync(admin.UserId)">Remove</button> <button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteAdminAsync(admin.UserId)">@L["Common.Remove"]</button>
</td> </td>
</tr> </tr>
} }
@@ -471,19 +471,19 @@ else
</section> </section>
<section> <section>
<h2>Invite administrator</h2> <h2>@L["RegistryAdmin.InviteAdministrator"]</h2>
<div class="row g-2"> <div class="row g-2">
<div class="col-md-8"> <div class="col-md-8">
<InputText class="form-control" @bind-Value="InviteEmail" placeholder="optional email" /> <InputText class="form-control" @bind-Value="InviteEmail" placeholder="@L["RegistryAdmin.OptionalEmail"]" />
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<button class="btn btn-outline-primary w-100" @onclick="CreateInviteAsync">Create invite</button> <button class="btn btn-outline-primary w-100" @onclick="CreateInviteAsync">@L["RegistryAdmin.CreateInvite"]</button>
</div> </div>
</div> </div>
@if (!string.IsNullOrWhiteSpace(InviteLink)) @if (!string.IsNullOrWhiteSpace(InviteLink))
{ {
<p class="mt-3"> <p class="mt-3">
<strong>Invite link:</strong> <strong>@L["RegistryAdmin.InviteLink"]</strong>
<br /> <br />
<a href="@InviteLink" target="_blank">@InviteLink</a> <a href="@InviteLink" target="_blank">@InviteLink</a>
</p> </p>
@@ -3,29 +3,29 @@
@using BirthList.Web.Features.Registries @using BirthList.Web.Features.Registries
<PageTitle>Contribution Amount</PageTitle> <PageTitle>@L["RegistryContributionAmount.PageTitle"]</PageTitle>
@if (Registry is null || Item is null) @if (Registry is null || Item is null)
{ {
<p>Item not found.</p> <p>@L["RegistryContributionAmount.ItemNotFound"]</p>
} }
else if (!IsAuthenticated) else if (!IsAuthenticated)
{ {
<p>Please log in first.</p> <p>@L["RegistryContributionAmount.LoginFirst"]</p>
} }
else else
{ {
<div class="registry-shell @RegistryThemeService.GetCssClass(Registry.RegistryType, Registry.ThemeKey)"> <div class="registry-shell @RegistryThemeService.GetCssClass(Registry.RegistryType, Registry.ThemeKey)">
<h1>Partially fulfill: @Item.Name</h1> <h1>@L["RegistryContributionAmount.PartiallyFulfill", Item.Name]</h1>
<div class="card p-3"> <div class="card p-3">
@if (RepresentableAmounts.Count == 0) @if (RepresentableAmounts.Count == 0)
{ {
<p class="text-muted mb-0">No representable amount can be formed from configured QR codes up to @GetCurrencySymbol()200.</p> <p class="text-muted mb-0">@L["RegistryContributionAmount.NoRepresentableAmount", GetCurrencySymbol()]</p>
} }
else else
{ {
<label class="form-label">Select amount: @GetCurrencySymbol()@SelectedAmount</label> <label class="form-label">@L["RegistryContributionAmount.SelectAmount", GetCurrencySymbol(), SelectedAmount]</label>
<input type="range" <input type="range"
class="form-range" class="form-range"
min="0" min="0"
@@ -37,11 +37,11 @@ else
@if (Suggestions.Count == 0) @if (Suggestions.Count == 0)
{ {
<p class="text-muted mt-2">No QR combination available for this amount.</p> <p class="text-muted mt-2">@L["RegistryContributionAmount.NoQrCombination"]</p>
} }
else else
{ {
<p class="mt-3 mb-2"><strong>Suggested QR combination:</strong></p> <p class="mt-3 mb-2"><strong>@L["RegistryContributionAmount.SuggestedCombination"]</strong></p>
<ul class="mb-3"> <ul class="mb-3">
@foreach (var suggestion in Suggestions) @foreach (var suggestion in Suggestions)
{ {
@@ -58,7 +58,7 @@ else
<div class="small mb-1">@GetCurrencySymbol()@suggestion.Amount</div> <div class="small mb-1">@GetCurrencySymbol()@suggestion.Amount</div>
<img src="@BuildQrImageUrl(suggestion.QrCodeUrl)" alt="QR code @suggestion.Amount" class="img-fluid" style="max-height: 220px;" /> <img src="@BuildQrImageUrl(suggestion.QrCodeUrl)" alt="QR code @suggestion.Amount" class="img-fluid" style="max-height: 220px;" />
<div class="small text-muted text-break"> <div class="small text-muted text-break">
<a href="@suggestion.QrCodeUrl" target="_blank" rel="noopener noreferrer">Open payment link</a> <a href="@suggestion.QrCodeUrl" target="_blank" rel="noopener noreferrer">@L["RegistryContributionAmount.OpenPaymentLink"]</a>
</div> </div>
</div> </div>
} }
@@ -67,8 +67,8 @@ else
} }
<div class="d-flex gap-2 mt-3"> <div class="d-flex gap-2 mt-3">
<button class="btn btn-success" @onclick="ConfirmAsync" disabled="@(SelectedAmount <= 0)">I transferred this amount</button> <button class="btn btn-success" @onclick="ConfirmAsync" disabled="@(SelectedAmount <= 0)">@L["RegistryContributionAmount.TransferredAmount"]</button>
<button class="btn btn-outline-secondary" @onclick="BackToRegistry">Back</button> <button class="btn btn-outline-secondary" @onclick="BackToRegistry">@L["Common.Back"]</button>
</div> </div>
</div> </div>
</div> </div>
@@ -1,19 +1,19 @@
@page "/registry/{RegistryId:guid}/invite/{Token}" @page "/registry/{RegistryId:guid}/invite/{Token}"
<PageTitle>Admin Invite</PageTitle> <PageTitle>@L["RegistryInvite.PageTitle"]</PageTitle>
<h1>Admin invitation</h1> <h1>@L["RegistryInvite.Title"]</h1>
@if (Redeemed is null) @if (Redeemed is null)
{ {
<p>Validating invitation...</p> <p>@L["RegistryInvite.Validating"]</p>
} }
else if (Redeemed.Value) else if (Redeemed.Value)
{ {
<p>Invitation accepted. You are now an admin.</p> <p>@L["RegistryInvite.Accepted"]</p>
<a class="btn btn-primary" href="/registry/@RegistryId/admin">Go to admin</a> <a class="btn btn-primary" href="/registry/@RegistryId/admin">@L["RegistryInvite.GoToAdmin"]</a>
} }
else else
{ {
<p>The invitation is invalid or already used.</p> <p>@L["RegistryInvite.Invalid"]</p>
} }
@@ -3,11 +3,11 @@
@using BirthList.Web.Features.Registries @using BirthList.Web.Features.Registries
<PageTitle>Registry</PageTitle> <PageTitle>@L["RegistryPublic.PageTitle"]</PageTitle>
@if (Registry is null) @if (Registry is null)
{ {
<p>Registry not found.</p> <p>@L["RegistryPublic.NotFound"]</p>
} }
else else
{ {
@@ -22,7 +22,7 @@ else
@if (!string.IsNullOrWhiteSpace(Registry.ShippingAddress)) @if (!string.IsNullOrWhiteSpace(Registry.ShippingAddress))
{ {
<div class="alert alert-info"> <div class="alert alert-info">
<strong>Shipping address</strong><br /> <strong>@L["RegistryPublic.ShippingAddress"]</strong><br />
<span class="shipping-address-text">@Registry.ShippingAddress</span> <span class="shipping-address-text">@Registry.ShippingAddress</span>
</div> </div>
} }
@@ -30,18 +30,18 @@ else
@if (Registry.ShowBankAccountName && !string.IsNullOrWhiteSpace(Registry.BankAccountIban)) @if (Registry.ShowBankAccountName && !string.IsNullOrWhiteSpace(Registry.BankAccountIban))
{ {
<div class="alert alert-secondary"> <div class="alert alert-secondary">
<strong>Bank transfer participation</strong><br /> <strong>@L["RegistryPublic.BankTransferParticipation"]</strong><br />
@Registry.BankAccountDisplayName<br /> @Registry.BankAccountDisplayName<br />
IBAN: @Registry.BankAccountIban @L["RegistryPublic.IBAN"]: @Registry.BankAccountIban
@if (!string.IsNullOrWhiteSpace(Registry.BankAccountBic)) @if (!string.IsNullOrWhiteSpace(Registry.BankAccountBic))
{ {
<span> | BIC: @Registry.BankAccountBic</span> <span> | @L["RegistryPublic.BIC"]: @Registry.BankAccountBic</span>
} }
</div> </div>
} }
<div class="category-list"> <div class="category-list">
@foreach (var category in Registry.Categories) @foreach (var category in Registry.Categories.Where(x => x.Items.Count > 0))
{ {
var isCollapsed = IsCategoryCollapsed(category.Id); var isCollapsed = IsCategoryCollapsed(category.Id);
<section class="category-section"> <section class="category-section">
@@ -66,11 +66,11 @@ else
<h5 class="card-title">@item.Name</h5> <h5 class="card-title">@item.Name</h5>
@if (item.PreferSecondHand == true) @if (item.PreferSecondHand == true)
{ {
<span class="badge bg-info">Second-hand preferred</span> <span class="badge bg-info">@L["RegistryPublic.SecondHandPreferred"]</span>
} }
else if (item.PreferSecondHand == null) else if (item.PreferSecondHand == null)
{ {
<span class="badge bg-warning">Second-hand optional</span> <span class="badge bg-warning">@L["RegistryPublic.SecondHandOptional"]</span>
} }
</div> </div>
@if (!string.IsNullOrWhiteSpace(item.Description)) @if (!string.IsNullOrWhiteSpace(item.Description))
@@ -79,24 +79,24 @@ else
} }
@if (item.DesiredQuantity > 1) @if (item.DesiredQuantity > 1)
{ {
<p class="mb-1"><strong>Qty:</strong> @item.PurchasedQuantity/@item.DesiredQuantity purchased</p> <p class="mb-1"><strong>@L["RegistryPublic.Qty"]</strong> @item.PurchasedQuantity/@item.DesiredQuantity @L["RegistryPublic.PurchasedSuffix"]</p>
} }
@if (item.PriceAmount.HasValue) @if (item.PriceAmount.HasValue)
{ {
<p class="mb-1"><strong>Price:</strong> @item.PriceAmount.Value.ToString("0.00") @item.CurrencyCode</p> <p class="mb-1"><strong>@L["RegistryPublic.Price"]</strong> @item.PriceAmount.Value.ToString("0.00") @item.CurrencyCode</p>
} }
@if (item.ParticipationAllowed && GetParticipationTotalAmount(item).HasValue) @if (item.ParticipationAllowed && GetParticipationTotalAmount(item).HasValue)
{ {
<p class="mb-2"> <p class="mb-2">
<strong>Participation:</strong> <strong>@L["RegistryPublic.Participation"]</strong>
@item.CurrencyCode@item.MoneyFulfilledAmount.ToString("0.00") @item.CurrencyCode@item.MoneyFulfilledAmount.ToString("0.00")
@if (GetParticipationTotalAmount(item)!.Value > 0) @if (GetParticipationTotalAmount(item)!.Value > 0)
{ {
<span> out of @item.CurrencyCode@GetParticipationTotalAmount(item)!.Value.ToString("0.00") fulfilled</span> <span> @L["RegistryPublic.OutOfFulfilled", item.CurrencyCode, GetParticipationTotalAmount(item)!.Value.ToString("0.00")]</span>
} }
else else
{ {
<span> fulfilled</span> <span> @L["RegistryPublic.FulfilledOnly"]</span>
} }
</p> </p>
} }
@@ -109,7 +109,7 @@ else
<div class="mb-2"> <div class="mb-2">
@if (item.CanViewPurchasers && Registry.IsAdmin) @if (item.CanViewPurchasers && Registry.IsAdmin)
{ {
<strong class="text-sm">Purchased by:</strong> <strong class="text-sm">@L["RegistryPublic.PurchasedBy"]</strong>
<div class="contributor-list"> <div class="contributor-list">
@foreach (var purchaser in item.Purchasers) @foreach (var purchaser in item.Purchasers)
{ {
@@ -119,18 +119,19 @@ else
} }
else if (item.CanViewPurchasers && item.CurrentUserPurchasedQuantity > 0) else if (item.CanViewPurchasers && item.CurrentUserPurchasedQuantity > 0)
{ {
<strong class="text-sm">Purchased by:</strong> <strong class="text-sm">@L["RegistryPublic.PurchasedBy"]</strong>
<div class="contributor-list"> <div class="contributor-list">
<span class="contributor-badge">You (@item.CurrentUserPurchasedQuantity)</span> <span class="contributor-badge">@L["RegistryPublic.YouQuantity", item.CurrentUserPurchasedQuantity]</span>
@if (item.Purchasers.Count > 1) @if (item.Purchasers.Count > 1)
{ {
<span class="contributor-badge">and @(item.Purchasers.Count - 1) other @(item.Purchasers.Count - 1 == 1 ? "person" : "people")</span> var otherCount = item.Purchasers.Count - 1;
<span class="contributor-badge">@L["RegistryPublic.AndOtherPeople", otherCount, otherCount == 1 ? L["RegistryPublic.Person"].Value : L["RegistryPublic.People"].Value]</span>
} }
</div> </div>
} }
else else
{ {
<span class="text-muted small">Purchased</span> <span class="text-muted small">@L["RegistryPublic.Purchased"]</span>
} }
</div> </div>
} }
@@ -139,7 +140,7 @@ else
<div class="mb-2"> <div class="mb-2">
@if (Registry.IsAdmin) @if (Registry.IsAdmin)
{ {
<strong class="text-sm">Contributed by:</strong> <strong class="text-sm">@L["RegistryPublic.ContributedBy"]</strong>
<div class="contributor-list"> <div class="contributor-list">
@foreach (var contributor in item.Contributors) @foreach (var contributor in item.Contributors)
{ {
@@ -152,23 +153,23 @@ else
var currentUserContribution = item.Contributors.FirstOrDefault(x => x.UserId == Registry.CurrentUserId); var currentUserContribution = item.Contributors.FirstOrDefault(x => x.UserId == Registry.CurrentUserId);
if (currentUserContribution is not null) if (currentUserContribution is not null)
{ {
<strong class="text-sm">Contributed by:</strong> <strong class="text-sm">@L["RegistryPublic.ContributedBy"]</strong>
<div class="contributor-list"> <div class="contributor-list">
<span class="contributor-badge">@currentUserContribution.DisplayName (@currentUserContribution.Amount.ToString("0.00") @item.CurrencyCode)</span> <span class="contributor-badge">@currentUserContribution.DisplayName (@currentUserContribution.Amount.ToString("0.00") @item.CurrencyCode)</span>
@if (item.Contributors.Count > 1) @if (item.Contributors.Count > 1)
{ {
<span class="contributor-badge">and others</span> <span class="contributor-badge">@L["RegistryPublic.AndOthers"]</span>
} }
</div> </div>
} }
else else
{ {
<span class="text-muted small">Contributed</span> <span class="text-muted small">@L["RegistryPublic.Contributed"]</span>
} }
} }
else else
{ {
<span class="text-muted small">Contributed</span> <span class="text-muted small">@L["RegistryPublic.Contributed"]</span>
} }
</div> </div>
} }
@@ -177,24 +178,24 @@ else
@if (!IsAuthenticated) @if (!IsAuthenticated)
{ {
<button class="btn btn-outline-primary btn-sm" @onclick="() => LoginRedirect()">Login to purchase</button> <button class="btn btn-outline-primary btn-sm" @onclick="() => LoginRedirect()">@L["RegistryPublic.LoginToPurchase"]</button>
} }
else else
{ {
<div class="d-flex gap-2 flex-wrap"> <div class="d-flex gap-2 flex-wrap">
@if (!string.IsNullOrWhiteSpace(item.ProductUrl) && item.CurrentUserPurchasedQuantity == 0) @if (!string.IsNullOrWhiteSpace(item.ProductUrl) && item.CurrentUserPurchasedQuantity == 0)
{ {
<button class="btn btn-primary btn-sm" @onclick="() => OpenPurchasePrompt(item.Id, openTab: true)">Purchase</button> <button class="btn btn-primary btn-sm" @onclick="() => OpenPurchasePrompt(item.Id, openTab: true)">@L["RegistryPublic.Purchase"]</button>
} }
<button class="btn btn-success btn-sm" @onclick="() => OpenPurchaseManagementPromptAsync(item.Id)"> <button class="btn btn-success btn-sm" @onclick="() => 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))
</button> </button>
@if (item.ParticipationAllowed) @if (item.ParticipationAllowed)
{ {
<button class="btn btn-secondary btn-sm" @onclick="() => OpenContributionActionAsync(item.Id, item.CurrentUserContributionAmount)"> <button class="btn btn-secondary btn-sm" @onclick="() => 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))
</button> </button>
} }
</div> </div>
@@ -214,36 +215,20 @@ else
{ {
<div class="prompt-overlay"> <div class="prompt-overlay">
<div class="prompt-card"> <div class="prompt-card">
<h3>Mark as purchased</h3> <h3>@L["RegistryPublic.MarkAsPurchased"]</h3>
<p>How many units did you purchase?</p> <p>@L["RegistryPublic.HowManyUnitsPurchased"]</p>
<InputNumber class="form-control" @bind-Value="PurchasedQuantity" /> <InputNumber class="form-control" @bind-Value="PurchasedQuantity" />
@if (!string.IsNullOrWhiteSpace(PurchaseItemUrl)) @if (!string.IsNullOrWhiteSpace(PurchaseItemUrl))
{ {
<div class="mt-3"> <div class="mt-3">
<a href="@PurchaseItemUrl" target="_blank" class="btn btn-outline-primary btn-sm w-100">Open product link</a> <a href="@PurchaseItemUrl" target="_blank" class="btn btn-outline-primary btn-sm w-100">@L["RegistryPublic.OpenProductLink"]</a>
</div> </div>
} }
<div class="d-flex gap-2 mt-3"> <div class="d-flex gap-2 mt-3">
<button class="btn btn-success" @onclick="ConfirmPurchaseAsync">Confirm purchase</button> <button class="btn btn-success" @onclick="ConfirmPurchaseAsync">@L["RegistryPublic.ConfirmPurchase"]</button>
<button class="btn btn-outline-secondary" @onclick="ClosePurchasePrompt">Cancel</button> <button class="btn btn-outline-secondary" @onclick="ClosePurchasePrompt">@L["Common.Cancel"]</button>
</div>
</div>
</div>
}
@if (ShowContributionPrompt)
{
<div class="prompt-overlay">
<div class="prompt-card">
<h3>Log contribution</h3>
<p>Transferred amount</p>
<InputNumber class="form-control" @bind-Value="ContributionAmount" />
<p class="mt-2">Message: @ContributionMessage</p>
<div class="d-flex gap-2 mt-3">
<button class="btn btn-success" @onclick="ConfirmContributionAsync">Confirm</button>
<button class="btn btn-outline-secondary" @onclick="CloseContributionPrompt">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
@@ -253,20 +238,20 @@ else
{ {
<div class="prompt-overlay"> <div class="prompt-overlay">
<div class="prompt-card"> <div class="prompt-card">
<h3>Select purchaser to unmark</h3> <h3>@L["RegistryPublic.SelectPurchaserToUnmark"]</h3>
<p>Multiple users have purchased this item. Choose which purchase to unmark:</p> <p>@L["RegistryPublic.MultipleUsersPurchased"]</p>
<div class="purchaser-list"> <div class="purchaser-list">
@foreach (var purchaser in PurchasersToUnmark) @foreach (var purchaser in PurchasersToUnmark)
{ {
<div class="purchaser-item"> <div class="purchaser-item">
<span>@purchaser.DisplayName (@purchaser.Quantity)</span> <span>@purchaser.DisplayName (@purchaser.Quantity)</span>
<button class="btn btn-warning btn-sm" @onclick="() => UnmarkPurchaserAsync(purchaser.UserId)">Unmark</button> <button class="btn btn-warning btn-sm" @onclick="() => UnmarkPurchaserAsync(purchaser.UserId)">@L["RegistryPublic.Unmark"]</button>
</div> </div>
} }
</div> </div>
<div class="d-flex gap-2 mt-3"> <div class="d-flex gap-2 mt-3">
<button class="btn btn-danger" @onclick="UnmarkAllPurchasersAsync">Unmark all</button> <button class="btn btn-danger" @onclick="UnmarkAllPurchasersAsync">@L["RegistryPublic.UnmarkAll"]</button>
<button class="btn btn-outline-secondary" @onclick="ClosePurchaserSelectionPrompt">Cancel</button> <button class="btn btn-outline-secondary" @onclick="ClosePurchaserSelectionPrompt">@L["Common.Cancel"]</button>
</div> </div>
</div> </div>
</div> </div>
@@ -276,29 +261,29 @@ else
{ {
<div class="prompt-overlay"> <div class="prompt-overlay">
<div class="prompt-card"> <div class="prompt-card">
<h3>Partially fulfill item</h3> <h3>@L["RegistryPublic.PartiallyFulfillItem"]</h3>
@if (PartialFulfillStep == 1) @if (PartialFulfillStep == 1)
{ {
<p>Select how you want to donate:</p> <p>@L["RegistryPublic.SelectDonateMethod"]</p>
<div class="d-flex flex-column gap-2"> <div class="d-flex flex-column gap-2">
@if (HasIbanPaymentOption()) @if (HasIbanPaymentOption())
{ {
<button class="btn btn-outline-primary text-start" @onclick="() => SelectPaymentMethod(ContributionPaymentMethodType.Iban)"> <button class="btn btn-outline-primary text-start" @onclick="() => SelectPaymentMethod(ContributionPaymentMethodType.Iban)">
IBAN transfer @L["RegistryPublic.IbanTransfer"]
</button> </button>
} }
@if (HasSingleQrPaymentOption()) @if (HasSingleQrPaymentOption())
{ {
<button class="btn btn-outline-primary text-start" @onclick="() => SelectPaymentMethod(ContributionPaymentMethodType.SingleQrCode)"> <button class="btn btn-outline-primary text-start" @onclick="() => SelectPaymentMethod(ContributionPaymentMethodType.SingleQrCode)">
Single QR code @L["RegistryPublic.SingleQrCode"]
</button> </button>
} }
@if (HasAmountSpecificQrPaymentOption()) @if (HasAmountSpecificQrPaymentOption())
{ {
<button class="btn btn-outline-primary text-start" @onclick="() => SelectPaymentMethod(ContributionPaymentMethodType.AmountSpecificQrCode)"> <button class="btn btn-outline-primary text-start" @onclick="() => SelectPaymentMethod(ContributionPaymentMethodType.AmountSpecificQrCode)">
QR code per amount @L["RegistryPublic.QrCodePerAmount"]
</button> </button>
} }
</div> </div>
@@ -306,10 +291,10 @@ else
@if (SelectedPaymentMethod == ContributionPaymentMethodType.Iban && !string.IsNullOrWhiteSpace(Registry?.BankAccountIban)) @if (SelectedPaymentMethod == ContributionPaymentMethodType.Iban && !string.IsNullOrWhiteSpace(Registry?.BankAccountIban))
{ {
<div class="alert alert-secondary mt-3 mb-0"> <div class="alert alert-secondary mt-3 mb-0">
<strong>IBAN:</strong> @Registry.BankAccountIban <strong>@L["RegistryPublic.IBAN"]:</strong> @Registry.BankAccountIban
@if (!string.IsNullOrWhiteSpace(Registry.BankAccountBic)) @if (!string.IsNullOrWhiteSpace(Registry.BankAccountBic))
{ {
<span> | <strong>BIC:</strong> @Registry.BankAccountBic</span> <span> | <strong>@L["RegistryPublic.BIC"]:</strong> @Registry.BankAccountBic</span>
} }
</div> </div>
} }
@@ -319,25 +304,25 @@ else
<div class="mt-3 text-center"> <div class="mt-3 text-center">
<img src="@BuildQrImageUrl(SelectedPaymentQrCodeUrl)" alt="Payment QR code" class="img-fluid" style="max-height: 240px;" /> <img src="@BuildQrImageUrl(SelectedPaymentQrCodeUrl)" alt="Payment QR code" class="img-fluid" style="max-height: 240px;" />
<div class="mt-2"> <div class="mt-2">
<a href="@SelectedPaymentQrCodeUrl" target="_blank" rel="noopener noreferrer">Open payment link</a> <a href="@SelectedPaymentQrCodeUrl" target="_blank" rel="noopener noreferrer">@L["RegistryPublic.OpenPaymentLink"]</a>
</div> </div>
</div> </div>
} }
<div class="d-flex gap-2 mt-3"> <div class="d-flex gap-2 mt-3">
<button class="btn btn-primary" @onclick="ContinueContributionWizard" disabled="@(SelectedPaymentMethod is null)">Next</button> <button class="btn btn-primary" @onclick="ContinueContributionWizard" disabled="@(SelectedPaymentMethod is null)">@L["Common.Next"]</button>
<button class="btn btn-outline-secondary" @onclick="CloseContributionPrompt">Cancel</button> <button class="btn btn-outline-secondary" @onclick="CloseContributionPrompt">@L["Common.Cancel"]</button>
</div> </div>
} }
else if (PartialFulfillStep == 2) else if (PartialFulfillStep == 2)
{ {
<p>How much did you add for this item?</p> <p>@L["RegistryPublic.HowMuchAdded"]</p>
<InputNumber class="form-control" @bind-Value="ContributionAmount" @bind-Value:after="OnContributionAmountChanged" /> <InputNumber class="form-control" @bind-Value="ContributionAmount" @bind-Value:after="OnContributionAmountChanged" />
<p class="mt-2">Message: @ContributionMessage</p> <p class="mt-2">@L["Common.Message"]: @ContributionMessage</p>
@if (SelectedPaymentMethod == ContributionPaymentMethodType.AmountSpecificQrCode && ContributionAmount > 0 && string.IsNullOrWhiteSpace(SelectedPaymentQrCodeUrl)) @if (SelectedPaymentMethod == ContributionPaymentMethodType.AmountSpecificQrCode && ContributionAmount > 0 && string.IsNullOrWhiteSpace(SelectedPaymentQrCodeUrl))
{ {
<p class="text-muted mt-2 mb-0">No QR code configured for this exact amount.</p> <p class="text-muted mt-2 mb-0">@L["RegistryPublic.NoQrForAmount"]</p>
} }
@if (!string.IsNullOrWhiteSpace(SelectedPaymentQrCodeUrl)) @if (!string.IsNullOrWhiteSpace(SelectedPaymentQrCodeUrl))
@@ -345,15 +330,15 @@ else
<div class="mt-3 text-center"> <div class="mt-3 text-center">
<img src="@BuildQrImageUrl(SelectedPaymentQrCodeUrl)" alt="Payment QR code" class="img-fluid" style="max-height: 240px;" /> <img src="@BuildQrImageUrl(SelectedPaymentQrCodeUrl)" alt="Payment QR code" class="img-fluid" style="max-height: 240px;" />
<div class="mt-2"> <div class="mt-2">
<a href="@SelectedPaymentQrCodeUrl" target="_blank" rel="noopener noreferrer">Open payment link</a> <a href="@SelectedPaymentQrCodeUrl" target="_blank" rel="noopener noreferrer">@L["RegistryPublic.OpenPaymentLink"]</a>
</div> </div>
</div> </div>
} }
<div class="d-flex gap-2 mt-3"> <div class="d-flex gap-2 mt-3">
<button class="btn btn-success" @onclick="ConfirmContributionAsync">Confirm</button> <button class="btn btn-success" @onclick="ConfirmContributionAsync">@L["RegistryPublic.Confirm"]</button>
<button class="btn btn-outline-secondary" @onclick="BackContributionWizard">Back</button> <button class="btn btn-outline-secondary" @onclick="BackContributionWizard">@L["Common.Back"]</button>
<button class="btn btn-outline-secondary" @onclick="CloseContributionPrompt">Cancel</button> <button class="btn btn-outline-secondary" @onclick="CloseContributionPrompt">@L["Common.Cancel"]</button>
</div> </div>
} }
</div> </div>
@@ -364,14 +349,14 @@ else
{ {
<div class="prompt-overlay"> <div class="prompt-overlay">
<div class="prompt-card"> <div class="prompt-card">
<h3>Manage purchase</h3> <h3>@L["RegistryPublic.ManagePurchase"]</h3>
@if (Registry?.IsAdmin == true) @if (Registry?.IsAdmin == true)
{ {
<label class="form-label">User</label> <label class="form-label">@L["RegistryPublic.User"]</label>
<InputText class="form-control mb-2" @bind-Value="UserFilterText" placeholder="Search user" /> <InputText class="form-control mb-2" @bind-Value="UserFilterText" placeholder="@L["Common.SearchUser"]" />
<select class="form-select mb-3" value="@SelectedPurchaseUserId" @onchange="OnPurchaseManagedUserChanged"> <select class="form-select mb-3" value="@SelectedPurchaseUserId" @onchange="OnPurchaseManagedUserChanged">
<option value="">Select user</option> <option value="">@L["Common.SelectUser"]</option>
@foreach (var user in FilteredSelectableUsers) @foreach (var user in FilteredSelectableUsers)
{ {
<option value="@user.UserId">@user.DisplayName</option> <option value="@user.UserId">@user.DisplayName</option>
@@ -379,13 +364,13 @@ else
</select> </select>
} }
<label class="form-label">Quantity</label> <label class="form-label">@L["Common.Quantity"]</label>
<InputNumber class="form-control" @bind-Value="ManagedPurchaseQuantity" /> <InputNumber class="form-control" @bind-Value="ManagedPurchaseQuantity" />
<div class="d-flex gap-2 mt-3"> <div class="d-flex gap-2 mt-3">
<button class="btn btn-success" @onclick="SavePurchaseManagementAsync">Save</button> <button class="btn btn-success" @onclick="SavePurchaseManagementAsync">@L["Common.Save"]</button>
<button class="btn btn-outline-danger" @onclick="RemoveManagedPurchaseAsync">Remove</button> <button class="btn btn-outline-danger" @onclick="RemoveManagedPurchaseAsync">@L["Common.Remove"]</button>
<button class="btn btn-outline-secondary" @onclick="ClosePurchaseManagementPrompt">Cancel</button> <button class="btn btn-outline-secondary" @onclick="ClosePurchaseManagementPrompt">@L["Common.Cancel"]</button>
</div> </div>
</div> </div>
</div> </div>
@@ -395,14 +380,14 @@ else
{ {
<div class="prompt-overlay"> <div class="prompt-overlay">
<div class="prompt-card"> <div class="prompt-card">
<h3>Manage participation</h3> <h3>@L["RegistryPublic.ManageParticipation"]</h3>
@if (Registry?.IsAdmin == true) @if (Registry?.IsAdmin == true)
{ {
<label class="form-label">User</label> <label class="form-label">@L["RegistryPublic.User"]</label>
<InputText class="form-control mb-2" @bind-Value="UserFilterText" placeholder="Search user" /> <InputText class="form-control mb-2" @bind-Value="UserFilterText" placeholder="@L["Common.SearchUser"]" />
<select class="form-select mb-3" value="@SelectedContributionUserId" @onchange="OnContributionManagedUserChanged"> <select class="form-select mb-3" value="@SelectedContributionUserId" @onchange="OnContributionManagedUserChanged">
<option value="">Select user</option> <option value="">@L["Common.SelectUser"]</option>
@foreach (var user in FilteredSelectableUsers) @foreach (var user in FilteredSelectableUsers)
{ {
<option value="@user.UserId">@user.DisplayName</option> <option value="@user.UserId">@user.DisplayName</option>
@@ -410,16 +395,16 @@ else
</select> </select>
} }
<label class="form-label">Amount</label> <label class="form-label">@L["Common.Amount"]</label>
<InputNumber class="form-control" @bind-Value="ManagedContributionAmount" /> <InputNumber class="form-control" @bind-Value="ManagedContributionAmount" />
<label class="form-label mt-2">Message</label> <label class="form-label mt-2">@L["Common.Message"]</label>
<InputText class="form-control" @bind-Value="ManagedContributionMessage" /> <InputText class="form-control" @bind-Value="ManagedContributionMessage" />
<div class="d-flex gap-2 mt-3"> <div class="d-flex gap-2 mt-3">
<button class="btn btn-success" @onclick="SaveContributionManagementAsync">Save</button> <button class="btn btn-success" @onclick="SaveContributionManagementAsync">@L["Common.Save"]</button>
<button class="btn btn-outline-danger" @onclick="RemoveManagedContributionAsync">Remove</button> <button class="btn btn-outline-danger" @onclick="RemoveManagedContributionAsync">@L["Common.Remove"]</button>
<button class="btn btn-outline-secondary" @onclick="CloseContributionManagementPrompt">Cancel</button> <button class="btn btn-outline-secondary" @onclick="CloseContributionManagementPrompt">@L["Common.Cancel"]</button>
</div> </div>
</div> </div>
</div> </div>
@@ -4,8 +4,11 @@
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
@using Microsoft.Extensions.Localization
@using static Microsoft.AspNetCore.Components.Web.RenderMode @using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using BirthList.Web @using BirthList.Web
@using BirthList.Web.Components @using BirthList.Web.Components
@inject IStringLocalizer<SharedResources> L
@@ -1,3 +1,4 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
namespace BirthList.Web.Data; namespace BirthList.Web.Data;
@@ -8,5 +9,8 @@ public class ApplicationUser : IdentityUser
public string? FirstName { get; set; } public string? FirstName { get; set; }
public string? LastName { get; set; } public string? LastName { get; set; }
public string? Address { get; set; } public string? Address { get; set; }
[MaxLength(10)]
public string? PreferredCulture { get; set; }
} }
@@ -0,0 +1,292 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("Address")
.HasColumnType("nvarchar(max)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FirstName")
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("PreferredCulture")
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("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<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BirthList.Web.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BirthList.Web.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", b =>
{
b.HasOne("BirthList.Web.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BirthList.Web.Migrations
{
/// <inheritdoc />
public partial class AddPreferredCultureToApplicationUser : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PreferredCulture",
table: "AspNetUsers",
type: "nvarchar(10)",
maxLength: 10,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PreferredCulture",
table: "AspNetUsers");
}
}
}
@@ -73,6 +73,10 @@ namespace BirthList.Web.Migrations
b.Property<bool>("PhoneNumberConfirmed") b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit"); .HasColumnType("bit");
b.Property<string>("PreferredCulture")
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<string>("SecurityStamp") b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
@@ -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<ApplicationUser> userManager)
{
private static readonly HashSet<string> SupportedCultureNames =
[
"en",
"nl-NL",
"nl-BE",
"fr-FR",
"qps-Ploc"
];
public static IReadOnlyList<string> 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));
}
}
+72
View File
@@ -1,18 +1,22 @@
using System.Data; using System.Data;
using System.Globalization;
using BirthList.Infrastructure.Persistence; using BirthList.Infrastructure.Persistence;
using BirthList.Web.Authorization; using BirthList.Web.Authorization;
using BirthList.Web.Components; using BirthList.Web.Components;
using BirthList.Web.Components.Account; using BirthList.Web.Components.Account;
using BirthList.Web.Configuration; using BirthList.Web.Configuration;
using BirthList.Web.Data; using BirthList.Web.Data;
using BirthList.Web.Features.Localization;
using BirthList.Web.Features.Registries; using BirthList.Web.Features.Registries;
using BirthList.Web.Services; using BirthList.Web.Services;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -44,6 +48,7 @@ builder.Services.AddScoped<RegistryMetadataService>();
builder.Services.AddScoped<RegistryThemeService>(); builder.Services.AddScoped<RegistryThemeService>();
builder.Services.AddScoped<RegistryUserContext>(); builder.Services.AddScoped<RegistryUserContext>();
builder.Services.AddScoped<SmtpConfigurationStatusService>(); builder.Services.AddScoped<SmtpConfigurationStatusService>();
builder.Services.AddScoped<LocalizationService>();
var googleClientId = builder.Configuration["Authentication:Google:ClientId"]; var googleClientId = builder.Configuration["Authentication:Google:ClientId"];
var googleClientSecret = builder.Configuration["Authentication:Google:ClientSecret"]; var googleClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];
@@ -126,6 +131,32 @@ builder.Services.AddScoped<SmtpEmailSender>();
builder.Services.AddScoped<IEmailSender<ApplicationUser>>(serviceProvider => serviceProvider.GetRequiredService<SmtpEmailSender>()); builder.Services.AddScoped<IEmailSender<ApplicationUser>>(serviceProvider => serviceProvider.GetRequiredService<SmtpEmailSender>());
builder.Services.AddScoped<ProfileCompletionService>(); builder.Services.AddScoped<ProfileCompletionService>();
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<RequestLocalizationOptions>(options =>
{
options.DefaultRequestCulture = new RequestCulture("en");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders =
[
new CookieRequestCultureProvider(),
new AcceptLanguageHeaderRequestCultureProvider()
];
});
var app = builder.Build(); var app = builder.Build();
using (var scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
@@ -190,9 +221,50 @@ app.UseAuthorization();
app.UseHttpsRedirection(); app.UseHttpsRedirection();
app.UseRequestLocalization();
app.UseStaticFiles(); app.UseStaticFiles();
app.UseAntiforgery(); app.UseAntiforgery();
app.MapPost("/set-language", async (
HttpContext context,
UserManager<ApplicationUser> 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<App>() app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(); .AddInteractiveServerRenderMode();
@@ -0,0 +1,239 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
<resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, ...</value></resheader>
<resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, ...</value></resheader>
<data name="Common.Yes" xml:space="preserve"><value>Oui</value></data>
<data name="Common.No" xml:space="preserve"><value>Non</value></data>
<data name="Common.Save" xml:space="preserve"><value>Enregistrer</value></data>
<data name="Common.Cancel" xml:space="preserve"><value>Annuler</value></data>
<data name="Common.Remove" xml:space="preserve"><value>Supprimer</value></data>
<data name="Common.Edit" xml:space="preserve"><value>Modifier</value></data>
<data name="Common.Back" xml:space="preserve"><value>Retour</value></data>
<data name="Common.Next" xml:space="preserve"><value>Suivant</value></data>
<data name="Common.Loading" xml:space="preserve"><value>Chargement...</value></data>
<data name="Common.AccessDenied" xml:space="preserve"><value>Accès refusé.</value></data>
<data name="Common.SelectUser" xml:space="preserve"><value>Sélectionner un utilisateur</value></data>
<data name="Common.SearchUser" xml:space="preserve"><value>Rechercher un utilisateur</value></data>
<data name="Common.Quantity" xml:space="preserve"><value>Quantité</value></data>
<data name="Common.Amount" xml:space="preserve"><value>Montant</value></data>
<data name="Common.Message" xml:space="preserve"><value>Message</value></data>
<data name="TopBar.Language" xml:space="preserve"><value>Langue</value></data>
<data name="TopBar.Brand" xml:space="preserve"><value>Gift List</value></data>
<data name="TopBar.ProfilePrompt" xml:space="preserve"><value>Veuillez compléter votre profil (prénom, nom et adresse).</value></data>
<data name="TopBar.CompleteProfile" xml:space="preserve"><value>Compléter le profil</value></data>
<data name="TopBar.AccountSettings" xml:space="preserve"><value>Paramètres du compte</value></data>
<data name="TopBar.SignIn" xml:space="preserve"><value>Se connecter</value></data>
<data name="TopBar.SignOut" xml:space="preserve"><value>Se déconnecter</value></data>
<data name="Home.PageTitle" xml:space="preserve"><value>Liste de naissance</value></data>
<data name="Home.Welcome" xml:space="preserve"><value>Bienvenue sur Gift List</value></data>
<data name="Home.ManagedRegistries" xml:space="preserve"><value>Listes que vous gérez</value></data>
<data name="Home.CreateNew" xml:space="preserve"><value>Créer</value></data>
<data name="Home.NoRegistries" xml:space="preserve"><value>Aucune liste pour le moment.</value></data>
<data name="Home.View" xml:space="preserve"><value>Voir</value></data>
<data name="Home.Manage" xml:space="preserve"><value>Gérer</value></data>
<data name="Home.VisitedRegistries" xml:space="preserve"><value>Listes visitées</value></data>
<data name="Home.NoVisitedRegistries" xml:space="preserve"><value>Aucune liste visitée pour le moment.</value></data>
<data name="Home.CreateRegistryTitle" xml:space="preserve"><value>Créer une nouvelle liste</value></data>
<data name="Home.Title" xml:space="preserve"><value>Titre</value></data>
<data name="Home.Type" xml:space="preserve"><value>Type</value></data>
<data name="Home.Theme" xml:space="preserve"><value>Thème</value></data>
<data name="Home.Theme.Default" xml:space="preserve"><value>Par défaut</value></data>
<data name="Home.Theme.Soft" xml:space="preserve"><value>Doux</value></data>
<data name="Home.Theme.Modern" xml:space="preserve"><value>Moderne</value></data>
<data name="Home.RegistryType.Birth" xml:space="preserve"><value>Naissance</value></data>
<data name="Home.RegistryType.Wedding" xml:space="preserve"><value>Mariage</value></data>
<data name="Home.RegistryType.Birthday" xml:space="preserve"><value>Anniversaire</value></data>
<data name="Home.MustBeLoggedIn" xml:space="preserve"><value>Vous devez être connecté pour créer une liste.</value></data>
<data name="Home.TitleRequired" xml:space="preserve"><value>Le titre est obligatoire.</value></data>
<data name="Home.LoginPrompt" xml:space="preserve"><value>Veuillez vous connecter pour créer et gérer des listes.</value></data>
<data name="Home.LoginPromptPrefix" xml:space="preserve"><value>Veuillez </value></data>
<data name="Home.LoginPromptLinkText" xml:space="preserve"><value>vous connecter</value></data>
<data name="Home.LoginPromptSuffix" xml:space="preserve"><value> pour créer et gérer des listes.</value></data>
<data name="RegistryPublic.PageTitle" xml:space="preserve"><value>Liste</value></data>
<data name="RegistryPublic.NotFound" xml:space="preserve"><value>Liste introuvable.</value></data>
<data name="RegistryPublic.ShippingAddress" xml:space="preserve"><value>Adresse de livraison</value></data>
<data name="RegistryPublic.BankTransferParticipation" xml:space="preserve"><value>Participation par virement</value></data>
<data name="RegistryPublic.IBAN" xml:space="preserve"><value>IBAN</value></data>
<data name="RegistryPublic.BIC" xml:space="preserve"><value>BIC</value></data>
<data name="RegistryPublic.SecondHandPreferred" xml:space="preserve"><value>Seconde main privilégiée</value></data>
<data name="RegistryPublic.SecondHandOptional" xml:space="preserve"><value>Seconde main possible</value></data>
<data name="RegistryPublic.Qty" xml:space="preserve"><value>Qté :</value></data>
<data name="RegistryPublic.PurchasedSuffix" xml:space="preserve"><value>achetés</value></data>
<data name="RegistryPublic.Price" xml:space="preserve"><value>Prix :</value></data>
<data name="RegistryPublic.Participation" xml:space="preserve"><value>Participation :</value></data>
<data name="RegistryPublic.OutOfFulfilled" xml:space="preserve"><value>sur {0}{1} atteints</value></data>
<data name="RegistryPublic.FulfilledOnly" xml:space="preserve"><value>atteints</value></data>
<data name="RegistryPublic.PurchasedBy" xml:space="preserve"><value>Acheté par :</value></data>
<data name="RegistryPublic.ContributedBy" xml:space="preserve"><value>Contribué par :</value></data>
<data name="RegistryPublic.YouQuantity" xml:space="preserve"><value>Vous ({0})</value></data>
<data name="RegistryPublic.AndOtherPeople" xml:space="preserve"><value>et {0} autre{1}</value></data>
<data name="RegistryPublic.Person" xml:space="preserve"><value> personne</value></data>
<data name="RegistryPublic.People" xml:space="preserve"><value>s personnes</value></data>
<data name="RegistryPublic.AndOthers" xml:space="preserve"><value>et d'autres</value></data>
<data name="RegistryPublic.Purchased" xml:space="preserve"><value>Acheté</value></data>
<data name="RegistryPublic.Contributed" xml:space="preserve"><value>Contribué</value></data>
<data name="RegistryPublic.LoginToPurchase" xml:space="preserve"><value>Connectez-vous pour acheter</value></data>
<data name="RegistryPublic.Purchase" xml:space="preserve"><value>Acheter</value></data>
<data name="RegistryPublic.ManagePurchases" xml:space="preserve"><value>Gérer les achats</value></data>
<data name="RegistryPublic.EditPurchase" xml:space="preserve"><value>Modifier l'achat</value></data>
<data name="RegistryPublic.MarkPurchased" xml:space="preserve"><value>Marquer comme acheté</value></data>
<data name="RegistryPublic.ManageParticipations" xml:space="preserve"><value>Gérer les participations</value></data>
<data name="RegistryPublic.EditParticipation" xml:space="preserve"><value>Modifier la participation</value></data>
<data name="RegistryPublic.PartiallyFulfill" xml:space="preserve"><value>Participer partiellement</value></data>
<data name="RegistryPublic.MarkAsPurchased" xml:space="preserve"><value>Marquer comme acheté</value></data>
<data name="RegistryPublic.HowManyUnitsPurchased" xml:space="preserve"><value>Combien d'unités avez-vous achetées ?</value></data>
<data name="RegistryPublic.OpenProductLink" xml:space="preserve"><value>Ouvrir le lien du produit</value></data>
<data name="RegistryPublic.LogContribution" xml:space="preserve"><value>Enregistrer la contribution</value></data>
<data name="RegistryPublic.TransferredAmount" xml:space="preserve"><value>Montant transféré</value></data>
<data name="RegistryPublic.Confirm" xml:space="preserve"><value>Confirmer</value></data>
<data name="RegistryPublic.ConfirmPurchase" xml:space="preserve"><value>Confirmer l'achat</value></data>
<data name="RegistryPublic.SelectPurchaserToUnmark" xml:space="preserve"><value>Sélectionner l'acheteur à annuler</value></data>
<data name="RegistryPublic.MultipleUsersPurchased" xml:space="preserve"><value>Plusieurs utilisateurs ont acheté cet article. Choisissez l'achat à annuler :</value></data>
<data name="RegistryPublic.Unmark" xml:space="preserve"><value>Annuler</value></data>
<data name="RegistryPublic.UnmarkAll" xml:space="preserve"><value>Tout annuler</value></data>
<data name="RegistryPublic.PartiallyFulfillItem" xml:space="preserve"><value>Participer partiellement à l'article</value></data>
<data name="RegistryPublic.SelectDonateMethod" xml:space="preserve"><value>Choisissez votre mode de participation :</value></data>
<data name="RegistryPublic.IbanTransfer" xml:space="preserve"><value>Virement IBAN</value></data>
<data name="RegistryPublic.SingleQrCode" xml:space="preserve"><value>QR code unique</value></data>
<data name="RegistryPublic.QrCodePerAmount" xml:space="preserve"><value>QR code par montant</value></data>
<data name="RegistryPublic.OpenPaymentLink" xml:space="preserve"><value>Ouvrir le lien de paiement</value></data>
<data name="RegistryPublic.HowMuchAdded" xml:space="preserve"><value>Quel montant avez-vous ajouté pour cet article ?</value></data>
<data name="RegistryPublic.NoQrForAmount" xml:space="preserve"><value>Aucun QR code configuré pour ce montant exact.</value></data>
<data name="RegistryPublic.ManagePurchase" xml:space="preserve"><value>Gérer l'achat</value></data>
<data name="RegistryPublic.ManageParticipation" xml:space="preserve"><value>Gérer la participation</value></data>
<data name="RegistryPublic.User" xml:space="preserve"><value>Utilisateur</value></data>
<data name="RegistryContributionAmount.PageTitle" xml:space="preserve"><value>Montant de participation</value></data>
<data name="RegistryContributionAmount.ItemNotFound" xml:space="preserve"><value>Article introuvable.</value></data>
<data name="RegistryContributionAmount.LoginFirst" xml:space="preserve"><value>Veuillez d'abord vous connecter.</value></data>
<data name="RegistryContributionAmount.PartiallyFulfill" xml:space="preserve"><value>Participation partielle : {0}</value></data>
<data name="RegistryContributionAmount.NoRepresentableAmount" xml:space="preserve"><value>Aucun montant représentable ne peut être formé avec les QR codes configurés jusqu'à {0}200.</value></data>
<data name="RegistryContributionAmount.SelectAmount" xml:space="preserve"><value>Sélectionnez un montant : {0}{1}</value></data>
<data name="RegistryContributionAmount.NoQrCombination" xml:space="preserve"><value>Aucune combinaison QR disponible pour ce montant.</value></data>
<data name="RegistryContributionAmount.SuggestedCombination" xml:space="preserve"><value>Combinaison QR suggérée :</value></data>
<data name="RegistryContributionAmount.OpenPaymentLink" xml:space="preserve"><value>Ouvrir le lien de paiement</value></data>
<data name="RegistryContributionAmount.TransferredAmount" xml:space="preserve"><value>J'ai transféré ce montant</value></data>
<data name="RegistryInvite.PageTitle" xml:space="preserve"><value>Invitation admin</value></data>
<data name="RegistryInvite.Title" xml:space="preserve"><value>Invitation administrateur</value></data>
<data name="RegistryInvite.Validating" xml:space="preserve"><value>Validation de l'invitation...</value></data>
<data name="RegistryInvite.Accepted" xml:space="preserve"><value>Invitation acceptée. Vous êtes maintenant administrateur.</value></data>
<data name="RegistryInvite.GoToAdmin" xml:space="preserve"><value>Aller à l'administration</value></data>
<data name="RegistryInvite.Invalid" xml:space="preserve"><value>L'invitation est invalide ou déjà utilisée.</value></data>
<data name="RegistryAdmin.PageTitle" xml:space="preserve"><value>Administration de la liste</value></data>
<data name="RegistryAdmin.Title" xml:space="preserve"><value>Administration de la liste</value></data>
<data name="RegistryAdmin.SmtpNotConfigured" xml:space="preserve"><value>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.</value></data>
<data name="RegistryAdmin.Tab.Items" xml:space="preserve"><value>Articles</value></data>
<data name="RegistryAdmin.Tab.Settings" xml:space="preserve"><value>Paramètres</value></data>
<data name="RegistryAdmin.Tab.Administrators" xml:space="preserve"><value>Administrateurs</value></data>
<data name="RegistryAdmin.Tab.Addresses" xml:space="preserve"><value>Adresses</value></data>
<data name="RegistryAdmin.Tab.ActionLog" xml:space="preserve"><value>Journal d'actions</value></data>
<data name="RegistryAdmin.AddOrEditItem" xml:space="preserve"><value>Ajouter ou modifier un article</value></data>
<data name="RegistryAdmin.Name" xml:space="preserve"><value>Nom</value></data>
<data name="RegistryAdmin.ProductUrl" xml:space="preserve"><value>URL du produit</value></data>
<data name="RegistryAdmin.AutoFetch" xml:space="preserve"><value>Remplissage auto</value></data>
<data name="RegistryAdmin.PictureUrl" xml:space="preserve"><value>URL de l'image</value></data>
<data name="RegistryAdmin.Description" xml:space="preserve"><value>Description</value></data>
<data name="RegistryAdmin.Price" xml:space="preserve"><value>Prix</value></data>
<data name="RegistryAdmin.CurrencySymbol" xml:space="preserve"><value>Symbole monétaire</value></data>
<data name="RegistryAdmin.DesiredQty" xml:space="preserve"><value>Quantité souhaitée</value></data>
<data name="RegistryAdmin.Participation" xml:space="preserve"><value>Participation</value></data>
<data name="RegistryAdmin.ParticipationTarget" xml:space="preserve"><value>Objectif de participation</value></data>
<data name="RegistryAdmin.SecondHandPreference" xml:space="preserve"><value>Préférence seconde main</value></data>
<data name="RegistryAdmin.SecondHandOptional" xml:space="preserve"><value>Seconde main possible</value></data>
<data name="RegistryAdmin.PreferSecondHand" xml:space="preserve"><value>Préférer la seconde main</value></data>
<data name="RegistryAdmin.NewOnly" xml:space="preserve"><value>Neuf uniquement</value></data>
<data name="RegistryAdmin.Given" xml:space="preserve"><value>Donné</value></data>
<data name="RegistryAdmin.Category" xml:space="preserve"><value>Catégorie</value></data>
<data name="RegistryAdmin.SaveItem" xml:space="preserve"><value>Enregistrer l'article</value></data>
<data name="RegistryAdmin.CategoriesAndItems" xml:space="preserve"><value>Catégories et articles</value></data>
<data name="RegistryAdmin.NewCategory" xml:space="preserve"><value>Nouvelle catégorie</value></data>
<data name="RegistryAdmin.AddCategory" xml:space="preserve"><value>Ajouter une catégorie</value></data>
<data name="RegistryAdmin.DragHint" xml:space="preserve"><value>Faites glisser les catégories ou les articles pour réorganiser. Déposez les articles dans une autre catégorie pour les regrouper.</value></data>
<data name="RegistryAdmin.Rename" xml:space="preserve"><value>Renommer</value></data>
<data name="RegistryAdmin.DropItemsHere" xml:space="preserve"><value>Déposez des articles ici.</value></data>
<data name="RegistryAdmin.Header.Name" xml:space="preserve"><value>Nom</value></data>
<data name="RegistryAdmin.Header.DesiredQty" xml:space="preserve"><value>Qté souhaitée</value></data>
<data name="RegistryAdmin.Header.Participation" xml:space="preserve"><value>Participation</value></data>
<data name="RegistryAdmin.Header.PurchasedContributedBy" xml:space="preserve"><value>Acheté par / Contribué par</value></data>
<data name="RegistryAdmin.Purchased" xml:space="preserve"><value>Acheté :</value></data>
<data name="RegistryAdmin.Contributed" xml:space="preserve"><value>Contribué :</value></data>
<data name="RegistryAdmin.RegistrySettings" xml:space="preserve"><value>Paramètres de la liste</value></data>
<data name="RegistryAdmin.BabyName" xml:space="preserve"><value>Prénom du bébé</value></data>
<data name="RegistryAdmin.BirthDate" xml:space="preserve"><value>Date de naissance</value></data>
<data name="RegistryAdmin.Theme" xml:space="preserve"><value>Thème</value></data>
<data name="RegistryAdmin.Theme.Default" xml:space="preserve"><value>Par défaut</value></data>
<data name="RegistryAdmin.Theme.Soft" xml:space="preserve"><value>Doux</value></data>
<data name="RegistryAdmin.Theme.Modern" xml:space="preserve"><value>Moderne</value></data>
<data name="RegistryAdmin.ShippingAddress" xml:space="preserve"><value>Adresse de livraison</value></data>
<data name="RegistryAdmin.LineBreaksPreserved" xml:space="preserve"><value>Les sauts de ligne seront conservés</value></data>
<data name="RegistryAdmin.TopContent" xml:space="preserve"><value>Contenu d'en-tête</value></data>
<data name="RegistryAdmin.WelcomeText" xml:space="preserve"><value>Texte d'accueil</value></data>
<data name="RegistryAdmin.BankAccountSettings" xml:space="preserve"><value>Paramètres du compte bancaire</value></data>
<data name="RegistryAdmin.BankAccountName" xml:space="preserve"><value>Nom du compte bancaire</value></data>
<data name="RegistryAdmin.DisplayBankAccountName" xml:space="preserve"><value>Afficher le nom du compte bancaire</value></data>
<data name="RegistryAdmin.ContributionPaymentOptions" xml:space="preserve"><value>Options de paiement des participations</value></data>
<data name="RegistryAdmin.SingleQrCodeUrl" xml:space="preserve"><value>URL du QR code unique</value></data>
<data name="RegistryAdmin.SingleQrHelp" xml:space="preserve"><value>Optionnel : un QR code unique que les donateurs peuvent scanner pour n'importe quel montant.</value></data>
<data name="RegistryAdmin.AmountSpecificQrCodes" xml:space="preserve"><value>QR codes par montant</value></data>
<data name="RegistryAdmin.AddQrAmount" xml:space="preserve"><value>Ajouter un montant QR</value></data>
<data name="RegistryAdmin.NoAmountSpecificQrCodes" xml:space="preserve"><value>Aucun QR code par montant configuré.</value></data>
<data name="RegistryAdmin.QrCodeUrl" xml:space="preserve"><value>URL du QR code</value></data>
<data name="RegistryAdmin.SaveSettings" xml:space="preserve"><value>Enregistrer les paramètres</value></data>
<data name="RegistryAdmin.UserAddresses" xml:space="preserve"><value>Adresses des utilisateurs</value></data>
<data name="RegistryAdmin.NoUsersFound" xml:space="preserve"><value>Aucun utilisateur trouvé pour le moment.</value></data>
<data name="RegistryAdmin.Email" xml:space="preserve"><value>E-mail</value></data>
<data name="RegistryAdmin.Address" xml:space="preserve"><value>Adresse</value></data>
<data name="RegistryAdmin.CurrentAdministrators" xml:space="preserve"><value>Administrateurs actuels</value></data>
<data name="RegistryAdmin.NoAdmins" xml:space="preserve"><value>Aucun administrateur assigné pour le moment.</value></data>
<data name="RegistryAdmin.EmailOrName" xml:space="preserve"><value>E-mail / Nom</value></data>
<data name="RegistryAdmin.InviteAdministrator" xml:space="preserve"><value>Inviter un administrateur</value></data>
<data name="RegistryAdmin.OptionalEmail" xml:space="preserve"><value>e-mail optionnel</value></data>
<data name="RegistryAdmin.CreateInvite" xml:space="preserve"><value>Créer une invitation</value></data>
<data name="RegistryAdmin.InviteLink" xml:space="preserve"><value>Lien d'invitation :</value></data>
<data name="RegistryActionLog.PageTitle" xml:space="preserve"><value>Journal d'actions - Admin liste</value></data>
<data name="RegistryActionLog.Title" xml:space="preserve"><value>Journal d'actions de la liste</value></data>
<data name="RegistryActionLog.Description" xml:space="preserve"><value>Ce journal montre toutes les actions des utilisateurs sur cette liste : achats, participations et autres interactions.</value></data>
<data name="RegistryActionLog.NoActions" xml:space="preserve"><value>Aucune action enregistrée pour le moment.</value></data>
<data name="RegistryActionLog.DateTime" xml:space="preserve"><value>Date/Heure</value></data>
<data name="RegistryActionLog.User" xml:space="preserve"><value>Utilisateur</value></data>
<data name="RegistryActionLog.Action" xml:space="preserve"><value>Action</value></data>
<data name="RegistryActionLog.Item" xml:space="preserve"><value>Article</value></data>
<data name="RegistryActionLog.Quantity" xml:space="preserve"><value>Quantité</value></data>
<data name="RegistryActionLog.Amount" xml:space="preserve"><value>Montant</value></data>
<data name="RegistryActionLog.Details" xml:space="preserve"><value>Détails</value></data>
<data name="RegistryActionLog.Badge.RegistryOpened" xml:space="preserve"><value>Liste ouverte</value></data>
<data name="RegistryActionLog.Badge.ItemLinkOpened" xml:space="preserve"><value>Lien article ouvert</value></data>
<data name="RegistryActionLog.Badge.PurchaseMarked" xml:space="preserve"><value>Achat marqué</value></data>
<data name="RegistryActionLog.Badge.PurchaseUnmarked" xml:space="preserve"><value>Achat annulé</value></data>
<data name="RegistryActionLog.Badge.PartialPurchase" xml:space="preserve"><value>Achat partiel</value></data>
<data name="RegistryActionLog.Badge.ContributionLogged" xml:space="preserve"><value>Participation enregistrée</value></data>
<data name="Error.PageTitle" xml:space="preserve"><value>Erreur</value></data>
<data name="Error.Title" xml:space="preserve"><value>Erreur.</value></data>
<data name="Error.Subtitle" xml:space="preserve"><value>Une erreur est survenue lors du traitement de votre demande.</value></data>
<data name="Error.RequestId" xml:space="preserve"><value>ID de requête :</value></data>
<data name="Error.DevMode" xml:space="preserve"><value>Mode Développement</value></data>
<data name="Error.DevHint1" xml:space="preserve"><value>Passer en environnement Development affichera des informations plus détaillées sur l'erreur.</value></data>
<data name="Error.DevHint2" xml:space="preserve"><value>L'environnement Development ne devrait pas être activé sur une application déployée.</value></data>
<data name="Error.DevHint3" xml:space="preserve"><value>Cela peut afficher des informations sensibles issues des exceptions aux utilisateurs finaux.</value></data>
<data name="Error.DevHint4" xml:space="preserve"><value>Pour le débogage local, activez l'environnement Development en définissant ASPNETCORE_ENVIRONMENT à Development puis redémarrez l'application.</value></data>
<data name="MainLayout.UnhandledError" xml:space="preserve"><value>Une erreur non gérée est survenue.</value></data>
<data name="MainLayout.Reload" xml:space="preserve"><value>Recharger</value></data>
<data name="NavMenu.Brand" xml:space="preserve"><value>BirthList</value></data>
<data name="NavMenu.NavigationMenu" xml:space="preserve"><value>Menu de navigation</value></data>
<data name="NavMenu.Home" xml:space="preserve"><value>Accueil</value></data>
<data name="NavMenu.Logout" xml:space="preserve"><value>Se déconnecter</value></data>
<data name="NavMenu.Register" xml:space="preserve"><value>S'inscrire</value></data>
<data name="NavMenu.Login" xml:space="preserve"><value>Se connecter</value></data>
</root>
@@ -0,0 +1,239 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
<resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, ...</value></resheader>
<resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, ...</value></resheader>
<data name="Common.Yes" xml:space="preserve"><value>Ja</value></data>
<data name="Common.No" xml:space="preserve"><value>Nee</value></data>
<data name="Common.Save" xml:space="preserve"><value>Opslaan</value></data>
<data name="Common.Cancel" xml:space="preserve"><value>Annuleren</value></data>
<data name="Common.Remove" xml:space="preserve"><value>Verwijderen</value></data>
<data name="Common.Edit" xml:space="preserve"><value>Bewerken</value></data>
<data name="Common.Back" xml:space="preserve"><value>Terug</value></data>
<data name="Common.Next" xml:space="preserve"><value>Volgende</value></data>
<data name="Common.Loading" xml:space="preserve"><value>Laden...</value></data>
<data name="Common.AccessDenied" xml:space="preserve"><value>Toegang geweigerd.</value></data>
<data name="Common.SelectUser" xml:space="preserve"><value>Selecteer gebruiker</value></data>
<data name="Common.SearchUser" xml:space="preserve"><value>Zoek gebruiker</value></data>
<data name="Common.Quantity" xml:space="preserve"><value>Aantal</value></data>
<data name="Common.Amount" xml:space="preserve"><value>Bedrag</value></data>
<data name="Common.Message" xml:space="preserve"><value>Bericht</value></data>
<data name="TopBar.Language" xml:space="preserve"><value>Taal</value></data>
<data name="TopBar.Brand" xml:space="preserve"><value>Gift List</value></data>
<data name="TopBar.ProfilePrompt" xml:space="preserve"><value>Vul je profiel aan (voornaam, achternaam en adres).</value></data>
<data name="TopBar.CompleteProfile" xml:space="preserve"><value>Profiel aanvullen</value></data>
<data name="TopBar.AccountSettings" xml:space="preserve"><value>Accountinstellingen</value></data>
<data name="TopBar.SignIn" xml:space="preserve"><value>Aanmelden</value></data>
<data name="TopBar.SignOut" xml:space="preserve"><value>Afmelden</value></data>
<data name="Home.PageTitle" xml:space="preserve"><value>Geboortelijst</value></data>
<data name="Home.Welcome" xml:space="preserve"><value>Welkom bij Gift List</value></data>
<data name="Home.ManagedRegistries" xml:space="preserve"><value>Lijsten die jij beheert</value></data>
<data name="Home.CreateNew" xml:space="preserve"><value>Nieuwe maken</value></data>
<data name="Home.NoRegistries" xml:space="preserve"><value>Nog geen lijsten.</value></data>
<data name="Home.View" xml:space="preserve"><value>Bekijken</value></data>
<data name="Home.Manage" xml:space="preserve"><value>Beheren</value></data>
<data name="Home.VisitedRegistries" xml:space="preserve"><value>Bezochte lijsten</value></data>
<data name="Home.NoVisitedRegistries" xml:space="preserve"><value>Nog geen bezochte lijsten.</value></data>
<data name="Home.CreateRegistryTitle" xml:space="preserve"><value>Nieuwe lijst aanmaken</value></data>
<data name="Home.Title" xml:space="preserve"><value>Titel</value></data>
<data name="Home.Type" xml:space="preserve"><value>Type</value></data>
<data name="Home.Theme" xml:space="preserve"><value>Thema</value></data>
<data name="Home.Theme.Default" xml:space="preserve"><value>Standaard</value></data>
<data name="Home.Theme.Soft" xml:space="preserve"><value>Zacht</value></data>
<data name="Home.Theme.Modern" xml:space="preserve"><value>Modern</value></data>
<data name="Home.RegistryType.Birth" xml:space="preserve"><value>Geboorte</value></data>
<data name="Home.RegistryType.Wedding" xml:space="preserve"><value>Huwelijk</value></data>
<data name="Home.RegistryType.Birthday" xml:space="preserve"><value>Verjaardag</value></data>
<data name="Home.MustBeLoggedIn" xml:space="preserve"><value>Je moet aangemeld zijn om een lijst aan te maken.</value></data>
<data name="Home.TitleRequired" xml:space="preserve"><value>Titel is verplicht.</value></data>
<data name="Home.LoginPrompt" xml:space="preserve"><value>Meld je aan om lijsten aan te maken en te beheren.</value></data>
<data name="Home.LoginPromptPrefix" xml:space="preserve"><value>Gelieve </value></data>
<data name="Home.LoginPromptLinkText" xml:space="preserve"><value>aan te melden</value></data>
<data name="Home.LoginPromptSuffix" xml:space="preserve"><value> om lijsten aan te maken en te beheren.</value></data>
<data name="RegistryPublic.PageTitle" xml:space="preserve"><value>Lijst</value></data>
<data name="RegistryPublic.NotFound" xml:space="preserve"><value>Lijst niet gevonden.</value></data>
<data name="RegistryPublic.ShippingAddress" xml:space="preserve"><value>Leveringsadres</value></data>
<data name="RegistryPublic.BankTransferParticipation" xml:space="preserve"><value>Deelname via overschrijving</value></data>
<data name="RegistryPublic.IBAN" xml:space="preserve"><value>IBAN</value></data>
<data name="RegistryPublic.BIC" xml:space="preserve"><value>BIC</value></data>
<data name="RegistryPublic.SecondHandPreferred" xml:space="preserve"><value>Tweedehands heeft de voorkeur</value></data>
<data name="RegistryPublic.SecondHandOptional" xml:space="preserve"><value>Tweedehands is mogelijk</value></data>
<data name="RegistryPublic.Qty" xml:space="preserve"><value>Aantal:</value></data>
<data name="RegistryPublic.PurchasedSuffix" xml:space="preserve"><value>gekocht</value></data>
<data name="RegistryPublic.Price" xml:space="preserve"><value>Prijs:</value></data>
<data name="RegistryPublic.Participation" xml:space="preserve"><value>Deelname:</value></data>
<data name="RegistryPublic.OutOfFulfilled" xml:space="preserve"><value>van {0}{1} behaald</value></data>
<data name="RegistryPublic.FulfilledOnly" xml:space="preserve"><value>behaald</value></data>
<data name="RegistryPublic.PurchasedBy" xml:space="preserve"><value>Gekocht door:</value></data>
<data name="RegistryPublic.ContributedBy" xml:space="preserve"><value>Bijgedragen door:</value></data>
<data name="RegistryPublic.YouQuantity" xml:space="preserve"><value>Jij ({0})</value></data>
<data name="RegistryPublic.AndOtherPeople" xml:space="preserve"><value>en {0} andere {1}</value></data>
<data name="RegistryPublic.Person" xml:space="preserve"><value>persoon</value></data>
<data name="RegistryPublic.People" xml:space="preserve"><value>personen</value></data>
<data name="RegistryPublic.AndOthers" xml:space="preserve"><value>en anderen</value></data>
<data name="RegistryPublic.Purchased" xml:space="preserve"><value>Gekocht</value></data>
<data name="RegistryPublic.Contributed" xml:space="preserve"><value>Bijgedragen</value></data>
<data name="RegistryPublic.LoginToPurchase" xml:space="preserve"><value>Meld je aan om te kopen</value></data>
<data name="RegistryPublic.Purchase" xml:space="preserve"><value>Kopen</value></data>
<data name="RegistryPublic.ManagePurchases" xml:space="preserve"><value>Aankopen beheren</value></data>
<data name="RegistryPublic.EditPurchase" xml:space="preserve"><value>Aankoop bewerken</value></data>
<data name="RegistryPublic.MarkPurchased" xml:space="preserve"><value>Markeer als gekocht</value></data>
<data name="RegistryPublic.ManageParticipations" xml:space="preserve"><value>Deelnames beheren</value></data>
<data name="RegistryPublic.EditParticipation" xml:space="preserve"><value>Deelname bewerken</value></data>
<data name="RegistryPublic.PartiallyFulfill" xml:space="preserve"><value>Gedeeltelijk vervullen</value></data>
<data name="RegistryPublic.MarkAsPurchased" xml:space="preserve"><value>Markeer als gekocht</value></data>
<data name="RegistryPublic.HowManyUnitsPurchased" xml:space="preserve"><value>Hoeveel stuks heb je gekocht?</value></data>
<data name="RegistryPublic.OpenProductLink" xml:space="preserve"><value>Open productlink</value></data>
<data name="RegistryPublic.LogContribution" xml:space="preserve"><value>Bijdrage registreren</value></data>
<data name="RegistryPublic.TransferredAmount" xml:space="preserve"><value>Overgeschreven bedrag</value></data>
<data name="RegistryPublic.Confirm" xml:space="preserve"><value>Bevestigen</value></data>
<data name="RegistryPublic.ConfirmPurchase" xml:space="preserve"><value>Aankoop bevestigen</value></data>
<data name="RegistryPublic.SelectPurchaserToUnmark" xml:space="preserve"><value>Selecteer koper om te verwijderen</value></data>
<data name="RegistryPublic.MultipleUsersPurchased" xml:space="preserve"><value>Meerdere gebruikers hebben dit item gekocht. Kies welke aankoop je wil verwijderen:</value></data>
<data name="RegistryPublic.Unmark" xml:space="preserve"><value>Verwijderen</value></data>
<data name="RegistryPublic.UnmarkAll" xml:space="preserve"><value>Alles verwijderen</value></data>
<data name="RegistryPublic.PartiallyFulfillItem" xml:space="preserve"><value>Item gedeeltelijk vervullen</value></data>
<data name="RegistryPublic.SelectDonateMethod" xml:space="preserve"><value>Kies hoe je wil bijdragen:</value></data>
<data name="RegistryPublic.IbanTransfer" xml:space="preserve"><value>IBAN-overschrijving</value></data>
<data name="RegistryPublic.SingleQrCode" xml:space="preserve"><value>Enkele QR-code</value></data>
<data name="RegistryPublic.QrCodePerAmount" xml:space="preserve"><value>QR-code per bedrag</value></data>
<data name="RegistryPublic.OpenPaymentLink" xml:space="preserve"><value>Open betaallink</value></data>
<data name="RegistryPublic.HowMuchAdded" xml:space="preserve"><value>Hoeveel heb je toegevoegd voor dit item?</value></data>
<data name="RegistryPublic.NoQrForAmount" xml:space="preserve"><value>Geen QR-code ingesteld voor dit exacte bedrag.</value></data>
<data name="RegistryPublic.ManagePurchase" xml:space="preserve"><value>Aankoop beheren</value></data>
<data name="RegistryPublic.ManageParticipation" xml:space="preserve"><value>Deelname beheren</value></data>
<data name="RegistryPublic.User" xml:space="preserve"><value>Gebruiker</value></data>
<data name="RegistryContributionAmount.PageTitle" xml:space="preserve"><value>Deelnamebedrag</value></data>
<data name="RegistryContributionAmount.ItemNotFound" xml:space="preserve"><value>Item niet gevonden.</value></data>
<data name="RegistryContributionAmount.LoginFirst" xml:space="preserve"><value>Meld je eerst aan.</value></data>
<data name="RegistryContributionAmount.PartiallyFulfill" xml:space="preserve"><value>Gedeeltelijk vervullen: {0}</value></data>
<data name="RegistryContributionAmount.NoRepresentableAmount" xml:space="preserve"><value>Er kan geen voorstelbaar bedrag gevormd worden met ingestelde QR-codes tot {0}200.</value></data>
<data name="RegistryContributionAmount.SelectAmount" xml:space="preserve"><value>Selecteer bedrag: {0}{1}</value></data>
<data name="RegistryContributionAmount.NoQrCombination" xml:space="preserve"><value>Geen QR-combinatie beschikbaar voor dit bedrag.</value></data>
<data name="RegistryContributionAmount.SuggestedCombination" xml:space="preserve"><value>Voorgestelde QR-combinatie:</value></data>
<data name="RegistryContributionAmount.OpenPaymentLink" xml:space="preserve"><value>Open betaallink</value></data>
<data name="RegistryContributionAmount.TransferredAmount" xml:space="preserve"><value>Ik heb dit bedrag overgeschreven</value></data>
<data name="RegistryInvite.PageTitle" xml:space="preserve"><value>Admin-uitnodiging</value></data>
<data name="RegistryInvite.Title" xml:space="preserve"><value>Uitnodiging voor beheerder</value></data>
<data name="RegistryInvite.Validating" xml:space="preserve"><value>Uitnodiging valideren...</value></data>
<data name="RegistryInvite.Accepted" xml:space="preserve"><value>Uitnodiging geaccepteerd. Je bent nu beheerder.</value></data>
<data name="RegistryInvite.GoToAdmin" xml:space="preserve"><value>Ga naar beheer</value></data>
<data name="RegistryInvite.Invalid" xml:space="preserve"><value>De uitnodiging is ongeldig of al gebruikt.</value></data>
<data name="RegistryAdmin.PageTitle" xml:space="preserve"><value>Lijstbeheer</value></data>
<data name="RegistryAdmin.Title" xml:space="preserve"><value>Lijstbeheer</value></data>
<data name="RegistryAdmin.SmtpNotConfigured" xml:space="preserve"><value>SMTP is niet geconfigureerd. E-mailfuncties (Identity-mails en admin-uitnodigingen) zijn uitgeschakeld. Configureer de sectie Smtp in appsettings of user secrets.</value></data>
<data name="RegistryAdmin.Tab.Items" xml:space="preserve"><value>Items</value></data>
<data name="RegistryAdmin.Tab.Settings" xml:space="preserve"><value>Instellingen</value></data>
<data name="RegistryAdmin.Tab.Administrators" xml:space="preserve"><value>Beheerders</value></data>
<data name="RegistryAdmin.Tab.Addresses" xml:space="preserve"><value>Adressen</value></data>
<data name="RegistryAdmin.Tab.ActionLog" xml:space="preserve"><value>Actielog</value></data>
<data name="RegistryAdmin.AddOrEditItem" xml:space="preserve"><value>Item toevoegen of bewerken</value></data>
<data name="RegistryAdmin.Name" xml:space="preserve"><value>Naam</value></data>
<data name="RegistryAdmin.ProductUrl" xml:space="preserve"><value>Product-URL</value></data>
<data name="RegistryAdmin.AutoFetch" xml:space="preserve"><value>Automatisch ophalen</value></data>
<data name="RegistryAdmin.PictureUrl" xml:space="preserve"><value>Afbeeldings-URL</value></data>
<data name="RegistryAdmin.Description" xml:space="preserve"><value>Beschrijving</value></data>
<data name="RegistryAdmin.Price" xml:space="preserve"><value>Prijs</value></data>
<data name="RegistryAdmin.CurrencySymbol" xml:space="preserve"><value>Valutasymbool</value></data>
<data name="RegistryAdmin.DesiredQty" xml:space="preserve"><value>Gewenst aantal</value></data>
<data name="RegistryAdmin.Participation" xml:space="preserve"><value>Deelname</value></data>
<data name="RegistryAdmin.ParticipationTarget" xml:space="preserve"><value>Doelbedrag deelname</value></data>
<data name="RegistryAdmin.SecondHandPreference" xml:space="preserve"><value>Voorkeur tweedehands</value></data>
<data name="RegistryAdmin.SecondHandOptional" xml:space="preserve"><value>Tweedehands mogelijk</value></data>
<data name="RegistryAdmin.PreferSecondHand" xml:space="preserve"><value>Tweedehands verkiezen</value></data>
<data name="RegistryAdmin.NewOnly" xml:space="preserve"><value>Enkel nieuw</value></data>
<data name="RegistryAdmin.Given" xml:space="preserve"><value>Gegeven</value></data>
<data name="RegistryAdmin.Category" xml:space="preserve"><value>Categorie</value></data>
<data name="RegistryAdmin.SaveItem" xml:space="preserve"><value>Item opslaan</value></data>
<data name="RegistryAdmin.CategoriesAndItems" xml:space="preserve"><value>Categorieën en items</value></data>
<data name="RegistryAdmin.NewCategory" xml:space="preserve"><value>Nieuwe categorie</value></data>
<data name="RegistryAdmin.AddCategory" xml:space="preserve"><value>Categorie toevoegen</value></data>
<data name="RegistryAdmin.DragHint" xml:space="preserve"><value>Sleep categorieën of items om te herschikken. Zet items in een andere categorie om ze te groeperen.</value></data>
<data name="RegistryAdmin.Rename" xml:space="preserve"><value>Hernoemen</value></data>
<data name="RegistryAdmin.DropItemsHere" xml:space="preserve"><value>Sleep items hierheen.</value></data>
<data name="RegistryAdmin.Header.Name" xml:space="preserve"><value>Naam</value></data>
<data name="RegistryAdmin.Header.DesiredQty" xml:space="preserve"><value>Gewenst aantal</value></data>
<data name="RegistryAdmin.Header.Participation" xml:space="preserve"><value>Deelname</value></data>
<data name="RegistryAdmin.Header.PurchasedContributedBy" xml:space="preserve"><value>Gekocht door / Bijgedragen door</value></data>
<data name="RegistryAdmin.Purchased" xml:space="preserve"><value>Gekocht:</value></data>
<data name="RegistryAdmin.Contributed" xml:space="preserve"><value>Bijgedragen:</value></data>
<data name="RegistryAdmin.RegistrySettings" xml:space="preserve"><value>Lijstinstellingen</value></data>
<data name="RegistryAdmin.BabyName" xml:space="preserve"><value>Naam baby</value></data>
<data name="RegistryAdmin.BirthDate" xml:space="preserve"><value>Geboortedatum</value></data>
<data name="RegistryAdmin.Theme" xml:space="preserve"><value>Thema</value></data>
<data name="RegistryAdmin.Theme.Default" xml:space="preserve"><value>Standaard</value></data>
<data name="RegistryAdmin.Theme.Soft" xml:space="preserve"><value>Zacht</value></data>
<data name="RegistryAdmin.Theme.Modern" xml:space="preserve"><value>Modern</value></data>
<data name="RegistryAdmin.ShippingAddress" xml:space="preserve"><value>Leveringsadres</value></data>
<data name="RegistryAdmin.LineBreaksPreserved" xml:space="preserve"><value>Regeleinden blijven behouden</value></data>
<data name="RegistryAdmin.TopContent" xml:space="preserve"><value>Bovenste inhoud</value></data>
<data name="RegistryAdmin.WelcomeText" xml:space="preserve"><value>Welkomsttekst</value></data>
<data name="RegistryAdmin.BankAccountSettings" xml:space="preserve"><value>Bankrekeninginstellingen</value></data>
<data name="RegistryAdmin.BankAccountName" xml:space="preserve"><value>Naam bankrekening</value></data>
<data name="RegistryAdmin.DisplayBankAccountName" xml:space="preserve"><value>Naam bankrekening tonen</value></data>
<data name="RegistryAdmin.ContributionPaymentOptions" xml:space="preserve"><value>Betaalopties voor deelname</value></data>
<data name="RegistryAdmin.SingleQrCodeUrl" xml:space="preserve"><value>URL van enkele QR-code</value></data>
<data name="RegistryAdmin.SingleQrHelp" xml:space="preserve"><value>Optioneel: één QR-code die schenkers voor eender welk bedrag kunnen scannen.</value></data>
<data name="RegistryAdmin.AmountSpecificQrCodes" xml:space="preserve"><value>Bedragsspecifieke QR-codes</value></data>
<data name="RegistryAdmin.AddQrAmount" xml:space="preserve"><value>QR-bedrag toevoegen</value></data>
<data name="RegistryAdmin.NoAmountSpecificQrCodes" xml:space="preserve"><value>Geen bedragsspecifieke QR-codes ingesteld.</value></data>
<data name="RegistryAdmin.QrCodeUrl" xml:space="preserve"><value>QR-code-URL</value></data>
<data name="RegistryAdmin.SaveSettings" xml:space="preserve"><value>Instellingen opslaan</value></data>
<data name="RegistryAdmin.UserAddresses" xml:space="preserve"><value>Gebruikersadressen</value></data>
<data name="RegistryAdmin.NoUsersFound" xml:space="preserve"><value>Nog geen gebruikers gevonden.</value></data>
<data name="RegistryAdmin.Email" xml:space="preserve"><value>E-mail</value></data>
<data name="RegistryAdmin.Address" xml:space="preserve"><value>Adres</value></data>
<data name="RegistryAdmin.CurrentAdministrators" xml:space="preserve"><value>Huidige beheerders</value></data>
<data name="RegistryAdmin.NoAdmins" xml:space="preserve"><value>Nog geen beheerders toegewezen.</value></data>
<data name="RegistryAdmin.EmailOrName" xml:space="preserve"><value>E-mail / Naam</value></data>
<data name="RegistryAdmin.InviteAdministrator" xml:space="preserve"><value>Beheerder uitnodigen</value></data>
<data name="RegistryAdmin.OptionalEmail" xml:space="preserve"><value>optionele e-mail</value></data>
<data name="RegistryAdmin.CreateInvite" xml:space="preserve"><value>Uitnodiging aanmaken</value></data>
<data name="RegistryAdmin.InviteLink" xml:space="preserve"><value>Uitnodigingslink:</value></data>
<data name="RegistryActionLog.PageTitle" xml:space="preserve"><value>Actielog - Lijstbeheer</value></data>
<data name="RegistryActionLog.Title" xml:space="preserve"><value>Actielog van de lijst</value></data>
<data name="RegistryActionLog.Description" xml:space="preserve"><value>Dit log toont alle gebruikersacties op deze lijst: aankopen, deelnames en andere interacties.</value></data>
<data name="RegistryActionLog.NoActions" xml:space="preserve"><value>Nog geen acties geregistreerd.</value></data>
<data name="RegistryActionLog.DateTime" xml:space="preserve"><value>Datum/tijd</value></data>
<data name="RegistryActionLog.User" xml:space="preserve"><value>Gebruiker</value></data>
<data name="RegistryActionLog.Action" xml:space="preserve"><value>Actie</value></data>
<data name="RegistryActionLog.Item" xml:space="preserve"><value>Item</value></data>
<data name="RegistryActionLog.Quantity" xml:space="preserve"><value>Aantal</value></data>
<data name="RegistryActionLog.Amount" xml:space="preserve"><value>Bedrag</value></data>
<data name="RegistryActionLog.Details" xml:space="preserve"><value>Details</value></data>
<data name="RegistryActionLog.Badge.RegistryOpened" xml:space="preserve"><value>Lijst geopend</value></data>
<data name="RegistryActionLog.Badge.ItemLinkOpened" xml:space="preserve"><value>Itemlink geopend</value></data>
<data name="RegistryActionLog.Badge.PurchaseMarked" xml:space="preserve"><value>Aankoop gemarkeerd</value></data>
<data name="RegistryActionLog.Badge.PurchaseUnmarked" xml:space="preserve"><value>Aankoop verwijderd</value></data>
<data name="RegistryActionLog.Badge.PartialPurchase" xml:space="preserve"><value>Gedeeltelijke aankoop</value></data>
<data name="RegistryActionLog.Badge.ContributionLogged" xml:space="preserve"><value>Deelname geregistreerd</value></data>
<data name="Error.PageTitle" xml:space="preserve"><value>Fout</value></data>
<data name="Error.Title" xml:space="preserve"><value>Fout.</value></data>
<data name="Error.Subtitle" xml:space="preserve"><value>Er is een fout opgetreden bij het verwerken van je aanvraag.</value></data>
<data name="Error.RequestId" xml:space="preserve"><value>Aanvraag-ID:</value></data>
<data name="Error.DevMode" xml:space="preserve"><value>Ontwikkelmodus</value></data>
<data name="Error.DevHint1" xml:space="preserve"><value>Wanneer je naar de Development-omgeving schakelt, zie je meer gedetailleerde foutinformatie.</value></data>
<data name="Error.DevHint2" xml:space="preserve"><value>De Development-omgeving mag niet ingeschakeld zijn op gedeployde toepassingen.</value></data>
<data name="Error.DevHint3" xml:space="preserve"><value>Dat kan gevoelige informatie uit uitzonderingen tonen aan eindgebruikers.</value></data>
<data name="Error.DevHint4" xml:space="preserve"><value>Voor lokaal debuggen: zet ASPNETCORE_ENVIRONMENT op Development en herstart de app.</value></data>
<data name="MainLayout.UnhandledError" xml:space="preserve"><value>Er is een onverwachte fout opgetreden.</value></data>
<data name="MainLayout.Reload" xml:space="preserve"><value>Herladen</value></data>
<data name="NavMenu.Brand" xml:space="preserve"><value>BirthList</value></data>
<data name="NavMenu.NavigationMenu" xml:space="preserve"><value>Navigatiemenu</value></data>
<data name="NavMenu.Home" xml:space="preserve"><value>Start</value></data>
<data name="NavMenu.Logout" xml:space="preserve"><value>Afmelden</value></data>
<data name="NavMenu.Register" xml:space="preserve"><value>Registreren</value></data>
<data name="NavMenu.Login" xml:space="preserve"><value>Aanmelden</value></data>
</root>
@@ -0,0 +1,675 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, ...</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, ...</value>
</resheader>
<data name="Common.Yes" xml:space="preserve">
<value>[[Yes]]</value>
</data>
<data name="Common.No" xml:space="preserve">
<value>[[No]]</value>
</data>
<data name="Common.Save" xml:space="preserve">
<value>[[Save]]</value>
</data>
<data name="Common.Cancel" xml:space="preserve">
<value>[[Cancel]]</value>
</data>
<data name="Common.Remove" xml:space="preserve">
<value>[[Remove]]</value>
</data>
<data name="Common.Edit" xml:space="preserve">
<value>[[Edit]]</value>
</data>
<data name="Common.Back" xml:space="preserve">
<value>[[Back]]</value>
</data>
<data name="Common.Next" xml:space="preserve">
<value>[[Next]]</value>
</data>
<data name="Common.Loading" xml:space="preserve">
<value>[[Loading...]]</value>
</data>
<data name="Common.AccessDenied" xml:space="preserve">
<value>[[Access denied.]]</value>
</data>
<data name="Common.SelectUser" xml:space="preserve">
<value>[[Select user]]</value>
</data>
<data name="Common.SearchUser" xml:space="preserve">
<value>[[Search user]]</value>
</data>
<data name="Common.Quantity" xml:space="preserve">
<value>[[Quantity]]</value>
</data>
<data name="Common.Amount" xml:space="preserve">
<value>[[Amount]]</value>
</data>
<data name="Common.Message" xml:space="preserve">
<value>[[Message]]</value>
</data>
<data name="TopBar.Language" xml:space="preserve">
<value>[[Language]]</value>
</data>
<data name="TopBar.Brand" xml:space="preserve">
<value>[[Gift List]]</value>
</data>
<data name="TopBar.ProfilePrompt" xml:space="preserve">
<value>[[Please complete your profile (first name, last name, and address).]]</value>
</data>
<data name="TopBar.CompleteProfile" xml:space="preserve">
<value>[[Complete profile]]</value>
</data>
<data name="TopBar.AccountSettings" xml:space="preserve">
<value>[[Account settings]]</value>
</data>
<data name="TopBar.SignIn" xml:space="preserve">
<value>[[Sign in]]</value>
</data>
<data name="TopBar.SignOut" xml:space="preserve">
<value>[[Sign out]]</value>
</data>
<data name="Home.PageTitle" xml:space="preserve">
<value>[[Birth Registry]]</value>
</data>
<data name="Home.Welcome" xml:space="preserve">
<value>[[Welcome to Gift List]]</value>
</data>
<data name="Home.ManagedRegistries" xml:space="preserve">
<value>[[Registries you manage]]</value>
</data>
<data name="Home.CreateNew" xml:space="preserve">
<value>[[Create new]]</value>
</data>
<data name="Home.NoRegistries" xml:space="preserve">
<value>[[No registries yet.]]</value>
</data>
<data name="Home.View" xml:space="preserve">
<value>[[View]]</value>
</data>
<data name="Home.Manage" xml:space="preserve">
<value>[[Manage]]</value>
</data>
<data name="Home.VisitedRegistries" xml:space="preserve">
<value>[[Visited registries]]</value>
</data>
<data name="Home.NoVisitedRegistries" xml:space="preserve">
<value>[[No visited registries yet.]]</value>
</data>
<data name="Home.CreateRegistryTitle" xml:space="preserve">
<value>[[Create new registry]]</value>
</data>
<data name="Home.Title" xml:space="preserve">
<value>[[Title]]</value>
</data>
<data name="Home.Type" xml:space="preserve">
<value>[[Type]]</value>
</data>
<data name="Home.Theme" xml:space="preserve">
<value>[[Theme]]</value>
</data>
<data name="Home.Theme.Default" xml:space="preserve">
<value>[[Default]]</value>
</data>
<data name="Home.Theme.Soft" xml:space="preserve">
<value>[[Soft]]</value>
</data>
<data name="Home.Theme.Modern" xml:space="preserve">
<value>[[Modern]]</value>
</data>
<data name="Home.RegistryType.Birth" xml:space="preserve">
<value>[[Birth]]</value>
</data>
<data name="Home.RegistryType.Wedding" xml:space="preserve">
<value>[[Wedding]]</value>
</data>
<data name="Home.RegistryType.Birthday" xml:space="preserve">
<value>[[Birthday]]</value>
</data>
<data name="Home.MustBeLoggedIn" xml:space="preserve">
<value>[[You must be logged in to create a registry.]]</value>
</data>
<data name="Home.TitleRequired" xml:space="preserve">
<value>[[Title is required.]]</value>
</data>
<data name="Home.LoginPromptPrefix" xml:space="preserve">
<value>[[Please ]]</value>
</data>
<data name="Home.LoginPromptLinkText" xml:space="preserve">
<value>[[log in]]</value>
</data>
<data name="Home.LoginPromptSuffix" xml:space="preserve">
<value>[[ to create and manage registries.]]</value>
</data>
<data name="RegistryPublic.PageTitle" xml:space="preserve">
<value>[[Registry]]</value>
</data>
<data name="RegistryPublic.NotFound" xml:space="preserve">
<value>[[Registry not found.]]</value>
</data>
<data name="RegistryPublic.ShippingAddress" xml:space="preserve">
<value>[[Shipping address]]</value>
</data>
<data name="RegistryPublic.BankTransferParticipation" xml:space="preserve">
<value>[[Bank transfer participation]]</value>
</data>
<data name="RegistryPublic.IBAN" xml:space="preserve">
<value>[[IBAN]]</value>
</data>
<data name="RegistryPublic.BIC" xml:space="preserve">
<value>[[BIC]]</value>
</data>
<data name="RegistryPublic.SecondHandPreferred" xml:space="preserve">
<value>[[Second-hand preferred]]</value>
</data>
<data name="RegistryPublic.SecondHandOptional" xml:space="preserve">
<value>[[Second-hand optional]]</value>
</data>
<data name="RegistryPublic.Qty" xml:space="preserve">
<value>[[Qty:]]</value>
</data>
<data name="RegistryPublic.PurchasedSuffix" xml:space="preserve">
<value>[[purchased]]</value>
</data>
<data name="RegistryPublic.Price" xml:space="preserve">
<value>[[Price:]]</value>
</data>
<data name="RegistryPublic.Participation" xml:space="preserve">
<value>[[Participation:]]</value>
</data>
<data name="RegistryPublic.OutOfFulfilled" xml:space="preserve">
<value>[[out of {0}{1} fulfilled]]</value>
</data>
<data name="RegistryPublic.FulfilledOnly" xml:space="preserve">
<value>[[fulfilled]]</value>
</data>
<data name="RegistryPublic.PurchasedBy" xml:space="preserve">
<value>[[Purchased by:]]</value>
</data>
<data name="RegistryPublic.ContributedBy" xml:space="preserve">
<value>[[Contributed by:]]</value>
</data>
<data name="RegistryPublic.YouQuantity" xml:space="preserve">
<value>[[You ({0})]]</value>
</data>
<data name="RegistryPublic.AndOtherPeople" xml:space="preserve">
<value>[[and {0} other {1}]]</value>
</data>
<data name="RegistryPublic.Person" xml:space="preserve">
<value>[[person]]</value>
</data>
<data name="RegistryPublic.People" xml:space="preserve">
<value>[[people]]</value>
</data>
<data name="RegistryPublic.AndOthers" xml:space="preserve">
<value>[[and others]]</value>
</data>
<data name="RegistryPublic.Purchased" xml:space="preserve">
<value>[[Purchased]]</value>
</data>
<data name="RegistryPublic.Contributed" xml:space="preserve">
<value>[[Contributed]]</value>
</data>
<data name="RegistryPublic.LoginToPurchase" xml:space="preserve">
<value>[[Login to purchase]]</value>
</data>
<data name="RegistryPublic.Purchase" xml:space="preserve">
<value>[[Purchase]]</value>
</data>
<data name="RegistryPublic.ManagePurchases" xml:space="preserve">
<value>[[Manage purchases]]</value>
</data>
<data name="RegistryPublic.EditPurchase" xml:space="preserve">
<value>[[Edit purchase]]</value>
</data>
<data name="RegistryPublic.MarkPurchased" xml:space="preserve">
<value>[[Mark purchased]]</value>
</data>
<data name="RegistryPublic.ManageParticipations" xml:space="preserve">
<value>[[Manage participations]]</value>
</data>
<data name="RegistryPublic.EditParticipation" xml:space="preserve">
<value>[[Edit participation]]</value>
</data>
<data name="RegistryPublic.PartiallyFulfill" xml:space="preserve">
<value>[[Partially fulfill]]</value>
</data>
<data name="RegistryPublic.MarkAsPurchased" xml:space="preserve">
<value>[[Mark as purchased]]</value>
</data>
<data name="RegistryPublic.HowManyUnitsPurchased" xml:space="preserve">
<value>[[How many units did you purchase?]]</value>
</data>
<data name="RegistryPublic.OpenProductLink" xml:space="preserve">
<value>[[Open product link]]</value>
</data>
<data name="RegistryPublic.ConfirmPurchase" xml:space="preserve">
<value>[[Confirm purchase]]</value>
</data>
<data name="RegistryPublic.LogContribution" xml:space="preserve">
<value>[[Log contribution]]</value>
</data>
<data name="RegistryPublic.TransferredAmount" xml:space="preserve">
<value>[[Transferred amount]]</value>
</data>
<data name="RegistryPublic.Confirm" xml:space="preserve">
<value>[[Confirm]]</value>
</data>
<data name="RegistryPublic.SelectPurchaserToUnmark" xml:space="preserve">
<value>[[Select purchaser to unmark]]</value>
</data>
<data name="RegistryPublic.MultipleUsersPurchased" xml:space="preserve">
<value>[[Multiple users have purchased this item. Choose which purchase to unmark:]]</value>
</data>
<data name="RegistryPublic.Unmark" xml:space="preserve">
<value>[[Unmark]]</value>
</data>
<data name="RegistryPublic.UnmarkAll" xml:space="preserve">
<value>[[Unmark all]]</value>
</data>
<data name="RegistryPublic.PartiallyFulfillItem" xml:space="preserve">
<value>[[Partially fulfill item]]</value>
</data>
<data name="RegistryPublic.SelectDonateMethod" xml:space="preserve">
<value>[[Select how you want to donate:]]</value>
</data>
<data name="RegistryPublic.IbanTransfer" xml:space="preserve">
<value>[[IBAN transfer]]</value>
</data>
<data name="RegistryPublic.SingleQrCode" xml:space="preserve">
<value>[[Single QR code]]</value>
</data>
<data name="RegistryPublic.QrCodePerAmount" xml:space="preserve">
<value>[[QR code per amount]]</value>
</data>
<data name="RegistryPublic.OpenPaymentLink" xml:space="preserve">
<value>[[Open payment link]]</value>
</data>
<data name="RegistryPublic.HowMuchAdded" xml:space="preserve">
<value>[[How much did you add for this item?]]</value>
</data>
<data name="RegistryPublic.NoQrForAmount" xml:space="preserve">
<value>[[No QR code configured for this exact amount.]]</value>
</data>
<data name="RegistryPublic.ManagePurchase" xml:space="preserve">
<value>[[Manage purchase]]</value>
</data>
<data name="RegistryPublic.ManageParticipation" xml:space="preserve">
<value>[[Manage participation]]</value>
</data>
<data name="RegistryPublic.User" xml:space="preserve">
<value>[[User]]</value>
</data>
<data name="RegistryContributionAmount.PageTitle" xml:space="preserve">
<value>[[Contribution Amount]]</value>
</data>
<data name="RegistryContributionAmount.ItemNotFound" xml:space="preserve">
<value>[[Item not found.]]</value>
</data>
<data name="RegistryContributionAmount.LoginFirst" xml:space="preserve">
<value>[[Please log in first.]]</value>
</data>
<data name="RegistryContributionAmount.PartiallyFulfill" xml:space="preserve">
<value>[[Partially fulfill: {0}]]</value>
</data>
<data name="RegistryContributionAmount.NoRepresentableAmount" xml:space="preserve">
<value>[[No representable amount can be formed from configured QR codes up to {0}200.]]</value>
</data>
<data name="RegistryContributionAmount.SelectAmount" xml:space="preserve">
<value>[[Select amount: {0}{1}]]</value>
</data>
<data name="RegistryContributionAmount.NoQrCombination" xml:space="preserve">
<value>[[No QR combination available for this amount.]]</value>
</data>
<data name="RegistryContributionAmount.SuggestedCombination" xml:space="preserve">
<value>[[Suggested QR combination:]]</value>
</data>
<data name="RegistryContributionAmount.OpenPaymentLink" xml:space="preserve">
<value>[[Open payment link]]</value>
</data>
<data name="RegistryContributionAmount.TransferredAmount" xml:space="preserve">
<value>[[I transferred this amount]]</value>
</data>
<data name="RegistryInvite.PageTitle" xml:space="preserve">
<value>[[Admin Invite]]</value>
</data>
<data name="RegistryInvite.Title" xml:space="preserve">
<value>[[Admin invitation]]</value>
</data>
<data name="RegistryInvite.Validating" xml:space="preserve">
<value>[[Validating invitation...]]</value>
</data>
<data name="RegistryInvite.Accepted" xml:space="preserve">
<value>[[Invitation accepted. You are now an admin.]]</value>
</data>
<data name="RegistryInvite.GoToAdmin" xml:space="preserve">
<value>[[Go to admin]]</value>
</data>
<data name="RegistryInvite.Invalid" xml:space="preserve">
<value>[[The invitation is invalid or already used.]]</value>
</data>
<data name="RegistryAdmin.PageTitle" xml:space="preserve">
<value>[[Registry Admin]]</value>
</data>
<data name="RegistryAdmin.Title" xml:space="preserve">
<value>[[Registry Admin]]</value>
</data>
<data name="RegistryAdmin.SmtpNotConfigured" xml:space="preserve">
<value>[[SMTP is not configured. Email features (identity emails and admin invite emails) are disabled. Configure the Smtp section in appsettings or user secrets.]]</value>
</data>
<data name="RegistryAdmin.Tab.Items" xml:space="preserve">
<value>[[Items]]</value>
</data>
<data name="RegistryAdmin.Tab.Settings" xml:space="preserve">
<value>[[Settings]]</value>
</data>
<data name="RegistryAdmin.Tab.Administrators" xml:space="preserve">
<value>[[Administrators]]</value>
</data>
<data name="RegistryAdmin.Tab.Addresses" xml:space="preserve">
<value>[[Addresses]]</value>
</data>
<data name="RegistryAdmin.Tab.ActionLog" xml:space="preserve">
<value>[[Action Log]]</value>
</data>
<data name="RegistryAdmin.AddOrEditItem" xml:space="preserve">
<value>[[Add or edit item]]</value>
</data>
<data name="RegistryAdmin.Name" xml:space="preserve">
<value>[[Name]]</value>
</data>
<data name="RegistryAdmin.ProductUrl" xml:space="preserve">
<value>[[Product URL]]</value>
</data>
<data name="RegistryAdmin.AutoFetch" xml:space="preserve">
<value>[[Auto fetch]]</value>
</data>
<data name="RegistryAdmin.PictureUrl" xml:space="preserve">
<value>[[Picture URL]]</value>
</data>
<data name="RegistryAdmin.Description" xml:space="preserve">
<value>[[Description]]</value>
</data>
<data name="RegistryAdmin.Price" xml:space="preserve">
<value>[[Price]]</value>
</data>
<data name="RegistryAdmin.CurrencySymbol" xml:space="preserve">
<value>[[Currency symbol]]</value>
</data>
<data name="RegistryAdmin.DesiredQty" xml:space="preserve">
<value>[[Desired qty]]</value>
</data>
<data name="RegistryAdmin.Participation" xml:space="preserve">
<value>[[Participation]]</value>
</data>
<data name="RegistryAdmin.ParticipationTarget" xml:space="preserve">
<value>[[Participation target]]</value>
</data>
<data name="RegistryAdmin.SecondHandPreference" xml:space="preserve">
<value>[[Second hand preference]]</value>
</data>
<data name="RegistryAdmin.SecondHandOptional" xml:space="preserve">
<value>[[Second hand optional]]</value>
</data>
<data name="RegistryAdmin.PreferSecondHand" xml:space="preserve">
<value>[[Prefer second hand]]</value>
</data>
<data name="RegistryAdmin.NewOnly" xml:space="preserve">
<value>[[New only]]</value>
</data>
<data name="RegistryAdmin.Given" xml:space="preserve">
<value>[[Given]]</value>
</data>
<data name="RegistryAdmin.Category" xml:space="preserve">
<value>[[Category]]</value>
</data>
<data name="RegistryAdmin.SaveItem" xml:space="preserve">
<value>[[Save item]]</value>
</data>
<data name="RegistryAdmin.CategoriesAndItems" xml:space="preserve">
<value>[[Categories and items]]</value>
</data>
<data name="RegistryAdmin.NewCategory" xml:space="preserve">
<value>[[New category]]</value>
</data>
<data name="RegistryAdmin.AddCategory" xml:space="preserve">
<value>[[Add category]]</value>
</data>
<data name="RegistryAdmin.DragHint" xml:space="preserve">
<value>[[Drag categories or items to reorder. Drop items into another category to regroup them.]]</value>
</data>
<data name="RegistryAdmin.Rename" xml:space="preserve">
<value>[[Rename]]</value>
</data>
<data name="RegistryAdmin.DropItemsHere" xml:space="preserve">
<value>[[Drop items here.]]</value>
</data>
<data name="RegistryAdmin.Header.Name" xml:space="preserve">
<value>[[Name]]</value>
</data>
<data name="RegistryAdmin.Header.DesiredQty" xml:space="preserve">
<value>[[Desired Qty]]</value>
</data>
<data name="RegistryAdmin.Header.Participation" xml:space="preserve">
<value>[[Participation]]</value>
</data>
<data name="RegistryAdmin.Header.PurchasedContributedBy" xml:space="preserve">
<value>[[Purchased by / Contributed by]]</value>
</data>
<data name="RegistryAdmin.Purchased" xml:space="preserve">
<value>[[Purchased:]]</value>
</data>
<data name="RegistryAdmin.Contributed" xml:space="preserve">
<value>[[Contributed:]]</value>
</data>
<data name="RegistryAdmin.RegistrySettings" xml:space="preserve">
<value>[[Registry Settings]]</value>
</data>
<data name="RegistryAdmin.BabyName" xml:space="preserve">
<value>[[Baby name]]</value>
</data>
<data name="RegistryAdmin.BirthDate" xml:space="preserve">
<value>[[Birth date]]</value>
</data>
<data name="RegistryAdmin.Theme" xml:space="preserve">
<value>[[Theme]]</value>
</data>
<data name="RegistryAdmin.Theme.Default" xml:space="preserve">
<value>[[Default]]</value>
</data>
<data name="RegistryAdmin.Theme.Soft" xml:space="preserve">
<value>[[Soft]]</value>
</data>
<data name="RegistryAdmin.Theme.Modern" xml:space="preserve">
<value>[[Modern]]</value>
</data>
<data name="RegistryAdmin.ShippingAddress" xml:space="preserve">
<value>[[Shipping address]]</value>
</data>
<data name="RegistryAdmin.LineBreaksPreserved" xml:space="preserve">
<value>[[Line breaks will be preserved]]</value>
</data>
<data name="RegistryAdmin.TopContent" xml:space="preserve">
<value>[[Top content]]</value>
</data>
<data name="RegistryAdmin.WelcomeText" xml:space="preserve">
<value>[[Welcome text]]</value>
</data>
<data name="RegistryAdmin.BankAccountSettings" xml:space="preserve">
<value>[[Bank Account Settings]]</value>
</data>
<data name="RegistryAdmin.BankAccountName" xml:space="preserve">
<value>[[Bank account name]]</value>
</data>
<data name="RegistryAdmin.DisplayBankAccountName" xml:space="preserve">
<value>[[Display bank account name]]</value>
</data>
<data name="RegistryAdmin.ContributionPaymentOptions" xml:space="preserve">
<value>[[Contribution Payment Options]]</value>
</data>
<data name="RegistryAdmin.SingleQrCodeUrl" xml:space="preserve">
<value>[[Single QR code URL]]</value>
</data>
<data name="RegistryAdmin.SingleQrHelp" xml:space="preserve">
<value>[[Optional: one QR code that donors can scan for any amount.]]</value>
</data>
<data name="RegistryAdmin.AmountSpecificQrCodes" xml:space="preserve">
<value>[[Amount-specific QR codes]]</value>
</data>
<data name="RegistryAdmin.AddQrAmount" xml:space="preserve">
<value>[[Add QR amount]]</value>
</data>
<data name="RegistryAdmin.NoAmountSpecificQrCodes" xml:space="preserve">
<value>[[No amount-specific QR codes configured.]]</value>
</data>
<data name="RegistryAdmin.QrCodeUrl" xml:space="preserve">
<value>[[QR code URL]]</value>
</data>
<data name="RegistryAdmin.SaveSettings" xml:space="preserve">
<value>[[Save settings]]</value>
</data>
<data name="RegistryAdmin.UserAddresses" xml:space="preserve">
<value>[[User addresses]]</value>
</data>
<data name="RegistryAdmin.NoUsersFound" xml:space="preserve">
<value>[[No users found yet.]]</value>
</data>
<data name="RegistryAdmin.Email" xml:space="preserve">
<value>[[Email]]</value>
</data>
<data name="RegistryAdmin.Address" xml:space="preserve">
<value>[[Address]]</value>
</data>
<data name="RegistryAdmin.CurrentAdministrators" xml:space="preserve">
<value>[[Current administrators]]</value>
</data>
<data name="RegistryAdmin.NoAdmins" xml:space="preserve">
<value>[[No admins assigned yet.]]</value>
</data>
<data name="RegistryAdmin.EmailOrName" xml:space="preserve">
<value>[[Email / Name]]</value>
</data>
<data name="RegistryAdmin.InviteAdministrator" xml:space="preserve">
<value>[[Invite administrator]]</value>
</data>
<data name="RegistryAdmin.OptionalEmail" xml:space="preserve">
<value>[[optional email]]</value>
</data>
<data name="RegistryAdmin.CreateInvite" xml:space="preserve">
<value>[[Create invite]]</value>
</data>
<data name="RegistryAdmin.InviteLink" xml:space="preserve">
<value>[[Invite link:]]</value>
</data>
<data name="RegistryActionLog.PageTitle" xml:space="preserve">
<value>[[Action Log - Registry Admin]]</value>
</data>
<data name="RegistryActionLog.Title" xml:space="preserve">
<value>[[Registry Action Log]]</value>
</data>
<data name="RegistryActionLog.Description" xml:space="preserve">
<value>[[This log shows all user actions on this registry: purchases, contributions, and other interactions.]]</value>
</data>
<data name="RegistryActionLog.NoActions" xml:space="preserve">
<value>[[No actions recorded yet.]]</value>
</data>
<data name="RegistryActionLog.DateTime" xml:space="preserve">
<value>[[Date/Time]]</value>
</data>
<data name="RegistryActionLog.User" xml:space="preserve">
<value>[[User]]</value>
</data>
<data name="RegistryActionLog.Action" xml:space="preserve">
<value>[[Action]]</value>
</data>
<data name="RegistryActionLog.Item" xml:space="preserve">
<value>[[Item]]</value>
</data>
<data name="RegistryActionLog.Quantity" xml:space="preserve">
<value>[[Quantity]]</value>
</data>
<data name="RegistryActionLog.Amount" xml:space="preserve">
<value>[[Amount]]</value>
</data>
<data name="RegistryActionLog.Details" xml:space="preserve">
<value>[[Details]]</value>
</data>
<data name="RegistryActionLog.Badge.RegistryOpened" xml:space="preserve">
<value>[[Registry opened]]</value>
</data>
<data name="RegistryActionLog.Badge.ItemLinkOpened" xml:space="preserve">
<value>[[Item link opened]]</value>
</data>
<data name="RegistryActionLog.Badge.PurchaseMarked" xml:space="preserve">
<value>[[Purchase marked]]</value>
</data>
<data name="RegistryActionLog.Badge.PurchaseUnmarked" xml:space="preserve">
<value>[[Purchase unmarked]]</value>
</data>
<data name="RegistryActionLog.Badge.PartialPurchase" xml:space="preserve">
<value>[[Partial purchase]]</value>
</data>
<data name="RegistryActionLog.Badge.ContributionLogged" xml:space="preserve">
<value>[[Contribution logged]]</value>
</data>
<data name="Error.PageTitle" xml:space="preserve">
<value>[[Error]]</value>
</data>
<data name="Error.Title" xml:space="preserve">
<value>[[Error.]]</value>
</data>
<data name="Error.Subtitle" xml:space="preserve">
<value>[[An error occurred while processing your request.]]</value>
</data>
<data name="Error.RequestId" xml:space="preserve">
<value>[[Request ID:]]</value>
</data>
<data name="Error.DevMode" xml:space="preserve">
<value>[[Development Mode]]</value>
</data>
<data name="Error.DevHint1" xml:space="preserve">
<value>[[Swapping to Development environment will display more detailed information about the error that occurred.]]</value>
</data>
<data name="Error.DevHint2" xml:space="preserve">
<value>[[The Development environment shouldn't be enabled for deployed applications.]]</value>
</data>
<data name="Error.DevHint3" xml:space="preserve">
<value>[[It can result in displaying sensitive information from exceptions to end users.]]</value>
</data>
<data name="Error.DevHint4" xml:space="preserve">
<value>[[For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app.]]</value>
</data>
<data name="MainLayout.UnhandledError" xml:space="preserve">
<value>[[An unhandled error has occurred.]]</value>
</data>
<data name="MainLayout.Reload" xml:space="preserve">
<value>[[Reload]]</value>
</data>
<data name="NavMenu.Brand" xml:space="preserve">
<value>[[BirthList]]</value>
</data>
<data name="NavMenu.NavigationMenu" xml:space="preserve">
<value>[[Navigation menu]]</value>
</data>
<data name="NavMenu.Home" xml:space="preserve">
<value>[[Home]]</value>
</data>
<data name="NavMenu.Logout" xml:space="preserve">
<value>[[Logout]]</value>
</data>
<data name="NavMenu.Register" xml:space="preserve">
<value>[[Register]]</value>
</data>
<data name="NavMenu.Login" xml:space="preserve">
<value>[[Login]]</value>
</data>
</root>
@@ -0,0 +1,247 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, ...</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, ...</value>
</resheader>
<data name="Common.Yes" xml:space="preserve"><value>Yes</value></data>
<data name="Common.No" xml:space="preserve"><value>No</value></data>
<data name="Common.Save" xml:space="preserve"><value>Save</value></data>
<data name="Common.Cancel" xml:space="preserve"><value>Cancel</value></data>
<data name="Common.Remove" xml:space="preserve"><value>Remove</value></data>
<data name="Common.Edit" xml:space="preserve"><value>Edit</value></data>
<data name="Common.Back" xml:space="preserve"><value>Back</value></data>
<data name="Common.Next" xml:space="preserve"><value>Next</value></data>
<data name="Common.Loading" xml:space="preserve"><value>Loading...</value></data>
<data name="Common.AccessDenied" xml:space="preserve"><value>Access denied.</value></data>
<data name="Common.SelectUser" xml:space="preserve"><value>Select user</value></data>
<data name="Common.SearchUser" xml:space="preserve"><value>Search user</value></data>
<data name="Common.Quantity" xml:space="preserve"><value>Quantity</value></data>
<data name="Common.Amount" xml:space="preserve"><value>Amount</value></data>
<data name="Common.Message" xml:space="preserve"><value>Message</value></data>
<data name="TopBar.Language" xml:space="preserve"><value>Language</value></data>
<data name="TopBar.Brand" xml:space="preserve"><value>Gift List</value></data>
<data name="TopBar.ProfilePrompt" xml:space="preserve"><value>Please complete your profile (first name, last name, and address).</value></data>
<data name="TopBar.CompleteProfile" xml:space="preserve"><value>Complete profile</value></data>
<data name="TopBar.AccountSettings" xml:space="preserve"><value>Account settings</value></data>
<data name="TopBar.SignIn" xml:space="preserve"><value>Sign in</value></data>
<data name="TopBar.SignOut" xml:space="preserve"><value>Sign out</value></data>
<data name="Home.PageTitle" xml:space="preserve"><value>Birth Registry</value></data>
<data name="Home.Welcome" xml:space="preserve"><value>Welcome to Gift List</value></data>
<data name="Home.ManagedRegistries" xml:space="preserve"><value>Registries you manage</value></data>
<data name="Home.CreateNew" xml:space="preserve"><value>Create new</value></data>
<data name="Home.NoRegistries" xml:space="preserve"><value>No registries yet.</value></data>
<data name="Home.View" xml:space="preserve"><value>View</value></data>
<data name="Home.Manage" xml:space="preserve"><value>Manage</value></data>
<data name="Home.VisitedRegistries" xml:space="preserve"><value>Visited registries</value></data>
<data name="Home.NoVisitedRegistries" xml:space="preserve"><value>No visited registries yet.</value></data>
<data name="Home.CreateRegistryTitle" xml:space="preserve"><value>Create new registry</value></data>
<data name="Home.Title" xml:space="preserve"><value>Title</value></data>
<data name="Home.Type" xml:space="preserve"><value>Type</value></data>
<data name="Home.Theme" xml:space="preserve"><value>Theme</value></data>
<data name="Home.Theme.Default" xml:space="preserve"><value>Default</value></data>
<data name="Home.Theme.Soft" xml:space="preserve"><value>Soft</value></data>
<data name="Home.Theme.Modern" xml:space="preserve"><value>Modern</value></data>
<data name="Home.RegistryType.Birth" xml:space="preserve"><value>Birth</value></data>
<data name="Home.RegistryType.Wedding" xml:space="preserve"><value>Wedding</value></data>
<data name="Home.RegistryType.Birthday" xml:space="preserve"><value>Birthday</value></data>
<data name="Home.MustBeLoggedIn" xml:space="preserve"><value>You must be logged in to create a registry.</value></data>
<data name="Home.TitleRequired" xml:space="preserve"><value>Title is required.</value></data>
<data name="Home.LoginPromptPrefix" xml:space="preserve"><value>Please </value></data>
<data name="Home.LoginPromptLinkText" xml:space="preserve"><value>log in</value></data>
<data name="Home.LoginPromptSuffix" xml:space="preserve"><value> to create and manage registries.</value></data>
<data name="RegistryPublic.PageTitle" xml:space="preserve"><value>Registry</value></data>
<data name="RegistryPublic.NotFound" xml:space="preserve"><value>Registry not found.</value></data>
<data name="RegistryPublic.ShippingAddress" xml:space="preserve"><value>Shipping address</value></data>
<data name="RegistryPublic.BankTransferParticipation" xml:space="preserve"><value>Bank transfer participation</value></data>
<data name="RegistryPublic.IBAN" xml:space="preserve"><value>IBAN</value></data>
<data name="RegistryPublic.BIC" xml:space="preserve"><value>BIC</value></data>
<data name="RegistryPublic.SecondHandPreferred" xml:space="preserve"><value>Second-hand preferred</value></data>
<data name="RegistryPublic.SecondHandOptional" xml:space="preserve"><value>Second-hand optional</value></data>
<data name="RegistryPublic.Qty" xml:space="preserve"><value>Qty:</value></data>
<data name="RegistryPublic.PurchasedSuffix" xml:space="preserve"><value>purchased</value></data>
<data name="RegistryPublic.Price" xml:space="preserve"><value>Price:</value></data>
<data name="RegistryPublic.Participation" xml:space="preserve"><value>Participation:</value></data>
<data name="RegistryPublic.OutOfFulfilled" xml:space="preserve"><value>out of {0}{1} fulfilled</value></data>
<data name="RegistryPublic.FulfilledOnly" xml:space="preserve"><value>fulfilled</value></data>
<data name="RegistryPublic.PurchasedBy" xml:space="preserve"><value>Purchased by:</value></data>
<data name="RegistryPublic.ContributedBy" xml:space="preserve"><value>Contributed by:</value></data>
<data name="RegistryPublic.YouQuantity" xml:space="preserve"><value>You ({0})</value></data>
<data name="RegistryPublic.AndOtherPeople" xml:space="preserve"><value>and {0} other {1}</value></data>
<data name="RegistryPublic.Person" xml:space="preserve"><value>person</value></data>
<data name="RegistryPublic.People" xml:space="preserve"><value>people</value></data>
<data name="RegistryPublic.AndOthers" xml:space="preserve"><value>and others</value></data>
<data name="RegistryPublic.Purchased" xml:space="preserve"><value>Purchased</value></data>
<data name="RegistryPublic.Contributed" xml:space="preserve"><value>Contributed</value></data>
<data name="RegistryPublic.LoginToPurchase" xml:space="preserve"><value>Login to purchase</value></data>
<data name="RegistryPublic.Purchase" xml:space="preserve"><value>Purchase</value></data>
<data name="RegistryPublic.ManagePurchases" xml:space="preserve"><value>Manage purchases</value></data>
<data name="RegistryPublic.EditPurchase" xml:space="preserve"><value>Edit purchase</value></data>
<data name="RegistryPublic.MarkPurchased" xml:space="preserve"><value>Mark purchased</value></data>
<data name="RegistryPublic.ManageParticipations" xml:space="preserve"><value>Manage participations</value></data>
<data name="RegistryPublic.EditParticipation" xml:space="preserve"><value>Edit participation</value></data>
<data name="RegistryPublic.PartiallyFulfill" xml:space="preserve"><value>Partially fulfill</value></data>
<data name="RegistryPublic.MarkAsPurchased" xml:space="preserve"><value>Mark as purchased</value></data>
<data name="RegistryPublic.HowManyUnitsPurchased" xml:space="preserve"><value>How many units did you purchase?</value></data>
<data name="RegistryPublic.OpenProductLink" xml:space="preserve"><value>Open product link</value></data>
<data name="RegistryPublic.ConfirmPurchase" xml:space="preserve"><value>Confirm purchase</value></data>
<data name="RegistryPublic.LogContribution" xml:space="preserve"><value>Log contribution</value></data>
<data name="RegistryPublic.TransferredAmount" xml:space="preserve"><value>Transferred amount</value></data>
<data name="RegistryPublic.Confirm" xml:space="preserve"><value>Confirm</value></data>
<data name="RegistryPublic.SelectPurchaserToUnmark" xml:space="preserve"><value>Select purchaser to unmark</value></data>
<data name="RegistryPublic.MultipleUsersPurchased" xml:space="preserve"><value>Multiple users have purchased this item. Choose which purchase to unmark:</value></data>
<data name="RegistryPublic.Unmark" xml:space="preserve"><value>Unmark</value></data>
<data name="RegistryPublic.UnmarkAll" xml:space="preserve"><value>Unmark all</value></data>
<data name="RegistryPublic.PartiallyFulfillItem" xml:space="preserve"><value>Partially fulfill item</value></data>
<data name="RegistryPublic.SelectDonateMethod" xml:space="preserve"><value>Select how you want to donate:</value></data>
<data name="RegistryPublic.IbanTransfer" xml:space="preserve"><value>IBAN transfer</value></data>
<data name="RegistryPublic.SingleQrCode" xml:space="preserve"><value>Single QR code</value></data>
<data name="RegistryPublic.QrCodePerAmount" xml:space="preserve"><value>QR code per amount</value></data>
<data name="RegistryPublic.OpenPaymentLink" xml:space="preserve"><value>Open payment link</value></data>
<data name="RegistryPublic.HowMuchAdded" xml:space="preserve"><value>How much did you add for this item?</value></data>
<data name="RegistryPublic.NoQrForAmount" xml:space="preserve"><value>No QR code configured for this exact amount.</value></data>
<data name="RegistryPublic.ManagePurchase" xml:space="preserve"><value>Manage purchase</value></data>
<data name="RegistryPublic.ManageParticipation" xml:space="preserve"><value>Manage participation</value></data>
<data name="RegistryPublic.User" xml:space="preserve"><value>User</value></data>
<data name="RegistryContributionAmount.PageTitle" xml:space="preserve"><value>Contribution Amount</value></data>
<data name="RegistryContributionAmount.ItemNotFound" xml:space="preserve"><value>Item not found.</value></data>
<data name="RegistryContributionAmount.LoginFirst" xml:space="preserve"><value>Please log in first.</value></data>
<data name="RegistryContributionAmount.PartiallyFulfill" xml:space="preserve"><value>Partially fulfill: {0}</value></data>
<data name="RegistryContributionAmount.NoRepresentableAmount" xml:space="preserve"><value>No representable amount can be formed from configured QR codes up to {0}200.</value></data>
<data name="RegistryContributionAmount.SelectAmount" xml:space="preserve"><value>Select amount: {0}{1}</value></data>
<data name="RegistryContributionAmount.NoQrCombination" xml:space="preserve"><value>No QR combination available for this amount.</value></data>
<data name="RegistryContributionAmount.SuggestedCombination" xml:space="preserve"><value>Suggested QR combination:</value></data>
<data name="RegistryContributionAmount.OpenPaymentLink" xml:space="preserve"><value>Open payment link</value></data>
<data name="RegistryContributionAmount.TransferredAmount" xml:space="preserve"><value>I transferred this amount</value></data>
<data name="RegistryInvite.PageTitle" xml:space="preserve"><value>Admin Invite</value></data>
<data name="RegistryInvite.Title" xml:space="preserve"><value>Admin invitation</value></data>
<data name="RegistryInvite.Validating" xml:space="preserve"><value>Validating invitation...</value></data>
<data name="RegistryInvite.Accepted" xml:space="preserve"><value>Invitation accepted. You are now an admin.</value></data>
<data name="RegistryInvite.GoToAdmin" xml:space="preserve"><value>Go to admin</value></data>
<data name="RegistryInvite.Invalid" xml:space="preserve"><value>The invitation is invalid or already used.</value></data>
<data name="RegistryAdmin.PageTitle" xml:space="preserve"><value>Registry Admin</value></data>
<data name="RegistryAdmin.Title" xml:space="preserve"><value>Registry Admin</value></data>
<data name="RegistryAdmin.SmtpNotConfigured" xml:space="preserve"><value>SMTP is not configured. Email features (identity emails and admin invite emails) are disabled. Configure the Smtp section in appsettings or user secrets.</value></data>
<data name="RegistryAdmin.Tab.Items" xml:space="preserve"><value>Items</value></data>
<data name="RegistryAdmin.Tab.Settings" xml:space="preserve"><value>Settings</value></data>
<data name="RegistryAdmin.Tab.Administrators" xml:space="preserve"><value>Administrators</value></data>
<data name="RegistryAdmin.Tab.Addresses" xml:space="preserve"><value>Addresses</value></data>
<data name="RegistryAdmin.Tab.ActionLog" xml:space="preserve"><value>Action Log</value></data>
<data name="RegistryAdmin.AddOrEditItem" xml:space="preserve"><value>Add or edit item</value></data>
<data name="RegistryAdmin.Name" xml:space="preserve"><value>Name</value></data>
<data name="RegistryAdmin.ProductUrl" xml:space="preserve"><value>Product URL</value></data>
<data name="RegistryAdmin.AutoFetch" xml:space="preserve"><value>Auto fetch</value></data>
<data name="RegistryAdmin.PictureUrl" xml:space="preserve"><value>Picture URL</value></data>
<data name="RegistryAdmin.Description" xml:space="preserve"><value>Description</value></data>
<data name="RegistryAdmin.Price" xml:space="preserve"><value>Price</value></data>
<data name="RegistryAdmin.CurrencySymbol" xml:space="preserve"><value>Currency symbol</value></data>
<data name="RegistryAdmin.DesiredQty" xml:space="preserve"><value>Desired qty</value></data>
<data name="RegistryAdmin.Participation" xml:space="preserve"><value>Participation</value></data>
<data name="RegistryAdmin.ParticipationTarget" xml:space="preserve"><value>Participation target</value></data>
<data name="RegistryAdmin.SecondHandPreference" xml:space="preserve"><value>Second hand preference</value></data>
<data name="RegistryAdmin.SecondHandOptional" xml:space="preserve"><value>Second hand optional</value></data>
<data name="RegistryAdmin.PreferSecondHand" xml:space="preserve"><value>Prefer second hand</value></data>
<data name="RegistryAdmin.NewOnly" xml:space="preserve"><value>New only</value></data>
<data name="RegistryAdmin.Given" xml:space="preserve"><value>Given</value></data>
<data name="RegistryAdmin.Category" xml:space="preserve"><value>Category</value></data>
<data name="RegistryAdmin.SaveItem" xml:space="preserve"><value>Save item</value></data>
<data name="RegistryAdmin.CategoriesAndItems" xml:space="preserve"><value>Categories and items</value></data>
<data name="RegistryAdmin.NewCategory" xml:space="preserve"><value>New category</value></data>
<data name="RegistryAdmin.AddCategory" xml:space="preserve"><value>Add category</value></data>
<data name="RegistryAdmin.DragHint" xml:space="preserve"><value>Drag categories or items to reorder. Drop items into another category to regroup them.</value></data>
<data name="RegistryAdmin.Rename" xml:space="preserve"><value>Rename</value></data>
<data name="RegistryAdmin.DropItemsHere" xml:space="preserve"><value>Drop items here.</value></data>
<data name="RegistryAdmin.Header.Name" xml:space="preserve"><value>Name</value></data>
<data name="RegistryAdmin.Header.DesiredQty" xml:space="preserve"><value>Desired Qty</value></data>
<data name="RegistryAdmin.Header.Participation" xml:space="preserve"><value>Participation</value></data>
<data name="RegistryAdmin.Header.PurchasedContributedBy" xml:space="preserve"><value>Purchased by / Contributed by</value></data>
<data name="RegistryAdmin.Purchased" xml:space="preserve"><value>Purchased:</value></data>
<data name="RegistryAdmin.Contributed" xml:space="preserve"><value>Contributed:</value></data>
<data name="RegistryAdmin.RegistrySettings" xml:space="preserve"><value>Registry Settings</value></data>
<data name="RegistryAdmin.BabyName" xml:space="preserve"><value>Baby name</value></data>
<data name="RegistryAdmin.BirthDate" xml:space="preserve"><value>Birth date</value></data>
<data name="RegistryAdmin.Theme" xml:space="preserve"><value>Theme</value></data>
<data name="RegistryAdmin.Theme.Default" xml:space="preserve"><value>Default</value></data>
<data name="RegistryAdmin.Theme.Soft" xml:space="preserve"><value>Soft</value></data>
<data name="RegistryAdmin.Theme.Modern" xml:space="preserve"><value>Modern</value></data>
<data name="RegistryAdmin.ShippingAddress" xml:space="preserve"><value>Shipping address</value></data>
<data name="RegistryAdmin.LineBreaksPreserved" xml:space="preserve"><value>Line breaks will be preserved</value></data>
<data name="RegistryAdmin.TopContent" xml:space="preserve"><value>Top content</value></data>
<data name="RegistryAdmin.WelcomeText" xml:space="preserve"><value>Welcome text</value></data>
<data name="RegistryAdmin.BankAccountSettings" xml:space="preserve"><value>Bank Account Settings</value></data>
<data name="RegistryAdmin.BankAccountName" xml:space="preserve"><value>Bank account name</value></data>
<data name="RegistryAdmin.DisplayBankAccountName" xml:space="preserve"><value>Display bank account name</value></data>
<data name="RegistryAdmin.ContributionPaymentOptions" xml:space="preserve"><value>Contribution Payment Options</value></data>
<data name="RegistryAdmin.SingleQrCodeUrl" xml:space="preserve"><value>Single QR code URL</value></data>
<data name="RegistryAdmin.SingleQrHelp" xml:space="preserve"><value>Optional: one QR code that donors can scan for any amount.</value></data>
<data name="RegistryAdmin.AmountSpecificQrCodes" xml:space="preserve"><value>Amount-specific QR codes</value></data>
<data name="RegistryAdmin.AddQrAmount" xml:space="preserve"><value>Add QR amount</value></data>
<data name="RegistryAdmin.NoAmountSpecificQrCodes" xml:space="preserve"><value>No amount-specific QR codes configured.</value></data>
<data name="RegistryAdmin.QrCodeUrl" xml:space="preserve"><value>QR code URL</value></data>
<data name="RegistryAdmin.SaveSettings" xml:space="preserve"><value>Save settings</value></data>
<data name="RegistryAdmin.UserAddresses" xml:space="preserve"><value>User addresses</value></data>
<data name="RegistryAdmin.NoUsersFound" xml:space="preserve"><value>No users found yet.</value></data>
<data name="RegistryAdmin.Email" xml:space="preserve"><value>Email</value></data>
<data name="RegistryAdmin.Address" xml:space="preserve"><value>Address</value></data>
<data name="RegistryAdmin.CurrentAdministrators" xml:space="preserve"><value>Current administrators</value></data>
<data name="RegistryAdmin.NoAdmins" xml:space="preserve"><value>No admins assigned yet.</value></data>
<data name="RegistryAdmin.EmailOrName" xml:space="preserve"><value>Email / Name</value></data>
<data name="RegistryAdmin.InviteAdministrator" xml:space="preserve"><value>Invite administrator</value></data>
<data name="RegistryAdmin.OptionalEmail" xml:space="preserve"><value>optional email</value></data>
<data name="RegistryAdmin.CreateInvite" xml:space="preserve"><value>Create invite</value></data>
<data name="RegistryAdmin.InviteLink" xml:space="preserve"><value>Invite link:</value></data>
<data name="RegistryActionLog.PageTitle" xml:space="preserve"><value>Action Log - Registry Admin</value></data>
<data name="RegistryActionLog.Title" xml:space="preserve"><value>Registry Action Log</value></data>
<data name="RegistryActionLog.Description" xml:space="preserve"><value>This log shows all user actions on this registry: purchases, contributions, and other interactions.</value></data>
<data name="RegistryActionLog.NoActions" xml:space="preserve"><value>No actions recorded yet.</value></data>
<data name="RegistryActionLog.DateTime" xml:space="preserve"><value>Date/Time</value></data>
<data name="RegistryActionLog.User" xml:space="preserve"><value>User</value></data>
<data name="RegistryActionLog.Action" xml:space="preserve"><value>Action</value></data>
<data name="RegistryActionLog.Item" xml:space="preserve"><value>Item</value></data>
<data name="RegistryActionLog.Quantity" xml:space="preserve"><value>Quantity</value></data>
<data name="RegistryActionLog.Amount" xml:space="preserve"><value>Amount</value></data>
<data name="RegistryActionLog.Details" xml:space="preserve"><value>Details</value></data>
<data name="RegistryActionLog.Badge.RegistryOpened" xml:space="preserve"><value>Registry opened</value></data>
<data name="RegistryActionLog.Badge.ItemLinkOpened" xml:space="preserve"><value>Item link opened</value></data>
<data name="RegistryActionLog.Badge.PurchaseMarked" xml:space="preserve"><value>Purchase marked</value></data>
<data name="RegistryActionLog.Badge.PurchaseUnmarked" xml:space="preserve"><value>Purchase unmarked</value></data>
<data name="RegistryActionLog.Badge.PartialPurchase" xml:space="preserve"><value>Partial purchase</value></data>
<data name="RegistryActionLog.Badge.ContributionLogged" xml:space="preserve"><value>Contribution logged</value></data>
<data name="Error.PageTitle" xml:space="preserve"><value>Error</value></data>
<data name="Error.Title" xml:space="preserve"><value>Error.</value></data>
<data name="Error.Subtitle" xml:space="preserve"><value>An error occurred while processing your request.</value></data>
<data name="Error.RequestId" xml:space="preserve"><value>Request ID:</value></data>
<data name="Error.DevMode" xml:space="preserve"><value>Development Mode</value></data>
<data name="Error.DevHint1" xml:space="preserve"><value>Swapping to Development environment will display more detailed information about the error that occurred.</value></data>
<data name="Error.DevHint2" xml:space="preserve"><value>The Development environment shouldn't be enabled for deployed applications.</value></data>
<data name="Error.DevHint3" xml:space="preserve"><value>It can result in displaying sensitive information from exceptions to end users.</value></data>
<data name="Error.DevHint4" xml:space="preserve"><value>For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app.</value></data>
<data name="MainLayout.UnhandledError" xml:space="preserve"><value>An unhandled error has occurred.</value></data>
<data name="MainLayout.Reload" xml:space="preserve"><value>Reload</value></data>
<data name="NavMenu.Brand" xml:space="preserve"><value>BirthList</value></data>
<data name="NavMenu.NavigationMenu" xml:space="preserve"><value>Navigation menu</value></data>
<data name="NavMenu.Home" xml:space="preserve"><value>Home</value></data>
<data name="NavMenu.Logout" xml:space="preserve"><value>Logout</value></data>
<data name="NavMenu.Register" xml:space="preserve"><value>Register</value></data>
<data name="NavMenu.Login" xml:space="preserve"><value>Login</value></data>
</root>
+5
View File
@@ -0,0 +1,5 @@
namespace BirthList.Web;
public sealed class SharedResources
{
}