diff --git a/Oqtane.Client/Modules/Admin/Languages/Index.razor b/Oqtane.Client/Modules/Admin/Languages/Index.razor index e98db7d9..f6126beb 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Index.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Index.razor @@ -19,15 +19,17 @@ else   @SharedLocalizer["Name"] @Localizer["Code"] + @Localizer["Translation"] @Localizer["Default"] -   +   @context.Name @context.Code - - + @context.Version + + @if (UpgradeAvailable(context.Code)) { @@ -50,9 +52,6 @@ else var cultures = await LocalizationService.GetCulturesAsync(); 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)) { _packages = await PackageService.GetPackagesAsync("translation"); @@ -81,7 +80,7 @@ else var upgradeavailable = false; 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) { upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0); diff --git a/Oqtane.Client/Resources/Modules/Admin/Languages/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Languages/Index.resx index 6b8e887f..f96a90cd 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Languages/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Languages/Index.resx @@ -144,4 +144,7 @@ Delete + + Translation + \ No newline at end of file diff --git a/Oqtane.Client/Services/Interfaces/ILanguageService.cs b/Oqtane.Client/Services/Interfaces/ILanguageService.cs index fd062d81..7a57e203 100644 --- a/Oqtane.Client/Services/Interfaces/ILanguageService.cs +++ b/Oqtane.Client/Services/Interfaces/ILanguageService.cs @@ -17,6 +17,14 @@ namespace Oqtane.Services /// Task> GetLanguagesAsync(int siteId); + /// + /// Returns a list of all available languages for the given and client assembly + /// + /// + /// + /// + Task> GetLanguagesAsync(int siteId, string clientAssemblyName); + /// /// Returns the given language /// diff --git a/Oqtane.Client/Services/LanguageService.cs b/Oqtane.Client/Services/LanguageService.cs index d9657420..413c568a 100644 --- a/Oqtane.Client/Services/LanguageService.cs +++ b/Oqtane.Client/Services/LanguageService.cs @@ -17,18 +17,27 @@ namespace Oqtane.Services public async Task> GetLanguagesAsync(int siteId) { - var languages = await GetJsonAsync>($"{Apiurl}?siteid={siteId}"); + return await GetLanguagesAsync(siteId, ""); + } - return languages?.OrderBy(l => l.Name).ToList() ?? Enumerable.Empty().ToList(); + public async Task> GetLanguagesAsync(int siteId, string clientAssemblyName) + { + return await GetJsonAsync>($"{Apiurl}?siteid={siteId}&clientassemblyname={clientAssemblyName}"); } public async Task GetLanguageAsync(int languageId) - => await GetJsonAsync($"{Apiurl}/{languageId}"); + { + return await GetJsonAsync($"{Apiurl}/{languageId}"); + } public async Task AddLanguageAsync(Language language) - => await PostJsonAsync(Apiurl, language); + { + return await PostJsonAsync(Apiurl, language); + } public async Task DeleteLanguageAsync(int languageId) - => await DeleteAsync($"{Apiurl}/{languageId}"); + { + await DeleteAsync($"{Apiurl}/{languageId}"); + } } } diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor index 41219b0b..7eb94f4d 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor @@ -24,13 +24,9 @@ @code{ private IEnumerable _supportedCultures; - protected override async Task OnParametersSetAsync() + protected override void OnParametersSet() { - var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId); - var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture); - - languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName }); - + var languages = PageState.Languages; _supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name }); } diff --git a/Oqtane.Client/UI/PageState.cs b/Oqtane.Client/UI/PageState.cs index 5b14ad00..46eacd49 100644 --- a/Oqtane.Client/UI/PageState.cs +++ b/Oqtane.Client/UI/PageState.cs @@ -8,6 +8,7 @@ namespace Oqtane.UI { public Alias Alias { get; set; } public Site Site { get; set; } + public List Languages { get; set; } public List Pages { get; set; } public Page Page { get; set; } public User User { get; set; } diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index f66983e0..8d75dc4f 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -6,6 +6,7 @@ @inject INavigationInterception NavigationInterception @inject ISyncService SyncService @inject ISiteService SiteService +@inject ILanguageService LanguageService @inject IPageService PageService @inject IUserService UserService @inject IModuleService ModuleService @@ -70,6 +71,7 @@ private async Task Refresh() { Site site; + List languages; List pages; Page page; User user = null; @@ -102,7 +104,7 @@ return; } } - + // the refresh parameter is used to refresh the client-side PageState if (querystring.ContainsKey("refresh")) { @@ -173,11 +175,13 @@ if (PageState == null || refresh == UI.Refresh.Site) { + languages = await LanguageService.GetLanguagesAsync(site.SiteId); pages = await PageService.GetPagesAsync(site.SiteId); pages = pages.Where(item => !item.IsDeleted).ToList(); } else { + languages = PageState.Languages; pages = PageState.Pages; } @@ -230,6 +234,7 @@ { Alias = SiteState.Alias, Site = site, + Languages = languages, Pages = pages, Page = page, User = user, diff --git a/Oqtane.Server/Controllers/LanguageController.cs b/Oqtane.Server/Controllers/LanguageController.cs index cbe67896..8ab3bf5b 100644 --- a/Oqtane.Server/Controllers/LanguageController.cs +++ b/Oqtane.Server/Controllers/LanguageController.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using System.IO; using System.Net; +using System.Reflection; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Oqtane.Enums; @@ -7,6 +9,9 @@ using Oqtane.Infrastructure; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Shared; +using System.Linq; +using System.Diagnostics; +using System.Globalization; namespace Oqtane.Controllers { @@ -14,23 +19,40 @@ namespace Oqtane.Controllers public class LanguageController : Controller { private readonly ILanguageRepository _languages; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public LanguageController(ILanguageRepository language, ILogManager logger, ITenantManager tenantManager) + public LanguageController(ILanguageRepository language, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _languages = language; + _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); } [HttpGet] - public IEnumerable Get(string siteid) + public IEnumerable Get(string siteid, string clientassemblyname) { int 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 { @@ -63,6 +85,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && language.SiteId == _alias.SiteId) { language = _languages.AddLanguage(language); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language); } else @@ -82,6 +105,7 @@ namespace Oqtane.Controllers if (language != null && language.SiteId == _alias.SiteId) { _languages.DeleteLanguage(id); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id); } else @@ -89,7 +113,6 @@ namespace Oqtane.Controllers _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Language Delete Attempt {LanguageId}", id); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } - } } } diff --git a/Oqtane.Server/Infrastructure/LocalizationManager.cs b/Oqtane.Server/Infrastructure/LocalizationManager.cs index 83a9cf25..ddc25096 100644 --- a/Oqtane.Server/Infrastructure/LocalizationManager.cs +++ b/Oqtane.Server/Infrastructure/LocalizationManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -21,19 +22,20 @@ namespace Oqtane.Infrastructure } public string GetDefaultCulture() - => String.IsNullOrEmpty(_localizationOptions.DefaultCulture) - ? DefaultCulture - : _localizationOptions.DefaultCulture; + { + if (string.IsNullOrEmpty(_localizationOptions.DefaultCulture)) + { + return DefaultCulture; + } + else + { + return _localizationOptions.DefaultCulture; + } + } public string[] GetSupportedCultures() { - var cultures = new List(DefaultSupportedCultures); - 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(); + return CultureInfo.GetCultures(CultureTypes.AllCultures).Select(item => item.Name).OrderBy(c => c).ToArray(); } } } diff --git a/Oqtane.Server/Repository/LanguageRepository.cs b/Oqtane.Server/Repository/LanguageRepository.cs index 7f92086b..fb252fd6 100644 --- a/Oqtane.Server/Repository/LanguageRepository.cs +++ b/Oqtane.Server/Repository/LanguageRepository.cs @@ -13,13 +13,16 @@ namespace Oqtane.Repository _db = context; } - public IEnumerable GetLanguages(int siteId) => _db.Language.Where(l => l.SiteId == siteId); + public IEnumerable GetLanguages(int siteId) + { + return _db.Language.Where(l => l.SiteId == siteId); + } public Language AddLanguage(Language language) { if (language.IsDefault) { - // Ensure all other languages are not set to current + // Ensure all other languages are not set to default _db.Language .Where(l => l.SiteId == language.SiteId) .ToList() @@ -32,7 +35,10 @@ namespace Oqtane.Repository 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) { diff --git a/Oqtane.Shared/Models/Language.cs b/Oqtane.Shared/Models/Language.cs index ef81a78d..a75d0685 100644 --- a/Oqtane.Shared/Models/Language.cs +++ b/Oqtane.Shared/Models/Language.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; namespace Oqtane.Models { @@ -34,6 +35,12 @@ namespace Oqtane.Models /// public bool IsDefault { get; set; } + [NotMapped] + /// + /// Version of the satellite assembly + /// + public string Version { get; set; } + #region IAuditable Properties /// diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 1d1b4fe7..087cec0d 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -9,6 +9,7 @@ namespace Oqtane.Shared public const string PackageId = "Oqtane.Framework"; public const string UpdaterPackageId = "Oqtane.Updater"; 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";