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 15 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
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,11 @@ 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 localizationService = _serviceProvider.GetService<ILocalizationService>();
var cultureSettings = await localizationService.GetCultureSettingsAsync();

using (CultureScope.Create(cultureAspect.Culture, cultureSettings))
{
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 @@ -16,117 +16,122 @@
using OrchardCore.Localization.ViewModels;
using OrchardCore.Settings;

namespace OrchardCore.Localization.Drivers
namespace OrchardCore.Localization.Drivers;

/// <summary>
/// Represents a <see cref="SectionDisplayDriver{TModel,TSection}"/> for the localization settings section in the admin site.
/// </summary>
public class LocalizationSettingsDisplayDriver : SectionDisplayDriver<ISite, LocalizationSettings>
{
/// <summary>
/// Represents a <see cref="SectionDisplayDriver{TModel,TSection}"/> for the localization settings section in the admin site.
/// </summary>
public class LocalizationSettingsDisplayDriver : SectionDisplayDriver<ISite, LocalizationSettings>
public const string GroupId = "localization";
private readonly INotifier _notifier;
private readonly IShellHost _shellHost;
private readonly ShellSettings _shellSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthorizationService _authorizationService;
private readonly ILocalizationService _localizationService;
private readonly IHtmlLocalizer H;
private readonly IStringLocalizer S;

public LocalizationSettingsDisplayDriver(
INotifier notifier,
IShellHost shellHost,
ShellSettings shellSettings,
IHttpContextAccessor httpContextAccessor,
IAuthorizationService authorizationService,
ILocalizationService localizationService,
IHtmlLocalizer<LocalizationSettingsDisplayDriver> h,
IStringLocalizer<LocalizationSettingsDisplayDriver> s
)
{
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 IHtmlLocalizer H;
private readonly IStringLocalizer S;

public LocalizationSettingsDisplayDriver(
INotifier notifier,
IShellHost shellHost,
ShellSettings shellSettings,
IHttpContextAccessor httpContextAccessor,
IAuthorizationService authorizationService,
IHtmlLocalizer<LocalizationSettingsDisplayDriver> h,
IStringLocalizer<LocalizationSettingsDisplayDriver> s
)
{
_notifier = notifier;
_shellHost = shellHost;
_shellSettings = shellSettings;
_httpContextAccessor = httpContextAccessor;
_authorizationService = authorizationService;
H = h;
S = s;
}
_notifier = notifier;
_shellHost = shellHost;
_shellSettings = shellSettings;
_httpContextAccessor = httpContextAccessor;
_authorizationService = authorizationService;
_localizationService = localizationService;
H = h;
S = s;
}

/// <inheritdocs />
public override async Task<IDisplayResult> EditAsync(LocalizationSettings settings, BuildEditorContext context)
/// <inheritdocs />
public override async Task<IDisplayResult> EditAsync(LocalizationSettings settings, BuildEditorContext context)
{
var user = _httpContextAccessor.HttpContext?.User;

if (!await _authorizationService.AuthorizeAsync(user, Permissions.ManageCultures))
{
var user = _httpContextAccessor.HttpContext?.User;
return null;
}

if (!await _authorizationService.AuthorizeAsync(user, Permissions.ManageCultures))
return Initialize<LocalizationSettingsViewModel>("LocalizationSettings_Edit", model =>
{
return null;
}
model.Cultures = CultureInfo.GetCultures(CultureTypes.AllCultures)
.Select(cultureInfo =>
{
return new CultureEntry
{
Supported = settings.SupportedCultures.Contains(cultureInfo.Name, StringComparer.OrdinalIgnoreCase),
CultureInfo = cultureInfo,
IsDefault = String.Equals(settings.DefaultCulture, cultureInfo.Name, StringComparison.OrdinalIgnoreCase)
};
}).ToArray();

return Initialize<LocalizationSettingsViewModel>("LocalizationSettings_Edit", model =>
if (!model.Cultures.Any(x => x.IsDefault))
{
model.Cultures = CultureInfo.GetCultures(CultureTypes.AllCultures)
.Select(cultureInfo =>
{
return new CultureEntry
{
Supported = settings.SupportedCultures.Contains(cultureInfo.Name, StringComparer.OrdinalIgnoreCase),
CultureInfo = cultureInfo,
IsDefault = String.Equals(settings.DefaultCulture, cultureInfo.Name, StringComparison.OrdinalIgnoreCase)
};
}).ToArray();

if (!model.Cultures.Any(x => x.IsDefault))
{
model.Cultures[0].IsDefault = true;
}
}).Location("Content:2").OnGroup(GroupId);
model.Cultures[0].IsDefault = true;
}
}).Location("Content:2").OnGroup(GroupId);
}

/// <inheritdocs />
public override async Task<IDisplayResult> UpdateAsync(LocalizationSettings section, BuildEditorContext context)
{
var user = _httpContextAccessor.HttpContext?.User;

if (!await _authorizationService.AuthorizeAsync(user, Permissions.ManageCultures))
{
return null;
}

/// <inheritdocs />
public override async Task<IDisplayResult> UpdateAsync(LocalizationSettings section, BuildEditorContext context)
if (context.GroupId == GroupId)
{
var user = _httpContextAccessor.HttpContext?.User;
var model = new LocalizationSettingsViewModel();

await context.Updater.TryUpdateModelAsync(model, Prefix);

if (!await _authorizationService.AuthorizeAsync(user, Permissions.ManageCultures))
var supportedCulture = JsonConvert.DeserializeObject<string[]>(model.SupportedCultures);

if (!supportedCulture.Any())
{
return null;
context.Updater.ModelState.AddModelError("SupportedCultures", S["A culture is required"]);
}

if (context.GroupId == GroupId)
if (context.Updater.ModelState.IsValid)
{
var model = new LocalizationSettingsViewModel();

await context.Updater.TryUpdateModelAsync(model, Prefix);

var supportedCulture = JsonConvert.DeserializeObject<string[]>(model.SupportedCultures);
section.CultureSettings = model.CultureSettings;
// Invariant culture name is empty so a null value is bound.
section.DefaultCulture = model.DefaultCulture ?? "";
section.SupportedCultures = supportedCulture;

if (!supportedCulture.Any())
if (!section.SupportedCultures.Contains(section.DefaultCulture))
{
context.Updater.ModelState.AddModelError("SupportedCultures", S["A culture is required"]);
section.DefaultCulture = section.SupportedCultures[0];
}

if (context.Updater.ModelState.IsValid)
{
// Invariant culture name is empty so a null value is bound.
section.DefaultCulture = model.DefaultCulture ?? "";
section.SupportedCultures = supportedCulture;

if (!section.SupportedCultures.Contains(section.DefaultCulture))
{
section.DefaultCulture = section.SupportedCultures[0];
}
// We always release the tenant for the default culture and also supported cultures to take effect
await _shellHost.ReleaseShellContextAsync(_shellSettings);

// We always release the tenant for the default culture and also supported cultures to take effect
await _shellHost.ReleaseShellContextAsync(_shellSettings);
var cultureSettings = await _localizationService.GetCultureSettingsAsync();

// 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))
{
await _notifier.WarningAsync(H["The site has been restarted for the settings to take effect."]);
}
// 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, cultureSettings))
{
await _notifier.WarningAsync(H["The site has been restarted for the settings to take effect."]);
}
}

return await EditAsync(section, context);
}

return await EditAsync(section, context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ namespace OrchardCore.Localization.Models
/// </summary>
public class LocalizationSettings
{
private static string[] DefaultSupportedCultures = new[] { CultureInfo.InstalledUICulture.Name };
private readonly static string[] DefaultSupportedCultures = new[] { CultureInfo.InstalledUICulture.Name };

/// <summary>
/// Creates a new instance of the <see cref="LocalizationSettings"/>.
/// </summary>
public LocalizationSettings()
{
CultureSettings = CultureSettings.Default;
DefaultCulture = CultureInfo.InstalledUICulture.Name;
SupportedCultures = DefaultSupportedCultures;
}

/// <summary>
/// Gets or sets the culture settings of the site.
/// </summary>
public CultureSettings CultureSettings { get; set; }

/// <summary>
/// Gets or sets the default culture of the site.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public LocalizationService(ISiteService siteService)
_siteService = siteService;
}

/// <inheritdocs />
public async Task<CultureSettings> GetCultureSettingsAsync()
{
await InitializeLocalizationSettingsAsync();

return _localizationSettings.CultureSettings;
}

/// <inheritdocs />
public async Task<string> GetDefaultCultureAsync()
{
Expand Down
16 changes: 9 additions & 7 deletions src/OrchardCore.Modules/OrchardCore.Localization/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,17 @@ public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder ro

var defaultCulture = localizationService.GetDefaultCultureAsync().GetAwaiter().GetResult();
var supportedCultures = localizationService.GetSupportedCulturesAsync().GetAwaiter().GetResult();
var useUserSelectedCultureSettings = localizationService.GetCultureSettingsAsync()
.GetAwaiter().GetResult() == CultureSettings.User;

var options = serviceProvider.GetService<IOptions<RequestLocalizationOptions>>().Value;
options.SetDefaultCulture(defaultCulture);
options
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures)
;
var requestLocalizationOptions = serviceProvider.GetService<IOptions<RequestLocalizationOptions>>().Value;

app.UseRequestLocalization(options);
requestLocalizationOptions
.SetDefaultCulture(defaultCulture)
.AddSupportedCultures(supportedCultures, useUserSelectedCultureSettings)
.AddSupportedUICultures(supportedCultures, useUserSelectedCultureSettings);

app.UseRequestLocalization(requestLocalizationOptions);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public class LocalizationSettingsViewModel
[BindNever]
public CultureEntry[] Cultures { get; set; } = Array.Empty<CultureEntry>();

public CultureSettings CultureSettings { get; set; }

/// <summary>
/// Gets or sets all the supported cultures of the site. It also contains the default culture.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@model LocalizationSettingsViewModel
@using OrchardCore.Localization
@using Newtonsoft.Json;
@using System.Globalization;

Expand All @@ -18,6 +19,19 @@
initializeOptionsEditor(document.getElementById('@Html.IdFor(m => m)'), @Html.Raw(supportedCultures), '@defaultCulture', '@selectedCulture', @Html.Raw(allCultures));
</script>

<div class="mb-3">
<label asp-for="CultureSettings">@T["Culture settings"]</label>
<select asp-for="CultureSettings" class="form-select">
<option value="@CultureSettings.Default" selected="@(CultureSettings.Default == Model.CultureSettings)">
@T["Default"]
</option>
<option value="@CultureSettings.User" selected="@(CultureSettings.User == Model.CultureSettings)">
@T["User"]
</option>
</select>
<span class="hint">@T["Use User settings if you want to override the culture settings, otherwise Default."]</span>
</div>

<script type="text/x-template" id="options-table">
<div class="mb-3">
<table class="table border-bottom">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class AdminController : Controller
private readonly INotifier _notifier;
private readonly IAuthorizationService _authorizationService;
private readonly IUpdateModelAccessor _updateModelAccessor;
private readonly ILocalizationService _localizationService;
private readonly IHtmlLocalizer H;

public AdminController(
Expand All @@ -25,13 +26,15 @@ public AdminController(
IAuthorizationService authorizationService,
INotifier notifier,
IHtmlLocalizer<AdminController> h,
ILocalizationService localizationService,
IUpdateModelAccessor updateModelAccessor)
{
_siteSettingsDisplayManager = siteSettingsDisplayManager;
_siteService = siteService;
_notifier = notifier;
_authorizationService = authorizationService;
_updateModelAccessor = updateModelAccessor;
_localizationService = localizationService;
H = h;
}

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

var cultureSettings = await _localizationService.GetCultureSettingsAsync();

// 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, cultureSettings) : null)
{
await _notifier.SuccessAsync(H["Site settings updated successfully."]);
}
Expand Down
Loading