Added version to Language Management, improved framework performance by loading languages into PageState, include all supported cultures and allow Administrator to add any language to a site regardless of translation availability, fix translation upgrade issue

This commit is contained in:
Shaun Walker 2022-07-16 09:59:47 -04:00
parent 6012275c7b
commit f97a6a2bee
12 changed files with 96 additions and 36 deletions

View File

@ -19,15 +19,17 @@ 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>
<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><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td> <td>@context.Version</td>
<td> <td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
<td>
@if (UpgradeAvailable(context.Code)) @if (UpgradeAvailable(context.Code))
{ {
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
@ -50,9 +52,6 @@ else
var cultures = await LocalizationService.GetCulturesAsync(); var cultures = await LocalizationService.GetCulturesAsync();
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture)); var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
// Adds English as default language
_languages.Insert(0, new Language { Name = culture.DisplayName, Code = culture.Name, IsDefault = !_languages.Any(l => l.IsDefault) });
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
_packages = await PackageService.GetPackagesAsync("translation"); _packages = await PackageService.GetPackagesAsync("translation");
@ -81,7 +80,7 @@ else
var upgradeavailable = false; var upgradeavailable = false;
if (_packages != null) if (_packages != null)
{ {
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + ".Client." + code)).FirstOrDefault(); var package = _packages.Where(item => item.PackageId == (Constants.ClientAssemblyName + "." + code)).FirstOrDefault();
if (package != null) if (package != null)
{ {
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0); upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0);

View File

@ -144,4 +144,7 @@
<data name="DeleteLanguage.Text" xml:space="preserve"> <data name="DeleteLanguage.Text" xml:space="preserve">
<value>Delete</value> <value>Delete</value>
</data> </data>
<data name="Translation" xml:space="preserve">
<value>Translation</value>
</data>
</root> </root>

View File

@ -17,6 +17,14 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<List<Language>> GetLanguagesAsync(int siteId); Task<List<Language>> GetLanguagesAsync(int siteId);
/// <summary>
/// Returns a list of all available languages for the given <see cref="Site" /> and client assembly
/// </summary>
/// <param name="siteId"></param>
/// <param name="clientAssemblyName"></param>
/// <returns></returns>
Task<List<Language>> GetLanguagesAsync(int siteId, string clientAssemblyName);
/// <summary> /// <summary>
/// Returns the given language /// Returns the given language
/// </summary> /// </summary>

View File

@ -17,18 +17,27 @@ namespace Oqtane.Services
public async Task<List<Language>> GetLanguagesAsync(int siteId) public async Task<List<Language>> GetLanguagesAsync(int siteId)
{ {
var languages = await GetJsonAsync<List<Language>>($"{Apiurl}?siteid={siteId}"); return await GetLanguagesAsync(siteId, "");
}
return languages?.OrderBy(l => l.Name).ToList() ?? Enumerable.Empty<Language>().ToList(); public async Task<List<Language>> GetLanguagesAsync(int siteId, string clientAssemblyName)
{
return await GetJsonAsync<List<Language>>($"{Apiurl}?siteid={siteId}&clientassemblyname={clientAssemblyName}");
} }
public async Task<Language> GetLanguageAsync(int languageId) public async Task<Language> GetLanguageAsync(int languageId)
=> await GetJsonAsync<Language>($"{Apiurl}/{languageId}"); {
return await GetJsonAsync<Language>($"{Apiurl}/{languageId}");
}
public async Task<Language> AddLanguageAsync(Language language) public async Task<Language> AddLanguageAsync(Language language)
=> await PostJsonAsync<Language>(Apiurl, language); {
return await PostJsonAsync<Language>(Apiurl, language);
}
public async Task DeleteLanguageAsync(int languageId) public async Task DeleteLanguageAsync(int languageId)
=> await DeleteAsync($"{Apiurl}/{languageId}"); {
await DeleteAsync($"{Apiurl}/{languageId}");
}
} }
} }

View File

@ -24,13 +24,9 @@
@code{ @code{
private IEnumerable<Culture> _supportedCultures; private IEnumerable<Culture> _supportedCultures;
protected override async Task OnParametersSetAsync() protected override void OnParametersSet()
{ {
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId); var languages = PageState.Languages;
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName });
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name }); _supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
} }

View File

@ -8,6 +8,7 @@ namespace Oqtane.UI
{ {
public Alias Alias { get; set; } public Alias Alias { get; set; }
public Site Site { get; set; } public Site Site { get; set; }
public List<Language> Languages { get; set; }
public List<Page> Pages { get; set; } public List<Page> Pages { get; set; }
public Page Page { get; set; } public Page Page { get; set; }
public User User { get; set; } public User User { get; set; }

View File

@ -6,6 +6,7 @@
@inject INavigationInterception NavigationInterception @inject INavigationInterception NavigationInterception
@inject ISyncService SyncService @inject ISyncService SyncService
@inject ISiteService SiteService @inject ISiteService SiteService
@inject ILanguageService LanguageService
@inject IPageService PageService @inject IPageService PageService
@inject IUserService UserService @inject IUserService UserService
@inject IModuleService ModuleService @inject IModuleService ModuleService
@ -70,6 +71,7 @@
private async Task Refresh() private async Task Refresh()
{ {
Site site; Site site;
List<Language> languages;
List<Page> pages; List<Page> pages;
Page page; Page page;
User user = null; User user = null;
@ -102,7 +104,7 @@
return; return;
} }
} }
// the refresh parameter is used to refresh the client-side PageState // the refresh parameter is used to refresh the client-side PageState
if (querystring.ContainsKey("refresh")) if (querystring.ContainsKey("refresh"))
{ {
@ -173,11 +175,13 @@
if (PageState == null || refresh == UI.Refresh.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
languages = await LanguageService.GetLanguagesAsync(site.SiteId);
pages = await PageService.GetPagesAsync(site.SiteId); pages = await PageService.GetPagesAsync(site.SiteId);
pages = pages.Where(item => !item.IsDeleted).ToList(); pages = pages.Where(item => !item.IsDeleted).ToList();
} }
else else
{ {
languages = PageState.Languages;
pages = PageState.Pages; pages = PageState.Pages;
} }
@ -230,6 +234,7 @@
{ {
Alias = SiteState.Alias, Alias = SiteState.Alias,
Site = site, Site = site,
Languages = languages,
Pages = pages, Pages = pages,
Page = page, Page = page,
User = user, User = user,

View File

@ -1,5 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Net; using System.Net;
using System.Reflection;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Oqtane.Enums; using Oqtane.Enums;
@ -7,6 +9,9 @@ using Oqtane.Infrastructure;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Shared; using Oqtane.Shared;
using System.Linq;
using System.Diagnostics;
using System.Globalization;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -14,23 +19,40 @@ namespace Oqtane.Controllers
public class LanguageController : Controller public class LanguageController : Controller
{ {
private readonly ILanguageRepository _languages; private readonly ILanguageRepository _languages;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; private readonly Alias _alias;
public LanguageController(ILanguageRepository language, ILogManager logger, ITenantManager tenantManager) public LanguageController(ILanguageRepository language, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{ {
_languages = language; _languages = language;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
[HttpGet] [HttpGet]
public IEnumerable<Language> Get(string siteid) public IEnumerable<Language> Get(string siteid, string clientassemblyname)
{ {
int SiteId; int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{ {
return _languages.GetLanguages(SiteId); if (string.IsNullOrEmpty(clientassemblyname))
{
clientassemblyname = Constants.ClientAssemblyName;
}
var languages = _languages.GetLanguages(SiteId).ToList();
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), clientassemblyname + ".resources.dll", SearchOption.AllDirectories))
{
var code = Path.GetFileName(Path.GetDirectoryName(file));
if (languages.Any(item => item.Code == code))
{
languages.Single(item => item.Code == code).Version = FileVersionInfo.GetVersionInfo(file).FileVersion;
}
}
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName, Version = Constants.Version, IsDefault = !languages.Any(l => l.IsDefault) });
return languages.OrderBy(item => item.Name);
} }
else else
{ {
@ -63,6 +85,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && language.SiteId == _alias.SiteId) if (ModelState.IsValid && language.SiteId == _alias.SiteId)
{ {
language = _languages.AddLanguage(language); language = _languages.AddLanguage(language);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language);
} }
else else
@ -82,6 +105,7 @@ namespace Oqtane.Controllers
if (language != null && language.SiteId == _alias.SiteId) if (language != null && language.SiteId == _alias.SiteId)
{ {
_languages.DeleteLanguage(id); _languages.DeleteLanguage(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id);
} }
else else
@ -89,7 +113,6 @@ namespace Oqtane.Controllers
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Language Delete Attempt {LanguageId}", id); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Language Delete Attempt {LanguageId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
} }
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -21,19 +22,20 @@ namespace Oqtane.Infrastructure
} }
public string GetDefaultCulture() public string GetDefaultCulture()
=> String.IsNullOrEmpty(_localizationOptions.DefaultCulture) {
? DefaultCulture if (string.IsNullOrEmpty(_localizationOptions.DefaultCulture))
: _localizationOptions.DefaultCulture; {
return DefaultCulture;
}
else
{
return _localizationOptions.DefaultCulture;
}
}
public string[] GetSupportedCultures() public string[] GetSupportedCultures()
{ {
var cultures = new List<string>(DefaultSupportedCultures); return CultureInfo.GetCultures(CultureTypes.AllCultures).Select(item => item.Name).OrderBy(c => c).ToArray();
foreach(var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Oqtane.Client.resources.dll", SearchOption.AllDirectories))
{
cultures.Add(Path.GetFileName(Path.GetDirectoryName(file)));
}
return cultures.OrderBy(c => c).ToArray();
} }
} }
} }

View File

@ -13,13 +13,16 @@ namespace Oqtane.Repository
_db = context; _db = context;
} }
public IEnumerable<Language> GetLanguages(int siteId) => _db.Language.Where(l => l.SiteId == siteId); public IEnumerable<Language> GetLanguages(int siteId)
{
return _db.Language.Where(l => l.SiteId == siteId);
}
public Language AddLanguage(Language language) public Language AddLanguage(Language language)
{ {
if (language.IsDefault) if (language.IsDefault)
{ {
// Ensure all other languages are not set to current // Ensure all other languages are not set to default
_db.Language _db.Language
.Where(l => l.SiteId == language.SiteId) .Where(l => l.SiteId == language.SiteId)
.ToList() .ToList()
@ -32,7 +35,10 @@ namespace Oqtane.Repository
return language; return language;
} }
public Language GetLanguage(int languageId) => _db.Language.Find(languageId); public Language GetLanguage(int languageId)
{
return _db.Language.Find(languageId);
}
public void DeleteLanguage(int languageId) public void DeleteLanguage(int languageId)
{ {

View File

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Oqtane.Models namespace Oqtane.Models
{ {
@ -34,6 +35,12 @@ namespace Oqtane.Models
/// </summary> /// </summary>
public bool IsDefault { get; set; } public bool IsDefault { get; set; }
[NotMapped]
/// <summary>
/// Version of the satellite assembly
/// </summary>
public string Version { get; set; }
#region IAuditable Properties #region IAuditable Properties
/// <inheritdoc/> /// <inheritdoc/>

View File

@ -9,6 +9,7 @@ namespace Oqtane.Shared
public const string PackageId = "Oqtane.Framework"; public const string PackageId = "Oqtane.Framework";
public const string UpdaterPackageId = "Oqtane.Updater"; public const string UpdaterPackageId = "Oqtane.Updater";
public const string PackageRegistryUrl = "https://www.oqtane.net"; public const string PackageRegistryUrl = "https://www.oqtane.net";
public const string ClientAssemblyName = "Oqtane.Client";
public const string DefaultDBType = "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer"; public const string DefaultDBType = "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer";