Skip to content

Commit

Permalink
Include all strings from GetAllStrings (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanbrandenburg authored Jul 15, 2016
1 parent 2669c32 commit e976c0f
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,31 +12,38 @@
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
{
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)
{
Expand All @@ -50,7 +56,7 @@ public ResourceManagerStringLocalizer(
/// </summary>
public ResourceManagerStringLocalizer(
ResourceManager resourceManager,
AssemblyWrapper resourceAssemblyWrapper,
IResourceStringProvider resourceStringProvider,
string baseName,
IResourceNamesCache resourceNamesCache)
{
Expand All @@ -59,9 +65,9 @@ public ResourceManagerStringLocalizer(
throw new ArgumentNullException(nameof(resourceManager));
}

if (resourceAssemblyWrapper == null)
if (resourceStringProvider == null)
{
throw new ArgumentNullException(nameof(resourceAssemblyWrapper));
throw new ArgumentNullException(nameof(resourceStringProvider));
}

if (baseName == null)
Expand All @@ -74,7 +80,7 @@ public ResourceManagerStringLocalizer(
throw new ArgumentNullException(nameof(resourceNamesCache));
}

_resourceAssemblyWrapper = resourceAssemblyWrapper;
_resourceStringProvider = resourceStringProvider;
_resourceManager = resourceManager;
_resourceBaseName = baseName;
_resourceNamesCache = resourceNamesCache;
Expand Down Expand Up @@ -121,12 +127,12 @@ public IStringLocalizer WithCulture(CultureInfo culture)
return culture == null
? new ResourceManagerStringLocalizer(
_resourceManager,
_resourceAssemblyWrapper.Assembly,
_resourceStringProvider,
_resourceBaseName,
_resourceNamesCache)
: new ResourceManagerWithCultureStringLocalizer(
_resourceManager,
_resourceAssemblyWrapper.Assembly,
_resourceStringProvider,
_resourceBaseName,
_resourceNamesCache,
culture);
Expand All @@ -151,14 +157,7 @@ protected IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures,

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)
{
Expand Down Expand Up @@ -209,7 +208,7 @@ private IEnumerable<string> GetResourceNamesFromCultureHierarchy(CultureInfo sta
while (true)
{

var cultureResourceNames = GetResourceNamesForCulture(currentCulture);
var cultureResourceNames = _resourceStringProvider.GetAllResourceStrings(currentCulture, false);

if (cultureResourceNames != null)
{
Expand All @@ -236,49 +235,5 @@ private IEnumerable<string> GetResourceNamesFromCultureHierarchy(CultureInfo sta

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;
}
}
}
Loading

0 comments on commit e976c0f

Please sign in to comment.