feat : add functionality to download language pack (#6943)

* refactor : added link to translations files

Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>

* feat : added ability to download langauge pack

Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>

* refactor: removed link for the edit page

Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>

* refactor: server-side downloading instead of JavaScript

Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>

* refactor : addressed request changes

Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>

* refactor: updated GetDictionary method and other changes

Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>

---------

Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
This commit is contained in:
Abhijay Jain
2025-11-26 18:56:24 +05:30
committed by GitHub
parent 5a487985f4
commit 03349b6ffe
3 changed files with 106 additions and 4 deletions

View File

@@ -1,4 +1,6 @@
using System;
using System.Data.Common;
using System.Net.Http;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Models.ServerViewModels;
@@ -122,5 +124,53 @@ namespace BTCPayServer.Controllers
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Dictionary {0} deleted", dictionary].Value;
return RedirectToAction(nameof(ListDictionaries));
}
[HttpPost("server/dictionaries/download")]
public async Task<IActionResult> DownloadLanguagePack(string language)
{
if (string.IsNullOrEmpty(language))
{
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Please select a language"].Value;
return RedirectToAction(nameof(ListDictionaries));
}
string translationsJson;
try
{
translationsJson = await FetchLanguagePackFromRepository(language);
}
catch (HttpRequestException ex)
{
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Failed to download language pack: {0}", ex.Message].Value;
return RedirectToAction(nameof(ListDictionaries));
}
var translations = Translations.CreateFromJson(translationsJson);
var existingDictionary = await _localizer.GetDictionary(language);
if (existingDictionary is null)
{
existingDictionary = await _localizer.CreateDictionary(language, Translations.DefaultLanguage, "Custom");
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Language pack '{0}' downloaded successfully", language].Value;
}
else
{
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["Language pack '{0}' updated successfully", language].Value;
}
await _localizer.Save(existingDictionary, translations);
return RedirectToAction(nameof(ListDictionaries));
}
private async Task<string> FetchLanguagePackFromRepository(string language)
{
var fileName = language.ToLowerInvariant();
var url = $"https://raw.githubusercontent.com/btcpayserver/btcpayserver-translator/main/translations/{fileName}.json";
var httpClient = HttpClientFactory.CreateClient();
using var response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}

View File

@@ -177,7 +177,7 @@ namespace BTCPayServer.Services
{
await using var ctx = _ContextFactory.CreateContext();
var db = ctx.Database.GetDbConnection();
var r = await db.QueryFirstAsync("SELECT * FROM lang_dictionaries WHERE dict_id=@dict_id", new { dict_id = name });
var r = await db.QueryFirstOrDefaultAsync("SELECT * FROM lang_dictionaries WHERE dict_id=@dict_id", new { dict_id = name });
if (r is null)
return null;
return new Dictionary(r.dict_id, r.fallback, r.source ?? "", JObject.Parse(r.metadata ?? "{}"));

View File

@@ -6,9 +6,14 @@
<div class="sticky-header">
<h2>@ViewData["Title"]</h2>
<a id="page-primary" asp-action="CreateDictionary" class="btn btn-primary" role="button">
Create
</a>
<div class="d-flex gap-2">
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#downloadLanguagePackModal" text-translate="true">
Download language pack
</button>
<a id="page-primary" asp-action="CreateDictionary" class="btn btn-primary" role="button">
Create
</a>
</div>
</div>
<partial name="_StatusMessage" />
@@ -65,3 +70,50 @@
</table>
<partial name="_Confirm" model="@(new ConfirmModel(StringLocalizer["Delete dictionary"], StringLocalizer["This dictionary will be removed from this server."], StringLocalizer["Delete"]))" />
</div>
@* Download Language Pack Modal *@
<div class="modal fade" id="downloadLanguagePackModal" tabindex="-1" role="dialog" aria-labelledby="downloadLanguagePackModalTitle" aria-hidden="true" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered" role="document">
<form method="post" asp-action="DownloadLanguagePack">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="downloadLanguagePackModalTitle" text-translate="true">Download Language Pack</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
<vc:icon symbol="close" />
</button>
</div>
<div class="modal-body">
<p text-translate="true">Select a language to download from the community translation repository.</p>
<p class="mb-3">
<span text-translate="true">These translations are maintained by the community at</span>
<a href="https://github.com/btcpayserver/btcpayserver-translator" target="_blank" rel="noopener noreferrer">btcpayserver-translator</a>.
</p>
<div class="form-group">
<label for="language" class="form-label" text-translate="true">Select Language</label>
<select id="language" name="language" class="form-select" required>
<option value="" text-translate="true">Choose a language...</option>
<option value="Dutch">Dutch</option>
<option value="French">French</option>
<option value="German">German</option>
<option value="Hindi">Hindi</option>
<option value="Indonesian">Indonesian</option>
<option value="Italian">Italian</option>
<option value="Japanese">Japanese</option>
<option value="Korean">Korean</option>
<option value="Portuguese (Brazil)">Portuguese (Brazil)</option>
<option value="Russian">Russian</option>
<option value="Serbian">Serbian</option>
<option value="Spanish">Spanish</option>
<option value="Thai">Thai</option>
<option value="Turkish">Turkish</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" text-translate="true">Cancel</button>
<button type="submit" class="btn btn-primary" text-translate="true">Download</button>
</div>
</div>
</form>
</div>
</div>