Use ResourceSets

This commit is contained in:
Ryan Brandenburg 2017-08-30 16:59:55 -07:00
parent 8b03ee8c81
commit 51549e8471
6 changed files with 208 additions and 161 deletions

View File

@ -1,115 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
namespace Microsoft.Extensions.Localization.Internal
{
public class AssemblyResourceStringProvider : IResourceStringProvider
{
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 = new AssemblyName(_assembly.FullName)
{
CultureName = culture.Name
};
return $"Assembly={assemblyName.FullName};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)
{
Assembly assembly;
var assemblyName = new AssemblyName(_assembly.FullName)
{
CultureName = culture.Name
};
try
{
assembly = Assembly.Load(assemblyName);
}
catch (FileNotFoundException)
{
return null;
}
return new AssemblyWrapper(assembly);
}
}
}

View File

@ -0,0 +1,80 @@
// 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;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Resources;
namespace Microsoft.Extensions.Localization.Internal
{
public class ResourceManagerStringProvider : IResourceStringProvider
{
private readonly IResourceNamesCache _resourceNamesCache;
private readonly ResourceManager _resourceManager;
private readonly Assembly _assembly;
private readonly string _resourceBaseName;
public ResourceManagerStringProvider(
IResourceNamesCache resourceCache,
ResourceManager resourceManager,
Assembly assembly,
string baseName)
{
_resourceManager = resourceManager;
_resourceNamesCache = resourceCache;
_assembly = assembly;
_resourceBaseName = baseName;
}
private string GetResourceCacheKey(CultureInfo culture)
{
var resourceName = _resourceManager.BaseName;
return $"Culture={culture.Name};resourceName={resourceName};Assembly={_assembly.FullName}";
}
private string GetResourceName(CultureInfo culture)
{
var resourceStreamName = _resourceBaseName;
if (!string.IsNullOrEmpty(culture.Name))
{
resourceStreamName += "." + culture.Name;
}
resourceStreamName += ".resources";
return resourceStreamName;
}
public IList<string> GetAllResourceStrings(CultureInfo culture, bool throwOnMissing)
{
var cacheKey = GetResourceCacheKey(culture);
return _resourceNamesCache.GetOrAdd(cacheKey, _ =>
{
// We purposly don't dispose the ResourceSet because it causes an ObjectDisposedException when you try to read the values later.
var resourceSet = _resourceManager.GetResourceSet(culture, createIfNotExists: true, tryParents: false);
if (resourceSet == null)
{
if (throwOnMissing)
{
throw new MissingManifestResourceException(Resources.FormatLocalization_MissingManifest(GetResourceName(culture)));
}
else
{
return null;
}
}
var names = new List<string>();
foreach (DictionaryEntry entry in resourceSet)
{
names.Add((string)entry.Key);
}
return names;
});
}
}
}

View File

@ -60,7 +60,11 @@ namespace Microsoft.Extensions.Localization
ILogger logger)
: this(
resourceManager,
new AssemblyResourceStringProvider(resourceNamesCache, resourceAssemblyWrapper, baseName),
new ResourceManagerStringProvider(
resourceNamesCache,
resourceManager,
resourceAssemblyWrapper.Assembly,
baseName),
baseName,
resourceNamesCache,
logger)

View File

@ -0,0 +1,52 @@
// 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;
using System.Linq;
using LocalizationWebsite.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace LocalizationWebsite
{
public class StartupGetAllStrings
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
}
public void Configure(
IApplicationBuilder app,
ILoggerFactory loggerFactory,
IStringLocalizer<Customer> customerStringLocalizer)
{
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = new List<CultureInfo>()
{
new CultureInfo("fr-FR")
},
SupportedUICultures = new List<CultureInfo>()
{
new CultureInfo("fr-FR")
}
});
app.Run(async (context) =>
{
var strings = customerStringLocalizer.GetAllStrings();
await context.Response.WriteAsync(strings.Count().ToString());
await context.Response.WriteAsync(" ");
await context.Response.WriteAsync(string.Join(" ", strings));
});
}
}
}

View File

@ -23,6 +23,15 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
"kr10.00");
}
[Fact]
public Task Localization_GetAllStrings()
{
return RunTest(
typeof(StartupGetAllStrings),
"fr-FR",
"1 Bonjour from Customer in resources folder");
}
[Fact]
public Task Localization_ResourcesInClassLibrary_ReturnLocalizedValue()
{
@ -81,7 +90,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
var response = await client.SendAsync(request);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Contains(expected, await response.Content.ReadAsStringAsync());
Assert.Equal(expected, await response.Content.ReadAsStringAsync());
}
}
}

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;
@ -23,8 +24,12 @@ namespace Microsoft.Extensions.Localization
var resourceNamesCache = new ResourceNamesCache();
var baseName = "test";
var resourceAssembly = new TestAssemblyWrapper();
var resourceManager = new TestResourceManager(baseName, resourceAssembly.Assembly);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceAssembly, baseName);
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
var resourceStreamManager = new TestResourceStringProvider(
resourceNamesCache,
resourceManager,
resourceAssembly.Assembly,
baseName);
var logger = Logger;
var localizer1 = new ResourceManagerStringLocalizer(resourceManager,
resourceStreamManager,
@ -46,7 +51,7 @@ namespace Microsoft.Extensions.Localization
// Assert
var expectedCallCount = GetCultureInfoDepth(CultureInfo.CurrentUICulture);
Assert.Equal(expectedCallCount, resourceAssembly.GetManifestResourceStreamCallCount);
Assert.Equal(expectedCallCount, resourceAssembly.ManifestResourceStreamCallCount);
}
[Fact]
@ -55,12 +60,12 @@ namespace Microsoft.Extensions.Localization
// Arrange
var resourceNamesCache = new ResourceNamesCache();
var baseName = "test";
var resourceAssembly1 = new TestAssemblyWrapper("Assembly1");
var resourceAssembly2 = new TestAssemblyWrapper("Assembly2");
var resourceManager1 = new TestResourceManager(baseName, resourceAssembly1.Assembly);
var resourceManager2 = new TestResourceManager(baseName, resourceAssembly2.Assembly);
var resourceStreamManager1 = new TestResourceStringProvider(resourceNamesCache, resourceAssembly1, baseName);
var resourceStreamManager2 = new TestResourceStringProvider(resourceNamesCache, resourceAssembly2, baseName);
var resourceAssembly1 = new TestAssemblyWrapper(typeof(ResourceManagerStringLocalizerTest));
var resourceAssembly2 = new TestAssemblyWrapper(typeof(ResourceManagerStringLocalizer));
var resourceManager1 = new TestResourceManager(baseName, resourceAssembly1);
var resourceManager2 = new TestResourceManager(baseName, resourceAssembly2);
var resourceStreamManager1 = new TestResourceStringProvider(resourceNamesCache, resourceManager1, resourceAssembly1.Assembly, baseName);
var resourceStreamManager2 = new TestResourceStringProvider(resourceNamesCache, resourceManager2, resourceAssembly2.Assembly, baseName);
var logger = Logger;
var localizer1 = new ResourceManagerStringLocalizer(
resourceManager1,
@ -81,8 +86,8 @@ namespace Microsoft.Extensions.Localization
// Assert
var expectedCallCount = GetCultureInfoDepth(CultureInfo.CurrentUICulture);
Assert.Equal(expectedCallCount, resourceAssembly1.GetManifestResourceStreamCallCount);
Assert.Equal(expectedCallCount, resourceAssembly2.GetManifestResourceStreamCallCount);
Assert.Equal(expectedCallCount, resourceAssembly1.ManifestResourceStreamCallCount);
Assert.Equal(expectedCallCount, resourceAssembly2.ManifestResourceStreamCallCount);
}
[Fact]
@ -92,8 +97,8 @@ namespace Microsoft.Extensions.Localization
var baseName = "Resources.TestResource";
var resourceNamesCache = new ResourceNamesCache();
var resourceAssembly = new TestAssemblyWrapper();
var resourceManager = new TestResourceManager(baseName, resourceAssembly.Assembly);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceAssembly, baseName);
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceManager, resourceAssembly.Assembly, baseName);
var logger = Logger;
var localizer = new ResourceManagerStringLocalizer(
resourceManager,
@ -117,8 +122,8 @@ namespace Microsoft.Extensions.Localization
var baseName = "Resources.TestResource";
var resourceNamesCache = new ResourceNamesCache();
var resourceAssembly = new TestAssemblyWrapper();
var resourceManager = new TestResourceManager(baseName, resourceAssembly.Assembly);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceAssembly, baseName);
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceManager, resourceAssembly.Assembly, baseName);
var logger = Logger;
var localizer = new ResourceManagerStringLocalizer(
@ -145,8 +150,8 @@ namespace Microsoft.Extensions.Localization
var baseName = "test";
var resourceNamesCache = new ResourceNamesCache();
var resourceAssembly = new TestAssemblyWrapper();
var resourceManager = new TestResourceManager(baseName, resourceAssembly.Assembly);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceAssembly, baseName);
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceManager, resourceAssembly.Assembly, baseName);
var logger = Logger;
var localizer = new ResourceManagerStringLocalizer(
resourceManager,
@ -172,8 +177,9 @@ namespace Microsoft.Extensions.Localization
// Arrange
var resourceNamesCache = new ResourceNamesCache();
var baseName = "testington";
var resourceAssembly = new TestAssemblyWrapper("Assembly1");
var resourceManager = new TestResourceManager(baseName, resourceAssembly.Assembly);
var resourceAssembly = new TestAssemblyWrapper();
resourceAssembly.HasResources = false;
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
var logger = Logger;
var localizer = new ResourceManagerWithCultureStringLocalizer(
@ -190,10 +196,13 @@ namespace Microsoft.Extensions.Localization
// We have to access the result so it evaluates.
localizer.GetAllStrings(includeParentCultures).ToArray();
});
var expectedTries = includeParentCultures ? 3 : 1;
var expected = includeParentCultures
? "No manifests exist for the current culture."
: $"The manifest 'testington.{CultureInfo.CurrentCulture}.resources' was not found.";
Assert.Equal(expected, exception.Message);
Assert.Equal(expectedTries, resourceAssembly.ManifestResourceStreamCallCount);
}
private static Stream MakeResourceStream()
@ -233,49 +242,57 @@ namespace Microsoft.Extensions.Localization
public class TestResourceManager : ResourceManager
{
public TestResourceManager(string baseName, Assembly assembly)
: base(baseName, assembly)
{
}
private AssemblyWrapper _assemblyWrapper;
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)
public TestResourceManager(string baseName, AssemblyWrapper assemblyWrapper)
: base(baseName, assemblyWrapper.Assembly)
{
_assemblyWrapper = assemblyWrapper;
}
protected override AssemblyWrapper GetAssembly(CultureInfo culture)
public override string GetString(string name, CultureInfo culture) => null;
public override ResourceSet GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
{
var resourceStream = _assemblyWrapper.GetManifestResourceStream(BaseName);
return resourceStream != null ? new ResourceSet(resourceStream) : null;
}
}
public class TestResourceStringProvider : ResourceManagerStringProvider
{
public TestResourceStringProvider(
IResourceNamesCache resourceCache,
TestResourceManager resourceManager,
Assembly assembly,
string resourceBaseName)
: base(resourceCache, resourceManager, assembly, resourceBaseName)
{
return _assemblyWrapper;
}
}
public class TestAssemblyWrapper : AssemblyWrapper
{
public TestAssemblyWrapper(string name = nameof(TestAssemblyWrapper))
: base(typeof(TestAssemblyWrapper).GetTypeInfo().Assembly)
public TestAssemblyWrapper()
: this(typeof(TestAssemblyWrapper))
{
FullName = name;
}
public int GetManifestResourceStreamCallCount { get; private set; }
public TestAssemblyWrapper(Type type)
: base(type.GetTypeInfo().Assembly)
{
}
public override string FullName { get; }
public bool HasResources { get; set; } = true;
public int ManifestResourceStreamCallCount { get; private set; }
public override Stream GetManifestResourceStream(string name)
{
GetManifestResourceStreamCallCount++;
return MakeResourceStream();
ManifestResourceStreamCallCount++;
return HasResources ? MakeResourceStream() : null;
}
}
}