Add more translations (#6302)

* Newlines

* Dashboard

* Add more translations

* Moar

* Remove   from translated texts

* Dictionary controller translations

* Batch 1 of controller updates

* Batch 2 of controller updates

* Component translations

* Batch 3 of controller updates

* Fixes
This commit is contained in:
d11n 2024-10-17 15:51:40 +02:00 committed by GitHub
parent 7e1712c8cd
commit 77fba4aee3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
204 changed files with 2639 additions and 1556 deletions

View File

@ -10,7 +10,7 @@ public static class SetStatusMessageModelExtensions
{ {
public static void SetStatusSuccess(this ITempDataDictionary tempData, string statusMessage) public static void SetStatusSuccess(this ITempDataDictionary tempData, string statusMessage)
{ {
tempData.SetStatusMessageModel(new StatusMessageModel() tempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = statusMessage Message = statusMessage
@ -34,19 +34,14 @@ public static class SetStatusMessageModelExtensions
tempData.TryGetValue("StatusMessageModel", out var model); tempData.TryGetValue("StatusMessageModel", out var model);
if (successMessage != null || errorMessage != null) if (successMessage != null || errorMessage != null)
{ {
var parsedModel = new StatusMessageModel(); var parsedModel = new StatusMessageModel
parsedModel.Message = (string)successMessage ?? (string)errorMessage;
if (successMessage != null)
{ {
parsedModel.Severity = StatusMessageModel.StatusSeverity.Success; Message = (string)successMessage ?? (string)errorMessage,
} Severity = successMessage != null ? StatusMessageModel.StatusSeverity.Success : StatusMessageModel.StatusSeverity.Error
else };
{
parsedModel.Severity = StatusMessageModel.StatusSeverity.Error;
}
return parsedModel; return parsedModel;
} }
else if (model != null && model is string str) if (model is string str)
{ {
return JObject.Parse(str).ToObject<StatusMessageModel>(); return JObject.Parse(str).ToObject<StatusMessageModel>();
} }

View File

@ -648,7 +648,7 @@ namespace BTCPayServer.Tests
var store2 = acc.GetController<UIStoresController>(); var store2 = acc.GetController<UIStoresController>();
await store2.Pair(pairingCode.ToString(), store2.CurrentStore.Id); await store2.Pair(pairingCode.ToString(), store2.CurrentStore.Id);
Assert.Contains(nameof(PairingResult.ReusedKey), Assert.Contains(nameof(PairingResult.ReusedKey),
(string)store2.TempData[WellKnownTempData.ErrorMessage], StringComparison.CurrentCultureIgnoreCase); store2.TempData[WellKnownTempData.ErrorMessage].ToString(), StringComparison.CurrentCultureIgnoreCase);
} }
[Fact(Timeout = LongRunningTestTimeout * 2)] [Fact(Timeout = LongRunningTestTimeout * 2)]

View File

@ -2,23 +2,30 @@
@using BTCPayServer.Abstractions.TagHelpers @using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Plugins.Crowdfund @using BTCPayServer.Plugins.Crowdfund
@model BTCPayServer.Components.AppSales.AppSalesViewModel @model BTCPayServer.Components.AppSales.AppSalesViewModel
@{
var label = Model.AppType == CrowdfundAppType.AppType ? "Contributions" : "Sales";
}
<div id="AppSales-@Model.Id" class="widget app-sales"> <div id="AppSales-@Model.Id" class="widget app-sales">
<header class="mb-3"> <header class="mb-3">
<h3>@Model.Name @label</h3> <h3>
@Model.Name
@if (Model.AppType == CrowdfundAppType.AppType)
{
<span text-translate="true">Contributions</span>
}
else
{
<span text-translate="true">Sales</span>
}
</h3>
@if (!string.IsNullOrEmpty(Model.AppUrl)) @if (!string.IsNullOrEmpty(Model.AppUrl))
{ {
<a href="@Model.AppUrl">Manage</a> <a href="@Model.AppUrl" text-translate="true">Manage</a>
} }
</header> </header>
@if (Model.InitialRendering) @if (Model.InitialRendering)
{ {
<div class="loading d-flex justify-content-center p-3"> <div class="loading d-flex justify-content-center p-3">
<div class="spinner-border text-light" role="status"> <div class="spinner-border text-light" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
</div> </div>
<script src="~/Components/AppSales/Default.cshtml.js" asp-append-version="true"></script> <script src="~/Components/AppSales/Default.cshtml.js" asp-append-version="true"></script>
@ -39,7 +46,15 @@
{ {
<header class="mb-3"> <header class="mb-3">
<span> <span>
<span class="sales-count">@Model.SalesCount</span> Total @label <span class="sales-count">@Model.SalesCount</span>
@if (Model.AppType == CrowdfundAppType.AppType)
{
<span text-translate="true">Total Contributions</span>
}
else
{
<span text-translate="true">Total Sales</span>
}
</span> </span>
<div class="btn-group only-for-js" role="group" aria-label="Filter"> <div class="btn-group only-for-js" role="group" aria-label="Filter">
<input type="radio" class="btn-check" name="AppSalesPeriod-@Model.Id" id="AppSalesPeriodWeek-@Model.Id" value="@AppSalesPeriod.Week" @(Model.Period == AppSalesPeriod.Week ? "checked" : "")> <input type="radio" class="btn-check" name="AppSalesPeriod-@Model.Id" id="AppSalesPeriodWeek-@Model.Id" value="@AppSalesPeriod.Week" @(Model.Period == AppSalesPeriod.Week ? "checked" : "")>

View File

@ -1,18 +1,24 @@
@using BTCPayServer.Plugins.Crowdfund @using BTCPayServer.Plugins.Crowdfund
@model BTCPayServer.Components.AppTopItems.AppTopItemsViewModel @model BTCPayServer.Components.AppTopItems.AppTopItemsViewModel
@{
var label = Model.AppType == CrowdfundAppType.AppType ? "contribution" : "sale";
}
<div id="AppTopItems-@Model.Id" class="widget app-top-items"> <div id="AppTopItems-@Model.Id" class="widget app-top-items">
<header class="mb-3"> <header class="mb-3">
<h3>Top @(Model.AppType == CrowdfundAppType.AppType ? "Perks" : "Items")</h3> <h3>
@if (Model.AppType == CrowdfundAppType.AppType)
{
<span text-translate="true">Top Perks</span>
}
else
{
<span text-translate="true">Top Items</span>
}
</h3>
</header> </header>
@if (Model.InitialRendering) @if (Model.InitialRendering)
{ {
<div class="loading d-flex justify-content-center p-3"> <div class="loading d-flex justify-content-center p-3">
<div class="spinner-border text-light" role="status"> <div class="spinner-border text-light" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
</div> </div>
<script src="~/Components/AppTopItems/Default.cshtml.js" asp-append-version="true"></script> <script src="~/Components/AppTopItems/Default.cshtml.js" asp-append-version="true"></script>
@ -45,7 +51,31 @@
@entry.Title @entry.Title
</span> </span>
<span class="app-item-value" data-sensitive> <span class="app-item-value" data-sensitive>
<span class="text-muted">@entry.SalesCount @($"{label}{(entry.SalesCount == 1 ? "" : "s")}"),</span> <span class="text-muted">
@entry.SalesCount
@if (Model.AppType == CrowdfundAppType.AppType)
{
if (entry.SalesCount == 1)
{
<span text-translate="true">contribution</span>
}
else
{
<span text-translate="true">contributions</span>
}
}
else
{
if (entry.SalesCount == 1)
{
<span text-translate="true">sale</span>
}
else
{
<span text-translate="true">sales</span>
}
},
</span>
@entry.TotalFormatted @entry.TotalFormatted
</span> </span>
</div> </div>
@ -55,7 +85,14 @@
else else
{ {
<p class="text-secondary mt-3"> <p class="text-secondary mt-3">
No @($"{label}s") have been made yet. @if (Model.AppType == CrowdfundAppType.AppType)
{
<span text-translate="true">No contributions have been made yet.</span>
}
else
{
<span text-translate="true">No sales have been made yet.</span>
}
</p> </p>
} }
</div> </div>

View File

@ -10,7 +10,7 @@
<div class="d-inline-flex align-items-center gap-2"> <div class="d-inline-flex align-items-center gap-2">
@if (Model.IsArchived) @if (Model.IsArchived)
{ {
<span class="badge bg-warning">archived</span> <span class="badge bg-warning" text-translate="true">archived</span>
} }
<div class="badge badge-@badgeClass" data-invoice-state-badge="@Model.InvoiceId"> <div class="badge badge-@badgeClass" data-invoice-state-badge="@Model.InvoiceId">
@if (canMark) @if (canMark)
@ -21,13 +21,13 @@
<div class="dropdown-menu"> <div class="dropdown-menu">
@if (Model.State.CanMarkInvalid()) @if (Model.State.CanMarkInvalid())
{ {
<button type="button" class="dropdown-item lh-base" data-invoice-id="@Model.InvoiceId" data-new-state="invalid"> <button type="button" class="dropdown-item lh-base" data-invoice-id="@Model.InvoiceId" data-new-state="invalid" text-translate="true">
Mark as invalid Mark as invalid
</button> </button>
} }
@if (Model.State.CanMarkComplete()) @if (Model.State.CanMarkComplete())
{ {
<button type="button" class="dropdown-item lh-base" data-invoice-id="@Model.InvoiceId" data-new-state="settled"> <button type="button" class="dropdown-item lh-base" data-invoice-id="@Model.InvoiceId" data-new-state="settled" text-translate="true">
Mark as settled Mark as settled
</button> </button>
} }
@ -62,6 +62,6 @@
} }
@if (Model.HasRefund) @if (Model.HasRefund)
{ {
<span class="badge bg-warning">Refund</span> <span class="badge bg-warning" text-translate="true">Refund</span>
} }
</div> </div>

View File

@ -11,7 +11,7 @@
walletId = Model.WalletObjectId.WalletId walletId = Model.WalletObjectId.WalletId
}): string.Empty; }): string.Empty;
} }
<input id="@elementId" placeholder="Select labels" autocomplete="off" value="@string.Join(",", Model.SelectedLabels)" <input id="@elementId" placeholder=@StringLocalizer["Select labels"] autocomplete="off" value="@string.Join(",", Model.SelectedLabels)"
class="only-for-js form-control label-manager ts-wrapper @(Model.DisplayInline ? "ts-inline" : "")" class="only-for-js form-control label-manager ts-wrapper @(Model.DisplayInline ? "ts-inline" : "")"
data-fetch-url="@fetchUrl" data-fetch-url="@fetchUrl"
data-update-url="@updateUrl" data-update-url="@updateUrl"

View File

@ -13,7 +13,7 @@
@if (Model.Skip > 0) @if (Model.Skip > 0)
{ {
<li class="page-item"> <li class="page-item">
<a class="page-link" tabindex="-1" href="@NavigatePages(-1, Model.Count)">Prev</a> <a class="page-link" tabindex="-1" href="@NavigatePages(-1, Model.Count)" text-translate="true">Prev</a>
</li> </li>
} }
<li class="page-item disabled"> <li class="page-item disabled">
@ -35,7 +35,7 @@
@if ((Model.Total is null && Model.CurrentPageCount >= Model.Count) || (Model.Total is not null && Model.Total.Value > Model.Skip + Model.Count)) @if ((Model.Total is null && Model.CurrentPageCount >= Model.Count) || (Model.Total is not null && Model.Total.Value > Model.Skip + Model.Count))
{ {
<li class="page-item"> <li class="page-item">
<a class="page-link" href="@NavigatePages(1, Model.Count)">Next</a> <a class="page-link" href="@NavigatePages(1, Model.Count)" text-translate="true">Next</a>
</li> </li>
} }
</ul> </ul>
@ -45,7 +45,7 @@
{ {
<ul class="pagination ms-auto"> <ul class="pagination ms-auto">
<li class="page-item disabled"> <li class="page-item disabled">
<span class="page-link">Page Size</span> <span class="page-link" text-translate="true">Page Size</span>
</li> </li>
@foreach (var pageSize in pageSizeOptions) @foreach (var pageSize in pageSizeOptions)
{ {

View File

@ -29,7 +29,7 @@
<div class="d-flex align-items-baseline gap-1"> <div class="d-flex align-items-baseline gap-1">
<h3 class="d-inline-block me-1" data-balance="@Model.TotalOffchain" data-sensitive>@Model.TotalOffchain</h3> <h3 class="d-inline-block me-1" data-balance="@Model.TotalOffchain" data-sensitive>@Model.TotalOffchain</h3>
<span class="text-secondary fw-semibold text-nowrap"> <span class="text-secondary fw-semibold text-nowrap">
@ViewLocalizer["<span class=\"currency\">{0}</span> in channels", @Model.CryptoCode] @ViewLocalizer["<span class=\"currency\">{0}</span> in channels", Model.CryptoCode]
</span> </span>
</div> </div>
@ -41,7 +41,7 @@
@Model.Balance.OffchainBalance.Opening @Model.Balance.OffchainBalance.Opening
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
@ViewLocalizer["<span class=\"currency\">{0}</span> opening channels", @Model.CryptoCode] @ViewLocalizer["<span class=\"currency\">{0}</span> opening channels", Model.CryptoCode]
</span> </span>
</div> </div>
} }
@ -52,7 +52,7 @@
@Model.Balance.OffchainBalance.Local @Model.Balance.OffchainBalance.Local
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
@ViewLocalizer["<span class=\"currency\">{0}</span> local balance", @Model.CryptoCode] @ViewLocalizer["<span class=\"currency\">{0}</span> local balance", Model.CryptoCode]
</span> </span>
</div> </div>
} }
@ -63,7 +63,7 @@
@Model.Balance.OffchainBalance.Remote @Model.Balance.OffchainBalance.Remote
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
@ViewLocalizer["<span class=\"currency\">{0}</span> remote balance", @Model.CryptoCode] @ViewLocalizer["<span class=\"currency\">{0}</span> remote balance", Model.CryptoCode]
</span> </span>
</div> </div>
} }
@ -74,7 +74,7 @@
@Model.Balance.OffchainBalance.Closing @Model.Balance.OffchainBalance.Closing
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
@ViewLocalizer["<span class=\"currency\">{0}</span> closing channels", @Model.CryptoCode] @ViewLocalizer["<span class=\"currency\">{0}</span> closing channels", Model.CryptoCode]
</span> </span>
</div> </div>
} }
@ -87,7 +87,7 @@
<div class="d-flex align-items-baseline gap-1"> <div class="d-flex align-items-baseline gap-1">
<h3 class="d-inline-block me-1" data-balance="@Model.TotalOnchain" data-sensitive>@Model.TotalOnchain</h3> <h3 class="d-inline-block me-1" data-balance="@Model.TotalOnchain" data-sensitive>@Model.TotalOnchain</h3>
<span class="text-secondary fw-semibold text-nowrap"> <span class="text-secondary fw-semibold text-nowrap">
@ViewLocalizer["<span class=\"currency\">{0}</span> on-chain", @Model.CryptoCode] @ViewLocalizer["<span class=\"currency\">{0}</span> on-chain", Model.CryptoCode]
</span> </span>
</div> </div>
<div class="balance-details collapse" id="balanceDetailsOnchain"> <div class="balance-details collapse" id="balanceDetailsOnchain">
@ -98,7 +98,7 @@
@Model.Balance.OnchainBalance.Confirmed @Model.Balance.OnchainBalance.Confirmed
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
@ViewLocalizer["<span class=\"currency\">{0}</span> confirmed", @Model.CryptoCode] @ViewLocalizer["<span class=\"currency\">{0}</span> confirmed", Model.CryptoCode]
</span> </span>
</div> </div>
} }
@ -109,7 +109,7 @@
@Model.Balance.OnchainBalance.Unconfirmed @Model.Balance.OnchainBalance.Unconfirmed
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
@ViewLocalizer["<span class=\"currency\">{0}</span> unconfirmed", @Model.CryptoCode] @ViewLocalizer["<span class=\"currency\">{0}</span> unconfirmed", Model.CryptoCode]
</span> </span>
</div> </div>
} }
@ -120,7 +120,7 @@
@Model.Balance.OnchainBalance.Reserved @Model.Balance.OnchainBalance.Reserved
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
@ViewLocalizer["<span class=\"currency\">{0}</span> reserved", @Model.CryptoCode] @ViewLocalizer["<span class=\"currency\">{0}</span> reserved", Model.CryptoCode]
</span> </span>
</div> </div>
} }

View File

@ -12,7 +12,7 @@
asp-route-storeId="@Model.Store.Id" asp-route-storeId="@Model.Store.Id"
target="_blank" target="_blank"
id="PublicNodeInfo" id="PublicNodeInfo"
text-translate="true"> text-translate="true">
Node Info Node Info
</a> </a>
</header> </header>

View File

@ -6,7 +6,7 @@
{ {
<div class="loading d-flex justify-content-center p-3"> <div class="loading d-flex justify-content-center p-3">
<div class="spinner-border text-light" role="status"> <div class="spinner-border text-light" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
</div> </div>
<script> <script>

View File

@ -23,6 +23,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -47,6 +48,7 @@ namespace BTCPayServer.Controllers
readonly ILogger _logger; readonly ILogger _logger;
public PoliciesSettings PoliciesSettings { get; } public PoliciesSettings PoliciesSettings { get; }
public IStringLocalizer StringLocalizer { get; }
public Logs Logs { get; } public Logs Logs { get; }
public UIAccountController( public UIAccountController(
@ -62,6 +64,7 @@ namespace BTCPayServer.Controllers
UserLoginCodeService userLoginCodeService, UserLoginCodeService userLoginCodeService,
LnurlAuthService lnurlAuthService, LnurlAuthService lnurlAuthService,
LinkGenerator linkGenerator, LinkGenerator linkGenerator,
IStringLocalizer stringLocalizer,
Logs logs) Logs logs)
{ {
_userManager = userManager; _userManager = userManager;
@ -78,6 +81,7 @@ namespace BTCPayServer.Controllers
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logs.PayServer; _logger = logs.PayServer;
Logs = logs; Logs = logs;
StringLocalizer = stringLocalizer;
} }
[TempData] [TempData]
@ -149,7 +153,7 @@ namespace BTCPayServer.Controllers
var userId = _userLoginCodeService.Verify(code); var userId = _userLoginCodeService.Verify(code);
if (userId is null) if (userId is null)
{ {
TempData[WellKnownTempData.ErrorMessage] = "Login code was invalid"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Login code was invalid"].Value;
return await Login(returnUrl); return await Login(returnUrl);
} }
@ -187,7 +191,7 @@ namespace BTCPayServer.Controllers
{ {
// Require the user to pass basic checks (approval, confirmed email, not disabled) before they can log on // Require the user to pass basic checks (approval, confirmed email, not disabled) before they can log on
var user = await _userManager.FindByEmailAsync(model.Email); var user = await _userManager.FindByEmailAsync(model.Email);
const string errorMessage = "Invalid login attempt."; var errorMessage = StringLocalizer["Invalid login attempt."].Value;
if (!UserService.TryCanLogin(user, out var message)) if (!UserService.TryCanLogin(user, out var message))
{ {
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
@ -311,7 +315,7 @@ namespace BTCPayServer.Controllers
} }
ViewData["ReturnUrl"] = returnUrl; ViewData["ReturnUrl"] = returnUrl;
var errorMessage = "Invalid login attempt."; var errorMessage = StringLocalizer["Invalid login attempt."].Value;
var user = await _userManager.FindByIdAsync(viewModel.UserId); var user = await _userManager.FindByIdAsync(viewModel.UserId);
if (!UserService.TryCanLogin(user, out var message)) if (!UserService.TryCanLogin(user, out var message))
{ {
@ -629,7 +633,7 @@ namespace BTCPayServer.Controllers
}); });
RegisteredUserId = user.Id; RegisteredUserId = user.Id;
TempData[WellKnownTempData.SuccessMessage] = "Account created."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Account created."].Value;
var requiresConfirmedEmail = policies.RequiresConfirmedEmail && !user.EmailConfirmed; var requiresConfirmedEmail = policies.RequiresConfirmedEmail && !user.EmailConfirmed;
var requiresUserApproval = policies.RequiresUserApproval && !user.Approved; var requiresUserApproval = policies.RequiresUserApproval && !user.Approved;
if (requiresConfirmedEmail) if (requiresConfirmedEmail)
@ -704,7 +708,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Your email has been confirmed." Message = StringLocalizer["Your email has been confirmed."].Value
}); });
await FinalizeInvitationIfApplicable(user); await FinalizeInvitationIfApplicable(user);
return RedirectToAction(nameof(Login), new { email = user.Email }); return RedirectToAction(nameof(Login), new { email = user.Email });
@ -713,7 +717,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Info, Severity = StatusMessageModel.StatusSeverity.Info,
Message = "Your email has been confirmed. Please set your password." Message = StringLocalizer["Your email has been confirmed. Please set your password."].Value
}); });
return await RedirectToSetPassword(user); return await RedirectToSetPassword(user);
} }
@ -811,7 +815,9 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = hasPassword ? "Password successfully set." : "Account successfully created." Message = hasPassword
? StringLocalizer["Password successfully set."].Value
: StringLocalizer["Account successfully created."].Value
}); });
if (!hasPassword) await FinalizeInvitationIfApplicable(user); if (!hasPassword) await FinalizeInvitationIfApplicable(user);
return RedirectToAction(nameof(Login)); return RedirectToAction(nameof(Login));
@ -848,7 +854,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Info, Severity = StatusMessageModel.StatusSeverity.Info,
Message = "Invitation accepted. Please set your password." Message = StringLocalizer["Invitation accepted. Please set your password."].Value
}); });
return await RedirectToSetPassword(user); return await RedirectToSetPassword(user);
} }
@ -857,7 +863,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Info, Severity = StatusMessageModel.StatusSeverity.Info,
Message = "Your password has been set by the user who invited you." Message = StringLocalizer["Your password has been set by the user who invited you."].Value
}); });
await FinalizeInvitationIfApplicable(user); await FinalizeInvitationIfApplicable(user);
@ -930,7 +936,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = "You cannot login over an insecure connection. Please use HTTPS or Tor." Message = StringLocalizer["You cannot login over an insecure connection. Please use HTTPS or Tor."].Value
}); });
ViewData["disabled"] = true; ViewData["disabled"] = true;

View File

@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
@ -30,6 +31,7 @@ namespace BTCPayServer.Controllers
StoreRepository storeRepository, StoreRepository storeRepository,
IFileService fileService, IFileService fileService,
AppService appService, AppService appService,
IStringLocalizer stringLocalizer,
IHtmlHelper html) IHtmlHelper html)
{ {
_userManager = userManager; _userManager = userManager;
@ -39,6 +41,7 @@ namespace BTCPayServer.Controllers
_fileService = fileService; _fileService = fileService;
_appService = appService; _appService = appService;
Html = html; Html = html;
StringLocalizer = stringLocalizer;
} }
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
@ -50,6 +53,7 @@ namespace BTCPayServer.Controllers
public string CreatedAppId { get; set; } public string CreatedAppId { get; set; }
public IHtmlHelper Html { get; } public IHtmlHelper Html { get; }
public IStringLocalizer StringLocalizer { get; }
public class AppUpdated public class AppUpdated
{ {
@ -158,7 +162,7 @@ namespace BTCPayServer.Controllers
var type = _appService.GetAppType(vm.AppType ?? vm.SelectedAppType); var type = _appService.GetAppType(vm.AppType ?? vm.SelectedAppType);
if (type is null) if (type is null)
{ {
ModelState.AddModelError(nameof(vm.SelectedAppType), "Invalid App Type"); ModelState.AddModelError(nameof(vm.SelectedAppType), StringLocalizer["Invalid App Type"]);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
@ -177,7 +181,7 @@ namespace BTCPayServer.Controllers
await _appService.SetDefaultSettings(appData, defaultCurrency); await _appService.SetDefaultSettings(appData, defaultCurrency);
await _appService.UpdateOrCreateApp(appData); await _appService.UpdateOrCreateApp(appData);
TempData[WellKnownTempData.SuccessMessage] = "App successfully created"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App successfully created"].Value;
CreatedAppId = appData.Id; CreatedAppId = appData.Id;
var url = await type.ConfigureLink(appData); var url = await type.ConfigureLink(appData);
@ -192,7 +196,7 @@ namespace BTCPayServer.Controllers
if (app == null) if (app == null)
return NotFound(); return NotFound();
return View("Confirm", new ConfirmModel("Delete app", $"The app <strong>{Html.Encode(app.Name)}</strong> and its settings will be permanently deleted. Are you sure?", "Delete")); return View("Confirm", new ConfirmModel(StringLocalizer["Delete app"], $"The app <strong>{Html.Encode(app.Name)}</strong> and its settings will be permanently deleted. Are you sure?", StringLocalizer["Delete"]));
} }
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
@ -204,7 +208,7 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
if (await _appService.DeleteApp(app)) if (await _appService.DeleteApp(app))
TempData[WellKnownTempData.SuccessMessage] = "App deleted successfully."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App deleted successfully."].Value;
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId = app.StoreDataId }); return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId = app.StoreDataId });
} }
@ -227,12 +231,14 @@ namespace BTCPayServer.Controllers
if (await _appService.SetArchived(app, archived)) if (await _appService.SetArchived(app, archived))
{ {
TempData[WellKnownTempData.SuccessMessage] = archived TempData[WellKnownTempData.SuccessMessage] = archived
? "The app has been archived and will no longer appear in the apps list by default." ? StringLocalizer["The app has been archived and will no longer appear in the apps list by default."].Value
: "The app has been unarchived and will appear in the apps list by default again."; : StringLocalizer["The app has been unarchived and will appear in the apps list by default again."].Value;
} }
else else
{ {
TempData[WellKnownTempData.ErrorMessage] = $"Failed to {(archived ? "archive" : "unarchive")} the app."; TempData[WellKnownTempData.ErrorMessage] = archived
? StringLocalizer["Failed to archive the app."].Value
: StringLocalizer["Failed to unarchive the app."].Value;
} }
var url = await type.ConfigureLink(app); var url = await type.ConfigureLink(app);

View File

@ -86,7 +86,7 @@ namespace BTCPayServer.Controllers
var newDeliveryId = await WebhookNotificationManager.Redeliver(deliveryId); var newDeliveryId = await WebhookNotificationManager.Redeliver(deliveryId);
if (newDeliveryId is null) if (newDeliveryId is null)
return NotFound(); return NotFound();
TempData[WellKnownTempData.SuccessMessage] = "Successfully planned a redelivery"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Successfully planned a redelivery"].Value;
return RedirectToAction(nameof(Invoice), return RedirectToAction(nameof(Invoice),
new new
{ {
@ -294,9 +294,9 @@ namespace BTCPayServer.Controllers
var payoutMethodIds = _payoutHandlers.GetSupportedPayoutMethods(this.GetCurrentStore()); var payoutMethodIds = _payoutHandlers.GetSupportedPayoutMethods(this.GetCurrentStore());
if (!payoutMethodIds.Any()) if (!payoutMethodIds.Any())
{ {
var vm = new RefundModel { Title = "No matching payment method" }; var vm = new RefundModel { Title = StringLocalizer["No matching payment method"] };
ModelState.AddModelError(nameof(vm.AvailablePaymentMethods), ModelState.AddModelError(nameof(vm.AvailablePaymentMethods),
"There are no payment methods available to provide refunds with for this invoice."); StringLocalizer["There are no payment methods available to provide refunds with for this invoice."]);
return View("_RefundModal", vm); return View("_RefundModal", vm);
} }
@ -306,7 +306,7 @@ namespace BTCPayServer.Controllers
var refund = new RefundModel var refund = new RefundModel
{ {
Title = "Payment method", Title = StringLocalizer["Payment method"],
AvailablePaymentMethods = AvailablePaymentMethods =
new SelectList(payoutMethodIds.Select(id => new SelectListItem(id.ToString(), id.ToString())), new SelectList(payoutMethodIds.Select(id => new SelectListItem(id.ToString(), id.ToString())),
"Value", "Text"), "Value", "Text"),
@ -344,7 +344,7 @@ namespace BTCPayServer.Controllers
var pmis = _payoutHandlers.GetSupportedPayoutMethods(store); var pmis = _payoutHandlers.GetSupportedPayoutMethods(store);
if (!pmis.Contains(pmi)) if (!pmis.Contains(pmi))
{ {
ModelState.AddModelError(nameof(model.SelectedPayoutMethod), $"Invalid payout method"); ModelState.AddModelError(nameof(model.SelectedPayoutMethod), StringLocalizer["Invalid payout method"]);
return View("_RefundModal", model); return View("_RefundModal", model);
} }
@ -353,7 +353,7 @@ namespace BTCPayServer.Controllers
var paymentMethod = paymentMethodId is null ? null : invoice.GetPaymentPrompt(paymentMethodId); var paymentMethod = paymentMethodId is null ? null : invoice.GetPaymentPrompt(paymentMethodId);
if (paymentMethod?.Currency is null) if (paymentMethod?.Currency is null)
{ {
ModelState.AddModelError(nameof(model.SelectedPayoutMethod), $"Invalid payout method"); ModelState.AddModelError(nameof(model.SelectedPayoutMethod), StringLocalizer["Invalid payout method"]);
return View("_RefundModal", model); return View("_RefundModal", model);
} }
@ -377,7 +377,7 @@ namespace BTCPayServer.Controllers
{ {
case RefundSteps.SelectPaymentMethod: case RefundSteps.SelectPaymentMethod:
model.RefundStep = RefundSteps.SelectRate; model.RefundStep = RefundSteps.SelectRate;
model.Title = "How much to refund?"; model.Title = StringLocalizer["How much to refund?"];
var paidCurrency = Math.Round(cryptoPaid * paymentMethod.Rate, cdCurrency.Divisibility); var paidCurrency = Math.Round(cryptoPaid * paymentMethod.Rate, cdCurrency.Divisibility);
model.CryptoAmountThen = cryptoPaid.RoundToSignificant(paymentMethod.Divisibility); model.CryptoAmountThen = cryptoPaid.RoundToSignificant(paymentMethod.Divisibility);
@ -390,7 +390,7 @@ namespace BTCPayServer.Controllers
if (rateResult.BidAsk is null) if (rateResult.BidAsk is null)
{ {
ModelState.AddModelError(nameof(model.SelectedRefundOption), ModelState.AddModelError(nameof(model.SelectedRefundOption),
$"Impossible to fetch rate: {rateResult.EvaluatedRule}"); StringLocalizer["Impossible to fetch rate: {0}", rateResult.EvaluatedRule]);
return View("_RefundModal", model); return View("_RefundModal", model);
} }
@ -413,7 +413,7 @@ namespace BTCPayServer.Controllers
case RefundSteps.SelectRate: case RefundSteps.SelectRate:
createPullPayment = new CreatePullPayment createPullPayment = new CreatePullPayment
{ {
Name = $"Refund {invoice.Id}", Name = StringLocalizer["Refund {0}", invoice.Id],
PayoutMethods = new[] { pmi }, PayoutMethods = new[] { pmi },
StoreId = invoice.StoreId, StoreId = invoice.StoreId,
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration
@ -423,7 +423,7 @@ namespace BTCPayServer.Controllers
.Succeeded; .Succeeded;
if (model.SubtractPercentage is < 0 or > 100) if (model.SubtractPercentage is < 0 or > 100)
{ {
ModelState.AddModelError(nameof(model.SubtractPercentage), "Percentage must be a numeric value between 0 and 100"); ModelState.AddModelError(nameof(model.SubtractPercentage), StringLocalizer["Percentage must be a numeric value between 0 and 100"]);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
@ -457,11 +457,11 @@ namespace BTCPayServer.Controllers
if (!isPaidOver) if (!isPaidOver)
{ {
ModelState.AddModelError(nameof(model.SelectedRefundOption), "Invoice is not overpaid"); ModelState.AddModelError(nameof(model.SelectedRefundOption), StringLocalizer["Invoice is not overpaid"]);
} }
if (overpaidAmount == null) if (overpaidAmount == null)
{ {
ModelState.AddModelError(nameof(model.SelectedRefundOption), "Overpaid amount cannot be calculated"); ModelState.AddModelError(nameof(model.SelectedRefundOption), StringLocalizer["Overpaid amount cannot be calculated"]);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
@ -474,17 +474,17 @@ namespace BTCPayServer.Controllers
break; break;
case "Custom": case "Custom":
model.Title = "How much to refund?"; model.Title = StringLocalizer["How much to refund?"];
model.RefundStep = RefundSteps.SelectRate; model.RefundStep = RefundSteps.SelectRate;
if (model.CustomAmount <= 0) if (model.CustomAmount <= 0)
{ {
model.AddModelError(refundModel => refundModel.CustomAmount, "Amount must be greater than 0", this); model.AddModelError(refundModel => refundModel.CustomAmount, StringLocalizer["Amount must be greater than 0"], this);
} }
if (string.IsNullOrEmpty(model.CustomCurrency) || if (string.IsNullOrEmpty(model.CustomCurrency) ||
_CurrencyNameTable.GetCurrencyData(model.CustomCurrency, false) == null) _CurrencyNameTable.GetCurrencyData(model.CustomCurrency, false) == null)
{ {
ModelState.AddModelError(nameof(model.CustomCurrency), "Invalid currency"); ModelState.AddModelError(nameof(model.CustomCurrency), StringLocalizer["Invalid currency"]);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
@ -500,7 +500,7 @@ namespace BTCPayServer.Controllers
if (rateResult.BidAsk is null) if (rateResult.BidAsk is null)
{ {
ModelState.AddModelError(nameof(model.SelectedRefundOption), ModelState.AddModelError(nameof(model.SelectedRefundOption),
$"Impossible to fetch rate: {rateResult.EvaluatedRule}"); StringLocalizer["Impossible to fetch rate: {0}", rateResult.EvaluatedRule]);
return View("_RefundModal", model); return View("_RefundModal", model);
} }
@ -510,7 +510,7 @@ namespace BTCPayServer.Controllers
break; break;
default: default:
ModelState.AddModelError(nameof(model.SelectedRefundOption), "Please select an option before proceeding"); ModelState.AddModelError(nameof(model.SelectedRefundOption), StringLocalizer["Please select an option before proceeding"]);
return View("_RefundModal", model); return View("_RefundModal", model);
} }
break; break;
@ -608,10 +608,12 @@ namespace BTCPayServer.Controllers
if (invoice == null) if (invoice == null)
return NotFound(); return NotFound();
await _InvoiceRepository.ToggleInvoiceArchival(invoiceId, !invoice.Archived); await _InvoiceRepository.ToggleInvoiceArchival(invoiceId, !invoice.Archived);
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = invoice.Archived ? "The invoice has been unarchived and will appear in the invoice list by default again." : "The invoice has been archived and will no longer appear in the invoice list by default." Message = invoice.Archived
? StringLocalizer["The invoice has been unarchived and will appear in the invoice list by default again."].Value
: StringLocalizer["The invoice has been archived and will no longer appear in the invoice list by default."].Value
}); });
return RedirectToAction(nameof(invoice), new { invoiceId }); return RedirectToAction(nameof(invoice), new { invoiceId });
} }
@ -626,28 +628,32 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(ListInvoices), new { storeId }); return RedirectToAction(nameof(ListInvoices), new { storeId });
} }
if (selectedItems.Length == 0) if (selectedItems.Length == 0)
return NotSupported("No invoice has been selected"); return NotSupported(StringLocalizer["No invoice has been selected"]);
switch (command) switch (command)
{ {
case "archive": case "archive":
await _InvoiceRepository.MassArchive(selectedItems); await _InvoiceRepository.MassArchive(selectedItems);
TempData[WellKnownTempData.SuccessMessage] = $"{selectedItems.Length} invoice{(selectedItems.Length == 1 ? "" : "s")} archived."; TempData[WellKnownTempData.SuccessMessage] = selectedItems.Length == 1
? StringLocalizer["{0} invoice archived.", selectedItems.Length].Value
: StringLocalizer["{0} invoices archived.", selectedItems.Length].Value;
break; break;
case "unarchive": case "unarchive":
await _InvoiceRepository.MassArchive(selectedItems, false); await _InvoiceRepository.MassArchive(selectedItems, false);
TempData[WellKnownTempData.SuccessMessage] = $"{selectedItems.Length} invoice{(selectedItems.Length == 1 ? "" : "s")} unarchived."; TempData[WellKnownTempData.SuccessMessage] = selectedItems.Length == 1
? StringLocalizer["{0} invoice unarchived.", selectedItems.Length].Value
: StringLocalizer["{0} invoices unarchived.", selectedItems.Length].Value;
break; break;
case "cpfp" when storeId is not null: case "cpfp" when storeId is not null:
var network = _NetworkProvider.DefaultNetwork; var network = _NetworkProvider.DefaultNetwork;
var explorer = _ExplorerClients.GetExplorerClient(network); var explorer = _ExplorerClients.GetExplorerClient(network);
if (explorer is null) if (explorer is null)
return NotSupported("This feature is only available to BTC wallets"); return NotSupported(StringLocalizer["This feature is only available to BTC wallets"]);
if (!GetCurrentStore().HasPermission(GetUserId(), Policies.CanModifyStoreSettings)) if (!GetCurrentStore().HasPermission(GetUserId(), Policies.CanModifyStoreSettings))
return Forbid(); return Forbid();
var derivationScheme = (this.GetCurrentStore().GetDerivationSchemeSettings(_handlers, network.CryptoCode))?.AccountDerivation; var derivationScheme = GetCurrentStore().GetDerivationSchemeSettings(_handlers, network.CryptoCode)?.AccountDerivation;
if (derivationScheme is null) if (derivationScheme is null)
return NotSupported("This feature is only available to BTC wallets"); return NotSupported("This feature is only available to BTC wallets");
var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC"); var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
@ -657,7 +663,7 @@ namespace BTCPayServer.Controllers
var parameters = new MultiValueDictionary<string, string>(); var parameters = new MultiValueDictionary<string, string>();
foreach (var utxo in bumpableUTXOs) foreach (var utxo in bumpableUTXOs)
{ {
parameters.Add($"outpoints[]", utxo.Outpoint.ToString()); parameters.Add("outpoints[]", utxo.Outpoint.ToString());
} }
return View("PostRedirect", new PostRedirectViewModel return View("PostRedirect", new PostRedirectViewModel
{ {
@ -1154,7 +1160,7 @@ namespace BTCPayServer.Controllers
{ {
if (string.IsNullOrEmpty(model?.StoreId)) if (string.IsNullOrEmpty(model?.StoreId))
{ {
TempData[WellKnownTempData.ErrorMessage] = "You need to select a store before creating an invoice."; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["You need to select a store before creating an invoice."].Value;
return RedirectToAction(nameof(UIHomeController.Index), "UIHome"); return RedirectToAction(nameof(UIHomeController.Index), "UIHome");
} }
@ -1202,7 +1208,7 @@ namespace BTCPayServer.Controllers
} }
catch (Exception) catch (Exception)
{ {
ModelState.AddModelError(nameof(model.Metadata), "Metadata was not valid JSON"); ModelState.AddModelError(nameof(model.Metadata), StringLocalizer["Metadata was not valid JSON"]);
} }
} }
@ -1228,7 +1234,7 @@ namespace BTCPayServer.Controllers
metadata.BuyerEmail = model.BuyerEmail; metadata.BuyerEmail = model.BuyerEmail;
} }
var result = await CreateInvoiceCoreRaw(new CreateInvoiceRequest() var result = await CreateInvoiceCoreRaw(new CreateInvoiceRequest
{ {
Amount = model.Amount, Amount = model.Amount,
Currency = model.Currency, Currency = model.Currency,
@ -1249,14 +1255,14 @@ namespace BTCPayServer.Controllers
}, },
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
TempData[WellKnownTempData.SuccessMessage] = $"Invoice {result.Id} just created!"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Invoice {0} just created!", result.Id].Value;
CreatedInvoiceId = result.Id; CreatedInvoiceId = result.Id;
return RedirectToAction(nameof(Invoice), new { storeId = result.StoreId, invoiceId = result.Id }); return RedirectToAction(nameof(Invoice), new { storeId = result.StoreId, invoiceId = result.Id });
} }
catch (BitpayHttpException ex) catch (BitpayHttpException ex)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = ex.Message Message = ex.Message

View File

@ -35,6 +35,7 @@ using StoreData = BTCPayServer.Data.StoreData;
using Serilog.Filters; using Serilog.Filters;
using PeterO.Numbers; using PeterO.Numbers;
using BTCPayServer.Payouts; using BTCPayServer.Payouts;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
@ -69,6 +70,7 @@ namespace BTCPayServer.Controllers
private readonly UriResolver _uriResolver; private readonly UriResolver _uriResolver;
public WebhookSender WebhookNotificationManager { get; } public WebhookSender WebhookNotificationManager { get; }
public IStringLocalizer StringLocalizer { get; }
public UIInvoiceController( public UIInvoiceController(
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
@ -98,6 +100,7 @@ namespace BTCPayServer.Controllers
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
TransactionLinkProviders transactionLinkProviders, TransactionLinkProviders transactionLinkProviders,
Dictionary<PaymentMethodId, ICheckoutModelExtension> paymentModelExtensions, Dictionary<PaymentMethodId, ICheckoutModelExtension> paymentModelExtensions,
IStringLocalizer stringLocalizer,
PrettyNameProvider prettyName) PrettyNameProvider prettyName)
{ {
_displayFormatter = displayFormatter; _displayFormatter = displayFormatter;
@ -127,6 +130,7 @@ namespace BTCPayServer.Controllers
_uriResolver = uriResolver; _uriResolver = uriResolver;
_defaultRules = defaultRules; _defaultRules = defaultRules;
_appService = appService; _appService = appService;
StringLocalizer = stringLocalizer;
} }
internal async Task<InvoiceEntity> CreatePaymentRequestInvoice(Data.PaymentRequestData prData, decimal? amount, decimal amountDue, StoreData storeData, HttpRequest request, CancellationToken cancellationToken) internal async Task<InvoiceEntity> CreatePaymentRequestInvoice(Data.PaymentRequestData prData, decimal? amount, decimal amountDue, StoreData storeData, HttpRequest request, CancellationToken cancellationToken)

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Localization;
using NBitcoin; using NBitcoin;
using NBitcoin.Crypto; using NBitcoin.Crypto;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
@ -23,22 +24,24 @@ namespace BTCPayServer
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly LnurlAuthService _lnurlAuthService; private readonly LnurlAuthService _lnurlAuthService;
private readonly LinkGenerator _linkGenerator; private readonly LinkGenerator _linkGenerator;
public IStringLocalizer StringLocalizer { get; }
public UILNURLAuthController(UserManager<ApplicationUser> userManager, LnurlAuthService lnurlAuthService, public UILNURLAuthController(UserManager<ApplicationUser> userManager, LnurlAuthService lnurlAuthService,
LinkGenerator linkGenerator) IStringLocalizer stringLocalizer, LinkGenerator linkGenerator)
{ {
_userManager = userManager; _userManager = userManager;
_lnurlAuthService = lnurlAuthService; _lnurlAuthService = lnurlAuthService;
_linkGenerator = linkGenerator; _linkGenerator = linkGenerator;
StringLocalizer = stringLocalizer;
} }
[HttpGet("{id}/delete")] [HttpGet("{id}/delete")]
public IActionResult Remove(string id) public IActionResult Remove(string id)
{ {
return View("Confirm", return View("Confirm",
new ConfirmModel("Remove LNURL Auth link", new ConfirmModel(StringLocalizer["Remove LNURL Auth link"],
"Your account will no longer have this Lightning wallet as an option for two-factor authentication.", StringLocalizer["Your account will no longer have this Lightning wallet as an option for two-factor authentication."],
"Remove")); StringLocalizer["Remove"]));
} }
[HttpPost("{id}/delete")] [HttpPost("{id}/delete")]
@ -49,7 +52,7 @@ namespace BTCPayServer
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Html = "LNURL Auth was removed successfully." Message = StringLocalizer["LNURL Auth was removed successfully."].Value
}); });
return RedirectToList(); return RedirectToList();
@ -65,7 +68,7 @@ namespace BTCPayServer
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Html = "The Lightning node could not be registered." Html = StringLocalizer["The Lightning node could not be registered."].Value
}); });
return RedirectToList(); return RedirectToList();

View File

@ -36,12 +36,11 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Localization;
using NBitcoin; using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using LightningAddressData = BTCPayServer.Data.LightningAddressData; using LightningAddressData = BTCPayServer.Data.LightningAddressData;
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
namespace BTCPayServer namespace BTCPayServer
{ {
@ -63,6 +62,7 @@ namespace BTCPayServer
private readonly InvoiceActivator _invoiceActivator; private readonly InvoiceActivator _invoiceActivator;
private readonly PaymentMethodHandlerDictionary _handlers; private readonly PaymentMethodHandlerDictionary _handlers;
private readonly PayoutProcessorService _payoutProcessorService; private readonly PayoutProcessorService _payoutProcessorService;
public IStringLocalizer StringLocalizer { get; }
public UILNURLController(InvoiceRepository invoiceRepository, public UILNURLController(InvoiceRepository invoiceRepository,
EventAggregator eventAggregator, EventAggregator eventAggregator,
@ -77,6 +77,7 @@ namespace BTCPayServer
PullPaymentHostedService pullPaymentHostedService, PullPaymentHostedService pullPaymentHostedService,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings, BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
IPluginHookService pluginHookService, IPluginHookService pluginHookService,
IStringLocalizer stringLocalizer,
InvoiceActivator invoiceActivator) InvoiceActivator invoiceActivator)
{ {
_invoiceRepository = invoiceRepository; _invoiceRepository = invoiceRepository;
@ -93,6 +94,7 @@ namespace BTCPayServer
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings; _btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_pluginHookService = pluginHookService; _pluginHookService = pluginHookService;
_invoiceActivator = invoiceActivator; _invoiceActivator = invoiceActivator;
StringLocalizer = stringLocalizer;
} }
[EnableCors(CorsPolicies.All)] [EnableCors(CorsPolicies.All)]
@ -302,7 +304,7 @@ namespace BTCPayServer
{ {
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out _); var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out _);
if (pmi is null) if (pmi is null)
return NotFound("LNUrl or LN is disabled"); return NotFound(StringLocalizer["LNURL or LN is disabled"]);
var escapedItemId = Extensions.UnescapeBackSlashUriString(itemCode); var escapedItemId = Extensions.UnescapeBackSlashUriString(itemCode);
item = items.FirstOrDefault(item1 => item = items.FirstOrDefault(item1 =>
item1.Id.Equals(itemCode, StringComparison.InvariantCultureIgnoreCase) || item1.Id.Equals(itemCode, StringComparison.InvariantCultureIgnoreCase) ||
@ -457,11 +459,11 @@ namespace BTCPayServer
{ {
var lightningAddressSettings = await _lightningAddressService.ResolveByAddress(username); var lightningAddressSettings = await _lightningAddressService.ResolveByAddress(username);
if (lightningAddressSettings is null || username is null) if (lightningAddressSettings is null || username is null)
return NotFound("Unknown username"); return NotFound(StringLocalizer["Unknown username"]);
var blob = lightningAddressSettings.GetBlob(); var blob = lightningAddressSettings.GetBlob();
var store = await _storeRepository.FindStore(lightningAddressSettings.StoreDataId); var store = await _storeRepository.FindStore(lightningAddressSettings.StoreDataId);
if (store is null) if (store is null)
return NotFound("Unknown username"); return NotFound(StringLocalizer["Unknown username"]);
var result = await GetLNURLRequest( var result = await GetLNURLRequest(
cryptoCode, cryptoCode,
store, store,
@ -503,7 +505,7 @@ namespace BTCPayServer
var blob = store.GetStoreBlob(); var blob = store.GetStoreBlob();
if (!blob.AnyoneCanInvoice) if (!blob.AnyoneCanInvoice)
return NotFound("'Anyone can invoice' is turned off"); return NotFound(StringLocalizer["'Anyone can invoice' is turned off"]);
var metadata = new InvoiceMetadata(); var metadata = new InvoiceMetadata();
if (!string.IsNullOrEmpty(orderId)) if (!string.IsNullOrEmpty(orderId))
{ {
@ -533,7 +535,7 @@ namespace BTCPayServer
{ {
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out _); var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out _);
if (pmi is null) if (pmi is null)
return NotFound("LNUrl or LN is disabled"); return NotFound(StringLocalizer["LNURL or LN is disabled"]);
InvoiceEntity i; InvoiceEntity i;
try try
@ -721,7 +723,7 @@ namespace BTCPayServer
new LNURLPayRequest.LNURLPayRequestCallbackResponse.LNURLPayRequestSuccessActionUrl new LNURLPayRequest.LNURLPayRequestCallbackResponse.LNURLPayRequestSuccessActionUrl
{ {
Tag = "url", Tag = "url",
Description = "Thank you for your purchase. Here is your receipt", Description = StringLocalizer["Thank you for your purchase. Here is your receipt"],
Url = _linkGenerator.GetUriByAction( Url = _linkGenerator.GetUriByAction(
nameof(UIInvoiceController.InvoiceReceipt), nameof(UIInvoiceController.InvoiceReceipt),
"UIInvoice", "UIInvoice",
@ -833,7 +835,7 @@ namespace BTCPayServer
{ {
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "LNURL is required for lightning addresses but has not yet been enabled.", Message = StringLocalizer["LNURL is required for lightning addresses but has not yet been enabled."].Value,
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId }); return RedirectToAction(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId });
@ -873,7 +875,7 @@ namespace BTCPayServer
if (!string.IsNullOrEmpty(vm.Add.CurrencyCode) && if (!string.IsNullOrEmpty(vm.Add.CurrencyCode) &&
currencyNameTable.GetCurrencyData(vm.Add.CurrencyCode, false) is null) currencyNameTable.GetCurrencyData(vm.Add.CurrencyCode, false) is null)
{ {
vm.AddModelError(addressVm => addressVm.Add.CurrencyCode, "Currency is invalid", this); vm.AddModelError(addressVm => addressVm.Add.CurrencyCode, StringLocalizer["Currency is invalid"], this);
} }
JObject metadata = null; JObject metadata = null;
@ -885,7 +887,7 @@ namespace BTCPayServer
} }
catch (Exception) catch (Exception)
{ {
vm.AddModelError(addressVm => addressVm.Add.InvoiceMetadata, "Metadata must be a valid json object", this); vm.AddModelError(addressVm => addressVm.Add.InvoiceMetadata, StringLocalizer["Metadata must be a valid JSON object"], this);
} }
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
@ -909,12 +911,12 @@ namespace BTCPayServer
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Lightning address added successfully." Message = StringLocalizer["Lightning address added successfully."].Value
}); });
} }
else else
{ {
vm.AddModelError(addressVm => addressVm.Add.Username, "Username is already taken", this); vm.AddModelError(addressVm => addressVm.Add.Username, StringLocalizer["Username is already taken"], this);
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
@ -932,13 +934,13 @@ namespace BTCPayServer
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = $"Lightning address {index} removed successfully." Message = StringLocalizer["Lightning address {0} removed successfully.", index].Value
}); });
return RedirectToAction("EditLightningAddress"); return RedirectToAction("EditLightningAddress");
} }
else else
{ {
vm.AddModelError(addressVm => addressVm.Add.Username, "Username could not be removed", this); vm.AddModelError(addressVm => addressVm.Add.Username, StringLocalizer["Username could not be removed"], this);
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {

View File

@ -58,10 +58,10 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
} }
await _apiKeyRepository.Remove(id, _userManager.GetUserId(User)); await _apiKeyRepository.Remove(id, _userManager.GetUserId(User));
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = "API Key removed" Message = StringLocalizer["API Key removed"].Value
}); });
return RedirectToAction("APIKeys"); return RedirectToAction("APIKeys");
} }
@ -71,10 +71,10 @@ namespace BTCPayServer.Controllers
{ {
if (!_btcPayServerEnvironment.IsSecure(HttpContext)) if (!_btcPayServerEnvironment.IsSecure(HttpContext))
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Cannot generate api keys while not on https or tor" Message = StringLocalizer["Cannot generate API keys while not using HTTPS or Tor"].Value
}); });
return RedirectToAction("APIKeys"); return RedirectToAction("APIKeys");
} }
@ -91,7 +91,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Cannot generate API keys while not on https or using Tor" Message = StringLocalizer["Cannot generate API keys while not using HTTPS or Tor"].Value
}); });
return RedirectToAction("APIKeys"); return RedirectToAction("APIKeys");
} }
@ -199,7 +199,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Html = $"API key generated! <code class='alert-link'>{key.Id}</code>" Html = StringLocalizer["API key generated!"].Value + $" <code class='alert-link'>{key.Id}</code>"
}); });
return RedirectToAction("APIKeys", new { key = key.Id }); return RedirectToAction("APIKeys", new { key = key.Id });
@ -242,7 +242,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Html = $"API key generated! <code class='alert-link'>{key.Id}</code>" Html = StringLocalizer["API key generated!"].Value + $" <code class='alert-link'>{key.Id}</code>"
}); });
return RedirectToAction("APIKeys"); return RedirectToAction("APIKeys");
} }

View File

@ -5,7 +5,6 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Models;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
@ -56,7 +55,7 @@ namespace BTCPayServer.Controllers
await _userManager.UpdateAsync(user); await _userManager.UpdateAsync(user);
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "Updated successfully.", Message = StringLocalizer["Updated successfully."].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
return RedirectToAction("NotificationSettings"); return RedirectToAction("NotificationSettings");

View File

@ -7,25 +7,21 @@ using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Fido2; using BTCPayServer.Fido2;
using BTCPayServer.Models;
using BTCPayServer.Models.ManageViewModels; using BTCPayServer.Models.ManageViewModels;
using BTCPayServer.Security.Greenfield; using BTCPayServer.Security.Greenfield;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Mails; using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MimeKit;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewProfile)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewProfile)]
[Route("account/{action:lowercase=Index}")] [Route("account/{action:lowercase=Index}")]
public partial class UIManageController : Controller public partial class UIManageController : Controller
@ -45,6 +41,7 @@ namespace BTCPayServer.Controllers
private readonly UriResolver _uriResolver; private readonly UriResolver _uriResolver;
private readonly IFileService _fileService; private readonly IFileService _fileService;
readonly StoreRepository _StoreRepository; readonly StoreRepository _StoreRepository;
public IStringLocalizer StringLocalizer { get; }
public UIManageController( public UIManageController(
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
@ -61,6 +58,7 @@ namespace BTCPayServer.Controllers
UserService userService, UserService userService,
UriResolver uriResolver, UriResolver uriResolver,
IFileService fileService, IFileService fileService,
IStringLocalizer stringLocalizer,
IHtmlHelper htmlHelper IHtmlHelper htmlHelper
) )
{ {
@ -79,6 +77,7 @@ namespace BTCPayServer.Controllers
_uriResolver = uriResolver; _uriResolver = uriResolver;
_fileService = fileService; _fileService = fileService;
_StoreRepository = storeRepository; _StoreRepository = storeRepository;
StringLocalizer = stringLocalizer;
} }
[HttpGet] [HttpGet]
@ -135,7 +134,7 @@ namespace BTCPayServer.Controllers
{ {
if (!(await _userManager.FindByEmailAsync(model.Email) is null)) if (!(await _userManager.FindByEmailAsync(model.Email) is null))
{ {
TempData[WellKnownTempData.ErrorMessage] = "The email address is already in use with an other account."; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["The email address is already in use with an other account."].Value;
return RedirectToAction(nameof(Index)); return RedirectToAction(nameof(Index));
} }
var setUserResult = await _userManager.SetUserNameAsync(user, model.Email); var setUserResult = await _userManager.SetUserNameAsync(user, model.Email);
@ -207,11 +206,11 @@ namespace BTCPayServer.Controllers
if (needUpdate is true) if (needUpdate is true)
{ {
needUpdate = await _userManager.UpdateAsync(user) is { Succeeded: true }; needUpdate = await _userManager.UpdateAsync(user) is { Succeeded: true };
TempData[WellKnownTempData.SuccessMessage] = "Your profile has been updated"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Your profile has been updated"].Value;
} }
else else
{ {
TempData[WellKnownTempData.ErrorMessage] = "Error updating profile"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Error updating profile"].Value;
} }
return RedirectToAction(nameof(Index)); return RedirectToAction(nameof(Index));
@ -235,7 +234,7 @@ namespace BTCPayServer.Controllers
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = _linkGenerator.EmailConfirmationLink(user.Id, code, Request.Scheme, Request.Host, Request.PathBase); var callbackUrl = _linkGenerator.EmailConfirmationLink(user.Id, code, Request.Scheme, Request.Host, Request.PathBase);
(await _EmailSenderFactory.GetEmailSender()).SendEmailConfirmation(user.GetMailboxAddress(), callbackUrl); (await _EmailSenderFactory.GetEmailSender()).SendEmailConfirmation(user.GetMailboxAddress(), callbackUrl);
TempData[WellKnownTempData.SuccessMessage] = "Verification email sent. Please check your email."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Verification email sent. Please check your email."].Value;
return RedirectToAction(nameof(Index)); return RedirectToAction(nameof(Index));
} }
@ -281,8 +280,8 @@ namespace BTCPayServer.Controllers
} }
await _signInManager.SignInAsync(user, isPersistent: false); await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User changed their password successfully."); _logger.LogInformation("User changed their password successfully");
TempData[WellKnownTempData.SuccessMessage] = "Your password has been changed."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Your password has been changed."].Value;
return RedirectToAction(nameof(ChangePassword)); return RedirectToAction(nameof(ChangePassword));
} }
@ -330,7 +329,7 @@ namespace BTCPayServer.Controllers
} }
await _signInManager.SignInAsync(user, isPersistent: false); await _signInManager.SignInAsync(user, isPersistent: false);
TempData[WellKnownTempData.SuccessMessage] = "Your password has been set."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Your password has been set."].Value;
return RedirectToAction(nameof(SetPassword)); return RedirectToAction(nameof(SetPassword));
} }
@ -345,7 +344,7 @@ namespace BTCPayServer.Controllers
} }
await _userService.DeleteUserAndAssociatedData(user); await _userService.DeleteUserAndAssociatedData(user);
TempData[WellKnownTempData.SuccessMessage] = "Account successfully deleted."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Account successfully deleted."].Value;
await _signInManager.SignOutAsync(); await _signInManager.SignOutAsync();
return RedirectToAction(nameof(UIAccountController.Login), "UIAccount"); return RedirectToAction(nameof(UIAccountController.Login), "UIAccount");
} }

View File

@ -23,6 +23,7 @@ using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData; using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
using StoreData = BTCPayServer.Data.StoreData; using StoreData = BTCPayServer.Data.StoreData;
@ -47,6 +48,7 @@ namespace BTCPayServer.Controllers
private FormComponentProviders FormProviders { get; } private FormComponentProviders FormProviders { get; }
public FormDataService FormDataService { get; } public FormDataService FormDataService { get; }
public IStringLocalizer StringLocalizer { get; }
public UIPaymentRequestController( public UIPaymentRequestController(
UIInvoiceController invoiceController, UIInvoiceController invoiceController,
@ -62,6 +64,7 @@ namespace BTCPayServer.Controllers
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
FormComponentProviders formProviders, FormComponentProviders formProviders,
FormDataService formDataService, FormDataService formDataService,
IStringLocalizer stringLocalizer,
BTCPayNetworkProvider networkProvider) BTCPayNetworkProvider networkProvider)
{ {
_InvoiceController = invoiceController; _InvoiceController = invoiceController;
@ -78,6 +81,7 @@ namespace BTCPayServer.Controllers
FormProviders = formProviders; FormProviders = formProviders;
FormDataService = formDataService; FormDataService = formDataService;
_networkProvider = networkProvider; _networkProvider = networkProvider;
StringLocalizer = stringLocalizer;
} }
[HttpGet("/stores/{storeId}/payment-requests")] [HttpGet("/stores/{storeId}/payment-requests")]
@ -169,7 +173,7 @@ namespace BTCPayServer.Controllers
if (paymentRequest?.Archived is true && viewModel.Archived) if (paymentRequest?.Archived is true && viewModel.Archived)
{ {
ModelState.AddModelError(string.Empty, "You cannot edit an archived payment request."); ModelState.AddModelError(string.Empty, StringLocalizer["You cannot edit an archived payment request."]);
} }
var data = paymentRequest ?? new PaymentRequestData(); var data = paymentRequest ?? new PaymentRequestData();
data.StoreDataId = viewModel.StoreId; data.StoreDataId = viewModel.StoreId;
@ -180,7 +184,7 @@ namespace BTCPayServer.Controllers
{ {
var prInvoices = (await _PaymentRequestService.GetPaymentRequest(payReqId, GetUserId())).Invoices; var prInvoices = (await _PaymentRequestService.GetPaymentRequest(payReqId, GetUserId())).Invoices;
if (prInvoices.Any()) if (prInvoices.Any())
ModelState.AddModelError(nameof(viewModel.Amount), "Amount and currency are not editable once payment request has invoices"); ModelState.AddModelError(nameof(viewModel.Amount), StringLocalizer["Amount and currency are not editable once payment request has invoices"]);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
@ -210,7 +214,9 @@ namespace BTCPayServer.Controllers
data = await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(data); data = await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(data);
_EventAggregator.Publish(new PaymentRequestUpdated { Data = data, PaymentRequestId = data.Id, }); _EventAggregator.Publish(new PaymentRequestUpdated { Data = data, PaymentRequestId = data.Id, });
TempData[WellKnownTempData.SuccessMessage] = $"Payment request \"{viewModel.Title}\" {(isNewPaymentRequest ? "created" : "updated")} successfully"; TempData[WellKnownTempData.SuccessMessage] = isNewPaymentRequest
? StringLocalizer["Payment request \"{0}\" created successfully", viewModel.Title].Value
: StringLocalizer["Payment request \"{0}\" updated successfully", viewModel.Title].Value;
return RedirectToAction(nameof(GetPaymentRequests), new { storeId = store.Id, payReqId = data.Id }); return RedirectToAction(nameof(GetPaymentRequests), new { storeId = store.Id, payReqId = data.Id });
} }
@ -302,7 +308,7 @@ namespace BTCPayServer.Controllers
{ {
if (amount.HasValue && amount.Value <= 0) if (amount.HasValue && amount.Value <= 0)
{ {
return BadRequest("Please provide an amount greater than 0"); return BadRequest(StringLocalizer["Please provide an amount greater than 0"]);
} }
var result = await _PaymentRequestService.GetPaymentRequest(payReqId, GetUserId()); var result = await _PaymentRequestService.GetPaymentRequest(payReqId, GetUserId());
@ -318,7 +324,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction("ViewPaymentRequest", new { payReqId }); return RedirectToAction("ViewPaymentRequest", new { payReqId });
} }
return BadRequest("Payment Request cannot be paid as it has been archived"); return BadRequest(StringLocalizer["Payment Request cannot be paid as it has been archived"]);
} }
if (!result.FormSubmitted && !string.IsNullOrEmpty(result.FormId)) if (!result.FormSubmitted && !string.IsNullOrEmpty(result.FormId))
{ {
@ -337,7 +343,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction("ViewPaymentRequest", new { payReqId }); return RedirectToAction("ViewPaymentRequest", new { payReqId });
} }
return BadRequest("Payment Request has already been settled."); return BadRequest(StringLocalizer["Payment Request has already been settled."]);
} }
if (result.ExpiryDate.HasValue && DateTime.UtcNow >= result.ExpiryDate) if (result.ExpiryDate.HasValue && DateTime.UtcNow >= result.ExpiryDate)
@ -347,7 +353,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction("ViewPaymentRequest", new { payReqId }); return RedirectToAction("ViewPaymentRequest", new { payReqId });
} }
return BadRequest("Payment Request has expired"); return BadRequest(StringLocalizer["Payment Request has expired"]);
} }
var currentInvoice = result.Invoices.GetReusableInvoice(amount); var currentInvoice = result.Invoices.GetReusableInvoice(amount);
@ -391,7 +397,7 @@ namespace BTCPayServer.Controllers
if (!result.AllowCustomPaymentAmounts) if (!result.AllowCustomPaymentAmounts)
{ {
return BadRequest("Not allowed to cancel this invoice"); return BadRequest(StringLocalizer["Not allowed to cancel this invoice"]);
} }
var invoices = result.Invoices.Where(requestInvoice => var invoices = result.Invoices.Where(requestInvoice =>
@ -399,7 +405,7 @@ namespace BTCPayServer.Controllers
if (!invoices.Any()) if (!invoices.Any())
{ {
return BadRequest("No unpaid pending invoice to cancel"); return BadRequest(StringLocalizer["No unpaid pending invoice to cancel"]);
} }
foreach (var invoice in invoices) foreach (var invoice in invoices)
@ -409,11 +415,11 @@ namespace BTCPayServer.Controllers
if (redirect) if (redirect)
{ {
TempData[WellKnownTempData.SuccessMessage] = "Payment cancelled"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Payment cancelled"].Value;
return RedirectToAction(nameof(ViewPaymentRequest), new { payReqId }); return RedirectToAction(nameof(ViewPaymentRequest), new { payReqId });
} }
return Ok("Payment cancelled"); return Ok(StringLocalizer["Payment cancelled"]);
} }
[HttpGet("{payReqId}/clone")] [HttpGet("{payReqId}/clone")]
@ -446,8 +452,8 @@ namespace BTCPayServer.Controllers
if(result is not null) if(result is not null)
{ {
TempData[WellKnownTempData.SuccessMessage] = result.Value TempData[WellKnownTempData.SuccessMessage] = result.Value
? "The payment request has been archived and will no longer appear in the payment request list by default again." ? StringLocalizer["The payment request has been archived and will no longer appear in the payment request list by default again."].Value
: "The payment request has been unarchived and will appear in the payment request list by default."; : StringLocalizer["The payment request has been unarchived and will appear in the payment request list by default."].Value;
return RedirectToAction("GetPaymentRequests", new { storeId = store.Id }); return RedirectToAction("GetPaymentRequests", new { storeId = store.Id });
} }

View File

@ -13,6 +13,7 @@ using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Localization;
using NicolasDorier.RateLimits; using NicolasDorier.RateLimits;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
@ -21,16 +22,19 @@ namespace BTCPayServer.Controllers
{ {
public UIPublicController(UIInvoiceController invoiceController, public UIPublicController(UIInvoiceController invoiceController,
StoreRepository storeRepository, StoreRepository storeRepository,
IStringLocalizer stringLocalizer,
LinkGenerator linkGenerator) LinkGenerator linkGenerator)
{ {
_InvoiceController = invoiceController; _InvoiceController = invoiceController;
_StoreRepository = storeRepository; _StoreRepository = storeRepository;
_linkGenerator = linkGenerator; _linkGenerator = linkGenerator;
StringLocalizer = stringLocalizer;
} }
private readonly UIInvoiceController _InvoiceController; private readonly UIInvoiceController _InvoiceController;
private readonly StoreRepository _StoreRepository; private readonly StoreRepository _StoreRepository;
private readonly LinkGenerator _linkGenerator; private readonly LinkGenerator _linkGenerator;
public IStringLocalizer StringLocalizer { get; }
[HttpGet] [HttpGet]
[IgnoreAntiforgeryToken] [IgnoreAntiforgeryToken]
@ -50,16 +54,16 @@ namespace BTCPayServer.Controllers
{ {
var store = await _StoreRepository.FindStore(model.StoreId); var store = await _StoreRepository.FindStore(model.StoreId);
if (store == null) if (store == null)
ModelState.AddModelError("Store", "Invalid store"); ModelState.AddModelError("Store", StringLocalizer["Invalid store"]);
else else
{ {
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
if (!storeBlob.AnyoneCanInvoice) if (!storeBlob.AnyoneCanInvoice)
ModelState.AddModelError("Store", "Store has not enabled Pay Button"); ModelState.AddModelError("Store", StringLocalizer["Store has not enabled Pay Button"]);
} }
if (model == null || (model.Price is decimal v ? v <= 0 : false)) if (model == null || (model.Price is decimal v ? v <= 0 : false))
ModelState.AddModelError("Price", "Price must be greater than 0"); ModelState.AddModelError("Price", StringLocalizer["Price must be greater than 0"]);
if (!ModelState.IsValid) if (!ModelState.IsValid)
return View(); return View();

View File

@ -1,14 +1,10 @@
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Data;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.NTag424; using BTCPayServer.NTag424;
using Dapper;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NBitcoin.DataEncoders;
using System; using System;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading; using System.Threading;
@ -23,7 +19,7 @@ namespace BTCPayServer.Controllers
[HttpGet("pull-payments/{pullPaymentId}/boltcard/{command}")] [HttpGet("pull-payments/{pullPaymentId}/boltcard/{command}")]
public IActionResult SetupBoltcard(string pullPaymentId, string command) public IActionResult SetupBoltcard(string pullPaymentId, string command)
{ {
return View(nameof(SetupBoltcard), new SetupBoltcardViewModel() return View(nameof(SetupBoltcard), new SetupBoltcardViewModel
{ {
ReturnUrl = Url.Action(nameof(ViewPullPayment), "UIPullPayment", new { pullPaymentId }), ReturnUrl = Url.Action(nameof(ViewPullPayment), "UIPullPayment", new { pullPaymentId }),
WebsocketPath = Url.Action(nameof(VaultNFCBridgeConnection), "UIPullPayment", new { pullPaymentId }), WebsocketPath = Url.Action(nameof(VaultNFCBridgeConnection), "UIPullPayment", new { pullPaymentId }),
@ -34,7 +30,7 @@ namespace BTCPayServer.Controllers
[HttpPost("pull-payments/{pullPaymentId}/boltcard/{command}")] [HttpPost("pull-payments/{pullPaymentId}/boltcard/{command}")]
public IActionResult SetupBoltcardPost(string pullPaymentId, string command) public IActionResult SetupBoltcardPost(string pullPaymentId, string command)
{ {
TempData[WellKnownTempData.SuccessMessage] = "Boltcard is configured"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Boltcard is configured"].Value;
return RedirectToAction(nameof(ViewPullPayment), new { pullPaymentId }); return RedirectToAction(nameof(ViewPullPayment), new { pullPaymentId });
} }
@ -78,24 +74,24 @@ next:
await vaultClient.Show(VaultMessageType.Error, "BTCPay Server Vault does not seem to be running, you can download it on <a target=\"_blank\" href=\"https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest\">Github</a>.", cts.Token); await vaultClient.Show(VaultMessageType.Error, "BTCPay Server Vault does not seem to be running, you can download it on <a target=\"_blank\" href=\"https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest\">Github</a>.", cts.Token);
goto next; goto next;
} }
await vaultClient.Show(VaultMessageType.Ok, "BTCPayServer successfully connected to the vault.", cts.Token); await vaultClient.Show(VaultMessageType.Ok, StringLocalizer["BTCPayServer successfully connected to the vault."], cts.Token);
if (permission is false) if (permission is false)
{ {
await vaultClient.Show(VaultMessageType.Error, "The user declined access to the vault.", cts.Token); await vaultClient.Show(VaultMessageType.Error, StringLocalizer["The user declined access to the vault."], cts.Token);
goto next; goto next;
} }
await vaultClient.Show(VaultMessageType.Ok, "Access to vault granted by owner.", cts.Token); await vaultClient.Show(VaultMessageType.Ok, StringLocalizer["Access to vault granted by owner."], cts.Token);
await vaultClient.Show(VaultMessageType.Processing, "Waiting for NFC to be presented...", cts.Token); await vaultClient.Show(VaultMessageType.Processing, StringLocalizer["Waiting for NFC to be presented..."], cts.Token);
await transport.WaitForCard(cts.Token); await transport.WaitForCard(cts.Token);
await vaultClient.Show(VaultMessageType.Ok, "NFC detected.", cts.Token); await vaultClient.Show(VaultMessageType.Ok, StringLocalizer["NFC detected."], cts.Token);
var issuerKey = await _settingsRepository.GetIssuerKey(_env); var issuerKey = await _settingsRepository.GetIssuerKey(_env);
CardOrigin cardOrigin = await GetCardOrigin(pullPaymentId, ntag, issuerKey, cts.Token); CardOrigin cardOrigin = await GetCardOrigin(pullPaymentId, ntag, issuerKey, cts.Token);
if (cardOrigin is CardOrigin.OtherIssuer) if (cardOrigin is CardOrigin.OtherIssuer)
{ {
await vaultClient.Show(VaultMessageType.Error, "This card is already configured for another issuer", cts.Token); await vaultClient.Show(VaultMessageType.Error, StringLocalizer["This card is already configured for another issuer"], cts.Token);
goto next; goto next;
} }
@ -103,7 +99,7 @@ next:
switch (command) switch (command)
{ {
case "configure-boltcard": case "configure-boltcard":
await vaultClient.Show(VaultMessageType.Processing, "Configuring Boltcard...", cts.Token); await vaultClient.Show(VaultMessageType.Processing, StringLocalizer["Configuring Boltcard..."], cts.Token);
if (cardOrigin is CardOrigin.Blank || cardOrigin is CardOrigin.ThisIssuerReset) if (cardOrigin is CardOrigin.Blank || cardOrigin is CardOrigin.ThisIssuerReset)
{ {
await ntag.AuthenticateEV2First(0, AESKey.Default, cts.Token); await ntag.AuthenticateEV2First(0, AESKey.Default, cts.Token);
@ -119,35 +115,35 @@ next:
await _dbContextFactory.SetBoltcardResetState(issuerKey, uid); await _dbContextFactory.SetBoltcardResetState(issuerKey, uid);
throw; throw;
} }
await vaultClient.Show(VaultMessageType.Ok, "The card is now configured", cts.Token); await vaultClient.Show(VaultMessageType.Ok, StringLocalizer["The card is now configured"], cts.Token);
} }
else if (cardOrigin is CardOrigin.ThisIssuer) else if (cardOrigin is CardOrigin.ThisIssuer)
{ {
await vaultClient.Show(VaultMessageType.Ok, "This card is already properly configured", cts.Token); await vaultClient.Show(VaultMessageType.Ok, StringLocalizer["This card is already properly configured"], cts.Token);
} }
success = true; success = true;
break; break;
case "reset-boltcard": case "reset-boltcard":
await vaultClient.Show(VaultMessageType.Processing, "Resetting Boltcard...", cts.Token); await vaultClient.Show(VaultMessageType.Processing, StringLocalizer["Resetting Boltcard..."], cts.Token);
if (cardOrigin is CardOrigin.Blank) if (cardOrigin is CardOrigin.Blank)
{ {
await vaultClient.Show(VaultMessageType.Ok, "This card is already in a factory state", cts.Token); await vaultClient.Show(VaultMessageType.Ok, StringLocalizer["This card is already in a factory state"], cts.Token);
} }
else if (cardOrigin is CardOrigin.ThisIssuer thisIssuer) else if (cardOrigin is CardOrigin.ThisIssuer thisIssuer)
{ {
var cardKey = issuerKey.CreatePullPaymentCardKey(thisIssuer.Registration.UId, thisIssuer.Registration.Version, pullPaymentId); var cardKey = issuerKey.CreatePullPaymentCardKey(thisIssuer.Registration.UId, thisIssuer.Registration.Version, pullPaymentId);
await ntag.ResetCard(issuerKey, cardKey); await ntag.ResetCard(issuerKey, cardKey);
await _dbContextFactory.SetBoltcardResetState(issuerKey, thisIssuer.Registration.UId); await _dbContextFactory.SetBoltcardResetState(issuerKey, thisIssuer.Registration.UId);
await vaultClient.Show(VaultMessageType.Ok, "Card reset succeed", cts.Token); await vaultClient.Show(VaultMessageType.Ok, StringLocalizer["Card reset succeed"], cts.Token);
} }
success = true; success = true;
break; break;
} }
if (success) if (success)
{ {
await vaultClient.Show(VaultMessageType.Processing, "Please remove the NFC from the card reader", cts.Token); await vaultClient.Show(VaultMessageType.Processing, StringLocalizer["Please remove the NFC from the card reader"], cts.Token);
await transport.WaitForRemoved(cts.Token); await transport.WaitForRemoved(cts.Token);
await vaultClient.Show(VaultMessageType.Ok, "Thank you!", cts.Token); await vaultClient.Show(VaultMessageType.Ok, StringLocalizer["Thank you!"], cts.Token);
await vaultClient.SendSimpleMessage("done", cts.Token); await vaultClient.SendSimpleMessage("done", cts.Token);
} }
} }
@ -159,7 +155,7 @@ next:
{ {
try try
{ {
await vaultClient.Show(VaultMessageType.Error, "Unexpected error: " + ex.Message, ex.ToString(), cts.Token); await vaultClient.Show(VaultMessageType.Error, StringLocalizer["Unexpected error: {0}", ex.Message], ex.ToString(), cts.Token);
} }
catch { } catch { }
} }

View File

@ -1,11 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Amazon.S3.Model;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
@ -14,24 +10,17 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Controllers.Greenfield; using BTCPayServer.Controllers.Greenfield;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.ModelBinders;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Models.WalletViewModels; using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.NTag424;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payouts; using BTCPayServer.Payouts;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Dapper;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NBitcoin; using Microsoft.Extensions.Localization;
using NBitcoin.DataEncoders;
using NdefLibrary.Ndef;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
@ -48,6 +37,7 @@ namespace BTCPayServer.Controllers
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly BTCPayServerEnvironment _env; private readonly BTCPayServerEnvironment _env;
private readonly SettingsRepository _settingsRepository; private readonly SettingsRepository _settingsRepository;
public IStringLocalizer StringLocalizer { get; }
public UIPullPaymentController(ApplicationDbContextFactory dbContextFactory, public UIPullPaymentController(ApplicationDbContextFactory dbContextFactory,
CurrencyNameTable currencyNameTable, CurrencyNameTable currencyNameTable,
@ -59,6 +49,7 @@ namespace BTCPayServer.Controllers
PayoutMethodHandlerDictionary payoutHandlers, PayoutMethodHandlerDictionary payoutHandlers,
StoreRepository storeRepository, StoreRepository storeRepository,
BTCPayServerEnvironment env, BTCPayServerEnvironment env,
IStringLocalizer stringLocalizer,
SettingsRepository settingsRepository) SettingsRepository settingsRepository)
{ {
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
@ -72,6 +63,7 @@ namespace BTCPayServer.Controllers
_env = env; _env = env;
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
_networkProvider = networkProvider; _networkProvider = networkProvider;
StringLocalizer = stringLocalizer;
} }
[AllowAnonymous] [AllowAnonymous]
@ -196,7 +188,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "Pull payment updated successfully", Message = StringLocalizer["Pull payment updated successfully"].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
@ -211,12 +203,12 @@ namespace BTCPayServer.Controllers
var pp = await ctx.PullPayments.FindAsync(pullPaymentId); var pp = await ctx.PullPayments.FindAsync(pullPaymentId);
if (pp is null) if (pp is null)
{ {
ModelState.AddModelError(nameof(pullPaymentId), "This pull payment does not exists"); ModelState.AddModelError(nameof(pullPaymentId), StringLocalizer["This pull payment does not exists"]);
} }
if (string.IsNullOrEmpty(vm.Destination)) if (string.IsNullOrEmpty(vm.Destination))
{ {
ModelState.AddModelError(nameof(vm.Destination), "Please provide a destination"); ModelState.AddModelError(nameof(vm.Destination), StringLocalizer["Please provide a destination"]);
return await ViewPullPayment(pullPaymentId); return await ViewPullPayment(pullPaymentId);
} }
@ -232,7 +224,7 @@ namespace BTCPayServer.Controllers
{ {
var handler = _payoutHandlers.TryGet(pmId); var handler = _payoutHandlers.TryGet(pmId);
(IClaimDestination dst, string err) = handler == null (IClaimDestination dst, string err) = handler == null
? (null, "No payment handler found for this payment method") ? (null, StringLocalizer["No payment handler found for this payment method"])
: await handler.ParseAndValidateClaimDestination(vm.Destination, ppBlob, cancellationToken); : await handler.ParseAndValidateClaimDestination(vm.Destination, ppBlob, cancellationToken);
error = err; error = err;
if (dst is not null && err is null) if (dst is not null && err is null)
@ -256,7 +248,7 @@ namespace BTCPayServer.Controllers
if (destination is null) if (destination is null)
{ {
ModelState.AddModelError(nameof(vm.Destination), error ?? "Invalid destination or payment method"); ModelState.AddModelError(nameof(vm.Destination), error ?? StringLocalizer["Invalid destination or payment method"]);
return await ViewPullPayment(pullPaymentId); return await ViewPullPayment(pullPaymentId);
} }
var amtError = ClaimRequest.IsPayoutAmountOk(destination, vm.ClaimedAmount == 0 ? null : vm.ClaimedAmount, payoutHandler.Currency, pp.Currency); var amtError = ClaimRequest.IsPayoutAmountOk(destination, vm.ClaimedAmount == 0 ? null : vm.ClaimedAmount, payoutHandler.Currency, pp.Currency);

View File

@ -28,10 +28,10 @@ namespace BTCPayServer.Controllers
} }
catch (Exception) catch (Exception)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Remote plugins lookup failed. Try again later." Message = StringLocalizer["Remote plugins lookup failed. Try again later."].Value
}); });
availablePlugins = Array.Empty<PluginService.AvailablePlugin>(); availablePlugins = Array.Empty<PluginService.AvailablePlugin>();
} }
@ -75,9 +75,9 @@ namespace BTCPayServer.Controllers
[FromServices] PluginService pluginService, string plugin) [FromServices] PluginService pluginService, string plugin)
{ {
pluginService.UninstallPlugin(plugin); pluginService.UninstallPlugin(plugin);
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "Plugin scheduled to be uninstalled.", Message = StringLocalizer["Plugin scheduled to be uninstalled."].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
@ -89,9 +89,9 @@ namespace BTCPayServer.Controllers
[FromServices] PluginService pluginService, string plugin) [FromServices] PluginService pluginService, string plugin)
{ {
pluginService.CancelCommands(plugin); pluginService.CancelCommands(plugin);
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "Plugin action cancelled.", Message = StringLocalizer["Plugin action cancelled."].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
@ -113,17 +113,17 @@ namespace BTCPayServer.Controllers
{ {
pluginService.InstallPlugin(plugin); pluginService.InstallPlugin(plugin);
} }
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "Plugin scheduled to be installed.", Message = StringLocalizer["Plugin scheduled to be installed."].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
} }
catch (Exception) catch (Exception)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "The plugin could not be downloaded. Try again later.", Message = StringLocalizer["The plugin could not be downloaded. Try again later."].Value,
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
} }
@ -142,9 +142,9 @@ namespace BTCPayServer.Controllers
StringComparison.InvariantCultureIgnoreCase)); StringComparison.InvariantCultureIgnoreCase));
} }
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "Files uploaded, restart server to load plugins", Message = StringLocalizer["Files uploaded, restart server to load plugins"].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
return RedirectToAction("ListPlugins"); return RedirectToAction("ListPlugins");

View File

@ -67,12 +67,12 @@ namespace BTCPayServer.Controllers
string successMessage = null; string successMessage = null;
if (role == "create") if (role == "create")
{ {
successMessage = "Role created"; successMessage = StringLocalizer["Role created"];
role = viewModel.Role; role = viewModel.Role;
} }
else else
{ {
successMessage = "Role updated"; successMessage = StringLocalizer["Role updated"];
var storeRole = await _StoreRepository.GetStoreRole(new StoreRoleId(role)); var storeRole = await _StoreRepository.GetStoreRole(new StoreRoleId(role));
if (storeRole == null) if (storeRole == null)
return NotFound(); return NotFound();
@ -86,15 +86,15 @@ namespace BTCPayServer.Controllers
var r = await _StoreRepository.AddOrUpdateStoreRole(new StoreRoleId(role), viewModel.Policies); var r = await _StoreRepository.AddOrUpdateStoreRole(new StoreRoleId(role), viewModel.Policies);
if (r is null) if (r is null)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Role could not be updated" Message = StringLocalizer["Role could not be updated"].Value
}); });
return View(viewModel); return View(viewModel);
} }
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = successMessage Message = successMessage
@ -114,11 +114,11 @@ namespace BTCPayServer.Controllers
return View("Confirm", return View("Confirm",
roleData.IsUsed is true roleData.IsUsed is true
? new ConfirmModel("Delete role", ? new ConfirmModel(StringLocalizer["Delete role"],
$"Unable to proceed: The role <strong>{Html.Encode(roleData.Role)}</strong> is currently assigned to one or more users, it cannot be removed.") $"Unable to proceed: The role <strong>{Html.Encode(roleData.Role)}</strong> is currently assigned to one or more users, it cannot be removed.")
: new ConfirmModel("Delete role", : new ConfirmModel(StringLocalizer["Delete role"],
$"The role <strong>{Html.Encode(roleData.Role)}</strong> will be permanently deleted. Are you sure?", $"The role <strong>{Html.Encode(roleData.Role)}</strong> will be permanently deleted. Are you sure?",
"Delete")); StringLocalizer["Delete"]));
} }
[HttpPost("server/roles/{role}/delete")] [HttpPost("server/roles/{role}/delete")]
@ -137,7 +137,7 @@ namespace BTCPayServer.Controllers
if (errorMessage is null) if (errorMessage is null)
{ {
TempData[WellKnownTempData.SuccessMessage] = "Role deleted"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Role deleted"].Value;
} }
else else
{ {
@ -153,12 +153,12 @@ namespace BTCPayServer.Controllers
var resolved = await _StoreRepository.ResolveStoreRoleId(null, role); var resolved = await _StoreRepository.ResolveStoreRoleId(null, role);
if (resolved is null) if (resolved is null)
{ {
TempData[WellKnownTempData.ErrorMessage] = "Role could not be set as default"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Role could not be set as default"].Value;
} }
else else
{ {
await _StoreRepository.SetDefaultRole(role); await _StoreRepository.SetDefaultRole(role);
TempData[WellKnownTempData.SuccessMessage] = "Role set default"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Role set default"].Value;
} }
return RedirectToAction(nameof(ListRoles)); return RedirectToAction(nameof(ListRoles));

View File

@ -52,9 +52,9 @@ namespace BTCPayServer.Controllers
if (!allFilesExist) if (!allFilesExist)
{ {
this.TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "Some of the files were not found", Message = StringLocalizer["Some of the files were not found"].Value,
Severity = StatusMessageModel.StatusSeverity.Warning, Severity = StatusMessageModel.StatusSeverity.Warning,
}); });
} }
@ -75,12 +75,12 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(Files), new return RedirectToAction(nameof(Files), new
{ {
fileIds = Array.Empty<string>(), fileIds = Array.Empty<string>(),
statusMessage = "File removed" statusMessage = StringLocalizer["File removed"].Value
}); });
} }
catch (Exception e) catch (Exception e)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = e.Message Message = e.Message
@ -108,7 +108,7 @@ namespace BTCPayServer.Controllers
{ {
if (viewModel.TimeAmount <= 0) if (viewModel.TimeAmount <= 0)
{ {
ModelState.AddModelError(nameof(viewModel.TimeAmount), "Time must be at least 1"); ModelState.AddModelError(nameof(viewModel.TimeAmount), StringLocalizer["Time must be at least 1"]);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
@ -192,21 +192,21 @@ namespace BTCPayServer.Controllers
if (invalidFileNameCount == 0) if (invalidFileNameCount == 0)
{ {
statusMessage = "Files Added Successfully"; statusMessage = StringLocalizer["Files added successfully"];
statusMessageSeverity = StatusMessageModel.StatusSeverity.Success; statusMessageSeverity = StatusMessageModel.StatusSeverity.Success;
} }
else if (invalidFileNameCount > 0 && invalidFileNameCount < files.Count) else if (invalidFileNameCount > 0 && invalidFileNameCount < files.Count)
{ {
statusMessage = $"{files.Count - invalidFileNameCount} files were added. {invalidFileNameCount} files had invalid names"; statusMessage = StringLocalizer["{0} files were added. {1} files had invalid names", files.Count - invalidFileNameCount, invalidFileNameCount].Value;
statusMessageSeverity = StatusMessageModel.StatusSeverity.Error; statusMessageSeverity = StatusMessageModel.StatusSeverity.Error;
} }
else else
{ {
statusMessage = $"Files could not be added due to invalid names"; statusMessage = StringLocalizer["Files could not be added due to invalid names"].Value;
statusMessageSeverity = StatusMessageModel.StatusSeverity.Error; statusMessageSeverity = StatusMessageModel.StatusSeverity.Error;
} }
this.TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = statusMessage, Message = statusMessage,
Severity = statusMessageSeverity Severity = statusMessageSeverity
@ -266,10 +266,10 @@ namespace BTCPayServer.Controllers
{ {
if (!Enum.TryParse(typeof(StorageProvider), provider, out var storageProvider)) if (!Enum.TryParse(typeof(StorageProvider), provider, out var storageProvider))
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{provider} provider is not supported" Message = StringLocalizer["{0} provider is not supported", provider].Value
}); });
return RedirectToAction(nameof(Storage)); return RedirectToAction(nameof(Storage));
} }
@ -282,10 +282,10 @@ namespace BTCPayServer.Controllers
switch (storageProviderService) switch (storageProviderService)
{ {
case null: case null:
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{storageProvider} is not supported" Message = StringLocalizer["{0} provider is not supported", storageProvider].Value
}); });
return RedirectToAction(nameof(Storage)); return RedirectToAction(nameof(Storage));
case AzureBlobStorageFileProviderService fileProviderService: case AzureBlobStorageFileProviderService fileProviderService:
@ -350,10 +350,10 @@ namespace BTCPayServer.Controllers
data.Provider = storageProvider; data.Provider = storageProvider;
data.Configuration = JObject.FromObject(viewModel); data.Configuration = JObject.FromObject(viewModel);
await _SettingsRepository.UpdateSetting(data); await _SettingsRepository.UpdateSetting(data);
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Storage settings updated successfully" Message = StringLocalizer["Storage settings updated successfully"].Value
}); });
return View(viewModel); return View(viewModel);
} }

View File

@ -38,13 +38,14 @@ namespace BTCPayServer.Controllers
[HttpGet("server/dictionaries/create")] [HttpGet("server/dictionaries/create")]
public async Task<IActionResult> CreateDictionary(string fallback = null) public async Task<IActionResult> CreateDictionary(string fallback = null)
{ {
var dictionaries = await this._localizer.GetDictionaries(); var dictionaries = await _localizer.GetDictionaries();
return View(new CreateDictionaryViewModel() return View(new CreateDictionaryViewModel
{ {
Name = fallback is not null ? $"Clone of {fallback}" : "", Name = fallback is not null ? $"Clone of {fallback}" : "",
Fallback = fallback ?? Translations.DefaultLanguage, Fallback = fallback ?? Translations.DefaultLanguage,
}.SetDictionaries(dictionaries)); }.SetDictionaries(dictionaries));
} }
[HttpPost("server/dictionaries/create")] [HttpPost("server/dictionaries/create")]
public async Task<IActionResult> CreateDictionary(CreateDictionaryViewModel viewModel) public async Task<IActionResult> CreateDictionary(CreateDictionaryViewModel viewModel)
{ {
@ -52,23 +53,23 @@ namespace BTCPayServer.Controllers
{ {
try try
{ {
await this._localizer.CreateDictionary(viewModel.Name, viewModel.Fallback, "Custom"); await _localizer.CreateDictionary(viewModel.Name, viewModel.Fallback, "Custom");
} }
catch (DbException) catch (DbException)
{ {
ModelState.AddModelError(nameof(viewModel.Name), $"'{viewModel.Name}' already exists"); ModelState.AddModelError(nameof(viewModel.Name), StringLocalizer["'{0}' already exists", viewModel.Name]);
} }
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
return View(viewModel.SetDictionaries(await this._localizer.GetDictionaries())); return View(viewModel.SetDictionaries(await _localizer.GetDictionaries()));
TempData[WellKnownTempData.SuccessMessage] = "Dictionary created"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Dictionary created"].Value;
return RedirectToAction(nameof(EditDictionary), new { dictionary = viewModel.Name }); return RedirectToAction(nameof(EditDictionary), new { dictionary = viewModel.Name });
} }
[HttpGet("server/dictionaries/{dictionary}")] [HttpGet("server/dictionaries/{dictionary}")]
public async Task<IActionResult> EditDictionary(string dictionary) public async Task<IActionResult> EditDictionary(string dictionary)
{ {
if ((await this._localizer.GetDictionary(dictionary)) is null) if ((await _localizer.GetDictionary(dictionary)) is null)
return NotFound(); return NotFound();
var translations = await _localizer.GetTranslations(dictionary); var translations = await _localizer.GetTranslations(dictionary);
return View(new EditDictionaryViewModel().SetTranslations(translations.Translations)); return View(new EditDictionaryViewModel().SetTranslations(translations.Translations));
@ -77,7 +78,7 @@ namespace BTCPayServer.Controllers
[HttpPost("server/dictionaries/{dictionary}")] [HttpPost("server/dictionaries/{dictionary}")]
public async Task<IActionResult> EditDictionary(string dictionary, EditDictionaryViewModel viewModel) public async Task<IActionResult> EditDictionary(string dictionary, EditDictionaryViewModel viewModel)
{ {
var d = await this._localizer.GetDictionary(dictionary); var d = await _localizer.GetDictionary(dictionary);
if (d is null) if (d is null)
return NotFound(); return NotFound();
if (Environment.CheatMode && viewModel.Command == "Fake") if (Environment.CheatMode && viewModel.Command == "Fake")
@ -90,32 +91,32 @@ namespace BTCPayServer.Controllers
} }
viewModel.Translations = Translations.CreateFromJson(jobj.ToString()).ToJsonFormat(); viewModel.Translations = Translations.CreateFromJson(jobj.ToString()).ToJsonFormat();
} }
if (!Translations.TryCreateFromJson(viewModel.Translations, out var translations)) if (!Translations.TryCreateFromJson(viewModel.Translations, out var translations))
{ {
ModelState.AddModelError(nameof(viewModel.Translations), "Syntax error"); ModelState.AddModelError(nameof(viewModel.Translations), StringLocalizer["Syntax error"]);
return View(viewModel); return View(viewModel);
} }
await _localizer.Save(d, translations); await _localizer.Save(d, translations);
TempData[WellKnownTempData.SuccessMessage] = "Dictionary updated"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Dictionary updated"].Value;
return RedirectToAction(nameof(ListDictionaries)); return RedirectToAction(nameof(ListDictionaries));
} }
[HttpGet("server/dictionaries/{dictionary}/select")] [HttpGet("server/dictionaries/{dictionary}/select")]
public async Task<IActionResult> SelectDictionary(string dictionary) public async Task<IActionResult> SelectDictionary(string dictionary)
{ {
var settings = await this._SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new(); var settings = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new();
settings.LangDictionary = dictionary; settings.LangDictionary = dictionary;
await _SettingsRepository.UpdateSetting(settings); await _SettingsRepository.UpdateSetting(settings);
await _localizer.Load(); await _localizer.Load();
TempData[WellKnownTempData.SuccessMessage] = $"Default dictionary changed to {dictionary}"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Default dictionary changed to {0}", dictionary].Value;
return RedirectToAction(nameof(ListDictionaries)); return RedirectToAction(nameof(ListDictionaries));
} }
[HttpPost("server/dictionaries/{dictionary}/delete")] [HttpPost("server/dictionaries/{dictionary}/delete")]
public async Task<IActionResult> DeleteDictionary(string dictionary) public async Task<IActionResult> DeleteDictionary(string dictionary)
{ {
await _localizer.DeleteDictionary(dictionary); await _localizer.DeleteDictionary(dictionary);
TempData[WellKnownTempData.SuccessMessage] = $"Dictionary {dictionary} deleted"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Dictionary {0} deleted", dictionary].Value;
return RedirectToAction(nameof(ListDictionaries)); return RedirectToAction(nameof(ListDictionaries));
} }
} }

View File

@ -139,18 +139,18 @@ namespace BTCPayServer.Controllers
{ {
if (viewModel.ImageFile.Length > 1_000_000) if (viewModel.ImageFile.Length > 1_000_000)
{ {
ModelState.AddModelError(nameof(viewModel.ImageFile), "The uploaded image file should be less than 1MB"); ModelState.AddModelError(nameof(viewModel.ImageFile), StringLocalizer["The uploaded image file should be less than {0}", "1MB"]);
} }
else if (!viewModel.ImageFile.ContentType.StartsWith("image/", StringComparison.InvariantCulture)) else if (!viewModel.ImageFile.ContentType.StartsWith("image/", StringComparison.InvariantCulture))
{ {
ModelState.AddModelError(nameof(viewModel.ImageFile), "The uploaded file needs to be an image"); ModelState.AddModelError(nameof(viewModel.ImageFile), StringLocalizer["The uploaded file needs to be an image"]);
} }
else else
{ {
var formFile = await viewModel.ImageFile.Bufferize(); var formFile = await viewModel.ImageFile.Bufferize();
if (!FileTypeDetector.IsPicture(formFile.Buffer, formFile.FileName)) if (!FileTypeDetector.IsPicture(formFile.Buffer, formFile.FileName))
{ {
ModelState.AddModelError(nameof(viewModel.ImageFile), "The uploaded file needs to be an image"); ModelState.AddModelError(nameof(viewModel.ImageFile), StringLocalizer["The uploaded file needs to be an image"]);
} }
else else
{ {
@ -165,7 +165,7 @@ namespace BTCPayServer.Controllers
} }
catch (Exception e) catch (Exception e)
{ {
ModelState.AddModelError(nameof(viewModel.ImageFile), $"Could not save image: {e.Message}"); ModelState.AddModelError(nameof(viewModel.ImageFile), StringLocalizer["Could not save image: {0}", e.Message]);
} }
} }
} }
@ -181,7 +181,7 @@ namespace BTCPayServer.Controllers
var wasAdmin = Roles.HasServerAdmin(roles); var wasAdmin = Roles.HasServerAdmin(roles);
if (!viewModel.IsAdmin && admins.Count == 1 && wasAdmin) if (!viewModel.IsAdmin && admins.Count == 1 && wasAdmin)
{ {
TempData[WellKnownTempData.ErrorMessage] = "This is the only Admin, so their role can't be removed until another Admin is added."; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["This is the only admin, so their role can't be removed until another Admin is added."].Value;
return View(viewModel); return View(viewModel);
} }
@ -199,11 +199,11 @@ namespace BTCPayServer.Controllers
{ {
if (propertiesChanged is not false && adminStatusChanged is not false && approvalStatusChanged is not false) if (propertiesChanged is not false && adminStatusChanged is not false && approvalStatusChanged is not false)
{ {
TempData[WellKnownTempData.SuccessMessage] = "User successfully updated"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["User successfully updated"].Value;
} }
else else
{ {
TempData[WellKnownTempData.ErrorMessage] = "Error updating user"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Error updating user"].Value;
} }
} }
@ -231,7 +231,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = result.Succeeded ? StatusMessageModel.StatusSeverity.Success : StatusMessageModel.StatusSeverity.Error, Severity = result.Succeeded ? StatusMessageModel.StatusSeverity.Success : StatusMessageModel.StatusSeverity.Error,
Message = result.Succeeded ? "Password successfully set" : "An error occurred while resetting user password" Message = result.Succeeded ? StringLocalizer["Password successfully set"].Value : StringLocalizer["An error occurred while resetting user password"].Value
}); });
return RedirectToAction(nameof(ListUsers)); return RedirectToAction(nameof(ListUsers));
} }
@ -326,16 +326,16 @@ namespace BTCPayServer.Controllers
{ {
if (await _userService.IsUserTheOnlyOneAdmin(user)) if (await _userService.IsUserTheOnlyOneAdmin(user))
{ {
return View("Confirm", new ConfirmModel("Delete admin", return View("Confirm", new ConfirmModel(StringLocalizer["Delete admin"],
$"Unable to proceed: As the user <strong>{Html.Encode(user.Email)}</strong> is the last enabled admin, it cannot be removed.")); $"Unable to proceed: As the user <strong>{Html.Encode(user.Email)}</strong> is the last enabled admin, it cannot be removed."));
} }
return View("Confirm", new ConfirmModel("Delete admin", return View("Confirm", new ConfirmModel(StringLocalizer["Delete admin"],
$"The admin <strong>{Html.Encode(user.Email)}</strong> will be permanently deleted. This action will also delete all accounts, users and data associated with the server account. Are you sure?", $"The admin <strong>{Html.Encode(user.Email)}</strong> will be permanently deleted. This action will also delete all accounts, users and data associated with the server account. Are you sure?",
"Delete")); "Delete"));
} }
return View("Confirm", new ConfirmModel("Delete user", $"The user <strong>{Html.Encode(user.Email)}</strong> will be permanently deleted. Are you sure?", "Delete")); return View("Confirm", new ConfirmModel(StringLocalizer["Delete user"], $"The user <strong>{Html.Encode(user.Email)}</strong> will be permanently deleted. Are you sure?", "Delete"));
} }
[HttpPost("server/users/{userId}/delete")] [HttpPost("server/users/{userId}/delete")]
@ -347,7 +347,7 @@ namespace BTCPayServer.Controllers
await _userService.DeleteUserAndAssociatedData(user); await _userService.DeleteUserAndAssociatedData(user);
TempData[WellKnownTempData.SuccessMessage] = "User deleted"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["User deleted"].Value;
return RedirectToAction(nameof(ListUsers)); return RedirectToAction(nameof(ListUsers));
} }
@ -360,7 +360,7 @@ namespace BTCPayServer.Controllers
if (!enable && await _userService.IsUserTheOnlyOneAdmin(user)) if (!enable && await _userService.IsUserTheOnlyOneAdmin(user))
{ {
return View("Confirm", new ConfirmModel("Disable admin", return View("Confirm", new ConfirmModel(StringLocalizer["Disable admin"],
$"Unable to proceed: As the user <strong>{Html.Encode(user.Email)}</strong> is the last enabled admin, it cannot be disabled.")); $"Unable to proceed: As the user <strong>{Html.Encode(user.Email)}</strong> is the last enabled admin, it cannot be disabled."));
} }
return View("Confirm", new ConfirmModel($"{(enable ? "Enable" : "Disable")} user", $"The user <strong>{Html.Encode(user.Email)}</strong> will be {(enable ? "enabled" : "disabled")}. Are you sure?", (enable ? "Enable" : "Disable"))); return View("Confirm", new ConfirmModel($"{(enable ? "Enable" : "Disable")} user", $"The user <strong>{Html.Encode(user.Email)}</strong> will be {(enable ? "enabled" : "disabled")}. Are you sure?", (enable ? "Enable" : "Disable")));
@ -374,12 +374,14 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
if (!enable && await _userService.IsUserTheOnlyOneAdmin(user)) if (!enable && await _userService.IsUserTheOnlyOneAdmin(user))
{ {
TempData[WellKnownTempData.SuccessMessage] = $"User was the last enabled admin and could not be disabled."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["User was the last enabled admin and could not be disabled."].Value;
return RedirectToAction(nameof(ListUsers)); return RedirectToAction(nameof(ListUsers));
} }
await _userService.ToggleUser(userId, enable ? null : DateTimeOffset.MaxValue); await _userService.ToggleUser(userId, enable ? null : DateTimeOffset.MaxValue);
TempData[WellKnownTempData.SuccessMessage] = $"User {(enable ? "enabled" : "disabled")}"; TempData[WellKnownTempData.SuccessMessage] = enable
? StringLocalizer["User enabled"].Value
: StringLocalizer["User disabled"].Value;
return RedirectToAction(nameof(ListUsers)); return RedirectToAction(nameof(ListUsers));
} }
@ -402,7 +404,9 @@ namespace BTCPayServer.Controllers
await _userService.SetUserApproval(userId, approved, Request.GetAbsoluteRootUri()); await _userService.SetUserApproval(userId, approved, Request.GetAbsoluteRootUri());
TempData[WellKnownTempData.SuccessMessage] = $"User {(approved ? "approved" : "unapproved")}"; TempData[WellKnownTempData.SuccessMessage] = approved
? StringLocalizer["User approved"].Value
: StringLocalizer["User unapproved"].Value;
return RedirectToAction(nameof(ListUsers)); return RedirectToAction(nameof(ListUsers));
} }
@ -413,7 +417,7 @@ namespace BTCPayServer.Controllers
if (user == null) if (user == null)
return NotFound(); return NotFound();
return View("Confirm", new ConfirmModel("Send verification email", $"This will send a verification email to <strong>{Html.Encode(user.Email)}</strong>.", "Send")); return View("Confirm", new ConfirmModel(StringLocalizer["Send verification email"], $"This will send a verification email to <strong>{Html.Encode(user.Email)}</strong>.", "Send"));
} }
[HttpPost("server/users/{userId}/verification-email")] [HttpPost("server/users/{userId}/verification-email")]
@ -430,7 +434,7 @@ namespace BTCPayServer.Controllers
(await _emailSenderFactory.GetEmailSender()).SendEmailConfirmation(user.GetMailboxAddress(), callbackUrl); (await _emailSenderFactory.GetEmailSender()).SendEmailConfirmation(user.GetMailboxAddress(), callbackUrl);
TempData[WellKnownTempData.SuccessMessage] = "Verification email sent"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Verification email sent"].Value;
return RedirectToAction(nameof(ListUsers)); return RedirectToAction(nameof(ListUsers));
} }

View File

@ -33,6 +33,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using MimeKit; using MimeKit;
@ -69,6 +70,7 @@ namespace BTCPayServer.Controllers
private readonly EmailSenderFactory _emailSenderFactory; private readonly EmailSenderFactory _emailSenderFactory;
private readonly TransactionLinkProviders _transactionLinkProviders; private readonly TransactionLinkProviders _transactionLinkProviders;
private readonly LocalizerService _localizer; private readonly LocalizerService _localizer;
public IStringLocalizer StringLocalizer { get; }
public UIServerController( public UIServerController(
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
@ -96,6 +98,7 @@ namespace BTCPayServer.Controllers
IHtmlHelper html, IHtmlHelper html,
TransactionLinkProviders transactionLinkProviders, TransactionLinkProviders transactionLinkProviders,
LocalizerService localizer, LocalizerService localizer,
IStringLocalizer stringLocalizer,
BTCPayServerEnvironment environment BTCPayServerEnvironment environment
) )
{ {
@ -125,6 +128,7 @@ namespace BTCPayServer.Controllers
_transactionLinkProviders = transactionLinkProviders; _transactionLinkProviders = transactionLinkProviders;
_localizer = localizer; _localizer = localizer;
Environment = environment; Environment = environment;
StringLocalizer = stringLocalizer;
} }
[HttpGet("server/stores")] [HttpGet("server/stores")]
@ -157,7 +161,7 @@ namespace BTCPayServer.Controllers
}; };
if (!vm.CanUseSSH) if (!vm.CanUseSSH)
TempData[WellKnownTempData.ErrorMessage] = "Maintenance feature requires access to SSH properly configured in BTCPay Server configuration."; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Maintenance feature requires access to SSH properly configured in BTCPay Server configuration."].Value;
if (IPAddress.TryParse(vm.DNSDomain, out var unused)) if (IPAddress.TryParse(vm.DNSDomain, out var unused))
vm.DNSDomain = null; vm.DNSDomain = null;
@ -170,7 +174,7 @@ namespace BTCPayServer.Controllers
vm.CanUseSSH = _sshState.CanUseSSH; vm.CanUseSSH = _sshState.CanUseSSH;
if (command != "soft-restart" && !vm.CanUseSSH) if (command != "soft-restart" && !vm.CanUseSSH)
{ {
TempData[WellKnownTempData.ErrorMessage] = "Maintenance feature requires access to SSH properly configured in BTCPay Server configuration."; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Maintenance feature requires access to SSH properly configured in BTCPay Server configuration."].Value;
return View(vm); return View(vm);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
@ -229,21 +233,21 @@ namespace BTCPayServer.Controllers
builder.Path = null; builder.Path = null;
builder.Query = null; builder.Query = null;
TempData[WellKnownTempData.SuccessMessage] = $"Domain name changing... the server will restart, please use \"{builder.Uri.AbsoluteUri}\" (this page won't reload automatically)"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Domain name changing... the server will restart, please use \"{0}\" (this page won't reload automatically)", builder.Uri.AbsoluteUri].Value;
} }
else if (command == "update") else if (command == "update")
{ {
var error = await RunSSH(vm, $"btcpay-update.sh"); var error = await RunSSH(vm, $"btcpay-update.sh");
if (error != null) if (error != null)
return error; return error;
TempData[WellKnownTempData.SuccessMessage] = $"The server might restart soon if an update is available... (this page won't reload automatically)"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The server might restart soon if an update is available... (this page won't reload automatically)"].Value;
} }
else if (command == "clean") else if (command == "clean")
{ {
var error = await RunSSH(vm, $"btcpay-clean.sh"); var error = await RunSSH(vm, $"btcpay-clean.sh");
if (error != null) if (error != null)
return error; return error;
TempData[WellKnownTempData.SuccessMessage] = $"The old docker images will be cleaned soon..."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The old docker images will be cleaned soon..."].Value;
} }
else if (command == "restart") else if (command == "restart")
{ {
@ -251,11 +255,11 @@ namespace BTCPayServer.Controllers
if (error != null) if (error != null)
return error; return error;
Logs.PayServer.LogInformation("A hard restart has been requested"); Logs.PayServer.LogInformation("A hard restart has been requested");
TempData[WellKnownTempData.SuccessMessage] = $"BTCPay will restart momentarily."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["BTCPay will restart momentarily."].Value;
} }
else if (command == "soft-restart") else if (command == "soft-restart")
{ {
TempData[WellKnownTempData.SuccessMessage] = $"BTCPay will restart momentarily."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["BTCPay will restart momentarily."].Value;
Logs.PayServer.LogInformation("A soft restart has been requested"); Logs.PayServer.LogInformation("A soft restart has been requested");
_ = Task.Delay(3000).ContinueWith((t) => ApplicationLifetime.StopApplication()); _ = Task.Delay(3000).ContinueWith((t) => ApplicationLifetime.StopApplication());
} }
@ -401,7 +405,7 @@ namespace BTCPayServer.Controllers
_ = _transactionLinkProviders.RefreshTransactionLinkTemplates(); _ = _transactionLinkProviders.RefreshTransactionLinkTemplates();
if (_policiesSettings.LangDictionary != settings.LangDictionary) if (_policiesSettings.LangDictionary != settings.LangDictionary)
await _localizer.Load(); await _localizer.Load();
TempData[WellKnownTempData.SuccessMessage] = "Policies updated successfully"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Policies updated successfully"].Value;
return RedirectToAction(nameof(Policies)); return RedirectToAction(nameof(Policies));
} }
@ -525,7 +529,7 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
if (!string.IsNullOrEmpty(cryptoCode) && !_dashBoard.IsFullySynched(cryptoCode, out _) && service.Type != ExternalServiceTypes.RPC) if (!string.IsNullOrEmpty(cryptoCode) && !_dashBoard.IsFullySynched(cryptoCode, out _) && service.Type != ExternalServiceTypes.RPC)
{ {
TempData[WellKnownTempData.ErrorMessage] = $"{cryptoCode} is not fully synched"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["{0} is not fully synched", cryptoCode].Value;
return RedirectToAction(nameof(Services)); return RedirectToAction(nameof(Services));
} }
try try
@ -575,7 +579,7 @@ namespace BTCPayServer.Controllers
case ExternalServiceTypes.Torq: case ExternalServiceTypes.Torq:
if (connectionString.AccessKey == null) if (connectionString.AccessKey == null)
{ {
TempData[WellKnownTempData.ErrorMessage] = $"The access key of the service is not set"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["The access key of the service is not set"].Value;
return RedirectToAction(nameof(Services)); return RedirectToAction(nameof(Services));
} }
LightningWalletServices vm = new LightningWalletServices(); LightningWalletServices vm = new LightningWalletServices();
@ -613,7 +617,7 @@ namespace BTCPayServer.Controllers
[HttpGet("server/services/{serviceName}/{cryptoCode}/removelndseed")] [HttpGet("server/services/{serviceName}/{cryptoCode}/removelndseed")]
public IActionResult RemoveLndSeed(string serviceName, string cryptoCode) public IActionResult RemoveLndSeed(string serviceName, string cryptoCode)
{ {
return View("Confirm", new ConfirmModel("Delete LND seed", "This action will permanently delete your LND seed and password. You will not be able to recover them if you don't have a backup. Are you sure?", "Delete")); return View("Confirm", new ConfirmModel(StringLocalizer["Delete LND seed"], StringLocalizer["This action will permanently delete your LND seed and password. You will not be able to recover them if you don't have a backup. Are you sure?"], StringLocalizer["Delete"]));
} }
[HttpPost("server/services/{serviceName}/{cryptoCode}/removelndseed")] [HttpPost("server/services/{serviceName}/{cryptoCode}/removelndseed")]
@ -626,24 +630,24 @@ namespace BTCPayServer.Controllers
var model = LndSeedBackupViewModel.Parse(service.ConnectionString.CookieFilePath); var model = LndSeedBackupViewModel.Parse(service.ConnectionString.CookieFilePath);
if (!model.IsWalletUnlockPresent) if (!model.IsWalletUnlockPresent)
{ {
TempData[WellKnownTempData.ErrorMessage] = $"File with wallet password and seed info not present"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["File with wallet password and seed info not present"].Value;
return RedirectToAction(nameof(Services)); return RedirectToAction(nameof(Services));
} }
if (string.IsNullOrEmpty(model.Seed)) if (string.IsNullOrEmpty(model.Seed))
{ {
TempData[WellKnownTempData.ErrorMessage] = $"Seed information was already removed"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Seed information was already removed"].Value;
return RedirectToAction(nameof(Services)); return RedirectToAction(nameof(Services));
} }
if (await model.RemoveSeedAndWrite(service.ConnectionString.CookieFilePath)) if (await model.RemoveSeedAndWrite(service.ConnectionString.CookieFilePath))
{ {
TempData[WellKnownTempData.SuccessMessage] = $"Seed successfully removed"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Seed successfully removed"].Value;
return RedirectToAction(nameof(Service), new { serviceName, cryptoCode }); return RedirectToAction(nameof(Service), new { serviceName, cryptoCode });
} }
else else
{ {
TempData[WellKnownTempData.ErrorMessage] = $"Seed removal failed"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Seed removal failed"].Value;
return RedirectToAction(nameof(Services)); return RedirectToAction(nameof(Services));
} }
} }
@ -725,7 +729,7 @@ namespace BTCPayServer.Controllers
{ {
if (!_dashBoard.IsFullySynched(cryptoCode, out _)) if (!_dashBoard.IsFullySynched(cryptoCode, out _))
{ {
TempData[WellKnownTempData.ErrorMessage] = $"{cryptoCode} is not fully synched"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["{0} is not fully synched", cryptoCode].Value;
return RedirectToAction(nameof(Services)); return RedirectToAction(nameof(Services));
} }
var service = GetService(serviceName, cryptoCode); var service = GetService(serviceName, cryptoCode);
@ -820,7 +824,7 @@ namespace BTCPayServer.Controllers
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient()); string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
if (errorMessage == null) if (errorMessage == null)
{ {
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS has been successfully queried, your configuration is saved"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The Dynamic DNS has been successfully queried, your configuration is saved"].Value;
viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow; viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow;
settings.Services.Add(viewModel.Settings); settings.Services.Add(viewModel.Settings);
await _SettingsRepository.UpdateSetting(settings); await _SettingsRepository.UpdateSetting(settings);
@ -856,7 +860,7 @@ namespace BTCPayServer.Controllers
viewModel.Settings.Hostname = viewModel.Settings.Hostname.Trim().ToLowerInvariant(); viewModel.Settings.Hostname = viewModel.Settings.Hostname.Trim().ToLowerInvariant();
if (!viewModel.Settings.Enabled) if (!viewModel.Settings.Enabled)
{ {
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS service has been disabled"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The Dynamic DNS service has been disabled"].Value;
viewModel.Settings.LastUpdated = null; viewModel.Settings.LastUpdated = null;
} }
else else
@ -864,7 +868,7 @@ namespace BTCPayServer.Controllers
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient()); string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
if (errorMessage == null) if (errorMessage == null)
{ {
TempData[WellKnownTempData.SuccessMessage] = $"The Dynamic DNS has been successfully queried, your configuration is saved"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The Dynamic DNS has been successfully queried, your configuration is saved"].Value;
viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow; viewModel.Settings.LastUpdated = DateTimeOffset.UtcNow;
} }
else else
@ -900,7 +904,7 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
settings.Services.RemoveAt(i); settings.Services.RemoveAt(i);
await _SettingsRepository.UpdateSetting(settings); await _SettingsRepository.UpdateSetting(settings);
TempData[WellKnownTempData.SuccessMessage] = "Dynamic DNS service successfully removed"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Dynamic DNS service successfully removed"].Value;
RouteData.Values.Remove(nameof(hostname)); RouteData.Values.Remove(nameof(hostname));
return RedirectToAction(nameof(DynamicDnsServices)); return RedirectToAction(nameof(DynamicDnsServices));
} }
@ -974,7 +978,7 @@ namespace BTCPayServer.Controllers
try try
{ {
await System.IO.File.WriteAllTextAsync(_Options.SSHSettings.AuthorizedKeysFile, newContent); await System.IO.File.WriteAllTextAsync(_Options.SSHSettings.AuthorizedKeysFile, newContent);
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["authorized_keys has been updated"].Value;
updated = true; updated = true;
} }
catch (Exception ex) catch (Exception ex)
@ -1003,7 +1007,7 @@ namespace BTCPayServer.Controllers
if (exception is null) if (exception is null)
{ {
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["authorized_keys has been updated"].Value;
} }
else else
{ {
@ -1032,7 +1036,7 @@ namespace BTCPayServer.Controllers
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings(); var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
policies.DisableSSHService = true; policies.DisableSSHService = true;
await _SettingsRepository.UpdateSetting(policies); await _SettingsRepository.UpdateSetting(policies);
TempData[WellKnownTempData.SuccessMessage] = "Changes to the SSH settings are now permanently disabled in the BTCPay Server user interface"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Changes to the SSH settings are now permanently disabled in the BTCPay Server user interface"].Value;
return RedirectToAction(nameof(Services)); return RedirectToAction(nameof(Services));
} }
@ -1186,7 +1190,7 @@ namespace BTCPayServer.Controllers
if (settingsChanged) if (settingsChanged)
{ {
await _SettingsRepository.UpdateSetting(theme); await _SettingsRepository.UpdateSetting(theme);
TempData[WellKnownTempData.SuccessMessage] = "Settings updated successfully"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Settings updated successfully"].Value;
return RedirectToAction(nameof(Branding)); return RedirectToAction(nameof(Branding));
} }
@ -1229,7 +1233,7 @@ namespace BTCPayServer.Controllers
await client.SendAsync(message); await client.SendAsync(message);
await client.DisconnectAsync(true); await client.DisconnectAsync(true);
} }
TempData[WellKnownTempData.SuccessMessage] = $"Email sent to {model.TestEmail}. Please verify you received it."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Email sent to {0}. Please verify you received it.", model.TestEmail].Value;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1249,14 +1253,14 @@ namespace BTCPayServer.Controllers
var settings = await _SettingsRepository.GetSettingAsync<EmailSettings>() ?? new EmailSettings(); var settings = await _SettingsRepository.GetSettingAsync<EmailSettings>() ?? new EmailSettings();
settings.Password = null; settings.Password = null;
await _SettingsRepository.UpdateSetting(settings); await _SettingsRepository.UpdateSetting(settings);
TempData[WellKnownTempData.SuccessMessage] = "Email server password reset"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Email server password reset"].Value;
return RedirectToAction(nameof(Emails)); return RedirectToAction(nameof(Emails));
} }
// save // save
if (model.Settings.From is not null && !MailboxAddressValidator.IsMailboxAddress(model.Settings.From)) if (model.Settings.From is not null && !MailboxAddressValidator.IsMailboxAddress(model.Settings.From))
{ {
ModelState.AddModelError("Settings.From", "Invalid email"); ModelState.AddModelError("Settings.From", StringLocalizer["Invalid email"]);
return View(model); return View(model);
} }
var oldSettings = await _SettingsRepository.GetSettingAsync<EmailSettings>() ?? new EmailSettings(); var oldSettings = await _SettingsRepository.GetSettingAsync<EmailSettings>() ?? new EmailSettings();
@ -1266,7 +1270,7 @@ namespace BTCPayServer.Controllers
} }
await _SettingsRepository.UpdateSetting(model.Settings); await _SettingsRepository.UpdateSetting(model.Settings);
TempData[WellKnownTempData.SuccessMessage] = "Email settings saved"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Email settings saved"].Value;
return RedirectToAction(nameof(Emails)); return RedirectToAction(nameof(Emails));
} }
@ -1282,16 +1286,14 @@ namespace BTCPayServer.Controllers
if (string.IsNullOrEmpty(_Options.LogFile)) if (string.IsNullOrEmpty(_Options.LogFile))
{ {
TempData[WellKnownTempData.ErrorMessage] = "File Logging Option not specified. " + TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["File Logging Option not specified. You need to set debuglog and optionally debugloglevel in the configuration or through runtime arguments"].Value;
"You need to set debuglog and optionally " +
"debugloglevel in the configuration or through runtime arguments";
} }
else else
{ {
var di = Directory.GetParent(_Options.LogFile); var di = Directory.GetParent(_Options.LogFile);
if (di is null) if (di is null)
{ {
TempData[WellKnownTempData.ErrorMessage] = "Could not load log files"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Could not load log files"].Value;
return View("Logs", vm); return View("Logs", vm);
} }

View File

@ -91,7 +91,7 @@ namespace BTCPayServer.Controllers
{ {
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = StringLocalizer["You must enable at least one payment method before creating a pull payment."], Message = StringLocalizer["You must enable at least one payment method before creating a pull payment."].Value,
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId }); return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });
@ -162,7 +162,7 @@ namespace BTCPayServer.Controllers
}); });
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = StringLocalizer["Pull payment request created"], Message = StringLocalizer["Pull payment request created"].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
return RedirectToAction(nameof(PullPayments), new { storeId }); return RedirectToAction(nameof(PullPayments), new { storeId });
@ -204,7 +204,7 @@ namespace BTCPayServer.Controllers
{ {
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = StringLocalizer["You must enable at least one payment method before creating a pull payment."], Message = StringLocalizer["You must enable at least one payment method before creating a pull payment."].Value,
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId }); return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });
@ -275,9 +275,9 @@ namespace BTCPayServer.Controllers
string pullPaymentId) string pullPaymentId)
{ {
await _pullPaymentService.Cancel(new PullPaymentHostedService.CancelRequest(pullPaymentId)); await _pullPaymentService.Cancel(new PullPaymentHostedService.CancelRequest(pullPaymentId));
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "Pull payment archived", Message = StringLocalizer["Pull payment archived"].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
return RedirectToAction(nameof(PullPayments), new { storeId }); return RedirectToAction(nameof(PullPayments), new { storeId });
@ -302,9 +302,9 @@ namespace BTCPayServer.Controllers
var payoutIds = vm.GetSelectedPayouts(commandState); var payoutIds = vm.GetSelectedPayouts(commandState);
if (payoutIds.Length == 0) if (payoutIds.Length == 0)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = StringLocalizer["No payout selected"], Message = StringLocalizer["No payout selected"].Value,
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction(nameof(Payouts), return RedirectToAction(nameof(Payouts),
@ -345,9 +345,9 @@ namespace BTCPayServer.Controllers
var rateResult = await _pullPaymentService.GetRate(payout, null, cancellationToken); var rateResult = await _pullPaymentService.GetRate(payout, null, cancellationToken);
if (rateResult.BidAsk == null) if (rateResult.BidAsk == null)
{ {
this.TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Message = StringLocalizer["Rate unavailable: {0}", rateResult.EvaluatedRule], Message = StringLocalizer["Rate unavailable: {0}", rateResult.EvaluatedRule].Value,
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
failed = true; failed = true;
@ -355,7 +355,7 @@ namespace BTCPayServer.Controllers
} }
var approveResult = await _pullPaymentService.Approve( var approveResult = await _pullPaymentService.Approve(
new HostedServices.PullPaymentHostedService.PayoutApproval() new PullPaymentHostedService.PayoutApproval
{ {
PayoutId = payout.Id, PayoutId = payout.Id,
Revision = payout.GetBlob(_jsonSerializerSettings).Revision, Revision = payout.GetBlob(_jsonSerializerSettings).Revision,
@ -363,7 +363,7 @@ namespace BTCPayServer.Controllers
}); });
if (approveResult.Result != PullPaymentHostedService.PayoutApproval.Result.Ok) if (approveResult.Result != PullPaymentHostedService.PayoutApproval.Result.Ok)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = PullPaymentHostedService.PayoutApproval.GetErrorMessage(approveResult.Result), Message = PullPaymentHostedService.PayoutApproval.GetErrorMessage(approveResult.Result),
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
@ -383,9 +383,9 @@ namespace BTCPayServer.Controllers
goto case "pay"; goto case "pay";
} }
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = StringLocalizer["Payouts approved"], Message = StringLocalizer["Payouts approved"].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
break; break;
@ -395,9 +395,9 @@ namespace BTCPayServer.Controllers
{ {
if (handler is { }) if (handler is { })
return await handler.InitiatePayment(payoutIds); return await handler.InitiatePayment(payoutIds);
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = StringLocalizer["Paying via this payment method is not supported"], Message = StringLocalizer["Paying via this payment method is not supported"].Value,
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
break; break;
@ -416,10 +416,10 @@ namespace BTCPayServer.Controllers
continue; continue;
var result = var result =
await _pullPaymentService.MarkPaid(new MarkPayoutRequest() { PayoutId = payout.Id }); await _pullPaymentService.MarkPaid(new MarkPayoutRequest { PayoutId = payout.Id });
if (result != MarkPayoutRequest.PayoutPaidResult.Ok) if (result != MarkPayoutRequest.PayoutPaidResult.Ok)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = MarkPayoutRequest.GetErrorMessage(result), Message = MarkPayoutRequest.GetErrorMessage(result),
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
@ -434,9 +434,9 @@ namespace BTCPayServer.Controllers
} }
} }
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = StringLocalizer["Payouts marked as paid"], Message = StringLocalizer["Payouts marked as paid"].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
break; break;
@ -445,9 +445,9 @@ namespace BTCPayServer.Controllers
case "cancel": case "cancel":
await _pullPaymentService.Cancel( await _pullPaymentService.Cancel(
new PullPaymentHostedService.CancelRequest(payoutIds, new[] { storeId })); new PullPaymentHostedService.CancelRequest(payoutIds, new[] { storeId }));
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = StringLocalizer["Payouts archived"], Message = StringLocalizer["Payouts archived"].Value,
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
break; break;
@ -488,7 +488,7 @@ namespace BTCPayServer.Controllers
{ {
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = StringLocalizer["You must enable at least one payment method before creating a payout."], Message = StringLocalizer["You must enable at least one payment method before creating a payout."].Value,
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId }); return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });

View File

@ -35,7 +35,8 @@ public partial class UIStoresController
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Warning, Severity = StatusMessageModel.StatusSeverity.Warning,
Html = $"You need to configure email settings before this feature works. <a class='alert-link' href='{Url.Action("StoreEmailSettings", new { storeId })}'>Configure store email settings</a>." Html = "You need to configure email settings before this feature works." +
$" <a class='alert-link' href='{Url.Action("StoreEmailSettings", new { storeId })}'>Configure store email settings</a>."
}); });
} }
} }
@ -76,11 +77,11 @@ public partial class UIStoresController
.Any(s => !MailboxAddressValidator.TryParse(s, out _))) .Any(s => !MailboxAddressValidator.TryParse(s, out _)))
{ {
ModelState.AddModelError($"{nameof(vm.Rules)}[{i}].{nameof(rule.To)}", ModelState.AddModelError($"{nameof(vm.Rules)}[{i}].{nameof(rule.To)}",
"Invalid mailbox address provided. Valid formats are: 'test@example.com' or 'Firstname Lastname <test@example.com>'"); StringLocalizer["Invalid mailbox address provided. Valid formats are: '{0}' or '{1}'", "test@example.com", "Firstname Lastname <test@example.com>"]);
} }
else if (!rule.CustomerEmail && string.IsNullOrEmpty(rule.To)) else if (!rule.CustomerEmail && string.IsNullOrEmpty(rule.To))
ModelState.AddModelError($"{nameof(vm.Rules)}[{i}].{nameof(rule.To)}", ModelState.AddModelError($"{nameof(vm.Rules)}[{i}].{nameof(rule.To)}",
"Either recipient or \"Send the email to the buyer\" is required"); StringLocalizer["Either recipient or \"Send the email to the buyer\" is required"]);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
@ -101,7 +102,7 @@ public partial class UIStoresController
if (store.SetStoreBlob(blob)) if (store.SetStoreBlob(blob))
{ {
await _storeRepo.UpdateStore(store); await _storeRepo.UpdateStore(store);
message += "Store email rules saved. "; message += StringLocalizer["Store email rules saved."] + " ";
} }
if (command.StartsWith("test", StringComparison.InvariantCultureIgnoreCase)) if (command.StartsWith("test", StringComparison.InvariantCultureIgnoreCase))
@ -122,16 +123,16 @@ public partial class UIStoresController
.ToArray(); .ToArray();
emailSender.SendEmail(recipients.ToArray(), null, null, $"[TEST] {rule.Subject}", rule.Body); emailSender.SendEmail(recipients.ToArray(), null, null, $"[TEST] {rule.Subject}", rule.Body);
message += "Test email sent — please verify you received it."; message += StringLocalizer["Test email sent — please verify you received it."];
} }
else else
{ {
message += "Complete the email setup to send test emails."; message += StringLocalizer["Complete the email setup to send test emails."];
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
TempData[WellKnownTempData.ErrorMessage] = message + "Error sending test email: " + ex.Message; TempData[WellKnownTempData.ErrorMessage] = message + StringLocalizer["Error sending test email: {0}", ex.Message].Value;
return RedirectToAction("StoreEmails", new { storeId }); return RedirectToAction("StoreEmails", new { storeId });
} }
} }
@ -222,14 +223,14 @@ public partial class UIStoresController
return View(model); return View(model);
var settings = useCustomSMTP ? model.Settings : model.FallbackSettings; var settings = useCustomSMTP ? model.Settings : model.FallbackSettings;
using var client = await settings.CreateSmtpClient(); using var client = await settings.CreateSmtpClient();
var message = settings.CreateMailMessage(MailboxAddress.Parse(model.TestEmail), $"{store.StoreName}: Email test", "You received it, the BTCPay Server SMTP settings work.", false); var message = settings.CreateMailMessage(MailboxAddress.Parse(model.TestEmail), $"{store.StoreName}: Email test", StringLocalizer["You received it, the BTCPay Server SMTP settings work."], false);
await client.SendAsync(message); await client.SendAsync(message);
await client.DisconnectAsync(true); await client.DisconnectAsync(true);
TempData[WellKnownTempData.SuccessMessage] = $"Email sent to {model.TestEmail}. Please verify you received it."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Email sent to {0}. Please verify you received it.", model.TestEmail].Value;
} }
catch (Exception ex) catch (Exception ex)
{ {
TempData[WellKnownTempData.ErrorMessage] = "Error: " + ex.Message; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Error: {0}", ex.Message].Value;
} }
return View(model); return View(model);
} }
@ -239,13 +240,13 @@ public partial class UIStoresController
storeBlob.EmailSettings.Password = null; storeBlob.EmailSettings.Password = null;
store.SetStoreBlob(storeBlob); store.SetStoreBlob(storeBlob);
await _storeRepo.UpdateStore(store); await _storeRepo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = "Email server password reset"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Email server password reset"].Value;
} }
if (useCustomSMTP) if (useCustomSMTP)
{ {
if (model.Settings.From is not null && !MailboxAddressValidator.IsMailboxAddress(model.Settings.From)) if (model.Settings.From is not null && !MailboxAddressValidator.IsMailboxAddress(model.Settings.From))
{ {
ModelState.AddModelError("Settings.From", "Invalid email"); ModelState.AddModelError("Settings.From", StringLocalizer["Invalid email"]);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
return View(model); return View(model);
@ -257,7 +258,7 @@ public partial class UIStoresController
storeBlob.EmailSettings = model.Settings; storeBlob.EmailSettings = model.Settings;
store.SetStoreBlob(storeBlob); store.SetStoreBlob(storeBlob);
await _storeRepo.UpdateStore(store); await _storeRepo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = "Email settings modified"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Email settings modified"].Value;
} }
return RedirectToAction(nameof(StoreEmailSettings), new { storeId }); return RedirectToAction(nameof(StoreEmailSettings), new { storeId });
} }

View File

@ -67,7 +67,7 @@ public partial class UIStoresController
if (webhook is null) if (webhook is null)
return NotFound(); return NotFound();
return View("Confirm", new ConfirmModel("Delete webhook", "This webhook will be removed from this store. Are you sure?", "Delete")); return View("Confirm", new ConfirmModel(StringLocalizer["Delete webhook"], StringLocalizer["This webhook will be removed from this store. Are you sure?"], StringLocalizer["Delete"]));
} }
[HttpPost("{storeId}/webhooks/{webhookId}/remove")] [HttpPost("{storeId}/webhooks/{webhookId}/remove")]
@ -79,7 +79,7 @@ public partial class UIStoresController
return NotFound(); return NotFound();
await _storeRepo.DeleteWebhook(CurrentStore.Id, webhookId); await _storeRepo.DeleteWebhook(CurrentStore.Id, webhookId);
TempData[WellKnownTempData.SuccessMessage] = "Webhook successfully deleted"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Webhook successfully deleted"].Value;
return RedirectToAction(nameof(Webhooks), new { storeId = CurrentStore.Id }); return RedirectToAction(nameof(Webhooks), new { storeId = CurrentStore.Id });
} }
@ -91,7 +91,7 @@ public partial class UIStoresController
return View(nameof(ModifyWebhook), viewModel); return View(nameof(ModifyWebhook), viewModel);
await _storeRepo.CreateWebhook(CurrentStore.Id, viewModel.CreateBlob()); await _storeRepo.CreateWebhook(CurrentStore.Id, viewModel.CreateBlob());
TempData[WellKnownTempData.SuccessMessage] = "The webhook has been created"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The webhook has been created"].Value;
return RedirectToAction(nameof(Webhooks), new { storeId }); return RedirectToAction(nameof(Webhooks), new { storeId });
} }
@ -123,7 +123,7 @@ public partial class UIStoresController
return View(nameof(ModifyWebhook), viewModel); return View(nameof(ModifyWebhook), viewModel);
await _storeRepo.UpdateWebhook(CurrentStore.Id, webhookId, viewModel.CreateBlob()); await _storeRepo.UpdateWebhook(CurrentStore.Id, webhookId, viewModel.CreateBlob());
TempData[WellKnownTempData.SuccessMessage] = "The webhook has been updated"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The webhook has been updated"].Value;
return RedirectToAction(nameof(Webhooks), new { storeId = CurrentStore.Id }); return RedirectToAction(nameof(Webhooks), new { storeId = CurrentStore.Id });
} }
@ -146,11 +146,11 @@ public partial class UIStoresController
if (result.Success) if (result.Success)
{ {
TempData[WellKnownTempData.SuccessMessage] = $"{viewModel.Type} event delivered successfully! Delivery ID is {result.DeliveryId}"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["{0} event delivered successfully! Delivery ID is {1}", viewModel.Type, result.DeliveryId!].Value;
} }
else else
{ {
TempData[WellKnownTempData.ErrorMessage] = $"{viewModel.Type} event could not be delivered. Error message received: {(result.ErrorMessage ?? "unknown")}"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["{0} event could not be delivered. Error message received: {1}", viewModel.Type, result.ErrorMessage ?? StringLocalizer["unknown"].Value].Value;
} }
return View(nameof(TestWebhook)); return View(nameof(TestWebhook));
@ -168,7 +168,7 @@ public partial class UIStoresController
if (newDeliveryId is null) if (newDeliveryId is null)
return NotFound(); return NotFound();
TempData[WellKnownTempData.SuccessMessage] = "Successfully planned a redelivery"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Successfully planned a redelivery"].Value;
return RedirectToAction(nameof(ModifyWebhook), return RedirectToAction(nameof(ModifyWebhook),
new new
{ {

View File

@ -115,7 +115,7 @@ public partial class UIStoresController
if (vm.CryptoCode == null) if (vm.CryptoCode == null)
{ {
ModelState.AddModelError(nameof(vm.CryptoCode), "Invalid network"); ModelState.AddModelError(nameof(vm.CryptoCode), StringLocalizer["Invalid network"]);
return View(vm); return View(vm);
} }
@ -132,7 +132,7 @@ public partial class UIStoresController
{ {
if (string.IsNullOrEmpty(vm.ConnectionString)) if (string.IsNullOrEmpty(vm.ConnectionString))
{ {
ModelState.AddModelError(nameof(vm.ConnectionString), "Please provide a connection string"); ModelState.AddModelError(nameof(vm.ConnectionString), StringLocalizer["Please provide a connection string"]);
return View(vm); return View(vm);
} }
paymentMethod = new LightningPaymentMethodConfig { ConnectionString = vm.ConnectionString }; paymentMethod = new LightningPaymentMethodConfig { ConnectionString = vm.ConnectionString };
@ -143,7 +143,7 @@ public partial class UIStoresController
JToken.FromObject(paymentMethod, handler.Serializer), User, oldConf is null ? null : JToken.FromObject(oldConf, handler.Serializer)); JToken.FromObject(paymentMethod, handler.Serializer), User, oldConf is null ? null : JToken.FromObject(oldConf, handler.Serializer));
await handler.ValidatePaymentMethodConfig(ctx); await handler.ValidatePaymentMethodConfig(ctx);
if (ctx.MissingPermission is not null) if (ctx.MissingPermission is not null)
ModelState.AddModelError(nameof(vm.ConnectionString), "You do not have the permissions to change this settings"); ModelState.AddModelError(nameof(vm.ConnectionString), StringLocalizer["You do not have the permissions to change this settings"]);
if (!ModelState.IsValid) if (!ModelState.IsValid)
return View(vm); return View(vm);
@ -159,7 +159,7 @@ public partial class UIStoresController
}); });
await _storeRepo.UpdateStore(store); await _storeRepo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning node updated."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["{0} Lightning node updated.", network.CryptoCode].Value;
return RedirectToAction(nameof(LightningSettings), new { storeId, cryptoCode }); return RedirectToAction(nameof(LightningSettings), new { storeId, cryptoCode });
case "test": case "test":
@ -172,9 +172,10 @@ public partial class UIStoresController
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20)); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
await handler.TestConnection(info.First(), cts.Token); await handler.TestConnection(info.First(), cts.Token);
} }
TempData[WellKnownTempData.SuccessMessage] = "Connection to the Lightning node successful" + (hasPublicAddress TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Connection to the Lightning node successful."].Value + " " +
? $". Your node address: {info.First()}" (hasPublicAddress
: ", but no public address has been configured"); ? StringLocalizer["Your node address: {0}", info.First()].Value
: StringLocalizer["No public address has been configured."].Value);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -202,7 +203,7 @@ public partial class UIStoresController
var lightning = GetConfig<LightningPaymentMethodConfig>(lnId, store); var lightning = GetConfig<LightningPaymentMethodConfig>(lnId, store);
if (lightning == null) if (lightning == null)
{ {
TempData[WellKnownTempData.ErrorMessage] = "You need to connect to a Lightning node before adjusting its settings."; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["You need to connect to a Lightning node before adjusting its settings."].Value;
return RedirectToAction(nameof(SetupLightningNode), new { storeId, cryptoCode }); return RedirectToAction(nameof(SetupLightningNode), new { storeId, cryptoCode });
} }
@ -241,7 +242,7 @@ public partial class UIStoresController
if (vm.CryptoCode == null) if (vm.CryptoCode == null)
{ {
ModelState.AddModelError(nameof(vm.CryptoCode), "Invalid network"); ModelState.AddModelError(nameof(vm.CryptoCode), StringLocalizer["Invalid network"]);
return View(vm); return View(vm);
} }
@ -289,7 +290,7 @@ public partial class UIStoresController
{ {
await _storeRepo.UpdateStore(store); await _storeRepo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning settings successfully updated."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["{0} Lightning settings successfully updated.", network.CryptoCode].Value;
} }
return RedirectToAction(nameof(LightningSettings), new { vm.StoreId, vm.CryptoCode }); return RedirectToAction(nameof(LightningSettings), new { vm.StoreId, vm.CryptoCode });

View File

@ -108,7 +108,7 @@ public partial class UIStoresController
if (fileContent is null || !_onChainWalletParsers.TryParseWalletFile(fileContent, network, out strategy, out _)) if (fileContent is null || !_onChainWalletParsers.TryParseWalletFile(fileContent, network, out strategy, out _))
{ {
ModelState.AddModelError(nameof(vm.WalletFile), $"Import failed, make sure you import a compatible wallet format"); ModelState.AddModelError(nameof(vm.WalletFile), StringLocalizer["Import failed, make sure you import a compatible wallet format"]);
return View(vm.ViewName, vm); return View(vm.ViewName, vm);
} }
} }
@ -116,7 +116,7 @@ public partial class UIStoresController
{ {
if (!_onChainWalletParsers.TryParseWalletFile(vm.WalletFileContent, network, out strategy, out var error)) if (!_onChainWalletParsers.TryParseWalletFile(vm.WalletFileContent, network, out strategy, out var error))
{ {
ModelState.AddModelError(nameof(vm.WalletFileContent), $"QR import failed: {error}"); ModelState.AddModelError(nameof(vm.WalletFileContent), StringLocalizer["QR import failed: {0}", error]);
return View(vm.ViewName, vm); return View(vm.ViewName, vm);
} }
} }
@ -145,7 +145,7 @@ public partial class UIStoresController
} }
catch (Exception ex) catch (Exception ex)
{ {
ModelState.AddModelError(nameof(vm.DerivationScheme), $"Invalid wallet format: {ex.Message}"); ModelState.AddModelError(nameof(vm.DerivationScheme), StringLocalizer["Invalid wallet format: {0}", ex.Message]);
return View(vm.ViewName, vm); return View(vm.ViewName, vm);
} }
} }
@ -157,14 +157,14 @@ public partial class UIStoresController
} }
catch catch
{ {
ModelState.AddModelError(nameof(vm.Config), "Config file was not in the correct format"); ModelState.AddModelError(nameof(vm.Config), StringLocalizer["Config file was not in the correct format"]);
return View(vm.ViewName, vm); return View(vm.ViewName, vm);
} }
} }
if (strategy is null) if (strategy is null)
{ {
ModelState.AddModelError(nameof(vm.DerivationScheme), "Please provide your extended public key"); ModelState.AddModelError(nameof(vm.DerivationScheme), StringLocalizer["Please provide your extended public key"]);
return View(vm.ViewName, vm); return View(vm.ViewName, vm);
} }
@ -184,13 +184,13 @@ public partial class UIStoresController
} }
catch catch
{ {
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid derivation scheme"); ModelState.AddModelError(nameof(vm.DerivationScheme), StringLocalizer["Invalid derivation scheme"]);
return View(vm.ViewName, vm); return View(vm.ViewName, vm);
} }
await _storeRepo.UpdateStore(store); await _storeRepo.UpdateStore(store);
_eventAggregator.Publish(new WalletChangedEvent { WalletId = new WalletId(vm.StoreId, vm.CryptoCode) }); _eventAggregator.Publish(new WalletChangedEvent { WalletId = new WalletId(vm.StoreId, vm.CryptoCode) });
TempData[WellKnownTempData.SuccessMessage] = $"Wallet settings for {network.CryptoCode} have been updated."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Wallet settings for {0} have been updated.", network.CryptoCode].Value;
// This is success case when derivation scheme is added to the store // This is success case when derivation scheme is added to the store
return RedirectToAction(nameof(WalletSettings), new { storeId = vm.StoreId, cryptoCode = vm.CryptoCode }); return RedirectToAction(nameof(WalletSettings), new { storeId = vm.StoreId, cryptoCode = vm.CryptoCode });
@ -287,7 +287,7 @@ public partial class UIStoresController
if (isImport && string.IsNullOrEmpty(request.ExistingMnemonic)) if (isImport && string.IsNullOrEmpty(request.ExistingMnemonic))
{ {
ModelState.AddModelError(nameof(request.ExistingMnemonic), "Please provide your existing seed"); ModelState.AddModelError(nameof(request.ExistingMnemonic), StringLocalizer["Please provide your existing seed"]);
return View(vm.ViewName, vm); return View(vm.ViewName, vm);
} }
@ -305,7 +305,7 @@ public partial class UIStoresController
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Html = $"There was an error generating your wallet: {e.Message}" Message = StringLocalizer["There was an error generating your wallet: {0}", e.Message].Value
}); });
return View(vm.ViewName, vm); return View(vm.ViewName, vm);
} }
@ -343,7 +343,7 @@ public partial class UIStoresController
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Html = "<span class='text-centered'>Your wallet has been generated.</span>" Html = "<span class='text-centered'>" + StringLocalizer["Your wallet has been generated."].Value + "</span>"
}); });
var seedVm = new RecoverySeedBackupViewModel var seedVm = new RecoverySeedBackupViewModel
{ {
@ -363,7 +363,7 @@ public partial class UIStoresController
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Warning, Severity = StatusMessageModel.StatusSeverity.Warning,
Html = "Please check your addresses and confirm." Message = StringLocalizer["Please check your addresses and confirm."].Value
}); });
return result; return result;
} }
@ -380,7 +380,7 @@ public partial class UIStoresController
return checkResult; return checkResult;
} }
TempData[WellKnownTempData.SuccessMessage] = $"Wallet settings for {network.CryptoCode} have been updated."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Wallet settings for {0} have been updated.", network.CryptoCode].Value;
var walletId = new WalletId(storeId, cryptoCode); var walletId = new WalletId(storeId, cryptoCode);
return RedirectToAction(nameof(UIWalletsController.WalletTransactions), "UIWallets", new { walletId }); return RedirectToAction(nameof(UIWalletsController.WalletTransactions), "UIWallets", new { walletId });
@ -608,7 +608,7 @@ public partial class UIStoresController
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = "The seed was not found" Message = StringLocalizer["The seed was not found"].Value
}); });
return RedirectToAction(nameof(WalletSettings)); return RedirectToAction(nameof(WalletSettings));
@ -628,9 +628,9 @@ public partial class UIStoresController
return View("Confirm", new ConfirmModel return View("Confirm", new ConfirmModel
{ {
Title = $"Replace {network.CryptoCode} wallet", Title = StringLocalizer["Replace {0} wallet", network.CryptoCode],
Description = WalletReplaceWarning(derivation.IsHotWallet), Description = WalletReplaceWarning(derivation.IsHotWallet),
Action = "Setup new wallet" Action = StringLocalizer["Setup new wallet"]
}); });
} }
@ -667,9 +667,9 @@ public partial class UIStoresController
return View("Confirm", new ConfirmModel return View("Confirm", new ConfirmModel
{ {
Title = $"Remove {network.CryptoCode} wallet", Title = StringLocalizer["Remove {0} wallet", network.CryptoCode],
Description = WalletRemoveWarning(derivation.IsHotWallet, network.CryptoCode), Description = WalletRemoveWarning(derivation.IsHotWallet, network.CryptoCode),
Action = "Remove" Action = StringLocalizer["Remove"]
}); });
} }

View File

@ -52,7 +52,7 @@ public partial class UIStoresController
} }
catch catch
{ {
ModelState.AddModelError(nameof(model.DefaultCurrencyPairs), "Invalid currency pairs (should be for example: BTC_USD,BTC_CAD,BTC_JPY)"); ModelState.AddModelError(nameof(model.DefaultCurrencyPairs), StringLocalizer["Invalid currency pairs (should be for example: {0})", "BTC_USD,BTC_CAD,BTC_JPY"]);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
@ -71,7 +71,7 @@ public partial class UIStoresController
{ {
errors ??= []; errors ??= [];
var errorString = string.Join(", ", errors.ToArray()); var errorString = string.Join(", ", errors.ToArray());
ModelState.AddModelError(nameof(model.Script), $"Parsing error ({errorString})"); ModelState.AddModelError(nameof(model.Script), StringLocalizer["Parsing error: {0}", errorString]);
FillFromStore(model, blob); FillFromStore(model, blob);
return View(model); return View(model);
} }
@ -90,7 +90,7 @@ public partial class UIStoresController
{ {
if (string.IsNullOrWhiteSpace(model.ScriptTest)) if (string.IsNullOrWhiteSpace(model.ScriptTest))
{ {
ModelState.AddModelError(nameof(model.ScriptTest), "Fill out currency pair to test for (like BTC_USD,BTC_CAD)"); ModelState.AddModelError(nameof(model.ScriptTest), StringLocalizer["Fill out currency pair to test for (like {0})", "BTC_USD,BTC_CAD"]);
return View(model); return View(model);
} }
var splitted = model.ScriptTest.Split(',', StringSplitOptions.RemoveEmptyEntries); var splitted = model.ScriptTest.Split(',', StringSplitOptions.RemoveEmptyEntries);
@ -100,7 +100,7 @@ public partial class UIStoresController
{ {
if (!CurrencyPair.TryParse(pair, out var currencyPair)) if (!CurrencyPair.TryParse(pair, out var currencyPair))
{ {
ModelState.AddModelError(nameof(model.ScriptTest), $"Invalid currency pair '{pair}' (it should be formatted like BTC_USD,BTC_CAD)"); ModelState.AddModelError(nameof(model.ScriptTest), StringLocalizer["Invalid currency pair '{0}' (it should be formatted like {1})", pair, "BTC_USD,BTC_CAD"]);
return View(model); return View(model);
} }
pairs.Add(currencyPair); pairs.Add(currencyPair);
@ -125,7 +125,7 @@ public partial class UIStoresController
if (model.PreferredExchange is not null && !model.AvailableExchanges.Any(a => a.Id == model.PreferredExchange)) if (model.PreferredExchange is not null && !model.AvailableExchanges.Any(a => a.Id == model.PreferredExchange))
{ {
ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange"); ModelState.AddModelError(nameof(model.PreferredExchange), StringLocalizer["Unsupported exchange"]);
return View(model); return View(model);
} }
@ -147,11 +147,11 @@ public partial class UIStoresController
{ {
return View("Confirm", new ConfirmModel return View("Confirm", new ConfirmModel
{ {
Action = "Continue", Action = StringLocalizer["Continue"],
Title = "Rate rule scripting", Title = StringLocalizer["Rate rule scripting"],
Description = scripting ? Description = scripting
"This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)" ? StringLocalizer["This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)"]
: "This action will delete your rate script. Are you sure to turn off rate rules scripting?", : StringLocalizer["This action will delete your rate script. Are you sure to turn off rate rules scripting?"],
ButtonClass = scripting ? "btn-primary" : "btn-danger" ButtonClass = scripting ? "btn-primary" : "btn-danger"
}); });
} }

View File

@ -77,13 +77,13 @@ public partial class UIStoresController
StoreRoleId roleId; StoreRoleId roleId;
if (role == "create") if (role == "create")
{ {
successMessage = "Role created"; successMessage = StringLocalizer["Role created"];
role = viewModel.Role; role = viewModel.Role;
roleId = new StoreRoleId(storeId, role); roleId = new StoreRoleId(storeId, role);
} }
else else
{ {
successMessage = "Role updated"; successMessage = StringLocalizer["Role updated"];
roleId = new StoreRoleId(storeId, role); roleId = new StoreRoleId(storeId, role);
var storeRole = await storeRepository.GetStoreRole(roleId); var storeRole = await storeRepository.GetStoreRole(roleId);
if (storeRole == null) if (storeRole == null)
@ -98,15 +98,15 @@ public partial class UIStoresController
var r = await storeRepository.AddOrUpdateStoreRole(roleId, viewModel.Policies); var r = await storeRepository.AddOrUpdateStoreRole(roleId, viewModel.Policies);
if (r is null) if (r is null)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Role could not be updated" Message = StringLocalizer["Role could not be updated"].Value
}); });
return View(viewModel); return View(viewModel);
} }
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = successMessage Message = successMessage
@ -128,11 +128,11 @@ public partial class UIStoresController
return View("Confirm", return View("Confirm",
roleData.IsUsed is true roleData.IsUsed is true
? new ConfirmModel("Delete role", ? new ConfirmModel(StringLocalizer["Delete role"],
$"Unable to proceed: The role <strong>{_html.Encode(roleData.Role)}</strong> is currently assigned to one or more users, it cannot be removed.") $"Unable to proceed: The role <strong>{_html.Encode(roleData.Role)}</strong> is currently assigned to one or more users, it cannot be removed.")
: new ConfirmModel("Delete role", : new ConfirmModel(StringLocalizer["Delete role"],
$"The role <strong>{_html.Encode(roleData.Role)}</strong> will be permanently deleted. Are you sure?", $"The role <strong>{_html.Encode(roleData.Role)}</strong> will be permanently deleted. Are you sure?",
"Delete")); StringLocalizer["Delete"]));
} }
[HttpPost("{storeId}/roles/{role}/delete")] [HttpPost("{storeId}/roles/{role}/delete")]
@ -152,7 +152,7 @@ public partial class UIStoresController
} }
await storeRepository.RemoveStoreRole(roleId); await storeRepository.RemoveStoreRole(roleId);
TempData[WellKnownTempData.SuccessMessage] = "Role deleted"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Role deleted"].Value;
return RedirectToAction(nameof(ListRoles), new { storeId }); return RedirectToAction(nameof(ListRoles), new { storeId });
} }
} }

View File

@ -89,7 +89,7 @@ public partial class UIStoresController
blob.MonitoringExpiration = TimeSpan.FromMinutes(model.MonitoringExpiration); blob.MonitoringExpiration = TimeSpan.FromMinutes(model.MonitoringExpiration);
if (!string.IsNullOrEmpty(model.BrandColor) && !ColorPalette.IsValid(model.BrandColor)) if (!string.IsNullOrEmpty(model.BrandColor) && !ColorPalette.IsValid(model.BrandColor))
{ {
ModelState.AddModelError(nameof(model.BrandColor), "The brand color needs to be a valid hex color code"); ModelState.AddModelError(nameof(model.BrandColor), StringLocalizer["The brand color needs to be a valid hex color code"]);
return View(model); return View(model);
} }
blob.BrandColor = model.BrandColor; blob.BrandColor = model.BrandColor;
@ -103,18 +103,18 @@ public partial class UIStoresController
{ {
if (model.LogoFile.Length > 1_000_000) if (model.LogoFile.Length > 1_000_000)
{ {
ModelState.AddModelError(nameof(model.LogoFile), "The uploaded logo file should be less than 1MB"); ModelState.AddModelError(nameof(model.LogoFile), StringLocalizer["The uploaded logo file should be less than {0}", "1MB"]);
} }
else if (!model.LogoFile.ContentType.StartsWith("image/", StringComparison.InvariantCulture)) else if (!model.LogoFile.ContentType.StartsWith("image/", StringComparison.InvariantCulture))
{ {
ModelState.AddModelError(nameof(model.LogoFile), "The uploaded logo file needs to be an image"); ModelState.AddModelError(nameof(model.LogoFile), StringLocalizer["The uploaded logo file needs to be an image"]);
} }
else else
{ {
var formFile = await model.LogoFile.Bufferize(); var formFile = await model.LogoFile.Bufferize();
if (!FileTypeDetector.IsPicture(formFile.Buffer, formFile.FileName)) if (!FileTypeDetector.IsPicture(formFile.Buffer, formFile.FileName))
{ {
ModelState.AddModelError(nameof(model.LogoFile), "The uploaded logo file needs to be an image"); ModelState.AddModelError(nameof(model.LogoFile), StringLocalizer["The uploaded logo file needs to be an image"]);
} }
else else
{ {
@ -127,7 +127,7 @@ public partial class UIStoresController
} }
catch (Exception e) catch (Exception e)
{ {
ModelState.AddModelError(nameof(model.LogoFile), $"Could not save logo: {e.Message}"); ModelState.AddModelError(nameof(model.LogoFile), StringLocalizer["Could not save logo: {0}", e.Message]);
} }
} }
} }
@ -142,15 +142,15 @@ public partial class UIStoresController
{ {
if (model.CssFile.Length > 1_000_000) if (model.CssFile.Length > 1_000_000)
{ {
ModelState.AddModelError(nameof(model.CssFile), "The uploaded file should be less than 1MB"); ModelState.AddModelError(nameof(model.CssFile), StringLocalizer["The uploaded file should be less than {0}", "1MB"]);
} }
else if (!model.CssFile.ContentType.Equals("text/css", StringComparison.InvariantCulture)) else if (!model.CssFile.ContentType.Equals("text/css", StringComparison.InvariantCulture))
{ {
ModelState.AddModelError(nameof(model.CssFile), "The uploaded file needs to be a CSS file"); ModelState.AddModelError(nameof(model.CssFile), StringLocalizer["The uploaded file needs to be a CSS file"]);
} }
else if (!model.CssFile.FileName.EndsWith(".css", StringComparison.OrdinalIgnoreCase)) else if (!model.CssFile.FileName.EndsWith(".css", StringComparison.OrdinalIgnoreCase))
{ {
ModelState.AddModelError(nameof(model.CssFile), "The uploaded file needs to be a CSS file"); ModelState.AddModelError(nameof(model.CssFile), StringLocalizer["The uploaded file needs to be a CSS file"]);
} }
else else
{ {
@ -162,7 +162,7 @@ public partial class UIStoresController
} }
catch (Exception e) catch (Exception e)
{ {
ModelState.AddModelError(nameof(model.CssFile), $"Could not save CSS file: {e.Message}"); ModelState.AddModelError(nameof(model.CssFile), StringLocalizer["Could not save CSS file: {0}", e.Message]);
} }
} }
} }
@ -211,7 +211,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult DeleteStore(string storeId) public IActionResult DeleteStore(string storeId)
{ {
return View("Confirm", new ConfirmModel("Delete store", "The store will be permanently deleted. This action will also delete all invoices, apps and data associated with the store. Are you sure?", "Delete")); return View("Confirm", new ConfirmModel(StringLocalizer["Delete store"], StringLocalizer["The store will be permanently deleted. This action will also delete all invoices, apps and data associated with the store. Are you sure?"], StringLocalizer["Delete"]));
} }
[HttpPost("{storeId}/delete")] [HttpPost("{storeId}/delete")]
@ -305,18 +305,18 @@ public partial class UIStoresController
{ {
if (model.SoundFile.Length > 1_000_000) if (model.SoundFile.Length > 1_000_000)
{ {
ModelState.AddModelError(nameof(model.SoundFile), "The uploaded sound file should be less than 1MB"); ModelState.AddModelError(nameof(model.SoundFile), StringLocalizer["The uploaded sound file should be less than {0}", "1MB"]);
} }
else if (!model.SoundFile.ContentType.StartsWith("audio/", StringComparison.InvariantCulture)) else if (!model.SoundFile.ContentType.StartsWith("audio/", StringComparison.InvariantCulture))
{ {
ModelState.AddModelError(nameof(model.SoundFile), "The uploaded sound file needs to be an audio file"); ModelState.AddModelError(nameof(model.SoundFile), StringLocalizer["The uploaded sound file needs to be an audio file"]);
} }
else else
{ {
var formFile = await model.SoundFile.Bufferize(); var formFile = await model.SoundFile.Bufferize();
if (!FileTypeDetector.IsAudio(formFile.Buffer, formFile.FileName)) if (!FileTypeDetector.IsAudio(formFile.Buffer, formFile.FileName))
{ {
ModelState.AddModelError(nameof(model.SoundFile), "The uploaded sound file needs to be an audio file"); ModelState.AddModelError(nameof(model.SoundFile), StringLocalizer["The uploaded sound file needs to be an audio file"]);
} }
else else
{ {
@ -330,7 +330,7 @@ public partial class UIStoresController
} }
catch (Exception e) catch (Exception e)
{ {
ModelState.AddModelError(nameof(model.SoundFile), $"Could not save sound: {e.Message}"); ModelState.AddModelError(nameof(model.SoundFile), StringLocalizer["Could not save sound: {0}", e.Message]);
} }
} }
} }

View File

@ -44,7 +44,7 @@ public partial class UIStoresController
var token = await _tokenRepository.GetToken(tokenId); var token = await _tokenRepository.GetToken(tokenId);
if (token == null || token.StoreId != CurrentStore.Id) if (token == null || token.StoreId != CurrentStore.Id)
return NotFound(); return NotFound();
return View("Confirm", new ConfirmModel("Revoke the token", $"The access token with the label <strong>{_html.Encode(token.Label)}</strong> will be revoked. Do you wish to continue?", "Revoke")); return View("Confirm", new ConfirmModel(StringLocalizer["Revoke the token"], $"The access token with the label <strong>{_html.Encode(token.Label)}</strong> will be revoked. Do you wish to continue?", "Revoke"));
} }
[HttpPost("{storeId}/tokens/{tokenId}/revoke")] [HttpPost("{storeId}/tokens/{tokenId}/revoke")]
@ -243,14 +243,14 @@ public partial class UIStoresController
StoreNotConfigured = store.GetPaymentMethodConfigs(_handlers).All(p => excludeFilter.Match(p.Key)); StoreNotConfigured = store.GetPaymentMethodConfigs(_handlers).All(p => excludeFilter.Match(p.Key));
TempData[WellKnownTempData.SuccessMessage] = "Pairing is successful"; TempData[WellKnownTempData.SuccessMessage] = "Pairing is successful";
if (pairingResult == PairingResult.Partial) if (pairingResult == PairingResult.Partial)
TempData[WellKnownTempData.SuccessMessage] = "Server initiated pairing code: " + pairingCode; TempData[WellKnownTempData.SuccessMessage] = $"Server initiated pairing code: {pairingCode}";
return RedirectToAction(nameof(ListTokens), new return RedirectToAction(nameof(ListTokens), new
{ {
storeId = store.Id, pairingCode storeId = store.Id, pairingCode
}); });
} }
TempData[WellKnownTempData.ErrorMessage] = $"Pairing failed ({pairingResult})"; TempData[WellKnownTempData.ErrorMessage] = $"Pairing failed: {pairingResult}";
return RedirectToAction(nameof(ListTokens), new return RedirectToAction(nameof(ListTokens), new
{ {
storeId = store.Id storeId = store.Id

View File

@ -39,7 +39,7 @@ public partial class UIStoresController
var roles = await _storeRepo.GetStoreRoles(CurrentStore.Id); var roles = await _storeRepo.GetStoreRoles(CurrentStore.Id);
if (roles.All(role => role.Id != vm.Role)) if (roles.All(role => role.Id != vm.Role))
{ {
ModelState.AddModelError(nameof(vm.Role), "Invalid role"); ModelState.AddModelError(nameof(vm.Role), StringLocalizer["Invalid role"]);
return View(vm); return View(vm);
} }
@ -116,9 +116,9 @@ public partial class UIStoresController
var isOwner = user.StoreRole.Id == StoreRoleId.Owner.Id; var isOwner = user.StoreRole.Id == StoreRoleId.Owner.Id;
var isLastOwner = isOwner && storeUsers.Count(u => u.StoreRole.Id == StoreRoleId.Owner.Id) == 1; var isLastOwner = isOwner && storeUsers.Count(u => u.StoreRole.Id == StoreRoleId.Owner.Id) == 1;
if (isLastOwner && roleId != StoreRoleId.Owner) if (isLastOwner && roleId != StoreRoleId.Owner)
TempData[WellKnownTempData.ErrorMessage] = $"User {user.Email} is the last owner. Their role cannot be changed."; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["User {0} is the last owner. Their role cannot be changed.", user.Email].Value;
else if (await _storeRepo.AddOrUpdateStoreUser(storeId, userId, roleId)) else if (await _storeRepo.AddOrUpdateStoreUser(storeId, userId, roleId))
TempData[WellKnownTempData.SuccessMessage] = $"The role of {user.Email} has been changed to {vm.Role}."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The role of {0} has been changed to {1}.", user.Email, vm.Role].Value;
return RedirectToAction(nameof(StoreUsers), new { storeId, userId }); return RedirectToAction(nameof(StoreUsers), new { storeId, userId });
} }
@ -127,9 +127,9 @@ public partial class UIStoresController
public async Task<IActionResult> DeleteStoreUser(string storeId, string userId) public async Task<IActionResult> DeleteStoreUser(string storeId, string userId)
{ {
if (await _storeRepo.RemoveStoreUser(storeId, userId)) if (await _storeRepo.RemoveStoreUser(storeId, userId))
TempData[WellKnownTempData.SuccessMessage] = "User removed successfully."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["User removed successfully."].Value;
else else
TempData[WellKnownTempData.ErrorMessage] = "Removing this user would result in the store having no owner."; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Removing this user would result in the store having no owner."].Value;
return RedirectToAction(nameof(StoreUsers), new { storeId, userId }); return RedirectToAction(nameof(StoreUsers), new { storeId, userId });
} }

View File

@ -22,6 +22,7 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using StoreData = BTCPayServer.Data.StoreData; using StoreData = BTCPayServer.Data.StoreData;
@ -62,6 +63,7 @@ public partial class UIStoresController : Controller
UriResolver uriResolver, UriResolver uriResolver,
SettingsRepository settingsRepository, SettingsRepository settingsRepository,
CurrencyNameTable currencyNameTable, CurrencyNameTable currencyNameTable,
IStringLocalizer stringLocalizer,
EventAggregator eventAggregator) EventAggregator eventAggregator)
{ {
_rateFactory = rateFactory; _rateFactory = rateFactory;
@ -93,6 +95,7 @@ public partial class UIStoresController : Controller
_dataProtector = dataProtector.CreateProtector("ConfigProtector"); _dataProtector = dataProtector.CreateProtector("ConfigProtector");
_webhookNotificationManager = webhookNotificationManager; _webhookNotificationManager = webhookNotificationManager;
_lightningNetworkOptions = lightningNetworkOptions.Value; _lightningNetworkOptions = lightningNetworkOptions.Value;
StringLocalizer = stringLocalizer;
} }
private readonly BTCPayServerOptions _btcpayServerOptions; private readonly BTCPayServerOptions _btcpayServerOptions;
@ -126,6 +129,7 @@ public partial class UIStoresController : Controller
private readonly IDataProtector _dataProtector; private readonly IDataProtector _dataProtector;
public string? GeneratedPairingCode { get; set; } public string? GeneratedPairingCode { get; set; }
public IStringLocalizer StringLocalizer { get; }
[TempData] [TempData]
private bool StoreNotConfigured { get; set; } private bool StoreNotConfigured { get; set; }

View File

@ -13,7 +13,6 @@ using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;

View File

@ -58,7 +58,7 @@ namespace BTCPayServer.Controllers
var psbt = (await nbx.CreatePSBTAsync(derivationSettings.AccountDerivation, psbtRequest, cancellationToken)); var psbt = (await nbx.CreatePSBTAsync(derivationSettings.AccountDerivation, psbtRequest, cancellationToken));
if (psbt == null) if (psbt == null)
throw new NotSupportedException("You need to update your version of NBXplorer"); throw new NotSupportedException(StringLocalizer["You need to update your version of NBXplorer"]);
// Not supported by coldcard, remove when they do support it // Not supported by coldcard, remove when they do support it
psbt.PSBT.GlobalXPubs.Clear(); psbt.PSBT.GlobalXPubs.Clear();
return psbt; return psbt;
@ -92,7 +92,7 @@ namespace BTCPayServer.Controllers
if (bumpableUTXOs.Length == 0) if (bumpableUTXOs.Length == 0)
{ {
TempData[WellKnownTempData.ErrorMessage] = "There isn't any UTXO available to bump fee"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["There isn't any UTXO available to bump fee"].Value;
return LocalRedirect(returnUrl); return LocalRedirect(returnUrl);
} }
Money bumpFee = Money.Zero; Money bumpFee = Money.Zero;
@ -267,10 +267,10 @@ namespace BTCPayServer.Controllers
psbt = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbt); psbt = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbt);
if (psbt == null) if (psbt == null)
{ {
TempData[WellKnownTempData.ErrorMessage] = "You need to update your version of NBXplorer"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["You need to update your version of NBXplorer"].Value;
return View(vm); return View(vm);
} }
TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["PSBT updated!"].Value;
return RedirectToWalletPSBT(new WalletPSBTViewModel return RedirectToWalletPSBT(new WalletPSBTViewModel
{ {
PSBT = psbt.ToBase64(), PSBT = psbt.ToBase64(),
@ -479,7 +479,7 @@ namespace BTCPayServer.Controllers
if (vm.InvalidPSBT || psbt is null) if (vm.InvalidPSBT || psbt is null)
{ {
if (vm.InvalidPSBT) if (vm.InvalidPSBT)
vm.Errors.Add("Invalid PSBT"); vm.Errors.Add(StringLocalizer["Invalid PSBT"]);
return View(nameof(WalletPSBT), vm); return View(nameof(WalletPSBT), vm);
} }
DerivationSchemeSettings derivationSchemeSettings = GetDerivationSchemeSettings(walletId); DerivationSchemeSettings derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
@ -537,15 +537,15 @@ namespace BTCPayServer.Controllers
} }
catch (PayjoinReceiverException ex) catch (PayjoinReceiverException ex)
{ {
error = $"The payjoin receiver could not complete the payjoin: {ex.Message}"; error = StringLocalizer["The payjoin receiver could not complete the payjoin: {0}", ex.Message];
} }
catch (PayjoinSenderException ex) catch (PayjoinSenderException ex)
{ {
error = $"We rejected the receiver's payjoin proposal: {ex.Message}"; error = StringLocalizer["We rejected the receiver's payjoin proposal: {0}", ex.Message];
} }
catch (Exception ex) catch (Exception ex)
{ {
error = $"Unexpected payjoin error: {ex.Message}"; error = StringLocalizer["Unexpected payjoin error: {0}", ex.Message];
} }
//we possibly exposed the tx to the receiver, so we need to broadcast straight away //we possibly exposed the tx to the receiver, so we need to broadcast straight away
@ -554,9 +554,7 @@ namespace BTCPayServer.Controllers
{ {
Severity = StatusMessageModel.StatusSeverity.Warning, Severity = StatusMessageModel.StatusSeverity.Warning,
AllowDismiss = false, AllowDismiss = false,
Html = $"The payjoin transaction could not be created.<br/>" + Html = $"The payjoin transaction could not be created.<br/>The original transaction was broadcasted instead ({psbt.ExtractTransaction().GetHash()})<br/><br/>" + error
$"The original transaction was broadcasted instead. ({psbt.ExtractTransaction().GetHash()})<br/><br/>" +
$"{error}"
}); });
return await WalletPSBTReady(walletId, vm, "broadcast"); return await WalletPSBTReady(walletId, vm, "broadcast");
case "broadcast" when !psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors): case "broadcast" when !psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors):
@ -576,14 +574,14 @@ namespace BTCPayServer.Controllers
{ {
Severity = StatusMessageModel.StatusSeverity.Warning, Severity = StatusMessageModel.StatusSeverity.Warning,
AllowDismiss = false, AllowDismiss = false,
Html = $"The payjoin transaction could not be broadcasted.<br/>({broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}).<br/>The transaction has been reverted back to its original format and has been broadcast." Html = $"The payjoin transaction could not be broadcasted: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}<br/>The transaction has been reverted back to its original format and has been broadcast."
}); });
vm.SigningContext.PSBT = vm.SigningContext.OriginalPSBT; vm.SigningContext.PSBT = vm.SigningContext.OriginalPSBT;
vm.SigningContext.OriginalPSBT = null; vm.SigningContext.OriginalPSBT = null;
return await WalletPSBTReady(walletId, vm, "broadcast"); return await WalletPSBTReady(walletId, vm, "broadcast");
} }
vm.Errors.Add($"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}"); vm.Errors.Add(StringLocalizer["RPC Error while broadcasting: {0}", $"{broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}"]);
return View(nameof(WalletPSBT), vm); return View(nameof(WalletPSBT), vm);
} }
else else
@ -595,13 +593,13 @@ namespace BTCPayServer.Controllers
} }
catch (Exception ex) catch (Exception ex)
{ {
vm.Errors.Add("Error while broadcasting: " + ex.Message); vm.Errors.Add(StringLocalizer["Error while broadcasting: {0}", ex.Message]);
return View(nameof(WalletPSBT), vm); return View(nameof(WalletPSBT), vm);
} }
if (!TempData.HasStatusMessage()) if (!TempData.HasStatusMessage())
{ {
TempData[WellKnownTempData.SuccessMessage] = $"Transaction broadcasted successfully ({transaction.GetHash()})"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Transaction broadcasted successfully ({0})", transaction.GetHash()].Value;
} }
if (!string.IsNullOrEmpty(vm.ReturnUrl)) if (!string.IsNullOrEmpty(vm.ReturnUrl))
{ {
@ -620,7 +618,7 @@ namespace BTCPayServer.Controllers
await FetchTransactionDetails(walletId, derivationSchemeSettings, vm, network); await FetchTransactionDetails(walletId, derivationSchemeSettings, vm, network);
return View("WalletPSBTDecoded", vm); return View("WalletPSBTDecoded", vm);
default: default:
vm.Errors.Add("Unknown command"); vm.Errors.Add(StringLocalizer["Unknown command"]);
return View(nameof(WalletPSBT), vm); return View(nameof(WalletPSBT), vm);
} }
} }
@ -646,7 +644,7 @@ namespace BTCPayServer.Controllers
return View(vm); return View(vm);
} }
sourcePSBT = sourcePSBT.Combine(psbt); sourcePSBT = sourcePSBT.Combine(psbt);
TempData[WellKnownTempData.SuccessMessage] = "PSBT Successfully combined!"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["PSBT Successfully combined!"].Value;
return RedirectToWalletPSBT(new WalletPSBTViewModel return RedirectToWalletPSBT(new WalletPSBTViewModel
{ {
PSBT = sourcePSBT.ToBase64(), PSBT = sourcePSBT.ToBase64(),

View File

@ -35,6 +35,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using NBitcoin; using NBitcoin;
using NBXplorer; using NBXplorer;
using NBXplorer.DerivationStrategy; using NBXplorer.DerivationStrategy;
@ -57,6 +58,7 @@ namespace BTCPayServer.Controllers
private ExplorerClientProvider ExplorerClientProvider { get; } private ExplorerClientProvider ExplorerClientProvider { get; }
public IServiceProvider ServiceProvider { get; } public IServiceProvider ServiceProvider { get; }
public RateFetcher RateFetcher { get; } public RateFetcher RateFetcher { get; }
public IStringLocalizer StringLocalizer { get; }
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly NBXplorerDashboard _dashboard; private readonly NBXplorerDashboard _dashboard;
@ -99,6 +101,7 @@ namespace BTCPayServer.Controllers
DefaultRulesCollection defaultRules, DefaultRulesCollection defaultRules,
PaymentMethodHandlerDictionary handlers, PaymentMethodHandlerDictionary handlers,
Dictionary<PaymentMethodId, ICheckoutModelExtension> paymentModelExtensions, Dictionary<PaymentMethodId, ICheckoutModelExtension> paymentModelExtensions,
IStringLocalizer stringLocalizer,
TransactionLinkProviders transactionLinkProviders) TransactionLinkProviders transactionLinkProviders)
{ {
_currencyTable = currencyTable; _currencyTable = currencyTable;
@ -124,6 +127,7 @@ namespace BTCPayServer.Controllers
_pullPaymentHostedService = pullPaymentHostedService; _pullPaymentHostedService = pullPaymentHostedService;
ServiceProvider = serviceProvider; ServiceProvider = serviceProvider;
_walletHistogramService = walletHistogramService; _walletHistogramService = walletHistogramService;
StringLocalizer = stringLocalizer;
} }
[HttpPost] [HttpPost]
@ -908,18 +912,17 @@ namespace BTCPayServer.Controllers
try try
{ {
address = BitcoinAddress.Create(bip21, network.NBitcoinNetwork); address = BitcoinAddress.Create(bip21, network.NBitcoinNetwork);
vm.Outputs.Add(new WalletSendModel.TransactionOutput() vm.Outputs.Add(new WalletSendModel.TransactionOutput
{ {
DestinationAddress = address.ToString() DestinationAddress = address.ToString()
} });
);
} }
catch catch
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = "The provided BIP21 payment URI was malformed" Message = StringLocalizer["The provided BIP21 payment URI was malformed"].Value
}); });
} }
} }
@ -1256,7 +1259,7 @@ namespace BTCPayServer.Controllers
selectedTransactions ??= Array.Empty<string>(); selectedTransactions ??= Array.Empty<string>();
if (selectedTransactions.Length == 0) if (selectedTransactions.Length == 0)
{ {
TempData[WellKnownTempData.ErrorMessage] = $"No transaction selected"; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["No transaction selected"].Value;
return RedirectToAction(nameof(WalletTransactions), new { walletId }); return RedirectToAction(nameof(WalletTransactions), new { walletId });
} }
@ -1287,12 +1290,12 @@ namespace BTCPayServer.Controllers
.PruneAsync(derivationScheme.AccountDerivation, new PruneRequest(), cancellationToken); .PruneAsync(derivationScheme.AccountDerivation, new PruneRequest(), cancellationToken);
if (result.TotalPruned == 0) if (result.TotalPruned == 0)
{ {
TempData[WellKnownTempData.SuccessMessage] = "The wallet is already pruned"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The wallet is already pruned"].Value;
} }
else else
{ {
TempData[WellKnownTempData.SuccessMessage] = TempData[WellKnownTempData.SuccessMessage] =
$"The wallet has been successfully pruned ({result.TotalPruned} transactions have been removed from the history)"; StringLocalizer["The wallet has been successfully pruned ({0} transactions have been removed from the history)", result.TotalPruned].Value;
} }
return RedirectToAction(nameof(WalletTransactions), new { walletId }); return RedirectToAction(nameof(WalletTransactions), new { walletId });
@ -1455,11 +1458,11 @@ namespace BTCPayServer.Controllers
; ;
if (await WalletRepository.RemoveWalletLabels(walletId, labels)) if (await WalletRepository.RemoveWalletLabels(walletId, labels))
{ {
TempData[WellKnownTempData.SuccessMessage] = "The label has been successfully removed."; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The label has been successfully removed."].Value;
} }
else else
{ {
TempData[WellKnownTempData.ErrorMessage] = "The label could not be removed."; TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["The label could not be removed."].Value;
} }
return RedirectToAction(nameof(WalletLabels), new { walletId }); return RedirectToAction(nameof(WalletLabels), new { walletId });

View File

@ -237,7 +237,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler, IHasNetwork
} }
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
return new StatusMessageModel() return new StatusMessageModel
{ {
Message = "Payout payments have been marked confirmed", Message = "Payout payments have been marked confirmed",
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success

View File

@ -10,6 +10,7 @@ using Fido2NetLib;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace BTCPayServer.Fido2 namespace BTCPayServer.Fido2
@ -20,17 +21,22 @@ namespace BTCPayServer.Fido2
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly Fido2Service _fido2Service; private readonly Fido2Service _fido2Service;
private IStringLocalizer StringLocalizer { get; }
public UIFido2Controller(UserManager<ApplicationUser> userManager, Fido2Service fido2Service) public UIFido2Controller(
UserManager<ApplicationUser> userManager,
Fido2Service fido2Service,
IStringLocalizer stringLocalizer)
{ {
_userManager = userManager; _userManager = userManager;
_fido2Service = fido2Service; _fido2Service = fido2Service;
StringLocalizer = stringLocalizer;
} }
[HttpGet("{id}/delete")] [HttpGet("{id}/delete")]
public IActionResult Remove(string id) public IActionResult Remove(string id)
{ {
return View("Confirm", new ConfirmModel("Remove security device", "Your account will no longer have this security device as an option for two-factor authentication.", "Remove")); return View("Confirm", new ConfirmModel(StringLocalizer["Remove security device"], StringLocalizer["Your account will no longer have this security device as an option for two-factor authentication."], StringLocalizer["Remove"]));
} }
[HttpPost("{id}/delete")] [HttpPost("{id}/delete")]
@ -41,7 +47,7 @@ namespace BTCPayServer.Fido2
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Html = "The security device was removed successfully." Html = StringLocalizer["The security device was removed successfully."].Value
}); });
return RedirectToList(); return RedirectToList();
@ -56,7 +62,7 @@ namespace BTCPayServer.Fido2
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Html = "The security device could not be registered." Html = StringLocalizer["The security device could not be registered."].Value
}); });
return RedirectToList(); return RedirectToList();
@ -75,7 +81,7 @@ namespace BTCPayServer.Fido2
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Html = "The security device was registered successfully." Html = StringLocalizer["The security device was registered successfully."].Value
}); });
} }
else else
@ -83,7 +89,7 @@ namespace BTCPayServer.Fido2
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Html = "The security device could not be registered." Html = StringLocalizer["The security device could not be registered."].Value
}); });
} }

View File

@ -19,6 +19,7 @@ using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace BTCPayServer.Forms; namespace BTCPayServer.Forms;
@ -31,9 +32,11 @@ public class UIFormsController : Controller
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private FormComponentProviders FormProviders { get; } private FormComponentProviders FormProviders { get; }
private IStringLocalizer StringLocalizer { get; }
public UIFormsController(FormComponentProviders formProviders, FormDataService formDataService, public UIFormsController(FormComponentProviders formProviders, FormDataService formDataService,
UriResolver uriResolver, UriResolver uriResolver,
IStringLocalizer stringLocalizer,
StoreRepository storeRepository, IAuthorizationService authorizationService) StoreRepository storeRepository, IAuthorizationService authorizationService)
{ {
FormProviders = formProviders; FormProviders = formProviders;
@ -41,6 +44,7 @@ public class UIFormsController : Controller
_uriResolver = uriResolver; _uriResolver = uriResolver;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_storeRepository = storeRepository; _storeRepository = storeRepository;
StringLocalizer = stringLocalizer;
} }
[HttpGet("~/stores/{storeId}/forms")] [HttpGet("~/stores/{storeId}/forms")]
@ -136,7 +140,7 @@ public class UIFormsController : Controller
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Form removed" Message = StringLocalizer["Form removed"].Value
}); });
return RedirectToAction("FormsList", new { storeId }); return RedirectToAction("FormsList", new { storeId });
} }
@ -223,10 +227,10 @@ public class UIFormsController : Controller
} }
catch (Exception e) catch (Exception e)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Could not generate invoice: "+ e.Message Message = StringLocalizer["Could not generate invoice: {0}", e.Message].Value
}); });
return await GetFormView(formData, form); return await GetFormView(formData, form);
} }

View File

@ -8,9 +8,11 @@ namespace BTCPayServer.Models.ManageViewModels
[Required] [Required]
[EmailAddress] [EmailAddress]
[MaxLength(50)] [MaxLength(50)]
[Display(Name = "Email")]
public string Email { get; set; } public string Email { get; set; }
public bool EmailConfirmed { get; set; } public bool EmailConfirmed { get; set; }
public bool RequiresEmailConfirmation { get; set; } public bool RequiresEmailConfirmation { get; set; }
[Display(Name = "Name")]
public string Name { get; set; } public string Name { get; set; }
[Display(Name = "Profile Picture")] [Display(Name = "Profile Picture")]

View File

@ -13,6 +13,7 @@ namespace BTCPayServer.Models.StoreViewModels
get; set; get; set;
} }
[Display(Name = "Label")]
public string Label public string Label
{ {
get; set; get; set;

View File

@ -14,6 +14,7 @@ using BTCPayServer.Payouts;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.PayoutProcessors.Lightning; namespace BTCPayServer.PayoutProcessors.Lightning;
@ -22,15 +23,18 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
private readonly LightningAutomatedPayoutSenderFactory _lightningAutomatedPayoutSenderFactory; private readonly LightningAutomatedPayoutSenderFactory _lightningAutomatedPayoutSenderFactory;
private readonly PayoutProcessorService _payoutProcessorService; private readonly PayoutProcessorService _payoutProcessorService;
private IStringLocalizer StringLocalizer { get; }
public UILightningAutomatedPayoutProcessorsController( public UILightningAutomatedPayoutProcessorsController(
EventAggregator eventAggregator, EventAggregator eventAggregator,
LightningAutomatedPayoutSenderFactory lightningAutomatedPayoutSenderFactory, LightningAutomatedPayoutSenderFactory lightningAutomatedPayoutSenderFactory,
PayoutProcessorService payoutProcessorService) PayoutProcessorService payoutProcessorService,
IStringLocalizer stringLocalizer)
{ {
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_lightningAutomatedPayoutSenderFactory = lightningAutomatedPayoutSenderFactory; _lightningAutomatedPayoutSenderFactory = lightningAutomatedPayoutSenderFactory;
_payoutProcessorService = payoutProcessorService; _payoutProcessorService = payoutProcessorService;
StringLocalizer = stringLocalizer;
} }
[HttpGet("~/stores/{storeId}/payout-processors/lightning-automated/{cryptocode}")] [HttpGet("~/stores/{storeId}/payout-processors/lightning-automated/{cryptocode}")]
@ -41,10 +45,10 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
var id = GetPayoutMethodId(cryptoCode); var id = GetPayoutMethodId(cryptoCode);
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i)) if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i))
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"This processor cannot handle {cryptoCode}." Message = StringLocalizer["This processor cannot handle {0}.", cryptoCode].Value
}); });
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors"); return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors");
} }
@ -75,10 +79,10 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
var id = GetPayoutMethodId(cryptoCode); var id = GetPayoutMethodId(cryptoCode);
if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i)) if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i))
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"This processor cannot handle {cryptoCode}." Message = StringLocalizer["This processor cannot handle {0}.", cryptoCode].Value
}); });
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors"); return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors");
} }
@ -109,7 +113,7 @@ public class UILightningAutomatedPayoutProcessorsController : Controller
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Processor updated." Message = StringLocalizer["Processor updated."].Value
}); });
await tcs.Task; await tcs.Task;
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors", new { storeId }); return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors", new { storeId });

View File

@ -13,6 +13,7 @@ using BTCPayServer.Payouts;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.PayoutProcessors.OnChain; namespace BTCPayServer.PayoutProcessors.OnChain;
@ -23,6 +24,8 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
private readonly OnChainAutomatedPayoutSenderFactory _onChainAutomatedPayoutSenderFactory; private readonly OnChainAutomatedPayoutSenderFactory _onChainAutomatedPayoutSenderFactory;
private readonly PayoutProcessorService _payoutProcessorService; private readonly PayoutProcessorService _payoutProcessorService;
public IStringLocalizer StringLocalizer { get; }
public UIOnChainAutomatedPayoutProcessorsController( public UIOnChainAutomatedPayoutProcessorsController(
EventAggregator eventAggregator, EventAggregator eventAggregator,
PaymentMethodHandlerDictionary handlers, PaymentMethodHandlerDictionary handlers,
@ -43,20 +46,20 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
var id = GetPayoutMethod(cryptoCode); var id = GetPayoutMethod(cryptoCode);
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i)) if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i))
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"This processor cannot handle {cryptoCode}." Message = StringLocalizer["This processor cannot handle {0}.", cryptoCode].Value
}); });
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors"); return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors");
} }
var wallet = HttpContext.GetStoreData().GetDerivationSchemeSettings(_handlers, cryptoCode); var wallet = HttpContext.GetStoreData().GetDerivationSchemeSettings(_handlers, cryptoCode);
if (wallet?.IsHotWallet is not true) if (wallet?.IsHotWallet is not true)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"Either your {cryptoCode} wallet is not configured, or it is not a hot wallet. This processor cannot function until a hot wallet is configured in your store." Message = StringLocalizer["Either your {0} wallet is not configured, or it is not a hot wallet. This processor cannot function until a hot wallet is configured in your store.", cryptoCode].Value
}); });
} }
var activeProcessor = var activeProcessor =
@ -85,10 +88,10 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
var id = GetPayoutMethod(cryptoCode); var id = GetPayoutMethod(cryptoCode);
if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i)) if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPayoutMethods().Any(i => id == i))
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"This processor cannot handle {cryptoCode}." Message = StringLocalizer["This processor cannot handle {0}.", cryptoCode].Value
}); });
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors"); return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors");
} }
@ -119,7 +122,7 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Processor updated." Message = StringLocalizer["Processor updated."].Value
}); });
await tcs.Task; await tcs.Task;
return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors", new { storeId }); return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors", new { storeId });

View File

@ -10,27 +10,27 @@ using BTCPayServer.Payments;
using BTCPayServer.Payouts; using BTCPayServer.Payouts;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.PayoutProcessors; namespace BTCPayServer.PayoutProcessors;
public class UIPayoutProcessorsController : Controller public class UIPayoutProcessorsController : Controller
{ {
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IEnumerable<IPayoutProcessorFactory> _payoutProcessorFactories; private readonly IEnumerable<IPayoutProcessorFactory> _payoutProcessorFactories;
private readonly PayoutProcessorService _payoutProcessorService; private readonly PayoutProcessorService _payoutProcessorService;
private IStringLocalizer StringLocalizer { get; }
public UIPayoutProcessorsController( public UIPayoutProcessorsController(
EventAggregator eventAggregator, EventAggregator eventAggregator,
BTCPayNetworkProvider btcPayNetworkProvider,
IEnumerable<IPayoutProcessorFactory> payoutProcessorFactories, IEnumerable<IPayoutProcessorFactory> payoutProcessorFactories,
PayoutProcessorService payoutProcessorService) PayoutProcessorService payoutProcessorService,
IStringLocalizer stringLocalizer)
{ {
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_btcPayNetworkProvider = btcPayNetworkProvider;
_payoutProcessorFactories = payoutProcessorFactories; _payoutProcessorFactories = payoutProcessorFactories;
_payoutProcessorService = payoutProcessorService; _payoutProcessorService = payoutProcessorService;
; StringLocalizer = stringLocalizer;
} }
[HttpGet("~/stores/{storeId}/payout-processors")] [HttpGet("~/stores/{storeId}/payout-processors")]
@ -69,10 +69,10 @@ public class UIPayoutProcessorsController : Controller
Id = id, Id = id,
Processed = tcs Processed = tcs
}); });
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Success, Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Payout Processor removed" Message = StringLocalizer["Payout Processor removed"].Value
}); });
await tcs.Task; await tcs.Task;
return RedirectToAction("ConfigureStorePayoutProcessors", new { storeId }); return RedirectToAction("ConfigureStorePayoutProcessors", new { storeId });

View File

@ -27,6 +27,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using NBitcoin; using NBitcoin;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
using NBitpayClient; using NBitpayClient;
@ -50,6 +51,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
UIInvoiceController invoiceController, UIInvoiceController invoiceController,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
FormDataService formDataService, FormDataService formDataService,
IStringLocalizer stringLocalizer,
CrowdfundAppType app) CrowdfundAppType app)
{ {
_currencies = currencies; _currencies = currencies;
@ -62,6 +64,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
_uriResolver = uriResolver; _uriResolver = uriResolver;
_invoiceController = invoiceController; _invoiceController = invoiceController;
FormDataService = formDataService; FormDataService = formDataService;
StringLocalizer = stringLocalizer;
} }
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
@ -74,6 +77,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly CrowdfundAppType _app; private readonly CrowdfundAppType _app;
public FormDataService FormDataService { get; } public FormDataService FormDataService { get; }
public IStringLocalizer StringLocalizer { get; }
[HttpGet("/")] [HttpGet("/")]
[HttpGet("/apps/{appId}/crowdfund")] [HttpGet("/apps/{appId}/crowdfund")]
@ -581,7 +585,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
StoreId = app.StoreDataId, StoreId = app.StoreDataId,
Settings = newSettings Settings = newSettings
}); });
TempData[WellKnownTempData.SuccessMessage] = "App updated"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value;
return RedirectToAction(nameof(UpdateCrowdfund), new { appId }); return RedirectToAction(nameof(UpdateCrowdfund), new { appId });
} }

View File

@ -11,6 +11,7 @@ using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using StoreData = BTCPayServer.Data.StoreData; using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Plugins.PayButton.Controllers namespace BTCPayServer.Plugins.PayButton.Controllers
@ -25,18 +26,21 @@ namespace BTCPayServer.Plugins.PayButton.Controllers
StoreRepository repo, StoreRepository repo,
UIStoresController storesController, UIStoresController storesController,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
IStringLocalizer stringLocalizer,
AppService appService) AppService appService)
{ {
_repo = repo; _repo = repo;
_userManager = userManager; _userManager = userManager;
_appService = appService; _appService = appService;
_storesController = storesController; _storesController = storesController;
StringLocalizer = stringLocalizer;
} }
readonly StoreRepository _repo; readonly StoreRepository _repo;
readonly UserManager<ApplicationUser> _userManager; readonly UserManager<ApplicationUser> _userManager;
private readonly AppService _appService; private readonly AppService _appService;
private readonly UIStoresController _storesController; private readonly UIStoresController _storesController;
public IStringLocalizer StringLocalizer { get; }
[HttpPost("{storeId}/disable-anyone-can-pay")] [HttpPost("{storeId}/disable-anyone-can-pay")]
public async Task<IActionResult> DisableAnyoneCanCreateInvoice(string storeId) public async Task<IActionResult> DisableAnyoneCanCreateInvoice(string storeId)
@ -44,7 +48,7 @@ namespace BTCPayServer.Plugins.PayButton.Controllers
var blob = GetCurrentStore.GetStoreBlob(); var blob = GetCurrentStore.GetStoreBlob();
blob.AnyoneCanInvoice = false; blob.AnyoneCanInvoice = false;
GetCurrentStore.SetStoreBlob(blob); GetCurrentStore.SetStoreBlob(blob);
TempData[WellKnownTempData.SuccessMessage] = "Feature disabled"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Feature disabled"].Value;
await _repo.UpdateStore(GetCurrentStore); await _repo.UpdateStore(GetCurrentStore);
return RedirectToAction(nameof(PayButton), new { storeId }); return RedirectToAction(nameof(PayButton), new { storeId });
} }
@ -93,7 +97,7 @@ namespace BTCPayServer.Plugins.PayButton.Controllers
if (GetCurrentStore.SetStoreBlob(blob)) if (GetCurrentStore.SetStoreBlob(blob))
{ {
await _repo.UpdateStore(GetCurrentStore); await _repo.UpdateStore(GetCurrentStore);
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Store successfully updated"].Value;
} }
return RedirectToAction(nameof(PayButton), new return RedirectToAction(nameof(PayButton), new

View File

@ -30,6 +30,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using NBitcoin; using NBitcoin;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
using NBitpayClient; using NBitpayClient;
@ -51,6 +52,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
UIInvoiceController invoiceController, UIInvoiceController invoiceController,
FormDataService formDataService, FormDataService formDataService,
IStringLocalizer stringLocalizer,
DisplayFormatter displayFormatter) DisplayFormatter displayFormatter)
{ {
_currencies = currencies; _currencies = currencies;
@ -60,6 +62,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
_invoiceRepository = invoiceRepository; _invoiceRepository = invoiceRepository;
_invoiceController = invoiceController; _invoiceController = invoiceController;
_displayFormatter = displayFormatter; _displayFormatter = displayFormatter;
StringLocalizer = stringLocalizer;
FormDataService = formDataService; FormDataService = formDataService;
} }
@ -71,6 +74,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
private readonly UIInvoiceController _invoiceController; private readonly UIInvoiceController _invoiceController;
private readonly DisplayFormatter _displayFormatter; private readonly DisplayFormatter _displayFormatter;
public FormDataService FormDataService { get; } public FormDataService FormDataService { get; }
public IStringLocalizer StringLocalizer { get; }
[HttpGet("/")] [HttpGet("/")]
[HttpGet("/apps/{appId}/pos")] [HttpGet("/apps/{appId}/pos")]
@ -688,7 +692,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
app.Archived = vm.Archived; app.Archived = vm.Archived;
app.SetSettings(settings); app.SetSettings(settings);
await _appService.UpdateOrCreateApp(app); await _appService.UpdateOrCreateApp(app);
TempData[WellKnownTempData.SuccessMessage] = "App updated"; TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value;
return RedirectToAction(nameof(UpdatePointOfSale), new { appId }); return RedirectToAction(nameof(UpdatePointOfSale), new { appId });
} }

View File

@ -12,9 +12,7 @@ using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Filters; using BTCPayServer.Filters;
using BTCPayServer.Models;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Security;
using BTCPayServer.Services.Altcoins.Monero.Configuration; using BTCPayServer.Services.Altcoins.Monero.Configuration;
using BTCPayServer.Services.Altcoins.Monero.Payments; using BTCPayServer.Services.Altcoins.Monero.Payments;
using BTCPayServer.Services.Altcoins.Monero.RPC.Models; using BTCPayServer.Services.Altcoins.Monero.RPC.Models;
@ -25,6 +23,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Services.Altcoins.Monero.UI namespace BTCPayServer.Services.Altcoins.Monero.UI
{ {
@ -39,18 +38,18 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
private readonly StoreRepository _StoreRepository; private readonly StoreRepository _StoreRepository;
private readonly MoneroRPCProvider _MoneroRpcProvider; private readonly MoneroRPCProvider _MoneroRpcProvider;
private readonly PaymentMethodHandlerDictionary _handlers; private readonly PaymentMethodHandlerDictionary _handlers;
private readonly BTCPayNetworkProvider _BtcPayNetworkProvider; private IStringLocalizer StringLocalizer { get; }
public UIMoneroLikeStoreController(MoneroLikeConfiguration moneroLikeConfiguration, public UIMoneroLikeStoreController(MoneroLikeConfiguration moneroLikeConfiguration,
StoreRepository storeRepository, MoneroRPCProvider moneroRpcProvider, StoreRepository storeRepository, MoneroRPCProvider moneroRpcProvider,
PaymentMethodHandlerDictionary handlers, PaymentMethodHandlerDictionary handlers,
BTCPayNetworkProvider btcPayNetworkProvider) IStringLocalizer stringLocalizer)
{ {
_MoneroLikeConfiguration = moneroLikeConfiguration; _MoneroLikeConfiguration = moneroLikeConfiguration;
_StoreRepository = storeRepository; _StoreRepository = storeRepository;
_MoneroRpcProvider = moneroRpcProvider; _MoneroRpcProvider = moneroRpcProvider;
_handlers = handlers; _handlers = handlers;
_BtcPayNetworkProvider = btcPayNetworkProvider; StringLocalizer = stringLocalizer;
} }
public StoreData StoreData => HttpContext.GetStoreData(); public StoreData StoreData => HttpContext.GetStoreData();
@ -178,7 +177,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
} }
catch (Exception) catch (Exception)
{ {
ModelState.AddModelError(nameof(viewModel.AccountIndex), "Could not create a new account."); ModelState.AddModelError(nameof(viewModel.AccountIndex), StringLocalizer["Could not create a new account."]);
} }
} }
@ -187,12 +186,12 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
var valid = true; var valid = true;
if (viewModel.WalletFile == null) if (viewModel.WalletFile == null)
{ {
ModelState.AddModelError(nameof(viewModel.WalletFile), "Please select the view-only wallet file"); ModelState.AddModelError(nameof(viewModel.WalletFile), StringLocalizer["Please select the view-only wallet file"]);
valid = false; valid = false;
} }
if (viewModel.WalletKeysFile == null) if (viewModel.WalletKeysFile == null)
{ {
ModelState.AddModelError(nameof(viewModel.WalletKeysFile), "Please select the view-only wallet keys file"); ModelState.AddModelError(nameof(viewModel.WalletKeysFile), StringLocalizer["Please select the view-only wallet keys file"]);
valid = false; valid = false;
} }
@ -202,10 +201,10 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
{ {
if (summary.WalletAvailable) if (summary.WalletAvailable)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"There is already an active wallet configured for {cryptoCode}. Replacing it would break any existing invoices!" Message = StringLocalizer["There is already an active wallet configured for {0}. Replacing it would break any existing invoices!", cryptoCode].Value
}); });
return RedirectToAction(nameof(GetStoreMoneroLikePaymentMethod), return RedirectToAction(nameof(GetStoreMoneroLikePaymentMethod),
new { cryptoCode }); new { cryptoCode });
@ -266,14 +265,14 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
} }
catch (Exception ex) catch (Exception ex)
{ {
ModelState.AddModelError(nameof(viewModel.AccountIndex), $"Could not open the wallet: {ex.Message}"); ModelState.AddModelError(nameof(viewModel.AccountIndex), StringLocalizer["Could not open the wallet: {0}", ex.Message]);
return View(viewModel); return View(viewModel);
} }
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Info, Severity = StatusMessageModel.StatusSeverity.Info,
Message = $"View-only wallet files uploaded. The wallet will soon become available." Message = StringLocalizer["View-only wallet files uploaded. The wallet will soon become available."].Value
}); });
return RedirectToAction(nameof(GetStoreMoneroLikePaymentMethod), new { cryptoCode }); return RedirectToAction(nameof(GetStoreMoneroLikePaymentMethod), new { cryptoCode });
} }

View File

@ -12,10 +12,8 @@ using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Filters; using BTCPayServer.Filters;
using BTCPayServer.Models;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin; using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Security;
using BTCPayServer.Services.Altcoins.Zcash.Configuration; using BTCPayServer.Services.Altcoins.Zcash.Configuration;
using BTCPayServer.Services.Altcoins.Zcash.Payments; using BTCPayServer.Services.Altcoins.Zcash.Payments;
using BTCPayServer.Services.Altcoins.Zcash.RPC.Models; using BTCPayServer.Services.Altcoins.Zcash.RPC.Models;
@ -26,6 +24,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Services.Altcoins.Zcash.UI namespace BTCPayServer.Services.Altcoins.Zcash.UI
{ {
@ -40,15 +39,18 @@ namespace BTCPayServer.Services.Altcoins.Zcash.UI
private readonly StoreRepository _StoreRepository; private readonly StoreRepository _StoreRepository;
private readonly ZcashRPCProvider _ZcashRpcProvider; private readonly ZcashRPCProvider _ZcashRpcProvider;
private readonly PaymentMethodHandlerDictionary _handlers; private readonly PaymentMethodHandlerDictionary _handlers;
private IStringLocalizer StringLocalizer { get; }
public UIZcashLikeStoreController(ZcashLikeConfiguration ZcashLikeConfiguration, public UIZcashLikeStoreController(ZcashLikeConfiguration ZcashLikeConfiguration,
StoreRepository storeRepository, ZcashRPCProvider ZcashRpcProvider, StoreRepository storeRepository, ZcashRPCProvider ZcashRpcProvider,
PaymentMethodHandlerDictionary handlers) PaymentMethodHandlerDictionary handlers,
IStringLocalizer stringLocalizer)
{ {
_ZcashLikeConfiguration = ZcashLikeConfiguration; _ZcashLikeConfiguration = ZcashLikeConfiguration;
_StoreRepository = storeRepository; _StoreRepository = storeRepository;
_ZcashRpcProvider = ZcashRpcProvider; _ZcashRpcProvider = ZcashRpcProvider;
_handlers = handlers; _handlers = handlers;
StringLocalizer = stringLocalizer;
} }
public StoreData StoreData => HttpContext.GetStoreData(); public StoreData StoreData => HttpContext.GetStoreData();
@ -151,7 +153,7 @@ namespace BTCPayServer.Services.Altcoins.Zcash.UI
} }
catch (Exception) catch (Exception)
{ {
ModelState.AddModelError(nameof(viewModel.AccountIndex), "Could not create new account."); ModelState.AddModelError(nameof(viewModel.AccountIndex), StringLocalizer["Could not create new account."]);
} }
} }
@ -160,12 +162,12 @@ namespace BTCPayServer.Services.Altcoins.Zcash.UI
var valid = true; var valid = true;
if (viewModel.WalletFile == null) if (viewModel.WalletFile == null)
{ {
ModelState.AddModelError(nameof(viewModel.WalletFile), "Please select the wallet file"); ModelState.AddModelError(nameof(viewModel.WalletFile), StringLocalizer["Please select the wallet file"]);
valid = false; valid = false;
} }
if (viewModel.WalletKeysFile == null) if (viewModel.WalletKeysFile == null)
{ {
ModelState.AddModelError(nameof(viewModel.WalletKeysFile), "Please select the wallet.keys file"); ModelState.AddModelError(nameof(viewModel.WalletKeysFile), StringLocalizer["Please select the wallet.keys file"]);
valid = false; valid = false;
} }
@ -175,10 +177,10 @@ namespace BTCPayServer.Services.Altcoins.Zcash.UI
{ {
if (summary.WalletAvailable) if (summary.WalletAvailable)
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"There is already an active wallet configured for {cryptoCode}. Replacing it would break any existing invoices" Message = StringLocalizer["There is already an active wallet configured for {0}. Replacing it would break any existing invoices!", cryptoCode].Value
}); });
return RedirectToAction(nameof(GetStoreZcashLikePaymentMethod), return RedirectToAction(nameof(GetStoreZcashLikePaymentMethod),
new { cryptoCode }); new { cryptoCode });

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,10 @@
#nullable enable #nullable enable
using System.Collections; using System.Collections;
using System.Collections.Frozen; using System.Collections.Frozen;
using Dapper;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using System;
using System.Runtime.InteropServices;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using static System.Net.Mime.MediaTypeNames;
using YamlDotNet.Core.Tokens;
namespace BTCPayServer.Services namespace BTCPayServer.Services
{ {

View File

@ -14,10 +14,10 @@
{ {
Title(line.Network.CryptoCode, "disabled"); Title(line.Network.CryptoCode, "disabled");
<ul> <ul>
<li>The node is offline</li> <li text-translate="true">The node is offline</li>
@if (line.Error != null) @if (line.Error != null)
{ {
<li>Last error: @line.Error</li> <li>@StringLocalizer["Last error:"] @line.Error</li>
} }
</ul> </ul>
} }
@ -29,19 +29,19 @@
{ {
Title(line.Network.CryptoCode, "pending"); Title(line.Network.CryptoCode, "pending");
<ul> <ul>
<li>NBXplorer headers height: @line.Status.ChainHeight</li> <li>@StringLocalizer["NBXplorer headers height: {0}", line.Status.ChainHeight]</li>
<li>The node is starting...</li> <li text-translate="true">The node is starting...</li>
</ul> </ul>
} }
else else
{ {
Title(line.Network.CryptoCode, "disabled"); Title(line.Network.CryptoCode, "disabled");
<ul> <ul>
<li>NBXplorer headers height: @line.Status.ChainHeight</li> <li>@StringLocalizer["NBXplorer headers height: {0}", line.Status.ChainHeight]</li>
<li>The node is offline</li> <li text-translate="true">The node is offline</li>
@if (line.Error != null) @if (line.Error != null)
{ {
<li>Last error: @line.Error</li> <li>@StringLocalizer["Last error:"] line.Error</li>
} }
</ul> </ul>
} }
@ -50,12 +50,12 @@
{ {
Title(line.Network.CryptoCode, "enabled"); Title(line.Network.CryptoCode, "enabled");
<ul> <ul>
<li>The node is synchronized (Height: @line.Status.BitcoinStatus.Headers)</li> <li>@StringLocalizer["The node is synchronized (Height: {0})", line.Status.BitcoinStatus.Headers]</li>
@if (line.Status.BitcoinStatus.IsSynched && @if (line.Status.BitcoinStatus.IsSynched &&
line.Status.SyncHeight.HasValue && line.Status.SyncHeight.HasValue &&
line.Status.SyncHeight.Value < line.Status.BitcoinStatus.Headers) line.Status.SyncHeight.Value < line.Status.BitcoinStatus.Headers)
{ {
<li>NBXplorer is synchronizing... (Height: @line.Status.SyncHeight.Value)</li> <li>@StringLocalizer["NBXplorer is synchronizing... (Height: {0})", line.Status.SyncHeight.Value]</li>
} }
</ul> </ul>
} }
@ -63,8 +63,8 @@
{ {
Title(line.Network.CryptoCode, "enabled"); Title(line.Network.CryptoCode, "enabled");
<ul> <ul>
<li>Node headers height: @line.Status.BitcoinStatus.Headers</li> <li>@StringLocalizer["Node headers height: {0}", line.Status.BitcoinStatus.Headers]</li>
<li>Validated blocks: @line.Status.BitcoinStatus.Blocks</li> <li>@StringLocalizer["Validated blocks: {0}", line.Status.BitcoinStatus.Blocks]</li>
</ul> </ul>
} }
@if (!line.Status.IsFullySynched && line.Status.BitcoinStatus != null) @if (!line.Status.IsFullySynched && line.Status.BitcoinStatus != null)

View File

@ -28,12 +28,12 @@
if (onChainPaymentData.PayjoinInformation is PayjoinInformation pj) if (onChainPaymentData.PayjoinInformation is PayjoinInformation pj)
{ {
payjoinInformation = pj; payjoinInformation = pj;
m.AdditionalInformation = "Original transaction"; m.AdditionalInformation = StringLocalizer["Original transaction"];
} }
if (payjoinInformation is PayjoinInformation && if (payjoinInformation is PayjoinInformation &&
payjoinInformation.CoinjoinTransactionHash == onChainPaymentData?.Outpoint.Hash) payjoinInformation.CoinjoinTransactionHash == onChainPaymentData?.Outpoint.Hash)
{ {
m.AdditionalInformation = "Payjoin transaction"; m.AdditionalInformation = StringLocalizer["Payjoin transaction"];
} }
m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString(); m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString();
m.ReceivedTime = payment.ReceivedTime; m.ReceivedTime = payment.ReceivedTime;
@ -54,26 +54,26 @@
{ {
var hasNetworkFee = payments.Sum(a => a.NetworkFee) > 0; var hasNetworkFee = payments.Sum(a => a.NetworkFee) > 0;
<section> <section>
<h5>On-Chain Payments</h5> <h5 text-translate="true">On-Chain Payments</h5>
<div class="invoice-payments table-responsive mt-0"> <div class="invoice-payments table-responsive mt-0">
<table class="table table-hover mb-0"> <table class="table table-hover mb-0">
<thead> <thead>
<tr> <tr>
<th class="w-75px">Payment Method</th> <th text-translate="true" class="w-75px">Payment Method</th>
<th class="w-100px">Index</th> <th text-translate="true" class="w-100px">Index</th>
<th class="w-175px">Destination</th> <th text-translate="true" class="w-175px">Destination</th>
<th class="text-nowrap">Payment Proof</th> <th text-translate="true" class="text-nowrap">Payment Proof</th>
@if (hasNetworkFee) @if (hasNetworkFee)
{ {
<th class="text-end"> <th class="text-end">
Network Fee <span text-translate="true">Network Fee</span>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener" title="More information..."> <a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info" /> <vc:icon symbol="info" />
</a> </a>
</th> </th>
} }
<th class="text-end">Confirmations</th> <th text-translate="true" class="text-end">Confirmations</th>
<th class="w-150px text-end">Paid</th> <th text-translate="true" class="w-150px text-end">Paid</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -81,7 +81,7 @@
{ {
<tr style="@(payment.Replaced ? "text-decoration: line-through" : "")"> <tr style="@(payment.Replaced ? "text-decoration: line-through" : "")">
<td>@payment.PaymentMethodId</td> <td>@payment.PaymentMethodId</td>
<td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? "Unknown")</td> <td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? StringLocalizer["Unknown"])</td>
<td> <td>
<vc:truncate-center text="@payment.DepositAddress.ToString()" classes="truncate-center-id" /> <vc:truncate-center text="@payment.DepositAddress.ToString()" classes="truncate-center-id" />
</td> </td>

View File

@ -24,7 +24,7 @@
<li class="nav-item" not-permission="@Policies.CanModifyStoreSettings" permission="@Policies.CanViewStoreSettings"> <li class="nav-item" not-permission="@Policies.CanModifyStoreSettings" permission="@Policies.CanViewStoreSettings">
<span class="nav-link"> <span class="nav-link">
<vc:icon symbol="nav-crowdfund" /> <vc:icon symbol="nav-crowdfund" />
<span>Crowdfund</span> <span text-translate="true">Crowdfund</span>
</span> </span>
</li> </li>
} }

View File

@ -34,15 +34,16 @@
if (item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum) if (item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Minimum)
{ {
@Safe.Raw("or more") @Safe.Raw(StringLocalizer["or more"])
} }
} }
else if (item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup ) else if (item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Topup )
{ {
@Safe.Raw("Any amount") @Safe.Raw(StringLocalizer["Any amount"])
}else if (item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed) }
else if (item.PriceType == ViewPointOfSaleViewModel.ItemPriceType.Fixed)
{ {
@Safe.Raw("Free") @Safe.Raw(StringLocalizer["Free"])
} }
</span> </span>
</div> </div>
@ -56,10 +57,10 @@
case null: case null:
break; break;
case <= 0: case <= 0:
<span>Sold out</span> <span text-translate="true">Sold out</span>
break; break;
default: default:
<span>@item.Inventory left</span> <span>@StringLocalizer["{0} left", item.Inventory]</span>
break; break;
} }
@if (hasCount) @if (hasCount)
@ -91,6 +92,6 @@
<span asp-validation-for="Amount" class="text-danger"></span> <span asp-validation-for="Amount" class="text-danger"></span>
</div> </div>
<input type="hidden" asp-for="RedirectToCheckout"/> <input type="hidden" asp-for="RedirectToCheckout"/>
<button type="submit" class="btn btn-primary">Contribute</button> <button type="submit" class="btn btn-primary" text-translate="true">Contribute</button>
} }
</form> </form>

View File

@ -40,8 +40,8 @@
<body class="min-vh-100 p-2"> <body class="min-vh-100 p-2">
@if (!Model.Enabled) @if (!Model.Enabled)
{ {
<div class="alert alert-warning text-center sticky-top mb-0 rounded-0" role="alert"> <div class="alert alert-warning text-center sticky-top mb-0 rounded-0" role="alert" text-translate="true">
This crowdfund page is not publically viewable! This crowdfund page is not publicly viewable!
</div> </div>
} }
@if (Model.AnimationsEnabled) @if (Model.AnimationsEnabled)
@ -71,19 +71,19 @@
<span v-if="srvModel.resetEvery !== 'Never'" <span v-if="srvModel.resetEvery !== 'Never'"
class="h5 ms-2" class="h5 ms-2"
v-b-tooltip v-b-tooltip
:title="'Goal resets every ' + srvModel.resetEveryAmount + ' ' + srvModel.resetEvery + ((srvModel.resetEveryAmount>1)?'s': '')"> :title="'Goal resets every ' + srvModel.resetEveryAmount + ' ' + srvModel.resetEvery + ((srvModel.resetEveryAmount>1)?'s': '')" text-translate="true">
Dynamic Dynamic
</span> </span>
} }
@if (Model.EnforceTargetAmount) @if (Model.EnforceTargetAmount)
{ {
<span v-if="srvModel.enforceTargetAmount" class="h5 ms-2" v-b-tooltip title="No contributions allowed after the goal has been reached"> <span v-if="srvModel.enforceTargetAmount" class="h5 ms-2" v-b-tooltip title=@StringLocalizer["No contributions allowed after the goal has been reached"] text-translate="true">
Hardcap Goal Hardcap Goal
</span> </span>
} }
else else
{ {
<span v-if="!srvModel.enforceTargetAmount" class="h5 ms-2" v-b-tooltip title="Contributions allowed even after goal is reached"> <span v-if="!srvModel.enforceTargetAmount" class="h5 ms-2" v-b-tooltip title=@StringLocalizer["Contributions allowed even after goal is reached"] text-translate="true">
Softcap Goal Softcap Goal
</span> </span>
} }
@ -92,18 +92,18 @@
@if (!Model.Started && Model.StartDate.HasValue) @if (!Model.Started && Model.StartDate.HasValue)
{ {
<h6 class="text-muted fst-italic mt-3" v-if="!started && srvModel.startDate" v-b-tooltip :title="startDate" v-text="`Starts in ${startDiff}`" data-test="time-state"> <h6 class="text-muted fst-italic mt-3" v-if="!started && srvModel.startDate" v-b-tooltip :title="startDate" v-text="`Starts in ${startDiff}`" data-test="time-state">
Starts @TimeZoneInfo.ConvertTimeFromUtc(Model.StartDate.Value, TimeZoneInfo.Local) @StringLocalizer["Starts {0}", TimeZoneInfo.ConvertTimeFromUtc(Model.StartDate.Value, TimeZoneInfo.Local)]
</h6> </h6>
} }
else if (Model.Started && !Model.Ended && Model.EndDate.HasValue) else if (Model.Started && !Model.Ended && Model.EndDate.HasValue)
{ {
<h6 class="text-muted fst-italic mt-3" v-if="started && !ended && srvModel.endDate" v-b-tooltip :title="endDate" v-text="`Ends in ${endDiff}`" data-test="time-state"> <h6 class="text-muted fst-italic mt-3" v-if="started && !ended && srvModel.endDate" v-b-tooltip :title="endDate" v-text="`Ends in ${endDiff}`" data-test="time-state">
Ends @TimeZoneInfo.ConvertTimeFromUtc(Model.EndDate.Value, TimeZoneInfo.Local) @StringLocalizer["Ends {0}", TimeZoneInfo.ConvertTimeFromUtc(Model.EndDate.Value, TimeZoneInfo.Local)]
</h6> </h6>
} }
else if (Model.Started && !Model.Ended && !Model.EndDate.HasValue) else if (Model.Started && !Model.Ended && !Model.EndDate.HasValue)
{ {
<h6 class="text-muted fst-italic mt-3" v-if="started && !ended && !srvModel.endDate" v-b-tooltip title="No set end date" data-test="time-state"> <h6 class="text-muted fst-italic mt-3" v-if="started && !ended && !srvModel.endDate" v-b-tooltip title="No set end date" data-test="time-state" text-translate="true">
Currently active! Currently active!
</h6> </h6>
} }
@ -160,7 +160,7 @@
<div class="col-sm border-end p-3 text-center" id="crowdfund-body-total-contributors"> <div class="col-sm border-end p-3 text-center" id="crowdfund-body-total-contributors">
<h3 v-text="new Intl.NumberFormat().format(srvModel.info.totalContributors)">@Model.Info.TotalContributors</h3> <h3 v-text="new Intl.NumberFormat().format(srvModel.info.totalContributors)">@Model.Info.TotalContributors</h3>
<h5 class="text-muted fst-italic mb-0">Contributors</h5> <h5 class="text-muted fst-italic mb-0" text-translate="true">Contributors</h5>
</div> </div>
@if (Model.StartDate.HasValue || Model.EndDate.HasValue) @if (Model.StartDate.HasValue || Model.EndDate.HasValue)
@ -170,20 +170,20 @@
{ {
<div v-if="startDiff"> <div v-if="startDiff">
<h3 v-text="startDiff">@TimeZoneInfo.ConvertTimeFromUtc(Model.StartDate.Value, TimeZoneInfo.Local)</h3> <h3 v-text="startDiff">@TimeZoneInfo.ConvertTimeFromUtc(Model.StartDate.Value, TimeZoneInfo.Local)</h3>
<h5 class="text-muted fst-italic mb-0" v-text="'Left to start'">Start Date</h5> <h5 class="text-muted fst-italic mb-0" v-text="'Left to start'" text-translate="true">Start Date</h5>
</div> </div>
} }
else if (Model.Started && !Model.Ended && Model.EndDate.HasValue) else if (Model.Started && !Model.Ended && Model.EndDate.HasValue)
{ {
<div v-if="!startDiff && endDiff"> <div v-if="!startDiff && endDiff">
<h3 v-text="endDiff">@TimeZoneInfo.ConvertTimeFromUtc(Model.EndDate.Value, TimeZoneInfo.Local)</h3> <h3 v-text="endDiff">@TimeZoneInfo.ConvertTimeFromUtc(Model.EndDate.Value, TimeZoneInfo.Local)</h3>
<h5 class="text-muted fst-italic mb-0" v-text="'Left'">End Date</h5> <h5 class="text-muted fst-italic mb-0" v-text="'Left'" text-translate="true">End Date</h5>
</div> </div>
} }
else if (Model.Ended) else if (Model.Ended)
{ {
<div v-if="ended"> <div v-if="ended">
<h3 class="mb-0">Campaign not active</h3> <h3 class="mb-0" text-translate="true">Campaign not active</h3>
</div> </div>
} }
<b-tooltip v-if="startDate || endDate" target="crowdfund-body-campaign-dates" class="only-for-js"> <b-tooltip v-if="startDate || endDate" target="crowdfund-body-campaign-dates" class="only-for-js">
@ -191,13 +191,13 @@
@if (Model.StartDate.HasValue) @if (Model.StartDate.HasValue)
{ {
<li v-if="startDate" class="list-unstyled"> <li v-if="startDate" class="list-unstyled">
{{started? "Started" : "Starts"}} {{startDate}} {{started ? "Started" : "Starts"}} {{startDate}}
</li> </li>
} }
@if (Model.EndDate.HasValue) @if (Model.EndDate.HasValue)
{ {
<li v-if="endDate" class="list-unstyled"> <li v-if="endDate" class="list-unstyled">
{{ended? "Ended" : "Ends"}} {{endDate}} {{ended ? "Ended" : "Ends"}} {{endDate}}
</li> </li>
} }
</ul> </ul>
@ -207,7 +207,7 @@
</div> </div>
<div class="text-center mb-4" id="crowdfund-body-header"> <div class="text-center mb-4" id="crowdfund-body-header">
<button v-if="active" id="crowdfund-body-header-cta" class="btn btn-lg btn-primary py-2 px-5 only-for-js" v-on:click="contribute">Contribute</button> <button v-if="active" id="crowdfund-body-header-cta" class="btn btn-lg btn-primary py-2 px-5 only-for-js" v-on:click="contribute" text-translate="true">Contribute</button>
</div> </div>
<div class="row mt-4 justify-content-between gap-5"> <div class="row mt-4 justify-content-between gap-5">
@ -245,10 +245,7 @@
<div class="overflow-hidden">@Safe.Raw(Model.Description)</div> <div class="overflow-hidden">@Safe.Raw(Model.Description)</div>
</div> </div>
<div class="col-md-4 col-sm-12"> <div class="col-md-4 col-sm-12">
<partial <partial name="Crowdfund/Public/ContributeForm" model="@(new ContributeToCrowdfund { ViewCrowdfundViewModel = Model, RedirectToCheckout = true })" />
name="Crowdfund/Public/ContributeForm"
model="@(new ContributeToCrowdfund { ViewCrowdfundViewModel = Model, RedirectToCheckout = true })">
</partial>
</div> </div>
</div> </div>
</noscript> </noscript>
@ -264,7 +261,7 @@
<footer class="store-footer"> <footer class="store-footer">
<p class="text-muted" v-text="`Updated ${lastUpdated}`">Updated @Model.Info.LastUpdated</p> <p class="text-muted" v-text="`Updated ${lastUpdated}`">Updated @Model.Info.LastUpdated</p>
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener"> <a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
Powered by <partial name="_StoreFooterLogo" /> <span text-translate="true">Powered by</span> <partial name="_StoreFooterLogo" />
</a> </a>
</footer> </footer>
</div> </div>
@ -300,7 +297,7 @@
</span> </span>
<div class="perk-zoom" v-if="canExpand"> <div class="perk-zoom" v-if="canExpand">
<div class="perk-zoom-bg"></div> <div class="perk-zoom-bg"></div>
<div class="perk-zoom-text w-100 py-2 px-4 text-center text-primary fw-semibold fs-5 lh-sm"> <div class="perk-zoom-text w-100 py-2 px-4 text-center text-primary fw-semibold fs-5 lh-sm" text-translate="true">
Select this contribution perk Select this contribution perk
</div> </div>
</div> </div>
@ -311,17 +308,15 @@
<div class="card-title d-flex justify-content-between" :class="{ 'mb-0': !perk.description }"> <div class="card-title d-flex justify-content-between" :class="{ 'mb-0': !perk.description }">
<span class="h5" :class="{ 'mb-0': !perk.description }">{{perk.title ? perk.title : perk.id}}</span> <span class="h5" :class="{ 'mb-0': !perk.description }">{{perk.title ? perk.title : perk.id}}</span>
<span class="text-muted"> <span class="text-muted">
<template v-if="perk.priceType === 'Fixed' && amount == 0" text-translate="true">
<template v-if="perk.priceType === 'Fixed' && amount ==0">
Free Free
</template> </template>
<template v-else-if="amount"> <template v-else-if="amount">
{{formatAmount(perk.price.noExponents(), srvModel.currencyData.divisibility)}} {{formatAmount(perk.price.noExponents(), srvModel.currencyData.divisibility)}}
{{targetCurrency}} {{targetCurrency}}
<template v-if="perk.price.type === 'Minimum'">or more</template> <template v-if="perk.price.type === 'Minimum'" text-translate="true">or more</template>
</template> </template>
<template v-else-if="perk.priceType === 'Topup' || (!amount && perk.priceType === 'Minimum')" text-translate="true">
<template v-else-if="perk.priceType === 'Topup' || (!amount && perk.priceType === 'Minimum')">
Any amount Any amount
</template> </template>
</span> </span>
@ -343,7 +338,7 @@
:class="{'btn-disabled': loading}" :class="{'btn-disabled': loading}"
type="submit"> type="submit">
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status"> <div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
{{perk.buyButtonText || 'Continue'}} {{perk.buyButtonText || 'Continue'}}
</button> </button>
@ -361,7 +356,7 @@
<template id="contribute-template"> <template id="contribute-template">
<div> <div>
<h3 v-if="!inModal" class="mb-3">Contribute</h3> <h3 v-if="!inModal" class="mb-3" text-translate="true">Contribute</h3>
<perks :perks="perks" <perks :perks="perks"
:loading="loading" :loading="loading"
:in-modal="inModal" :in-modal="inModal"

View File

@ -109,7 +109,7 @@
<div> <div>
<label asp-for="Enabled" class="form-check-label"></label> <label asp-for="Enabled" class="form-check-label"></label>
<span asp-validation-for="Enabled" class="text-danger"></span> <span asp-validation-for="Enabled" class="text-danger"></span>
<div class="text-muted">The crowdfund will be visible to anyone.</div> <div class="text-muted" text-translate="true">The crowdfund will be visible to anyone.</div>
</div> </div>
</div> </div>
</div> </div>
@ -173,9 +173,9 @@
<div class="d-flex align-items-center mb-3"> <div class="d-flex align-items-center mb-3">
<input asp-for="IsRecurring" type="checkbox" class="btcpay-toggle me-3" data-bs-toggle="collapse" data-bs-target="#ResetEverySettings" aria-expanded="@(Model.IsRecurring)" aria-controls="ResetEverySettings" /> <input asp-for="IsRecurring" type="checkbox" class="btcpay-toggle me-3" data-bs-toggle="collapse" data-bs-target="#ResetEverySettings" aria-expanded="@(Model.IsRecurring)" aria-controls="ResetEverySettings" />
<div> <div>
<label asp-for="IsRecurring" class="form-check-label">Recurring Goal</label> <label asp-for="IsRecurring" class="form-check-label" text-translate="true">Recurring Goal</label>
<span asp-validation-for="IsRecurring" class="text-danger"></span> <span asp-validation-for="IsRecurring" class="text-danger"></span>
<div class="text-muted">Reset goal after a specific period of time, based on your crowdfund's start date.</div> <div class="text-muted" text-translate="true">Reset goal after a specific period of time, based on your crowdfund's start date.</div>
</div> </div>
</div> </div>
@ -204,7 +204,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-xl-8 col-xxl-constrain"> <div class="col-xl-8 col-xxl-constrain">
<h3 class="mt-5 mb-4">Contributions</h3> <h3 class="mt-5 mb-4" text-translate="true">Contributions</h3>
<div class="d-flex mb-3"> <div class="d-flex mb-3">
<input asp-for="SortPerksByPopularity" type="checkbox" class="btcpay-toggle me-3" /> <input asp-for="SortPerksByPopularity" type="checkbox" class="btcpay-toggle me-3" />
<label asp-for="SortPerksByPopularity" class="form-check-label"></label> <label asp-for="SortPerksByPopularity" class="form-check-label"></label>
@ -226,27 +226,27 @@
<span asp-validation-for="EnforceTargetAmount" class="text-danger"></span> <span asp-validation-for="EnforceTargetAmount" class="text-danger"></span>
</div> </div>
<h3 class="mt-5 mb-4">Crowdfund Behavior</h3> <h3 class="mt-5 mb-4" text-translate="true">Crowdfund Behavior</h3>
<div class="d-flex"> <div class="d-flex">
<input asp-for="UseAllStoreInvoices" type="checkbox" class="btcpay-toggle me-3" /> <input asp-for="UseAllStoreInvoices" type="checkbox" class="btcpay-toggle me-3" />
<label asp-for="UseAllStoreInvoices" class="form-check-label"></label> <label asp-for="UseAllStoreInvoices" class="form-check-label"></label>
<span asp-validation-for="UseAllStoreInvoices" class="text-danger"></span> <span asp-validation-for="UseAllStoreInvoices" class="text-danger"></span>
</div> </div>
<h3 class="mt-5 mb-4">Checkout</h3> <h3 class="mt-5 mb-4" text-translate="true">Checkout</h3>
<div class="form-group"> <div class="form-group">
<label asp-for="FormId" class="form-label"></label> <label asp-for="FormId" class="form-label"></label>
<select asp-for="FormId" class="form-select w-auto" asp-items="@checkoutFormOptions"></select> <select asp-for="FormId" class="form-select w-auto" asp-items="@checkoutFormOptions"></select>
<span asp-validation-for="FormId" class="text-danger"></span> <span asp-validation-for="FormId" class="text-danger"></span>
</div> </div>
<h3 class="mt-5 mb-2">Additional Options</h3> <h3 class="mt-5 mb-2" text-translate="true">Additional Options</h3>
<div class="form-group"> <div class="form-group">
<div class="accordion" id="additional"> <div class="accordion" id="additional">
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header" id="additional-sound-header"> <h2 class="accordion-header" id="additional-sound-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-sound" aria-expanded="false" aria-controls="additional-sound"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-sound" aria-expanded="false" aria-controls="additional-sound">
Sound <span text-translate="true">Sound</span>
<vc:icon symbol="caret-down" /> <vc:icon symbol="caret-down" />
</button> </button>
</h2> </h2>
@ -272,7 +272,7 @@
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header" id="additional-animation-header"> <h2 class="accordion-header" id="additional-animation-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-animation" aria-expanded="false" aria-controls="additional-animation"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-animation" aria-expanded="false" aria-controls="additional-animation">
Animation <span text-translate="true">Animation</span>
<vc:icon symbol="caret-down" /> <vc:icon symbol="caret-down" />
</button> </button>
</h2> </h2>

View File

@ -12,9 +12,9 @@
} }
<div class="form-group"> <div class="form-group">
<div class="d-flex flex-wrap gap-2 align-items-center justify-content-between"> <div class="d-flex flex-wrap gap-2 align-items-center justify-content-between">
<label asp-for="Settings.Server" class="form-label">SMTP Server</label> <label asp-for="Settings.Server" class="form-label" text-translate="true">SMTP Server</label>
<div class="dropdown only-for-js mt-n2" id="quick-fill"> <div class="dropdown only-for-js mt-n2" id="quick-fill">
<button class="btn btn-link p-0 dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="QuickFillDropdownToggle"> <button class="btn btn-link p-0 dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="QuickFillDropdownToggle" text-translate="true">
Quick Fill Quick Fill
</button> </button>
<div class="dropdown-menu" aria-labelledby="QuickFillDropdownToggle"> <div class="dropdown-menu" aria-labelledby="QuickFillDropdownToggle">
@ -30,31 +30,30 @@
<span asp-validation-for="Settings.Server" class="text-danger"></span> <span asp-validation-for="Settings.Server" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.Port" class="form-label"></label> <label asp-for="Settings.Port" class="form-label" text-translate="true">Port</label>
<input asp-for="Settings.Port" data-fill="port" class="form-control"/> <input asp-for="Settings.Port" data-fill="port" class="form-control"/>
<span asp-validation-for="Settings.Port" class="text-danger"></span> <span asp-validation-for="Settings.Port" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.From" class="form-label">Sender's Email Address</label> <label asp-for="Settings.From" class="form-label" text-translate="true">Sender's Email Address</label>
<input asp-for="Settings.From" class="form-control" placeholder="Firstname Lastname <email@example.com>" /> <input asp-for="Settings.From" class="form-control" placeholder="Firstname Lastname <email@example.com>" />
<span asp-validation-for="Settings.From" class="text-danger"></span> <span asp-validation-for="Settings.From" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.Login" class="form-label"></label> <label asp-for="Settings.Login" class="form-label" text-translate="true">Login</label>
<input asp-for="Settings.Login" class="form-control"/> <input asp-for="Settings.Login" class="form-control"/>
<div class="form-text">For many email providers (like Gmail) your login is your email address.</div> <div class="form-text" text-translate="true">For many email providers (like Gmail) your login is your email address.</div>
<span asp-validation-for="Settings.Login" class="text-danger"></span> <span asp-validation-for="Settings.Login" class="text-danger"></span>
</div> </div>
<div class="form-group" permission="@(Model is ServerEmailsViewModel ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings)"> <div class="form-group" permission="@(Model is ServerEmailsViewModel ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings)">
<label asp-for="Settings.Password" class="form-label" text-translate="true">Password</label>
@if (!Model.PasswordSet) @if (!Model.PasswordSet)
{ {
<label asp-for="Settings.Password" class="form-label"></label>
<input asp-for="Settings.Password" type="password" class="form-control"/> <input asp-for="Settings.Password" type="password" class="form-control"/>
<span asp-validation-for="Settings.Password" class="text-danger"></span> <span asp-validation-for="Settings.Password" class="text-danger"></span>
} }
else else
{ {
<label asp-for="Settings.Password" class="form-label"></label>
<div class="input-group"> <div class="input-group">
<input value="Configured" type="text" readonly class="form-control"/> <input value="Configured" type="text" readonly class="form-control"/>
<button type="submit" class="btn btn-danger" name="command" value="ResetPassword" id="ResetPassword">Reset</button> <button type="submit" class="btn btn-danger" name="command" value="ResetPassword" id="ResetPassword">Reset</button>
@ -65,13 +64,13 @@
<div class="my-4"> <div class="my-4">
<button class="d-inline-flex align-items-center btn btn-link text-primary fw-semibold p-0" type="button" id="AdvancedSettingsButton" data-bs-toggle="collapse" data-bs-target="#AdvancedSettings" aria-expanded="false" aria-controls="AdvancedSettings"> <button class="d-inline-flex align-items-center btn btn-link text-primary fw-semibold p-0" type="button" id="AdvancedSettingsButton" data-bs-toggle="collapse" data-bs-target="#AdvancedSettings" aria-expanded="false" aria-controls="AdvancedSettings">
<vc:icon symbol="caret-down"/> <vc:icon symbol="caret-down"/>
<span class="ms-1">Advanced settings</span> <span class="ms-1" text-translate="true">Advanced settings</span>
</button> </button>
<div id="AdvancedSettings" class="collapse"> <div id="AdvancedSettings" class="collapse">
<div class="pt-3 pb-1"> <div class="pt-3 pb-1">
<div class="d-flex"> <div class="d-flex">
<input asp-for="Settings.EnabledCertificateCheck" type="checkbox" class="btcpay-toggle me-3" /> <input asp-for="Settings.EnabledCertificateCheck" type="checkbox" class="btcpay-toggle me-3" />
<label asp-for="Settings.EnabledCertificateCheck" class="form-check-label">TLS certificate security checks</label> <label asp-for="Settings.EnabledCertificateCheck" class="form-check-label" text-translate="true">TLS certificate security checks</label>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,13 +1,13 @@
@model BTCPayServer.Models.EmailsViewModel @model BTCPayServer.Models.EmailsViewModel
<h3 class="my-3">Testing</h3> <h3 class="my-3" text-translate="true">Testing</h3>
<div class="row"> <div class="row">
<div class="col-xl-10 col-xxl-constrain"> <div class="col-xl-10 col-xxl-constrain">
<div class="form-group"> <div class="form-group">
<label asp-for="TestEmail" class="form-label">To test your settings, enter an email address</label> <label asp-for="TestEmail" class="form-label" text-translate="true">To test your settings, enter an email address</label>
<input asp-for="TestEmail" placeholder="Firstname Lastname <email@example.com>" class="form-control" /> <input asp-for="TestEmail" placeholder="Firstname Lastname <email@example.com>" class="form-control" />
<span asp-validation-for="TestEmail" class="text-danger"></span> <span asp-validation-for="TestEmail" class="text-danger"></span>
</div> </div>
<button type="submit" class="btn btn-secondary mt-2" name="command" value="Test" id="Test">Send Test Email</button> <button type="submit" class="btn btn-secondary mt-2" name="command" value="Test" id="Test" text-translate="true">Send Test Email</button>
</div> </div>
</div> </div>

View File

@ -42,17 +42,17 @@
asp-route-sortOrder="@(nextRoleSortOrder ?? "asc")" asp-route-sortOrder="@(nextRoleSortOrder ?? "asc")"
class="text-nowrap" class="text-nowrap"
title="@(nextRoleSortOrder == "desc" ? sortByAsc : sortByDesc)"> title="@(nextRoleSortOrder == "desc" ? sortByAsc : sortByDesc)">
Role <span text-translate="true">Role</span>
<vc:icon symbol="actions-sort-alpha-@(roleSortOrder ?? nextRoleSortOrder ?? "desc")" /> <vc:icon symbol="actions-sort-alpha-@(roleSortOrder ?? nextRoleSortOrder ?? "desc")" />
</a> </a>
</th> </th>
<th>Scope</th> <th text-translate="true">Scope</th>
<th>Permissions</th> <th text-translate="true">Permissions</th>
@if (showInUseColumn) @if (showInUseColumn)
{ {
<th class="text-center w-75px">In use</th> <th class="text-center w-75px" text-translate="true">In use</th>
} }
<th class="actions-col" permission="@permission">Actions</th> <th class="actions-col" permission="@permission" text-translate="true">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -64,7 +64,7 @@
<span>@role.Role</span> <span>@role.Role</span>
@if (Model.DefaultRole == role.Id) @if (Model.DefaultRole == role.Id)
{ {
<span class="badge bg-info"> <span class="badge bg-info" text-translate="true">
Default Default
</span> </span>
} }
@ -73,13 +73,13 @@
<td> <td>
@if (role.IsServerRole) @if (role.IsServerRole)
{ {
<span class="badge bg-dark"> <span class="badge bg-dark" text-translate="true">
Server-wide Server-wide
</span> </span>
} }
else else
{ {
<span class="badge bg-light"> <span class="badge bg-light" text-translate="true">
Store-level Store-level
</span> </span>
} }
@ -89,7 +89,7 @@
{ {
<span class="info-note text-warning"> <span class="info-note text-warning">
<vc:icon symbol="warning"/> <vc:icon symbol="warning"/>
No policies <span text-translate="true">No policies</span>
</span> </span>
} }
else else
@ -117,10 +117,10 @@
<div class="d-inline-flex align-items-center gap-3"> <div class="d-inline-flex align-items-center gap-3">
@if (role.IsServerRole && Model.DefaultRole != role.Id) @if (role.IsServerRole && Model.DefaultRole != role.Id)
{ {
<a permission="@Policies.CanModifyServerSettings" asp-action="SetDefaultRole" asp-route-role="@role.Role" asp-controller="UIServer" id="SetDefault">Set as default</a> <a permission="@Policies.CanModifyServerSettings" asp-action="SetDefaultRole" asp-route-role="@role.Role" asp-controller="UIServer" id="SetDefault" text-translate="true">Set as default</a>
} }
<a permission="@(role.IsServerRole ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings)" asp-action="CreateOrEditRole" asp-route-storeId="@storeId" asp-route-role="@role.Role" asp-controller="@(role.IsServerRole ? "UIServer" : "UIStores")">Edit</a> <a permission="@(role.IsServerRole ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings)" asp-action="CreateOrEditRole" asp-route-storeId="@storeId" asp-route-role="@role.Role" asp-controller="@(role.IsServerRole ? "UIServer" : "UIStores")" text-translate="true">Edit</a>
<a permission="@(role.IsServerRole ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings)" asp-action="DeleteRole" asp-route-storeId="@storeId" asp-route-role="@role.Role" asp-controller="@(role.IsServerRole ? "UIServer" : "UIStores")">Remove</a> <a permission="@(role.IsServerRole ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings)" asp-action="DeleteRole" asp-route-storeId="@storeId" asp-route-role="@role.Role" asp-controller="@(role.IsServerRole ? "UIServer" : "UIStores")" text-translate="true">Remove</a>
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -129,7 +129,7 @@
</main> </main>
<footer class="store-footer"> <footer class="store-footer">
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener"> <a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
Powered by <partial name="_StoreFooterLogo" /> <span text-translate="true">Powered by</span> <partial name="_StoreFooterLogo" />
</a> </a>
</footer> </footer>
</div> </div>
@ -244,7 +244,7 @@
<td colspan="2" class="pt-4"> <td colspan="2" class="pt-4">
<button id="CartSubmit" class="btn btn-primary btn-lg w-100" :disabled="payButtonLoading" type="submit"> <button id="CartSubmit" class="btn btn-primary btn-lg w-100" :disabled="payButtonLoading" type="submit">
<div v-if="payButtonLoading" class="spinner-border spinner-border-sm" id="pay-button-spinner" role="status"> <div v-if="payButtonLoading" class="spinner-border spinner-border-sm" id="pay-button-spinner" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
<template v-else>Pay</template> <template v-else>Pay</template>
</button> </button>

View File

@ -29,7 +29,7 @@
} }
<footer class="store-footer"> <footer class="store-footer">
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener"> <a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
Powered by <partial name="_StoreFooterLogo" /> <span text-translate="true">Powered by</span> <partial name="_StoreFooterLogo" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -4,7 +4,7 @@
<div class="input-group"> <div class="input-group">
<span class="input-group-text">@Model.CurrencySymbol</span> <span class="input-group-text">@Model.CurrencySymbol</span>
<input class="form-control" type="number" step="@Model.Step" name="amount" placeholder="Amount"> <input class="form-control" type="number" step="@Model.Step" name="amount" placeholder="Amount">
<button class="btn btn-primary" type="submit">Pay</button> <button class="btn btn-primary" type="submit" text-translate="true">Pay</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -133,7 +133,7 @@ else
</main> </main>
<footer class="store-footer d-print-none"> <footer class="store-footer d-print-none">
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener"> <a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
Powered by <partial name="_StoreFooterLogo" /> <span text-translate="true">Powered by</span> <partial name="_StoreFooterLogo" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -8,7 +8,7 @@
<h5 class="modal-title">Recent Transactions</h5> <h5 class="modal-title">Recent Transactions</h5>
<button type="button" class="btn btn-link px-3 py-0" aria-label="Refresh" v-on:click="loadRecentTransactions" :disabled="recentTransactionsLoading" id="RecentTransactionsRefresh"> <button type="button" class="btn btn-link px-3 py-0" aria-label="Refresh" v-on:click="loadRecentTransactions" :disabled="recentTransactionsLoading" id="RecentTransactionsRefresh">
<vc:icon symbol="actions-refresh"/> <vc:icon symbol="actions-refresh"/>
<span v-if="recentTransactionsLoading" class="visually-hidden">Loading...</span> <span v-if="recentTransactionsLoading" class="visually-hidden" text-translate="true">Loading...</span>
</button> </button>
<button type="button" class="btn-close py-3" aria-label="Close" v-on:click="hideRecentTransactions"> <button type="button" class="btn-close py-3" aria-label="Close" v-on:click="hideRecentTransactions">
<vc:icon symbol="close"/> <vc:icon symbol="close"/>

View File

@ -55,7 +55,14 @@
@if (item.Inventory.HasValue) @if (item.Inventory.HasValue)
{ {
<span class="badge text-bg-warning"> <span class="badge text-bg-warning">
@(item.Inventory > 0 ? $"{item.Inventory} left" : "Sold out") @if (item.Inventory > 0)
{
<span text-translate="true">@ViewLocalizer["{0} left", item.Inventory.ToString()]</span>
}
else
{
<span text-translate="true">Sold out</span>
}
</span> </span>
} }
</div> </div>
@ -88,8 +95,8 @@
<div class="col posItem posItem--displayed posItem--last@(Model.Items.Length == 0 ? " posItem--first" : null)"> <div class="col posItem posItem--displayed posItem--last@(Model.Items.Length == 0 ? " posItem--first" : null)">
<div class="card h-100 px-0"> <div class="card h-100 px-0">
<div class="card-body p-3 d-flex flex-column gap-2 mb-auto"> <div class="card-body p-3 d-flex flex-column gap-2 mb-auto">
<h5 class="card-title">Custom Amount</h5> <h5 class="card-title" text-translate="true">Custom Amount</h5>
<p class="card-text">Create invoice to pay custom amount</p> <p class="card-text" text-translate="true">Create invoice to pay custom amount</p>
</div> </div>
<div class="card-footer bg-transparent border-0 pb-3"> <div class="card-footer bg-transparent border-0 pb-3">
<form method="post" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" autocomplete="off"> <form method="post" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" autocomplete="off">
@ -107,7 +114,7 @@
</main> </main>
<footer class="store-footer"> <footer class="store-footer">
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener"> <a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
Powered by <partial name="_StoreFooterLogo" /> <span text-translate="true">Powered by</span> <partial name="_StoreFooterLogo" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -69,9 +69,11 @@
</div> </div>
<button class="btn btn-lg btn-primary mx-3" type="submit" :disabled="payButtonLoading || totalNumeric <= 0" id="pay-button"> <button class="btn btn-lg btn-primary mx-3" type="submit" :disabled="payButtonLoading || totalNumeric <= 0" id="pay-button">
<div v-if="payButtonLoading" class="spinner-border spinner-border-sm" id="pay-button-spinner" role="status"> <div v-if="payButtonLoading" class="spinner-border spinner-border-sm" id="pay-button-spinner" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
<template v-else>Charge</template> <template v-else>
<span text-translate="true">Charge</span>
</template>
</button> </button>
<partial name="PointOfSale/Public/RecentTransactions" model="Model"/> <partial name="PointOfSale/Public/RecentTransactions" model="Model"/>
<button type="button" class="btn btn-link p-1" data-bs-toggle="modal" data-bs-target="#RecentTransactions" id="RecentTransactionsToggle" permission="@Policies.CanViewInvoices"> <button type="button" class="btn btn-link p-1" data-bs-toggle="modal" data-bs-target="#RecentTransactions" id="RecentTransactionsToggle" permission="@Policies.CanViewInvoices">

View File

@ -7,11 +7,11 @@
@if (Model.StoreId is not null) @if (Model.StoreId is not null)
{ {
<h1 text-translate="true">@ViewLocalizer["Store: {0}", @Model.StoreId]</h1> <h1 text-translate="true">@ViewLocalizer["Store: {0}", Model.StoreId]</h1>
} }
else else
{ {
<h1 text-translate="true">No scope</h1> <h1 text-translate="true" text-translate="true">No scope</h1>
} }
<ul> <ul>

View File

@ -11,7 +11,7 @@
@if (isEmailConfigured) @if (isEmailConfigured)
{ {
<p> <p text-translate="true">
We all forget passwords every now and then. Just provide email address tied to We all forget passwords every now and then. Just provide email address tied to
your account and we'll start the process of helping you recover your account. your account and we'll start the process of helping you recover your account.
</p> </p>
@ -27,13 +27,13 @@
<span asp-validation-for="Email" class="text-danger"></span> <span asp-validation-for="Email" class="text-danger"></span>
</div> </div>
<div class="form-group mt-4"> <div class="form-group mt-4">
<button type="submit" class="btn btn-primary btn-lg w-100">Submit</button> <button type="submit" class="btn btn-primary btn-lg w-100" text-translate="true">Submit</button>
</div> </div>
</form> </form>
} }
else else
{ {
<p>Email password reset functionality is not configured for this server. Please contact the server administrator to assist with account recovery.</p> <p text-translate="true">Email password reset functionality is not configured for this server. Please contact the server administrator to assist with account recovery.</p>
<p> <p>
If you are the administrator, please follow these steps to If you are the administrator, please follow these steps to
<a href="https://docs.btcpayserver.org/Notifications/#smtp-email-setup" target="_blank" rel="noreferrer noopener">configure email password resets</a> <a href="https://docs.btcpayserver.org/Notifications/#smtp-email-setup" target="_blank" rel="noreferrer noopener">configure email password resets</a>
@ -43,5 +43,5 @@ else
} }
<p class="text-center mt-2 mb-0"> <p class="text-center mt-2 mb-0">
<a id="Login" style="font-size:1.15rem" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]">Log in</a> <a id="Login" style="font-size:1.15rem" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" text-translate="true">Log in</a>
</p> </p>

View File

@ -3,8 +3,8 @@
Layout = "_LayoutSignedOut"; Layout = "_LayoutSignedOut";
} }
<p>Please check your email to reset your password.</p> <p text-translate="true">Please check your email to reset your password.</p>
<p class="text-center mt-2 mb-0"> <p class="text-center mt-2 mb-0">
<a id="Login" style="font-size:1.15rem" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]">Log in</a> <a id="Login" style="font-size:1.15rem" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" text-translate="true">Log in</a>
</p> </p>

View File

@ -6,13 +6,13 @@
@if (Model is null) @if (Model is null)
{ {
<p class="mb-0">This account has been locked out because of multiple invalid login attempts. Please try again later.</p> <p class="mb-0" text-translate="true">This account has been locked out because of multiple invalid login attempts. Please try again later.</p>
} }
else if (DateTimeOffset.MaxValue - Model.Value < TimeSpan.FromSeconds(1)) else if (DateTimeOffset.MaxValue - Model.Value < TimeSpan.FromSeconds(1))
{ {
<p class="mb-0">Your account has been disabled. Please contact server administrator.</p> <p class="mb-0" text-translate="true">Your account has been disabled. Please contact server administrator.</p>
} }
else else
{ {
<p class="mb-0">This account has been locked out. Please try again @Model.Value.ToBrowserDate(ViewsRazor.DateDisplayFormat.Relative).</p> <p class="mb-0"><span text-translate="true">This account has been locked out. Please try again</span> @Model.Value.ToBrowserDate(ViewsRazor.DateDisplayFormat.Relative).</p>
} }

View File

@ -21,7 +21,7 @@
<div class="form-group"> <div class="form-group">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<label asp-for="Password" class="form-label"></label> <label asp-for="Password" class="form-label"></label>
<a asp-action="ForgotPassword" text-translate="true" tabindex="-1">Forgot password?</a> <a asp-action="ForgotPassword" text-translate="true" tabindex="-1" text-translate="true">Forgot password?</a>
</div> </div>
<div class="input-group d-flex"> <div class="input-group d-flex">
<input asp-for="Password" class="form-control" required /> <input asp-for="Password" class="form-control" required />
@ -35,7 +35,7 @@
</div> </div>
<div class="form-group mt-4"> <div class="form-group mt-4">
<div class="btn-group w-100"> <div class="btn-group w-100">
<button type="submit" class="btn btn-primary btn-lg w-100" id="LoginButton"><span class="ps-3" text-translate="true">Sign in</span></button> <button type="submit" class="btn btn-primary btn-lg w-100" id="LoginButton"><span class="ps-3" text-translate="true" text-translate="true">Sign in</span></button>
<button type="button" class="btn btn-outline-primary btn-lg w-auto only-for-js" data-bs-toggle="modal" data-bs-target="#scanModal" title="Scan Login code with camera"> <button type="button" class="btn btn-outline-primary btn-lg w-auto only-for-js" data-bs-toggle="modal" data-bs-target="#scanModal" title="Scan Login code with camera">
<vc:icon symbol="scan-qr" /> <vc:icon symbol="scan-qr" />
</button> </button>
@ -49,7 +49,7 @@
@if (!PoliciesSettings.LockSubscription) @if (!PoliciesSettings.LockSubscription)
{ {
<p class="text-center mt-2 mb-0"> <p class="text-center mt-2 mb-0">
<a id="Register" style="font-size:1.15rem" asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]" text-translate="true">Create your account</a> <a id="Register" style="font-size:1.15rem" asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]" text-translate="true" text-translate="true">Create your account</a>
</p> </p>
} }

View File

@ -1,7 +1,7 @@
@model LoginWith2faViewModel @model LoginWith2faViewModel
<div class="twoFaBox"> <div class="twoFaBox">
<h2 class="h3 mb-3">Two-Factor Authentication</h2> <h2 class="h3 mb-3" text-translate="true">Two-Factor Authentication</h2>
<form method="post" asp-route-returnUrl="@ViewData["ReturnUrl"]" asp-action="LoginWith2fa"> <form method="post" asp-route-returnUrl="@ViewData["ReturnUrl"]" asp-action="LoginWith2fa">
@if (!ViewContext.ModelState.IsValid) @if (!ViewContext.ModelState.IsValid)
{ {
@ -19,7 +19,7 @@
<span asp-validation-for="RememberMachine" class="text-danger"></span> <span asp-validation-for="RememberMachine" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<button type="submit" class="btn btn-primary">Log in</button> <button type="submit" class="btn btn-primary" text-translate="true">Log in</button>
</div> </div>
</form> </form>
<p class="text-secondary mt-4 mb-0"> <p class="text-secondary mt-4 mb-0">

View File

@ -7,19 +7,19 @@
<input type="hidden" asp-for="UserId"/> <input type="hidden" asp-for="UserId"/>
<input type="hidden" asp-for="RememberMe"/> <input type="hidden" asp-for="RememberMe"/>
</form> </form>
<h2 class="h3 mb-3">FIDO2 Authentication</h2> <h2 class="h3 mb-3" text-translate="true">FIDO2 Authentication</h2>
<p>Insert your security device and proceed.</p> <p text-translate="true">Insert your security device and proceed.</p>
<div id="info-message" class="alert alert-info mb-0 d-none"> <div id="info-message" class="alert alert-info mb-0 d-none">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="spinner-border spinner-border-sm me-2 fido-running" role="status"> <div class="spinner-border spinner-border-sm me-2 fido-running" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
<span>If your security device has a button, tap on it.</span> <span text-translate="true">If your security device has a button, tap on it.</span>
</div> </div>
</div> </div>
<button id="btn-start" class="btn btn-primary d-none" type="button">Start</button> <button id="btn-start" class="btn btn-primary d-none" type="button" text-translate="true">Start</button>
<p id="error-message" class="d-none alert alert-danger mb-4"></p> <p id="error-message" class="d-none alert alert-danger mb-4"></p>
<button id="btn-retry" class="btn btn-secondary d-none" type="button">Retry</button> <button id="btn-retry" class="btn btn-secondary d-none" type="button" text-translate="true">Retry</button>
<script> <script>
document.getElementById('btn-retry').addEventListener('click', () => window.location.reload()) document.getElementById('btn-retry').addEventListener('click', () => window.location.reload())

View File

@ -12,8 +12,8 @@
<input type="hidden" asp-for="LNURLEndpoint"/> <input type="hidden" asp-for="LNURLEndpoint"/>
<input type="hidden" asp-for="UserId"/> <input type="hidden" asp-for="UserId"/>
</form> </form>
<h2 class="h3 mb-3">LNURL Authentication</h2> <h2 class="h3 mb-3" text-translate="true">LNURL Authentication</h2>
<p>Scan the QR code with your Lightning wallet to sign in.</p> <p text-translate="true">Scan the QR code with your Lightning wallet to sign in.</p>
<div class="align-items-center" style="width:256px"> <div class="align-items-center" style="width:256px">
<ul class="nav my-3 btcpay-pills align-items-center gap-2"> <ul class="nav my-3 btcpay-pills align-items-center gap-2">
@for (var i = 0; i < formats.Count; i++) @for (var i = 0; i < formats.Count; i++)
@ -34,7 +34,7 @@
<div class="qr-container" style="min-height:256px"> <div class="qr-container" style="min-height:256px">
<vc:qr-code data="@mode.Value" /> <vc:qr-code data="@mode.Value" />
</div> </div>
<a href="@mode.Value" class="btn btn-primary mt-3" rel="noreferrer noopener"> <a href="@mode.Value" class="btn btn-primary mt-3" rel="noreferrer noopener" text-translate="true">
Open in wallet Open in wallet
</a> </a>
</div> </div>

View File

@ -4,7 +4,7 @@
ViewData["Title"] = "Recovery code verification"; ViewData["Title"] = "Recovery code verification";
} }
<p> <p text-translate="true">
You have requested to login with a recovery code. This login will not be remembered until you provide You have requested to login with a recovery code. This login will not be remembered until you provide
an authenticator app code at login or disable 2FA and login again. an authenticator app code at login or disable 2FA and login again.
</p> </p>
@ -14,7 +14,7 @@
<input asp-for="RecoveryCode" class="form-control" autocomplete="off" /> <input asp-for="RecoveryCode" class="form-control" autocomplete="off" />
<span asp-validation-for="RecoveryCode" class="text-danger"></span> <span asp-validation-for="RecoveryCode" class="text-danger"></span>
</div> </div>
<button type="submit" class="btn btn-primary">Log in</button> <button type="submit" class="btn btn-primary" text-translate="true">Log in</button>
</form> </form>
@section PageFootContent { @section PageFootContent {

View File

@ -41,5 +41,5 @@
</fieldset> </fieldset>
</form> </form>
<p class="text-center mt-2 mb-0"> <p class="text-center mt-2 mb-0">
<a id="Login" style="font-size:1.15rem" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]">Log in</a> <a id="Login" style="font-size:1.15rem" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" text-translate="true">Log in</a>
</p> </p>

View File

@ -21,7 +21,7 @@ else if (Model.LoginWith2FaViewModel == null && Model.LoginWithFido2ViewModel ==
{ {
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<h2 class="bg-danger">2FA and U2F/FIDO2 and LNURL-Auth Authentication Methods are not available. Please go to the https endpoint.</h2> <h2 class="bg-danger" text-translate="true">2FA and U2F/FIDO2 and LNURL-Auth Authentication Methods are not available. Please go to the https endpoint.</h2>
<hr class="danger"> <hr class="danger">
</div> </div>
</div> </div>

View File

@ -3,6 +3,6 @@
} }
<h2 text-translate="true">@ViewData["Title"]</h2> <h2 text-translate="true">@ViewData["Title"]</h2>
<p> <p text-translate="true">
You have successfully signed out. You have successfully signed out.
</p> </p>

View File

@ -59,7 +59,7 @@
class="text-nowrap" class="text-nowrap"
title="@(appNameSortOrder == "desc" ? sortByDesc : sortByAsc)" title="@(appNameSortOrder == "desc" ? sortByDesc : sortByAsc)"
> >
Name <span text-translate="true">Name</span>
<vc:icon symbol="actions-sort-alpha-@(appNameSortOrder ?? nextAppNameSortOrder ?? "desc")" /> <vc:icon symbol="actions-sort-alpha-@(appNameSortOrder ?? nextAppNameSortOrder ?? "desc")" />
</a> </a>
</th> </th>
@ -72,7 +72,7 @@
class="text-nowrap" class="text-nowrap"
title="@(appTypeSortOrder == "desc" ? sortByDesc : sortByAsc)" title="@(appTypeSortOrder == "desc" ? sortByDesc : sortByAsc)"
> >
App Type <span text-translate="true">App Type</span>
<vc:icon symbol="actions-sort-alpha-@(appTypeSortOrder ?? nextAppTypeSortOrder ?? "desc")" /> <vc:icon symbol="actions-sort-alpha-@(appTypeSortOrder ?? nextAppTypeSortOrder ?? "desc")" />
</a> </a>
</th> </th>
@ -85,7 +85,7 @@
class="text-nowrap" class="text-nowrap"
title="@(storeNameSortOrder == "desc" ? sortByDesc : sortByAsc)" title="@(storeNameSortOrder == "desc" ? sortByDesc : sortByAsc)"
> >
Store <span text-translate="true">Store</span>
<vc:icon symbol="actions-sort-alpha-@(storeNameSortOrder ?? nextStoreNameSortOrder ?? "desc")" /> <vc:icon symbol="actions-sort-alpha-@(storeNameSortOrder ?? nextStoreNameSortOrder ?? "desc")" />
</a> </a>
</th> </th>
@ -103,7 +103,7 @@
<span not-permission="@Policies.CanModifyStoreSettings">@app.AppName</span> <span not-permission="@Policies.CanModifyStoreSettings">@app.AppName</span>
@if (app.Archived) @if (app.Archived)
{ {
<span class="badge bg-info ms-2">archived</span> <span class="badge bg-info ms-2" text-translate="true">archived</span>
} }
</td> </td>
<td> <td>
@ -130,7 +130,7 @@
} }
else else
{ {
<p class="text-secondary mt-3"> <p class="text-secondary mt-3" text-translate="true">
There are no apps yet. There are no apps yet.
</p> </p>
} }

View File

@ -13,5 +13,5 @@
} }
<p class="mt-4">A generic error occurred (HTTP Code: @Model)</p> <p class="mt-4">A generic error occurred (HTTP Code: @Model)</p>
<p>Please consult the server log for more details.</p> <p text-translate="true">Please consult the server log for more details.</p>
<a href="/">Navigate back to home</a> <a href="/" text-translate="true">Navigate back to home</a>

View File

@ -7,16 +7,16 @@
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a asp-controller="UIManage" asp-action="TwoFactorAuthentication">Two Factor Authentication</a> <a asp-controller="UIManage" asp-action="TwoFactorAuthentication" text-translate="true">Two Factor Authentication</a>
</li> </li>
<li class="breadcrumb-item active" aria-current="page">Register Device</li> <li class="breadcrumb-item active" aria-current="page" text-translate="true">Register Device</li>
</ol> </ol>
<h2 text-translate="true">@ViewData["Title"]</h2> <h2 text-translate="true">@ViewData["Title"]</h2>
</nav> </nav>
</div> </div>
<partial name="_StatusMessage" /> <partial name="_StatusMessage" />
<p>Insert your security device and proceed.</p> <p text-translate="true">Insert your security device and proceed.</p>
<form asp-action="CreateResponse" id="registerForm"> <form asp-action="CreateResponse" id="registerForm">
<input type="hidden" name="data" id="data"/> <input type="hidden" name="data" id="data"/>
@ -27,14 +27,14 @@
<div id="info-message" class="alert alert-info mb-3 d-none"> <div id="info-message" class="alert alert-info mb-3 d-none">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="spinner-border spinner-border-sm me-2 fido-running" role="status"> <div class="spinner-border spinner-border-sm me-2 fido-running" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
<span>If your security device has a button, tap on it.</span> <span text-translate="true">If your security device has a button, tap on it.</span>
</div> </div>
</div> </div>
<button id="btn-start" class="btn btn-primary d-none" type="button">Start</button> <button id="btn-start" class="btn btn-primary d-none" type="button" text-translate="true">Start</button>
<p id="error-message" class="d-none alert alert-danger"></p> <p id="error-message" class="d-none alert alert-danger"></p>
<a id="btn-retry" class="btn btn-secondary d-none">Retry</a> <a id="btn-retry" class="btn btn-secondary d-none" text-translate="true">Retry</a>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@
<div class="sticky-header"> <div class="sticky-header">
<h2> <h2>
<span>@ViewData["Title"]</span> <span text-translate="true">@ViewData["Title"]</span>
<a href="https://docs.btcpayserver.org/Forms" target="_blank" rel="noreferrer noopener" title="More information..."> <a href="https://docs.btcpayserver.org/Forms" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info" /> <vc:icon symbol="info" />
</a> </a>
@ -27,8 +27,8 @@
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th text-translate="true">Name</th>
<th class="actions-col" permission="@Policies.CanModifyStoreSettings">Actions</th> <th text-translate="true" class="actions-col" permission="@Policies.CanModifyStoreSettings">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -40,8 +40,8 @@
<a asp-action="ViewPublicForm" asp-route-formId="@item.Id" id="View-@item.Name" not-permission="@Policies.CanModifyStoreSettings">@item.Name</a> <a asp-action="ViewPublicForm" asp-route-formId="@item.Id" id="View-@item.Name" not-permission="@Policies.CanModifyStoreSettings">@item.Name</a>
</td> </td>
<td class="actions-col" permission="@Policies.CanModifyStoreSettings"> <td class="actions-col" permission="@Policies.CanModifyStoreSettings">
<a asp-action="Remove" asp-route-storeId="@item.StoreId" asp-route-id="@item.Id" id="Remove-@item.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-confirm-input="DELETE">Remove</a> - <a asp-action="Remove" asp-route-storeId="@item.StoreId" asp-route-id="@item.Id" id="Remove-@item.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-confirm-input="DELETE" text-translate="true">Remove</a> -
<a asp-action="ViewPublicForm" asp-route-formId="@item.Id" id="View-@item.Name">View</a> <a asp-action="ViewPublicForm" asp-route-formId="@item.Id" id="View-@item.Name" text-translate="true">View</a>
</td> </td>
</tr> </tr>
} }
@ -51,7 +51,7 @@
} }
else else
{ {
<p class="text-secondary"> <p class="text-secondary" text-translate="true">
There are no forms yet. There are no forms yet.
</p> </p>
} }

View File

@ -16,10 +16,10 @@
@section PageFootContent { @section PageFootContent {
<datalist id="special-field-names"> <datalist id="special-field-names">
<option value="invoice_amount">Determine the generated invoice amount</option> <option text-translate="true" value="invoice_amount">Determine the generated invoice amount</option>
<option value="invoice_currency">Determine the generated invoice currency</option> <option text-translate="true" value="invoice_currency">Determine the generated invoice currency</option>
<option value="invoice_amount_adjustment">Adjusts the generated invoice amount — use as a prefix to have multiple adjustment fields</option> <option text-translate="true" value="invoice_amount_adjustment">Adjusts the generated invoice amount — use as a prefix to have multiple adjustment fields</option>
<option value="invoice_amount_multiply_adjustment">Adjusts the generated invoice amount by multiplying with this value — use as a prefix to have multiple adjustment fields</option> <option text-translate="true" value="invoice_amount_multiply_adjustment">Adjusts the generated invoice amount by multiplying with this value — use as a prefix to have multiple adjustment fields</option>
</datalist> </datalist>
<template id="form-template-email"> <template id="form-template-email">
@FormDataService.StaticFormEmail @FormDataService.StaticFormEmail
@ -42,14 +42,14 @@
<div class="form-group"> <div class="form-group">
<label for="field-editor-field-name" class="form-label" data-required>Name</label> <label for="field-editor-field-name" class="form-label" data-required>Name</label>
<input id="field-editor-field-name" class="form-control" list="special-field-names" required v-model="field.name" /> <input id="field-editor-field-name" class="form-control" list="special-field-names" required v-model="field.name" />
<div class="form-text">The name of the field in the invoice's metadata.</div> <div text-translate="true" class="form-text">The name of the field in the invoice's metadata.</div>
<div class="form-text text-info" v-if="field.name === 'invoice_currency'">The configured name means the value of this field will determine the invoice currency for public forms.</div> <div text-translate="true" class="form-text text-info" v-if="field.name === 'invoice_currency'">The configured name means the value of this field will determine the invoice currency for public forms.</div>
<div class="form-text text-info" v-if="field.name === 'invoice_amount'">The configured name means the value of this field will determine the invoice amount for public forms.</div> <div text-translate="true" class="form-text text-info" v-if="field.name === 'invoice_amount'">The configured name means the value of this field will determine the invoice amount for public forms.</div>
<div class="form-text text-info" v-if="field.name && field.name.startsWith('invoice_amount_adjustment')">The configured name means the value of this field will adjust the invoice amount for public forms and the point of sale app.</div> <div text-translate="true" class="form-text text-info" v-if="field.name && field.name.startsWith('invoice_amount_adjustment')">The configured name means the value of this field will adjust the invoice amount for public forms and the point of sale app.</div>
<div class="form-text text-info" v-if="field.name && field.name.startsWith('invoice_amount_multiply_adjustment')">The configured name means the value of this field will adjust the invoice amount by multiplying it for public forms and the point of sale app.</div> <div text-translate="true" class="form-text text-info" v-if="field.name && field.name.startsWith('invoice_amount_multiply_adjustment')">The configured name means the value of this field will adjust the invoice amount by multiplying it for public forms and the point of sale app.</div>
</div> </div>
<div class="form-group" v-if="field.type === 'select'"> <div class="form-group" v-if="field.type === 'select'">
<h5 class="mt-2">Options</h5> <h5 class="mt-2" text-translate="true">Options</h5>
<div class="options" v-sortable="{ handle: '.drag', onUpdate: sortOptions }"> <div class="options" v-sortable="{ handle: '.drag', onUpdate: sortOptions }">
<div v-for="(option, index) in field.options" :key="option.value" class="d-flex align-items-start gap-2 pt-3"> <div v-for="(option, index) in field.options" :key="option.value" class="d-flex align-items-start gap-2 pt-3">
<button type="button" class="btn b-0 control drag"> <button type="button" class="btn b-0 control drag">
@ -70,23 +70,23 @@
</div> </div>
<button type="button" class="btn btn-link px-1 py-2 gap-1 add fw-semibold d-inline-flex align-items-center" v-on:click.stop="addOption($event)"> <button type="button" class="btn btn-link px-1 py-2 gap-1 add fw-semibold d-inline-flex align-items-center" v-on:click.stop="addOption($event)">
<vc:icon symbol="actions-add" /> <vc:icon symbol="actions-add" />
Add Option <span text-translate="true">Add Option</span>
</button> </button>
</div> </div>
<div class="form-group" v-if="field.type !== 'fieldset' && field.type !== 'mirror'"> <div class="form-group" v-if="field.type !== 'fieldset' && field.type !== 'mirror'">
<label for="field-editor-field-value" class="form-label">Default Value</label> <label for="field-editor-field-value" class="form-label" text-translate="true">Default Value</label>
<input id="field-editor-field-value" class="form-control" v-model="field.value" /> <input id="field-editor-field-value" class="form-control" v-model="field.value" />
</div> </div>
<div class="form-group" v-if="field.type === 'mirror'"> <div class="form-group" v-if="field.type === 'mirror'">
<label for="field-editor-field-mirror" class="form-label">Field to mirror</label> <label for="field-editor-field-mirror" class="form-label" text-translate="true">Field to mirror</label>
<select id="field-editor-field-mirror" class="form-select" v-model="field.value"> <select id="field-editor-field-mirror" class="form-select" v-model="field.value">
<option v-for="option in $root.allFields" v-if="option.name && option.name !== field.name" :key="option.name" :value="option.name" :selected="option.name === field.value" v-text="option.label || option.name"></option> <option v-for="option in $root.allFields" v-if="option.name && option.name !== field.name" :key="option.name" :value="option.name" :selected="option.name === field.value" v-text="option.label || option.name"></option>
</select> </select>
<div class="form-text">The chosen field's selected value will be copied to this field upon submission.</div> <div class="form-text" text-translate="true">The chosen field's selected value will be copied to this field upon submission.</div>
</div> </div>
<div class="form-group" v-if="field.type === 'mirror'"> <div class="form-group" v-if="field.type === 'mirror'">
<h5 class="mt-2">Value Mapper</h5> <h5 class="mt-2" text-translate="true">Value Mapper</h5>
<div class="form-text">The values being mirrored from another field will be mapped to another value if configured.</div> <div class="form-text" text-translate="true">The values being mirrored from another field will be mapped to another value if configured.</div>
<div class="options"> <div class="options">
<div v-if="field.valuemap" v-for="(v, k, index) in field.valuemap" :key="k" class="d-flex align-items-start gap-2 pt-3"> <div v-if="field.valuemap" v-for="(v, k, index) in field.valuemap" :key="k" class="d-flex align-items-start gap-2 pt-3">
<div class="field flex-grow-1"> <div class="field flex-grow-1">
@ -107,25 +107,25 @@
</div> </div>
<button type="button" class="btn btn-link px-1 py-2 gap-1 add fw-semibold d-inline-flex align-items-center" v-on:click.stop="addValueMap($event)"> <button type="button" class="btn btn-link px-1 py-2 gap-1 add fw-semibold d-inline-flex align-items-center" v-on:click.stop="addValueMap($event)">
<vc:icon symbol="actions-add" /> <vc:icon symbol="actions-add" />
Add mapped value <span text-translate="true">Add mapped value</span>
</button> </button>
</div> </div>
<div class="form-group" v-if="field.type !== 'fieldset' && field.type !== 'mirror'"> <div class="form-group" v-if="field.type !== 'fieldset' && field.type !== 'mirror'">
<label for="field-editor-field-helpText" class="form-label">Helper Text</label> <label for="field-editor-field-helpText" class="form-label" text-translate="true">Helper Text</label>
<input id="field-editor-field-helpText" class="form-control" v-model="field.helpText" /> <input id="field-editor-field-helpText" class="form-control" v-model="field.helpText" />
<div class="form-text">Additional text to provide an explanation for the field</div> <div class="form-text" text-translate="true">Additional text to provide an explanation for the field</div>
</div> </div>
<div class="form-group form-check" v-if="field.type !== 'fieldset' && field.type !== 'mirror'"> <div class="form-group form-check" v-if="field.type !== 'fieldset' && field.type !== 'mirror'">
<input id="field-editor-field-required" type="checkbox" class="form-check-input" v-model="field.required" /> <input id="field-editor-field-required" type="checkbox" class="form-check-input" v-model="field.required" />
<label for="field-editor-field-required" class="form-check-label">Required Field</label> <label for="field-editor-field-required" class="form-check-label" text-translate="true">Required Field</label>
</div> </div>
<div class="form-group form-check" v-if="field.type !== 'fieldset' && field.type !== 'select' && field.type !== 'mirror'"> <div class="form-group form-check" v-if="field.type !== 'fieldset' && field.type !== 'select' && field.type !== 'mirror'">
<input id="field-editor-field-constant" type="checkbox" class="form-check-input" v-model="field.constant" /> <input id="field-editor-field-constant" type="checkbox" class="form-check-input" v-model="field.constant" />
<label for="field-editor-field-constant" class="form-check-label">Constant</label> <label for="field-editor-field-constant" class="form-check-label" text-translate="true">Constant</label>
<div class="form-text">The user will not be able to change the field's value</div> <div class="form-text" text-translate="true">The user will not be able to change the field's value</div>
</div> </div>
</div> </div>
<div v-else>Select a field to edit</div> <div v-else text-translate="true">Select a field to edit</div>
</template> </template>
<template id="fields-editor"> <template id="fields-editor">
<div> <div>
@ -144,7 +144,7 @@
</div> </div>
<button type="button" class="btn btn-link py-0 px-2 mt-2 mb-2 gap-1 add fw-semibold d-inline-flex align-items-center" v-on:click.stop="$emit('add-field', $event, path)"> <button type="button" class="btn btn-link py-0 px-2 mt-2 mb-2 gap-1 add fw-semibold d-inline-flex align-items-center" v-on:click.stop="$emit('add-field', $event, path)">
<vc:icon symbol="actions-add" /> <vc:icon symbol="actions-add" />
Add Form Field <span text-translate="true">Add Form Field</span>
</button> </button>
</div> </div>
</template> </template>
@ -174,7 +174,7 @@
<template id="field-type-mirror"> <template id="field-type-mirror">
<div class="form-group mb-0"> <div class="form-group mb-0">
<label class="form-label" v-text="label" v-if="label"></label> <label class="form-label" v-text="label" v-if="label"></label>
<div class="form-text">Mirror of {{value}}</div> <div class="form-text"><span text-translate="true">Mirror of</span> {{value}}</div>
</div> </div>
</template> </template>
<template id="field-type-fieldset"> <template id="field-type-fieldset">
@ -196,9 +196,9 @@
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a asp-controller="UIForms" asp-action="FormsList" asp-route-storeId="@storeId">Forms</a> <a asp-controller="UIForms" asp-action="FormsList" asp-route-storeId="@storeId" text-translate="true">Forms</a>
</li> </li>
<li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li> <li class="breadcrumb-item active" aria-current="page" text-translate="true">@ViewData["Title"]</li>
</ol> </ol>
<h2> <h2>
@ViewData["Title"] @ViewData["Title"]
@ -211,7 +211,7 @@
<button id="page-primary" type="submit" class="btn btn-primary order-sm-1">Save</button> <button id="page-primary" type="submit" class="btn btn-primary order-sm-1">Save</button>
@if (!isNew) @if (!isNew)
{ {
<a class="btn btn-secondary" asp-action="ViewPublicForm" asp-route-formId="@formId" id="ViewForm">View</a> <a class="btn btn-secondary" asp-action="ViewPublicForm" asp-route-formId="@formId" id="ViewForm" text-translate="true">View</a>
} }
</div> </div>
</div> </div>
@ -230,7 +230,7 @@
<input asp-for="Public" type="checkbox" class="btcpay-toggle" /> <input asp-for="Public" type="checkbox" class="btcpay-toggle" />
<div> <div>
<label asp-for="Public"></label> <label asp-for="Public"></label>
<div class="form-text"> <div class="form-text" text-translate="true">
Standalone mode, which can be used to generate invoices Standalone mode, which can be used to generate invoices
independent of payment requests or apps. independent of payment requests or apps.
</div> </div>
@ -243,16 +243,16 @@
<div class="d-flex flex-wrap align-items-end justify-content-between gap-3 mb-3"> <div class="d-flex flex-wrap align-items-end justify-content-between gap-3 mb-3">
<ul class="nav nav-pills gap-4" role="tablist"> <ul class="nav nav-pills gap-4" role="tablist">
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link active" id="EditorTabButton" data-bs-toggle="pill" data-bs-target="#EditorTabPane" type="button" role="tab" aria-controls="EditorTabPane" aria-selected="true">Editor</button> <button class="nav-link active" id="EditorTabButton" data-bs-toggle="pill" data-bs-target="#EditorTabPane" type="button" role="tab" aria-controls="EditorTabPane" aria-selected="true" text-translate="true">Editor</button>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link" id="CodeTabButton" data-bs-toggle="pill" data-bs-target="#CodeTabPane" type="button" role="tab" aria-controls="CodeTabPane" aria-selected="false">Code</button> <button class="nav-link" id="CodeTabButton" data-bs-toggle="pill" data-bs-target="#CodeTabPane" type="button" role="tab" aria-controls="CodeTabPane" aria-selected="false" text-translate="true">Code</button>
</li> </li>
</ul> </ul>
<div class="d-flex align-items-center gap-2 mb-1"> <div class="d-flex align-items-center gap-2 mb-1">
<span class="fw-semibold">Templates</span> <span class="fw-semibold" text-translate="true">Templates</span>
<button type="button" class="btn btn-link p-0 fw-semibold" v-on:click="applyTemplate('email')" id="ApplyEmailTemplate">Email</button> <button type="button" class="btn btn-link p-0 fw-semibold" v-on:click="applyTemplate('email')" id="ApplyEmailTemplate" text-translate="true">Email</button>
<button type="button" class="btn btn-link p-0 fw-semibold" v-on:click="applyTemplate('address')" id="ApplyAddressTemplate">Address</button> <button type="button" class="btn btn-link p-0 fw-semibold" v-on:click="applyTemplate('address')" id="ApplyAddressTemplate" text-translate="true">Address</button>
</div> </div>
</div> </div>
<div class="tab-content"> <div class="tab-content">
@ -271,7 +271,7 @@
</div> </div>
<div class="col-xl-5 offcanvas-xl offcanvas-end" tabindex="-1" ref="editorOffcanvas"> <div class="col-xl-5 offcanvas-xl offcanvas-end" tabindex="-1" ref="editorOffcanvas">
<div class="offcanvas-header justify-content-between p-3"> <div class="offcanvas-header justify-content-between p-3">
<h5 class="offcanvas-title">Edit Field</h5> <h5 class="offcanvas-title" text-translate="true">Edit Field</h5>
<button type="button" class="btn-close" aria-label="Close" v-on:click="hideOffcanvas"> <button type="button" class="btn-close" aria-label="Close" v-on:click="hideOffcanvas">
<vc:icon symbol="close" /> <vc:icon symbol="close" />
</button> </button>
@ -283,7 +283,7 @@
</div> </div>
</div> </div>
<div class="tab-pane fade" id="CodeTabPane" role="tabpanel" aria-labelledby="CodeTabButton" tabindex="0"> <div class="tab-pane fade" id="CodeTabPane" role="tabpanel" aria-labelledby="CodeTabButton" tabindex="0">
<label asp-for="FormConfig" class="form-label" data-required>Form JSON</label> <label asp-for="FormConfig" class="form-label" data-required text-translate="true">Form JSON</label>
<textarea asp-for="FormConfig" class="form-control font-monospace" style="font-size:.85rem" rows="21" cols="21" v-model="configJSON" v-on:change="updateFromJSON"></textarea> <textarea asp-for="FormConfig" class="form-control font-monospace" style="font-size:.85rem" rows="21" cols="21" v-model="configJSON" v-on:change="updateFromJSON"></textarea>
<span asp-validation-for="FormConfig" class="text-danger"></span> <span asp-validation-for="FormConfig" class="text-danger"></span>
</div> </div>

View File

@ -48,7 +48,7 @@
</main> </main>
<footer class="store-footer"> <footer class="store-footer">
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener"> <a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
Powered by <partial name="_StoreFooterLogo" /> <span text-translate="true">Powered by</span> <partial name="_StoreFooterLogo" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -13,13 +13,13 @@
@if (!Model.HasStore) @if (!Model.HasStore)
{ {
<p class="lead text-secondary">To start accepting payments, set up a store.</p> <p class="lead text-secondary" text-translate="true">To start accepting payments, set up a store.</p>
<div class="list-group mt-4" id="SetupGuide"> <div class="list-group mt-4" id="SetupGuide">
<a asp-controller="UIUserStores" asp-action="CreateStore" id="SetupGuide-Store" class="list-group-item list-group-item-action d-flex align-items-center"> <a asp-controller="UIUserStores" asp-action="CreateStore" id="SetupGuide-Store" class="list-group-item list-group-item-action d-flex align-items-center">
<vc:icon symbol="wallet-new"/> <vc:icon symbol="wallet-new"/>
<div class="content"> <div class="content">
<h5 class="mb-0">Create your store</h5> <h5 class="mb-0" text-translate="true">Create your store</h5>
</div> </div>
<vc:icon symbol="caret-right"/> <vc:icon symbol="caret-right"/>
</a> </a>

View File

@ -20,23 +20,23 @@
</div> </div>
</form> </form>
<form id="mine-block" :action="`/i/${invoiceId}/mine-blocks`" method="post" v-on:submit.prevent="handleFormSubmit($event, 'mining')" v-if="displayMine"> <form id="mine-block" :action="`/i/${invoiceId}/mine-blocks`" method="post" v-on:submit.prevent="handleFormSubmit($event, 'mining')" v-if="displayMine">
<label for="BlockCount" class="control-label form-label">Mine to test processing and settlement</label> <label for="BlockCount" class="control-label form-label" text-translate="true">Mine to test processing and settlement</label>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<div class="input-group"> <div class="input-group">
<input id="BlockCount" name="BlockCount" type="number" step="1" min="1" class="form-control" value="1"/> <input id="BlockCount" name="BlockCount" type="number" step="1" min="1" class="form-control" value="1"/>
<div class="input-group-addon input-group-text">blocks</div> <div class="input-group-addon input-group-text" text-translate="true">blocks</div>
</div> </div>
<button class="btn btn-secondary flex-shrink-0 px-3 w-100px" type="submit" :disabled="mining" id="mine-block">Mine</button> <button class="btn btn-secondary flex-shrink-0 px-3 w-100px" type="submit" :disabled="mining" id="mine-block" text-translate="true">Mine</button>
</div> </div>
</form> </form>
<form id="expire-invoice" :action="`/i/${invoiceId}/expire`" method="post" v-on:submit.prevent="handleFormSubmit($event, 'expiring')" v-if="displayExpire"> <form id="expire-invoice" :action="`/i/${invoiceId}/expire`" method="post" v-on:submit.prevent="handleFormSubmit($event, 'expiring')" v-if="displayExpire">
<label for="ExpirySeconds" class="control-label form-label">Expire invoice in …</label> <label for="ExpirySeconds" class="control-label form-label" text-translate="true">Expire invoice in …</label>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<div class="input-group"> <div class="input-group">
<input id="ExpirySeconds" name="Seconds" type="number" step="1" min="0" class="form-control" value="20" /> <input id="ExpirySeconds" name="Seconds" type="number" step="1" min="0" class="form-control" value="20" />
<div class="input-group-addon input-group-text">seconds</div> <div class="input-group-addon input-group-text" text-translate="true">seconds</div>
</div> </div>
<button class="btn btn-secondary flex-shrink-0 px-3 w-100px" type="submit" :disabled="expiring" id="Expire">Expire</button> <button class="btn btn-secondary flex-shrink-0 px-3 w-100px" type="submit" :disabled="expiring" id="Expire" text-translate="true">Expire</button>
</div> </div>
</form> </form>
</section> </section>

View File

@ -16,30 +16,30 @@
<h1>Pay with @Model.StoreName</h1> <h1>Pay with @Model.StoreName</h1>
@if (Model.Status == "new") @if (Model.Status == "new")
{ {
<h1 class="text-danger">This payment method requires javascript.</h1> <h1 class="text-danger" text-translate="true">This payment method requires javascript.</h1>
} }
else if (Model.Status == "paid" || Model.Status == "complete" || Model.Status == "confirmed") else if (Model.Status == "paid" || Model.Status == "complete" || Model.Status == "confirmed")
{ {
<div> <div>
<p>This invoice has been paid</p> <p text-translate="true">This invoice has been paid</p>
</div> </div>
} }
else if (Model.Status == "expired" || Model.Status == "invalid") else if (Model.Status == "expired" || Model.Status == "invalid")
{ {
<div> <div>
<p>This invoice has expired</p> <p text-translate="true">This invoice has expired</p>
</div> </div>
} }
else else
{ {
<div> <div>
<p>Non-supported state of invoice</p> <p text-translate="true">Non-supported state of invoice</p>
</div> </div>
} }
<hr /> <hr />
<p> <p>
<a asp-action="Checkout" asp-route-invoiceId="@Model.InvoiceId">Go back to Javascript enabled invoice</a> <a asp-action="Checkout" asp-route-invoiceId="@Model.InvoiceId" text-translate="true">Go back to Javascript enabled invoice</a>
</p> </p>
@await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-noscript-end", model = Model }) @await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-noscript-end", model = Model })
</body> </body>

View File

@ -32,9 +32,9 @@
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a asp-action="ListInvoices" asp-route-storeId="@Model.StoreId">Invoices</a> <a asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" text-translate="true">Invoices</a>
</li> </li>
<li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li> <li class="breadcrumb-item active" aria-current="page" text-translate="true">@ViewData["Title"]</li>
</ol> </ol>
<h2 text-translate="true">@ViewData["Title"]</h2> <h2 text-translate="true">@ViewData["Title"]</h2>
</nav> </nav>
@ -91,29 +91,29 @@
<div class="form-group"> <div class="form-group">
<label asp-for="DefaultPaymentMethod" class="form-label"></label> <label asp-for="DefaultPaymentMethod" class="form-label"></label>
<select asp-for="DefaultPaymentMethod" asp-items="Model.AvailablePaymentMethods" class="form-select"> <select asp-for="DefaultPaymentMethod" asp-items="Model.AvailablePaymentMethods" class="form-select">
<option value="" selected>Use the stores default</option> <option value="" selected text-translate="true">Use the stores default</option>
</select> </select>
<span asp-validation-for="DefaultPaymentMethod" class="text-danger"></span> <span asp-validation-for="DefaultPaymentMethod" class="text-danger"></span>
</div> </div>
<h4 class="mt-5 mb-4">Customer Information</h4> <h4 class="mt-5 mb-4" text-translate="true">Customer Information</h4>
<div class="form-group"> <div class="form-group">
<label asp-for="BuyerEmail" class="form-label"></label> <label asp-for="BuyerEmail" class="form-label"></label>
<input asp-for="BuyerEmail" class="form-control"/> <input asp-for="BuyerEmail" class="form-control"/>
<span asp-validation-for="BuyerEmail" class="text-danger"></span> <span asp-validation-for="BuyerEmail" class="text-danger"></span>
</div> </div>
<h4 class="mt-5 mb-2">Additional Options</h4> <h4 class="mt-5 mb-2" text-translate="true">Additional Options</h4>
<div class="form-group"> <div class="form-group">
<div class="accordion" id="additional"> <div class="accordion" id="additional">
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header" id="additional-pos-data-header"> <h2 class="accordion-header" id="additional-pos-data-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-pos-data" aria-expanded="false" aria-controls="additional-pos-data"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-pos-data" aria-expanded="false" aria-controls="additional-pos-data">
Metadata <span text-translate="true">Metadata</span>
<vc:icon symbol="caret-down" /> <vc:icon symbol="caret-down" />
</button> </button>
</h2> </h2>
<div id="additional-pos-data" class="accordion-collapse collapse" aria-labelledby="additional-pos-data-header"> <div id="additional-pos-data" class="accordion-collapse collapse" aria-labelledby="additional-pos-data-header">
<p>Custom data to expand the invoice. This data is a JSON object, e.g. <code>{ "orderId": 615, "product": "Pizza" }</code></p> <p html-translate="true">Custom data to expand the invoice. This data is a JSON object, e.g. <code>{ "orderId": 615, "product": "Pizza" }</code></p>
<div class="form-group"> <div class="form-group">
<label asp-for="Metadata" class="form-label"></label> <label asp-for="Metadata" class="form-label"></label>
<textarea asp-for="Metadata" class="form-control" rows="10" cols="40"></textarea> <textarea asp-for="Metadata" class="form-control" rows="10" cols="40"></textarea>
@ -124,7 +124,7 @@
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header" id="additional-notifications-header"> <h2 class="accordion-header" id="additional-notifications-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-notifications" aria-expanded="false" aria-controls="additional-notifications"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-notifications" aria-expanded="false" aria-controls="additional-notifications">
Invoice Notifications <span text-translate="true">Invoice Notifications</span>
<vc:icon symbol="caret-down" /> <vc:icon symbol="caret-down" />
</button> </button>
</h2> </h2>
@ -139,7 +139,7 @@
<label asp-for="NotificationEmail" class="form-label"></label> <label asp-for="NotificationEmail" class="form-label"></label>
<input asp-for="NotificationEmail" class="form-control" /> <input asp-for="NotificationEmail" class="form-control" />
<span asp-validation-for="NotificationEmail" class="text-danger"></span> <span asp-validation-for="NotificationEmail" class="text-danger"></span>
<div id="InvoiceEmailHelpBlock" class="form-text">Receive updates for this invoice.</div> <div id="InvoiceEmailHelpBlock" class="form-text" text-translate="true">Receive updates for this invoice.</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -161,7 +161,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="spinner-border" role="status"> <div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -186,7 +186,7 @@
</a> </a>
</p> </p>
<a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener"> <a class="store-powered-by" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener">
Powered by <partial name="_StoreFooterLogo" /> <span text-translate="true">Powered by</span> <partial name="_StoreFooterLogo" />
</a> </a>
</footer> </footer>
</div> </div>

View File

@ -251,7 +251,7 @@
} }
</div> </div>
<div class="store-footer p-3"> <div class="store-footer p-3">
<a class="store-powered-by" style="color:#000;">Powered by <partial name="_StoreFooterLogo" /></a> <a class="store-powered-by" style="color:#000;"><span text-translate="true">Powered by</span> <partial name="_StoreFooterLogo" /></a>
</div> </div>
<hr class="w-100 my-0 bg-none"/> <hr class="w-100 my-0 bg-none"/>
</center> </center>

View File

@ -9,29 +9,29 @@
<vc:icon symbol="close" /> <vc:icon symbol="close" />
</button> </button>
<h5 class="alert-heading">Updated in v1.4.0</h5> <h5 class="alert-heading">Updated in v1.4.0</h5>
<p class="mb-2">Invoice states have been updated to match the Greenfield API:</p> <p class="mb-2" text-translate="true">Invoice states have been updated to match the Greenfield API:</p>
<div class="row"> <div class="row">
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<ul class="list-unstyled mb-md-0"> <ul class="list-unstyled mb-md-0">
<li> <li>
<span class="badge badge-processing">Paid</span> <span text-translate="true" class="badge badge-processing">Paid</span>
<span class="mx-1">is now shown as</span> <span text-translate="true" class="mx-1">is now shown as</span>
<span class="badge badge-processing">Processing</span> <span text-translate="true" class="badge badge-processing">Processing</span>
</li> </li>
<li class="mt-2"> <li class="mt-2">
<span class="badge badge-settled">Completed</span> <span text-translate="true" class="badge badge-settled">Completed</span>
<span class="mx-1">is now shown as</span> <span text-translate="true" class="mx-1">is now shown as</span>
<span class="badge badge-settled">Settled</span> <span text-translate="true" class="badge badge-settled">Settled</span>
</li> </li>
<li class="mt-2"> <li class="mt-2">
<span class="badge badge-settled">Confirmed</span> <span text-translate="true" class="badge badge-settled">Confirmed</span>
<span class="mx-1">is now shown as</span> <span text-translate="true" class="mx-1">is now shown as</span>
<span class="badge badge-settled">Settled</span> <span text-translate="true" class="badge badge-settled">Settled</span>
</li> </li>
</ul> </ul>
</div> </div>
<div class="col-12 col-md-6 d-flex justify-content-md-end align-items-md-end"> <div class="col-12 col-md-6 d-flex justify-content-md-end align-items-md-end">
<button name="command" type="submit" value="save" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="alert">Don't Show Again</button> <button name="command" type="submit" value="save" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="alert" text-translate="true">Don't Show Again</button>
</div> </div>
</div> </div>
</form> </form>

Some files were not shown because too many files have changed in this diff Show More