From 0796ce54a9a04c9cb9a1a73b26c7149dc565b127 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 17:30:56 +0300 Subject: [PATCH 01/14] Add localization settings --- .../Localization/LocalizationSettings.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs diff --git a/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs b/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs new file mode 100644 index 00000000..80811310 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace Oqtane.Infrastructure.Localization +{ + public static class LocalizationSettings + { + private const string EnglishCulture = "en-US"; + + static LocalizationSettings() + { + DefaultCulture = EnglishCulture; + SupportedCultures = new List { DefaultCulture }; + } + + public static string DefaultCulture { get; set; } + + public static IList SupportedCultures { get; set; } + } +} From edecfa10cdc464a5b6007fce74ec3fff749db5c6 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 17:31:54 +0300 Subject: [PATCH 02/14] Load satellite assemblies on startup --- .../OqtaneServiceCollectionExtensions.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index f49d189a..e474ab8f 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Runtime.Loader; using Microsoft.Extensions.Hosting; using Oqtane.Infrastructure; +using Oqtane.Infrastructure.Localization; using Oqtane.Modules; using Oqtane.Services; using Oqtane.UI; @@ -14,10 +15,14 @@ namespace Microsoft.Extensions.DependencyInjection { public static class OqtaneServiceCollectionExtensions { + private static readonly string StalliteAssemblyExtension = ".resources.dll"; + public static IServiceCollection AddOqtaneParts(this IServiceCollection services, Runtime runtime) { LoadAssemblies(); + LoadSatelliteAssemblies(); services.AddOqtaneServices(runtime); + return services; } @@ -119,6 +124,46 @@ namespace Microsoft.Extensions.DependencyInjection } } + private static void LoadSatelliteAssemblies() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); + if (assemblyPath == null) + { + return; + } + + AssemblyLoadContext.Default.Resolving += ResolveDependencies; + + foreach (var culture in LocalizationSettings.SupportedCultures) + { + var assembliesFolder = new DirectoryInfo(Path.Combine(assemblyPath, culture)); + foreach (var assemblyFile in assembliesFolder.EnumerateFiles(StalliteAssemblyExtension)) + { + AssemblyName assemblyName; + try + { + assemblyName = AssemblyName.GetAssemblyName(assemblyFile.FullName); + } + catch + { + Console.WriteLine($"Not Satellite Assembly : {assemblyFile.Name}"); + continue; + } + + try + { + Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(assemblyFile.FullName))); + Console.WriteLine($"Loaded : {assemblyName}"); + } + catch (Exception e) + { + Console.WriteLine($"Failed : {assemblyName}\n{e}"); + } + } + } + } + private static Assembly ResolveDependencies(AssemblyLoadContext context, AssemblyName name) { var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) + "\\" + name.Name + ".dll"; From 396d584615a46a5eaa06212de4699ded55b106a5 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 18:01:57 +0300 Subject: [PATCH 03/14] Dowanlod satellite assemblies to the browser --- .../Controllers/InstallationController.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index 50b45710..f1ef2a8a 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -1,17 +1,17 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Oqtane.Models; -using Oqtane.Shared; -using Oqtane.Infrastructure; -using System; +using System; using System.IO; using System.Reflection; using System.Linq; using System.IO.Compression; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Oqtane.Infrastructure; +using Oqtane.Infrastructure.Localization; +using Oqtane.Models; using Oqtane.Modules; +using Oqtane.Shared; using Oqtane.Themes; -using System.Diagnostics; namespace Oqtane.Controllers { @@ -73,6 +73,16 @@ namespace Oqtane.Controllers // get list of assemblies which should be downloaded to browser var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies(); var list = assemblies.Select(a => a.GetName().Name).ToList(); + var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + + // Get the satellite assemblies + foreach (var cultureFolder in LocalizationSettings.SupportedCultures) + { + foreach (var resourceFile in Directory.EnumerateFiles(Path.Combine(Path.GetDirectoryName(binFolder), cultureFolder))) + { + list.Add(Path.Combine(cultureFolder, Path.GetFileNameWithoutExtension(resourceFile))); + } + } // get module and theme dependencies foreach (var assembly in assemblies) @@ -96,7 +106,6 @@ namespace Oqtane.Controllers } // create zip file containing assemblies and debug symbols - string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); byte[] zipfile; using (var memoryStream = new MemoryStream()) { @@ -106,17 +115,17 @@ namespace Oqtane.Controllers foreach (string file in list) { entry = archive.CreateEntry(file + ".dll"); - using (var filestream = new FileStream(Path.Combine(binfolder, file + ".dll"), FileMode.Open, FileAccess.Read)) + using (var filestream = new FileStream(Path.Combine(binFolder, file + ".dll"), FileMode.Open, FileAccess.Read)) using (var entrystream = entry.Open()) { filestream.CopyTo(entrystream); } // include debug symbols ( we may want to consider restricting this to only host users or when running in debug mode for performance ) - if (System.IO.File.Exists(Path.Combine(binfolder, file + ".pdb"))) + if (System.IO.File.Exists(Path.Combine(binFolder, file + ".pdb"))) { entry = archive.CreateEntry(file + ".pdb"); - using (var filestream = new FileStream(Path.Combine(binfolder, file + ".pdb"), FileMode.Open, FileAccess.Read)) + using (var filestream = new FileStream(Path.Combine(binFolder, file + ".pdb"), FileMode.Open, FileAccess.Read)) using (var entrystream = entry.Open()) { filestream.CopyTo(entrystream); From ec73c958c97b677ae02fcdfd064eb0411224e437 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 18:03:24 +0300 Subject: [PATCH 04/14] AddOqtaneParts -> AddOqtane --- Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs | 2 +- Oqtane.Server/Startup.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index e474ab8f..b274faa5 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -17,7 +17,7 @@ namespace Microsoft.Extensions.DependencyInjection { private static readonly string StalliteAssemblyExtension = ".resources.dll"; - public static IServiceCollection AddOqtaneParts(this IServiceCollection services, Runtime runtime) + public static IServiceCollection AddOqtane(this IServiceCollection services, Runtime runtime) { LoadAssemblies(); LoadSatelliteAssemblies(); diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index ee53f340..f70397bb 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -197,7 +197,7 @@ namespace Oqtane services.AddTransient(); // load the external assemblies into the app domain, install services - services.AddOqtaneParts(_runtime); + services.AddOqtane(_runtime); services.AddMvc() .AddNewtonsoftJson() From accf947afd649516b5cf0e5df5e808ab7ab22c9b Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 18:28:02 +0300 Subject: [PATCH 05/14] LoadClientAssemblies adds satellite assemblies --- Oqtane.Client/Program.cs | 37 +++++++++++-------- .../OqtaneServiceCollectionExtensions.cs | 5 +-- Oqtane.Shared/Shared/Constants.cs | 2 + 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index 8a1fd2b8..f0218d64 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -1,19 +1,18 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; -using System.Threading.Tasks; -using Oqtane.Services; -using System.Reflection; -using System; +using System; using System.Collections.Generic; +using System.IO; +using System.IO.Compression; using System.Linq; using System.Net.Http; -using System.Net.Http.Json; -using Oqtane.Modules; -using Oqtane.Shared; -using Oqtane.Providers; +using System.Reflection; +using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Authorization; -using System.IO.Compression; -using System.IO; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Modules; +using Oqtane.Providers; +using Oqtane.Shared; +using Oqtane.Services; namespace Oqtane.Client { @@ -101,8 +100,8 @@ namespace Oqtane.Client // asemblies and debug symbols are packaged in a zip file using (ZipArchive archive = new ZipArchive(new MemoryStream(zip))) { - Dictionary dlls = new Dictionary(); - Dictionary pdbs = new Dictionary(); + var dlls = new Dictionary(); + var pdbs = new Dictionary(); foreach (ZipArchiveEntry entry in archive.Entries) { @@ -115,7 +114,15 @@ namespace Oqtane.Client switch (Path.GetExtension(entry.Name)) { case ".dll": - dlls.Add(entry.Name, file); + // Loads the stallite assemblies early + if (entry.Name.EndsWith(Constants.StalliteAssemblyExtension)) + { + Assembly.Load(entry.Name); + } + else + { + dlls.Add(entry.Name, file); + } break; case ".pdb": pdbs.Add(entry.Name, file); diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index b274faa5..a51595ca 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -8,6 +8,7 @@ using Oqtane.Infrastructure; using Oqtane.Infrastructure.Localization; using Oqtane.Modules; using Oqtane.Services; +using Oqtane.Shared; using Oqtane.UI; // ReSharper disable once CheckNamespace @@ -15,8 +16,6 @@ namespace Microsoft.Extensions.DependencyInjection { public static class OqtaneServiceCollectionExtensions { - private static readonly string StalliteAssemblyExtension = ".resources.dll"; - public static IServiceCollection AddOqtane(this IServiceCollection services, Runtime runtime) { LoadAssemblies(); @@ -138,7 +137,7 @@ namespace Microsoft.Extensions.DependencyInjection foreach (var culture in LocalizationSettings.SupportedCultures) { var assembliesFolder = new DirectoryInfo(Path.Combine(assemblyPath, culture)); - foreach (var assemblyFile in assembliesFolder.EnumerateFiles(StalliteAssemblyExtension)) + foreach (var assemblyFile in assembliesFolder.EnumerateFiles(Constants.StalliteAssemblyExtension)) { AssemblyName assemblyName; try diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index c237857d..7b9b3a0e 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -57,5 +57,7 @@ namespace Oqtane.Shared (Char) 28, (Char) 29, (Char) 30, (Char) 31, ':', '*', '?', '\\', '/' }; public static readonly string[] InvalidFileNameEndingChars = { ".", " " }; + + public static readonly string StalliteAssemblyExtension = ".resources.dll"; } } From 7f28c5f2ffc58f197c836ab25ec451ec4a06820e Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 18:28:30 +0300 Subject: [PATCH 06/14] Add localization service to Oqtane.Client --- Oqtane.Client/Oqtane.Client.csproj | 1 + Oqtane.Client/Program.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index bff51215..515bf8f5 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -32,6 +32,7 @@ + diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index f0218d64..e7b6f632 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -27,6 +27,9 @@ namespace Oqtane.Client builder.Services.AddSingleton(httpClient); builder.Services.AddOptions(); + // Register localization services + builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); + // register auth services builder.Services.AddAuthorizationCore(); builder.Services.AddScoped(); From 468327d59764c62b2e1560ba0a69920cf154dfaf Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 18:29:18 +0300 Subject: [PATCH 07/14] Set localization RootNamespace to make it works --- Oqtane.Client/AssemblyInfo.cs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Oqtane.Client/AssemblyInfo.cs diff --git a/Oqtane.Client/AssemblyInfo.cs b/Oqtane.Client/AssemblyInfo.cs new file mode 100644 index 00000000..d598bfb9 --- /dev/null +++ b/Oqtane.Client/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Microsoft.Extensions.Localization; + +[assembly: RootNamespace("Oqtane")] From 52d1d5841e70017e915e6b318f5295f7778ebe1c Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 18:50:06 +0300 Subject: [PATCH 08/14] Avoid looking for en-US culture resources --- Oqtane.Server/Controllers/InstallationController.cs | 11 ++++++++--- .../Extensions/OqtaneServiceCollectionExtensions.cs | 5 +++++ .../Localization/LocalizationSettings.cs | 5 ++--- Oqtane.Shared/Shared/Constants.cs | 2 ++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index f1ef2a8a..940ab6a8 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -76,11 +76,16 @@ namespace Oqtane.Controllers var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); // Get the satellite assemblies - foreach (var cultureFolder in LocalizationSettings.SupportedCultures) + foreach (var culture in LocalizationSettings.SupportedCultures) { - foreach (var resourceFile in Directory.EnumerateFiles(Path.Combine(Path.GetDirectoryName(binFolder), cultureFolder))) + if (culture == Constants.DefaultCulture) { - list.Add(Path.Combine(cultureFolder, Path.GetFileNameWithoutExtension(resourceFile))); + continue; + } + + foreach (var resourceFile in Directory.EnumerateFiles(Path.Combine(Path.GetDirectoryName(binFolder), culture))) + { + list.Add(Path.Combine(culture, Path.GetFileNameWithoutExtension(resourceFile))); } } diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index a51595ca..09776f45 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -136,6 +136,11 @@ namespace Microsoft.Extensions.DependencyInjection foreach (var culture in LocalizationSettings.SupportedCultures) { + if (culture == Constants.DefaultCulture) + { + continue; + } + var assembliesFolder = new DirectoryInfo(Path.Combine(assemblyPath, culture)); foreach (var assemblyFile in assembliesFolder.EnumerateFiles(Constants.StalliteAssemblyExtension)) { diff --git a/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs b/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs index 80811310..0e6ff714 100644 --- a/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs +++ b/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs @@ -1,14 +1,13 @@ using System.Collections.Generic; +using Oqtane.Shared; namespace Oqtane.Infrastructure.Localization { public static class LocalizationSettings { - private const string EnglishCulture = "en-US"; - static LocalizationSettings() { - DefaultCulture = EnglishCulture; + DefaultCulture = Constants.DefaultCulture; SupportedCultures = new List { DefaultCulture }; } diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 7b9b3a0e..a45e2808 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -59,5 +59,7 @@ namespace Oqtane.Shared public static readonly string[] InvalidFileNameEndingChars = { ".", " " }; public static readonly string StalliteAssemblyExtension = ".resources.dll"; + + public static readonly string DefaultCulture = "en-US"; } } From ad9146ead1df7cb04eaacd2eec82821df218bde7 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 19:14:48 +0300 Subject: [PATCH 09/14] Fix stallite assemblies folder path --- Oqtane.Server/Controllers/InstallationController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index 940ab6a8..06c3fb48 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -83,7 +83,7 @@ namespace Oqtane.Controllers continue; } - foreach (var resourceFile in Directory.EnumerateFiles(Path.Combine(Path.GetDirectoryName(binFolder), culture))) + foreach (var resourceFile in Directory.EnumerateFiles(Path.Combine(binFolder, culture))) { list.Add(Path.Combine(culture, Path.GetFileNameWithoutExtension(resourceFile))); } From b52dd571ee7814508c2129432ba7d275db5bc925 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 19:18:56 +0300 Subject: [PATCH 10/14] Fix loading bug --- Oqtane.Client/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index e7b6f632..912720d9 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -120,7 +120,7 @@ namespace Oqtane.Client // Loads the stallite assemblies early if (entry.Name.EndsWith(Constants.StalliteAssemblyExtension)) { - Assembly.Load(entry.Name); + Assembly.Load(file); } else { From 437170671f013a5ee290834ff6148f3ed4fbb9c4 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 20:20:38 +0300 Subject: [PATCH 11/14] Support server-sider localization --- Oqtane.Server/Oqtane.Server.csproj | 1 + Oqtane.Server/Pages/_Host.cshtml | 7 +++++++ Oqtane.Server/Startup.cs | 10 ++++++++++ 3 files changed, 18 insertions(+) diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 44237543..31523442 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -43,6 +43,7 @@ + diff --git a/Oqtane.Server/Pages/_Host.cshtml b/Oqtane.Server/Pages/_Host.cshtml index 0fe00957..e4b6e5a7 100644 --- a/Oqtane.Server/Pages/_Host.cshtml +++ b/Oqtane.Server/Pages/_Host.cshtml @@ -1,10 +1,17 @@ @page "/" @namespace Oqtane.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@using System.Globalization +@using Microsoft.AspNetCore.Localization @using Microsoft.Extensions.Configuration @inject IConfiguration Configuration @model Oqtane.Pages.HostModel +@{ + // Set localization cookie + var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture)); + HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue); +} diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index f70397bb..7de3eceb 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; @@ -16,6 +17,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; using Oqtane.Extensions; using Oqtane.Infrastructure; +using Oqtane.Infrastructure.Localization; using Oqtane.Repository; using Oqtane.Security; using Oqtane.Services; @@ -51,6 +53,10 @@ namespace Oqtane // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + // Register localization services + services.AddLocalization(options => options.ResourcesPath = "Resources"); + CultureInfo.CurrentUICulture = new CultureInfo(LocalizationSettings.DefaultCulture); + services.AddServerSideBlazor(); // setup HttpClient for server side in a client side compatible fashion ( with auth cookie ) @@ -225,6 +231,10 @@ namespace Oqtane } // to allow install middleware it should be moved up app.ConfigureOqtaneAssemblies(env); + + app.UseRequestLocalization(options => options + .AddSupportedUICultures(LocalizationSettings.SupportedCultures.ToArray())); + app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseBlazorFrameworkFiles(); From 2924e7849fe916484cf0e68e253eb5c06333be6a Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 21:23:22 +0300 Subject: [PATCH 12/14] Read supported cultures from appsettings.json --- .../ApplicationBuilderExtensions.cs | 29 +++++++++++++++++++ .../Localization/LocalizationSettings.cs | 2 +- Oqtane.Server/Startup.cs | 7 ++--- Oqtane.Server/appsettings.json | 4 +++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs index d1c77301..24bd3367 100644 --- a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs @@ -1,14 +1,43 @@ using System; +using System.Globalization; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Oqtane.Infrastructure; +using Oqtane.Infrastructure.Localization; namespace Oqtane.Extensions { public static class ApplicationBuilderExtensions { + private static readonly string DefaultCultureKey = "Localization:DefaultCulture"; + private static readonly string SupportedCulturesKey = "Localization:SupportedCultures"; + + public static IApplicationBuilder UseOqtaneLocalization(this IApplicationBuilder app) + { + var configuration = app.ApplicationServices.GetService(); + var defaultCulture = configuration.GetSection(DefaultCultureKey).Value; + var supportedCultures = configuration.GetSection(SupportedCulturesKey).Get(); + if (supportedCultures.Length > 0) + { + LocalizationSettings.SupportedCultures.AddRange(supportedCultures); + } + + LocalizationSettings.DefaultCulture = defaultCulture; + CultureInfo.CurrentUICulture = new CultureInfo(defaultCulture); + + app.UseRequestLocalization(options => { + options.SetDefaultCulture(defaultCulture) + .AddSupportedUICultures(supportedCultures) + .AddSupportedUICultures(supportedCultures); + }); + + return app; + } + public static IApplicationBuilder ConfigureOqtaneAssemblies(this IApplicationBuilder app, IWebHostEnvironment env) { var startUps = AppDomain.CurrentDomain diff --git a/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs b/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs index 0e6ff714..38f74295 100644 --- a/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs +++ b/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs @@ -13,6 +13,6 @@ namespace Oqtane.Infrastructure.Localization public static string DefaultCulture { get; set; } - public static IList SupportedCultures { get; set; } + public static List SupportedCultures { get; } } } diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 7de3eceb..256d44a3 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; @@ -17,7 +16,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; using Oqtane.Extensions; using Oqtane.Infrastructure; -using Oqtane.Infrastructure.Localization; using Oqtane.Repository; using Oqtane.Security; using Oqtane.Services; @@ -55,7 +53,6 @@ namespace Oqtane { // Register localization services services.AddLocalization(options => options.ResourcesPath = "Resources"); - CultureInfo.CurrentUICulture = new CultureInfo(LocalizationSettings.DefaultCulture); services.AddServerSideBlazor(); @@ -232,8 +229,8 @@ namespace Oqtane // to allow install middleware it should be moved up app.ConfigureOqtaneAssemblies(env); - app.UseRequestLocalization(options => options - .AddSupportedUICultures(LocalizationSettings.SupportedCultures.ToArray())); + // Allow oqtane localization middleware + app.UseOqtaneLocalization(); app.UseHttpsRedirection(); app.UseStaticFiles(); diff --git a/Oqtane.Server/appsettings.json b/Oqtane.Server/appsettings.json index 0ab499f0..743c6e19 100644 --- a/Oqtane.Server/appsettings.json +++ b/Oqtane.Server/appsettings.json @@ -11,5 +11,9 @@ "DefaultTheme": "", "DefaultLayout": "", "DefaultContainer": "" + }, + "Localization": { + "DefaultCulture": "en-US", + "SupportedCultures": [] } } \ No newline at end of file From f83c1b17419f34c3307c304739ebd32d1f930c8f Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 29 Sep 2020 22:12:03 +0300 Subject: [PATCH 13/14] Use invariant culture by default --- Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs | 6 +++++- Oqtane.Server/appsettings.json | 2 +- Oqtane.Shared/Shared/Constants.cs | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs index 24bd3367..476d8bee 100644 --- a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs @@ -21,12 +21,16 @@ namespace Oqtane.Extensions var configuration = app.ApplicationServices.GetService(); var defaultCulture = configuration.GetSection(DefaultCultureKey).Value; var supportedCultures = configuration.GetSection(SupportedCulturesKey).Get(); + if (defaultCulture == CultureInfo.InstalledUICulture.Name) + { + LocalizationSettings.DefaultCulture = defaultCulture; + } + if (supportedCultures.Length > 0) { LocalizationSettings.SupportedCultures.AddRange(supportedCultures); } - LocalizationSettings.DefaultCulture = defaultCulture; CultureInfo.CurrentUICulture = new CultureInfo(defaultCulture); app.UseRequestLocalization(options => { diff --git a/Oqtane.Server/appsettings.json b/Oqtane.Server/appsettings.json index 743c6e19..1af87b4c 100644 --- a/Oqtane.Server/appsettings.json +++ b/Oqtane.Server/appsettings.json @@ -13,7 +13,7 @@ "DefaultContainer": "" }, "Localization": { - "DefaultCulture": "en-US", + "DefaultCulture": "", "SupportedCultures": [] } } \ No newline at end of file diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index a45e2808..db62f416 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace Oqtane.Shared { @@ -60,6 +61,6 @@ namespace Oqtane.Shared public static readonly string StalliteAssemblyExtension = ".resources.dll"; - public static readonly string DefaultCulture = "en-US"; + public static readonly string DefaultCulture = CultureInfo.InstalledUICulture.Name; } } From 2e2d46996ade6991431a1125b597ecb02ac23a33 Mon Sep 17 00:00:00 2001 From: hishamco Date: Wed, 30 Sep 2020 00:07:00 +0300 Subject: [PATCH 14/14] Refactoring --- .../Controllers/InstallationController.cs | 7 +-- .../ApplicationBuilderExtensions.cs | 48 +++++++----------- .../OqtaneServiceCollectionExtensions.cs | 49 ++++++++++--------- .../Interfaces/ILocalizationManager.cs | 9 ++++ .../Localization/LocalizationOptions.cs | 9 ++++ .../Localization/LocalizationSettings.cs | 18 ------- .../Infrastructure/LocalizationManager.cs | 29 +++++++++++ Oqtane.Server/Startup.cs | 6 +++ .../Extensions/EnumerableExtensions.cs | 8 +++ Oqtane.Shared/Shared/ServiceActivator.cs | 22 +++++++++ 10 files changed, 130 insertions(+), 75 deletions(-) create mode 100644 Oqtane.Server/Infrastructure/Interfaces/ILocalizationManager.cs create mode 100644 Oqtane.Server/Infrastructure/Localization/LocalizationOptions.cs delete mode 100644 Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs create mode 100644 Oqtane.Server/Infrastructure/LocalizationManager.cs create mode 100644 Oqtane.Shared/Extensions/EnumerableExtensions.cs create mode 100644 Oqtane.Shared/Shared/ServiceActivator.cs diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index 06c3fb48..d3529a98 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Oqtane.Infrastructure; -using Oqtane.Infrastructure.Localization; using Oqtane.Models; using Oqtane.Modules; using Oqtane.Shared; @@ -21,12 +20,14 @@ namespace Oqtane.Controllers private readonly IConfigurationRoot _config; private readonly IInstallationManager _installationManager; private readonly IDatabaseManager _databaseManager; + private readonly ILocalizationManager _localizationManager; - public InstallationController(IConfigurationRoot config, IInstallationManager installationManager, IDatabaseManager databaseManager) + public InstallationController(IConfigurationRoot config, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager) { _config = config; _installationManager = installationManager; _databaseManager = databaseManager; + _localizationManager = localizationManager; } // POST api/ @@ -76,7 +77,7 @@ namespace Oqtane.Controllers var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); // Get the satellite assemblies - foreach (var culture in LocalizationSettings.SupportedCultures) + foreach (var culture in _localizationManager.GetSupportedCultures()) { if (culture == Constants.DefaultCulture) { diff --git a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs index 476d8bee..67676d16 100644 --- a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs @@ -4,44 +4,13 @@ using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Oqtane.Infrastructure; -using Oqtane.Infrastructure.Localization; namespace Oqtane.Extensions { public static class ApplicationBuilderExtensions { - private static readonly string DefaultCultureKey = "Localization:DefaultCulture"; - private static readonly string SupportedCulturesKey = "Localization:SupportedCultures"; - - public static IApplicationBuilder UseOqtaneLocalization(this IApplicationBuilder app) - { - var configuration = app.ApplicationServices.GetService(); - var defaultCulture = configuration.GetSection(DefaultCultureKey).Value; - var supportedCultures = configuration.GetSection(SupportedCulturesKey).Get(); - if (defaultCulture == CultureInfo.InstalledUICulture.Name) - { - LocalizationSettings.DefaultCulture = defaultCulture; - } - - if (supportedCultures.Length > 0) - { - LocalizationSettings.SupportedCultures.AddRange(supportedCultures); - } - - CultureInfo.CurrentUICulture = new CultureInfo(defaultCulture); - - app.UseRequestLocalization(options => { - options.SetDefaultCulture(defaultCulture) - .AddSupportedUICultures(supportedCultures) - .AddSupportedUICultures(supportedCultures); - }); - - return app; - } - public static IApplicationBuilder ConfigureOqtaneAssemblies(this IApplicationBuilder app, IWebHostEnvironment env) { var startUps = AppDomain.CurrentDomain @@ -55,5 +24,22 @@ namespace Oqtane.Extensions return app; } + + public static IApplicationBuilder UseOqtaneLocalization(this IApplicationBuilder app) + { + var localizationManager = app.ApplicationServices.GetService(); + var defaultCulture = localizationManager.GetDefaultCulture(); + var supportedCultures = localizationManager.GetSupportedCultures(); + + CultureInfo.CurrentUICulture = new CultureInfo(defaultCulture); + + app.UseRequestLocalization(options => { + options.SetDefaultCulture(defaultCulture) + .AddSupportedUICultures(supportedCultures) + .AddSupportedUICultures(supportedCultures); + }); + + return app; + } } } diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 09776f45..4090be8d 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -5,7 +5,6 @@ using System.Reflection; using System.Runtime.Loader; using Microsoft.Extensions.Hosting; using Oqtane.Infrastructure; -using Oqtane.Infrastructure.Localization; using Oqtane.Modules; using Oqtane.Services; using Oqtane.Shared; @@ -134,35 +133,39 @@ namespace Microsoft.Extensions.DependencyInjection AssemblyLoadContext.Default.Resolving += ResolveDependencies; - foreach (var culture in LocalizationSettings.SupportedCultures) + using (var serviceScope = ServiceActivator.GetScope()) { - if (culture == Constants.DefaultCulture) + var localizationManager = serviceScope.ServiceProvider.GetService(); + foreach (var culture in localizationManager.GetSupportedCultures()) { - continue; - } - - var assembliesFolder = new DirectoryInfo(Path.Combine(assemblyPath, culture)); - foreach (var assemblyFile in assembliesFolder.EnumerateFiles(Constants.StalliteAssemblyExtension)) - { - AssemblyName assemblyName; - try + if (culture == Constants.DefaultCulture) { - assemblyName = AssemblyName.GetAssemblyName(assemblyFile.FullName); - } - catch - { - Console.WriteLine($"Not Satellite Assembly : {assemblyFile.Name}"); continue; } - try + var assembliesFolder = new DirectoryInfo(Path.Combine(assemblyPath, culture)); + foreach (var assemblyFile in assembliesFolder.EnumerateFiles(Constants.StalliteAssemblyExtension)) { - Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(assemblyFile.FullName))); - Console.WriteLine($"Loaded : {assemblyName}"); - } - catch (Exception e) - { - Console.WriteLine($"Failed : {assemblyName}\n{e}"); + AssemblyName assemblyName; + try + { + assemblyName = AssemblyName.GetAssemblyName(assemblyFile.FullName); + } + catch + { + Console.WriteLine($"Not Satellite Assembly : {assemblyFile.Name}"); + continue; + } + + try + { + Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(assemblyFile.FullName))); + Console.WriteLine($"Loaded : {assemblyName}"); + } + catch (Exception e) + { + Console.WriteLine($"Failed : {assemblyName}\n{e}"); + } } } } diff --git a/Oqtane.Server/Infrastructure/Interfaces/ILocalizationManager.cs b/Oqtane.Server/Infrastructure/Interfaces/ILocalizationManager.cs new file mode 100644 index 00000000..fa20eef4 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Interfaces/ILocalizationManager.cs @@ -0,0 +1,9 @@ +namespace Oqtane.Infrastructure +{ + public interface ILocalizationManager + { + string GetDefaultCulture(); + + string[] GetSupportedCultures(); + } +} diff --git a/Oqtane.Server/Infrastructure/Localization/LocalizationOptions.cs b/Oqtane.Server/Infrastructure/Localization/LocalizationOptions.cs new file mode 100644 index 00000000..6330130e --- /dev/null +++ b/Oqtane.Server/Infrastructure/Localization/LocalizationOptions.cs @@ -0,0 +1,9 @@ +namespace Oqtane.Infrastructure +{ + public class LocalizationOptions + { + public string DefaultCulture { get; set; } + + public string[] SupportedCultures { get; set; } + } +} diff --git a/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs b/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs deleted file mode 100644 index 38f74295..00000000 --- a/Oqtane.Server/Infrastructure/Localization/LocalizationSettings.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using Oqtane.Shared; - -namespace Oqtane.Infrastructure.Localization -{ - public static class LocalizationSettings - { - static LocalizationSettings() - { - DefaultCulture = Constants.DefaultCulture; - SupportedCultures = new List { DefaultCulture }; - } - - public static string DefaultCulture { get; set; } - - public static List SupportedCultures { get; } - } -} diff --git a/Oqtane.Server/Infrastructure/LocalizationManager.cs b/Oqtane.Server/Infrastructure/LocalizationManager.cs new file mode 100644 index 00000000..22b4ca79 --- /dev/null +++ b/Oqtane.Server/Infrastructure/LocalizationManager.cs @@ -0,0 +1,29 @@ +using System.Collections; +using Microsoft.Extensions.Options; +using Oqtane.Shared; + +namespace Oqtane.Infrastructure +{ + public class LocalizationManager : ILocalizationManager + { + private static readonly string DefaultCulture = Constants.DefaultCulture; + private static readonly string[] SupportedCultures = new[] { DefaultCulture }; + + private readonly LocalizationOptions _localizationOptions; + + public LocalizationManager(IOptions localizationOptions) + { + _localizationOptions = localizationOptions.Value; + } + + public string GetDefaultCulture() + => string.IsNullOrEmpty(_localizationOptions.DefaultCulture) + ? DefaultCulture + : _localizationOptions.DefaultCulture; + + public string[] GetSupportedCultures() + => _localizationOptions.SupportedCultures.IsNullOrEmpty() + ? SupportedCultures + : _localizationOptions.SupportedCultures; + } +} diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 256d44a3..98d43bcd 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -128,6 +128,8 @@ namespace Oqtane .AddSignInManager() .AddDefaultTokenProviders(); + services.Configure(Configuration.GetSection("Localization")); + services.Configure(options => { // Password settings @@ -190,6 +192,7 @@ namespace Oqtane services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -199,6 +202,9 @@ namespace Oqtane services.AddTransient(); services.AddTransient(); + // TODO: Check if there's a better way instead of building service provider + ServiceActivator.Configure(services.BuildServiceProvider()); + // load the external assemblies into the app domain, install services services.AddOqtane(_runtime); diff --git a/Oqtane.Shared/Extensions/EnumerableExtensions.cs b/Oqtane.Shared/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..a4bdde7c --- /dev/null +++ b/Oqtane.Shared/Extensions/EnumerableExtensions.cs @@ -0,0 +1,8 @@ +namespace System.Collections +{ + public static class EnumerableExtensions + { + public static bool IsNullOrEmpty(this IEnumerable source) + => source == null || source.GetEnumerator().MoveNext() == false; + } +} diff --git a/Oqtane.Shared/Shared/ServiceActivator.cs b/Oqtane.Shared/Shared/ServiceActivator.cs new file mode 100644 index 00000000..5718d174 --- /dev/null +++ b/Oqtane.Shared/Shared/ServiceActivator.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace Oqtane.Shared +{ + public static class ServiceActivator + { + private static IServiceProvider _serviceProvider = null; + + public static void Configure(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public static IServiceScope GetScope(IServiceProvider serviceProvider = null) + { + var provider = serviceProvider ?? _serviceProvider; + + return provider?.GetRequiredService().CreateScope(); + } + } +}