fix #2464 - translation install/upgrade experience

This commit is contained in:
Shaun Walker 2022-10-20 13:16:18 -04:00
parent 82c074ce2e
commit 368b900a6e
10 changed files with 324 additions and 409 deletions

View File

@ -9,7 +9,7 @@
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_supportedCultures == null) @if (_cultures == null)
{ {
<p><em>@SharedLocalizer["Loading"]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
@ -17,20 +17,23 @@ else
{ {
<TabStrip> <TabStrip>
<TabPanel Name="Manage" ResourceKey="Manage"> <TabPanel Name="Manage" ResourceKey="Manage">
@if (_availableCultures.Count() == 0)
{
<ModuleMessage Type="MessageType.Info" Message="@_message"></ModuleMessage>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
}
else
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="translated" HelpText="Specify If You Wish To Select Languages That Have Translations Installed" ResourceKey="Translated">Translated?</Label>
<div class="col-sm-9">
<select id="translated" class="form-select" value="@_translated" @onchange="(e => TranslatedChanged(e))" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label> <Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="_code" class="form-select" @bind="@_code" required> <select id="_code" class="form-select" @bind="@_code" required>
@foreach (var culture in _availableCultures) <option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var culture in _cultures)
{ {
<option value="@culture.Name">@(culture.DisplayName + " (" + culture.Name + ")")</option> <option value="@culture.Name">@(culture.DisplayName + " (" + culture.Name + ")")</option>
} }
@ -40,7 +43,7 @@ else
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label> <Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="default" class="form-select" @bind="@_isDefault" required> <select id="default" class="form-select" @bind="@_default" required>
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
@ -50,71 +53,6 @@ else
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form> </form>
}
</TabPanel>
<TabPanel Name="Translations" Heading="Translations" ResourceKey="Download" Security="SecurityAccessLevel.Host">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</div>
</div>
</div>
@if (_packages != null)
{
@if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
}
else
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
</Row>
</Pager>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
}
</TabPanel> </TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host"> <TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<div class="container"> <div class="container">
@ -125,140 +63,58 @@ else
</div> </div>
</div> </div>
</div> </div>
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button> <button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
} }
@if (_productname != "")
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_license))
{
@((MarkupString)_license)
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
@code { @code {
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private string _code = string.Empty; private string _translated = "True";
private string _isDefault = "False"; private string _code = "-";
private string _message; private string _default = "False";
private IEnumerable<Culture> _supportedCultures; private List<string> _languages;
private IEnumerable<Culture> _availableCultures; private IEnumerable<Culture> _cultures;
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId); var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var languagesCodes = languages.Select(l => l.Code).ToList(); _languages = languages.Select(l => l.Code).ToList();
_supportedCultures = await LocalizationService.GetCulturesAsync(); await LoadCultures();
_availableCultures = _supportedCultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name));
await LoadTranslations();
if (_supportedCultures.Count() == 1)
{
_message = Localizer["OnlyEnglish"];
}
else if (_availableCultures.Count() == 0)
{
_message = Localizer["AllLanguages"];
}
} }
private async Task LoadTranslations() private async Task LoadCultures()
{ {
_packages = await PackageService.GetPackagesAsync("translation", _search, _price, ""); _cultures = await LocalizationService.GetCulturesAsync(bool.Parse(_translated));
_cultures = _cultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !_languages.Contains(c.Name));
_code = "-";
} }
private async void PriceChanged(ChangeEventArgs e) private async void TranslatedChanged(ChangeEventArgs e)
{ {
try _translated = (string)e.Value;
{ await LoadCultures();
_price = (string)e.Value;
_search = "";
await LoadTranslations();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
}
private async Task Search()
{
try
{
await LoadTranslations();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
private async Task Reset()
{
try
{
_search = "";
await LoadTranslations();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
}
private async Task SaveLanguage() private async Task SaveLanguage()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form) && _code != "-")
{ {
var language = new Language var language = new Language
{ {
SiteId = PageState.Page.SiteId, SiteId = PageState.Page.SiteId,
Name = CultureInfo.GetCultureInfo(_code).DisplayName, Name = CultureInfo.GetCultureInfo(_code).DisplayName,
Code = _code, Code = _code,
IsDefault = (_isDefault == null ? false : Boolean.Parse(_isDefault)) IsDefault = (_default == null ? false : Boolean.Parse(_default))
}; };
try try
@ -286,55 +142,7 @@ else
} }
} }
private async Task InstallTranslations()
private void HideModal()
{
_productname = "";
_license = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Language.Download"], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Translation {Name} {Version}", _packageid, _version);
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
}
}
private async Task InstallLanguages()
{ {
try try
{ {

View File

@ -19,29 +19,86 @@ else
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th> <th>@Localizer["Code"]</th>
<th>@Localizer["Translation"]</th>
<th>@Localizer["Default"]</th> <th>@Localizer["Default"]</th>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<th style="width: 1px;">@Localizer["Translation"]</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
}
</Header> </Header>
<Row> <Row>
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td> <td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Code</td> <td>@context.Code</td>
<td>@context.Version</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td> <td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
<td> @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@if (UpgradeAvailable(context.Code, context.Version))
{ {
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button> <td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<td>
@switch (TranslationAvailable(context.Code, context.Version))
{
case "install":
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code))>@SharedLocalizer["Download"]</button>
break;
case "upgrade":
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code))>@SharedLocalizer["Upgrade"]</button>
break;
} }
</td> </td>
}
</Row> </Row>
</Pager> </Pager>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _install)
{
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
}
@if (_package != null)
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h4 style="display: inline;"><a href="@_package.ProductUrl" target="_new">@_package.Name</a></h4><br />
@SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@_package.OwnerUrl" target="new">@_package.Owner</a></strong><br />
@(_package.Description.Length > 400 ? (_package.Description.Substring(0, 400) + "...") : _package.Description)<br />
<strong>@(String.Format("{0:n0}", _package.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@_package.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@_package.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(_package.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(_package.PackageUrl).Host + "</strong>" : ""))
<br /><br />
@if (!string.IsNullOrEmpty(_package.License))
{
@((MarkupString)_package.License.Replace("\n", "<br />"))
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
} }
@code { @code {
private List<Language> _languages; private List<Language> _languages;
private List<Package> _packages; private List<Package> _packages;
private Package _package;
private bool _install = false;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -49,7 +106,7 @@ else
{ {
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId); _languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
var cultures = await LocalizationService.GetCulturesAsync(); var cultures = await LocalizationService.GetCulturesAsync(false);
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture)); var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@ -75,38 +132,83 @@ else
} }
} }
private bool UpgradeAvailable(string code, string version) private string TranslationAvailable(string code, string version)
{ {
var upgradeavailable = false; if (_packages != null)
if (_packages != null && version != null)
{ {
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault(); var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault();
if (package != null) if (package != null)
{ {
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0) && // package version needs to match current framework version
(Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0); if (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0)
{
if (string.IsNullOrEmpty(version))
{
return "install";
}
else
{
if (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
return "upgrade";
}
}
}
}
}
return "";
} }
} private async Task GetPackage(string code)
return upgradeavailable;
}
private async Task DownloadLanguage(string code)
{ {
try try
{ {
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) _package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, Constants.Version);
{ StateHasChanged();
await PackageService.DownloadPackageAsync(Constants.PackageId + "." + code, Constants.Version, Constants.PackagesFolder);
await logger.LogInformation("Translation Downloaded {Code} {Version}", code, Constants.Version);
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Downloading Translation {Code} {Version} {Error}", code, Constants.Version, ex.Message); await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", Constants.PackageId + "." + code, Constants.Version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
AddModuleMessage(Localizer["Success.Language.Download"], MessageType.Success);
_package = null;
_install = true;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Translation {Name} {Version}", _package.PackageId, _package.Version);
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error); AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
} }
} }
private void HideModal()
{
_package = null;
StateHasChanged();
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
_install = false;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
} }

View File

@ -108,30 +108,31 @@
<Header> <Header>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th> <th>@Localizer["Code"]</th>
<th>@Localizer["Version"]</th> <th style="width: 1px;">@Localizer["Version"]</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
</Header> </Header>
<Row> <Row>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Code</td> <td>@context.Code</td>
<td>@context.Version</td> <td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<td> <td>
@if (context.IsDefault) @switch (TranslationAvailable(_packagename + "." + context.Code, context.Version))
{ {
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Download"]</button> case "install":
} <button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Download"]</button>
else break;
{ case "upgrade":
if (UpgradeAvailable(_packagename + "." + context.Code, context.Version)) <button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button>
{ break;
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button>
}
} }
</td> </td>
</Row> </Row>
</Pager> </Pager>
@if (_install)
{
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button> <button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
} }
}
else else
{ {
<br /> <br />
@ -143,7 +144,7 @@
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
@if (_productname != "") @if (_package != null)
{ {
<div class="app-actiondialog"> <div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog"> <div class="modal" tabindex="-1" role="dialog">
@ -155,10 +156,17 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p style="height: 200px; overflow-y: scroll;"> <p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3> <h4 style="display: inline;"><a href="@_package.ProductUrl" target="_new">@_package.Name</a></h4><br />
@if (!string.IsNullOrEmpty(_packagelicense)) @SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@_package.OwnerUrl" target="new">@_package.Owner</a></strong><br />
@(_package.Description.Length > 400 ? (_package.Description.Substring(0, 400) + "...") : _package.Description)<br />
<strong>@(String.Format("{0:n0}", _package.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@_package.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@_package.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(_package.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(_package.PackageUrl).Host + "</strong>" : ""))
<br /><br />
@if (!string.IsNullOrEmpty(_package.License))
{ {
@((MarkupString)_packagelicense) @((MarkupString)_package.License.Replace("\n", "<br />"))
} }
else else
{ {
@ -167,7 +175,7 @@
</p> </p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadPackage(_packageid))>@SharedLocalizer["Accept"]</button> <button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div> </div>
</div> </div>
@ -203,9 +211,8 @@
private List<Package> _packages; private List<Package> _packages;
private List<Language> _languages; private List<Language> _languages;
private string _productname = ""; private Package _package;
private string _packagelicense = ""; private bool _install = false;
private string _packageid = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -297,24 +304,31 @@
private void HideModal() private void HideModal()
{ {
_productname = ""; _package = null;
_packagelicense = "";
StateHasChanged(); StateHasChanged();
} }
private bool UpgradeAvailable(string packagename, string version) private string TranslationAvailable(string packagename, string version)
{ {
var upgradeavailable = false;
if (_packages != null) if (_packages != null)
{ {
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null) if (package != null)
{ {
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0); if (string.IsNullOrEmpty(version))
{
return "install";
} }
else
{
if (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
return "upgrade";
} }
return upgradeavailable; }
}
}
return "";
} }
private async Task GetPackage(string packagename) private async Task GetPackage(string packagename)
@ -322,16 +336,7 @@
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version; var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
try try
{ {
var package = await PackageService.GetPackageAsync(packagename, version); _package = await PackageService.GetPackageAsync(packagename, version);
if (package != null)
{
_productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
}
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -341,16 +346,15 @@
} }
} }
private async Task DownloadPackage(string packagename) private async Task DownloadPackage()
{ {
try try
{ {
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version; await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder); await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", packagename, version);
AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success); AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success);
_productname = ""; _package = null;
_packagelicense = ""; _install = true;
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -366,6 +370,8 @@
{ {
await PackageService.InstallPackagesAsync(); await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
_install = false;
StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -444,7 +444,7 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message); await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error); AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
} }
} }

View File

@ -16,6 +16,7 @@ using Microsoft.JSInterop;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI; using Oqtane.UI;
namespace Oqtane.Client namespace Oqtane.Client
@ -56,7 +57,11 @@ namespace Oqtane.Client
RegisterClientStartups(assembly, builder.Services); RegisterClientStartups(assembly, builder.Services);
} }
await builder.Build().RunAsync(); var host = builder.Build();
await SetCultureFromLocalizationCookie(host.Services);
await host.RunAsync();
} }
private static async Task LoadClientAssemblies(HttpClient http, IServiceProvider serviceProvider) private static async Task LoadClientAssemblies(HttpClient http, IServiceProvider serviceProvider)
@ -220,7 +225,7 @@ namespace Oqtane.Client
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName); var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value; var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value;
var localizationService = serviceProvider.GetRequiredService<ILocalizationService>(); var localizationService = serviceProvider.GetRequiredService<ILocalizationService>();
var cultures = await localizationService.GetCulturesAsync(); var cultures = await localizationService.GetCulturesAsync(false);
if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase))) if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase)))
{ {

View File

@ -117,45 +117,30 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="The Only Supported Culture That Has Been Defined Is English" xml:space="preserve">
<value>The Only Supported Culture That Has Been Defined Is English</value>
</data>
<data name="Error.Language.Add" xml:space="preserve"> <data name="Error.Language.Add" xml:space="preserve">
<value>Error Adding Language</value> <value>Error Adding Language</value>
</data> </data>
<data name="Translated.HelpText" xml:space="preserve">
<value>Specify If You Wish To Select Languages That Have Translations Installed</value>
</data>
<data name="Name.HelpText" xml:space="preserve"> <data name="Name.HelpText" xml:space="preserve">
<value>Name Of The Langauage</value> <value>Name Of The Langauage</value>
</data> </data>
<data name="IsDefault.HelpText" xml:space="preserve"> <data name="IsDefault.HelpText" xml:space="preserve">
<value>Indicates Whether Or Not This Language Is The Default For The Site</value> <value>Indicates Whether Or Not This Language Is The Default For The Site</value>
</data> </data>
<data name="Translated.Text" xml:space="preserve">
<value>Translated?</value>
</data>
<data name="Name.Text" xml:space="preserve"> <data name="Name.Text" xml:space="preserve">
<value>Name:</value> <value>Name:</value>
</data> </data>
<data name="IsDefault.Text" xml:space="preserve"> <data name="IsDefault.Text" xml:space="preserve">
<value>Default?</value> <value>Default?</value>
</data> </data>
<data name="AllLanguages" xml:space="preserve">
<value>All The Installed Languages Have Been Added.</value>
</data>
<data name="Error.Language.Download" xml:space="preserve">
<value>Error Downloading Translation</value>
</data>
<data name="OnlyEnglish" xml:space="preserve">
<value>The Only Installed Language Is English</value>
</data>
<data name="Success.Language.Download" xml:space="preserve">
<value>Translation Downloaded Successfully. Click Install To Complete Installation.</value>
</data>
<data name="Success.Language.Install" xml:space="preserve"> <data name="Success.Language.Install" xml:space="preserve">
<value>Translations Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value> <value>Translations Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data> </data>
<data name="Search.NoResults" xml:space="preserve">
<value>No Translations Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Translations</value>
</data>
<data name="LanguageUpload.HelpText" xml:space="preserve"> <data name="LanguageUpload.HelpText" xml:space="preserve">
<value>Upload one or more translation packages. Once they are uploaded click Install to complete the installation.</value> <value>Upload one or more translation packages. Once they are uploaded click Install to complete the installation.</value>
</data> </data>

View File

@ -13,6 +13,6 @@ namespace Oqtane.Services
/// Returns a collection of supported cultures /// Returns a collection of supported cultures
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<Culture>> GetCulturesAsync(); Task<IEnumerable<Culture>> GetCulturesAsync(bool installed);
} }
} }

View File

@ -14,6 +14,6 @@ namespace Oqtane.Services
private string Apiurl => CreateApiUrl("Localization"); private string Apiurl => CreateApiUrl("Localization");
public async Task<IEnumerable<Culture>> GetCulturesAsync() => await GetJsonAsync<IEnumerable<Culture>>(Apiurl); public async Task<IEnumerable<Culture>> GetCulturesAsync(bool installed) => await GetJsonAsync<IEnumerable<Culture>>($"{Apiurl}?installed={installed}");
} }
} }

View File

@ -47,7 +47,7 @@ namespace Oqtane.Controllers
var code = Path.GetFileName(Path.GetDirectoryName(file)); var code = Path.GetFileName(Path.GetDirectoryName(file));
if (!languages.Any(item => item.Code == code)) if (!languages.Any(item => item.Code == code))
{ {
languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = FileVersionInfo.GetVersionInfo(file).FileVersion, IsDefault = false }); languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = FileVersionInfo.GetVersionInfo(file).ProductVersion, IsDefault = false });
} }
} }
} }
@ -62,7 +62,7 @@ namespace Oqtane.Controllers
var code = Path.GetFileName(Path.GetDirectoryName(file)); var code = Path.GetFileName(Path.GetDirectoryName(file));
if (languages.Any(item => item.Code == code)) if (languages.Any(item => item.Code == code))
{ {
languages.Single(item => item.Code == code).Version = FileVersionInfo.GetVersionInfo(file).FileVersion; languages.Single(item => item.Code == code).Version = FileVersionInfo.GetVersionInfo(file).ProductVersion;
} }
} }
} }

View File

@ -21,9 +21,18 @@ namespace Oqtane.Controllers
// GET: api/localization // GET: api/localization
[HttpGet()] [HttpGet()]
public IEnumerable<Culture> Get() public IEnumerable<Culture> Get(bool installed)
{ {
var cultures = _localizationManager.GetSupportedCultures().Select(c => new Culture string[] culturecodes;
if (installed)
{
culturecodes = _localizationManager.GetInstalledCultures();
}
else
{
culturecodes = _localizationManager.GetSupportedCultures();
}
var cultures = culturecodes.Select(c => new Culture
{ {
Name = CultureInfo.GetCultureInfo(c).Name, Name = CultureInfo.GetCultureInfo(c).Name,
DisplayName = CultureInfo.GetCultureInfo(c).DisplayName, DisplayName = CultureInfo.GetCultureInfo(c).DisplayName,