Add translations to the Dashboard

This commit is contained in:
nicolas.dorier
2024-10-14 14:11:00 +09:00
parent 73a9835a27
commit c35af2dc69
31 changed files with 331 additions and 153 deletions

View File

@@ -8,6 +8,14 @@ namespace BTCPayServer.Abstractions.Extensions;
public static class SetStatusMessageModelExtensions public static class SetStatusMessageModelExtensions
{ {
public static void SetStatusSuccess(this ITempDataDictionary tempData, string statusMessage)
{
tempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Success,
Message = statusMessage
});
}
public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage) public static void SetStatusMessageModel(this ITempDataDictionary tempData, StatusMessageModel statusMessage)
{ {
if (statusMessage == null) if (statusMessage == null)

View File

@@ -16,11 +16,13 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Controllers; using BTCPayServer.Controllers;
using ExchangeSharp; using ExchangeSharp;
using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.FileSystemGlobbing;
using NBitcoin; using NBitcoin;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -349,6 +351,8 @@ retry:
{ {
defaultTranslatedKeys.Add(k); defaultTranslatedKeys.Add(k);
} }
AddLocalizers(defaultTranslatedKeys, txt);
} }
// Go through all cshtml file, search for text-translate or ViewLocalizer usage // Go through all cshtml file, search for text-translate or ViewLocalizer usage
@@ -360,21 +364,11 @@ retry:
{ {
var filePath = file.FullName; var filePath = file.FullName;
var txt = File.ReadAllText(file.FullName); var txt = File.ReadAllText(file.FullName);
foreach (string localizer in new[] { "ViewLocalizer", "StringLocalizer" }) AddLocalizers(defaultTranslatedKeys, txt);
{
if (txt.Contains(localizer))
{
var matches = Regex.Matches(txt, localizer + "\\[\"(.*?)\"[\\],]");
foreach (Match match in matches)
{
defaultTranslatedKeys.Add(match.Groups[1].Value);
}
}
}
filePath = filePath.Replace(Path.Combine(soldir.FullName, "BTCPayServer"), "/"); filePath = filePath.Replace(Path.Combine(soldir.FullName, "BTCPayServer"), "/");
var item = engine.FileSystem.GetItem(filePath); var item = engine.FileSystem.GetItem(filePath);
var node = (DocumentIntermediateNode)engine.Process(item).Items[typeof(DocumentIntermediateNode)]; var node = (DocumentIntermediateNode)engine.Process(item).Items[typeof(DocumentIntermediateNode)];
var w = new TranslatedKeyNodeWalker(defaultTranslatedKeys, txt); var w = new TranslatedKeyNodeWalker(defaultTranslatedKeys, txt);
w.Visit(node); w.Visit(node);
@@ -397,6 +391,24 @@ retry:
content += defaultTranslation.Substring(endIdx); content += defaultTranslation.Substring(endIdx);
File.WriteAllText(path, content); File.WriteAllText(path, content);
} }
private static void AddLocalizers(List<string> defaultTranslatedKeys, string txt)
{
foreach (string localizer in new[] { "ViewLocalizer", "StringLocalizer" })
{
if (txt.Contains(localizer))
{
var matches = Regex.Matches(txt, localizer + "\\[\"(.*?)\"[\\],]");
foreach (Match match in matches)
{
var k = match.Groups[1].Value;
k = k.Replace("\\", "");
defaultTranslatedKeys.Add(k);
}
}
}
}
class DisplayNameWalker : CSharpSyntaxWalker class DisplayNameWalker : CSharpSyntaxWalker
{ {
public List<string> Keys = new List<string>(); public List<string> Keys = new List<string>();

View File

@@ -107,13 +107,13 @@
@if (ViewData.IsCategoryActive(typeof(WalletsNavPages), scheme.WalletId.ToString()) || ViewData.IsPageActive([WalletsNavPages.Settings], scheme.WalletId.ToString()) || ViewData.IsPageActive([StoreNavPages.OnchainSettings], categoryId)) @if (ViewData.IsCategoryActive(typeof(WalletsNavPages), scheme.WalletId.ToString()) || ViewData.IsPageActive([WalletsNavPages.Settings], scheme.WalletId.ToString()) || ViewData.IsPageActive([StoreNavPages.OnchainSettings], categoryId))
{ {
<li class="nav-item nav-item-sub"> <li class="nav-item nav-item-sub">
<a id="WalletNav-Send" class="nav-link @ViewData.ActivePageClass([WalletsNavPages.Send, WalletsNavPages.PSBT], scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@scheme.WalletId">Send</a> <a id="WalletNav-Send" class="nav-link @ViewData.ActivePageClass([WalletsNavPages.Send, WalletsNavPages.PSBT], scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@scheme.WalletId" text-translate="true">Send</a>
</li> </li>
<li class="nav-item nav-item-sub"> <li class="nav-item nav-item-sub">
<a id="WalletNav-Receive" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Receive, scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletReceive" asp-route-walletId="@scheme.WalletId">Receive</a> <a id="WalletNav-Receive" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Receive, scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletReceive" asp-route-walletId="@scheme.WalletId" text-translate="true">Receive</a>
</li> </li>
<li class="nav-item nav-item-sub"> <li class="nav-item nav-item-sub">
<a id="WalletNav-Settings" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Settings, scheme.WalletId.ToString()) @ViewData.ActivePageClass(StoreNavPages.OnchainSettings, categoryId)" asp-area="" asp-controller="UIStores" asp-action="WalletSettings" asp-route-cryptoCode="@scheme.WalletId.CryptoCode" asp-route-storeId="@scheme.WalletId.StoreId">Settings</a> <a id="WalletNav-Settings" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Settings, scheme.WalletId.ToString()) @ViewData.ActivePageClass(StoreNavPages.OnchainSettings, categoryId)" asp-area="" asp-controller="UIStores" asp-action="WalletSettings" asp-route-cryptoCode="@scheme.WalletId.CryptoCode" asp-route-storeId="@scheme.WalletId.StoreId" text-translate="true">Settings</a>
</li> </li>
<vc:ui-extension-point location="wallet-nav" model="@Model" /> <vc:ui-extension-point location="wallet-nav" model="@Model" />
} }
@@ -143,7 +143,7 @@
@if (ViewData.IsPageActive([StoreNavPages.Lightning, StoreNavPages.LightningSettings], $"{Model.Store.Id}-{scheme.CryptoCode}")) @if (ViewData.IsPageActive([StoreNavPages.Lightning, StoreNavPages.LightningSettings], $"{Model.Store.Id}-{scheme.CryptoCode}"))
{ {
<li class="nav-item nav-item-sub"> <li class="nav-item nav-item-sub">
<a id="StoreNav-@(nameof(StoreNavPages.LightningSettings))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.LightningSettings)" asp-controller="UIStores" asp-action="LightningSettings" asp-route-storeId="@Model.Store.Id" asp-route-cryptoCode="@scheme.CryptoCode">Settings</a> <a id="StoreNav-@(nameof(StoreNavPages.LightningSettings))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.LightningSettings)" asp-controller="UIStores" asp-action="LightningSettings" asp-route-storeId="@Model.Store.Id" asp-route-cryptoCode="@scheme.CryptoCode" text-translate="true">Settings</a>
</li> </li>
<vc:ui-extension-point location="lightning-nav" model="@Model"/> <vc:ui-extension-point location="lightning-nav" model="@Model"/>
} }

View File

@@ -5,7 +5,7 @@
} }
<div id="StoreLightningBalance-@Model.Store.Id" class="widget store-lightning-balance"> <div id="StoreLightningBalance-@Model.Store.Id" class="widget store-lightning-balance">
<div class="d-flex gap-3 align-items-center justify-content-between mb-2"> <div class="d-flex gap-3 align-items-center justify-content-between mb-2">
<h6>Lightning Balance</h6> <h6 text-translate="true">Lightning Balance</h6>
@if (Model.CryptoCode != Model.DefaultCurrency && Model.Balance != null) @if (Model.CryptoCode != Model.DefaultCurrency && Model.Balance != null)
{ {
<div class="btn-group btn-group-sm gap-0 currency-toggle" role="group"> <div class="btn-group btn-group-sm gap-0 currency-toggle" role="group">
@@ -29,7 +29,7 @@
<div class="d-flex align-items-baseline gap-1"> <div class="d-flex align-items-baseline gap-1">
<h3 class="d-inline-block me-1" data-balance="@Model.TotalOffchain" data-sensitive>@Model.TotalOffchain</h3> <h3 class="d-inline-block me-1" data-balance="@Model.TotalOffchain" data-sensitive>@Model.TotalOffchain</h3>
<span class="text-secondary fw-semibold text-nowrap"> <span class="text-secondary fw-semibold text-nowrap">
<span class="currency">@Model.CryptoCode</span> in channels @ViewLocalizer["<span class=\"currency\">{0}</span> in channels", @Model.CryptoCode]
</span> </span>
</div> </div>
@@ -41,7 +41,7 @@
@Model.Balance.OffchainBalance.Opening @Model.Balance.OffchainBalance.Opening
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
<span class="currency">@Model.CryptoCode</span> opening channels @ViewLocalizer["<span class=\"currency\">{0}</span> opening channels", @Model.CryptoCode]
</span> </span>
</div> </div>
} }
@@ -52,7 +52,7 @@
@Model.Balance.OffchainBalance.Local @Model.Balance.OffchainBalance.Local
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
<span class="currency">@Model.CryptoCode</span> local balance @ViewLocalizer["<span class=\"currency\">{0}</span> local balance", @Model.CryptoCode]
</span> </span>
</div> </div>
} }
@@ -63,7 +63,7 @@
@Model.Balance.OffchainBalance.Remote @Model.Balance.OffchainBalance.Remote
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
<span class="currency">@Model.CryptoCode</span> remote balance @ViewLocalizer["<span class=\"currency\">{0}</span> remote balance", @Model.CryptoCode]
</span> </span>
</div> </div>
} }
@@ -74,7 +74,7 @@
@Model.Balance.OffchainBalance.Closing @Model.Balance.OffchainBalance.Closing
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
<span class="currency">@Model.CryptoCode</span> closing channels @ViewLocalizer["<span class=\"currency\">{0}</span> closing channels", @Model.CryptoCode]
</span> </span>
</div> </div>
} }
@@ -87,7 +87,7 @@
<div class="d-flex align-items-baseline gap-1"> <div class="d-flex align-items-baseline gap-1">
<h3 class="d-inline-block me-1" data-balance="@Model.TotalOnchain" data-sensitive>@Model.TotalOnchain</h3> <h3 class="d-inline-block me-1" data-balance="@Model.TotalOnchain" data-sensitive>@Model.TotalOnchain</h3>
<span class="text-secondary fw-semibold text-nowrap"> <span class="text-secondary fw-semibold text-nowrap">
<span class="currency">@Model.CryptoCode</span> on-chain @ViewLocalizer["<span class=\"currency\">{0}</span> on-chain", @Model.CryptoCode]
</span> </span>
</div> </div>
<div class="balance-details collapse" id="balanceDetailsOnchain"> <div class="balance-details collapse" id="balanceDetailsOnchain">
@@ -98,7 +98,7 @@
@Model.Balance.OnchainBalance.Confirmed @Model.Balance.OnchainBalance.Confirmed
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
<span class="currency">@Model.CryptoCode</span> confirmed @ViewLocalizer["<span class=\"currency\">{0}</span> confirmed", @Model.CryptoCode]
</span> </span>
</div> </div>
} }
@@ -109,7 +109,7 @@
@Model.Balance.OnchainBalance.Unconfirmed @Model.Balance.OnchainBalance.Unconfirmed
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
<span class="currency">@Model.CryptoCode</span> unconfirmed @ViewLocalizer["<span class=\"currency\">{0}</span> unconfirmed", @Model.CryptoCode]
</span> </span>
</div> </div>
} }
@@ -120,7 +120,7 @@
@Model.Balance.OnchainBalance.Reserved @Model.Balance.OnchainBalance.Reserved
</span> </span>
<span class="text-secondary text-nowrap"> <span class="text-secondary text-nowrap">
<span class="currency">@Model.CryptoCode</span> reserved @ViewLocalizer["<span class=\"currency\">{0}</span> reserved", @Model.CryptoCode]
</span> </span>
</div> </div>
} }
@@ -132,7 +132,7 @@
{ {
<button class="d-inline-flex align-items-center btn btn-link text-primary fw-semibold p-0 mt-3 ms-n1" type="button" data-bs-toggle="collapse" data-bs-target=".balance-details" aria-expanded="false" aria-controls="balanceDetailsOffchain balanceDetailsOnchain"> <button class="d-inline-flex align-items-center btn btn-link text-primary fw-semibold p-0 mt-3 ms-n1" type="button" data-bs-toggle="collapse" data-bs-target=".balance-details" aria-expanded="false" aria-controls="balanceDetailsOffchain balanceDetailsOnchain">
<vc:icon symbol="caret-down"/> <vc:icon symbol="caret-down"/>
<span class="ms-1">Details</span> <span class="ms-1" text-translate="true">Details</span>
</button> </button>
} }
} }
@@ -140,7 +140,7 @@
{ {
<div class="loading d-flex justify-content-center p-3"> <div class="loading d-flex justify-content-center p-3">
<div class="spinner-border text-light" role="status"> <div class="spinner-border text-light" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
</div> </div>
<script> <script>

View File

@@ -4,14 +4,15 @@
{ {
<div id="StoreLightningServices-@Model.Store.Id" class="widget store-lightning-services"> <div id="StoreLightningServices-@Model.Store.Id" class="widget store-lightning-services">
<header class="mb-4"> <header class="mb-4">
<h6>Lightning Services</h6> <h6 text-translate="true">Lightning Services</h6>
<a <a
asp-controller="UIPublicLightningNodeInfo" asp-controller="UIPublicLightningNodeInfo"
asp-action="ShowLightningNodeInfo"app-top-items asp-action="ShowLightningNodeInfo"app-top-items
asp-route-cryptoCode="@Model.CryptoCode" asp-route-cryptoCode="@Model.CryptoCode"
asp-route-storeId="@Model.Store.Id" asp-route-storeId="@Model.Store.Id"
target="_blank" target="_blank"
id="PublicNodeInfo"> id="PublicNodeInfo"
text-translate="true">
Node Info Node Info
</a> </a>
</header> </header>

View File

@@ -24,7 +24,7 @@
{ {
<div class="store-number"> <div class="store-number">
<header> <header>
<h6>Paid invoices in the last @Model.TimeframeDays days</h6> <h6 text-translate="true">@ViewLocalizer["Paid invoices in the last {0} days", @Model.TimeframeDays]</h6>
@if (Model.PaidInvoices > 0) @if (Model.PaidInvoices > 0)
{ {
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.Store.Id" permission="@Policies.CanViewInvoices">View All</a> <a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.Store.Id" permission="@Policies.CanViewInvoices">View All</a>
@@ -34,14 +34,14 @@
</div> </div>
<div class="store-number"> <div class="store-number">
<header> <header>
<h6>Payouts Pending</h6> <h6 text-translate="true">Payouts Pending</h6>
<a asp-controller="UIStorePullPayments" asp-action="Payouts" asp-route-storeId="@Model.Store.Id" permission="@Policies.CanManagePullPayments">Manage</a> <a asp-controller="UIStorePullPayments" asp-action="Payouts" asp-route-storeId="@Model.Store.Id" permission="@Policies.CanManagePullPayments" text-translate="true">Manage</a>
</header> </header>
<div class="h3">@Model.PayoutsPending</div> <div class="h3">@Model.PayoutsPending</div>
</div> </div>
<div class="store-number"> <div class="store-number">
<header> <header>
<h6>Refunds Issued</h6> <h6 text-translate="true">Refunds Issued</h6>
</header> </header>
<div class="h3">@Model.RefundsIssued</div> <div class="h3">@Model.RefundsIssued</div>
</div> </div>

View File

@@ -6,17 +6,17 @@
<div class="widget store-recent-invoices" id="StoreRecentInvoices-@Model.Store.Id"> <div class="widget store-recent-invoices" id="StoreRecentInvoices-@Model.Store.Id">
<header> <header>
<h3>Recent Invoices</h3> <h3 text-translate="true">Recent Invoices</h3>
@if (Model.Invoices.Any()) @if (Model.Invoices.Any())
{ {
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.Store.Id">View All</a> <a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.Store.Id" text-translate="true">View All</a>
} }
</header> </header>
@if (Model.InitialRendering) @if (Model.InitialRendering)
{ {
<div class="loading d-flex justify-content-center p-3"> <div class="loading d-flex justify-content-center p-3">
<div class="spinner-border text-light" role="status"> <div class="spinner-border text-light" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
</div> </div>
<script> <script>
@@ -36,10 +36,10 @@
<table class="table table-hover mb-0"> <table class="table table-hover mb-0">
<thead> <thead>
<tr> <tr>
<th class="w-125px">Date</th> <th class="w-125px" text-translate="true">Date</th>
<th class="text-nowrap">Invoice Id</th> <th class="text-nowrap" text-translate="true">Invoice Id</th>
<th>Status</th> <th text-translate="true">Status</th>
<th class="text-end">Amount</th> <th class="text-end" text-translate="true">Amount</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -65,10 +65,10 @@
} }
else else
{ {
<p class="text-secondary my-3"> <p class="text-secondary my-3" text-translate="true">
There are no recent invoices. There are no recent invoices.
</p> </p>
<a asp-controller="UIInvoice" asp-action="CreateInvoice" asp-route-storeId="@Model.Store.Id" class="fw-semibold"> <a asp-controller="UIInvoice" asp-action="CreateInvoice" asp-route-storeId="@Model.Store.Id" class="fw-semibold" text-translate="true">
Create Invoice Create Invoice
</a> </a>
} }

View File

@@ -4,17 +4,17 @@
<div class="widget store-recent-transactions" id="StoreRecentTransactions-@Model.Store.Id"> <div class="widget store-recent-transactions" id="StoreRecentTransactions-@Model.Store.Id">
<header> <header>
<h3>Recent Transactions</h3> <h3 text-translate="true">Recent Transactions</h3>
@if (Model.Transactions.Any()) @if (Model.Transactions.Any())
{ {
<a asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@Model.WalletId">View All</a> <a asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@Model.WalletId" text-translate="true">View All</a>
} }
</header> </header>
@if (Model.InitialRendering) @if (Model.InitialRendering)
{ {
<div class="loading d-flex justify-content-center p-3"> <div class="loading d-flex justify-content-center p-3">
<div class="spinner-border text-light" role="status"> <div class="spinner-border text-light" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden" text-translate="true">Loading...</span>
</div> </div>
</div> </div>
<script> <script>
@@ -34,10 +34,10 @@
<table class="table table-hover mb-0"> <table class="table table-hover mb-0">
<thead> <thead>
<tr> <tr>
<th class="w-125px">Date</th> <th class="w-125px" text-translate="true">Date</th>
<th>Transaction</th> <th text-translate="true">Transaction</th>
<th>Labels</th> <th text-translate="true">Labels</th>
<th class="text-end">Amount</th> <th class="text-end" text-translate="true">Amount</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -90,7 +90,7 @@
} }
else else
{ {
<p class="text-secondary mt-3 mb-0"> <p class="text-secondary mt-3 mb-0" text-translate="true">
There are no recent transactions. There are no recent transactions.
</p> </p>
} }

View File

@@ -4,7 +4,7 @@
@inject BTCPayNetworkProvider NetworkProvider @inject BTCPayNetworkProvider NetworkProvider
<div id="StoreWalletBalance-@Model.Store.Id" class="widget store-wallet-balance"> <div id="StoreWalletBalance-@Model.Store.Id" class="widget store-wallet-balance">
<div class="d-flex gap-3 align-items-center justify-content-between mb-2"> <div class="d-flex gap-3 align-items-center justify-content-between mb-2">
<h6>Wallet Balance</h6> <h6 text-translate="true">Wallet Balance</h6>
@if (Model.CryptoCode != Model.DefaultCurrency) @if (Model.CryptoCode != Model.DefaultCurrency)
{ {
<div class="btn-group btn-group-sm gap-0 currency-toggle" role="group"> <div class="btn-group btn-group-sm gap-0 currency-toggle" role="group">
@@ -39,7 +39,7 @@
{ {
<div class="ct-chart"></div> <div class="ct-chart"></div>
} }
else if (Model.Store.GetPaymentMethodConfig(PaymentTypes.CHAIN.GetPaymentMethodId(Model.CryptoCode)) is null) else if (Model.Store.GetPaymentMethodConfig(PaymentTypes.CHAIN.GetPaymentMethodId(Model.CryptoCode)) is null)
{ {
<p> <p>
We would like to show you a chart of your balance but you have not yet <a href="@Url.Action("SetupWallet", "UIStores", new {storeId = Model.Store.Id, cryptoCode = Model.CryptoCode})">configured a wallet</a>. We would like to show you a chart of your balance but you have not yet <a href="@Url.Action("SetupWallet", "UIStores", new {storeId = Model.Store.Id, cryptoCode = Model.CryptoCode})">configured a wallet</a>.

View File

@@ -18,8 +18,10 @@ using BTCPayServer.Services;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest; using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
using PayoutData = BTCPayServer.Data.PayoutData; using PayoutData = BTCPayServer.Data.PayoutData;
@@ -50,7 +52,10 @@ namespace BTCPayServer.Controllers
} }
} }
public IStringLocalizer StringLocalizer { get; }
public UIStorePullPaymentsController(BTCPayNetworkProvider btcPayNetworkProvider, public UIStorePullPaymentsController(BTCPayNetworkProvider btcPayNetworkProvider,
IStringLocalizer stringLocalizer,
PayoutMethodHandlerDictionary payoutHandlers, PayoutMethodHandlerDictionary payoutHandlers,
CurrencyNameTable currencyNameTable, CurrencyNameTable currencyNameTable,
DisplayFormatter displayFormatter, DisplayFormatter displayFormatter,
@@ -62,6 +67,7 @@ namespace BTCPayServer.Controllers
IAuthorizationService authorizationService) IAuthorizationService authorizationService)
{ {
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
StringLocalizer = stringLocalizer;
_payoutHandlers = payoutHandlers; _payoutHandlers = payoutHandlers;
_currencyNameTable = currencyNameTable; _currencyNameTable = currencyNameTable;
_displayFormatter = displayFormatter; _displayFormatter = displayFormatter;
@@ -85,7 +91,7 @@ namespace BTCPayServer.Controllers
{ {
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "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."],
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId }); return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });
@@ -119,25 +125,25 @@ namespace BTCPayServer.Controllers
// them here to reflect user's selection so that they can correct their mistake // them here to reflect user's selection so that they can correct their mistake
model.PayoutMethodsItem = model.PayoutMethodsItem =
paymentMethodOptions.Select(id => new SelectListItem(id.ToString(), id.ToString(), false)); paymentMethodOptions.Select(id => new SelectListItem(id.ToString(), id.ToString(), false));
ModelState.AddModelError(nameof(model.PayoutMethods), "You need at least one payout method"); ModelState.AddModelError(nameof(model.PayoutMethods), StringLocalizer["You need at least one payout method"]);
} }
if (_currencyNameTable.GetCurrencyData(model.Currency, false) is null) if (_currencyNameTable.GetCurrencyData(model.Currency, false) is null)
{ {
ModelState.AddModelError(nameof(model.Currency), "Invalid currency"); ModelState.AddModelError(nameof(model.Currency), StringLocalizer["Invalid currency"]);
} }
if (model.Amount <= 0.0m) if (model.Amount <= 0.0m)
{ {
ModelState.AddModelError(nameof(model.Amount), "The amount should be more than zero"); ModelState.AddModelError(nameof(model.Amount), StringLocalizer["The amount should be more than zero"]);
} }
if (model.Name.Length > 50) if (model.Name.Length > 50)
{ {
ModelState.AddModelError(nameof(model.Name), "The name should be maximum 50 characters."); ModelState.AddModelError(nameof(model.Name), StringLocalizer["The name should be maximum 50 characters."]);
} }
var selectedPaymentMethodIds = model.PayoutMethods.Select(PayoutMethodId.Parse).ToArray(); var selectedPaymentMethodIds = model.PayoutMethods.Select(PayoutMethodId.Parse).ToArray();
if (!selectedPaymentMethodIds.All(id => paymentMethodOptions.Contains(id))) if (!selectedPaymentMethodIds.All(id => paymentMethodOptions.Contains(id)))
{ {
ModelState.AddModelError(nameof(model.Name), "Not all payout methods are supported"); ModelState.AddModelError(nameof(model.Name), StringLocalizer["Not all payout methods are supported"]);
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
return View(model); return View(model);
@@ -156,7 +162,7 @@ namespace BTCPayServer.Controllers
}); });
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "Pull payment request created", Message = StringLocalizer["Pull payment request created"],
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
return RedirectToAction(nameof(PullPayments), new { storeId }); return RedirectToAction(nameof(PullPayments), new { storeId });
@@ -198,7 +204,7 @@ namespace BTCPayServer.Controllers
{ {
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "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."],
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId }); return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });
@@ -260,7 +266,7 @@ namespace BTCPayServer.Controllers
string pullPaymentId) string pullPaymentId)
{ {
return View("Confirm", return View("Confirm",
new ConfirmModel("Archive pull payment", "Do you really want to archive the pull payment?", "Archive")); new ConfirmModel(StringLocalizer["Archive pull payment"], StringLocalizer["Do you really want to archive the pull payment?"], "Archive"));
} }
[HttpPost("stores/{storeId}/pull-payments/{pullPaymentId}/archive")] [HttpPost("stores/{storeId}/pull-payments/{pullPaymentId}/archive")]
@@ -298,7 +304,7 @@ namespace BTCPayServer.Controllers
{ {
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Message = "No payout selected", Message = StringLocalizer["No payout selected"],
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction(nameof(Payouts), return RedirectToAction(nameof(Payouts),
@@ -341,7 +347,7 @@ namespace BTCPayServer.Controllers
{ {
this.TempData.SetStatusMessageModel(new StatusMessageModel() this.TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Message = $"Rate unavailable: {rateResult.EvaluatedRule}", Message = StringLocalizer["Rate unavailable: {0}", rateResult.EvaluatedRule],
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
failed = true; failed = true;
@@ -379,7 +385,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Message = "Payouts approved", Message = StringLocalizer["Payouts approved"],
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
break; break;
@@ -391,7 +397,7 @@ namespace BTCPayServer.Controllers
return await handler.InitiatePayment(payoutIds); return await handler.InitiatePayment(payoutIds);
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Message = "Paying via this payment method is not supported", Message = StringLocalizer["Paying via this payment method is not supported"],
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
break; break;
@@ -430,7 +436,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Message = "Payouts marked as paid", Message = StringLocalizer["Payouts marked as paid"],
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
break; break;
@@ -441,7 +447,7 @@ namespace BTCPayServer.Controllers
new PullPaymentHostedService.CancelRequest(payoutIds, new[] { storeId })); new PullPaymentHostedService.CancelRequest(payoutIds, new[] { storeId }));
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Message = "Payouts archived", Message = StringLocalizer["Payouts archived"],
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
break; break;
@@ -482,7 +488,7 @@ namespace BTCPayServer.Controllers
{ {
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "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."],
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId }); return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId });

View File

@@ -176,7 +176,7 @@ public partial class UIStoresController
vm.AvailableExchanges = sources; vm.AvailableExchanges = sources;
var exchange = storeBlob.GetPreferredExchange(_defaultRules); var exchange = storeBlob.GetPreferredExchange(_defaultRules);
var chosenSource = sources.First(r => r.Id == exchange); var chosenSource = sources.First(r => r.Id == exchange);
vm.Exchanges = UIUserStoresController.GetExchangesSelectList(_rateFactory, _defaultRules, storeBlob); vm.Exchanges = _userStoresController.GetExchangesSelectList(storeBlob);
vm.PreferredExchange = vm.Exchanges.SelectedValue as string; vm.PreferredExchange = vm.Exchanges.SelectedValue as string;
vm.PreferredResolvedExchange = chosenSource.Id; vm.PreferredResolvedExchange = chosenSource.Id;
vm.RateSource = chosenSource.Url; vm.RateSource = chosenSource.Url;

View File

@@ -58,6 +58,7 @@ public partial class UIStoresController : Controller
DefaultRulesCollection defaultRules, DefaultRulesCollection defaultRules,
EmailSenderFactory emailSenderFactory, EmailSenderFactory emailSenderFactory,
WalletFileParsers onChainWalletParsers, WalletFileParsers onChainWalletParsers,
UIUserStoresController userStoresController,
UriResolver uriResolver, UriResolver uriResolver,
SettingsRepository settingsRepository, SettingsRepository settingsRepository,
CurrencyNameTable currencyNameTable, CurrencyNameTable currencyNameTable,
@@ -82,6 +83,7 @@ public partial class UIStoresController : Controller
_externalServiceOptions = externalServiceOptions; _externalServiceOptions = externalServiceOptions;
_emailSenderFactory = emailSenderFactory; _emailSenderFactory = emailSenderFactory;
_onChainWalletParsers = onChainWalletParsers; _onChainWalletParsers = onChainWalletParsers;
_userStoresController = userStoresController;
_uriResolver = uriResolver; _uriResolver = uriResolver;
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
_currencyNameTable = currencyNameTable; _currencyNameTable = currencyNameTable;
@@ -115,6 +117,7 @@ public partial class UIStoresController : Controller
private readonly IOptions<ExternalServicesOptions> _externalServiceOptions; private readonly IOptions<ExternalServicesOptions> _externalServiceOptions;
private readonly EmailSenderFactory _emailSenderFactory; private readonly EmailSenderFactory _emailSenderFactory;
private readonly WalletFileParsers _onChainWalletParsers; private readonly WalletFileParsers _onChainWalletParsers;
private readonly UIUserStoresController _userStoresController;
private readonly UriResolver _uriResolver; private readonly UriResolver _uriResolver;
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
private readonly IHtmlHelper _html; private readonly IHtmlHelper _html;

View File

@@ -2,6 +2,7 @@ using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Data;
@@ -12,7 +13,9 @@ using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
@@ -21,6 +24,7 @@ namespace BTCPayServer.Controllers
public class UIUserStoresController : Controller public class UIUserStoresController : Controller
{ {
private readonly StoreRepository _repo; private readonly StoreRepository _repo;
private readonly IStringLocalizer StringLocalizer;
private readonly SettingsRepository _settingsRepository; private readonly SettingsRepository _settingsRepository;
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly DefaultRulesCollection _defaultRules; private readonly DefaultRulesCollection _defaultRules;
@@ -31,10 +35,12 @@ namespace BTCPayServer.Controllers
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
DefaultRulesCollection defaultRules, DefaultRulesCollection defaultRules,
StoreRepository storeRepository, StoreRepository storeRepository,
IStringLocalizer stringLocalizer,
RateFetcher rateFactory, RateFetcher rateFactory,
SettingsRepository settingsRepository) SettingsRepository settingsRepository)
{ {
_repo = storeRepository; _repo = storeRepository;
StringLocalizer = stringLocalizer;
_userManager = userManager; _userManager = userManager;
_defaultRules = defaultRules; _defaultRules = defaultRules;
_rateFactory = rateFactory; _rateFactory = rateFactory;
@@ -95,7 +101,7 @@ namespace BTCPayServer.Controllers
store.SetStoreBlob(blob); store.SetStoreBlob(blob);
await _repo.CreateStore(GetUserId(), store); await _repo.CreateStore(GetUserId(), store);
CreatedStoreId = store.Id; CreatedStoreId = store.Id;
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created"; TempData.SetStatusSuccess(StringLocalizer["Store successfully created"]);
return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new
{ {
storeId = store.Id storeId = store.Id
@@ -109,7 +115,7 @@ namespace BTCPayServer.Controllers
var store = HttpContext.GetStoreData(); var store = HttpContext.GetStoreData();
if (store == null) if (store == null)
return NotFound(); return NotFound();
return View("Confirm", new ConfirmModel($"Delete store {store.StoreName}", "This store will still be accessible to users sharing it", "Delete")); return View("Confirm", new ConfirmModel(StringLocalizer["Delete store {0}", store.StoreName], StringLocalizer["This store will still be accessible to users sharing it"], "Delete"));
} }
[HttpPost("{storeId}/me/delete")] [HttpPost("{storeId}/me/delete")]
@@ -121,24 +127,23 @@ namespace BTCPayServer.Controllers
if (store == null) if (store == null)
return NotFound(); return NotFound();
await _repo.RemoveStore(storeId, userId); await _repo.RemoveStore(storeId, userId);
TempData[WellKnownTempData.SuccessMessage] = "Store removed successfully"; TempData.SetStatusSuccess(StringLocalizer["Store removed successfully"]);
return RedirectToAction(nameof(UIHomeController.Index), "UIHome"); return RedirectToAction(nameof(UIHomeController.Index), "UIHome");
} }
private string GetUserId() => _userManager.GetUserId(User); private string GetUserId() => _userManager.GetUserId(User);
private SelectList GetExchangesSelectList(StoreBlob storeBlob) => GetExchangesSelectList(_rateFactory, _defaultRules, storeBlob); internal SelectList GetExchangesSelectList(StoreBlob storeBlob)
internal static SelectList GetExchangesSelectList(RateFetcher rateFetcher, DefaultRulesCollection defaultRules, StoreBlob storeBlob)
{ {
if (storeBlob is null) if (storeBlob is null)
storeBlob = new StoreBlob(); storeBlob = new StoreBlob();
var defaultExchange = defaultRules.GetRecommendedExchange(storeBlob.DefaultCurrency); var defaultExchange = _defaultRules.GetRecommendedExchange(storeBlob.DefaultCurrency);
var exchanges = rateFetcher.RateProviderFactory var exchanges = _rateFactory.RateProviderFactory
.AvailableRateProviders .AvailableRateProviders
.OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase) .OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase)
.ToList(); .ToList();
var exchange = exchanges.First(e => e.Id == defaultExchange); var exchange = exchanges.First(e => e.Id == defaultExchange);
exchanges.Insert(0, new(null, $"Recommendation ({exchange.DisplayName})", "")); exchanges.Insert(0, new(null, StringLocalizer["Recommendation ({0})", exchange.DisplayName], ""));
var chosen = exchanges.FirstOrDefault(f => f.Id == storeBlob.PreferredExchange) ?? exchanges.First(); var chosen = exchanges.FirstOrDefault(f => f.Id == storeBlob.PreferredExchange) ?? exchanges.First();
return new SelectList(exchanges, nameof(chosen.Id), nameof(chosen.DisplayName), chosen.Id); return new SelectList(exchanges, nameof(chosen.Id), nameof(chosen.DisplayName), chosen.Id);
} }

View File

@@ -29,6 +29,7 @@ using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payouts; using BTCPayServer.Payouts;
using BTCPayServer.Security; using BTCPayServer.Security;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Reporting; using BTCPayServer.Services.Reporting;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
@@ -291,7 +292,17 @@ namespace BTCPayServer
} }
} }
#nullable enable
public static IServiceCollection AddDefaultTransactions(this IServiceCollection services, params string[] keyValues)
{
return services.AddDefaultTransactions(keyValues.Select(k => KeyValuePair.Create<string, string?>(k, string.Empty)).ToArray());
}
public static IServiceCollection AddDefaultTransactions(this IServiceCollection services, params KeyValuePair<string, string?>[] keyValues)
{
services.AddSingleton<IDefaultTransactionProvider>(new InMemoryDefaultTransactionProvider(keyValues));
return services;
}
#nullable restore
public static IServiceCollection AddUIExtension(this IServiceCollection services, string location, string partialViewName) public static IServiceCollection AddUIExtension(this IServiceCollection services, string location, string partialViewName)
{ {
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete

View File

@@ -631,6 +631,7 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
(IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentLinkExtension), new object[] { network, pmi })); (IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentLinkExtension), new object[] { network, pmi }));
services.AddSingleton<ICheckoutModelExtension>(provider => services.AddSingleton<ICheckoutModelExtension>(provider =>
(BitcoinCheckoutModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinCheckoutModelExtension), new object[] { network, pmi })); (BitcoinCheckoutModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinCheckoutModelExtension), new object[] { network, pmi }));
services.AddDefaultTransactions(network.DisplayName);
services.AddSingleton<IPaymentMethodBitpayAPIExtension>(provider => services.AddSingleton<IPaymentMethodBitpayAPIExtension>(provider =>
(IPaymentMethodBitpayAPIExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentMethodBitpayAPIExtension), new object[] { pmi })); (IPaymentMethodBitpayAPIExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentMethodBitpayAPIExtension), new object[] { pmi }));

View File

@@ -7,6 +7,7 @@ using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using NBitcoin; using NBitcoin;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
@@ -17,6 +18,7 @@ namespace BTCPayServer.Payments.Bitcoin
public const string CheckoutBodyComponentName = "BitcoinCheckoutBody"; public const string CheckoutBodyComponentName = "BitcoinCheckoutBody";
private readonly PaymentMethodHandlerDictionary _handlers; private readonly PaymentMethodHandlerDictionary _handlers;
private readonly BTCPayNetwork _Network; private readonly BTCPayNetwork _Network;
private readonly IStringLocalizer StringLocalizer;
private readonly DisplayFormatter _displayFormatter; private readonly DisplayFormatter _displayFormatter;
private readonly IPaymentLinkExtension paymentLinkExtension; private readonly IPaymentLinkExtension paymentLinkExtension;
private readonly IPaymentLinkExtension? lnPaymentLinkExtension; private readonly IPaymentLinkExtension? lnPaymentLinkExtension;
@@ -26,6 +28,7 @@ namespace BTCPayServer.Payments.Bitcoin
public BitcoinCheckoutModelExtension( public BitcoinCheckoutModelExtension(
PaymentMethodId paymentMethodId, PaymentMethodId paymentMethodId,
BTCPayNetwork network, BTCPayNetwork network,
IStringLocalizer stringLocalizer,
IEnumerable<IPaymentLinkExtension> paymentLinkExtensions, IEnumerable<IPaymentLinkExtension> paymentLinkExtensions,
DisplayFormatter displayFormatter, DisplayFormatter displayFormatter,
PaymentMethodHandlerDictionary handlers) PaymentMethodHandlerDictionary handlers)
@@ -33,6 +36,7 @@ namespace BTCPayServer.Payments.Bitcoin
PaymentMethodId = paymentMethodId; PaymentMethodId = paymentMethodId;
_handlers = handlers; _handlers = handlers;
_Network = network; _Network = network;
StringLocalizer = stringLocalizer;
_displayFormatter = displayFormatter; _displayFormatter = displayFormatter;
paymentLinkExtension = paymentLinkExtensions.Single(p => p.PaymentMethodId == PaymentMethodId); paymentLinkExtension = paymentLinkExtensions.Single(p => p.PaymentMethodId == PaymentMethodId);
var lnPmi = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode); var lnPmi = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode);
@@ -41,7 +45,7 @@ namespace BTCPayServer.Payments.Bitcoin
lnurlPaymentLinkExtension = paymentLinkExtensions.SingleOrDefault(p => p.PaymentMethodId == lnurlPmi); lnurlPaymentLinkExtension = paymentLinkExtensions.SingleOrDefault(p => p.PaymentMethodId == lnurlPmi);
_bech32Prefix = network.NBitcoinNetwork.GetBech32Encoder(Bech32Type.WITNESS_PUBKEY_ADDRESS, false) is { } enc ? Encoders.ASCII.EncodeData(enc.HumanReadablePart) : null; _bech32Prefix = network.NBitcoinNetwork.GetBech32Encoder(Bech32Type.WITNESS_PUBKEY_ADDRESS, false) is { } enc ? Encoders.ASCII.EncodeData(enc.HumanReadablePart) : null;
} }
public string DisplayName => _Network.DisplayName; public string DisplayName => StringLocalizer[_Network.DisplayName];
public string Image => _Network.CryptoImagePath; public string Image => _Network.CryptoImagePath;
public string Badge => ""; public string Badge => "";
public PaymentMethodId PaymentMethodId { get; } public PaymentMethodId PaymentMethodId { get; }

View File

@@ -6,6 +6,7 @@ using BTCPayServer.Services;
using Org.BouncyCastle.Crypto.Modes.Gcm; using Org.BouncyCastle.Crypto.Modes.Gcm;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Localization;
namespace BTCPayServer.Payments.Lightning namespace BTCPayServer.Payments.Lightning
{ {
@@ -14,27 +15,31 @@ namespace BTCPayServer.Payments.Lightning
public const string CheckoutBodyComponentName = "LightningCheckoutBody"; public const string CheckoutBodyComponentName = "LightningCheckoutBody";
private readonly DisplayFormatter _displayFormatter; private readonly DisplayFormatter _displayFormatter;
IPaymentLinkExtension _PaymentLinkExtension; IPaymentLinkExtension _PaymentLinkExtension;
private readonly bool isBTC;
public LNCheckoutModelExtension( public LNCheckoutModelExtension(
PaymentMethodId paymentMethodId, PaymentMethodId paymentMethodId,
BTCPayNetwork network, BTCPayNetwork network,
DisplayFormatter displayFormatter, DisplayFormatter displayFormatter,
IEnumerable<IPaymentLinkExtension> paymentLinkExtensions, IEnumerable<IPaymentLinkExtension> paymentLinkExtensions,
IStringLocalizer stringLocalizer,
PaymentMethodHandlerDictionary handlers) PaymentMethodHandlerDictionary handlers)
{ {
Network = network; Network = network;
_displayFormatter = displayFormatter; _displayFormatter = displayFormatter;
StringLocalizer = stringLocalizer;
Handlers = handlers; Handlers = handlers;
PaymentMethodId = paymentMethodId; PaymentMethodId = paymentMethodId;
_PaymentLinkExtension = paymentLinkExtensions.Single(p => p.PaymentMethodId == PaymentMethodId); _PaymentLinkExtension = paymentLinkExtensions.Single(p => p.PaymentMethodId == PaymentMethodId);
var isBTC = PaymentTypes.LN.GetPaymentMethodId("BTC") == paymentMethodId; isBTC = PaymentTypes.LN.GetPaymentMethodId("BTC") == paymentMethodId;
DisplayName = isBTC ? "Lightning" : $"Lightning ({Network.DisplayName})";
} }
public BTCPayNetwork Network { get; } public BTCPayNetwork Network { get; }
public IStringLocalizer StringLocalizer { get; }
public PaymentMethodHandlerDictionary Handlers { get; } public PaymentMethodHandlerDictionary Handlers { get; }
public PaymentMethodId PaymentMethodId { get; } public PaymentMethodId PaymentMethodId { get; }
public string DisplayName { get; } public string DisplayName => isBTC ? StringLocalizer["Lightning"] : StringLocalizer["Lightning ({0})", Network.DisplayName];
public string Image => Network.LightningImagePath; public string Image => Network.LightningImagePath;
public string Badge => "⚡"; public string Badge => "⚡";
@@ -50,7 +55,7 @@ namespace BTCPayServer.Payments.Lightning
if (context.Model.InvoiceBitcoinUrl is not null) if (context.Model.InvoiceBitcoinUrl is not null)
context.Model.InvoiceBitcoinUrlQR = $"lightning:{context.Model.InvoiceBitcoinUrl.ToUpperInvariant()?.Substring("LIGHTNING:".Length)}"; context.Model.InvoiceBitcoinUrlQR = $"lightning:{context.Model.InvoiceBitcoinUrl.ToUpperInvariant()?.Substring("LIGHTNING:".Length)}";
context.Model.PeerInfo = handler.ParsePaymentPromptDetails(paymentPrompt.Details).NodeInfo; context.Model.PeerInfo = handler.ParsePaymentPromptDetails(paymentPrompt.Details).NodeInfo;
if (context.StoreBlob.LightningAmountInSatoshi && context.Model.PaymentMethodCurrency == "BTC") if (context.StoreBlob.LightningAmountInSatoshi && isBTC)
{ {
BitcoinCheckoutModelExtension.PreparePaymentModelForAmountInSats(context.Model, paymentPrompt.Rate, _displayFormatter); BitcoinCheckoutModelExtension.PreparePaymentModelForAmountInSats(context.Model, paymentPrompt.Rate, _displayFormatter);
} }

View File

@@ -19,16 +19,35 @@ using static BTCPayServer.Services.LocalizerService;
namespace BTCPayServer.Services namespace BTCPayServer.Services
{ {
public interface IDefaultTransactionProvider
{
Task<KeyValuePair<string, string?>[]> GetDefaultTransaction();
}
public class InMemoryDefaultTransactionProvider : IDefaultTransactionProvider
{
private readonly KeyValuePair<string, string?>[] _values;
public InMemoryDefaultTransactionProvider(KeyValuePair<string, string?>[] values)
{
_values = values;
}
public Task<KeyValuePair<string, string?>[]> GetDefaultTransaction()
{
return Task.FromResult(_values);
}
}
public class LocalizerService public class LocalizerService
{ {
public LocalizerService( public LocalizerService(
ILogger<LocalizerService> logger, ILogger<LocalizerService> logger,
ApplicationDbContextFactory contextFactory, ApplicationDbContextFactory contextFactory,
ISettingsAccessor<PoliciesSettings> settingsAccessor) ISettingsAccessor<PoliciesSettings> settingsAccessor,
IEnumerable<IDefaultTransactionProvider> defaultTransactionProviders)
{ {
_logger = logger; _logger = logger;
_ContextFactory = contextFactory; _ContextFactory = contextFactory;
_settingsAccessor = settingsAccessor; _settingsAccessor = settingsAccessor;
_defaultTransactionProviders = defaultTransactionProviders;
_LoadedTranslations = new LoadedTranslations(Translations.Default, Translations.Default, Translations.DefaultLanguage); _LoadedTranslations = new LoadedTranslations(Translations.Default, Translations.Default, Translations.DefaultLanguage);
} }
@@ -39,6 +58,7 @@ namespace BTCPayServer.Services
private readonly ILogger<LocalizerService> _logger; private readonly ILogger<LocalizerService> _logger;
private readonly ApplicationDbContextFactory _ContextFactory; private readonly ApplicationDbContextFactory _ContextFactory;
private readonly ISettingsAccessor<PoliciesSettings> _settingsAccessor; private readonly ISettingsAccessor<PoliciesSettings> _settingsAccessor;
private readonly IEnumerable<IDefaultTransactionProvider> _defaultTransactionProviders;
/// <summary> /// <summary>
/// Load the translation of the server into memory /// Load the translation of the server into memory
@@ -69,7 +89,18 @@ namespace BTCPayServer.Services
{ {
dict_id = dictionaryName, dict_id = dictionaryName,
}); });
var fallback = new Translations(all.Where(a => a.fallback).Select(o => KeyValuePair.Create(o.sentence, o.translation)), Translations.Default); var defaultDict = Translations.Default;
var loading = _defaultTransactionProviders.Select(d => d.GetDefaultTransaction()).ToArray();
Dictionary<string, string?> additionalDefault = new();
foreach (var defaultProvider in loading)
{
foreach (var kv in await defaultProvider)
{
additionalDefault.TryAdd(kv.Key, string.IsNullOrEmpty(kv.Value) ? kv.Key : kv.Value);
}
}
defaultDict = new Translations(additionalDefault, defaultDict);
var fallback = new Translations(all.Where(a => a.fallback).Select(o => KeyValuePair.Create(o.sentence, o.translation)), defaultDict);
var translations = new Translations(all.Where(a => !a.fallback).Select(o => KeyValuePair.Create(o.sentence, o.translation)), fallback); var translations = new Translations(all.Where(a => !a.fallback).Select(o => KeyValuePair.Create(o.sentence, o.translation)), fallback);
return new LoadedTranslations(translations, fallback, dictionaryName); return new LoadedTranslations(translations, fallback, dictionaryName);
} }

View File

@@ -14,7 +14,17 @@ namespace BTCPayServer.Services
{ {
"... on every payment": "", "... on every payment": "",
"... only if the customer makes more than one payment for the invoice": "", "... only if the customer makes more than one payment for the invoice": "",
"<span class=\"currency\">{0}</span> closing channels": "",
"<span class=\"currency\">{0}</span> confirmed": "",
"<span class=\"currency\">{0}</span> in channels": "",
"<span class=\"currency\">{0}</span> local balance": "",
"<span class=\"currency\">{0}</span> on-chain": "",
"<span class=\"currency\">{0}</span> opening channels": "",
"<span class=\"currency\">{0}</span> remote balance": "",
"<span class=\"currency\">{0}</span> reserved": "",
"<span class=\"currency\">{0}</span> unconfirmed": "",
"A given currency pair match the most specific rule. If two rules are matching and are as specific, the first rule will be chosen.": "", "A given currency pair match the most specific rule. If two rules are matching and are as specific, the first rule will be chosen.": "",
"A self-hosted, open-source bitcoin payment processor.": "",
"Access Tokens": "", "Access Tokens": "",
"Account": "", "Account": "",
"Account key": "", "Account key": "",
@@ -48,6 +58,7 @@ namespace BTCPayServer.Services
"Application": "", "Application": "",
"Apply the brand color to the store's backend as well": "", "Apply the brand color to the store's backend as well": "",
"Approve": "", "Approve": "",
"Archive pull payment": "",
"Archive this store": "", "Archive this store": "",
"At Least One": "", "At Least One": "",
"At Least Ten": "", "At Least Ten": "",
@@ -62,6 +73,7 @@ namespace BTCPayServer.Services
"blocks": "", "blocks": "",
"Brand Color": "", "Brand Color": "",
"Branding": "", "Branding": "",
"BTCPay Server currently supports:": "",
"But now, what if you want to support <code>DOGE</code>? The problem with <code>DOGE</code> is that most exchange do not have any pair for it. But <code>bitpay</code> has a <code>DOGE_BTC</code> pair. <br />\r\n Luckily, the rule engine allow you to reference rules:": "", "But now, what if you want to support <code>DOGE</code>? The problem with <code>DOGE</code> is that most exchange do not have any pair for it. But <code>bitpay</code> has a <code>DOGE_BTC</code> pair. <br />\r\n Luckily, the rule engine allow you to reference rules:": "",
"Buyer Email": "", "Buyer Email": "",
"Callback Notification URL": "", "Callback Notification URL": "",
@@ -79,6 +91,8 @@ namespace BTCPayServer.Services
"Confirm password": "", "Confirm password": "",
"Connect an existing wallet": "", "Connect an existing wallet": "",
"Connect hardware&nbsp;wallet": "", "Connect hardware&nbsp;wallet": "",
"Connect to a Lightning node": "",
"Connection configuration for your custom Lightning node:": "",
"Connection string": "", "Connection string": "",
"Consider the invoice paid even if the paid amount is … % less than expected": "", "Consider the invoice paid even if the paid amount is … % less than expected": "",
"Consider the invoice settled when the payment transaction …": "", "Consider the invoice settled when the payment transaction …": "",
@@ -89,6 +103,7 @@ namespace BTCPayServer.Services
"Create": "", "Create": "",
"Create a new app": "", "Create a new app": "",
"Create a new wallet": "", "Create a new wallet": "",
"Create account": "",
"Create Account": "", "Create Account": "",
"Create Form": "", "Create Form": "",
"Create Invoice": "", "Create Invoice": "",
@@ -97,6 +112,7 @@ namespace BTCPayServer.Services
"Create Store": "", "Create Store": "",
"Create Webhook": "", "Create Webhook": "",
"Create your account": "", "Create your account": "",
"Create your store": "",
"Crowdfund": "", "Crowdfund": "",
"Currency": "", "Currency": "",
"Current password": "", "Current password": "",
@@ -107,18 +123,21 @@ namespace BTCPayServer.Services
"Custom Theme Extension Type": "", "Custom Theme Extension Type": "",
"Custom Theme File": "", "Custom Theme File": "",
"Dashboard": "", "Dashboard": "",
"Date": "",
"days": "", "days": "",
"Default currency": "", "Default currency": "",
"Default Currency Pairs": "", "Default Currency Pairs": "",
"Default language on checkout": "", "Default language on checkout": "",
"Default payment method on checkout": "", "Default payment method on checkout": "",
"Default role for users on a new store": "", "Default role for users on a new store": "",
"Delete store {0}": "",
"Delete this store": "", "Delete this store": "",
"Derivation scheme": "", "Derivation scheme": "",
"Derivation scheme format": "", "Derivation scheme format": "",
"Description": "", "Description": "",
"Description template of the lightning invoice": "", "Description template of the lightning invoice": "",
"Destination Address": "", "Destination Address": "",
"Details": "",
"Dictionaries": "", "Dictionaries": "",
"Dictionaries enable you to translate the BTCPay Server backend into different languages.": "", "Dictionaries enable you to translate the BTCPay Server backend into different languages.": "",
"Dictionary": "", "Dictionary": "",
@@ -136,10 +155,14 @@ namespace BTCPayServer.Services
"Display Title": "", "Display Title": "",
"Disqus Shortname": "", "Disqus Shortname": "",
"Do not allow additional contributions after target has been reached": "", "Do not allow additional contributions after target has been reached": "",
"Do not photograph it. Do not store it digitally.": "",
"Do not photograph the recovery phrase, and do not store it digitally.": "",
"Do you really want to archive the pull payment?": "",
"Does not extend a BTCPay Server theme, fully custom": "", "Does not extend a BTCPay Server theme, fully custom": "",
"Domain": "", "Domain": "",
"Domain name": "", "Domain name": "",
"Don't create UTXO change": "", "Don't create UTXO change": "",
"Done": "",
"Email": "", "Email": "",
"Email address": "", "Email address": "",
"Email confirmation required": "", "Email confirmation required": "",
@@ -188,27 +211,40 @@ namespace BTCPayServer.Services
"However, explicitely setting specific pairs like this can be a bit difficult. Instead, you can define a rule <code>X_X</code> which will match any currency pair. The following example will use <code>kraken</code> for getting the rate of any currency pair.": "", "However, explicitely setting specific pairs like this can be a bit difficult. Instead, you can define a rule <code>X_X</code> which will match any currency pair. The following example will use <code>kraken</code> for getting the rate of any currency pair.": "",
"I don't have a wallet": "", "I don't have a wallet": "",
"I have a wallet": "", "I have a wallet": "",
"I have written down my recovery phrase and stored it in a secure location": "",
"If a translation isnt available in the new dictionary, it will be searched in the fallback.": "", "If a translation isnt available in the new dictionary, it will be searched in the fallback.": "",
"If you lose it or write it down incorrectly, you may permanently lose access to your funds.": "",
"If you lose it or write it down incorrectly, you will permanently lose access to your funds.": "",
"Image": "", "Image": "",
"Import {0} Wallet": "", "Import {0} Wallet": "",
"Import an existing hardware or software wallet": "", "Import an existing hardware or software wallet": "",
"Import wallet file": "", "Import wallet file": "",
"Import your public keys using our Vault application": "", "Import your public keys using our Vault application": "",
"Input the key string manually": "", "Input the key string manually": "",
"Invalid currency": "",
"Invitation URL": "", "Invitation URL": "",
"Invoice currency": "", "Invoice currency": "",
"Invoice expires if the full amount has not been paid after …": "", "Invoice expires if the full amount has not been paid after …": "",
"Invoice Id": "",
"Invoice metadata": "", "Invoice metadata": "",
"Invoices": "", "Invoices": "",
"Is administrator?": "", "Is administrator?": "",
"Is signing key": "", "Is signing key": "",
"Is unconfirmed": "", "Is unconfirmed": "",
"It is secure, private, censorship-resistant and free.": "",
"It is worth noting that the inverses of those pairs are automatically supported as well.<br />\r\n It means that the rule <code>USD_DOGE = 1 / DOGE_USD</code> implicitely exists.": "", "It is worth noting that the inverses of those pairs are automatically supported as well.<br />\r\n It means that the rule <code>USD_DOGE = 1 / DOGE_USD</code> implicitely exists.": "",
"Item Description": "", "Item Description": "",
"Keypad": "", "Keypad": "",
"Labels": "",
"Let's get started": "", "Let's get started": "",
"Lightning": "",
"Lightning ({0})": "",
"Lightning Address": "",
"Lightning Balance": "",
"Lightning node (LNURL Auth)": "", "Lightning node (LNURL Auth)": "",
"Lightning Services": "",
"LNURL Classic Mode": "", "LNURL Classic Mode": "",
"Loading...": "",
"Local File System": "", "Local File System": "",
"Log in": "", "Log in": "",
"Login Codes": "", "Login Codes": "",
@@ -217,6 +253,7 @@ namespace BTCPayServer.Services
"Logs": "", "Logs": "",
"Maintenance": "", "Maintenance": "",
"Make Crowdfund Public": "", "Make Crowdfund Public": "",
"Manage": "",
"Manage Account": "", "Manage Account": "",
"Manage Plugins": "", "Manage Plugins": "",
"Master fingerprint": "", "Master fingerprint": "",
@@ -228,11 +265,15 @@ namespace BTCPayServer.Services
"Never add network fee": "", "Never add network fee": "",
"New password": "", "New password": "",
"Next": "", "Next": "",
"No payout selected": "",
"No scope": "",
"Node Info": "",
"Non-admins can access the User Creation API Endpoint": "", "Non-admins can access the User Creation API Endpoint": "",
"Non-admins can create Hot Wallets for their Store": "", "Non-admins can create Hot Wallets for their Store": "",
"Non-admins can import Hot Wallets for their Store": "", "Non-admins can import Hot Wallets for their Store": "",
"Non-admins can use the Internal Lightning Node for their Store": "", "Non-admins can use the Internal Lightning Node for their Store": "",
"Non-admins cannot access the User Creation API Endpoint": "", "Non-admins cannot access the User Creation API Endpoint": "",
"Not all payout methods are supported": "",
"Not recommended": "", "Not recommended": "",
"Notification Email": "", "Notification Email": "",
"Notification URL": "", "Notification URL": "",
@@ -241,18 +282,27 @@ namespace BTCPayServer.Services
"Optional seed passphrase": "", "Optional seed passphrase": "",
"Order Id": "", "Order Id": "",
"Override the block explorers used": "", "Override the block explorers used": "",
"Paid invoices in the last {0} days": "",
"Pair to": "", "Pair to": "",
"Password": "", "Password": "",
"Password (leave blank to generate invite-link)": "", "Password (leave blank to generate invite-link)": "",
"Pay Button": "", "Pay Button": "",
"Paying via this payment method is not supported": "",
"PayJoin BIP21": "", "PayJoin BIP21": "",
"Payment": "", "Payment": "",
"Payment invalid if transactions fails to confirm … after invoice expiration": "", "Payment invalid if transactions fails to confirm … after invoice expiration": "",
"Payment Requests": "",
"Payments": "", "Payments": "",
"Payout Methods": "", "Payout Methods": "",
"Payout Processors": "", "Payout Processors": "",
"Payouts": "", "Payouts": "",
"Payouts approved": "",
"Payouts archived": "",
"Payouts marked as paid": "",
"Payouts Pending": "",
"Permissions": "",
"Please enable JavaScript for this option to be available": "", "Please enable JavaScript for this option to be available": "",
"Please make sure to also write down your passphrase.": "",
"Please note that creating a hot wallet is not supported by this instance for non administrators.": "", "Please note that creating a hot wallet is not supported by this instance for non administrators.": "",
"Plugin server": "", "Plugin server": "",
"Plugins": "", "Plugins": "",
@@ -261,6 +311,7 @@ namespace BTCPayServer.Services
"Policies": "", "Policies": "",
"Preferred Price Source": "", "Preferred Price Source": "",
"Print display": "", "Print display": "",
"Process approved payouts instantly": "",
"Product list": "", "Product list": "",
"Product list with cart": "", "Product list with cart": "",
"Profile Picture": "", "Profile Picture": "",
@@ -268,15 +319,22 @@ namespace BTCPayServer.Services
"PSBT content": "", "PSBT content": "",
"PSBT to combine with…": "", "PSBT to combine with…": "",
"Public Key": "", "Public Key": "",
"Pull payment request created": "",
"Pull Payments": "", "Pull Payments": "",
"Rate Rules": "", "Rate Rules": "",
"Rate script allows you to express precisely how you want to calculate rates for currency pairs.": "", "Rate script allows you to express precisely how you want to calculate rates for currency pairs.": "",
"Rate unavailable: {0}": "",
"Rates": "", "Rates": "",
"Receive": "",
"Recent Invoices": "",
"Recent Transactions": "",
"Recommendation ({0})": "",
"Recommended": "", "Recommended": "",
"Recommended fee confirmation target blocks": "", "Recommended fee confirmation target blocks": "",
"Recovery Code": "", "Recovery Code": "",
"Redirect invoice to redirect url automatically after paid": "", "Redirect invoice to redirect url automatically after paid": "",
"Redirect URL": "", "Redirect URL": "",
"Refunds Issued": "",
"Register": "", "Register": "",
"Remember me": "", "Remember me": "",
"Remember this machine": "", "Remember this machine": "",
@@ -298,10 +356,12 @@ namespace BTCPayServer.Services
"Scope": "", "Scope": "",
"Scripting": "", "Scripting": "",
"Search engines can index this site": "", "Search engines can index this site": "",
"Secure your recovery phrase": "",
"Security device (FIDO2)": "", "Security device (FIDO2)": "",
"Select": "", "Select": "",
"Select the Default Currency during Store Creation": "", "Select the Default Currency during Store Creation": "",
"Select the payout method used for refund": "", "Select the payout method used for refund": "",
"Send": "",
"Send invitation email": "", "Send invitation email": "",
"Send test webhook": "", "Send test webhook": "",
"Server Name": "", "Server Name": "",
@@ -309,6 +369,8 @@ namespace BTCPayServer.Services
"Services": "", "Services": "",
"Set Password": "", "Set Password": "",
"Set to default settings": "", "Set to default settings": "",
"Set up a Lightning node": "",
"Set up a wallet": "",
"Settings": "", "Settings": "",
"Setup {0} Wallet": "", "Setup {0} Wallet": "",
"Shop Name": "", "Shop Name": "",
@@ -326,28 +388,46 @@ namespace BTCPayServer.Services
"Specify the amount and currency for the refund": "", "Specify the amount and currency for the refund": "",
"Start date": "", "Start date": "",
"Starting index": "", "Starting index": "",
"Status": "",
"Store": "", "Store": "",
"Store Id": "", "Store Id": "",
"Store Name": "", "Store Name": "",
"Store removed successfully": "",
"Store Settings": "", "Store Settings": "",
"Store Speed Policy": "", "Store Speed Policy": "",
"Store successfully created": "",
"Store Website": "", "Store Website": "",
"Store: {0}": "",
"Submit": "", "Submit": "",
"Subtract fees from amount": "", "Subtract fees from amount": "",
"Support URL": "", "Support URL": "",
"Supported by BlueWallet, Cobo Vault, Passport and Specter DIY": "", "Supported by BlueWallet, Cobo Vault, Passport and Specter DIY": "",
"Supported Transaction Currencies": "", "Supported Transaction Currencies": "",
"Target Amount": "", "Target Amount": "",
"Test connection": "",
"Test Email": "", "Test Email": "",
"Test Results:": "", "Test Results:": "",
"Testing": "", "Testing": "",
"Text to display in the tip input": "", "Text to display in the tip input": "",
"Text to display on buttons allowing the user to enter a custom amount": "", "Text to display on buttons allowing the user to enter a custom amount": "",
"Text to display on each button for items with a specific price": "", "Text to display on each button for items with a specific price": "",
"The amount should be more than zero": "",
"The combination of words below are called your recovery phrase.\r\n The recovery phrase allows you to access and restore your wallet.\r\n Write them down on a piece of paper in the exact order:": "",
"The following methods assume that you already have an existing&nbsp;wallet created and backed up.": "", "The following methods assume that you already have an existing&nbsp;wallet created and backed up.": "",
"The name should be maximum 50 characters.": "",
"The recommended price source gets chosen based on the default currency.": "",
"The recovery phrase is a backup that allows you to restore your wallet in case of a server crash.": "",
"The recovery phrase will also be stored on the server as a hot wallet.": "",
"The recovery phrase will be permanently erased from the server.": "",
"The script language is composed of several rules composed of a currency pair and a mathematic expression.\r\n The example below will use <code>kraken</code> for both <code>LTC_USD</code> and <code>BTC_USD</code> pairs.": "", "The script language is composed of several rules composed of a currency pair and a mathematic expression.\r\n The example below will use <code>kraken</code> for both <code>LTC_USD</code> and <code>BTC_USD</code> pairs.": "",
"Theme": "", "Theme": "",
"There are no recent invoices.": "",
"There are no recent transactions.": "",
"This store is ready to accept transactions, good job!": "",
"This store will still be accessible to users sharing it": "",
"Tip percentage amounts (comma separated)": "", "Tip percentage amounts (comma separated)": "",
"To start accepting payments, set up a wallet or a Lightning node.": "",
"Transaction": "",
"Translations": "", "Translations": "",
"Two-Factor Authentication": "", "Two-Factor Authentication": "",
"Unarchive this store": "", "Unarchive this store": "",
@@ -357,14 +437,19 @@ namespace BTCPayServer.Services
"Upload a file exported from your wallet": "", "Upload a file exported from your wallet": "",
"Upload PSBT from file…": "", "Upload PSBT from file…": "",
"Url of the Dynamic DNS service you are using": "", "Url of the Dynamic DNS service you are using": "",
"Use custom node": "",
"Use custom theme": "", "Use custom theme": "",
"Use internal node": "",
"Use SSL": "", "Use SSL": "",
"User can input custom amount": "", "User can input custom amount": "",
"User can input discount in %": "", "User can input discount in %": "",
"Users": "", "Users": "",
"Using the BTCPay Server internal node for this store requires no further configuration. Click the save button below to start accepting Bitcoin through the Lightning Network.": "",
"UTXOs to spend from": "", "UTXOs to spend from": "",
"Verification Code": "", "Verification Code": "",
"View All": "",
"View-Only Wallet File": "", "View-Only Wallet File": "",
"Wallet Balance": "",
"Wallet file": "", "Wallet file": "",
"Wallet file content": "", "Wallet file content": "",
"Wallet Keys File": "", "Wallet Keys File": "",
@@ -376,8 +461,12 @@ namespace BTCPayServer.Services
"Webhooks": "", "Webhooks": "",
"Welcome to {0}": "", "Welcome to {0}": "",
"With <code>DOGE_USD</code> will be expanded to <code>bitpay(DOGE_BTC) * kraken(BTC_USD)</code>. And <code>DOGE_CAD</code> will be expanded to <code>bitpay(DOGE_BTC) * ndax(BTC_CAD)</code>. <br />\r\n However, we advise you to write it that way to increase coverage so that <code>DOGE_BTC</code> is also supported:": "", "With <code>DOGE_USD</code> will be expanded to <code>bitpay(DOGE_BTC) * kraken(BTC_USD)</code>. And <code>DOGE_CAD</code> will be expanded to <code>bitpay(DOGE_BTC) * ndax(BTC_CAD)</code>. <br />\r\n However, we advise you to write it that way to increase coverage so that <code>DOGE_BTC</code> is also supported:": "",
"You must enable at least one payment method before creating a payout.": "",
"You must enable at least one payment method before creating a pull payment.": "",
"You need at least one payout method": "",
"You really should not type your seed into a device that is connected to the internet.": "", "You really should not type your seed into a device that is connected to the internet.": "",
"Your dynamic DNS hostname": "", "Your dynamic DNS hostname": "",
"Your instance administrator has disabled the use of the Internal node for non-admin users.": "",
"Zero Confirmation": "" "Zero Confirmation": ""
} }
"""; """;

View File

@@ -1,6 +1,6 @@
@model ErrorViewModel @model ErrorViewModel
@{ @{
ViewData["Title"] = "Error"; ViewData["Title"] = ViewLocalizer["Error"];
} }
<h1 class="text-danger"> <h1 class="text-danger">

View File

@@ -10,7 +10,7 @@
<li class="nav-item" permission="@Policies.CanModifyStoreSettings"> <li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UILNURL" asp-action="EditLightningAddress" asp-route-storeId="@store.Id" class="nav-link @ViewData.ActivePageClass("LightningAddress", nameof(StoreNavPages))" id="StoreNav-LightningAddress"> <a asp-area="" asp-controller="UILNURL" asp-action="EditLightningAddress" asp-route-storeId="@store.Id" class="nav-link @ViewData.ActivePageClass("LightningAddress", nameof(StoreNavPages))" id="StoreNav-LightningAddress">
<vc:icon symbol="nav-lightning-address "/> <vc:icon symbol="nav-lightning-address "/>
<span>Lightning Address</span> <span text-translate="true">Lightning Address</span>
</a> </a>
</li> </li>
} }

View File

@@ -53,8 +53,8 @@
@if (ViewBag.ShowLeadText) @if (ViewBag.ShowLeadText)
{ {
<p class="lead"> <p class="lead">
<span class="d-sm-block">A self-hosted, open-source bitcoin payment processor.</span> <span class="d-sm-block" text-translate="true">A self-hosted, open-source bitcoin payment processor.</span>
<span class="d-sm-block">It is secure, private, censorship-resistant and free.</span> <span class="d-sm-block" text-translate="true">It is secure, private, censorship-resistant and free.</span>
</p> </p>
} }

View File

@@ -1,17 +1,17 @@
@model CheatPermissionsViewModel @model CheatPermissionsViewModel
@{ @{
ViewData["Title"] = "Permissions"; ViewData["Title"] = ViewLocalizer["Permissions"];
Layout = "_LayoutSignedOut"; Layout = "_LayoutSignedOut";
} }
@if (Model.StoreId is not null) @if (Model.StoreId is not null)
{ {
<h1>Store: @Model.StoreId</h1> <h1 text-translate="true">@ViewLocalizer["Store: {0}", @Model.StoreId]</h1>
} }
else else
{ {
<h1>No scope</h1> <h1 text-translate="true">No scope</h1>
} }
<ul> <ul>

View File

@@ -1,7 +1,7 @@
@model RegisterViewModel @model RegisterViewModel
@inject BTCPayServer.Services.BTCPayServerEnvironment env @inject BTCPayServer.Services.BTCPayServerEnvironment env
@{ @{
ViewData["Title"] = "Create account"; ViewData["Title"] = ViewLocalizer["Create account"];
ViewBag.ShowLeadText = true; ViewBag.ShowLeadText = true;
Layout = "_LayoutSignedOut"; Layout = "_LayoutSignedOut";
} }
@@ -36,7 +36,7 @@
</div> </div>
} }
<div class="form-group mt-4"> <div class="form-group mt-4">
<button type="submit" class="btn btn-primary btn-lg w-100" id="RegisterButton">Create account</button> <button type="submit" class="btn btn-primary btn-lg w-100" id="RegisterButton" text-translate="true">Create account</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>

View File

@@ -30,13 +30,13 @@
<vc:icon symbol="warning" /> <vc:icon symbol="warning" />
</div> </div>
<div class="lead text-center"> <div class="lead text-center">
<h1 class="text-center text-warning mb-3"> <h1 class="text-center text-warning mb-3" text-translate="true">
Secure your recovery&nbsp;phrase Secure your recovery phrase
</h1> </h1>
</div> </div>
</div> </div>
<div class="lead text-center"> <div class="lead text-center">
<p class="mb-0"> <p class="mb-0" text-translate="true">
The combination of words below are called your recovery phrase. The combination of words below are called your recovery phrase.
The recovery phrase allows you to access and restore your wallet. The recovery phrase allows you to access and restore your wallet.
Write them down on a piece of paper in the exact order: Write them down on a piece of paper in the exact order:
@@ -54,41 +54,41 @@
@if (Model.IsStored) @if (Model.IsStored)
{ {
<p> <p>
<span>The recovery phrase is a backup that allows you to restore your wallet in case of a server crash.</span> <span text-translate="true">The recovery phrase is a backup that allows you to restore your wallet in case of a server crash.</span>
<span>If you lose it or write it down incorrectly, you may permanently lose access to your funds.</span> <span text-translate="true">If you lose it or write it down incorrectly, you may permanently lose access to your funds.</span>
<span>Do not photograph it. Do not store it digitally.</span> <span text-translate="true">Do not photograph it. Do not store it digitally.</span>
</p> </p>
<p class="text-warning"> <p class="text-warning">
<strong>The recovery phrase will also be stored on the server as a hot wallet.</strong> <strong text-translate="true">The recovery phrase will also be stored on the server as a hot wallet.</strong>
</p> </p>
} }
else else
{ {
<p> <p>
<span>If you lose it or write it down incorrectly, you will permanently lose access to your funds.</span> <span text-translate="true">If you lose it or write it down incorrectly, you will permanently lose access to your funds.</span>
<span>Do not photograph the recovery phrase, and do not store it digitally.</span> <span text-translate="true">Do not photograph the recovery phrase, and do not store it digitally.</span>
</p> </p>
<br /> <br />
<p class="text-warning"> <p class="text-warning">
<strong>The recovery phrase will be permanently erased from the server.</strong> <strong text-translate="true">The recovery phrase will be permanently erased from the server.</strong>
</p> </p>
} }
@if (!string.IsNullOrEmpty(Model.Passphrase)) @if (!string.IsNullOrEmpty(Model.Passphrase))
{ {
<p class="mt-3 mb-0">Please make sure to also write down your passphrase.</p> <p class="mt-3 mb-0" text-translate="true">Please make sure to also write down your passphrase.</p>
} }
</div> </div>
@if (Model.RequireConfirm) @if (Model.RequireConfirm)
{ {
<form id="RecoveryConfirmation" action="@Url.EnsureLocal(Model.ReturnUrl, Context.Request)" class="position-relative d-flex align-items-start justify-content-center" style="padding:20px 0 100px" rel="noreferrer noopener"> <form id="RecoveryConfirmation" action="@Url.EnsureLocal(Model.ReturnUrl, Context.Request)" class="position-relative d-flex align-items-start justify-content-center" style="padding:20px 0 100px" rel="noreferrer noopener">
<label class="form-check-label lead order-2" for="confirm">I have written down my recovery phrase and stored it in a secure location</label> <label class="form-check-label lead order-2" for="confirm" text-translate="true">I have written down my recovery phrase and stored it in a secure location</label>
<input type="checkbox" class="me-3 order-1 form-check-input" id="confirm" style="margin-top:.35rem;flex-shrink:0"> <input type="checkbox" class="me-3 order-1 form-check-input" id="confirm" style="margin-top:.35rem;flex-shrink:0">
<button type="submit" class="btn btn-primary btn-lg px-5 order-3" id="submit">Done</button> <button type="submit" class="btn btn-primary btn-lg px-5 order-3" id="submit" text-translate="true">Done</button>
<button type="submit" class="btn btn-primary btn-lg px-5 order-3" disabled>Done</button> <button type="submit" class="btn btn-primary btn-lg px-5 order-3" disabled text-translate="true">Done</button>
</form> </form>
} }
else else
{ {
<a href="@Url.EnsureLocal(Model.ReturnUrl, Context.Request)" class="btn btn-primary btn-lg mt-3 px-5 order-3" id="proceed" rel="noreferrer noopener">Done</a> <a href="@Url.EnsureLocal(Model.ReturnUrl, Context.Request)" class="btn btn-primary btn-lg mt-3 px-5 order-3" id="proceed" rel="noreferrer noopener" text-translate="true">Done</a>
} }
</main> </main>

View File

@@ -1,6 +1,6 @@
@model BTCPayServer.Models.NotificationViewModels.NotificationIndexViewModel @model BTCPayServer.Models.NotificationViewModels.NotificationIndexViewModel
@{ @{
ViewData["Title"] = "Notifications"; ViewData["Title"] = ViewLocalizer["Notifications"];
string status = ViewBag.Status; string status = ViewBag.Status;
var statusFilterCount = CountArrayFilter("type"); var statusFilterCount = CountArrayFilter("type");
var storesFilterCount = CountArrayFilter("storeid"); var storesFilterCount = CountArrayFilter("storeid");

View File

@@ -6,7 +6,7 @@
@model BTCPayServer.Models.PaymentRequestViewModels.ListPaymentRequestsViewModel @model BTCPayServer.Models.PaymentRequestViewModels.ListPaymentRequestsViewModel
@{ @{
Layout = "_Layout"; Layout = "_Layout";
ViewData["Title"] = "Payment Requests"; ViewData["Title"] = ViewLocalizer["Payment Requests"];
var storeId = Context.GetStoreData().Id; var storeId = Context.GetStoreData().Id;
var statusFilterCount = CountArrayFilter("status") + (HasBooleanFilter("includearchived") ? 1 : 0); var statusFilterCount = CountArrayFilter("status") + (HasBooleanFilter("includearchived") ? 1 : 0);
} }

View File

@@ -9,8 +9,8 @@
@using BTCPayServer.Client @using BTCPayServer.Client
@model StoreDashboardViewModel @model StoreDashboardViewModel
@{ @{
BTCPayServer.Plugins.PluginExceptionHandler.SetDisablePluginIfCrash(Context); BTCPayServer.Plugins.PluginExceptionHandler.SetDisablePluginIfCrash(Context);
ViewData.SetActivePage(StoreNavPages.Dashboard, Model.StoreName, Model.StoreId); ViewData.SetActivePage(StoreNavPages.Dashboard, Model.StoreName, Model.StoreId);
var store = ViewContext.HttpContext.GetStoreData(); var store = ViewContext.HttpContext.GetStoreData();
} }
@@ -50,61 +50,61 @@
}; };
</script> </script>
<div id="Dashboard" class="mt-4"> <div id="Dashboard" class="mt-4">
<vc:ui-extension-point location="dashboard" model="@Model"/> <vc:ui-extension-point location="dashboard" model="@Model" />
@if (Model.WalletEnabled) @if (Model.WalletEnabled)
{ {
<vc:store-wallet-balance store="@store"/> <vc:store-wallet-balance store="@store" />
} }
else else
{ {
<div class="widget setup-guide"> <div class="widget setup-guide">
<header> <header>
<h5 class="mb-4 text-muted">This store is ready to accept transactions, good job!</h5> <h5 class="mb-4 text-muted" text-translate="true">This store is ready to accept transactions, good job!</h5>
</header> </header>
<div class="list-group" id="SetupGuide"> <div class="list-group" id="SetupGuide">
<div class="list-group-item d-flex align-items-center" id="SetupGuide-LightningDone"> <div class="list-group-item d-flex align-items-center" id="SetupGuide-LightningDone">
<vc:icon symbol="done"/> <vc:icon symbol="done" />
<div class="content"> <div class="content">
<h5 class="mb-0 text-success">Set up a Lightning node</h5> <h5 class="mb-0 text-success" text-translate="true">Set up a Lightning node</h5>
</div> </div>
</div> </div>
<a asp-controller="UIStores" asp-action="SetupWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center"> <a asp-controller="UIStores" asp-action="SetupWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center">
<vc:icon symbol="wallet-new"/> <vc:icon symbol="wallet-new" />
<div class="content"> <div class="content">
<h5 class="mb-0">Set up a wallet</h5> <h5 class="mb-0" text-translate="true">Set up a wallet</h5>
</div> </div>
<vc:icon symbol="caret-right"/> <vc:icon symbol="caret-right" />
</a> </a>
</div> </div>
</div> </div>
} }
<vc:store-numbers vm="@(new StoreNumbersViewModel { Store = store, CryptoCode = Model.CryptoCode, InitialRendering = true })"/> <vc:store-numbers vm="@(new StoreNumbersViewModel { Store = store, CryptoCode = Model.CryptoCode, InitialRendering = true })" />
@if (Model.LightningEnabled) @if (Model.LightningEnabled)
{ {
<vc:store-lightning-balance vm="@(new StoreLightningBalanceViewModel { Store = store, CryptoCode = Model.CryptoCode, InitialRendering = true })"/> <vc:store-lightning-balance vm="@(new StoreLightningBalanceViewModel { Store = store, CryptoCode = Model.CryptoCode, InitialRendering = true })" />
<vc:store-lightning-services vm="@(new StoreLightningServicesViewModel { Store = store, CryptoCode = Model.CryptoCode })" permission="@Policies.CanModifyServerSettings"/> <vc:store-lightning-services vm="@(new StoreLightningServicesViewModel { Store = store, CryptoCode = Model.CryptoCode })" permission="@Policies.CanModifyServerSettings" />
} }
@if (Model.WalletEnabled) @if (Model.WalletEnabled)
{ {
<vc:store-recent-transactions vm="@(new StoreRecentTransactionsViewModel { Store = store, CryptoCode = Model.CryptoCode, InitialRendering = true })"/> <vc:store-recent-transactions vm="@(new StoreRecentTransactionsViewModel { Store = store, CryptoCode = Model.CryptoCode, InitialRendering = true })" />
} }
<vc:store-recent-invoices vm="@(new StoreRecentInvoicesViewModel { Store = store, CryptoCode = Model.CryptoCode, InitialRendering = true })"/> <vc:store-recent-invoices vm="@(new StoreRecentInvoicesViewModel { Store = store, CryptoCode = Model.CryptoCode, InitialRendering = true })" />
@foreach (var app in Model.Apps) @foreach (var app in Model.Apps)
{ {
<vc:app-sales app-id="@app.Id" app-type="@app.AppType" /> <vc:app-sales app-id="@app.Id" app-type="@app.AppType" />
<vc:app-top-items app-id="@app.Id" app-type="@app.AppType" /> <vc:app-top-items app-id="@app.Id" app-type="@app.AppType" />
} }
</div> </div>
} }
else else
{ {
<p class="lead text-secondary mt-2">To start accepting payments, set up a wallet or a Lightning node.</p> <p class="lead text-secondary mt-2" text-translate="true">To start accepting payments, set up a wallet or a Lightning node.</p>
<div class="list-group" id="SetupGuide"> <div class="list-group" id="SetupGuide">
<div class="list-group-item d-flex align-items-center" id="SetupGuide-StoreDone"> <div class="list-group-item d-flex align-items-center" id="SetupGuide-StoreDone">
<vc:icon symbol="done"/> <vc:icon symbol="done" />
<div class="content"> <div class="content">
<h5 class="mb-0 text-success">Create your store</h5> <h5 class="mb-0 text-success" text-translate="true">Create your store</h5>
</div> </div>
</div> </div>
@if (Model.Network is BTCPayNetwork) @if (Model.Network is BTCPayNetwork)
@@ -113,40 +113,41 @@ else
@if (!Model.WalletEnabled) @if (!Model.WalletEnabled)
{ {
<a asp-controller="UIStores" asp-action="SetupWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center order-1"> <a asp-controller="UIStores" asp-action="SetupWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<vc:icon symbol="wallet-new"/> <vc:icon symbol="wallet-new" />
<div class="content"> <div class="content">
<h5 class="mb-0">Set up a wallet</h5> <h5 class="mb-0" text-translate="true">Set up a wallet</h5>
</div> </div>
<vc:icon symbol="caret-right"/> <vc:icon symbol="caret-right" />
</a> </a>
} }
else else
{ {
<div class="list-group-item d-flex align-items-center" id="SetupGuide-WalletDone"> <div class="list-group-item d-flex align-items-center" id="SetupGuide-WalletDone">
<vc:icon symbol="done"/> <vc:icon symbol="done" />
<div class="content"> <div class="content">
<h5 class="mb-0 text-success">Set up a wallet</h5> <h5 class="mb-0 text-success" text-translate="true">Set up a wallet</h5>
</div> </div>
</div> </div>
} }
} }
@if (Model.LightningSupported) { @if (Model.LightningSupported)
{
if (!Model.LightningEnabled) if (!Model.LightningEnabled)
{ {
<a asp-controller="UIStores" asp-action="SetupLightningNode" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" id="SetupGuide-Lightning" class="list-group-item list-group-item-action d-flex align-items-center order-1"> <a asp-controller="UIStores" asp-action="SetupLightningNode" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" id="SetupGuide-Lightning" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<vc:icon symbol="wallet-new"/> <vc:icon symbol="wallet-new" />
<div class="content"> <div class="content">
<h5 class="mb-0">Set up a Lightning node</h5> <h5 class="mb-0" text-translate="true">Set up a Lightning node</h5>
</div> </div>
<vc:icon symbol="caret-right"/> <vc:icon symbol="caret-right" />
</a> </a>
} }
else else
{ {
<div class="list-group-item d-flex align-items-center" id="SetupGuide-LightningDone"> <div class="list-group-item d-flex align-items-center" id="SetupGuide-LightningDone">
<vc:icon symbol="done"/> <vc:icon symbol="done" />
<div class="content"> <div class="content">
<h5 class="mb-0 text-success">Set up a Lightning node</h5> <h5 class="mb-0 text-success" text-translate="true">Set up a Lightning node</h5>
</div> </div>
</div> </div>
} }

View File

@@ -1,7 +1,7 @@
@model LightningNodeViewModel @model LightningNodeViewModel
@{ @{
Layout = "_LayoutWalletSetup.cshtml"; Layout = "_LayoutWalletSetup.cshtml";
ViewData.SetActivePage(StoreNavPages.LightningSettings, "Connect to a Lightning node", Context.GetStoreData().Id); ViewData.SetActivePage(StoreNavPages.LightningSettings, StringLocalizer["Connect to a Lightning node"], Context.GetStoreData().Id);
} }
@section PageHeadContent { @section PageHeadContent {
@@ -28,10 +28,10 @@
<form method="post" class="mt-n2 text-center"> <form method="post" class="mt-n2 text-center">
<div id="LightningNodeTypeTablist" class="nav btcpay-pills align-items-center justify-content-center mb-3" role="tablist"> <div id="LightningNodeTypeTablist" class="nav btcpay-pills align-items-center justify-content-center mb-3" role="tablist">
<input asp-for="LightningNodeType" value="@LightningNodeType.Internal" type="radio" id="LightningNodeType-@LightningNodeType.Internal" data-bs-toggle="pill" data-bs-target="#InternalSetup" role="tab" aria-controls="InternalSetup" aria-selected="@(Model.LightningNodeType == LightningNodeType.Internal ? "true" : "false")" class="@(Model.LightningNodeType == LightningNodeType.Internal ? "active" : "")" disabled="@(!Model.CanUseInternalNode)"> <input asp-for="LightningNodeType" value="@LightningNodeType.Internal" type="radio" id="LightningNodeType-@LightningNodeType.Internal" data-bs-toggle="pill" data-bs-target="#InternalSetup" role="tab" aria-controls="InternalSetup" aria-selected="@(Model.LightningNodeType == LightningNodeType.Internal ? "true" : "false")" class="@(Model.LightningNodeType == LightningNodeType.Internal ? "active" : "")" disabled="@(!Model.CanUseInternalNode)">
<label asp-for="LightningNodeType" for="@($"LightningNodeType-{LightningNodeType.Internal}")">Use internal node</label> <label asp-for="LightningNodeType" for="@($"LightningNodeType-{LightningNodeType.Internal}")" text-translate="true">Use internal node</label>
<input asp-for="LightningNodeType" value="@LightningNodeType.Custom" type="radio" id="LightningNodeType-@LightningNodeType.Custom" data-bs-toggle="pill" data-bs-target="#CustomSetup" role="tab" aria-controls="CustomSetup" aria-selected="@(Model.LightningNodeType == LightningNodeType.Custom ? "true" : "false")" class="@(Model.LightningNodeType == LightningNodeType.Custom ? "active" : "")"> <input asp-for="LightningNodeType" value="@LightningNodeType.Custom" type="radio" id="LightningNodeType-@LightningNodeType.Custom" data-bs-toggle="pill" data-bs-target="#CustomSetup" role="tab" aria-controls="CustomSetup" aria-selected="@(Model.LightningNodeType == LightningNodeType.Custom ? "true" : "false")" class="@(Model.LightningNodeType == LightningNodeType.Custom ? "active" : "")">
<label asp-for="LightningNodeType" for="@($"LightningNodeType-{LightningNodeType.Custom}")">Use custom node</label> <label asp-for="LightningNodeType" for="@($"LightningNodeType-{LightningNodeType.Custom}")" text-translate="true">Use custom node</label>
<vc:ui-extension-point location="ln-payment-method-setup-tabhead" model="@Model"/> <vc:ui-extension-point location="ln-payment-method-setup-tabhead" model="@Model"/>
</div> </div>
@@ -40,24 +40,24 @@
<div id="InternalSetup" class="pt-3 tab-pane fade @(Model.LightningNodeType == LightningNodeType.Internal ? "show active" : "")" role="tabpanel" aria-labelledby="LightningNodeType-@LightningNodeType.Internal"> <div id="InternalSetup" class="pt-3 tab-pane fade @(Model.LightningNodeType == LightningNodeType.Internal ? "show active" : "")" role="tabpanel" aria-labelledby="LightningNodeType-@LightningNodeType.Internal">
@if (Model.CanUseInternalNode) @if (Model.CanUseInternalNode)
{ {
<p class="mb-4">Using the BTCPay Server internal node for this store requires no further configuration. Click the save button below to start accepting Bitcoin through the Lightning Network.</p> <p class="mb-4" text-translate="true">Using the BTCPay Server internal node for this store requires no further configuration. Click the save button below to start accepting Bitcoin through the Lightning Network.</p>
} }
else else
{ {
<p class="mb-4">Your instance administrator has disabled the use of the Internal node for non-admin users.</p> <p class="mb-4" text-translate="true">Your instance administrator has disabled the use of the Internal node for non-admin users.</p>
} }
</div> </div>
<div id="CustomSetup" class="pt-3 tab-pane fade @(Model.LightningNodeType == LightningNodeType.Custom ? "show active" : "")" role="tabpanel" aria-labelledby="LightningNodeType-@LightningNodeType.Custom"> <div id="CustomSetup" class="pt-3 tab-pane fade @(Model.LightningNodeType == LightningNodeType.Custom ? "show active" : "")" role="tabpanel" aria-labelledby="LightningNodeType-@LightningNodeType.Custom">
<div class="form-group"> <div class="form-group">
<label asp-for="ConnectionString" class="form-label">Connection configuration for your custom Lightning node:</label> <label asp-for="ConnectionString" class="form-label" text-translate="true">Connection configuration for your custom Lightning node:</label>
<div class="d-sm-flex"> <div class="d-sm-flex">
<input asp-for="ConnectionString" class="form-control mb-2 me-2" placeholder="type=…;server=…;" value="@(Model.LightningNodeType == LightningNodeType.Internal ? "" : Model.ConnectionString)"/> <input asp-for="ConnectionString" class="form-control mb-2 me-2" placeholder="type=…;server=…;" value="@(Model.LightningNodeType == LightningNodeType.Internal ? "" : Model.ConnectionString)"/>
<button id="test" name="command" type="submit" value="test" class="btn btn-secondary text-nowrap mb-2">Test connection</button> <button id="test" name="command" type="submit" value="test" class="btn btn-secondary text-nowrap mb-2" text-translate="true">Test connection</button>
</div> </div>
<span asp-validation-for="ConnectionString" class="text-danger"></span> <span asp-validation-for="ConnectionString" class="text-danger"></span>
</div> </div>
<vc:ui-extension-point location="ln-payment-method-setup-custom" model="@Model"/> <vc:ui-extension-point location="ln-payment-method-setup-custom" model="@Model"/>
<p class="mt-4 mb-2">BTCPay Server currently supports:</p> <p class="mt-4 mb-2" text-translate="true">BTCPay Server currently supports:</p>
<div class="accordion" id="CustomNodeSupport"> <div class="accordion" id="CustomNodeSupport">
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header" id="CustomNodeCLightningHeader"> <h2 class="accordion-header" id="CustomNodeCLightningHeader">

View File

@@ -18,7 +18,7 @@
<div class="form-group"> <div class="form-group">
<label asp-for="PreferredExchange" class="form-label" data-required></label> <label asp-for="PreferredExchange" class="form-label" data-required></label>
<select asp-for="PreferredExchange" asp-items="Model.Exchanges" class="form-select w-300px"></select> <select asp-for="PreferredExchange" asp-items="Model.Exchanges" class="form-select w-300px"></select>
<div class="form-text mt-2 only-for-js">The recommended price source gets chosen based on the default currency.</div> <div class="form-text mt-2 only-for-js" text-translate="true">The recommended price source gets chosen based on the default currency.</div>
<span asp-validation-for="PreferredExchange" class="text-danger"></span> <span asp-validation-for="PreferredExchange" class="text-danger"></span>
</div> </div>
<div class="form-group mt-4"> <div class="form-group mt-4">

View File

@@ -11,6 +11,7 @@
@using Microsoft.AspNetCore.Routing; @using Microsoft.AspNetCore.Routing;
@using BTCPayServer.Abstractions.Extensions; @using BTCPayServer.Abstractions.Extensions;
@inject Microsoft.AspNetCore.Mvc.Localization.ViewLocalizer ViewLocalizer @inject Microsoft.AspNetCore.Mvc.Localization.ViewLocalizer ViewLocalizer
@inject Microsoft.Extensions.Localization.IStringLocalizer StringLocalizer
@inject Safe Safe @inject Safe Safe
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, BTCPayServer @addTagHelper *, BTCPayServer