Include all strings from GetAllStrings (#264)

This commit is contained in:
Ryan Brandenburg 2016-07-15 10:01:02 -07:00 committed by GitHub
parent 2669c32641
commit e976c0fa22
5 changed files with 280 additions and 76 deletions

View File

@ -0,0 +1,149 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Text;
namespace Microsoft.Extensions.Localization.Internal
{
public class AssemblyResourceStringProvider : IResourceStringProvider
{
private const string AssemblyElementDelimiter = ", ";
private static readonly string[] _assemblyElementDelimiterArray = new[] { AssemblyElementDelimiter };
private static readonly char[] _assemblyEqualDelimiter = new[] { '=' };
private readonly AssemblyWrapper _assembly;
private readonly string _resourceBaseName;
private readonly IResourceNamesCache _resourceNamesCache;
public AssemblyResourceStringProvider(
IResourceNamesCache resourceCache,
AssemblyWrapper resourceAssembly,
string resourceBaseName)
{
_resourceNamesCache = resourceCache;
_assembly = resourceAssembly;
_resourceBaseName = resourceBaseName;
}
private string GetResourceCacheKey(CultureInfo culture)
{
var assemblyName = ApplyCultureToAssembly(culture);
return $"Assembly={assemblyName};resourceName={_resourceBaseName}";
}
private string GetResourceName(CultureInfo culture)
{
var resourceStreamName = _resourceBaseName;
if (!string.IsNullOrEmpty(culture.Name))
{
resourceStreamName += "." + culture.Name;
}
resourceStreamName += ".resources";
return resourceStreamName;
}
private IList<string> ThrowOrNull(CultureInfo culture, bool throwOnMissing)
{
if (throwOnMissing)
{
throw new MissingManifestResourceException(
Resources.FormatLocalization_MissingManifest(GetResourceName(culture)));
}
return null;
}
public IList<string> GetAllResourceStrings(CultureInfo culture, bool throwOnMissing)
{
var cacheKey = GetResourceCacheKey(culture);
return _resourceNamesCache.GetOrAdd(cacheKey, _ =>
{
var assembly = GetAssembly(culture);
if (assembly == null)
{
return ThrowOrNull(culture, throwOnMissing);
}
var resourceStreamName = GetResourceName(culture);
using (var resourceStream = assembly.GetManifestResourceStream(resourceStreamName))
{
if (resourceStream == null)
{
return ThrowOrNull(culture, throwOnMissing);
}
using (var resources = new ResourceReader(resourceStream))
{
var names = new List<string>();
foreach (DictionaryEntry entry in resources)
{
var resourceName = (string)entry.Key;
names.Add(resourceName);
}
return names;
}
}
});
}
protected virtual AssemblyWrapper GetAssembly(CultureInfo culture)
{
var assemblyString = ApplyCultureToAssembly(culture);
Assembly assembly;
try
{
assembly = Assembly.Load(new AssemblyName(assemblyString));
}
catch (FileNotFoundException)
{
return null;
}
return new AssemblyWrapper(assembly);
}
// This is all a workaround for https://github.com/dotnet/coreclr/issues/6123
private string ApplyCultureToAssembly(CultureInfo culture)
{
var builder = new StringBuilder(_assembly.FullName);
var cultureName = string.IsNullOrEmpty(culture.Name) ? "neutral" : culture.Name;
var cultureString = $"Culture={cultureName}";
var cultureStartIndex = _assembly.FullName.IndexOf("Culture", StringComparison.OrdinalIgnoreCase);
if (cultureStartIndex < 0)
{
builder.Append(AssemblyElementDelimiter + cultureString);
}
else
{
var cultureEndIndex = _assembly.FullName.IndexOf(
AssemblyElementDelimiter,
cultureStartIndex,
StringComparison.Ordinal);
var cultureLength = cultureEndIndex - cultureStartIndex;
builder.Remove(cultureStartIndex, cultureLength);
builder.Insert(cultureStartIndex, cultureString);
}
var firstSplit = _assembly.FullName.IndexOf(AssemblyElementDelimiter);
if (firstSplit < 0)
{
//Index of end of Assembly name
firstSplit = _assembly.FullName.Length;
}
builder.Insert(firstSplit, ".resources");
return builder.ToString();
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
namespace Microsoft.Extensions.Localization.Internal
{
public interface IResourceStringProvider
{
IList<string> GetAllResourceStrings(CultureInfo culture, bool throwOnMissing);
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
@ -13,8 +12,8 @@ using Microsoft.Extensions.Localization.Internal;
namespace Microsoft.Extensions.Localization
{
/// <summary>
/// An <see cref="IStringLocalizer"/> that uses the <see cref="System.Resources.ResourceManager"/> and
/// <see cref="System.Resources.ResourceReader"/> to provide localized strings.
/// An <see cref="IStringLocalizer"/> that uses the <see cref="ResourceManager"/> and
/// <see cref="ResourceReader"/> to provide localized strings.
/// </summary>
/// <remarks>This type is thread-safe.</remarks>
public class ResourceManagerStringLocalizer : IStringLocalizer
@ -22,22 +21,29 @@ namespace Microsoft.Extensions.Localization
private readonly ConcurrentDictionary<string, object> _missingManifestCache = new ConcurrentDictionary<string, object>();
private readonly IResourceNamesCache _resourceNamesCache;
private readonly ResourceManager _resourceManager;
private readonly AssemblyWrapper _resourceAssemblyWrapper;
private readonly IResourceStringProvider _resourceStringProvider;
private readonly string _resourceBaseName;
/// <summary>
/// Creates a new <see cref="ResourceManagerStringLocalizer"/>.
/// </summary>
/// <param name="resourceManager">The <see cref="System.Resources.ResourceManager"/> to read strings from.</param>
/// <param name="resourceManager">The <see cref="ResourceManager"/> to read strings from.</param>
/// <param name="resourceAssembly">The <see cref="Assembly"/> that contains the strings as embedded resources.</param>
/// <param name="baseName">The base name of the embedded resource in the <see cref="Assembly"/> that contains the strings.</param>
/// <param name="baseName">The base name of the embedded resource that contains the strings.</param>
/// <param name="resourceNamesCache">Cache of the list of strings for a given resource assembly name.</param>
public ResourceManagerStringLocalizer(
ResourceManager resourceManager,
Assembly resourceAssembly,
string baseName,
IResourceNamesCache resourceNamesCache)
: this(resourceManager, new AssemblyWrapper(resourceAssembly), baseName, resourceNamesCache)
: this(
resourceManager,
new AssemblyResourceStringProvider(
resourceNamesCache,
new AssemblyWrapper(resourceAssembly),
baseName),
baseName,
resourceNamesCache)
{
if (resourceAssembly == null)
{
@ -50,7 +56,7 @@ namespace Microsoft.Extensions.Localization
/// </summary>
public ResourceManagerStringLocalizer(
ResourceManager resourceManager,
AssemblyWrapper resourceAssemblyWrapper,
IResourceStringProvider resourceStringProvider,
string baseName,
IResourceNamesCache resourceNamesCache)
{
@ -59,9 +65,9 @@ namespace Microsoft.Extensions.Localization
throw new ArgumentNullException(nameof(resourceManager));
}
if (resourceAssemblyWrapper == null)
if (resourceStringProvider == null)
{
throw new ArgumentNullException(nameof(resourceAssemblyWrapper));
throw new ArgumentNullException(nameof(resourceStringProvider));
}
if (baseName == null)
@ -74,7 +80,7 @@ namespace Microsoft.Extensions.Localization
throw new ArgumentNullException(nameof(resourceNamesCache));
}
_resourceAssemblyWrapper = resourceAssemblyWrapper;
_resourceStringProvider = resourceStringProvider;
_resourceManager = resourceManager;
_resourceBaseName = baseName;
_resourceNamesCache = resourceNamesCache;
@ -121,12 +127,12 @@ namespace Microsoft.Extensions.Localization
return culture == null
? new ResourceManagerStringLocalizer(
_resourceManager,
_resourceAssemblyWrapper.Assembly,
_resourceStringProvider,
_resourceBaseName,
_resourceNamesCache)
: new ResourceManagerWithCultureStringLocalizer(
_resourceManager,
_resourceAssemblyWrapper.Assembly,
_resourceStringProvider,
_resourceBaseName,
_resourceNamesCache,
culture);
@ -151,14 +157,7 @@ namespace Microsoft.Extensions.Localization
var resourceNames = includeParentCultures
? GetResourceNamesFromCultureHierarchy(culture)
: GetResourceNamesForCulture(culture);
if (resourceNames == null && !includeParentCultures)
{
var resourceStreamName = GetResourceStreamName(culture);
throw new MissingManifestResourceException(
Resources.FormatLocalization_MissingManifest(resourceStreamName));
}
: _resourceStringProvider.GetAllResourceStrings(culture, true);
foreach (var name in resourceNames)
{
@ -209,7 +208,7 @@ namespace Microsoft.Extensions.Localization
while (true)
{
var cultureResourceNames = GetResourceNamesForCulture(currentCulture);
var cultureResourceNames = _resourceStringProvider.GetAllResourceStrings(currentCulture, false);
if (cultureResourceNames != null)
{
@ -236,49 +235,5 @@ namespace Microsoft.Extensions.Localization
return resourceNames;
}
private string GetResourceStreamName(CultureInfo culture)
{
var resourceStreamName = _resourceBaseName;
if (!string.IsNullOrEmpty(culture.Name))
{
resourceStreamName += "." + culture.Name;
}
resourceStreamName += ".resources";
return resourceStreamName;
}
private IList<string> GetResourceNamesForCulture(CultureInfo culture)
{
var resourceStreamName = GetResourceStreamName(culture);
var cacheKey = $"assembly={_resourceAssemblyWrapper.FullName};resourceStreamName={resourceStreamName}";
var cultureResourceNames = _resourceNamesCache.GetOrAdd(cacheKey, _ =>
{
using (var cultureResourceStream = _resourceAssemblyWrapper.GetManifestResourceStream(resourceStreamName))
{
if (cultureResourceStream == null)
{
return null;
}
using (var resources = new ResourceReader(cultureResourceStream))
{
var names = new List<string>();
foreach (DictionaryEntry entry in resources)
{
var resourceName = (string)entry.Key;
names.Add(resourceName);
}
return names;
}
}
});
return cultureResourceNames;
}
}
}

View File

@ -6,12 +6,13 @@ using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Resources;
using Microsoft.Extensions.Localization.Internal;
namespace Microsoft.Extensions.Localization
{
/// <summary>
/// An <see cref="IStringLocalizer"/> that uses the <see cref="System.Resources.ResourceManager"/> and
/// <see cref="System.Resources.ResourceReader"/> to provide localized strings for a specific <see cref="CultureInfo"/>.
/// An <see cref="IStringLocalizer"/> that uses the <see cref="ResourceManager"/> and
/// <see cref="ResourceReader"/> to provide localized strings for a specific <see cref="CultureInfo"/>.
/// </summary>
public class ResourceManagerWithCultureStringLocalizer : ResourceManagerStringLocalizer
{
@ -20,9 +21,53 @@ namespace Microsoft.Extensions.Localization
/// <summary>
/// Creates a new <see cref="ResourceManagerWithCultureStringLocalizer"/>.
/// </summary>
/// <param name="resourceManager">The <see cref="System.Resources.ResourceManager"/> to read strings from.</param>
/// <param name="resourceManager">The <see cref="ResourceManager"/> to read strings from.</param>
/// <param name="resourceStringProvider">The <see cref="IResourceStringProvider"/> that can find the resources.</param>
/// <param name="baseName">The base name of the embedded resource that contains the strings.</param>
/// <param name="resourceNamesCache">Cache of the list of strings for a given resource assembly name.</param>
/// <param name="culture">The specific <see cref="CultureInfo"/> to use.</param>
internal ResourceManagerWithCultureStringLocalizer(
ResourceManager resourceManager,
IResourceStringProvider resourceStringProvider,
string baseName,
IResourceNamesCache resourceNamesCache,
CultureInfo culture)
: base(resourceManager, resourceStringProvider, baseName, resourceNamesCache)
{
if (resourceManager == null)
{
throw new ArgumentNullException(nameof(resourceManager));
}
if (resourceStringProvider == null)
{
throw new ArgumentNullException(nameof(resourceStringProvider));
}
if (baseName == null)
{
throw new ArgumentNullException(nameof(baseName));
}
if (resourceNamesCache == null)
{
throw new ArgumentNullException(nameof(resourceNamesCache));
}
if (culture == null)
{
throw new ArgumentNullException(nameof(culture));
}
_culture = culture;
}
/// <summary>
/// Creates a new <see cref="ResourceManagerWithCultureStringLocalizer"/>.
/// </summary>
/// <param name="resourceManager">The <see cref="ResourceManager"/> to read strings from.</param>
/// <param name="resourceAssembly">The <see cref="Assembly"/> that contains the strings as embedded resources.</param>
/// <param name="baseName">The base name of the embedded resource in the <see cref="Assembly"/> that contains the strings.</param>
/// <param name="baseName">The base name of the embedded resource that contains the strings.</param>
/// <param name="resourceNamesCache">Cache of the list of strings for a given resource assembly name.</param>
/// <param name="culture">The specific <see cref="CultureInfo"/> to use.</param>
public ResourceManagerWithCultureStringLocalizer(

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.IO;
using System.Linq;
@ -21,8 +22,15 @@ namespace Microsoft.Extensions.Localization.Tests
var baseName = "test";
var resourceAssembly = new TestAssemblyWrapper();
var resourceManager = new TestResourceManager(baseName, resourceAssembly.Assembly);
var localizer1 = new ResourceManagerStringLocalizer(resourceManager, resourceAssembly, baseName, resourceNamesCache);
var localizer2 = new ResourceManagerStringLocalizer(resourceManager, resourceAssembly, baseName, resourceNamesCache);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceAssembly, baseName);
var localizer1 = new ResourceManagerStringLocalizer(resourceManager,
resourceStreamManager,
baseName,
resourceNamesCache);
var localizer2 = new ResourceManagerStringLocalizer(resourceManager,
resourceStreamManager,
baseName,
resourceNamesCache);
// Act
for (int i = 0; i < 5; i++)
@ -46,8 +54,19 @@ namespace Microsoft.Extensions.Localization.Tests
var resourceAssembly2 = new TestAssemblyWrapper("Assembly2");
var resourceManager1 = new TestResourceManager(baseName, resourceAssembly1.Assembly);
var resourceManager2 = new TestResourceManager(baseName, resourceAssembly2.Assembly);
var localizer1 = new ResourceManagerStringLocalizer(resourceManager1, resourceAssembly1, baseName, resourceNamesCache);
var localizer2 = new ResourceManagerStringLocalizer(resourceManager2, resourceAssembly2, baseName, resourceNamesCache);
var resourceStreamManager1 = new TestResourceStringProvider(resourceNamesCache, resourceAssembly1, baseName);
var resourceStreamManager2 = new TestResourceStringProvider(resourceNamesCache, resourceAssembly2, baseName);
var localizer1 = new ResourceManagerStringLocalizer(
resourceManager1,
resourceStreamManager1,
baseName,
resourceNamesCache);
var localizer2 = new ResourceManagerStringLocalizer(
resourceManager2,
resourceStreamManager2,
baseName,
resourceNamesCache);
// Act
localizer1.GetAllStrings().ToList();
@ -69,7 +88,12 @@ namespace Microsoft.Extensions.Localization.Tests
var resourceNamesCache = new ResourceNamesCache();
var resourceAssembly = new TestAssemblyWrapper();
var resourceManager = new TestResourceManager(baseName, resourceAssembly.Assembly);
var localizer = new ResourceManagerStringLocalizer(resourceManager, resourceAssembly, baseName, resourceNamesCache);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceAssembly, baseName);
var localizer = new ResourceManagerStringLocalizer(
resourceManager,
resourceStreamManager,
baseName,
resourceNamesCache);
// Act
// We have to access the result so it evaluates.
@ -144,12 +168,30 @@ namespace Microsoft.Extensions.Localization.Tests
public TestResourceManager(string baseName, Assembly assembly)
: base(baseName, assembly)
{
}
public override string GetString(string name, CultureInfo culture) => null;
}
public class TestResourceStringProvider : AssemblyResourceStringProvider
{
private TestAssemblyWrapper _assemblyWrapper;
public TestResourceStringProvider(
IResourceNamesCache resourceCache,
TestAssemblyWrapper assemblyWrapper,
string resourceBaseName)
: base(resourceCache, assemblyWrapper, resourceBaseName)
{
_assemblyWrapper = assemblyWrapper;
}
protected override AssemblyWrapper GetAssembly(CultureInfo culture)
{
return _assemblyWrapper;
}
}
public class TestAssemblyWrapper : AssemblyWrapper
{
private readonly string _name;