diff --git a/demo/RulesEngineEditorServer/Pages/_Host.cshtml b/demo/RulesEngineEditorServer/Pages/_Host.cshtml index cf1d136e..f5f6f032 100644 --- a/demo/RulesEngineEditorServer/Pages/_Host.cshtml +++ b/demo/RulesEngineEditorServer/Pages/_Host.cshtml @@ -1,4 +1,4 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ @@ -38,5 +38,6 @@ + diff --git a/demo/RulesEngineEditorServer/Shared/MainLayout.razor b/demo/RulesEngineEditorServer/Shared/MainLayout.razor index b8542a86..002f15e7 100644 --- a/demo/RulesEngineEditorServer/Shared/MainLayout.razor +++ b/demo/RulesEngineEditorServer/Shared/MainLayout.razor @@ -1,8 +1,9 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ @inherits LayoutComponentBase +@using RulesEngineEditor.Resources
@@ -14,11 +15,13 @@ - Rules Engine Editor + @RulesEngineEditor.Resources.SharedResources.RulesEngineEditorTitle
@@ -37,4 +40,4 @@ { collapseNavMenu = !collapseNavMenu; } -} \ No newline at end of file +} diff --git a/demo/RulesEngineEditorServer/Shared/NavMenu.razor b/demo/RulesEngineEditorServer/Shared/NavMenu.razor index 560f37a1..cfddfe3c 100644 --- a/demo/RulesEngineEditorServer/Shared/NavMenu.razor +++ b/demo/RulesEngineEditorServer/Shared/NavMenu.razor @@ -1,32 +1,33 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ +@using RulesEngineEditor.Resources
diff --git a/demo/RulesEngineEditorServer/Startup.cs b/demo/RulesEngineEditorServer/Startup.cs index ca9167ce..6c4e07fd 100644 --- a/demo/RulesEngineEditorServer/Startup.cs +++ b/demo/RulesEngineEditorServer/Startup.cs @@ -16,6 +16,8 @@ using RulesEngineEditor.Services; using RulesEngineEditor.Data; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Localization; +using System.Globalization; namespace RulesEngineEditorServer { @@ -34,6 +36,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); + services.AddLocalization(options => options.ResourcesPath = "Resources"); services.AddSingleton(); //services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("RulesEngineEditorDB"))); @@ -45,6 +48,14 @@ public void ConfigureServices(IServiceCollection services) // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + var supportedCultures = new[] { new CultureInfo("en-US"), new CultureInfo("zh-CN") }; + var localizationOptions = new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture("en-US"), + SupportedCultures = supportedCultures, + SupportedUICultures = supportedCultures + }; + app.UseRequestLocalization(localizationOptions); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); diff --git a/demo/RulesEngineEditorServer/wwwroot/culture.js b/demo/RulesEngineEditorServer/wwwroot/culture.js new file mode 100644 index 00000000..16d85b6d --- /dev/null +++ b/demo/RulesEngineEditorServer/wwwroot/culture.js @@ -0,0 +1,14 @@ +window.blazorCulture = { + get: function () { + return window.localStorage['BlazorCulture']; + }, + set: function (value) { + window.localStorage['BlazorCulture'] = value; + } +}; + +window.setCultureCookie = function (culture) { + var cookieValue = 'c=' + culture + '|uic=' + culture; + var secure = (location.protocol === 'https:') ? '; secure' : ''; + document.cookie = '.AspNetCore.Culture=' + cookieValue + '; path=/; samesite=lax' + secure; +}; \ No newline at end of file diff --git a/demo/RulesEngineEditorWebAssembly/Program.cs b/demo/RulesEngineEditorWebAssembly/Program.cs index 0a319520..7b73080c 100644 --- a/demo/RulesEngineEditorWebAssembly/Program.cs +++ b/demo/RulesEngineEditorWebAssembly/Program.cs @@ -13,6 +13,8 @@ using RulesEngineEditor.Services; using System.Text.Json; using RulesEngineEditor.Shared; +using Microsoft.JSInterop; +using System.Globalization; namespace RulesEngineEditorWebAssembly { @@ -27,13 +29,24 @@ public static async Task Main(string[] args) builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddRulesEngineEditor(); + builder.Services.AddLocalization(); builder.Services.AddScoped(sp => { return RulesEngineEditor.Shared.RulesEngineJsonSourceContext.Default.Options; }); - await builder.Build().RunAsync(); + var host = builder.Build(); + var js = host.Services.GetRequiredService(); + var cultureName = await js.InvokeAsync("blazorCulture.get"); + if (string.IsNullOrWhiteSpace(cultureName)) + { + cultureName = "en-US"; + } + var culture = new CultureInfo(cultureName); + CultureInfo.DefaultThreadCurrentCulture = culture; + CultureInfo.DefaultThreadCurrentUICulture = culture; + await host.RunAsync(); } } } diff --git a/demo/RulesEngineEditorWebAssembly/RulesEngineEditorWebAssembly.csproj b/demo/RulesEngineEditorWebAssembly/RulesEngineEditorWebAssembly.csproj index a3ff6c62..75647782 100644 --- a/demo/RulesEngineEditorWebAssembly/RulesEngineEditorWebAssembly.csproj +++ b/demo/RulesEngineEditorWebAssembly/RulesEngineEditorWebAssembly.csproj @@ -7,6 +7,7 @@ true true true + true @@ -30,5 +31,6 @@ + \ No newline at end of file diff --git a/demo/RulesEngineEditorWebAssembly/Shared/MainLayout.razor b/demo/RulesEngineEditorWebAssembly/Shared/MainLayout.razor index 1bee1699..128be7d4 100644 --- a/demo/RulesEngineEditorWebAssembly/Shared/MainLayout.razor +++ b/demo/RulesEngineEditorWebAssembly/Shared/MainLayout.razor @@ -1,8 +1,9 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ @inherits LayoutComponentBase +@using RulesEngineEditor.Resources @@ -37,4 +40,4 @@ { collapseNavMenu = !collapseNavMenu; } -} \ No newline at end of file +} diff --git a/demo/RulesEngineEditorWebAssembly/Shared/NavMenu.razor b/demo/RulesEngineEditorWebAssembly/Shared/NavMenu.razor index 4164867d..23fe98d6 100644 --- a/demo/RulesEngineEditorWebAssembly/Shared/NavMenu.razor +++ b/demo/RulesEngineEditorWebAssembly/Shared/NavMenu.razor @@ -1,29 +1,29 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ +@using RulesEngineEditor.Resources
- diff --git a/demo/RulesEngineEditorWebAssembly/wwwroot/culture.js b/demo/RulesEngineEditorWebAssembly/wwwroot/culture.js new file mode 100644 index 00000000..16d85b6d --- /dev/null +++ b/demo/RulesEngineEditorWebAssembly/wwwroot/culture.js @@ -0,0 +1,14 @@ +window.blazorCulture = { + get: function () { + return window.localStorage['BlazorCulture']; + }, + set: function (value) { + window.localStorage['BlazorCulture'] = value; + } +}; + +window.setCultureCookie = function (culture) { + var cookieValue = 'c=' + culture + '|uic=' + culture; + var secure = (location.protocol === 'https:') ? '; secure' : ''; + document.cookie = '.AspNetCore.Culture=' + cookieValue + '; path=/; samesite=lax' + secure; +}; \ No newline at end of file diff --git a/demo/RulesEngineEditorWebAssembly/wwwroot/index.html b/demo/RulesEngineEditorWebAssembly/wwwroot/index.html index 8aa1d016..f451efc6 100644 --- a/demo/RulesEngineEditorWebAssembly/wwwroot/index.html +++ b/demo/RulesEngineEditorWebAssembly/wwwroot/index.html @@ -45,6 +45,7 @@ 🗙
+ diff --git a/src/RulesEngineEditor/Components/CultureSwitcher.razor b/src/RulesEngineEditor/Components/CultureSwitcher.razor new file mode 100644 index 00000000..bcb5ecad --- /dev/null +++ b/src/RulesEngineEditor/Components/CultureSwitcher.razor @@ -0,0 +1,29 @@ +@using Microsoft.JSInterop +@inject IJSRuntime JS +@inject NavigationManager Nav +@code { + private string selectedCulture = System.Globalization.CultureInfo.CurrentUICulture.Name; + private async Task OnChange(ChangeEventArgs e) + { + selectedCulture = e.Value?.ToString() ?? "en-US"; + try + { + await JS.InvokeVoidAsync("blazorCulture.set", selectedCulture); + await JS.InvokeVoidAsync("setCultureCookie", selectedCulture); + } + catch (JSException) + { + // If the culture.js script or functions are unavailable, ignore and continue. + } + catch (Exception) + { + // Swallow unexpected errors from JS interop to avoid breaking navigation. + } + Nav.NavigateTo(Nav.Uri, forceLoad: true); + } +} + \ No newline at end of file diff --git a/src/RulesEngineEditor/Components/InputEditor.razor b/src/RulesEngineEditor/Components/InputEditor.razor index 2fc0ac5f..c5693f12 100644 --- a/src/RulesEngineEditor/Components/InputEditor.razor +++ b/src/RulesEngineEditor/Components/InputEditor.razor @@ -1,34 +1,35 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ @using RulesEngineEditor.Models @inject RulesEngineEditor.Services.WorkflowService WorkflowService +@using RulesEngineEditor.Resources @if (Input != null) { -
Input Name
+
@RulesEngineEditor.Resources.SharedResources.InputName
- +
-
-
Parameters
- - +
@RulesEngineEditor.Resources.SharedResources.Parameters
+ + -
Input Param Name
-
Value
+
@RulesEngineEditor.Resources.SharedResources.InputParamName
+
@RulesEngineEditor.Resources.SharedResources.Value
@if (sort) diff --git a/src/RulesEngineEditor/Components/InputParamEditor.razor b/src/RulesEngineEditor/Components/InputParamEditor.razor index a071097a..6c34cc19 100644 --- a/src/RulesEngineEditor/Components/InputParamEditor.razor +++ b/src/RulesEngineEditor/Components/InputParamEditor.razor @@ -1,19 +1,20 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ @using RulesEngineEditor.Models @inject RulesEngineEditor.Services.WorkflowService WorkflowService +@using RulesEngineEditor.Resources
- +
- +
- - - - + + + +
@@ -56,19 +57,19 @@
-
-
Local Params
+
@RulesEngineEditor.Resources.SharedResources.LocalParams
- +
-
Rules
+
@RulesEngineEditor.Resources.SharedResources.Rules
- +
@@ -135,7 +136,7 @@ rule.LocalParams = new List(); } rule.LocalParams.Insert(0, new ScopedParamData()); - StateHasChanged(); + InvokeAsync(StateHasChanged); } protected override void OnInitialized() @@ -154,6 +155,18 @@ } WorkflowService.WorkflowUpdate(); } + private string LocalizeMessage(string message) + { + if (string.IsNullOrEmpty(message)) return message; + if (message == "Rule was successful.") return RulesEngineEditor.Resources.SharedResources.RuleWasSuccessful; + if (message == "One or more adjust rules failed.") return RulesEngineEditor.Resources.SharedResources.OneOrMoreAdjustRulesFailed; + var m = message; + m = m.Replace("Validation failed:", RulesEngineEditor.Resources.SharedResources.ValidationFailed); + m = m.Replace("Severity: Error", RulesEngineEditor.Resources.SharedResources.SeverityError); + m = m.Replace("Atleast one of Rules or WorkflowsToInject must be not empty", RulesEngineEditor.Resources.SharedResources.RulesOrWorkflowsToInjectRequired); + m = m.Replace("Expression cannot be null or empty when RuleExpressionType is LambdaExpression", RulesEngineEditor.Resources.SharedResources.ExpressionCannotBeNullWhenLambda); + return m; + } private Task OnValueChanged(string value) { if (value == "null") diff --git a/src/RulesEngineEditor/Components/Rules.razor b/src/RulesEngineEditor/Components/Rules.razor index 90db5287..35224c14 100644 --- a/src/RulesEngineEditor/Components/Rules.razor +++ b/src/RulesEngineEditor/Components/Rules.razor @@ -1,13 +1,14 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ @using RulesEngineEditor.Models @inject RulesEngineEditor.Services.WorkflowService WorkflowService +@using RulesEngineEditor.Resources @if (rules != null) { - +   @if (sort) { diff --git a/src/RulesEngineEditor/Components/ScopedParamEditor.razor b/src/RulesEngineEditor/Components/ScopedParamEditor.razor index 21256382..35441c5c 100644 --- a/src/RulesEngineEditor/Components/ScopedParamEditor.razor +++ b/src/RulesEngineEditor/Components/ScopedParamEditor.razor @@ -1,19 +1,20 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ @using RulesEngineEditor.Models @inject RulesEngineEditor.Services.WorkflowService WorkflowService +@using RulesEngineEditor.Resources
- +
- +
+   -
Scoped Param Name
-
Expression
+
@RulesEngineEditor.Resources.SharedResources.ScopedParamName
+
@RulesEngineEditor.Resources.SharedResources.ScopedParamExpression
} @if (sort) diff --git a/src/RulesEngineEditor/Components/WorkflowEditor.razor b/src/RulesEngineEditor/Components/WorkflowEditor.razor index a3c92f13..f3b585bd 100644 --- a/src/RulesEngineEditor/Components/WorkflowEditor.razor +++ b/src/RulesEngineEditor/Components/WorkflowEditor.razor @@ -1,19 +1,20 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ @using RulesEngineEditor.Models @inject RulesEngineEditor.Services.WorkflowService WorkflowService +@using RulesEngineEditor.Resources
- +
-
diff --git a/src/RulesEngineEditor/Pages/RulesEngineEditorPage.razor b/src/RulesEngineEditor/Pages/RulesEngineEditorPage.razor index 991c4f13..62e2257b 100644 --- a/src/RulesEngineEditor/Pages/RulesEngineEditorPage.razor +++ b/src/RulesEngineEditor/Pages/RulesEngineEditorPage.razor @@ -1,18 +1,19 @@ -@* +@* // Copyright (c) Alex Reich. // Licensed under the CC BY 4.0 License. *@ @using RulesEngineEditor.Models @using RulesEngineEditor.Components +@using RulesEngineEditor.Resources @inject NavigationManager NavigationManager @inject RulesEngineEditor.Services.WorkflowService WorkflowService @implements IDisposable
- +
- +

@@ -34,7 +35,7 @@ } @@ -86,13 +87,13 @@ w.WorkflowName == CurrentWorkflowName).ToList())" Class="reeditor_dragdrop">
-
Rules
- +
@RulesEngineEditor.Resources.SharedResources.Rules
+ -
Global Params
+
@RulesEngineEditor.Resources.SharedResources.GlobalParams
- +
@@ -104,13 +105,13 @@ {
-
Rules
- +
@RulesEngineEditor.Resources.SharedResources.Rules
+ -
Global Params
+
@RulesEngineEditor.Resources.SharedResources.GlobalParams
- +
@@ -125,27 +126,27 @@