Skip to content

Commit

Permalink
#6793: Adding a content-independent culture selector shape for the fr…
Browse files Browse the repository at this point in the history
…ont-end (#8784)

* Adds a new CultureSelector shape for front-end

* fixed query string culture change

* Moving NameValueCollectionExtensions from Orchard.DynamicForms and Orchard.Localization to Orchard.Framework

* Code styling

* Simplifying UserCultureSelectorController and removing the addition of the culture to the query string

* EOF empty lines and code styling

* Fixing that the main Orchard.Localization should depend on Orchard.Autoroute

* Code styling in LocalizationService

* Updating LocalizationService to not have to use IEnumerable.Single

* Matching culture name matching in LocalizationService culture- and casing-invariant

---------

Co-authored-by: Sergio Navarro <[email protected]>
Co-authored-by: psp589 <[email protected]>
  • Loading branch information
3 people authored Apr 18, 2024
1 parent 0b86413 commit 15cad85
Show file tree
Hide file tree
Showing 13 changed files with 226 additions and 55 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,6 @@
<Compile Include="Migrations.cs" />
<Compile Include="Handlers\ReadFormValuesHandler.cs" />
<Compile Include="Services\FormElementEventHandlerBase.cs" />
<Compile Include="Helpers\NameValueCollectionExtensions.cs" />
<Compile Include="Models\Submission.cs" />
<Compile Include="Permissions.cs" />
<Compile Include="Services\FormService.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,4 +467,4 @@ private static bool IsFormElementType(IElementValidator validator, Type elementT
return validatorElementType == elementType || validatorElementType.IsAssignableFrom(elementType);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Web.Mvc;
using Orchard.Autoroute.Models;
using Orchard.CulturePicker.Services;
using Orchard.Environment.Extensions;
using Orchard.Localization.Providers;
using Orchard.Localization.Services;
using Orchard.Mvc.Extensions;

namespace Orchard.Localization.Controllers {
[OrchardFeature("Orchard.Localization.CultureSelector")]
public class UserCultureSelectorController : Controller {
private readonly ILocalizationService _localizationService;
private readonly ICultureStorageProvider _cultureStorageProvider;
public IOrchardServices Services { get; set; }

public UserCultureSelectorController(
IOrchardServices services,
ILocalizationService localizationService,
ICultureStorageProvider cultureStorageProvider) {
Services = services;
_localizationService = localizationService;
_cultureStorageProvider = cultureStorageProvider;
}

public ActionResult ChangeCulture(string culture) {
if (string.IsNullOrEmpty(culture)) {
throw new ArgumentNullException(culture);
}

var returnUrl = Utils.GetReturnUrl(Services.WorkContext.HttpContext.Request);
if (string.IsNullOrEmpty(returnUrl))
returnUrl = "";

if (_localizationService.TryGetRouteForUrl(returnUrl, out AutoroutePart currentRoutePart)
&& _localizationService.TryFindLocalizedRoute(currentRoutePart.ContentItem, culture, out AutoroutePart localizedRoutePart)) {
returnUrl = localizedRoutePart.Path;
}

_cultureStorageProvider.SetCulture(culture);
if (!returnUrl.StartsWith("~/")) {
returnUrl = "~/" + returnUrl;
}

return this.RedirectLocal(returnUrl);
}
}
}
4 changes: 2 additions & 2 deletions src/Orchard.Web/Modules/Orchard.Localization/Module.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Features:
Orchard.Localization:
Description: Enables localization of content items.
Category: Content
Dependencies: Settings
Dependencies: Settings, Orchard.Autoroute
Name: Content Localization
Orchard.Localization.DateTimeFormat:
Description: Enables PO-based translation of date/time formats and names of days and months.
Expand All @@ -30,4 +30,4 @@ Features:
Description: Enables transliteration of the autoroute slug when creating a piece of content.
Category: Content
Name: URL Transliteration
Dependencies: Orchard.Localization.Transliteration, Orchard.Autoroute
Dependencies: Orchard.Localization.Transliteration
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,11 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Controllers\AdminCultureSelectorController.cs" />
<Compile Include="Extensions\Constants.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Controllers\TransliterationAdminController.cs" />
<Compile Include="Controllers\AdminCultureSelectorController.cs" />
<Compile Include="Controllers\UserCultureSelectorController.cs" />
<Compile Include="Models\TransliterationSpecificationRecord.cs" />
<Compile Include="Providers\ContentLocalizationTokens.cs" />
<Compile Include="Selectors\ContentCultureSelector.cs" />
Expand All @@ -118,6 +119,7 @@
<Compile Include="Services\LocalizationService.cs" />
<Compile Include="Services\TransliterationService.cs" />
<Compile Include="Events\TransliterationSlugEventHandler.cs" />
<Compile Include="Services\Utils.cs" />
<Compile Include="ViewModels\ContentLocalizationsViewModel.cs" />
<Compile Include="ViewModels\EditLocalizationViewModel.cs" />
<Compile Include="ViewModels\CreateTransliterationViewModel.cs" />
Expand Down Expand Up @@ -196,6 +198,9 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\UserCultureSelector.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
Expand Down Expand Up @@ -229,4 +234,4 @@
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Web;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
Expand All @@ -19,7 +18,8 @@ public class CookieCultureSelector : ICultureSelector, ICultureStorageProvider {
private const string AdminCookieName = "OrchardCurrentCulture-Admin";
private const int DefaultExpireTimeYear = 1;

public CookieCultureSelector(IHttpContextAccessor httpContextAccessor,
public CookieCultureSelector(
IHttpContextAccessor httpContextAccessor,
IClock clock,
ShellSettings shellSettings) {
_httpContextAccessor = httpContextAccessor;
Expand All @@ -36,11 +36,10 @@ public void SetCulture(string culture) {

var cookie = new HttpCookie(cookieName, culture) {
Expires = _clock.UtcNow.AddYears(DefaultExpireTimeYear),
Domain = httpContext.Request.IsLocal ? null : httpContext.Request.Url.Host
};

cookie.Domain = !httpContext.Request.IsLocal ? httpContext.Request.Url.Host : null;

if (!String.IsNullOrEmpty(_shellSettings.RequestUrlPrefix)) {
if (!string.IsNullOrEmpty(_shellSettings.RequestUrlPrefix)) {
cookie.Path = GetCookiePath(httpContext);
}

Expand Down Expand Up @@ -73,4 +72,4 @@ private string GetCookiePath(HttpContextBase httpContext) {
return cookiePath;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Orchard.Autoroute.Models;
using Orchard.ContentManagement;
using Orchard.Localization.Models;

Expand All @@ -10,5 +11,7 @@ public interface ILocalizationService : IDependency {
void SetContentCulture(IContent content, string culture);
IEnumerable<LocalizationPart> GetLocalizations(IContent content);
IEnumerable<LocalizationPart> GetLocalizations(IContent content, VersionOptions versionOptions);
bool TryFindLocalizedRoute(ContentItem routableContent, string cultureName, out AutoroutePart localizedRoute);
bool TryGetRouteForUrl(string url, out AutoroutePart route);
}
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,59 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.Autoroute.Models;
using Orchard.Autoroute.Services;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Localization.Models;

namespace Orchard.Localization.Services {
public class LocalizationService : ILocalizationService {
private readonly IContentManager _contentManager;
private readonly ICultureManager _cultureManager;
private readonly IHomeAliasService _homeAliasService;


public LocalizationService(IContentManager contentManager, ICultureManager cultureManager) {
public LocalizationService(IContentManager contentManager, ICultureManager cultureManager, IHomeAliasService homeAliasService) {
_contentManager = contentManager;
_cultureManager = cultureManager;
_homeAliasService = homeAliasService;
}

/// <summary>
/// Warning: Returns only the first item of same culture localizations.
/// </summary>
public LocalizationPart GetLocalizedContentItem(IContent content, string culture) =>
GetLocalizedContentItem(content, culture, null);

public LocalizationPart GetLocalizedContentItem(IContent content, string culture) {
// Warning: Returns only the first of same culture localizations.
return GetLocalizedContentItem(content, culture, null);
}

/// <summary>
/// Warning: Returns only the first item of same culture localizations.
/// </summary>
public LocalizationPart GetLocalizedContentItem(IContent content, string culture, VersionOptions versionOptions) {
var cultureRecord = _cultureManager.GetCultureByName(culture);

if (cultureRecord == null) return null;
if (cultureRecord == null) {
return null;
}

var localized = content.As<LocalizationPart>();

if (localized == null) return null;
if (localized == null) {
return null;
}

var masterContentItemId = localized.HasTranslationGroup ? localized.Record.MasterContentItemId : localized.Id;

// Warning: Returns only the first of same culture localizations.
return _contentManager
.Query<LocalizationPart>(versionOptions, content.ContentItem.ContentType)
.Where<LocalizationPartRecord>(l =>
(l.Id == masterContentItemId || l.MasterContentItemId == masterContentItemId) &&
l.CultureId == cultureRecord.Id)
.Where<LocalizationPartRecord>(localization =>
(localization.Id == masterContentItemId || localization.MasterContentItemId == masterContentItemId)
&& localization.CultureId == cultureRecord.Id)
.Slice(1)
.FirstOrDefault();
}

public string GetContentCulture(IContent content) {
var localized = content.As<LocalizationPart>();

return localized?.Culture == null ?
_cultureManager.GetSiteCulture() :
localized.Culture.Culture;
}
public string GetContentCulture(IContent content) =>
content.As<LocalizationPart>()?.Culture?.Culture ?? _cultureManager.GetSiteCulture();

public void SetContentCulture(IContent content, string culture) {
var localized = content.As<LocalizationPart>();
Expand All @@ -57,11 +63,14 @@ public void SetContentCulture(IContent content, string culture) {
localized.Culture = _cultureManager.GetCultureByName(culture);
}

public IEnumerable<LocalizationPart> GetLocalizations(IContent content) {
// Warning: May contain more than one localization of the same culture.
return GetLocalizations(content, null);
}
/// <summary>
/// Warning: May contain more than one localization of the same culture.
/// </summary>
public IEnumerable<LocalizationPart> GetLocalizations(IContent content) => GetLocalizations(content, null);

/// <summary>
/// Warning: May contain more than one localization of the same culture.
/// </summary>
public IEnumerable<LocalizationPart> GetLocalizations(IContent content, VersionOptions versionOptions) {
if (content.ContentItem.Id == 0) return Enumerable.Empty<LocalizationPart>();

Expand All @@ -76,16 +85,58 @@ public IEnumerable<LocalizationPart> GetLocalizations(IContent content, VersionO
if (localized.HasTranslationGroup) {
int masterContentItemId = localized.MasterContentItem.ContentItem.Id;

query = query.Where<LocalizationPartRecord>(l =>
l.Id != contentItemId && // Exclude the content
(l.Id == masterContentItemId || l.MasterContentItemId == masterContentItemId));
query = query.Where<LocalizationPartRecord>(localization =>
localization.Id != contentItemId && // Exclude the content
(localization.Id == masterContentItemId || localization.MasterContentItemId == masterContentItemId));
}
else {
query = query.Where<LocalizationPartRecord>(l => l.MasterContentItemId == contentItemId);
query = query.Where<LocalizationPartRecord>(localization => localization.MasterContentItemId == contentItemId);
}

// Warning: May contain more than one localization of the same culture.
return query.List().ToList();
}

public bool TryGetRouteForUrl(string url, out AutoroutePart route) {
route = _contentManager.Query<AutoroutePart, AutoroutePartRecord>()
.ForVersion(VersionOptions.Published)
.Where(r => r.DisplayAlias == url)
.List()
.FirstOrDefault();

route = route ?? _homeAliasService.GetHomePage(VersionOptions.Latest).As<AutoroutePart>();

return route != null;
}

public bool TryFindLocalizedRoute(ContentItem routableContent, string cultureName, out AutoroutePart localizedRoute) {
if (!routableContent.Parts.Any(p => p.Is<ILocalizableAspect>())) {
localizedRoute = null;

return false;
}

IEnumerable<LocalizationPart> localizations = GetLocalizations(routableContent, VersionOptions.Published);

ILocalizableAspect localizationPart = null, siteCultureLocalizationPart = null;
foreach (var localization in localizations) {
if (localization.Culture.Culture.Equals(cultureName, StringComparison.InvariantCultureIgnoreCase)) {
localizationPart = localization;

break;
}

if (localization.Culture == null && siteCultureLocalizationPart == null) {
siteCultureLocalizationPart = localization;
}
}

if (localizationPart == null) {
localizationPart = siteCultureLocalizationPart;
}

localizedRoute = localizationPart?.As<AutoroutePart>();

return localizedRoute != null;
}
}
}
}
31 changes: 31 additions & 0 deletions src/Orchard.Web/Modules/Orchard.Localization/Services/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Web;

namespace Orchard.CulturePicker.Services {
public static class Utils {
public static string GetReturnUrl(HttpRequestBase request) {
if (request.UrlReferrer == null) {
return "";
}

string localUrl = GetAppRelativePath(request.UrlReferrer.AbsolutePath, request);
return HttpUtility.UrlDecode(localUrl);
}

public static string GetAppRelativePath(string logicalPath, HttpRequestBase request) {
if (request.ApplicationPath == null) {
return "";
}

logicalPath = logicalPath.ToLower();
string appPath = request.ApplicationPath.ToLower();
if (appPath != "/") {
appPath += "/";
}
else {
return logicalPath.Substring(1);
}

return logicalPath.Replace(appPath, "");
}
}
}
Loading

0 comments on commit 15cad85

Please sign in to comment.