diff --git a/Localization.sln b/Localization.sln index e641bd0819..3c4a667429 100644 --- a/Localization.sln +++ b/Localization.sln @@ -31,6 +31,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LocalizationWebsite", "test EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Localization.FunctionalTests", "test\Microsoft.AspNetCore.Localization.FunctionalTests\Microsoft.AspNetCore.Localization.FunctionalTests.xproj", "{B1B441BA-3AC8-49F8-850D-E5A178E77DE2}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResourcesClassLibraryWithAttribute", "test\ResourcesClassLibraryWithAttribute\ResourcesClassLibraryWithAttribute.xproj", "{F27639B9-913E-43AF-9D64-BBD98D9A420A}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResourcesClassLibraryNoAttribute", "test\ResourcesClassLibraryNoAttribute\ResourcesClassLibraryNoAttribute.xproj", "{34740578-D5B5-4FB4-AFD4-5E87B5443E20}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -73,6 +77,14 @@ Global {B1B441BA-3AC8-49F8-850D-E5A178E77DE2}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1B441BA-3AC8-49F8-850D-E5A178E77DE2}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1B441BA-3AC8-49F8-850D-E5A178E77DE2}.Release|Any CPU.Build.0 = Release|Any CPU + {F27639B9-913E-43AF-9D64-BBD98D9A420A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F27639B9-913E-43AF-9D64-BBD98D9A420A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F27639B9-913E-43AF-9D64-BBD98D9A420A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F27639B9-913E-43AF-9D64-BBD98D9A420A}.Release|Any CPU.Build.0 = Release|Any CPU + {34740578-D5B5-4FB4-AFD4-5E87B5443E20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34740578-D5B5-4FB4-AFD4-5E87B5443E20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34740578-D5B5-4FB4-AFD4-5E87B5443E20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34740578-D5B5-4FB4-AFD4-5E87B5443E20}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -87,5 +99,7 @@ Global {19A2A931-5C60-47A0-816A-0DC9C4CE5736} = {B723DB83-A670-4BCB-95FB-195361331AD2} {EF6C7431-2FB8-4396-8947-F50F31689AF4} = {B723DB83-A670-4BCB-95FB-195361331AD2} {B1B441BA-3AC8-49F8-850D-E5A178E77DE2} = {B723DB83-A670-4BCB-95FB-195361331AD2} + {F27639B9-913E-43AF-9D64-BBD98D9A420A} = {B723DB83-A670-4BCB-95FB-195361331AD2} + {34740578-D5B5-4FB4-AFD4-5E87B5443E20} = {B723DB83-A670-4BCB-95FB-195361331AD2} EndGlobalSection EndGlobal diff --git a/src/Microsoft.Extensions.Localization/ResourceLocationAttribute.cs b/src/Microsoft.Extensions.Localization/ResourceLocationAttribute.cs new file mode 100644 index 0000000000..5bf281d90e --- /dev/null +++ b/src/Microsoft.Extensions.Localization/ResourceLocationAttribute.cs @@ -0,0 +1,33 @@ +// 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; + +namespace Microsoft.Extensions.Localization +{ + /// + /// Provides the location of resources for an Assembly. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] + public class ResourceLocationAttribute : Attribute + { + /// + /// Creates a new . + /// + /// The location of resources for this Assembly. + public ResourceLocationAttribute(string resourceLocation) + { + if (string.IsNullOrEmpty(resourceLocation)) + { + throw new ArgumentNullException(nameof(resourceLocation)); + } + + ResourceLocation = resourceLocation; + } + + /// + /// The location of resources for this Assembly. + /// + public string ResourceLocation { get; } + } +} diff --git a/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs b/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs index 95c672509c..e488019e87 100644 --- a/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs +++ b/src/Microsoft.Extensions.Localization/ResourceManagerStringLocalizerFactory.cs @@ -14,12 +14,17 @@ namespace Microsoft.Extensions.Localization /// /// An that creates instances of . /// + /// + /// offers multiple ways to set the relative path of + /// resources to be used. They are, in order of precedence: + /// -> -> the project root. + /// public class ResourceManagerStringLocalizerFactory : IStringLocalizerFactory { private readonly IResourceNamesCache _resourceNamesCache = new ResourceNamesCache(); private readonly ConcurrentDictionary _localizerCache = new ConcurrentDictionary(); - private readonly IHostingEnvironment _hostingEnvironment; + private readonly string _applicationName; private readonly string _resourcesRelativePath; /// @@ -41,7 +46,7 @@ namespace Microsoft.Extensions.Localization throw new ArgumentNullException(nameof(localizationOptions)); } - _hostingEnvironment = hostingEnvironment; + _applicationName = hostingEnvironment.ApplicationName; _resourcesRelativePath = localizationOptions.Value.ResourcesPath ?? string.Empty; if (!string.IsNullOrEmpty(_resourcesRelativePath)) { @@ -62,7 +67,7 @@ namespace Microsoft.Extensions.Localization throw new ArgumentNullException(nameof(typeInfo)); } - return GetResourcePrefix(typeInfo, _hostingEnvironment.ApplicationName, _resourcesRelativePath); + return GetResourcePrefix(typeInfo, new AssemblyName(typeInfo.Assembly.FullName).Name, _resourcesRelativePath); } /// @@ -106,9 +111,16 @@ namespace Microsoft.Extensions.Localization throw new ArgumentNullException(nameof(baseResourceName)); } - var locationPath = baseNamespace == _hostingEnvironment.ApplicationName ? - baseNamespace + "." + _resourcesRelativePath : - baseNamespace + "."; + if (string.IsNullOrEmpty(baseNamespace)) + { + throw new ArgumentNullException(nameof(baseNamespace)); + } + + var assemblyName = new AssemblyName(baseNamespace); + var assembly = Assembly.Load(assemblyName); + var resourceLocation = GetResourcePath(assembly); + var locationPath = baseNamespace + "." + resourceLocation; + baseResourceName = locationPath + TrimPrefix(baseResourceName, baseNamespace + "."); return baseResourceName; @@ -129,17 +141,12 @@ namespace Microsoft.Extensions.Localization var typeInfo = resourceSource.GetTypeInfo(); var assembly = typeInfo.Assembly; + var assemblyName = new AssemblyName(assembly.FullName); + var resourcePath = GetResourcePath(assembly); - // Re-root the base name if a resources path is set - var baseName = GetResourcePrefix(typeInfo); + var baseName = GetResourcePrefix(typeInfo, assemblyName.Name, resourcePath); - return _localizerCache.GetOrAdd(baseName, _ => - new ResourceManagerStringLocalizer( - new ResourceManager(baseName, assembly), - assembly, - baseName, - _resourceNamesCache) - ); + return _localizerCache.GetOrAdd(baseName, _ => CreateResourceManagerStringLocalizer(assembly, baseName)); } /// @@ -155,21 +162,71 @@ namespace Microsoft.Extensions.Localization throw new ArgumentNullException(nameof(baseName)); } - location = location ?? _hostingEnvironment.ApplicationName; - - baseName = GetResourcePrefix(baseName, location); + location = location ?? _applicationName; return _localizerCache.GetOrAdd($"B={baseName},L={location}", _ => { - var assembly = Assembly.Load(new AssemblyName(location)); - return new ResourceManagerStringLocalizer( - new ResourceManager(baseName, assembly), - assembly, - baseName, - _resourceNamesCache); + var assemblyName = new AssemblyName(location); + var assembly = Assembly.Load(assemblyName); + baseName = GetResourcePrefix(baseName, location); + + return CreateResourceManagerStringLocalizer(assembly, baseName); }); } + /// Creates a for the given input. + /// The assembly to create a for. + /// The base name of the resource to search for. + /// A for the given and . + /// This method is virtual for testing purposes only. + protected virtual ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer( + Assembly assembly, + string baseName) + { + return new ResourceManagerStringLocalizer( + new ResourceManager(baseName, assembly), + assembly, + baseName, + _resourceNamesCache); + } + + /// + /// Gets the resource prefix used to look up the resource. + /// + /// The general location of the resource. + /// The base name of the resource. + /// The location of the resource within . + /// The resource prefix used to look up the resource. + protected virtual string GetResourcePrefix(string location, string baseName, string resourceLocation) + { + // Re-root the base name if a resources path is set + return location + "." + resourceLocation + TrimPrefix(baseName, location + "."); + } + + /// Gets a from the provided . + /// The assembly to get a from. + /// The associated with the given . + /// This method is protected and virtual for testing purposes only. + protected virtual ResourceLocationAttribute GetResourceLocationAttribute(Assembly assembly) + { + return assembly.GetCustomAttribute(); + } + + private string GetResourcePath(Assembly assembly) + { + var resourceLocationAttribute = GetResourceLocationAttribute(assembly); + + // If we don't have an attribute assume all assemblies use the same resource location. + var resourceLocation = resourceLocationAttribute == null + ? _resourcesRelativePath + : resourceLocationAttribute.ResourceLocation + "."; + resourceLocation = resourceLocation + .Replace(Path.DirectorySeparatorChar, '.') + .Replace(Path.AltDirectorySeparatorChar, '.'); + + return resourceLocation; + } + private static string TrimPrefix(string name, string prefix) { if (name.StartsWith(prefix, StringComparison.Ordinal)) diff --git a/src/Microsoft.Extensions.Localization/project.json b/src/Microsoft.Extensions.Localization/project.json index 890d9c4e72..b0703ae070 100644 --- a/src/Microsoft.Extensions.Localization/project.json +++ b/src/Microsoft.Extensions.Localization/project.json @@ -22,7 +22,8 @@ "Microsoft.AspNetCore.Hosting.Abstractions": "1.1.0-*", "Microsoft.Extensions.DependencyInjection.Abstractions": "1.1.0-*", "Microsoft.Extensions.Localization.Abstractions": "1.1.0-*", - "Microsoft.Extensions.Options": "1.1.0-*" + "Microsoft.Extensions.Options": "1.1.0-*", + "System.Reflection.Extensions": "4.0.1-*" }, "frameworks": { "net451": {}, diff --git a/test/LocalizationWebsite/StartupResourcesInClassLibrary.cs b/test/LocalizationWebsite/StartupResourcesInClassLibrary.cs new file mode 100644 index 0000000000..2f4c42850b --- /dev/null +++ b/test/LocalizationWebsite/StartupResourcesInClassLibrary.cs @@ -0,0 +1,70 @@ +// 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.Reflection; +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 StartupResourcesInClassLibrary + { + public void ConfigureServices(IServiceCollection services) + { + services.AddLocalization(options => options.ResourcesPath = "Resources"); + } + + public void Configure( + IApplicationBuilder app, + ILoggerFactory loggerFactory, + IStringLocalizerFactory stringLocalizerFactory) + { + loggerFactory.AddConsole(minLevel: LogLevel.Warning); + + var supportedCultures = new List() + { + new CultureInfo("en-US"), + new CultureInfo("fr-FR") + }; + + app.UseRequestLocalization(new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture("en-US"), + SupportedCultures = supportedCultures, + SupportedUICultures = supportedCultures + }); + + var noAttributeStringLocalizer = stringLocalizerFactory.Create(typeof(ResourcesClassLibraryNoAttribute.Model)); + var withAttributeStringLocalizer = stringLocalizerFactory.Create(typeof(ResourcesClassLibraryWithAttribute.Model)); + + var noAttributeAssembly = typeof(ResourcesClassLibraryNoAttribute.Model).GetTypeInfo().Assembly; + var noAttributeName = new AssemblyName(noAttributeAssembly.FullName).Name; + var noAttributeNameStringLocalizer = stringLocalizerFactory.Create( + nameof(ResourcesClassLibraryNoAttribute.Model), + noAttributeName); + + var withAttributeAssembly = typeof(ResourcesClassLibraryWithAttribute.Model).GetTypeInfo().Assembly; + var withAttributeName = new AssemblyName(withAttributeAssembly.FullName).Name; + var withAttributeNameStringLocalizer = stringLocalizerFactory.Create( + nameof(ResourcesClassLibraryWithAttribute.Model), + withAttributeName); + + app.Run(async (context) => + { + await context.Response.WriteAsync(noAttributeNameStringLocalizer["Hello"]); + await context.Response.WriteAsync(" "); + await context.Response.WriteAsync(noAttributeStringLocalizer["Hello"]); + await context.Response.WriteAsync(" "); + await context.Response.WriteAsync(withAttributeNameStringLocalizer["Hello"]); + await context.Response.WriteAsync(" "); + await context.Response.WriteAsync(withAttributeStringLocalizer["Hello"]); + }); + } + } +} diff --git a/test/LocalizationWebsite/StartupResourcesInFolder.cs b/test/LocalizationWebsite/StartupResourcesInFolder.cs index fa15bf8fc2..8643382dd9 100644 --- a/test/LocalizationWebsite/StartupResourcesInFolder.cs +++ b/test/LocalizationWebsite/StartupResourcesInFolder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Reflection; using LocalizationWebsite.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -43,6 +44,9 @@ namespace LocalizationWebsite }); var stringLocalizer = stringLocalizerFactory.Create("Test", location: null); + var assembly = typeof(StartupResourcesInFolder).GetTypeInfo().Assembly; + var assemblyName = new AssemblyName(assembly.FullName).Name; + var stringLocalizerExplicitLocation = stringLocalizerFactory.Create("Test", assemblyName); app.Run(async (context) => { @@ -51,6 +55,8 @@ namespace LocalizationWebsite await context.Response.WriteAsync(stringLocalizer["Hello"]); await context.Response.WriteAsync(" "); await context.Response.WriteAsync(custromerStringLocalizer["Hello"]); + await context.Response.WriteAsync(" "); + await context.Response.WriteAsync(stringLocalizerExplicitLocation["Hello"]); }); } } diff --git a/test/LocalizationWebsite/project.json b/test/LocalizationWebsite/project.json index a5a4894f52..c44942c900 100644 --- a/test/LocalizationWebsite/project.json +++ b/test/LocalizationWebsite/project.json @@ -9,7 +9,9 @@ "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*", "Microsoft.Extensions.Configuration.CommandLine": "1.1.0-*", "Microsoft.Extensions.Localization": "1.1.0-*", - "Microsoft.Extensions.Logging.Console": "1.1.0-*" + "Microsoft.Extensions.Logging.Console": "1.1.0-*", + "ResourcesClassLibraryNoAttribute": "1.0.0-*", + "ResourcesClassLibraryWithAttribute": "1.0.0-*" }, "frameworks": { "netcoreapp1.0": { diff --git a/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs b/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs index 0130ff1f87..6ae327ef15 100644 --- a/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs +++ b/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs @@ -12,7 +12,43 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests public class LocalizationTest { private static readonly string _applicationPath = Path.Combine("test", "LocalizationWebsite"); - + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(RuntimeFlavor.Clr, "http://localhost:5070/", RuntimeArchitecture.x64)] + public Task Localization_ResourcesInClassLibrary_ReturnLocalizedValue_Windows( + RuntimeFlavor runtimeFlavor, + string applicationBaseUrl, + RuntimeArchitecture runtimeArchitecture) + { + var testRunner = new TestRunner(_applicationPath); + return testRunner.RunTestAndVerifyResponse( + runtimeFlavor, + runtimeArchitecture, + applicationBaseUrl, + "ResourcesInClassLibrary", + "fr-FR", + "Bonjour from ResourcesClassLibraryNoAttribute Bonjour from ResourcesClassLibraryNoAttribute Bonjour from ResourcesClassLibraryWithAttribute Bonjour from ResourcesClassLibraryWithAttribute"); + } + + [Theory] + [InlineData(RuntimeFlavor.CoreClr, "http://localhost:5071/", RuntimeArchitecture.x64)] + public Task Localization_ResourcesInClassLibrary_ReturnLocalizedValue_AllOS( + RuntimeFlavor runtimeFlavor, + string applicationBaseUrl, + RuntimeArchitecture runtimeArchitecture) + { + var testRunner = new TestRunner(_applicationPath); + return testRunner.RunTestAndVerifyResponse( + runtimeFlavor, + runtimeArchitecture, + applicationBaseUrl, + "ResourcesInClassLibrary", + "fr-FR", + "Bonjour from ResourcesClassLibraryNoAttribute Bonjour from ResourcesClassLibraryNoAttribute Bonjour from ResourcesClassLibraryWithAttribute Bonjour from ResourcesClassLibraryWithAttribute"); + } + [ConditionalTheory] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] @@ -30,7 +66,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests applicationBaseUrl, "ResourcesInFolder", "fr-FR", - "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder"); + "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder"); } [ConditionalTheory] @@ -50,7 +86,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests applicationBaseUrl, "ResourcesInFolder", "fr-FR-test", - "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder"); + "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder"); } [ConditionalTheory] @@ -70,7 +106,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests applicationBaseUrl, "ResourcesInFolder", "fr-FR-test-again-too-deep-to-work", - "Hello Hello Hello"); + "Hello Hello Hello Hello"); } [ConditionalFact] @@ -85,7 +121,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests "http://localhost:5072", "ResourcesInFolder", "fr-FR", - "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder"); + "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder"); } [ConditionalFact] @@ -100,7 +136,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests "http://localhost:5072", "ResourcesInFolder", "fr-FR-test", - "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder"); + "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder"); } [ConditionalFact] @@ -115,7 +151,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests "http://localhost:5073/", "ResourcesInFolder", "fr-FR", - "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder"); + "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder"); } [ConditionalFact] @@ -130,7 +166,7 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests "http://localhost:5073/", "ResourcesInFolder", "fr-FR-test", - "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder"); + "Bonjour from StartupResourcesInFolder Bonjour from Test in resources folder Bonjour from Customer in resources folder Bonjour from Test in resources folder"); } [ConditionalTheory] diff --git a/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs b/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs index e06056af98..70032985a7 100644 --- a/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs +++ b/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs @@ -1,16 +1,78 @@ // 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.IO; using System.Reflection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Options; using Moq; using Xunit; -namespace Microsoft.Extensions.Localization.Test +namespace Microsoft.Extensions.Localization.Tests { + public class TestResourceManagerStringLocalizerFactory : ResourceManagerStringLocalizerFactory + { + private ResourceLocationAttribute _resourceLocationAttribute; + + public Assembly Assembly { get; private set; } + public string BaseName { get; private set; } + + public TestResourceManagerStringLocalizerFactory( + IHostingEnvironment hostingEnvironment, + IOptions localizationOptions, + ResourceLocationAttribute resourceLocationAttribute) + : base(hostingEnvironment, localizationOptions) + { + _resourceLocationAttribute = resourceLocationAttribute; + } + + protected override ResourceLocationAttribute GetResourceLocationAttribute(Assembly assembly) + { + return _resourceLocationAttribute; + } + + protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName) + { + BaseName = baseName; + Assembly = assembly; + + return base.CreateResourceManagerStringLocalizer(assembly, baseName); + } + } + public class ResourceManagerStringLocalizerFactoryTest { + [Fact] + public void Create_OverloadsProduceSameResult() + { + // Arrange + var hostingEnvironment = new Mock(); + hostingEnvironment.SetupGet(a => a.ApplicationName).Returns("TestApplication"); + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + + var resourceLocationAttribute = new ResourceLocationAttribute(Path.Combine("My", "Resources")); + var typeFactory = new TestResourceManagerStringLocalizerFactory( + hostingEnvironment.Object, + options.Object, + resourceLocationAttribute); + var stringFactory = new TestResourceManagerStringLocalizerFactory( + hostingEnvironment.Object, + options.Object, + resourceLocationAttribute); + var type = typeof(ResourceManagerStringLocalizerFactoryTest); + var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName); + + // Act + typeFactory.Create(type); + stringFactory.Create(type.Name, assemblyName.Name); + + // Assert + Assert.Equal(typeFactory.BaseName, stringFactory.BaseName); + Assert.Equal(typeFactory.Assembly.FullName, stringFactory.Assembly.FullName); + } + [Fact] public void Create_FromType_ReturnsCachedResultForSameType() { @@ -49,6 +111,27 @@ namespace Microsoft.Extensions.Localization.Test Assert.NotSame(result1, result2); } + [Fact] + public void Create_FromType_ResourcesPathDirectorySeperatorToDot() + { + // Arrange + var hostingEnvironment = new Mock(); + var locOptions = new LocalizationOptions(); + locOptions.ResourcesPath = Path.Combine("My", "Resources"); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var factory = new TestResourceManagerStringLocalizerFactory( + hostingEnvironment.Object, + options.Object, + resourceLocationAttribute: null); + + // Act + factory.Create(typeof(ResourceManagerStringLocalizerFactoryTest)); + + // Assert + Assert.Equal("Microsoft.Extensions.Localization.Tests.My.Resources." + nameof(ResourceManagerStringLocalizerFactoryTest), factory.BaseName); + } + [Fact] public void Create_FromNameLocation_ReturnsCachedResultForSameNameLocation() { @@ -99,8 +182,8 @@ namespace Microsoft.Extensions.Localization.Test var options = new Mock>(); options.Setup(o => o.Value).Returns(locOptions); var factory = new ResourceManagerStringLocalizerFactory(hostingEnvironment.Object, localizationOptions: options.Object); - var location1 = typeof(ResourceManagerStringLocalizer).GetTypeInfo().Assembly.FullName; - var location2 = typeof(ResourceManagerStringLocalizerFactoryTest).GetTypeInfo().Assembly.FullName; + var location1 = new AssemblyName(typeof(ResourceManagerStringLocalizer).GetTypeInfo().Assembly.FullName).Name; + var location2 = new AssemblyName(typeof(ResourceManagerStringLocalizerFactoryTest).GetTypeInfo().Assembly.FullName).Name; // Act var result1 = factory.Create("baseName", location1); @@ -109,5 +192,46 @@ namespace Microsoft.Extensions.Localization.Test // Assert Assert.NotSame(result1, result2); } + + [Fact] + public void Create_FromNameLocation_ResourcesPathDirectorySeparatorToDot() + { + // Arrange + var hostingEnvironment = new Mock(); + hostingEnvironment.SetupGet(a => a.ApplicationName).Returns("Microsoft.Extensions.Localization.Tests"); + var locOptions = new LocalizationOptions(); + locOptions.ResourcesPath = Path.Combine("My", "Resources"); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var factory = new TestResourceManagerStringLocalizerFactory( + hostingEnvironment.Object, + options.Object, + resourceLocationAttribute: null); + + // Act + var result1 = factory.Create("baseName", location: null); + + // Assert + Assert.Equal("Microsoft.Extensions.Localization.Tests.My.Resources.baseName", factory.BaseName); + } + + [Fact] + public void Create_FromNameLocation_NullLocationUsesApplicationPath() + { + // Arrange + var hostingEnvironment = new Mock(); + hostingEnvironment.SetupGet(a => a.ApplicationName).Returns("Microsoft.Extensions.Localization.Tests"); + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var factory = new ResourceManagerStringLocalizerFactory(hostingEnvironment.Object, localizationOptions: options.Object); + + // Act + var result1 = factory.Create("baseName", location: null); + var result2 = factory.Create("baseName", location: null); + + // Assert + Assert.Same(result1, result2); + } } } diff --git a/test/ResourcesClassLibraryNoAttribute/Model.cs b/test/ResourcesClassLibraryNoAttribute/Model.cs new file mode 100644 index 0000000000..dc85c2440c --- /dev/null +++ b/test/ResourcesClassLibraryNoAttribute/Model.cs @@ -0,0 +1,12 @@ +// 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. + +namespace ResourcesClassLibraryNoAttribute +{ + public class Model + { + public Model() + { + } + } +} diff --git a/test/ResourcesClassLibraryNoAttribute/Properties/AssemblyInfo.cs b/test/ResourcesClassLibraryNoAttribute/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b47aa1a1fd --- /dev/null +++ b/test/ResourcesClassLibraryNoAttribute/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// 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.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ResourcesClassLibraryNoAttribute")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("34740578-d5b5-4fb4-afd4-5e87b5443e20")] diff --git a/test/ResourcesClassLibraryNoAttribute/Resources/Model.resx b/test/ResourcesClassLibraryNoAttribute/Resources/Model.resx new file mode 100644 index 0000000000..af89e6e6ee --- /dev/null +++ b/test/ResourcesClassLibraryNoAttribute/Resources/Model.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bonjour from ResourcesClassLibraryNoAttribute + + \ No newline at end of file diff --git a/test/ResourcesClassLibraryNoAttribute/project.json b/test/ResourcesClassLibraryNoAttribute/project.json new file mode 100644 index 0000000000..1f60f0dd08 --- /dev/null +++ b/test/ResourcesClassLibraryNoAttribute/project.json @@ -0,0 +1,17 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "warningsAsErrors": true, + "keyFile": "../../tools/Key.snk" + }, + "dependencies": { + "NETStandard.Library": "1.6.0" + }, + + "frameworks": { + "net451": {}, + "netstandard1.6": { + "imports": "dnxcore50" + } + } +} diff --git a/test/ResourcesClassLibraryWithAttribute/Model.cs b/test/ResourcesClassLibraryWithAttribute/Model.cs new file mode 100644 index 0000000000..f0007e0139 --- /dev/null +++ b/test/ResourcesClassLibraryWithAttribute/Model.cs @@ -0,0 +1,12 @@ +// 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. + +namespace ResourcesClassLibraryWithAttribute +{ + public class Model + { + public Model() + { + } + } +} diff --git a/test/ResourcesClassLibraryWithAttribute/Properties/AssemblyInfo.cs b/test/ResourcesClassLibraryWithAttribute/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..143b120c3a --- /dev/null +++ b/test/ResourcesClassLibraryWithAttribute/Properties/AssemblyInfo.cs @@ -0,0 +1,24 @@ +// 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.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Extensions.Localization; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ResourcesClassLibraryWithAttribute")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f27639b9-913e-43af-9d64-bbd98d9a420a")] +[assembly: ResourceLocation("ResourceFolder")] diff --git a/test/ResourcesClassLibraryWithAttribute/ResourceFolder/Model.resx b/test/ResourcesClassLibraryWithAttribute/ResourceFolder/Model.resx new file mode 100644 index 0000000000..1b10f56f23 --- /dev/null +++ b/test/ResourcesClassLibraryWithAttribute/ResourceFolder/Model.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bonjour from ResourcesClassLibraryWithAttribute + + \ No newline at end of file diff --git a/test/ResourcesClassLibraryWithAttribute/project.json b/test/ResourcesClassLibraryWithAttribute/project.json new file mode 100644 index 0000000000..816819bb46 --- /dev/null +++ b/test/ResourcesClassLibraryWithAttribute/project.json @@ -0,0 +1,18 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "warningsAsErrors": true, + "keyFile": "../../tools/Key.snk" + }, + "dependencies": { + "Microsoft.Extensions.Localization": "1.1.0-*", + "NETStandard.Library": "1.6.0" + }, + + "frameworks": { + "net451": {}, + "netstandard1.6": { + "imports": "dnxcore50" + } + } +}