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

View File

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

View File

@ -2,23 +2,30 @@
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Plugins.Crowdfund
@model BTCPayServer.Components.AppSales.AppSalesViewModel
@{
var label = Model.AppType == CrowdfundAppType.AppType ? "Contributions" : "Sales";
}
<div id="AppSales-@Model.Id" class="widget app-sales">
<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))
{
<a href="@Model.AppUrl">Manage</a>
<a href="@Model.AppUrl" text-translate="true">Manage</a>
}
</header>
@if (Model.InitialRendering)
{
<div class="loading d-flex justify-content-center p-3">
<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>
<script src="~/Components/AppSales/Default.cshtml.js" asp-append-version="true"></script>
@ -39,7 +46,15 @@
{
<header class="mb-3">
<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>
<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" : "")>

View File

@ -1,18 +1,24 @@
@using BTCPayServer.Plugins.Crowdfund
@model BTCPayServer.Components.AppTopItems.AppTopItemsViewModel
@{
var label = Model.AppType == CrowdfundAppType.AppType ? "contribution" : "sale";
}
<div id="AppTopItems-@Model.Id" class="widget app-top-items">
<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>
@if (Model.InitialRendering)
{
<div class="loading d-flex justify-content-center p-3">
<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>
<script src="~/Components/AppTopItems/Default.cshtml.js" asp-append-version="true"></script>
@ -45,7 +51,31 @@
@entry.Title
</span>
<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
</span>
</div>
@ -55,7 +85,14 @@
else
{
<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>
}
</div>

View File

@ -10,7 +10,7 @@
<div class="d-inline-flex align-items-center gap-2">
@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">
@if (canMark)
@ -21,13 +21,13 @@
<div class="dropdown-menu">
@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
</button>
}
@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
</button>
}
@ -62,6 +62,6 @@
}
@if (Model.HasRefund)
{
<span class="badge bg-warning">Refund</span>
<span class="badge bg-warning" text-translate="true">Refund</span>
}
</div>

View File

@ -11,7 +11,7 @@
walletId = Model.WalletObjectId.WalletId
}): 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" : "")"
data-fetch-url="@fetchUrl"
data-update-url="@updateUrl"

View File

@ -13,7 +13,7 @@
@if (Model.Skip > 0)
{
<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 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))
{
<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>
}
</ul>
@ -45,7 +45,7 @@
{
<ul class="pagination ms-auto">
<li class="page-item disabled">
<span class="page-link">Page Size</span>
<span class="page-link" text-translate="true">Page Size</span>
</li>
@foreach (var pageSize in pageSizeOptions)
{

View File

@ -29,7 +29,7 @@
<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>
<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>
</div>
@ -41,7 +41,7 @@
@Model.Balance.OffchainBalance.Opening
</span>
<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>
</div>
}
@ -52,7 +52,7 @@
@Model.Balance.OffchainBalance.Local
</span>
<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>
</div>
}
@ -63,7 +63,7 @@
@Model.Balance.OffchainBalance.Remote
</span>
<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>
</div>
}
@ -74,7 +74,7 @@
@Model.Balance.OffchainBalance.Closing
</span>
<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>
</div>
}
@ -87,7 +87,7 @@
<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>
<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>
</div>
<div class="balance-details collapse" id="balanceDetailsOnchain">
@ -98,7 +98,7 @@
@Model.Balance.OnchainBalance.Confirmed
</span>
<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>
</div>
}
@ -109,7 +109,7 @@
@Model.Balance.OnchainBalance.Unconfirmed
</span>
<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>
</div>
}
@ -120,7 +120,7 @@
@Model.Balance.OnchainBalance.Reserved
</span>
<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>
</div>
}

View File

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

View File

@ -6,7 +6,7 @@
{
<div class="loading d-flex justify-content-center p-3">
<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>
<script>

View File

@ -23,6 +23,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using NBitcoin.DataEncoders;
using Newtonsoft.Json.Linq;
@ -47,6 +48,7 @@ namespace BTCPayServer.Controllers
readonly ILogger _logger;
public PoliciesSettings PoliciesSettings { get; }
public IStringLocalizer StringLocalizer { get; }
public Logs Logs { get; }
public UIAccountController(
@ -62,6 +64,7 @@ namespace BTCPayServer.Controllers
UserLoginCodeService userLoginCodeService,
LnurlAuthService lnurlAuthService,
LinkGenerator linkGenerator,
IStringLocalizer stringLocalizer,
Logs logs)
{
_userManager = userManager;
@ -78,6 +81,7 @@ namespace BTCPayServer.Controllers
_eventAggregator = eventAggregator;
_logger = logs.PayServer;
Logs = logs;
StringLocalizer = stringLocalizer;
}
[TempData]
@ -149,7 +153,7 @@ namespace BTCPayServer.Controllers
var userId = _userLoginCodeService.Verify(code);
if (userId is null)
{
TempData[WellKnownTempData.ErrorMessage] = "Login code was invalid";
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Login code was invalid"].Value;
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
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))
{
TempData.SetStatusMessageModel(new StatusMessageModel
@ -311,7 +315,7 @@ namespace BTCPayServer.Controllers
}
ViewData["ReturnUrl"] = returnUrl;
var errorMessage = "Invalid login attempt.";
var errorMessage = StringLocalizer["Invalid login attempt."].Value;
var user = await _userManager.FindByIdAsync(viewModel.UserId);
if (!UserService.TryCanLogin(user, out var message))
{
@ -629,7 +633,7 @@ namespace BTCPayServer.Controllers
});
RegisteredUserId = user.Id;
TempData[WellKnownTempData.SuccessMessage] = "Account created.";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Account created."].Value;
var requiresConfirmedEmail = policies.RequiresConfirmedEmail && !user.EmailConfirmed;
var requiresUserApproval = policies.RequiresUserApproval && !user.Approved;
if (requiresConfirmedEmail)
@ -704,7 +708,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Your email has been confirmed."
Message = StringLocalizer["Your email has been confirmed."].Value
});
await FinalizeInvitationIfApplicable(user);
return RedirectToAction(nameof(Login), new { email = user.Email });
@ -713,7 +717,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
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);
}
@ -811,7 +815,9 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
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);
return RedirectToAction(nameof(Login));
@ -848,7 +854,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Info,
Message = "Invitation accepted. Please set your password."
Message = StringLocalizer["Invitation accepted. Please set your password."].Value
});
return await RedirectToSetPassword(user);
}
@ -857,7 +863,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
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);
@ -930,7 +936,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
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;

View File

@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Controllers
{
@ -30,6 +31,7 @@ namespace BTCPayServer.Controllers
StoreRepository storeRepository,
IFileService fileService,
AppService appService,
IStringLocalizer stringLocalizer,
IHtmlHelper html)
{
_userManager = userManager;
@ -39,6 +41,7 @@ namespace BTCPayServer.Controllers
_fileService = fileService;
_appService = appService;
Html = html;
StringLocalizer = stringLocalizer;
}
private readonly UserManager<ApplicationUser> _userManager;
@ -50,6 +53,7 @@ namespace BTCPayServer.Controllers
public string CreatedAppId { get; set; }
public IHtmlHelper Html { get; }
public IStringLocalizer StringLocalizer { get; }
public class AppUpdated
{
@ -158,7 +162,7 @@ namespace BTCPayServer.Controllers
var type = _appService.GetAppType(vm.AppType ?? vm.SelectedAppType);
if (type is null)
{
ModelState.AddModelError(nameof(vm.SelectedAppType), "Invalid App Type");
ModelState.AddModelError(nameof(vm.SelectedAppType), StringLocalizer["Invalid App Type"]);
}
if (!ModelState.IsValid)
@ -177,7 +181,7 @@ namespace BTCPayServer.Controllers
await _appService.SetDefaultSettings(appData, defaultCurrency);
await _appService.UpdateOrCreateApp(appData);
TempData[WellKnownTempData.SuccessMessage] = "App successfully created";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App successfully created"].Value;
CreatedAppId = appData.Id;
var url = await type.ConfigureLink(appData);
@ -192,7 +196,7 @@ namespace BTCPayServer.Controllers
if (app == null)
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)]
@ -204,7 +208,7 @@ namespace BTCPayServer.Controllers
return NotFound();
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 });
}
@ -227,12 +231,14 @@ namespace BTCPayServer.Controllers
if (await _appService.SetArchived(app, archived))
{
TempData[WellKnownTempData.SuccessMessage] = archived
? "The app has been archived and will no longer appear in the apps list by default."
: "The app has been unarchived and will appear in the apps list by default again.";
? StringLocalizer["The app has been archived and will no longer appear in the apps list by default."].Value
: StringLocalizer["The app has been unarchived and will appear in the apps list by default again."].Value;
}
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);

View File

@ -86,7 +86,7 @@ namespace BTCPayServer.Controllers
var newDeliveryId = await WebhookNotificationManager.Redeliver(deliveryId);
if (newDeliveryId is null)
return NotFound();
TempData[WellKnownTempData.SuccessMessage] = "Successfully planned a redelivery";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Successfully planned a redelivery"].Value;
return RedirectToAction(nameof(Invoice),
new
{
@ -294,9 +294,9 @@ namespace BTCPayServer.Controllers
var payoutMethodIds = _payoutHandlers.GetSupportedPayoutMethods(this.GetCurrentStore());
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),
"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);
}
@ -306,7 +306,7 @@ namespace BTCPayServer.Controllers
var refund = new RefundModel
{
Title = "Payment method",
Title = StringLocalizer["Payment method"],
AvailablePaymentMethods =
new SelectList(payoutMethodIds.Select(id => new SelectListItem(id.ToString(), id.ToString())),
"Value", "Text"),
@ -344,7 +344,7 @@ namespace BTCPayServer.Controllers
var pmis = _payoutHandlers.GetSupportedPayoutMethods(store);
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);
}
@ -353,7 +353,7 @@ namespace BTCPayServer.Controllers
var paymentMethod = paymentMethodId is null ? null : invoice.GetPaymentPrompt(paymentMethodId);
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);
}
@ -377,7 +377,7 @@ namespace BTCPayServer.Controllers
{
case RefundSteps.SelectPaymentMethod:
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);
model.CryptoAmountThen = cryptoPaid.RoundToSignificant(paymentMethod.Divisibility);
@ -390,7 +390,7 @@ namespace BTCPayServer.Controllers
if (rateResult.BidAsk is null)
{
ModelState.AddModelError(nameof(model.SelectedRefundOption),
$"Impossible to fetch rate: {rateResult.EvaluatedRule}");
StringLocalizer["Impossible to fetch rate: {0}", rateResult.EvaluatedRule]);
return View("_RefundModal", model);
}
@ -413,7 +413,7 @@ namespace BTCPayServer.Controllers
case RefundSteps.SelectRate:
createPullPayment = new CreatePullPayment
{
Name = $"Refund {invoice.Id}",
Name = StringLocalizer["Refund {0}", invoice.Id],
PayoutMethods = new[] { pmi },
StoreId = invoice.StoreId,
BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration
@ -423,7 +423,7 @@ namespace BTCPayServer.Controllers
.Succeeded;
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)
{
@ -457,11 +457,11 @@ namespace BTCPayServer.Controllers
if (!isPaidOver)
{
ModelState.AddModelError(nameof(model.SelectedRefundOption), "Invoice is not overpaid");
ModelState.AddModelError(nameof(model.SelectedRefundOption), StringLocalizer["Invoice is not overpaid"]);
}
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)
{
@ -474,17 +474,17 @@ namespace BTCPayServer.Controllers
break;
case "Custom":
model.Title = "How much to refund?";
model.Title = StringLocalizer["How much to refund?"];
model.RefundStep = RefundSteps.SelectRate;
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) ||
_CurrencyNameTable.GetCurrencyData(model.CustomCurrency, false) == null)
{
ModelState.AddModelError(nameof(model.CustomCurrency), "Invalid currency");
ModelState.AddModelError(nameof(model.CustomCurrency), StringLocalizer["Invalid currency"]);
}
if (!ModelState.IsValid)
{
@ -500,7 +500,7 @@ namespace BTCPayServer.Controllers
if (rateResult.BidAsk is null)
{
ModelState.AddModelError(nameof(model.SelectedRefundOption),
$"Impossible to fetch rate: {rateResult.EvaluatedRule}");
StringLocalizer["Impossible to fetch rate: {0}", rateResult.EvaluatedRule]);
return View("_RefundModal", model);
}
@ -510,7 +510,7 @@ namespace BTCPayServer.Controllers
break;
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);
}
break;
@ -608,10 +608,12 @@ namespace BTCPayServer.Controllers
if (invoice == null)
return NotFound();
await _InvoiceRepository.ToggleInvoiceArchival(invoiceId, !invoice.Archived);
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
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 });
}
@ -626,28 +628,32 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(ListInvoices), new { storeId });
}
if (selectedItems.Length == 0)
return NotSupported("No invoice has been selected");
return NotSupported(StringLocalizer["No invoice has been selected"]);
switch (command)
{
case "archive":
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;
case "unarchive":
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;
case "cpfp" when storeId is not null:
var network = _NetworkProvider.DefaultNetwork;
var explorer = _ExplorerClients.GetExplorerClient(network);
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))
return Forbid();
var derivationScheme = (this.GetCurrentStore().GetDerivationSchemeSettings(_handlers, network.CryptoCode))?.AccountDerivation;
var derivationScheme = GetCurrentStore().GetDerivationSchemeSettings(_handlers, network.CryptoCode)?.AccountDerivation;
if (derivationScheme is null)
return NotSupported("This feature is only available to BTC wallets");
var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
@ -657,7 +663,7 @@ namespace BTCPayServer.Controllers
var parameters = new MultiValueDictionary<string, string>();
foreach (var utxo in bumpableUTXOs)
{
parameters.Add($"outpoints[]", utxo.Outpoint.ToString());
parameters.Add("outpoints[]", utxo.Outpoint.ToString());
}
return View("PostRedirect", new PostRedirectViewModel
{
@ -1154,7 +1160,7 @@ namespace BTCPayServer.Controllers
{
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");
}
@ -1202,7 +1208,7 @@ namespace BTCPayServer.Controllers
}
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;
}
var result = await CreateInvoiceCoreRaw(new CreateInvoiceRequest()
var result = await CreateInvoiceCoreRaw(new CreateInvoiceRequest
{
Amount = model.Amount,
Currency = model.Currency,
@ -1249,14 +1255,14 @@ namespace BTCPayServer.Controllers
},
cancellationToken: cancellationToken);
TempData[WellKnownTempData.SuccessMessage] = $"Invoice {result.Id} just created!";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Invoice {0} just created!", result.Id].Value;
CreatedInvoiceId = result.Id;
return RedirectToAction(nameof(Invoice), new { storeId = result.StoreId, invoiceId = result.Id });
}
catch (BitpayHttpException ex)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = ex.Message

View File

@ -35,6 +35,7 @@ using StoreData = BTCPayServer.Data.StoreData;
using Serilog.Filters;
using PeterO.Numbers;
using BTCPayServer.Payouts;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Controllers
{
@ -69,6 +70,7 @@ namespace BTCPayServer.Controllers
private readonly UriResolver _uriResolver;
public WebhookSender WebhookNotificationManager { get; }
public IStringLocalizer StringLocalizer { get; }
public UIInvoiceController(
InvoiceRepository invoiceRepository,
@ -98,6 +100,7 @@ namespace BTCPayServer.Controllers
IAuthorizationService authorizationService,
TransactionLinkProviders transactionLinkProviders,
Dictionary<PaymentMethodId, ICheckoutModelExtension> paymentModelExtensions,
IStringLocalizer stringLocalizer,
PrettyNameProvider prettyName)
{
_displayFormatter = displayFormatter;
@ -127,6 +130,7 @@ namespace BTCPayServer.Controllers
_uriResolver = uriResolver;
_defaultRules = defaultRules;
_appService = appService;
StringLocalizer = stringLocalizer;
}
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.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Localization;
using NBitcoin;
using NBitcoin.Crypto;
using NBitcoin.DataEncoders;
@ -23,22 +24,24 @@ namespace BTCPayServer
private readonly UserManager<ApplicationUser> _userManager;
private readonly LnurlAuthService _lnurlAuthService;
private readonly LinkGenerator _linkGenerator;
public IStringLocalizer StringLocalizer { get; }
public UILNURLAuthController(UserManager<ApplicationUser> userManager, LnurlAuthService lnurlAuthService,
LinkGenerator linkGenerator)
IStringLocalizer stringLocalizer, LinkGenerator linkGenerator)
{
_userManager = userManager;
_lnurlAuthService = lnurlAuthService;
_linkGenerator = linkGenerator;
StringLocalizer = stringLocalizer;
}
[HttpGet("{id}/delete")]
public IActionResult Remove(string id)
{
return View("Confirm",
new ConfirmModel("Remove LNURL Auth link",
"Your account will no longer have this Lightning wallet as an option for two-factor authentication.",
"Remove"));
new ConfirmModel(StringLocalizer["Remove LNURL Auth link"],
StringLocalizer["Your account will no longer have this Lightning wallet as an option for two-factor authentication."],
StringLocalizer["Remove"]));
}
[HttpPost("{id}/delete")]
@ -49,7 +52,7 @@ namespace BTCPayServer
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Html = "LNURL Auth was removed successfully."
Message = StringLocalizer["LNURL Auth was removed successfully."].Value
});
return RedirectToList();
@ -65,7 +68,7 @@ namespace BTCPayServer
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Html = "The Lightning node could not be registered."
Html = StringLocalizer["The Lightning node could not be registered."].Value
});
return RedirectToList();

View File

@ -36,12 +36,11 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Localization;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using LightningAddressData = BTCPayServer.Data.LightningAddressData;
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
namespace BTCPayServer
{
@ -63,6 +62,7 @@ namespace BTCPayServer
private readonly InvoiceActivator _invoiceActivator;
private readonly PaymentMethodHandlerDictionary _handlers;
private readonly PayoutProcessorService _payoutProcessorService;
public IStringLocalizer StringLocalizer { get; }
public UILNURLController(InvoiceRepository invoiceRepository,
EventAggregator eventAggregator,
@ -77,6 +77,7 @@ namespace BTCPayServer
PullPaymentHostedService pullPaymentHostedService,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
IPluginHookService pluginHookService,
IStringLocalizer stringLocalizer,
InvoiceActivator invoiceActivator)
{
_invoiceRepository = invoiceRepository;
@ -93,6 +94,7 @@ namespace BTCPayServer
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_pluginHookService = pluginHookService;
_invoiceActivator = invoiceActivator;
StringLocalizer = stringLocalizer;
}
[EnableCors(CorsPolicies.All)]
@ -302,7 +304,7 @@ namespace BTCPayServer
{
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out _);
if (pmi is null)
return NotFound("LNUrl or LN is disabled");
return NotFound(StringLocalizer["LNURL or LN is disabled"]);
var escapedItemId = Extensions.UnescapeBackSlashUriString(itemCode);
item = items.FirstOrDefault(item1 =>
item1.Id.Equals(itemCode, StringComparison.InvariantCultureIgnoreCase) ||
@ -457,11 +459,11 @@ namespace BTCPayServer
{
var lightningAddressSettings = await _lightningAddressService.ResolveByAddress(username);
if (lightningAddressSettings is null || username is null)
return NotFound("Unknown username");
return NotFound(StringLocalizer["Unknown username"]);
var blob = lightningAddressSettings.GetBlob();
var store = await _storeRepository.FindStore(lightningAddressSettings.StoreDataId);
if (store is null)
return NotFound("Unknown username");
return NotFound(StringLocalizer["Unknown username"]);
var result = await GetLNURLRequest(
cryptoCode,
store,
@ -503,7 +505,7 @@ namespace BTCPayServer
var blob = store.GetStoreBlob();
if (!blob.AnyoneCanInvoice)
return NotFound("'Anyone can invoice' is turned off");
return NotFound(StringLocalizer["'Anyone can invoice' is turned off"]);
var metadata = new InvoiceMetadata();
if (!string.IsNullOrEmpty(orderId))
{
@ -533,7 +535,7 @@ namespace BTCPayServer
{
var pmi = GetLNUrlPaymentMethodId(cryptoCode, store, out _);
if (pmi is null)
return NotFound("LNUrl or LN is disabled");
return NotFound(StringLocalizer["LNURL or LN is disabled"]);
InvoiceEntity i;
try
@ -721,7 +723,7 @@ namespace BTCPayServer
new LNURLPayRequest.LNURLPayRequestCallbackResponse.LNURLPayRequestSuccessActionUrl
{
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(
nameof(UIInvoiceController.InvoiceReceipt),
"UIInvoice",
@ -833,7 +835,7 @@ namespace BTCPayServer
{
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
});
return RedirectToAction(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId });
@ -873,7 +875,7 @@ namespace BTCPayServer
if (!string.IsNullOrEmpty(vm.Add.CurrencyCode) &&
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;
@ -885,7 +887,7 @@ namespace BTCPayServer
}
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)
@ -909,12 +911,12 @@ namespace BTCPayServer
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Lightning address added successfully."
Message = StringLocalizer["Lightning address added successfully."].Value
});
}
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)
{
@ -932,13 +934,13 @@ namespace BTCPayServer
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = $"Lightning address {index} removed successfully."
Message = StringLocalizer["Lightning address {0} removed successfully.", index].Value
});
return RedirectToAction("EditLightningAddress");
}
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)
{

View File

@ -58,10 +58,10 @@ namespace BTCPayServer.Controllers
return NotFound();
}
await _apiKeyRepository.Remove(id, _userManager.GetUserId(User));
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = "API Key removed"
Message = StringLocalizer["API Key removed"].Value
});
return RedirectToAction("APIKeys");
}
@ -71,10 +71,10 @@ namespace BTCPayServer.Controllers
{
if (!_btcPayServerEnvironment.IsSecure(HttpContext))
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
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");
}
@ -91,7 +91,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
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");
}
@ -199,7 +199,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
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 });
@ -242,7 +242,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
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");
}

View File

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

View File

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

View File

@ -23,6 +23,7 @@ using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
using StoreData = BTCPayServer.Data.StoreData;
@ -47,6 +48,7 @@ namespace BTCPayServer.Controllers
private FormComponentProviders FormProviders { get; }
public FormDataService FormDataService { get; }
public IStringLocalizer StringLocalizer { get; }
public UIPaymentRequestController(
UIInvoiceController invoiceController,
@ -62,6 +64,7 @@ namespace BTCPayServer.Controllers
InvoiceRepository invoiceRepository,
FormComponentProviders formProviders,
FormDataService formDataService,
IStringLocalizer stringLocalizer,
BTCPayNetworkProvider networkProvider)
{
_InvoiceController = invoiceController;
@ -78,6 +81,7 @@ namespace BTCPayServer.Controllers
FormProviders = formProviders;
FormDataService = formDataService;
_networkProvider = networkProvider;
StringLocalizer = stringLocalizer;
}
[HttpGet("/stores/{storeId}/payment-requests")]
@ -169,7 +173,7 @@ namespace BTCPayServer.Controllers
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();
data.StoreDataId = viewModel.StoreId;
@ -180,7 +184,7 @@ namespace BTCPayServer.Controllers
{
var prInvoices = (await _PaymentRequestService.GetPaymentRequest(payReqId, GetUserId())).Invoices;
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)
@ -210,7 +214,9 @@ namespace BTCPayServer.Controllers
data = await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(data);
_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 });
}
@ -302,7 +308,7 @@ namespace BTCPayServer.Controllers
{
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());
@ -318,7 +324,7 @@ namespace BTCPayServer.Controllers
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))
{
@ -337,7 +343,7 @@ namespace BTCPayServer.Controllers
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)
@ -347,7 +353,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction("ViewPaymentRequest", new { payReqId });
}
return BadRequest("Payment Request has expired");
return BadRequest(StringLocalizer["Payment Request has expired"]);
}
var currentInvoice = result.Invoices.GetReusableInvoice(amount);
@ -391,7 +397,7 @@ namespace BTCPayServer.Controllers
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 =>
@ -399,7 +405,7 @@ namespace BTCPayServer.Controllers
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)
@ -409,11 +415,11 @@ namespace BTCPayServer.Controllers
if (redirect)
{
TempData[WellKnownTempData.SuccessMessage] = "Payment cancelled";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Payment cancelled"].Value;
return RedirectToAction(nameof(ViewPaymentRequest), new { payReqId });
}
return Ok("Payment cancelled");
return Ok(StringLocalizer["Payment cancelled"]);
}
[HttpGet("{payReqId}/clone")]
@ -446,8 +452,8 @@ namespace BTCPayServer.Controllers
if(result is not null)
{
TempData[WellKnownTempData.SuccessMessage] = result.Value
? "The payment request has been archived and will no longer appear in the payment request list by default again."
: "The payment request has been unarchived and will appear in the payment request list by default.";
? StringLocalizer["The payment request has been archived and will no longer appear in the payment request list by default again."].Value
: 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 });
}

View File

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

View File

@ -1,14 +1,10 @@
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Data;
using BTCPayServer.Lightning;
using BTCPayServer.Models;
using BTCPayServer.NTag424;
using Dapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NBitcoin.DataEncoders;
using System;
using System.Net.WebSockets;
using System.Threading;
@ -23,7 +19,7 @@ namespace BTCPayServer.Controllers
[HttpGet("pull-payments/{pullPaymentId}/boltcard/{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 }),
WebsocketPath = Url.Action(nameof(VaultNFCBridgeConnection), "UIPullPayment", new { pullPaymentId }),
@ -34,7 +30,7 @@ namespace BTCPayServer.Controllers
[HttpPost("pull-payments/{pullPaymentId}/boltcard/{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 });
}
@ -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);
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)
{
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;
}
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 vaultClient.Show(VaultMessageType.Ok, "NFC detected.", cts.Token);
await vaultClient.Show(VaultMessageType.Ok, StringLocalizer["NFC detected."], cts.Token);
var issuerKey = await _settingsRepository.GetIssuerKey(_env);
CardOrigin cardOrigin = await GetCardOrigin(pullPaymentId, ntag, issuerKey, cts.Token);
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;
}
@ -103,7 +99,7 @@ next:
switch (command)
{
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)
{
await ntag.AuthenticateEV2First(0, AESKey.Default, cts.Token);
@ -119,35 +115,35 @@ next:
await _dbContextFactory.SetBoltcardResetState(issuerKey, uid);
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)
{
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;
break;
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)
{
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)
{
var cardKey = issuerKey.CreatePullPaymentCardKey(thisIssuer.Registration.UId, thisIssuer.Registration.Version, pullPaymentId);
await ntag.ResetCard(issuerKey, cardKey);
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;
break;
}
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 vaultClient.Show(VaultMessageType.Ok, "Thank you!", cts.Token);
await vaultClient.Show(VaultMessageType.Ok, StringLocalizer["Thank you!"], cts.Token);
await vaultClient.SendSimpleMessage("done", cts.Token);
}
}
@ -159,7 +155,7 @@ next:
{
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 { }
}

View File

@ -1,11 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Amazon.S3.Model;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
@ -14,24 +10,17 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Controllers.Greenfield;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.ModelBinders;
using BTCPayServer.Models;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.NTag424;
using BTCPayServer.Payments;
using BTCPayServer.Payouts;
using BTCPayServer.Services;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Dapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
using NBitcoin.DataEncoders;
using NdefLibrary.Ndef;
using Newtonsoft.Json.Linq;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Controllers
{
@ -48,6 +37,7 @@ namespace BTCPayServer.Controllers
private readonly StoreRepository _storeRepository;
private readonly BTCPayServerEnvironment _env;
private readonly SettingsRepository _settingsRepository;
public IStringLocalizer StringLocalizer { get; }
public UIPullPaymentController(ApplicationDbContextFactory dbContextFactory,
CurrencyNameTable currencyNameTable,
@ -59,6 +49,7 @@ namespace BTCPayServer.Controllers
PayoutMethodHandlerDictionary payoutHandlers,
StoreRepository storeRepository,
BTCPayServerEnvironment env,
IStringLocalizer stringLocalizer,
SettingsRepository settingsRepository)
{
_dbContextFactory = dbContextFactory;
@ -72,6 +63,7 @@ namespace BTCPayServer.Controllers
_env = env;
_settingsRepository = settingsRepository;
_networkProvider = networkProvider;
StringLocalizer = stringLocalizer;
}
[AllowAnonymous]
@ -196,7 +188,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
Message = "Pull payment updated successfully",
Message = StringLocalizer["Pull payment updated successfully"].Value,
Severity = StatusMessageModel.StatusSeverity.Success
});
@ -211,12 +203,12 @@ namespace BTCPayServer.Controllers
var pp = await ctx.PullPayments.FindAsync(pullPaymentId);
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))
{
ModelState.AddModelError(nameof(vm.Destination), "Please provide a destination");
ModelState.AddModelError(nameof(vm.Destination), StringLocalizer["Please provide a destination"]);
return await ViewPullPayment(pullPaymentId);
}
@ -232,7 +224,7 @@ namespace BTCPayServer.Controllers
{
var handler = _payoutHandlers.TryGet(pmId);
(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);
error = err;
if (dst is not null && err is null)
@ -256,7 +248,7 @@ namespace BTCPayServer.Controllers
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);
}
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)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
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>();
}
@ -75,9 +75,9 @@ namespace BTCPayServer.Controllers
[FromServices] PluginService pluginService, string 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
});
@ -89,9 +89,9 @@ namespace BTCPayServer.Controllers
[FromServices] PluginService pluginService, string 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
});
@ -113,17 +113,17 @@ namespace BTCPayServer.Controllers
{
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
});
}
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
});
}
@ -142,9 +142,9 @@ namespace BTCPayServer.Controllers
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
});
return RedirectToAction("ListPlugins");

View File

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

View File

@ -52,9 +52,9 @@ namespace BTCPayServer.Controllers
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,
});
}
@ -75,12 +75,12 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(Files), new
{
fileIds = Array.Empty<string>(),
statusMessage = "File removed"
statusMessage = StringLocalizer["File removed"].Value
});
}
catch (Exception e)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = e.Message
@ -108,7 +108,7 @@ namespace BTCPayServer.Controllers
{
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)
@ -192,21 +192,21 @@ namespace BTCPayServer.Controllers
if (invalidFileNameCount == 0)
{
statusMessage = "Files Added Successfully";
statusMessage = StringLocalizer["Files added successfully"];
statusMessageSeverity = StatusMessageModel.StatusSeverity.Success;
}
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;
}
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;
}
this.TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Message = statusMessage,
Severity = statusMessageSeverity
@ -266,10 +266,10 @@ namespace BTCPayServer.Controllers
{
if (!Enum.TryParse(typeof(StorageProvider), provider, out var storageProvider))
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{provider} provider is not supported"
Message = StringLocalizer["{0} provider is not supported", provider].Value
});
return RedirectToAction(nameof(Storage));
}
@ -282,10 +282,10 @@ namespace BTCPayServer.Controllers
switch (storageProviderService)
{
case null:
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = $"{storageProvider} is not supported"
Message = StringLocalizer["{0} provider is not supported", storageProvider].Value
});
return RedirectToAction(nameof(Storage));
case AzureBlobStorageFileProviderService fileProviderService:
@ -350,10 +350,10 @@ namespace BTCPayServer.Controllers
data.Provider = storageProvider;
data.Configuration = JObject.FromObject(viewModel);
await _SettingsRepository.UpdateSetting(data);
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Storage settings updated successfully"
Message = StringLocalizer["Storage settings updated successfully"].Value
});
return View(viewModel);
}

View File

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

View File

@ -139,18 +139,18 @@ namespace BTCPayServer.Controllers
{
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))
{
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
{
var formFile = await viewModel.ImageFile.Bufferize();
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
{
@ -165,7 +165,7 @@ namespace BTCPayServer.Controllers
}
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);
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);
}
@ -199,11 +199,11 @@ namespace BTCPayServer.Controllers
{
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
{
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
{
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));
}
@ -326,16 +326,16 @@ namespace BTCPayServer.Controllers
{
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."));
}
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?",
"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")]
@ -347,7 +347,7 @@ namespace BTCPayServer.Controllers
await _userService.DeleteUserAndAssociatedData(user);
TempData[WellKnownTempData.SuccessMessage] = "User deleted";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["User deleted"].Value;
return RedirectToAction(nameof(ListUsers));
}
@ -360,7 +360,7 @@ namespace BTCPayServer.Controllers
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."));
}
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();
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));
}
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));
}
@ -402,7 +404,9 @@ namespace BTCPayServer.Controllers
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));
}
@ -413,7 +417,7 @@ namespace BTCPayServer.Controllers
if (user == null)
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")]
@ -430,7 +434,7 @@ namespace BTCPayServer.Controllers
(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));
}

View File

@ -33,6 +33,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MimeKit;
@ -69,6 +70,7 @@ namespace BTCPayServer.Controllers
private readonly EmailSenderFactory _emailSenderFactory;
private readonly TransactionLinkProviders _transactionLinkProviders;
private readonly LocalizerService _localizer;
public IStringLocalizer StringLocalizer { get; }
public UIServerController(
UserManager<ApplicationUser> userManager,
@ -96,6 +98,7 @@ namespace BTCPayServer.Controllers
IHtmlHelper html,
TransactionLinkProviders transactionLinkProviders,
LocalizerService localizer,
IStringLocalizer stringLocalizer,
BTCPayServerEnvironment environment
)
{
@ -125,6 +128,7 @@ namespace BTCPayServer.Controllers
_transactionLinkProviders = transactionLinkProviders;
_localizer = localizer;
Environment = environment;
StringLocalizer = stringLocalizer;
}
[HttpGet("server/stores")]
@ -157,7 +161,7 @@ namespace BTCPayServer.Controllers
};
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))
vm.DNSDomain = null;
@ -170,7 +174,7 @@ namespace BTCPayServer.Controllers
vm.CanUseSSH = _sshState.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);
}
if (!ModelState.IsValid)
@ -229,21 +233,21 @@ namespace BTCPayServer.Controllers
builder.Path = 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")
{
var error = await RunSSH(vm, $"btcpay-update.sh");
if (error != null)
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")
{
var error = await RunSSH(vm, $"btcpay-clean.sh");
if (error != null)
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")
{
@ -251,11 +255,11 @@ namespace BTCPayServer.Controllers
if (error != null)
return error;
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")
{
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");
_ = Task.Delay(3000).ContinueWith((t) => ApplicationLifetime.StopApplication());
}
@ -401,7 +405,7 @@ namespace BTCPayServer.Controllers
_ = _transactionLinkProviders.RefreshTransactionLinkTemplates();
if (_policiesSettings.LangDictionary != settings.LangDictionary)
await _localizer.Load();
TempData[WellKnownTempData.SuccessMessage] = "Policies updated successfully";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Policies updated successfully"].Value;
return RedirectToAction(nameof(Policies));
}
@ -525,7 +529,7 @@ namespace BTCPayServer.Controllers
return NotFound();
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));
}
try
@ -575,7 +579,7 @@ namespace BTCPayServer.Controllers
case ExternalServiceTypes.Torq:
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));
}
LightningWalletServices vm = new LightningWalletServices();
@ -613,7 +617,7 @@ namespace BTCPayServer.Controllers
[HttpGet("server/services/{serviceName}/{cryptoCode}/removelndseed")]
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")]
@ -626,24 +630,24 @@ namespace BTCPayServer.Controllers
var model = LndSeedBackupViewModel.Parse(service.ConnectionString.CookieFilePath);
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));
}
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));
}
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 });
}
else
{
TempData[WellKnownTempData.ErrorMessage] = $"Seed removal failed";
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Seed removal failed"].Value;
return RedirectToAction(nameof(Services));
}
}
@ -725,7 +729,7 @@ namespace BTCPayServer.Controllers
{
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));
}
var service = GetService(serviceName, cryptoCode);
@ -820,7 +824,7 @@ namespace BTCPayServer.Controllers
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
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;
settings.Services.Add(viewModel.Settings);
await _SettingsRepository.UpdateSetting(settings);
@ -856,7 +860,7 @@ namespace BTCPayServer.Controllers
viewModel.Settings.Hostname = viewModel.Settings.Hostname.Trim().ToLowerInvariant();
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;
}
else
@ -864,7 +868,7 @@ namespace BTCPayServer.Controllers
string errorMessage = await viewModel.Settings.SendUpdateRequest(HttpClientFactory.CreateClient());
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;
}
else
@ -900,7 +904,7 @@ namespace BTCPayServer.Controllers
return NotFound();
settings.Services.RemoveAt(i);
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));
return RedirectToAction(nameof(DynamicDnsServices));
}
@ -974,7 +978,7 @@ namespace BTCPayServer.Controllers
try
{
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;
}
catch (Exception ex)
@ -1003,7 +1007,7 @@ namespace BTCPayServer.Controllers
if (exception is null)
{
TempData[WellKnownTempData.SuccessMessage] = "authorized_keys has been updated";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["authorized_keys has been updated"].Value;
}
else
{
@ -1032,7 +1036,7 @@ namespace BTCPayServer.Controllers
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
policies.DisableSSHService = true;
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));
}
@ -1186,7 +1190,7 @@ namespace BTCPayServer.Controllers
if (settingsChanged)
{
await _SettingsRepository.UpdateSetting(theme);
TempData[WellKnownTempData.SuccessMessage] = "Settings updated successfully";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Settings updated successfully"].Value;
return RedirectToAction(nameof(Branding));
}
@ -1229,7 +1233,7 @@ namespace BTCPayServer.Controllers
await client.SendAsync(message);
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)
{
@ -1249,14 +1253,14 @@ namespace BTCPayServer.Controllers
var settings = await _SettingsRepository.GetSettingAsync<EmailSettings>() ?? new EmailSettings();
settings.Password = null;
await _SettingsRepository.UpdateSetting(settings);
TempData[WellKnownTempData.SuccessMessage] = "Email server password reset";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Email server password reset"].Value;
return RedirectToAction(nameof(Emails));
}
// save
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);
}
var oldSettings = await _SettingsRepository.GetSettingAsync<EmailSettings>() ?? new EmailSettings();
@ -1266,7 +1270,7 @@ namespace BTCPayServer.Controllers
}
await _SettingsRepository.UpdateSetting(model.Settings);
TempData[WellKnownTempData.SuccessMessage] = "Email settings saved";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Email settings saved"].Value;
return RedirectToAction(nameof(Emails));
}
@ -1282,16 +1286,14 @@ namespace BTCPayServer.Controllers
if (string.IsNullOrEmpty(_Options.LogFile))
{
TempData[WellKnownTempData.ErrorMessage] = "File Logging Option not specified. " +
"You need to set debuglog and optionally " +
"debugloglevel in the configuration or through runtime arguments";
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;
}
else
{
var di = Directory.GetParent(_Options.LogFile);
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);
}

View File

@ -91,7 +91,7 @@ namespace BTCPayServer.Controllers
{
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
});
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });
@ -162,7 +162,7 @@ namespace BTCPayServer.Controllers
});
TempData.SetStatusMessageModel(new StatusMessageModel
{
Message = StringLocalizer["Pull payment request created"],
Message = StringLocalizer["Pull payment request created"].Value,
Severity = StatusMessageModel.StatusSeverity.Success
});
return RedirectToAction(nameof(PullPayments), new { storeId });
@ -204,7 +204,7 @@ namespace BTCPayServer.Controllers
{
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
});
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });
@ -275,9 +275,9 @@ namespace BTCPayServer.Controllers
string 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
});
return RedirectToAction(nameof(PullPayments), new { storeId });
@ -302,9 +302,9 @@ namespace BTCPayServer.Controllers
var payoutIds = vm.GetSelectedPayouts(commandState);
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
});
return RedirectToAction(nameof(Payouts),
@ -345,9 +345,9 @@ namespace BTCPayServer.Controllers
var rateResult = await _pullPaymentService.GetRate(payout, null, cancellationToken);
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
});
failed = true;
@ -355,7 +355,7 @@ namespace BTCPayServer.Controllers
}
var approveResult = await _pullPaymentService.Approve(
new HostedServices.PullPaymentHostedService.PayoutApproval()
new PullPaymentHostedService.PayoutApproval
{
PayoutId = payout.Id,
Revision = payout.GetBlob(_jsonSerializerSettings).Revision,
@ -363,7 +363,7 @@ namespace BTCPayServer.Controllers
});
if (approveResult.Result != PullPaymentHostedService.PayoutApproval.Result.Ok)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Message = PullPaymentHostedService.PayoutApproval.GetErrorMessage(approveResult.Result),
Severity = StatusMessageModel.StatusSeverity.Error
@ -383,9 +383,9 @@ namespace BTCPayServer.Controllers
goto case "pay";
}
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Message = StringLocalizer["Payouts approved"],
Message = StringLocalizer["Payouts approved"].Value,
Severity = StatusMessageModel.StatusSeverity.Success
});
break;
@ -395,9 +395,9 @@ namespace BTCPayServer.Controllers
{
if (handler is { })
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
});
break;
@ -416,10 +416,10 @@ namespace BTCPayServer.Controllers
continue;
var result =
await _pullPaymentService.MarkPaid(new MarkPayoutRequest() { PayoutId = payout.Id });
await _pullPaymentService.MarkPaid(new MarkPayoutRequest { PayoutId = payout.Id });
if (result != MarkPayoutRequest.PayoutPaidResult.Ok)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Message = MarkPayoutRequest.GetErrorMessage(result),
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
});
break;
@ -445,9 +445,9 @@ namespace BTCPayServer.Controllers
case "cancel":
await _pullPaymentService.Cancel(
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
});
break;
@ -488,7 +488,7 @@ namespace BTCPayServer.Controllers
{
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
});
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });

View File

@ -35,7 +35,8 @@ public partial class UIStoresController
TempData.SetStatusMessageModel(new StatusMessageModel
{
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 _)))
{
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))
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)
@ -101,7 +102,7 @@ public partial class UIStoresController
if (store.SetStoreBlob(blob))
{
await _storeRepo.UpdateStore(store);
message += "Store email rules saved. ";
message += StringLocalizer["Store email rules saved."] + " ";
}
if (command.StartsWith("test", StringComparison.InvariantCultureIgnoreCase))
@ -122,16 +123,16 @@ public partial class UIStoresController
.ToArray();
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
{
message += "Complete the email setup to send test emails.";
message += StringLocalizer["Complete the email setup to send test emails."];
}
}
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 });
}
}
@ -222,14 +223,14 @@ public partial class UIStoresController
return View(model);
var settings = useCustomSMTP ? model.Settings : model.FallbackSettings;
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.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)
{
TempData[WellKnownTempData.ErrorMessage] = "Error: " + ex.Message;
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Error: {0}", ex.Message].Value;
}
return View(model);
}
@ -239,13 +240,13 @@ public partial class UIStoresController
storeBlob.EmailSettings.Password = null;
store.SetStoreBlob(storeBlob);
await _storeRepo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = "Email server password reset";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Email server password reset"].Value;
}
if (useCustomSMTP)
{
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)
return View(model);
@ -257,7 +258,7 @@ public partial class UIStoresController
storeBlob.EmailSettings = model.Settings;
store.SetStoreBlob(storeBlob);
await _storeRepo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = "Email settings modified";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Email settings modified"].Value;
}
return RedirectToAction(nameof(StoreEmailSettings), new { storeId });
}

View File

@ -67,7 +67,7 @@ public partial class UIStoresController
if (webhook is null)
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")]
@ -79,7 +79,7 @@ public partial class UIStoresController
return NotFound();
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 });
}
@ -91,7 +91,7 @@ public partial class UIStoresController
return View(nameof(ModifyWebhook), viewModel);
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 });
}
@ -123,7 +123,7 @@ public partial class UIStoresController
return View(nameof(ModifyWebhook), viewModel);
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 });
}
@ -146,11 +146,11 @@ public partial class UIStoresController
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
{
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));
@ -168,7 +168,7 @@ public partial class UIStoresController
if (newDeliveryId is null)
return NotFound();
TempData[WellKnownTempData.SuccessMessage] = "Successfully planned a redelivery";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Successfully planned a redelivery"].Value;
return RedirectToAction(nameof(ModifyWebhook),
new
{

View File

@ -115,7 +115,7 @@ public partial class UIStoresController
if (vm.CryptoCode == null)
{
ModelState.AddModelError(nameof(vm.CryptoCode), "Invalid network");
ModelState.AddModelError(nameof(vm.CryptoCode), StringLocalizer["Invalid network"]);
return View(vm);
}
@ -132,7 +132,7 @@ public partial class UIStoresController
{
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);
}
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));
await handler.ValidatePaymentMethodConfig(ctx);
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)
return View(vm);
@ -159,7 +159,7 @@ public partial class UIStoresController
});
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 });
case "test":
@ -172,9 +172,10 @@ public partial class UIStoresController
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
await handler.TestConnection(info.First(), cts.Token);
}
TempData[WellKnownTempData.SuccessMessage] = "Connection to the Lightning node successful" + (hasPublicAddress
? $". Your node address: {info.First()}"
: ", but no public address has been configured");
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Connection to the Lightning node successful."].Value + " " +
(hasPublicAddress
? StringLocalizer["Your node address: {0}", info.First()].Value
: StringLocalizer["No public address has been configured."].Value);
}
catch (Exception ex)
{
@ -202,7 +203,7 @@ public partial class UIStoresController
var lightning = GetConfig<LightningPaymentMethodConfig>(lnId, store);
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 });
}
@ -241,7 +242,7 @@ public partial class UIStoresController
if (vm.CryptoCode == null)
{
ModelState.AddModelError(nameof(vm.CryptoCode), "Invalid network");
ModelState.AddModelError(nameof(vm.CryptoCode), StringLocalizer["Invalid network"]);
return View(vm);
}
@ -289,7 +290,7 @@ public partial class UIStoresController
{
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 });

View File

@ -108,7 +108,7 @@ public partial class UIStoresController
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);
}
}
@ -116,7 +116,7 @@ public partial class UIStoresController
{
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);
}
}
@ -145,7 +145,7 @@ public partial class UIStoresController
}
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);
}
}
@ -157,14 +157,14 @@ public partial class UIStoresController
}
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);
}
}
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);
}
@ -184,13 +184,13 @@ public partial class UIStoresController
}
catch
{
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid derivation scheme");
ModelState.AddModelError(nameof(vm.DerivationScheme), StringLocalizer["Invalid derivation scheme"]);
return View(vm.ViewName, vm);
}
await _storeRepo.UpdateStore(store);
_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
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))
{
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);
}
@ -305,7 +305,7 @@ public partial class UIStoresController
TempData.SetStatusMessageModel(new StatusMessageModel
{
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);
}
@ -343,7 +343,7 @@ public partial class UIStoresController
TempData.SetStatusMessageModel(new StatusMessageModel
{
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
{
@ -363,7 +363,7 @@ public partial class UIStoresController
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Warning,
Html = "Please check your addresses and confirm."
Message = StringLocalizer["Please check your addresses and confirm."].Value
});
return result;
}
@ -380,7 +380,7 @@ public partial class UIStoresController
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);
return RedirectToAction(nameof(UIWalletsController.WalletTransactions), "UIWallets", new { walletId });
@ -608,7 +608,7 @@ public partial class UIStoresController
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "The seed was not found"
Message = StringLocalizer["The seed was not found"].Value
});
return RedirectToAction(nameof(WalletSettings));
@ -628,9 +628,9 @@ public partial class UIStoresController
return View("Confirm", new ConfirmModel
{
Title = $"Replace {network.CryptoCode} wallet",
Title = StringLocalizer["Replace {0} wallet", network.CryptoCode],
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
{
Title = $"Remove {network.CryptoCode} wallet",
Title = StringLocalizer["Remove {0} wallet", network.CryptoCode],
Description = WalletRemoveWarning(derivation.IsHotWallet, network.CryptoCode),
Action = "Remove"
Action = StringLocalizer["Remove"]
});
}

View File

@ -52,7 +52,7 @@ public partial class UIStoresController
}
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)
{
@ -71,7 +71,7 @@ public partial class UIStoresController
{
errors ??= [];
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);
return View(model);
}
@ -90,7 +90,7 @@ public partial class UIStoresController
{
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);
}
var splitted = model.ScriptTest.Split(',', StringSplitOptions.RemoveEmptyEntries);
@ -100,7 +100,7 @@ public partial class UIStoresController
{
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);
}
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))
{
ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange");
ModelState.AddModelError(nameof(model.PreferredExchange), StringLocalizer["Unsupported exchange"]);
return View(model);
}
@ -147,11 +147,11 @@ public partial class UIStoresController
{
return View("Confirm", new ConfirmModel
{
Action = "Continue",
Title = "Rate rule scripting",
Description = scripting ?
"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?",
Action = StringLocalizer["Continue"],
Title = StringLocalizer["Rate rule scripting"],
Description = scripting
? StringLocalizer["This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)"]
: StringLocalizer["This action will delete your rate script. Are you sure to turn off rate rules scripting?"],
ButtonClass = scripting ? "btn-primary" : "btn-danger"
});
}

View File

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

View File

@ -89,7 +89,7 @@ public partial class UIStoresController
blob.MonitoringExpiration = TimeSpan.FromMinutes(model.MonitoringExpiration);
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);
}
blob.BrandColor = model.BrandColor;
@ -103,18 +103,18 @@ public partial class UIStoresController
{
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))
{
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
{
var formFile = await model.LogoFile.Bufferize();
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
{
@ -127,7 +127,7 @@ public partial class UIStoresController
}
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)
{
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))
{
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))
{
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
{
@ -162,7 +162,7 @@ public partial class UIStoresController
}
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)]
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")]
@ -305,18 +305,18 @@ public partial class UIStoresController
{
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))
{
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
{
var formFile = await model.SoundFile.Bufferize();
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
{
@ -330,7 +330,7 @@ public partial class UIStoresController
}
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);
if (token == null || token.StoreId != CurrentStore.Id)
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")]
@ -243,14 +243,14 @@ public partial class UIStoresController
StoreNotConfigured = store.GetPaymentMethodConfigs(_handlers).All(p => excludeFilter.Match(p.Key));
TempData[WellKnownTempData.SuccessMessage] = "Pairing is successful";
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
{
storeId = store.Id, pairingCode
});
}
TempData[WellKnownTempData.ErrorMessage] = $"Pairing failed ({pairingResult})";
TempData[WellKnownTempData.ErrorMessage] = $"Pairing failed: {pairingResult}";
return RedirectToAction(nameof(ListTokens), new
{
storeId = store.Id

View File

@ -39,7 +39,7 @@ public partial class UIStoresController
var roles = await _storeRepo.GetStoreRoles(CurrentStore.Id);
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);
}
@ -116,9 +116,9 @@ public partial class UIStoresController
var isOwner = user.StoreRole.Id == StoreRoleId.Owner.Id;
var isLastOwner = isOwner && storeUsers.Count(u => u.StoreRole.Id == StoreRoleId.Owner.Id) == 1;
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))
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 });
}
@ -127,9 +127,9 @@ public partial class UIStoresController
public async Task<IActionResult> DeleteStoreUser(string storeId, string userId)
{
if (await _storeRepo.RemoveStoreUser(storeId, userId))
TempData[WellKnownTempData.SuccessMessage] = "User removed successfully.";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["User removed successfully."].Value;
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 });
}

View File

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

View File

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

View File

@ -58,7 +58,7 @@ namespace BTCPayServer.Controllers
var psbt = (await nbx.CreatePSBTAsync(derivationSettings.AccountDerivation, psbtRequest, cancellationToken));
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
psbt.PSBT.GlobalXPubs.Clear();
return psbt;
@ -92,7 +92,7 @@ namespace BTCPayServer.Controllers
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);
}
Money bumpFee = Money.Zero;
@ -267,10 +267,10 @@ namespace BTCPayServer.Controllers
psbt = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbt);
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);
}
TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["PSBT updated!"].Value;
return RedirectToWalletPSBT(new WalletPSBTViewModel
{
PSBT = psbt.ToBase64(),
@ -479,7 +479,7 @@ namespace BTCPayServer.Controllers
if (vm.InvalidPSBT || psbt is null)
{
if (vm.InvalidPSBT)
vm.Errors.Add("Invalid PSBT");
vm.Errors.Add(StringLocalizer["Invalid PSBT"]);
return View(nameof(WalletPSBT), vm);
}
DerivationSchemeSettings derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
@ -537,15 +537,15 @@ namespace BTCPayServer.Controllers
}
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)
{
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)
{
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
@ -554,9 +554,7 @@ namespace BTCPayServer.Controllers
{
Severity = StatusMessageModel.StatusSeverity.Warning,
AllowDismiss = false,
Html = $"The payjoin transaction could not be created.<br/>" +
$"The original transaction was broadcasted instead. ({psbt.ExtractTransaction().GetHash()})<br/><br/>" +
$"{error}"
Html = $"The payjoin transaction could not be created.<br/>The original transaction was broadcasted instead ({psbt.ExtractTransaction().GetHash()})<br/><br/>" + error
});
return await WalletPSBTReady(walletId, vm, "broadcast");
case "broadcast" when !psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors):
@ -576,14 +574,14 @@ namespace BTCPayServer.Controllers
{
Severity = StatusMessageModel.StatusSeverity.Warning,
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.OriginalPSBT = null;
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);
}
else
@ -595,13 +593,13 @@ namespace BTCPayServer.Controllers
}
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);
}
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))
{
@ -620,7 +618,7 @@ namespace BTCPayServer.Controllers
await FetchTransactionDetails(walletId, derivationSchemeSettings, vm, network);
return View("WalletPSBTDecoded", vm);
default:
vm.Errors.Add("Unknown command");
vm.Errors.Add(StringLocalizer["Unknown command"]);
return View(nameof(WalletPSBT), vm);
}
}
@ -646,7 +644,7 @@ namespace BTCPayServer.Controllers
return View(vm);
}
sourcePSBT = sourcePSBT.Combine(psbt);
TempData[WellKnownTempData.SuccessMessage] = "PSBT Successfully combined!";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["PSBT Successfully combined!"].Value;
return RedirectToWalletPSBT(new WalletPSBTViewModel
{
PSBT = sourcePSBT.ToBase64(),

View File

@ -35,6 +35,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using NBitcoin;
using NBXplorer;
using NBXplorer.DerivationStrategy;
@ -57,6 +58,7 @@ namespace BTCPayServer.Controllers
private ExplorerClientProvider ExplorerClientProvider { get; }
public IServiceProvider ServiceProvider { get; }
public RateFetcher RateFetcher { get; }
public IStringLocalizer StringLocalizer { get; }
private readonly UserManager<ApplicationUser> _userManager;
private readonly NBXplorerDashboard _dashboard;
@ -99,6 +101,7 @@ namespace BTCPayServer.Controllers
DefaultRulesCollection defaultRules,
PaymentMethodHandlerDictionary handlers,
Dictionary<PaymentMethodId, ICheckoutModelExtension> paymentModelExtensions,
IStringLocalizer stringLocalizer,
TransactionLinkProviders transactionLinkProviders)
{
_currencyTable = currencyTable;
@ -124,6 +127,7 @@ namespace BTCPayServer.Controllers
_pullPaymentHostedService = pullPaymentHostedService;
ServiceProvider = serviceProvider;
_walletHistogramService = walletHistogramService;
StringLocalizer = stringLocalizer;
}
[HttpPost]
@ -908,18 +912,17 @@ namespace BTCPayServer.Controllers
try
{
address = BitcoinAddress.Create(bip21, network.NBitcoinNetwork);
vm.Outputs.Add(new WalletSendModel.TransactionOutput()
vm.Outputs.Add(new WalletSendModel.TransactionOutput
{
DestinationAddress = address.ToString()
}
);
});
}
catch
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
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>();
if (selectedTransactions.Length == 0)
{
TempData[WellKnownTempData.ErrorMessage] = $"No transaction selected";
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["No transaction selected"].Value;
return RedirectToAction(nameof(WalletTransactions), new { walletId });
}
@ -1287,12 +1290,12 @@ namespace BTCPayServer.Controllers
.PruneAsync(derivationScheme.AccountDerivation, new PruneRequest(), cancellationToken);
if (result.TotalPruned == 0)
{
TempData[WellKnownTempData.SuccessMessage] = "The wallet is already pruned";
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["The wallet is already pruned"].Value;
}
else
{
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 });
@ -1455,11 +1458,11 @@ namespace BTCPayServer.Controllers
;
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
{
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 });

View File

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

View File

@ -10,6 +10,7 @@ using Fido2NetLib;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Fido2
@ -20,17 +21,22 @@ namespace BTCPayServer.Fido2
{
private readonly UserManager<ApplicationUser> _userManager;
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;
_fido2Service = fido2Service;
StringLocalizer = stringLocalizer;
}
[HttpGet("{id}/delete")]
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")]
@ -41,7 +47,7 @@ namespace BTCPayServer.Fido2
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Html = "The security device was removed successfully."
Html = StringLocalizer["The security device was removed successfully."].Value
});
return RedirectToList();
@ -56,7 +62,7 @@ namespace BTCPayServer.Fido2
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Html = "The security device could not be registered."
Html = StringLocalizer["The security device could not be registered."].Value
});
return RedirectToList();
@ -75,7 +81,7 @@ namespace BTCPayServer.Fido2
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Html = "The security device was registered successfully."
Html = StringLocalizer["The security device was registered successfully."].Value
});
}
else
@ -83,7 +89,7 @@ namespace BTCPayServer.Fido2
TempData.SetStatusMessageModel(new StatusMessageModel
{
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.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Forms;
@ -31,9 +32,11 @@ public class UIFormsController : Controller
private readonly IAuthorizationService _authorizationService;
private readonly StoreRepository _storeRepository;
private FormComponentProviders FormProviders { get; }
private IStringLocalizer StringLocalizer { get; }
public UIFormsController(FormComponentProviders formProviders, FormDataService formDataService,
UriResolver uriResolver,
IStringLocalizer stringLocalizer,
StoreRepository storeRepository, IAuthorizationService authorizationService)
{
FormProviders = formProviders;
@ -41,6 +44,7 @@ public class UIFormsController : Controller
_uriResolver = uriResolver;
_authorizationService = authorizationService;
_storeRepository = storeRepository;
StringLocalizer = stringLocalizer;
}
[HttpGet("~/stores/{storeId}/forms")]
@ -136,7 +140,7 @@ public class UIFormsController : Controller
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = "Form removed"
Message = StringLocalizer["Form removed"].Value
});
return RedirectToAction("FormsList", new { storeId });
}
@ -223,10 +227,10 @@ public class UIFormsController : Controller
}
catch (Exception e)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,9 +12,7 @@ using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.Models;
using BTCPayServer.Payments;
using BTCPayServer.Security;
using BTCPayServer.Services.Altcoins.Monero.Configuration;
using BTCPayServer.Services.Altcoins.Monero.Payments;
using BTCPayServer.Services.Altcoins.Monero.RPC.Models;
@ -25,6 +23,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Services.Altcoins.Monero.UI
{
@ -39,18 +38,18 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
private readonly StoreRepository _StoreRepository;
private readonly MoneroRPCProvider _MoneroRpcProvider;
private readonly PaymentMethodHandlerDictionary _handlers;
private readonly BTCPayNetworkProvider _BtcPayNetworkProvider;
private IStringLocalizer StringLocalizer { get; }
public UIMoneroLikeStoreController(MoneroLikeConfiguration moneroLikeConfiguration,
StoreRepository storeRepository, MoneroRPCProvider moneroRpcProvider,
PaymentMethodHandlerDictionary handlers,
BTCPayNetworkProvider btcPayNetworkProvider)
IStringLocalizer stringLocalizer)
{
_MoneroLikeConfiguration = moneroLikeConfiguration;
_StoreRepository = storeRepository;
_MoneroRpcProvider = moneroRpcProvider;
_handlers = handlers;
_BtcPayNetworkProvider = btcPayNetworkProvider;
StringLocalizer = stringLocalizer;
}
public StoreData StoreData => HttpContext.GetStoreData();
@ -178,7 +177,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
}
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;
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;
}
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;
}
@ -202,10 +201,10 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
{
if (summary.WalletAvailable)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
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),
new { cryptoCode });
@ -266,14 +265,14 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
}
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);
}
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
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 });
}

View File

@ -12,10 +12,8 @@ using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.Models;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Security;
using BTCPayServer.Services.Altcoins.Zcash.Configuration;
using BTCPayServer.Services.Altcoins.Zcash.Payments;
using BTCPayServer.Services.Altcoins.Zcash.RPC.Models;
@ -26,6 +24,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Services.Altcoins.Zcash.UI
{
@ -40,15 +39,18 @@ namespace BTCPayServer.Services.Altcoins.Zcash.UI
private readonly StoreRepository _StoreRepository;
private readonly ZcashRPCProvider _ZcashRpcProvider;
private readonly PaymentMethodHandlerDictionary _handlers;
private IStringLocalizer StringLocalizer { get; }
public UIZcashLikeStoreController(ZcashLikeConfiguration ZcashLikeConfiguration,
StoreRepository storeRepository, ZcashRPCProvider ZcashRpcProvider,
PaymentMethodHandlerDictionary handlers)
PaymentMethodHandlerDictionary handlers,
IStringLocalizer stringLocalizer)
{
_ZcashLikeConfiguration = ZcashLikeConfiguration;
_StoreRepository = storeRepository;
_ZcashRpcProvider = ZcashRpcProvider;
_handlers = handlers;
StringLocalizer = stringLocalizer;
}
public StoreData StoreData => HttpContext.GetStoreData();
@ -151,7 +153,7 @@ namespace BTCPayServer.Services.Altcoins.Zcash.UI
}
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;
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;
}
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;
}
@ -175,10 +177,10 @@ namespace BTCPayServer.Services.Altcoins.Zcash.UI
{
if (summary.WalletAvailable)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
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),
new { cryptoCode });

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -28,12 +28,12 @@
if (onChainPaymentData.PayjoinInformation is PayjoinInformation pj)
{
payjoinInformation = pj;
m.AdditionalInformation = "Original transaction";
m.AdditionalInformation = StringLocalizer["Original transaction"];
}
if (payjoinInformation is PayjoinInformation &&
payjoinInformation.CoinjoinTransactionHash == onChainPaymentData?.Outpoint.Hash)
{
m.AdditionalInformation = "Payjoin transaction";
m.AdditionalInformation = StringLocalizer["Payjoin transaction"];
}
m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString();
m.ReceivedTime = payment.ReceivedTime;
@ -54,26 +54,26 @@
{
var hasNetworkFee = payments.Sum(a => a.NetworkFee) > 0;
<section>
<h5>On-Chain Payments</h5>
<h5 text-translate="true">On-Chain Payments</h5>
<div class="invoice-payments table-responsive mt-0">
<table class="table table-hover mb-0">
<thead>
<tr>
<th class="w-75px">Payment Method</th>
<th class="w-100px">Index</th>
<th class="w-175px">Destination</th>
<th class="text-nowrap">Payment Proof</th>
<th text-translate="true" class="w-75px">Payment Method</th>
<th text-translate="true" class="w-100px">Index</th>
<th text-translate="true" class="w-175px">Destination</th>
<th text-translate="true" class="text-nowrap">Payment Proof</th>
@if (hasNetworkFee)
{
<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...">
<vc:icon symbol="info" />
</a>
</th>
}
<th class="text-end">Confirmations</th>
<th class="w-150px text-end">Paid</th>
<th text-translate="true" class="text-end">Confirmations</th>
<th text-translate="true" class="w-150px text-end">Paid</th>
</tr>
</thead>
<tbody>
@ -81,7 +81,7 @@
{
<tr style="@(payment.Replaced ? "text-decoration: line-through" : "")">
<td>@payment.PaymentMethodId</td>
<td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? "Unknown")</td>
<td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? StringLocalizer["Unknown"])</td>
<td>
<vc:truncate-center text="@payment.DepositAddress.ToString()" classes="truncate-center-id" />
</td>

View File

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

View File

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

View File

@ -40,8 +40,8 @@
<body class="min-vh-100 p-2">
@if (!Model.Enabled)
{
<div class="alert alert-warning text-center sticky-top mb-0 rounded-0" role="alert">
This crowdfund page is not publically viewable!
<div class="alert alert-warning text-center sticky-top mb-0 rounded-0" role="alert" text-translate="true">
This crowdfund page is not publicly viewable!
</div>
}
@if (Model.AnimationsEnabled)
@ -71,19 +71,19 @@
<span v-if="srvModel.resetEvery !== 'Never'"
class="h5 ms-2"
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
</span>
}
@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
</span>
}
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
</span>
}
@ -92,18 +92,18 @@
@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">
Starts @TimeZoneInfo.ConvertTimeFromUtc(Model.StartDate.Value, TimeZoneInfo.Local)
@StringLocalizer["Starts {0}", TimeZoneInfo.ConvertTimeFromUtc(Model.StartDate.Value, TimeZoneInfo.Local)]
</h6>
}
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">
Ends @TimeZoneInfo.ConvertTimeFromUtc(Model.EndDate.Value, TimeZoneInfo.Local)
@StringLocalizer["Ends {0}", TimeZoneInfo.ConvertTimeFromUtc(Model.EndDate.Value, TimeZoneInfo.Local)]
</h6>
}
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!
</h6>
}
@ -160,7 +160,7 @@
<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>
<h5 class="text-muted fst-italic mb-0">Contributors</h5>
<h5 class="text-muted fst-italic mb-0" text-translate="true">Contributors</h5>
</div>
@if (Model.StartDate.HasValue || Model.EndDate.HasValue)
@ -170,20 +170,20 @@
{
<div v-if="startDiff">
<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>
}
else if (Model.Started && !Model.Ended && Model.EndDate.HasValue)
{
<div v-if="!startDiff && endDiff">
<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>
}
else if (Model.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>
}
<b-tooltip v-if="startDate || endDate" target="crowdfund-body-campaign-dates" class="only-for-js">
@ -191,13 +191,13 @@
@if (Model.StartDate.HasValue)
{
<li v-if="startDate" class="list-unstyled">
{{started? "Started" : "Starts"}} {{startDate}}
{{started ? "Started" : "Starts"}} {{startDate}}
</li>
}
@if (Model.EndDate.HasValue)
{
<li v-if="endDate" class="list-unstyled">
{{ended? "Ended" : "Ends"}} {{endDate}}
{{ended ? "Ended" : "Ends"}} {{endDate}}
</li>
}
</ul>
@ -207,7 +207,7 @@
</div>
<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 class="row mt-4 justify-content-between gap-5">
@ -245,10 +245,7 @@
<div class="overflow-hidden">@Safe.Raw(Model.Description)</div>
</div>
<div class="col-md-4 col-sm-12">
<partial
name="Crowdfund/Public/ContributeForm"
model="@(new ContributeToCrowdfund { ViewCrowdfundViewModel = Model, RedirectToCheckout = true })">
</partial>
<partial name="Crowdfund/Public/ContributeForm" model="@(new ContributeToCrowdfund { ViewCrowdfundViewModel = Model, RedirectToCheckout = true })" />
</div>
</div>
</noscript>
@ -264,7 +261,7 @@
<footer class="store-footer">
<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">
Powered by <partial name="_StoreFooterLogo" />
<span text-translate="true">Powered by</span> <partial name="_StoreFooterLogo" />
</a>
</footer>
</div>
@ -300,7 +297,7 @@
</span>
<div class="perk-zoom" v-if="canExpand">
<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
</div>
</div>
@ -311,17 +308,15 @@
<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="text-muted">
<template v-if="perk.priceType === 'Fixed' && amount ==0">
<template v-if="perk.priceType === 'Fixed' && amount == 0" text-translate="true">
Free
</template>
<template v-else-if="amount">
{{formatAmount(perk.price.noExponents(), srvModel.currencyData.divisibility)}}
{{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 v-else-if="perk.priceType === 'Topup' || (!amount && perk.priceType === 'Minimum')">
<template v-else-if="perk.priceType === 'Topup' || (!amount && perk.priceType === 'Minimum')" text-translate="true">
Any amount
</template>
</span>
@ -343,7 +338,7 @@
:class="{'btn-disabled': loading}"
type="submit">
<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>
{{perk.buyButtonText || 'Continue'}}
</button>
@ -361,7 +356,7 @@
<template id="contribute-template">
<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"
:loading="loading"
:in-modal="inModal"

View File

@ -109,7 +109,7 @@
<div>
<label asp-for="Enabled" class="form-check-label"></label>
<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>
@ -173,9 +173,9 @@
<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" />
<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>
<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>
@ -204,7 +204,7 @@
</div>
<div class="row">
<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">
<input asp-for="SortPerksByPopularity" type="checkbox" class="btcpay-toggle me-3" />
<label asp-for="SortPerksByPopularity" class="form-check-label"></label>
@ -226,27 +226,27 @@
<span asp-validation-for="EnforceTargetAmount" class="text-danger"></span>
</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">
<input asp-for="UseAllStoreInvoices" type="checkbox" class="btcpay-toggle me-3" />
<label asp-for="UseAllStoreInvoices" class="form-check-label"></label>
<span asp-validation-for="UseAllStoreInvoices" class="text-danger"></span>
</div>
<h3 class="mt-5 mb-4">Checkout</h3>
<h3 class="mt-5 mb-4" text-translate="true">Checkout</h3>
<div class="form-group">
<label asp-for="FormId" class="form-label"></label>
<select asp-for="FormId" class="form-select w-auto" asp-items="@checkoutFormOptions"></select>
<span asp-validation-for="FormId" class="text-danger"></span>
</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="accordion" id="additional">
<div class="accordion-item">
<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">
Sound
<span text-translate="true">Sound</span>
<vc:icon symbol="caret-down" />
</button>
</h2>
@ -272,7 +272,7 @@
<div class="accordion-item">
<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">
Animation
<span text-translate="true">Animation</span>
<vc:icon symbol="caret-down" />
</button>
</h2>

View File

@ -12,9 +12,9 @@
}
<div class="form-group">
<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">
<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
</button>
<div class="dropdown-menu" aria-labelledby="QuickFillDropdownToggle">
@ -30,31 +30,30 @@
<span asp-validation-for="Settings.Server" class="text-danger"></span>
</div>
<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"/>
<span asp-validation-for="Settings.Port" class="text-danger"></span>
</div>
<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>" />
<span asp-validation-for="Settings.From" class="text-danger"></span>
</div>
<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"/>
<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>
</div>
<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)
{
<label asp-for="Settings.Password" class="form-label"></label>
<input asp-for="Settings.Password" type="password" class="form-control"/>
<span asp-validation-for="Settings.Password" class="text-danger"></span>
}
else
{
<label asp-for="Settings.Password" class="form-label"></label>
<div class="input-group">
<input value="Configured" type="text" readonly class="form-control"/>
<button type="submit" class="btn btn-danger" name="command" value="ResetPassword" id="ResetPassword">Reset</button>
@ -65,13 +64,13 @@
<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">
<vc:icon symbol="caret-down"/>
<span class="ms-1">Advanced settings</span>
<span class="ms-1" text-translate="true">Advanced settings</span>
</button>
<div id="AdvancedSettings" class="collapse">
<div class="pt-3 pb-1">
<div class="d-flex">
<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>

View File

@ -1,13 +1,13 @@
@model BTCPayServer.Models.EmailsViewModel
<h3 class="my-3">Testing</h3>
<h3 class="my-3" text-translate="true">Testing</h3>
<div class="row">
<div class="col-xl-10 col-xxl-constrain">
<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" />
<span asp-validation-for="TestEmail" class="text-danger"></span>
</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>

View File

@ -42,17 +42,17 @@
asp-route-sortOrder="@(nextRoleSortOrder ?? "asc")"
class="text-nowrap"
title="@(nextRoleSortOrder == "desc" ? sortByAsc : sortByDesc)">
Role
<span text-translate="true">Role</span>
<vc:icon symbol="actions-sort-alpha-@(roleSortOrder ?? nextRoleSortOrder ?? "desc")" />
</a>
</th>
<th>Scope</th>
<th>Permissions</th>
<th text-translate="true">Scope</th>
<th text-translate="true">Permissions</th>
@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>
</thead>
<tbody>
@ -64,7 +64,7 @@
<span>@role.Role</span>
@if (Model.DefaultRole == role.Id)
{
<span class="badge bg-info">
<span class="badge bg-info" text-translate="true">
Default
</span>
}
@ -73,13 +73,13 @@
<td>
@if (role.IsServerRole)
{
<span class="badge bg-dark">
<span class="badge bg-dark" text-translate="true">
Server-wide
</span>
}
else
{
<span class="badge bg-light">
<span class="badge bg-light" text-translate="true">
Store-level
</span>
}
@ -89,7 +89,7 @@
{
<span class="info-note text-warning">
<vc:icon symbol="warning"/>
No policies
<span text-translate="true">No policies</span>
</span>
}
else
@ -117,10 +117,10 @@
<div class="d-inline-flex align-items-center gap-3">
@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="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="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")" text-translate="true">Remove</a>
</div>
</td>
</tr>

View File

@ -129,7 +129,7 @@
</main>
<footer class="store-footer">
<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>
</footer>
</div>
@ -244,7 +244,7 @@
<td colspan="2" class="pt-4">
<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">
<span class="visually-hidden">Loading...</span>
<span class="visually-hidden" text-translate="true">Loading...</span>
</div>
<template v-else>Pay</template>
</button>

View File

@ -29,7 +29,7 @@
}
<footer class="store-footer">
<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>
</footer>
</div>

View File

@ -4,7 +4,7 @@
<div class="input-group">
<span class="input-group-text">@Model.CurrencySymbol</span>
<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>
</form>
</div>

View File

@ -133,7 +133,7 @@ else
</main>
<footer class="store-footer d-print-none">
<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>
</footer>
</div>

View File

@ -8,7 +8,7 @@
<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">
<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 type="button" class="btn-close py-3" aria-label="Close" v-on:click="hideRecentTransactions">
<vc:icon symbol="close"/>

View File

@ -55,7 +55,14 @@
@if (item.Inventory.HasValue)
{
<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>
}
</div>
@ -88,8 +95,8 @@
<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-body p-3 d-flex flex-column gap-2 mb-auto">
<h5 class="card-title">Custom Amount</h5>
<p class="card-text">Create invoice to pay custom amount</p>
<h5 class="card-title" text-translate="true">Custom Amount</h5>
<p class="card-text" text-translate="true">Create invoice to pay custom amount</p>
</div>
<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">
@ -107,7 +114,7 @@
</main>
<footer class="store-footer">
<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>
</footer>
</div>

View File

@ -69,9 +69,11 @@
</div>
<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">
<span class="visually-hidden">Loading...</span>
<span class="visually-hidden" text-translate="true">Loading...</span>
</div>
<template v-else>Charge</template>
<template v-else>
<span text-translate="true">Charge</span>
</template>
</button>
<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">

View File

@ -7,11 +7,11 @@
@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
{
<h1 text-translate="true">No scope</h1>
<h1 text-translate="true" text-translate="true">No scope</h1>
}
<ul>

View File

@ -11,7 +11,7 @@
@if (isEmailConfigured)
{
<p>
<p text-translate="true">
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.
</p>
@ -27,13 +27,13 @@
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<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>
</form>
}
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>
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>
@ -43,5 +43,5 @@ else
}
<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>

View File

@ -3,8 +3,8 @@
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">
<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>

View File

@ -6,13 +6,13 @@
@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))
{
<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
{
<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="d-flex justify-content-between">
<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 class="input-group d-flex">
<input asp-for="Password" class="form-control" required />
@ -35,7 +35,7 @@
</div>
<div class="form-group mt-4">
<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">
<vc:icon symbol="scan-qr" />
</button>
@ -49,7 +49,7 @@
@if (!PoliciesSettings.LockSubscription)
{
<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>
}

View File

@ -1,7 +1,7 @@
@model LoginWith2faViewModel
<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">
@if (!ViewContext.ModelState.IsValid)
{
@ -19,7 +19,7 @@
<span asp-validation-for="RememberMachine" class="text-danger"></span>
</div>
<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>
</form>
<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="RememberMe"/>
</form>
<h2 class="h3 mb-3">FIDO2 Authentication</h2>
<p>Insert your security device and proceed.</p>
<h2 class="h3 mb-3" text-translate="true">FIDO2 Authentication</h2>
<p text-translate="true">Insert your security device and proceed.</p>
<div id="info-message" class="alert alert-info mb-0 d-none">
<div class="d-flex align-items-center">
<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>
<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>
<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>
<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>
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="UserId"/>
</form>
<h2 class="h3 mb-3">LNURL Authentication</h2>
<p>Scan the QR code with your Lightning wallet to sign in.</p>
<h2 class="h3 mb-3" text-translate="true">LNURL Authentication</h2>
<p text-translate="true">Scan the QR code with your Lightning wallet to sign in.</p>
<div class="align-items-center" style="width:256px">
<ul class="nav my-3 btcpay-pills align-items-center gap-2">
@for (var i = 0; i < formats.Count; i++)
@ -34,7 +34,7 @@
<div class="qr-container" style="min-height:256px">
<vc:qr-code data="@mode.Value" />
</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
</a>
</div>

View File

@ -4,7 +4,7 @@
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
an authenticator app code at login or disable 2FA and login again.
</p>
@ -14,7 +14,7 @@
<input asp-for="RecoveryCode" class="form-control" autocomplete="off" />
<span asp-validation-for="RecoveryCode" class="text-danger"></span>
</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>
@section PageFootContent {

View File

@ -41,5 +41,5 @@
</fieldset>
</form>
<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>

View File

@ -21,7 +21,7 @@ else if (Model.LoginWith2FaViewModel == null && Model.LoginWithFido2ViewModel ==
{
<div class="row">
<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">
</div>
</div>

View File

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

View File

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

View File

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

View File

@ -7,16 +7,16 @@
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<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 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>
<h2 text-translate="true">@ViewData["Title"]</h2>
</nav>
</div>
<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">
<input type="hidden" name="data" id="data"/>
@ -27,14 +27,14 @@
<div id="info-message" class="alert alert-info mb-3 d-none">
<div class="d-flex align-items-center">
<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>
<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>
<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>
<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>

View File

@ -9,7 +9,7 @@
<div class="sticky-header">
<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...">
<vc:icon symbol="info" />
</a>
@ -27,8 +27,8 @@
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th class="actions-col" permission="@Policies.CanModifyStoreSettings">Actions</th>
<th text-translate="true">Name</th>
<th text-translate="true" class="actions-col" permission="@Policies.CanModifyStoreSettings">Actions</th>
</tr>
</thead>
<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>
</td>
<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="ViewPublicForm" asp-route-formId="@item.Id" id="View-@item.Name">View</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" text-translate="true">View</a>
</td>
</tr>
}
@ -51,7 +51,7 @@
}
else
{
<p class="text-secondary">
<p class="text-secondary" text-translate="true">
There are no forms yet.
</p>
}

View File

@ -16,10 +16,10 @@
@section PageFootContent {
<datalist id="special-field-names">
<option value="invoice_amount">Determine the generated invoice amount</option>
<option 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 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">Determine the generated invoice amount</option>
<option text-translate="true" value="invoice_currency">Determine the generated invoice currency</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 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>
<template id="form-template-email">
@FormDataService.StaticFormEmail
@ -42,14 +42,14 @@
<div class="form-group">
<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" />
<div 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 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 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">The name of the field in the invoice's metadata.</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 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 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 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 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 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">
@ -70,23 +70,23 @@
</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)">
<vc:icon symbol="actions-add" />
Add Option
<span text-translate="true">Add Option</span>
</button>
</div>
<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" />
</div>
<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">
<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>
<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 class="form-group" v-if="field.type === 'mirror'">
<h5 class="mt-2">Value Mapper</h5>
<div class="form-text">The values being mirrored from another field will be mapped to another value if configured.</div>
<h5 class="mt-2" text-translate="true">Value Mapper</h5>
<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 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">
@ -107,25 +107,25 @@
</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)">
<vc:icon symbol="actions-add" />
Add mapped value
<span text-translate="true">Add mapped value</span>
</button>
</div>
<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" />
<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 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" />
<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 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" />
<label for="field-editor-field-constant" class="form-check-label">Constant</label>
<div class="form-text">The user will not be able to change the field's value</div>
<label for="field-editor-field-constant" class="form-check-label" text-translate="true">Constant</label>
<div class="form-text" text-translate="true">The user will not be able to change the field's value</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 id="fields-editor">
<div>
@ -144,7 +144,7 @@
</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)">
<vc:icon symbol="actions-add" />
Add Form Field
<span text-translate="true">Add Form Field</span>
</button>
</div>
</template>
@ -174,7 +174,7 @@
<template id="field-type-mirror">
<div class="form-group mb-0">
<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>
</template>
<template id="field-type-fieldset">
@ -196,9 +196,9 @@
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<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 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>
<h2>
@ViewData["Title"]
@ -211,7 +211,7 @@
<button id="page-primary" type="submit" class="btn btn-primary order-sm-1">Save</button>
@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>
@ -230,7 +230,7 @@
<input asp-for="Public" type="checkbox" class="btcpay-toggle" />
<div>
<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
independent of payment requests or apps.
</div>
@ -243,16 +243,16 @@
<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">
<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 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>
</ul>
<div class="d-flex align-items-center gap-2 mb-1">
<span class="fw-semibold">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('address')" id="ApplyAddressTemplate">Address</button>
<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" text-translate="true">Email</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 class="tab-content">
@ -271,7 +271,7 @@
</div>
<div class="col-xl-5 offcanvas-xl offcanvas-end" tabindex="-1" ref="editorOffcanvas">
<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">
<vc:icon symbol="close" />
</button>
@ -283,7 +283,7 @@
</div>
</div>
<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>
<span asp-validation-for="FormConfig" class="text-danger"></span>
</div>

View File

@ -48,7 +48,7 @@
</main>
<footer class="store-footer">
<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>
</footer>
</div>

View File

@ -13,13 +13,13 @@
@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">
<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"/>
<div class="content">
<h5 class="mb-0">Create your store</h5>
<h5 class="mb-0" text-translate="true">Create your store</h5>
</div>
<vc:icon symbol="caret-right"/>
</a>

View File

@ -20,23 +20,23 @@
</div>
</form>
<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="input-group">
<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>
<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>
</form>
<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="input-group">
<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>
<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>
</form>
</section>

View File

@ -16,30 +16,30 @@
<h1>Pay with @Model.StoreName</h1>
@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")
{
<div>
<p>This invoice has been paid</p>
<p text-translate="true">This invoice has been paid</p>
</div>
}
else if (Model.Status == "expired" || Model.Status == "invalid")
{
<div>
<p>This invoice has expired</p>
<p text-translate="true">This invoice has expired</p>
</div>
}
else
{
<div>
<p>Non-supported state of invoice</p>
<p text-translate="true">Non-supported state of invoice</p>
</div>
}
<hr />
<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>
@await Component.InvokeAsync("UiExtensionPoint", new { location = "checkout-noscript-end", model = Model })
</body>

View File

@ -32,9 +32,9 @@
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<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 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>
<h2 text-translate="true">@ViewData["Title"]</h2>
</nav>
@ -91,29 +91,29 @@
<div class="form-group">
<label asp-for="DefaultPaymentMethod" class="form-label"></label>
<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>
<span asp-validation-for="DefaultPaymentMethod" class="text-danger"></span>
</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">
<label asp-for="BuyerEmail" class="form-label"></label>
<input asp-for="BuyerEmail" class="form-control"/>
<span asp-validation-for="BuyerEmail" class="text-danger"></span>
</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="accordion" id="additional">
<div class="accordion-item">
<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">
Metadata
<span text-translate="true">Metadata</span>
<vc:icon symbol="caret-down" />
</button>
</h2>
<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">
<label asp-for="Metadata" class="form-label"></label>
<textarea asp-for="Metadata" class="form-control" rows="10" cols="40"></textarea>
@ -124,7 +124,7 @@
<div class="accordion-item">
<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">
Invoice Notifications
<span text-translate="true">Invoice Notifications</span>
<vc:icon symbol="caret-down" />
</button>
</h2>
@ -139,7 +139,7 @@
<label asp-for="NotificationEmail" class="form-label"></label>
<input asp-for="NotificationEmail" class="form-control" />
<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>

View File

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

View File

@ -186,7 +186,7 @@
</a>
</p>
<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>
</footer>
</div>

View File

@ -251,7 +251,7 @@
}
</div>
<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>
<hr class="w-100 my-0 bg-none"/>
</center>

View File

@ -9,29 +9,29 @@
<vc:icon symbol="close" />
</button>
<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="col-12 col-md-6">
<ul class="list-unstyled mb-md-0">
<li>
<span class="badge badge-processing">Paid</span>
<span class="mx-1">is now shown as</span>
<span class="badge badge-processing">Processing</span>
<span text-translate="true" class="badge badge-processing">Paid</span>
<span text-translate="true" class="mx-1">is now shown as</span>
<span text-translate="true" class="badge badge-processing">Processing</span>
</li>
<li class="mt-2">
<span class="badge badge-settled">Completed</span>
<span class="mx-1">is now shown as</span>
<span class="badge badge-settled">Settled</span>
<span text-translate="true" class="badge badge-settled">Completed</span>
<span text-translate="true" class="mx-1">is now shown as</span>
<span text-translate="true" class="badge badge-settled">Settled</span>
</li>
<li class="mt-2">
<span class="badge badge-settled">Confirmed</span>
<span class="mx-1">is now shown as</span>
<span class="badge badge-settled">Settled</span>
<span text-translate="true" class="badge badge-settled">Confirmed</span>
<span text-translate="true" class="mx-1">is now shown as</span>
<span text-translate="true" class="badge badge-settled">Settled</span>
</li>
</ul>
</div>
<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>
</form>

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