Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CultureInfo should independent from local computer settings (Lombiq Technologies: OCORE-86) #12467

Merged
merged 38 commits into from
Oct 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8d27f7f
CultureInfo should independent from local computer settings
hishamco Sep 21, 2022
13c7e71
LocalizationOptions -> CultureLocalizationOptions
hishamco Sep 21, 2022
513deb6
Ability to set the user selected culture settings via CultureScope
hishamco Sep 21, 2022
09ae038
Add GetCultureSettingsAsync() to ILocalizationService
hishamco Sep 21, 2022
8a73fe4
Fix the build
hishamco Sep 21, 2022
a3943c6
Use ShellScope.Services
hishamco Sep 21, 2022
c19f09f
Fix CultureScopeTests
hishamco Sep 21, 2022
ed03682
Add cultureSettings param
hishamco Sep 22, 2022
a6814bb
Update LocalizationSettings.cs
sebastienros Sep 22, 2022
c147a58
Update LocalizationService.cs
sebastienros Sep 22, 2022
80f35c7
Update Startup.cs
sebastienros Sep 22, 2022
aec12f5
Update CultureScope.cs
sebastienros Sep 22, 2022
6f82230
Update ILocalizationService.cs
sebastienros Sep 22, 2022
7732ae2
Update DefaultLocalizationService.cs
sebastienros Sep 22, 2022
ea11abe
Update CultureScopeTests.cs
sebastienros Sep 22, 2022
7ad6674
Merge remote-tracking branch 'origin/hishamco/culture-info' into hish…
hishamco Sep 24, 2022
33d949e
Bring CultureLocalizationOptions
hishamco Sep 24, 2022
75a6271
Address feedback
hishamco Sep 24, 2022
1ba99ba
Revert changes in localization service
hishamco Sep 24, 2022
7b1d4ad
Update OC.Settings
hishamco Sep 24, 2022
ba30774
Cleanup
hishamco Sep 24, 2022
f09b7a3
Add docs
hishamco Sep 24, 2022
b9d31dc
Rename params
hishamco Sep 26, 2022
df51b5e
Rename section name
hishamco Sep 26, 2022
198aa65
Move CultureOptions registration into AddDefaultServices()
hishamco Sep 26, 2022
1a64d88
Update appsettings.json
hishamco Sep 26, 2022
ba0796b
Simplification
hishamco Sep 29, 2022
464194a
Add missing docs
hishamco Sep 30, 2022
2af3693
Fix useUserOverride value
hishamco Sep 30, 2022
624d69c
Add SetDefaultCulture() extension method
hishamco Sep 30, 2022
021a0e1
Simplification with OrchardCoreRequestLocalizationOptions
hishamco Sep 30, 2022
1400dbd
Don't use optional params in constructor
hishamco Sep 30, 2022
7139293
Fix the build
hishamco Sep 30, 2022
d716912
Address feedback
hishamco Sep 30, 2022
5c56161
Remove extra line
hishamco Oct 1, 2022
c76fc25
Fix typo
hishamco Oct 1, 2022
01ed694
Refer to the issue in the remarks tag
hishamco Oct 1, 2022
1abcbf0
Address feedback
hishamco Oct 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/OrchardCore.Cms.Web/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@
// "TenantId": "",
// "CallbackPath": "/signin-oidc",
// "SaveTokens": false
//},
//"OrchardCore_Localization_CultureOptions": {
// "IgnoreSystemSettings": true
//}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,10 @@ private async Task GenerateContainerPathFromPatternAsync(AutoroutePart part)
_contentManager ??= _serviceProvider.GetRequiredService<IContentManager>();

var cultureAspect = await _contentManager.PopulateAspectAsync(part.ContentItem, new CultureAspect());
using (CultureScope.Create(cultureAspect.Culture))

var cultureOptions = _serviceProvider.GetService<IOptions<CultureOptions>>().Value;

using (CultureScope.Create(cultureAspect.Culture, ignoreSystemSettings: cultureOptions.IgnoreSystemSettings))
{
part.Path = await _liquidTemplateManager.RenderStringAsync(pattern, NullEncoder.Default, model,
new Dictionary<string, FluidValue>() { [nameof(ContentItem)] = new ObjectValue(model.ContentItem) });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using OrchardCore.DisplayManagement.Entities;
using OrchardCore.DisplayManagement.Handlers;
Expand All @@ -24,11 +25,12 @@ namespace OrchardCore.Localization.Drivers
public class LocalizationSettingsDisplayDriver : SectionDisplayDriver<ISite, LocalizationSettings>
{
public const string GroupId = "localization";
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthorizationService _authorizationService;
private readonly INotifier _notifier;
private readonly IShellHost _shellHost;
private readonly ShellSettings _shellSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthorizationService _authorizationService;
private readonly CultureOptions _cultureOptions;
private readonly IHtmlLocalizer H;
private readonly IStringLocalizer S;

Expand All @@ -38,6 +40,7 @@ public LocalizationSettingsDisplayDriver(
ShellSettings shellSettings,
IHttpContextAccessor httpContextAccessor,
IAuthorizationService authorizationService,
IOptions<CultureOptions> cultureOptions,
IHtmlLocalizer<LocalizationSettingsDisplayDriver> h,
IStringLocalizer<LocalizationSettingsDisplayDriver> s
)
Expand All @@ -47,6 +50,7 @@ IStringLocalizer<LocalizationSettingsDisplayDriver> s
_shellSettings = shellSettings;
_httpContextAccessor = httpContextAccessor;
_authorizationService = authorizationService;
_cultureOptions = cultureOptions.Value;
H = h;
S = s;
}
Expand Down Expand Up @@ -119,7 +123,7 @@ public override async Task<IDisplayResult> UpdateAsync(LocalizationSettings sect
await _shellHost.ReleaseShellContextAsync(_shellSettings);

// We create a transient scope with the newly selected culture to create a notification that will use it instead of the previous culture
using (CultureScope.Create(section.DefaultCulture))
using (CultureScope.Create(section.DefaultCulture, ignoreSystemSettings: _cultureOptions.IgnoreSystemSettings))
{
await _notifier.WarningAsync(H["The site has been restarted for the settings to take effect."]);
}
Expand Down
12 changes: 6 additions & 6 deletions src/OrchardCore.Modules/OrchardCore.Localization/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder ro
var defaultCulture = localizationService.GetDefaultCultureAsync().GetAwaiter().GetResult();
var supportedCultures = localizationService.GetSupportedCulturesAsync().GetAwaiter().GetResult();

var options = serviceProvider.GetService<IOptions<RequestLocalizationOptions>>().Value;
options.SetDefaultCulture(defaultCulture);
options
var cultureOptions = serviceProvider.GetService<IOptions<CultureOptions>>().Value;

var requestLocalizationOptions = new OrchardCoreRequestLocalizationOptions(ignoreSystemSettings: cultureOptions.IgnoreSystemSettings)
.SetDefaultCulture(defaultCulture)
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures)
;
.AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(options);
app.UseRequestLocalization(requestLocalizationOptions);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Options;
using OrchardCore.DisplayManagement;
using OrchardCore.DisplayManagement.ModelBinding;
using OrchardCore.DisplayManagement.Notify;
Expand All @@ -17,6 +18,7 @@ public class AdminController : Controller
private readonly INotifier _notifier;
private readonly IAuthorizationService _authorizationService;
private readonly IUpdateModelAccessor _updateModelAccessor;
private readonly CultureOptions _cultureOptions;
private readonly IHtmlLocalizer H;

public AdminController(
Expand All @@ -25,13 +27,15 @@ public AdminController(
IAuthorizationService authorizationService,
INotifier notifier,
IHtmlLocalizer<AdminController> h,
IOptions<CultureOptions> cultureOptions,
IUpdateModelAccessor updateModelAccessor)
{
_siteSettingsDisplayManager = siteSettingsDisplayManager;
_siteService = siteService;
_notifier = notifier;
_authorizationService = authorizationService;
_updateModelAccessor = updateModelAccessor;
_cultureOptions = cultureOptions.Value;
H = h;
}

Expand Down Expand Up @@ -79,9 +83,8 @@ public async Task<IActionResult> IndexPost(string groupId)
{
culture = settings.Value<string>("DefaultCulture");
}

// We create a transient scope with the newly selected culture to create a notification that will use it instead of the previous culture
using (culture != null ? CultureScope.Create(culture) : null)
using (culture != null ? CultureScope.Create(culture, ignoreSystemSettings: _cultureOptions.IgnoreSystemSettings) : null)
{
await _notifier.SuccessAsync(H["Site settings updated successfully."]);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace OrchardCore.Localization;

/// <summary>
/// Represents a programmable options for localization culture.
/// </summary>
public class CultureOptions
{
/// <summary>
/// Gets or sets whether to ignore the system culture settings or not.
hishamco marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <remarks>
/// The current culture should not depend on local computer settings by default.
/// For more information refer to https://github.com/OrchardCMS/OrchardCore/issues/11228
/// </remarks>
public bool IgnoreSystemSettings { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,74 @@

namespace OrchardCore.Localization
{
/// <summary>
/// Represents a scope that you can change the current culture within.
/// </summary>
/// <remarks>
/// The scope disallow the current culture depends on local computer settings by default.
/// For more information refer to https://github.com/OrchardCMS/OrchardCore/issues/11228
/// </remarks>
public sealed class CultureScope : IDisposable
{
private readonly CultureInfo _originalCulture;
private readonly CultureInfo _originalUICulture;

private CultureScope(CultureInfo culture, CultureInfo uiCulture)
private CultureScope(string culture, string uiCulture, bool ignoreSystemSettings)
{
Culture = culture;
UICulture = uiCulture;
var useUserOverride = !ignoreSystemSettings;
Culture = new CultureInfo(culture, useUserOverride);
UICulture = new CultureInfo(uiCulture, useUserOverride);
hishamco marked this conversation as resolved.
Show resolved Hide resolved
_originalCulture = CultureInfo.CurrentCulture;
_originalUICulture = CultureInfo.CurrentUICulture;

SetCultures(culture, uiCulture);
SetCultures(Culture, UICulture);
}

/// <summary>
/// Gets the current culture.
/// </summary>
public CultureInfo Culture { get; }

/// <summary>
/// Get the current UI culture.
/// </summary>
public CultureInfo UICulture { get; }

public static CultureScope Create(string culture) => Create(culture, culture);
/// <summary>
/// Creates a scope with a given culture.
/// </summary>
/// <param name="culture">The culture that will be used within the scope.</param>
/// <param name="ignoreSystemSettings">Whether to ignore the system culture settings or not. Defaults to <c>false</c>.</param>
public static CultureScope Create(string culture, bool ignoreSystemSettings = false)
=> Create(culture, culture, ignoreSystemSettings);

public static CultureScope Create(string culture, string uiCulture)
=> Create(CultureInfo.GetCultureInfo(culture), CultureInfo.GetCultureInfo(uiCulture));
/// <summary>
/// Creates a scope with a given culture.
/// </summary>
/// <param name="culture">The culture that will be used within the scope.</param>
/// <param name="uiCulture">The UI culture that will be used within the scope.</param>
/// <param name="ignoreSystemSettings">Whether to ignore the system culture settings or not. Defaults to <c>false</c>.</param>
public static CultureScope Create(string culture, string uiCulture, bool ignoreSystemSettings = false)
=> new(culture, uiCulture, ignoreSystemSettings);

public static CultureScope Create(CultureInfo culture) => Create(culture, culture);
/// <summary>
/// Creates a scope with a given culture.
/// </summary>
/// <param name="culture">The culture that will be used within the scope.</param>
/// <param name="ignoreSystemSettings">Whether to ignore the system culture settings or not. Defaults to <c>false</c>.</param>
public static CultureScope Create(CultureInfo culture, bool ignoreSystemSettings = false)
=> Create(culture, culture, ignoreSystemSettings);

public static CultureScope Create(CultureInfo culture, CultureInfo uiCulture) => new CultureScope(culture, uiCulture);
/// <summary>
/// Creates a scope with a given culture.
/// </summary>
/// <param name="culture">The culture that will be used within the scope.</param>
/// <param name="uiCulture">The UI culture that will be used within the scope.</param>
/// <param name="ignoreSystemSettings">Whether to ignore the system culture settings or not. Defaults to <c>false</c>.</param>
public static CultureScope Create(CultureInfo culture, CultureInfo uiCulture, bool ignoreSystemSettings = false)
=> new(culture.Name, uiCulture.Name, ignoreSystemSettings);

/// <inheritdoc/>
public void Dispose()
{
SetCultures(_originalCulture, _originalUICulture);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>OrchardCore.Localization</RootNamespace>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using System.Collections.Generic;
using System.Globalization;

namespace OrchardCore.Localization;

/// <summary>
/// Represents an options for the <see cref="RequestLocalizationMiddleware"/> that extends <see cref="RequestLocalizationOptions"/>.
/// </summary>
public class OrchardCoreRequestLocalizationOptions : RequestLocalizationOptions
{
private readonly bool _useUserOverride;

/// <summary>
/// Creates a new <see cref="OrchardCoreRequestLocalizationOptions"/> with default values.
/// </summary>
public OrchardCoreRequestLocalizationOptions() : this(ignoreSystemSettings: false)
{
}

/// <summary>
/// Creates a new <see cref="OrchardCoreRequestLocalizationOptions"/> with default values and ability to ignore system settings.
/// <param name="ignoreSystemSettings">Whether to ignore the system culture settings or not.</param>
/// </summary>
public OrchardCoreRequestLocalizationOptions(bool ignoreSystemSettings) : base()
{
_useUserOverride = !ignoreSystemSettings;
}

/// <inheritdoc/>
public new RequestLocalizationOptions AddSupportedCultures(params string[] cultures)
{
var supportedCultures = new List<CultureInfo>();

foreach (var culture in cultures)
{
supportedCultures.Add(new CultureInfo(culture, _useUserOverride));
}

SupportedCultures = supportedCultures;

return this;
}

/// <inheritdoc/>
public new RequestLocalizationOptions AddSupportedUICultures(params string[] uiCultures)
{
var supportedUICultures = new List<CultureInfo>();
foreach (var culture in uiCultures)
{
supportedUICultures.Add(new CultureInfo(culture, _useUserOverride));
}

SupportedUICultures = supportedUICultures;

return this;
}

/// <inheritdoc/>
public new RequestLocalizationOptions SetDefaultCulture(string defaultCulture)
{
DefaultRequestCulture = new RequestCulture(new CultureInfo(defaultCulture, _useUserOverride));

return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,15 @@ private static void AddDefaultServices(OrchardCoreBuilder builder)

services.AddScoped<IOrchardHelper, DefaultOrchardHelper>();

builder.ConfigureServices(s =>
builder.ConfigureServices((services, serviceProvider) =>
{
s.AddSingleton<LocalLock>();
s.AddSingleton<ILocalLock>(sp => sp.GetRequiredService<LocalLock>());
s.AddSingleton<IDistributedLock>(sp => sp.GetRequiredService<LocalLock>());
services.AddSingleton<LocalLock>();
services.AddSingleton<ILocalLock>(sp => sp.GetRequiredService<LocalLock>());
services.AddSingleton<IDistributedLock>(sp => sp.GetRequiredService<LocalLock>());

var configuration = serviceProvider.GetService<IShellConfiguration>();

services.Configure<CultureOptions>(configuration.GetSection("OrchardCore_Localization_CultureOptions"));
});
}

Expand Down
1 change: 1 addition & 0 deletions test/OrchardCore.Tests/Localization/CultureScopeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ await Assert.ThrowsAsync<Exception>(() =>
throw new Exception("Something goes wrong!!");
}
});

Assert.Equal(culture, CultureInfo.CurrentCulture);
Assert.Equal(uiCulture, CultureInfo.CurrentUICulture);
}
Expand Down