mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
* Manage multiple scopes in localization concurrent dictionary (issue 8318) Co-authored-by: matteo.piovanelli <matteo.piovanelli@laser-group.com>
This commit is contained in:
committed by
GitHub
parent
87477518fa
commit
0d3f3ed9f3
@@ -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>();
|
||||
|
||||
16
src/Orchard/Localization/FormatForScope.cs
Normal file
16
src/Orchard/Localization/FormatForScope.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user