Include all strings from GetAllStrings (#264)
This commit is contained in:
parent
2669c32641
commit
e976c0fa22
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue