Issue #8318: Managing multiple scopes in localization concurrent dictionary. (#8334)

* Manage multiple scopes in localization concurrent dictionary (issue 8318)

Co-authored-by: matteo.piovanelli <matteo.piovanelli@laser-group.com>
This commit is contained in:
DiegoMarguerettaz-Laser
2020-09-18 08:40:09 +02:00
committed by GitHub
parent 87477518fa
commit 0d3f3ed9f3
8 changed files with 92 additions and 48 deletions

View File

@@ -5,6 +5,7 @@ using Orchard.Localization;
using Orchard.Localization.Services;
using Orchard.Mvc;
using Orchard.Tests.Stubs;
using System.Collections.Generic;
using System.Web;
namespace Orchard.Tests.Localization {
@@ -17,8 +18,8 @@ namespace Orchard.Tests.Localization {
public void Init() {
var mockLocalizedManager = new Mock<ILocalizedStringManager>();
mockLocalizedManager
.Setup(x => x.GetLocalizedString(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns("foo {0}");
.Setup(x => x.GetLocalizedString(new List<string> { It.IsAny<string>() }, It.IsAny<string>(), It.IsAny<string>()))
.Returns(new FormatForScope("foo {0}", null));
var builder = new ContainerBuilder();
builder.RegisterInstance(new StubCultureSelector("fr-CA")).As<ICultureSelector>();

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Orchard.Localization {
public class FormatForScope {
public FormatForScope(string format, string scope) {
Scope = scope;
Format = format;
}
public string Scope { get; set; }
public string Format { get; set; }
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Autofac;
using Autofac.Core;
@@ -20,18 +22,24 @@ namespace Orchard.Localization {
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) {
var userProperty = FindUserProperty(registration.Activator.LimitType);
if (userProperty != null) {
var scope = registration.Activator.LimitType.FullName;
List<string> scopes = new List<string>();
var type = registration.Activator.LimitType;
// we don't need this behavior on CLR types, so that's an optimization
while (!type.Namespace.Equals("System")) {
scopes.Add(type.FullName);
type = type.BaseType;
}
registration.Activated += (sender, e) => {
if (e.Instance.GetType().FullName != scope) {
return;
}
var localizer = _localizerCache.GetOrAdd(scope, key => LocalizationUtilities.Resolve(e.Context, scope));
userProperty.SetValue(e.Instance, localizer, null);
};
foreach(var scope in scopes) {
registration.Activated += (sender, e) => {
if (e.Instance.GetType().FullName != scope) {
return;
}
var localizer = _localizerCache.GetOrAdd(scope, key => LocalizationUtilities.Resolve(e.Context, scopes));
userProperty.SetValue(e.Instance, localizer, null);
};
}
}
}

View File

@@ -1,19 +1,25 @@
using System.Web.Mvc;
using System.Collections.Generic;
using System.Web.Mvc;
using Autofac;
namespace Orchard.Localization {
public class LocalizationUtilities {
public static Localizer Resolve(WorkContext workContext, string scope) {
return workContext == null ? NullLocalizer.Instance : Resolve(workContext.Resolve<ILifetimeScope>(), scope);
return workContext == null ? NullLocalizer.Instance : Resolve(workContext.Resolve<ILifetimeScope>(), new List<string> { scope });
}
public static Localizer Resolve(ControllerContext controllerContext, string scope) {
var workContext = controllerContext.GetWorkContext();
return Resolve(workContext, scope);
return Resolve(workContext, scope );
}
public static Localizer Resolve(IComponentContext context, string scope) {
var text = context.Resolve<IText>(new NamedParameter("scope", scope));
var text = context.Resolve<IText>(new NamedParameter("scope", new List<string> { scope }));
return text.Get;
}
public static Localizer Resolve(IComponentContext context, IEnumerable<string> scopes) {
var text = context.Resolve<IText>(new NamedParameter("scopes", scopes));
return text.Get;
}
}

View File

@@ -8,6 +8,8 @@ using Orchard.FileSystems.WebSite;
using Orchard.Logging;
using Orchard.Environment.Descriptor.Models;
using System.Linq;
using System.Collections;
using System;
namespace Orchard.Localization.Services {
public class DefaultLocalizedStringManager : ILocalizedStringManager {
@@ -47,28 +49,34 @@ namespace Orchard.Localization.Services {
ILogger Logger { get; set; }
public bool DisableMonitoring { get; set; }
public FormatForScope GetLocalizedString(IEnumerable<string> scopes, string text, string cultureName) {
var culture = LoadCulture(cultureName);
foreach (var scope in scopes) {
string scopedKey = (scope + "|" + text).ToLowerInvariant();
if (culture.Translations.ContainsKey(scopedKey)) {
return new FormatForScope(culture.Translations[scopedKey], scope);
}
}
string genericKey = ("|" + text).ToLowerInvariant();
if (culture.Translations.ContainsKey(genericKey)) {
return new FormatForScope(culture.Translations[genericKey], null);
}
foreach (var scope in scopes) {
string parent_text = GetParentTranslation(scope, text, cultureName);
if (!parent_text.Equals(text)) {
return new FormatForScope(parent_text, scope);
}
}
return new FormatForScope(text, scopes.FirstOrDefault());
}
// This will translate a string into a string in the target cultureName.
// The scope portion is optional, it amounts to the location of the file containing
// the string in case it lives in a view, or the namespace name if the string lives in a binary.
// If the culture doesn't have a translation for the string, it will fallback to the
// parent culture as defined in the .net culture hierarchy. e.g. fr-FR will fallback to fr.
// In case it's not found anywhere, the text is returned as is.
public string GetLocalizedString(string scope, string text, string cultureName) {
var culture = LoadCulture(cultureName);
string scopedKey = (scope + "|" + text).ToLowerInvariant();
if (culture.Translations.ContainsKey(scopedKey)) {
return culture.Translations[scopedKey];
}
string genericKey = ("|" + text).ToLowerInvariant();
if (culture.Translations.ContainsKey(genericKey)) {
return culture.Translations[genericKey];
}
return GetParentTranslation(scope, text, cultureName);
}
private string GetParentTranslation(string scope, string text, string cultureName) {
string scopedKey = (scope + "|" + text).ToLowerInvariant();
string genericKey = ("|" + text).ToLowerInvariant();
@@ -143,8 +151,7 @@ namespace Orchard.Localization.Services {
}
foreach (var theme in _extensionManager.AvailableExtensions()) {
if (DefaultExtensionTypes.IsTheme(theme.ExtensionType) && _shellDescriptor.Features.Any(x => x.Name == theme.Id))
{
if (DefaultExtensionTypes.IsTheme(theme.ExtensionType) && _shellDescriptor.Features.Any(x => x.Name == theme.Id)) {
string themePath = string.Format(ThemesLocalizationFilePathFormat, theme.VirtualPath, culture);
text = _webSiteFolder.ReadFile(themePath);

View File

@@ -1,5 +1,8 @@
namespace Orchard.Localization.Services {
using System.Collections.Generic;
namespace Orchard.Localization.Services {
public interface ILocalizedStringManager : IDependency {
string GetLocalizedString(string scope, string text, string cultureName);
FormatForScope GetLocalizedString(IEnumerable<string> scopes, string text, string cultureName);
}
}

View File

@@ -4,43 +4,45 @@ using Orchard.Localization.Services;
using Orchard.Logging;
using System.Web;
using System.Linq;
using System.Collections.Generic;
namespace Orchard.Localization {
public class Text : IText {
private readonly string _scope;
private readonly IEnumerable<string> _scopes;
private readonly IWorkContextAccessor _workContextAccessor;
private readonly ILocalizedStringManager _localizedStringManager;
public Text(string scope, IWorkContextAccessor workContextAccessor, ILocalizedStringManager localizedStringManager) {
_scope = scope;
public Text(IEnumerable<string> scopes, IWorkContextAccessor workContextAccessor, ILocalizedStringManager localizedStringManager) {
_scopes = scopes;
_workContextAccessor = workContextAccessor;
_localizedStringManager = localizedStringManager;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public LocalizedString Get(string textHint, params object[] args) {
Logger.Debug("{0} localizing '{1}'", _scope, textHint);
Logger.Debug("{0} localizing '{1}'", _scopes.FirstOrDefault(), textHint);
string scope = null;
var workContext = _workContextAccessor.GetContext();
if (workContext != null) {
var currentCulture = workContext.CurrentCulture;
var localizedFormat = _localizedStringManager.GetLocalizedString(_scope, textHint, currentCulture);
FormatForScope localizedFormatScope = _localizedStringManager.GetLocalizedString(_scopes, textHint, currentCulture);
scope = localizedFormatScope.Scope;
// localization arguments are HTML-encoded unless they implement IHtmlString
return args.Length == 0
? new LocalizedString(localizedFormat, _scope, textHint, args)
? new LocalizedString(localizedFormatScope.Format, scope, textHint, args)
: new LocalizedString(
String.Format(GetFormatProvider(currentCulture), localizedFormat, args.Select(Encode).ToArray()),
_scope,
String.Format(GetFormatProvider(currentCulture), localizedFormatScope.Format, args.Select(Encode).ToArray()),
scope,
textHint,
args);
}
return new LocalizedString(textHint, _scope, textHint, args);
return new LocalizedString(textHint, scope, textHint, args);
}
private static IFormatProvider GetFormatProvider(string currentCulture) {

View File

@@ -185,6 +185,7 @@
<Compile Include="Data\Migration\Schema\DropUniqueConstraintCommand.cs" />
<Compile Include="Environment\Extensions\Models\LifecycleStatus.cs" />
<Compile Include="Environment\ShellBuilders\ICompositionStrategy.cs" />
<Compile Include="Localization\FormatForScope.cs" />
<Compile Include="Locking\ILockingProvider.cs" />
<Compile Include="Locking\LockingProvider.cs" />
<Compile Include="Mvc\Updater.cs" />