Merge branch 'release/2.2'

\n\nCommit migrated from 75b156c635
This commit is contained in:
Nate McMaster 2018-12-04 10:48:16 -08:00
commit 2987427e1a
30 changed files with 3384 additions and 0 deletions

View File

@ -0,0 +1,45 @@
// 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
{
/// <summary>
/// Represents a service that provides localized strings.
/// </summary>
public interface IStringLocalizer
{
/// <summary>
/// Gets the string resource with the given name.
/// </summary>
/// <param name="name">The name of the string resource.</param>
/// <returns>The string resource as a <see cref="LocalizedString"/>.</returns>
LocalizedString this[string name] { get; }
/// <summary>
/// Gets the string resource with the given name and formatted with the supplied arguments.
/// </summary>
/// <param name="name">The name of the string resource.</param>
/// <param name="arguments">The values to format the string with.</param>
/// <returns>The formatted string resource as a <see cref="LocalizedString"/>.</returns>
LocalizedString this[string name, params object[] arguments] { get; }
/// <summary>
/// Gets all string resources.
/// </summary>
/// <param name="includeParentCultures">
/// A <see cref="System.Boolean"/> indicating whether to include strings from parent cultures.
/// </param>
/// <returns>The strings.</returns>
IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures);
/// <summary>
/// Creates a new <see cref="IStringLocalizer"/> for a specific <see cref="CultureInfo"/>.
/// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> to use.</param>
/// <returns>A culture-specific <see cref="IStringLocalizer"/>.</returns>
IStringLocalizer WithCulture(CultureInfo culture);
}
}

View File

@ -0,0 +1,29 @@
// 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
{
/// <summary>
/// Represents a factory that creates <see cref="IStringLocalizer"/> instances.
/// </summary>
public interface IStringLocalizerFactory
{
/// <summary>
/// Creates an <see cref="IStringLocalizer"/> using the <see cref="System.Reflection.Assembly"/> and
/// <see cref="Type.FullName"/> of the specified <see cref="Type"/>.
/// </summary>
/// <param name="resourceSource">The <see cref="Type"/>.</param>
/// <returns>The <see cref="IStringLocalizer"/>.</returns>
IStringLocalizer Create(Type resourceSource);
/// <summary>
/// Creates an <see cref="IStringLocalizer"/>.
/// </summary>
/// <param name="baseName">The base name of the resource to load strings from.</param>
/// <param name="location">The location to load resources from.</param>
/// <returns>The <see cref="IStringLocalizer"/>.</returns>
IStringLocalizer Create(string baseName, string location);
}
}

View File

@ -0,0 +1,14 @@
// 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 Microsoft.Extensions.Localization
{
/// <summary>
/// Represents an <see cref="IStringLocalizer"/> that provides strings for <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The <see cref="System.Type"/> to provide strings for.</typeparam>
public interface IStringLocalizer<T> : IStringLocalizer
{
}
}

View File

@ -0,0 +1,90 @@
// 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
{
/// <summary>
/// A locale specific string.
/// </summary>
public class LocalizedString
{
/// <summary>
/// Creates a new <see cref="LocalizedString"/>.
/// </summary>
/// <param name="name">The name of the string in the resource it was loaded from.</param>
/// <param name="value">The actual string.</param>
public LocalizedString(string name, string value)
: this(name, value, resourceNotFound: false)
{
}
/// <summary>
/// Creates a new <see cref="LocalizedString"/>.
/// </summary>
/// <param name="name">The name of the string in the resource it was loaded from.</param>
/// <param name="value">The actual string.</param>
/// <param name="resourceNotFound">Whether the string was not found in a resource. Set this to <c>true</c> to indicate an alternate string value was used.</param>
public LocalizedString(string name, string value, bool resourceNotFound)
: this(name, value, resourceNotFound, searchedLocation: null)
{
}
/// <summary>
/// Creates a new <see cref="LocalizedString"/>.
/// </summary>
/// <param name="name">The name of the string in the resource it was loaded from.</param>
/// <param name="value">The actual string.</param>
/// <param name="resourceNotFound">Whether the string was not found in a resource. Set this to <c>true</c> to indicate an alternate string value was used.</param>
/// <param name="searchedLocation">The location which was searched for a localization value.</param>
public LocalizedString(string name, string value, bool resourceNotFound, string searchedLocation)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
Name = name;
Value = value;
ResourceNotFound = resourceNotFound;
SearchedLocation = searchedLocation;
}
public static implicit operator string(LocalizedString localizedString)
{
return localizedString?.Value;
}
/// <summary>
/// The name of the string in the resource it was loaded from.
/// </summary>
public string Name { get; }
/// <summary>
/// The actual string.
/// </summary>
public string Value { get; }
/// <summary>
/// Whether the string was not found in a resource. If <c>true</c>, an alternate string value was used.
/// </summary>
public bool ResourceNotFound { get; }
/// <summary>
/// The location which was searched for a localization value.
/// </summary>
public string SearchedLocation { get; }
/// <summary>
/// Returns the actual string.
/// </summary>
/// <returns>The actual string.</returns>
public override string ToString() => Value;
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Product>Microsoft .NET Extensions</Product>
<Description>Abstractions of application localization services.
Commonly used types:
Microsoft.Extensions.Localization.IStringLocalizer
Microsoft.Extensions.Localization.IStringLocalizer&lt;T&gt;</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>localization</PackageTags>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,74 @@
// 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.Generic;
namespace Microsoft.Extensions.Localization
{
public static class StringLocalizerExtensions
{
/// <summary>
/// Gets the string resource with the given name.
/// </summary>
/// <param name="stringLocalizer">The <see cref="IStringLocalizer"/>.</param>
/// <param name="name">The name of the string resource.</param>
/// <returns>The string resource as a <see cref="LocalizedString"/>.</returns>
public static LocalizedString GetString(
this IStringLocalizer stringLocalizer,
string name)
{
if (stringLocalizer == null)
{
throw new ArgumentNullException(nameof(stringLocalizer));
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
return stringLocalizer[name];
}
/// <summary>
/// Gets the string resource with the given name and formatted with the supplied arguments.
/// </summary>
/// <param name="stringLocalizer">The <see cref="IStringLocalizer"/>.</param>
/// <param name="name">The name of the string resource.</param>
/// <param name="arguments">The values to format the string with.</param>
/// <returns>The formatted string resource as a <see cref="LocalizedString"/>.</returns>
public static LocalizedString GetString(
this IStringLocalizer stringLocalizer,
string name,
params object[] arguments)
{
if (stringLocalizer == null)
{
throw new ArgumentNullException(nameof(stringLocalizer));
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
return stringLocalizer[name, arguments];
}
/// <summary>
/// Gets all string resources including those for parent cultures.
/// </summary>
/// <param name="stringLocalizer">The <see cref="IStringLocalizer"/>.</param>
/// <returns>The string resources.</returns>
public static IEnumerable<LocalizedString> GetAllStrings(this IStringLocalizer stringLocalizer)
{
if (stringLocalizer == null)
{
throw new ArgumentNullException(nameof(stringLocalizer));
}
return stringLocalizer.GetAllStrings(includeParentCultures: true);
}
}
}

View File

@ -0,0 +1,67 @@
// 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.Generic;
using System.Globalization;
namespace Microsoft.Extensions.Localization
{
/// <summary>
/// Provides strings for <typeparamref name="TResourceSource"/>.
/// </summary>
/// <typeparam name="TResourceSource">The <see cref="Type"/> to provide strings for.</typeparam>
public class StringLocalizer<TResourceSource> : IStringLocalizer<TResourceSource>
{
private IStringLocalizer _localizer;
/// <summary>
/// Creates a new <see cref="StringLocalizer{TResourceSource}"/>.
/// </summary>
/// <param name="factory">The <see cref="IStringLocalizerFactory"/> to use.</param>
public StringLocalizer(IStringLocalizerFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
_localizer = factory.Create(typeof(TResourceSource));
}
/// <inheritdoc />
public virtual IStringLocalizer WithCulture(CultureInfo culture) => _localizer.WithCulture(culture);
/// <inheritdoc />
public virtual LocalizedString this[string name]
{
get
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
return _localizer[name];
}
}
/// <inheritdoc />
public virtual LocalizedString this[string name, params object[] arguments]
{
get
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
return _localizer[name, arguments];
}
}
/// <inheritdoc />
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) =>
_localizer.GetAllStrings(includeParentCultures);
}
}

View File

@ -0,0 +1,413 @@
{
"AssemblyIdentity": "Microsoft.Extensions.Localization.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"Kind": "Interface",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Item",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Item",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "arguments",
"Type": "System.Object[]",
"IsParams": true
}
],
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetAllStrings",
"Parameters": [
{
"Name": "includeParentCultures",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "WithCulture",
"Parameters": [
{
"Name": "culture",
"Type": "System.Globalization.CultureInfo"
}
],
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.IStringLocalizerFactory",
"Visibility": "Public",
"Kind": "Interface",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "Create",
"Parameters": [
{
"Name": "resourceSource",
"Type": "System.Type"
}
],
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Create",
"Parameters": [
{
"Name": "baseName",
"Type": "System.String"
},
{
"Name": "location",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.IStringLocalizer<T0>",
"Visibility": "Public",
"Kind": "Interface",
"Abstract": true,
"ImplementedInterfaces": [
"Microsoft.Extensions.Localization.IStringLocalizer"
],
"Members": [],
"GenericParameters": [
{
"ParameterName": "T",
"ParameterPosition": 0,
"BaseTypeOrInterfaces": []
}
]
},
{
"Name": "Microsoft.Extensions.Localization.LocalizedString",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "op_Implicit",
"Parameters": [
{
"Name": "localizedString",
"Type": "Microsoft.Extensions.Localization.LocalizedString"
}
],
"ReturnType": "System.String",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Name",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Value",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_ResourceNotFound",
"Parameters": [],
"ReturnType": "System.Boolean",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_SearchedLocation",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "ToString",
"Parameters": [],
"ReturnType": "System.String",
"Virtual": true,
"Override": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "value",
"Type": "System.String"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "value",
"Type": "System.String"
},
{
"Name": "resourceNotFound",
"Type": "System.Boolean"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "value",
"Type": "System.String"
},
{
"Name": "resourceNotFound",
"Type": "System.Boolean"
},
{
"Name": "searchedLocation",
"Type": "System.String"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.StringLocalizerExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "GetString",
"Parameters": [
{
"Name": "stringLocalizer",
"Type": "Microsoft.Extensions.Localization.IStringLocalizer"
},
{
"Name": "name",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetString",
"Parameters": [
{
"Name": "stringLocalizer",
"Type": "Microsoft.Extensions.Localization.IStringLocalizer"
},
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "arguments",
"Type": "System.Object[]",
"IsParams": true
}
],
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetAllStrings",
"Parameters": [
{
"Name": "stringLocalizer",
"Type": "Microsoft.Extensions.Localization.IStringLocalizer"
}
],
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.StringLocalizer<T0>",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.Extensions.Localization.IStringLocalizer<T0>"
],
"Members": [
{
"Kind": "Method",
"Name": "get_Item",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Item",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "arguments",
"Type": "System.Object[]",
"IsParams": true
}
],
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetAllStrings",
"Parameters": [
{
"Name": "includeParentCultures",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "WithCulture",
"Parameters": [
{
"Name": "culture",
"Type": "System.Globalization.CultureInfo"
}
],
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "factory",
"Type": "Microsoft.Extensions.Localization.IStringLocalizerFactory"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": [
{
"ParameterName": "TResourceSource",
"ParameterPosition": 0,
"BaseTypeOrInterfaces": []
}
]
}
]
}

View File

@ -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;
using System.Collections.Generic;
namespace Microsoft.Extensions.Localization
{
/// <summary>
/// Represents a cache of string names in resources.
/// </summary>
public interface IResourceNamesCache
{
/// <summary>
/// Adds a set of resource names to the cache by using the specified function, if the name does not already exist.
/// </summary>
/// <param name="name">The resource name to add string names for.</param>
/// <param name="valueFactory">The function used to generate the string names for the resource.</param>
/// <returns>The string names for the resource.</returns>
IList<string> GetOrAdd(string name, Func<string, IList<string>> valueFactory);
}
}

View File

@ -0,0 +1,28 @@
// 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.IO;
using System.Reflection;
namespace Microsoft.Extensions.Localization.Internal
{
public class AssemblyWrapper
{
public AssemblyWrapper(Assembly assembly)
{
if (assembly == null)
{
throw new ArgumentNullException(nameof(assembly));
}
Assembly = assembly;
}
public Assembly Assembly { get; }
public virtual string FullName => Assembly.FullName;
public virtual Stream GetManifestResourceStream(string name) => Assembly.GetManifestResourceStream(name);
}
}

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

@ -0,0 +1,27 @@
// 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 Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.Localization.Internal
{
internal static class ResourceManagerStringLocalizerLoggerExtensions
{
private static readonly Action<ILogger, string, string, CultureInfo, Exception> _searchedLocation;
static ResourceManagerStringLocalizerLoggerExtensions()
{
_searchedLocation = LoggerMessage.Define<string, string, CultureInfo>(
LogLevel.Debug,
1,
$"{nameof(ResourceManagerStringLocalizer)} searched for '{{Key}}' in '{{LocationSearched}}' with culture '{{Culture}}'.");
}
public static void SearchedLocation(this ILogger logger, string key, string searchedLocation, CultureInfo culture)
{
_searchedLocation(logger, key, searchedLocation, culture, null);
}
}
}

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

@ -0,0 +1,16 @@
// 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 Microsoft.Extensions.Localization
{
/// <summary>
/// Provides programmatic configuration for localization.
/// </summary>
public class LocalizationOptions
{
/// <summary>
/// The relative path under application root where resource files are located.
/// </summary>
public string ResourcesPath { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,76 @@
// 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 Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Localization;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extension methods for setting up localization services in an <see cref="IServiceCollection" />.
/// </summary>
public static class LocalizationServiceCollectionExtensions
{
/// <summary>
/// Adds services required for application localization.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddLocalization(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddOptions();
AddLocalizationServices(services);
return services;
}
/// <summary>
/// Adds services required for application localization.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="setupAction">
/// An <see cref="Action{LocalizationOptions}"/> to configure the <see cref="LocalizationOptions"/>.
/// </param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddLocalization(
this IServiceCollection services,
Action<LocalizationOptions> setupAction)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
AddLocalizationServices(services, setupAction);
return services;
}
// To enable unit testing
internal static void AddLocalizationServices(IServiceCollection services)
{
services.TryAddSingleton<IStringLocalizerFactory, ResourceManagerStringLocalizerFactory>();
services.TryAddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
}
internal static void AddLocalizationServices(
IServiceCollection services,
Action<LocalizationOptions> setupAction)
{
AddLocalizationServices(services);
services.Configure(setupAction);
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Product>Microsoft .NET Extensions</Product>
<Description>Application localization services and default implementation based on ResourceManager to load localized assembly resources.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>localization</PackageTags>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<Reference Include="Microsoft.Extensions.Options" />
<Reference Include="Microsoft.Extensions.Localization.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
// 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Extensions.Localization.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -0,0 +1,62 @@
// <auto-generated />
namespace Microsoft.Extensions.Localization
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Extensions.Localization.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The manifest '{0}' was not found.
/// </summary>
internal static string Localization_MissingManifest
{
get { return GetString("Localization_MissingManifest"); }
}
/// <summary>
/// The manifest '{0}' was not found.
/// </summary>
internal static string FormatLocalization_MissingManifest(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("Localization_MissingManifest"), p0);
}
/// <summary>
/// No manifests exist for the current culture.
/// </summary>
internal static string Localization_MissingManifest_Parent
{
get { return GetString("Localization_MissingManifest_Parent"); }
}
/// <summary>
/// No manifests exist for the current culture.
/// </summary>
internal static string FormatLocalization_MissingManifest_Parent()
{
return GetString("Localization_MissingManifest_Parent");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -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
{
/// <summary>
/// Provides the location of resources for an Assembly.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
public class ResourceLocationAttribute : Attribute
{
/// <summary>
/// Creates a new <see cref="ResourceLocationAttribute"/>.
/// </summary>
/// <param name="resourceLocation">The location of resources for this Assembly.</param>
public ResourceLocationAttribute(string resourceLocation)
{
if (string.IsNullOrEmpty(resourceLocation))
{
throw new ArgumentNullException(nameof(resourceLocation));
}
ResourceLocation = resourceLocation;
}
/// <summary>
/// The location of resources for this Assembly.
/// </summary>
public string ResourceLocation { get; }
}
}

View File

@ -0,0 +1,274 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Resources;
using Microsoft.Extensions.Localization.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.Localization
{
/// <summary>
/// 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 IResourceStringProvider _resourceStringProvider;
private readonly string _resourceBaseName;
private readonly ILogger _logger;
/// <summary>
/// Creates a new <see cref="ResourceManagerStringLocalizer"/>.
/// </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 that contains the strings.</param>
/// <param name="resourceNamesCache">Cache of the list of strings for a given resource assembly name.</param>
/// <param name="logger">The <see cref="ILogger"/>.</param>
public ResourceManagerStringLocalizer(
ResourceManager resourceManager,
Assembly resourceAssembly,
string baseName,
IResourceNamesCache resourceNamesCache,
ILogger logger)
: this(
resourceManager,
new AssemblyWrapper(resourceAssembly),
baseName,
resourceNamesCache,
logger)
{
}
/// <summary>
/// Intended for testing purposes only.
/// </summary>
public ResourceManagerStringLocalizer(
ResourceManager resourceManager,
AssemblyWrapper resourceAssemblyWrapper,
string baseName,
IResourceNamesCache resourceNamesCache,
ILogger logger)
: this(
resourceManager,
new ResourceManagerStringProvider(
resourceNamesCache,
resourceManager,
resourceAssemblyWrapper.Assembly,
baseName),
baseName,
resourceNamesCache,
logger)
{
}
/// <summary>
/// Intended for testing purposes only.
/// </summary>
public ResourceManagerStringLocalizer(
ResourceManager resourceManager,
IResourceStringProvider resourceStringProvider,
string baseName,
IResourceNamesCache resourceNamesCache,
ILogger logger)
{
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 (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_resourceStringProvider = resourceStringProvider;
_resourceManager = resourceManager;
_resourceBaseName = baseName;
_resourceNamesCache = resourceNamesCache;
_logger = logger;
}
/// <inheritdoc />
public virtual LocalizedString this[string name]
{
get
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var value = GetStringSafely(name, null);
return new LocalizedString(name, value ?? name, resourceNotFound: value == null, searchedLocation: _resourceBaseName);
}
}
/// <inheritdoc />
public virtual LocalizedString this[string name, params object[] arguments]
{
get
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var format = GetStringSafely(name, null);
var value = string.Format(format ?? name, arguments);
return new LocalizedString(name, value, resourceNotFound: format == null, searchedLocation: _resourceBaseName);
}
}
/// <summary>
/// Creates a new <see cref="ResourceManagerStringLocalizer"/> for a specific <see cref="CultureInfo"/>.
/// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> to use.</param>
/// <returns>A culture-specific <see cref="ResourceManagerStringLocalizer"/>.</returns>
public IStringLocalizer WithCulture(CultureInfo culture)
{
return culture == null
? new ResourceManagerStringLocalizer(
_resourceManager,
_resourceStringProvider,
_resourceBaseName,
_resourceNamesCache,
_logger)
: new ResourceManagerWithCultureStringLocalizer(
_resourceManager,
_resourceStringProvider,
_resourceBaseName,
_resourceNamesCache,
culture,
_logger);
}
/// <inheritdoc />
public virtual IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) =>
GetAllStrings(includeParentCultures, CultureInfo.CurrentUICulture);
/// <summary>
/// Returns all strings in the specified culture.
/// </summary>
/// <param name="includeParentCultures"></param>
/// <param name="culture">The <see cref="CultureInfo"/> to get strings for.</param>
/// <returns>The strings.</returns>
protected IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures, CultureInfo culture)
{
if (culture == null)
{
throw new ArgumentNullException(nameof(culture));
}
var resourceNames = includeParentCultures
? GetResourceNamesFromCultureHierarchy(culture)
: _resourceStringProvider.GetAllResourceStrings(culture, true);
foreach (var name in resourceNames)
{
var value = GetStringSafely(name, culture);
yield return new LocalizedString(name, value ?? name, resourceNotFound: value == null, searchedLocation: _resourceBaseName);
}
}
/// <summary>
/// Gets a resource string from the <see cref="_resourceManager"/> and returns <c>null</c> instead of
/// throwing exceptions if a match isn't found.
/// </summary>
/// <param name="name">The name of the string resource.</param>
/// <param name="culture">The <see cref="CultureInfo"/> to get the string for.</param>
/// <returns>The resource string, or <c>null</c> if none was found.</returns>
protected string GetStringSafely(string name, CultureInfo culture)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var keyCulture = culture ?? CultureInfo.CurrentUICulture;
var cacheKey = $"name={name}&culture={keyCulture.Name}";
_logger.SearchedLocation(name, _resourceBaseName, keyCulture);
if (_missingManifestCache.ContainsKey(cacheKey))
{
return null;
}
try
{
return culture == null ? _resourceManager.GetString(name) : _resourceManager.GetString(name, culture);
}
catch (MissingManifestResourceException)
{
_missingManifestCache.TryAdd(cacheKey, null);
return null;
}
}
private IEnumerable<string> GetResourceNamesFromCultureHierarchy(CultureInfo startingCulture)
{
var currentCulture = startingCulture;
var resourceNames = new HashSet<string>();
var hasAnyCultures = false;
while (true)
{
var cultureResourceNames = _resourceStringProvider.GetAllResourceStrings(currentCulture, false);
if (cultureResourceNames != null)
{
foreach (var resourceName in cultureResourceNames)
{
resourceNames.Add(resourceName);
}
hasAnyCultures = true;
}
if (currentCulture == currentCulture.Parent)
{
// currentCulture begat currentCulture, probably time to leave
break;
}
currentCulture = currentCulture.Parent;
}
if (!hasAnyCultures)
{
throw new MissingManifestResourceException(Resources.Localization_MissingManifest_Parent);
}
return resourceNames;
}
}
}

View File

@ -0,0 +1,270 @@
// 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.Concurrent;
using System.IO;
using System.Reflection;
using System.Resources;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.Localization
{
/// <summary>
/// An <see cref="IStringLocalizerFactory"/> that creates instances of <see cref="ResourceManagerStringLocalizer"/>.
/// </summary>
/// <remarks>
/// <see cref="ResourceManagerStringLocalizerFactory"/> offers multiple ways to set the relative path of
/// resources to be used. They are, in order of precedence:
/// <see cref="ResourceLocationAttribute"/> -> <see cref="LocalizationOptions.ResourcesPath"/> -> the project root.
/// </remarks>
public class ResourceManagerStringLocalizerFactory : IStringLocalizerFactory
{
private readonly IResourceNamesCache _resourceNamesCache = new ResourceNamesCache();
private readonly ConcurrentDictionary<string, ResourceManagerStringLocalizer> _localizerCache =
new ConcurrentDictionary<string, ResourceManagerStringLocalizer>();
private readonly string _resourcesRelativePath;
private readonly ILoggerFactory _loggerFactory;
/// <summary>
/// Creates a new <see cref="ResourceManagerStringLocalizer"/>.
/// </summary>
/// <param name="localizationOptions">The <see cref="IOptions{LocalizationOptions}"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public ResourceManagerStringLocalizerFactory(
IOptions<LocalizationOptions> localizationOptions,
ILoggerFactory loggerFactory)
{
if (localizationOptions == null)
{
throw new ArgumentNullException(nameof(localizationOptions));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_resourcesRelativePath = localizationOptions.Value.ResourcesPath ?? string.Empty;
_loggerFactory = loggerFactory;
if (!string.IsNullOrEmpty(_resourcesRelativePath))
{
_resourcesRelativePath = _resourcesRelativePath.Replace(Path.AltDirectorySeparatorChar, '.')
.Replace(Path.DirectorySeparatorChar, '.') + ".";
}
}
/// <summary>
/// Gets the resource prefix used to look up the resource.
/// </summary>
/// <param name="typeInfo">The type of the resource to be looked up.</param>
/// <returns>The prefix for resource lookup.</returns>
protected virtual string GetResourcePrefix(TypeInfo typeInfo)
{
if (typeInfo == null)
{
throw new ArgumentNullException(nameof(typeInfo));
}
return GetResourcePrefix(typeInfo, GetRootNamespace(typeInfo.Assembly), GetResourcePath(typeInfo.Assembly));
}
/// <summary>
/// Gets the resource prefix used to look up the resource.
/// </summary>
/// <param name="typeInfo">The type of the resource to be looked up.</param>
/// <param name="baseNamespace">The base namespace of the application.</param>
/// <param name="resourcesRelativePath">The folder containing all resources.</param>
/// <returns>The prefix for resource lookup.</returns>
/// <remarks>
/// For the type "Sample.Controllers.Home" if there's a resourceRelativePath return
/// "Sample.Resourcepath.Controllers.Home" if there isn't one then it would return "Sample.Controllers.Home".
/// </remarks>
protected virtual string GetResourcePrefix(TypeInfo typeInfo, string baseNamespace, string resourcesRelativePath)
{
if (typeInfo == null)
{
throw new ArgumentNullException(nameof(typeInfo));
}
if (string.IsNullOrEmpty(baseNamespace))
{
throw new ArgumentNullException(nameof(baseNamespace));
}
if (string.IsNullOrEmpty(resourcesRelativePath))
{
return typeInfo.FullName;
}
else
{
// This expectation is defined by dotnet's automatic resource storage.
// We have to conform to "{RootNamespace}.{ResourceLocation}.{FullTypeName - AssemblyName}".
var assemblyName = new AssemblyName(typeInfo.Assembly.FullName).Name;
return baseNamespace + "." + resourcesRelativePath + TrimPrefix(typeInfo.FullName, assemblyName + ".");
}
}
/// <summary>
/// Gets the resource prefix used to look up the resource.
/// </summary>
/// <param name="baseResourceName">The name of the resource to be looked up</param>
/// <param name="baseNamespace">The base namespace of the application.</param>
/// <returns>The prefix for resource lookup.</returns>
protected virtual string GetResourcePrefix(string baseResourceName, string baseNamespace)
{
if (string.IsNullOrEmpty(baseResourceName))
{
throw new ArgumentNullException(nameof(baseResourceName));
}
if (string.IsNullOrEmpty(baseNamespace))
{
throw new ArgumentNullException(nameof(baseNamespace));
}
var assemblyName = new AssemblyName(baseNamespace);
var assembly = Assembly.Load(assemblyName);
var rootNamespace = GetRootNamespace(assembly);
var resourceLocation = GetResourcePath(assembly);
var locationPath = rootNamespace + "." + resourceLocation;
baseResourceName = locationPath + TrimPrefix(baseResourceName, baseNamespace + ".");
return baseResourceName;
}
/// <summary>
/// Creates a <see cref="ResourceManagerStringLocalizer"/> using the <see cref="Assembly"/> and
/// <see cref="Type.FullName"/> of the specified <see cref="Type"/>.
/// </summary>
/// <param name="resourceSource">The <see cref="Type"/>.</param>
/// <returns>The <see cref="ResourceManagerStringLocalizer"/>.</returns>
public IStringLocalizer Create(Type resourceSource)
{
if (resourceSource == null)
{
throw new ArgumentNullException(nameof(resourceSource));
}
var typeInfo = resourceSource.GetTypeInfo();
var baseName = GetResourcePrefix(typeInfo);
var assembly = typeInfo.Assembly;
return _localizerCache.GetOrAdd(baseName, _ => CreateResourceManagerStringLocalizer(assembly, baseName));
}
/// <summary>
/// Creates a <see cref="ResourceManagerStringLocalizer"/>.
/// </summary>
/// <param name="baseName">The base name of the resource to load strings from.</param>
/// <param name="location">The location to load resources from.</param>
/// <returns>The <see cref="ResourceManagerStringLocalizer"/>.</returns>
public IStringLocalizer Create(string baseName, string location)
{
if (baseName == null)
{
throw new ArgumentNullException(nameof(baseName));
}
if (location == null)
{
throw new ArgumentNullException(nameof(location));
}
return _localizerCache.GetOrAdd($"B={baseName},L={location}", _ =>
{
var assemblyName = new AssemblyName(location);
var assembly = Assembly.Load(assemblyName);
baseName = GetResourcePrefix(baseName, location);
return CreateResourceManagerStringLocalizer(assembly, baseName);
});
}
/// <summary>Creates a <see cref="ResourceManagerStringLocalizer"/> for the given input.</summary>
/// <param name="assembly">The assembly to create a <see cref="ResourceManagerStringLocalizer"/> for.</param>
/// <param name="baseName">The base name of the resource to search for.</param>
/// <returns>A <see cref="ResourceManagerStringLocalizer"/> for the given <paramref name="assembly"/> and <paramref name="baseName"/>.</returns>
/// <remarks>This method is virtual for testing purposes only.</remarks>
protected virtual ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(
Assembly assembly,
string baseName)
{
return new ResourceManagerStringLocalizer(
new ResourceManager(baseName, assembly),
assembly,
baseName,
_resourceNamesCache,
_loggerFactory.CreateLogger<ResourceManagerStringLocalizer>());
}
/// <summary>
/// Gets the resource prefix used to look up the resource.
/// </summary>
/// <param name="location">The general location of the resource.</param>
/// <param name="baseName">The base name of the resource.</param>
/// <param name="resourceLocation">The location of the resource within <paramref name="location"/>.</param>
/// <returns>The resource prefix used to look up the resource.</returns>
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 + ".");
}
/// <summary>Gets a <see cref="ResourceLocationAttribute"/> from the provided <see cref="Assembly"/>.</summary>
/// <param name="assembly">The assembly to get a <see cref="ResourceLocationAttribute"/> from.</param>
/// <returns>The <see cref="ResourceLocationAttribute"/> associated with the given <see cref="Assembly"/>.</returns>
/// <remarks>This method is protected and virtual for testing purposes only.</remarks>
protected virtual ResourceLocationAttribute GetResourceLocationAttribute(Assembly assembly)
{
return assembly.GetCustomAttribute<ResourceLocationAttribute>();
}
/// <summary>Gets a <see cref="RootNamespaceAttribute"/> from the provided <see cref="Assembly"/>.</summary>
/// <param name="assembly">The assembly to get a <see cref="RootNamespaceAttribute"/> from.</param>
/// <returns>The <see cref="RootNamespaceAttribute"/> associated with the given <see cref="Assembly"/>.</returns>
/// <remarks>This method is protected and virtual for testing purposes only.</remarks>
protected virtual RootNamespaceAttribute GetRootNamespaceAttribute(Assembly assembly)
{
return assembly.GetCustomAttribute<RootNamespaceAttribute>();
}
private string GetRootNamespace(Assembly assembly)
{
var rootNamespaceAttribute = GetRootNamespaceAttribute(assembly);
return rootNamespaceAttribute?.RootNamespace ??
new AssemblyName(assembly.FullName).Name;
}
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))
{
return name.Substring(prefix.Length);
}
return name;
}
}
}

View File

@ -0,0 +1,164 @@
// 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.Generic;
using System.Globalization;
using System.Reflection;
using System.Resources;
using Microsoft.Extensions.Localization.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.Localization
{
/// <summary>
/// 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
{
private readonly string _resourceBaseName;
private readonly CultureInfo _culture;
/// <summary>
/// Creates a new <see cref="ResourceManagerWithCultureStringLocalizer"/>.
/// </summary>
/// <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>
/// <param name="logger">The <see cref="ILogger"/>.</param>
internal ResourceManagerWithCultureStringLocalizer(
ResourceManager resourceManager,
IResourceStringProvider resourceStringProvider,
string baseName,
IResourceNamesCache resourceNamesCache,
CultureInfo culture,
ILogger logger)
: base(resourceManager, resourceStringProvider, baseName, resourceNamesCache, logger)
{
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));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_resourceBaseName = baseName;
_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 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>
/// <param name="logger">The <see cref="ILogger"/>.</param>
public ResourceManagerWithCultureStringLocalizer(
ResourceManager resourceManager,
Assembly resourceAssembly,
string baseName,
IResourceNamesCache resourceNamesCache,
CultureInfo culture,
ILogger logger)
: base(resourceManager, resourceAssembly, baseName, resourceNamesCache, logger)
{
if (resourceManager == null)
{
throw new ArgumentNullException(nameof(resourceManager));
}
if (resourceAssembly == null)
{
throw new ArgumentNullException(nameof(resourceAssembly));
}
if (baseName == null)
{
throw new ArgumentNullException(nameof(baseName));
}
if (resourceNamesCache == null)
{
throw new ArgumentNullException(nameof(resourceNamesCache));
}
if (culture == null)
{
throw new ArgumentNullException(nameof(culture));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_resourceBaseName = baseName;
_culture = culture;
}
/// <inheritdoc />
public override LocalizedString this[string name]
{
get
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var value = GetStringSafely(name, _culture);
return new LocalizedString(name, value ?? name, resourceNotFound: value == null, searchedLocation: _resourceBaseName);
}
}
/// <inheritdoc />
public override LocalizedString this[string name, params object[] arguments]
{
get
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
var format = GetStringSafely(name, _culture);
var value = string.Format(_culture, format ?? name, arguments);
return new LocalizedString(name, value, resourceNotFound: format == null, searchedLocation: _resourceBaseName);
}
}
/// <inheritdoc />
public override IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) =>
GetAllStrings(includeParentCultures, _culture);
}
}

View File

@ -0,0 +1,23 @@
// 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.Concurrent;
using System.Collections.Generic;
namespace Microsoft.Extensions.Localization
{
/// <summary>
/// An implementation of <see cref="IResourceNamesCache"/> backed by a <see cref="ConcurrentDictionary{TKey, TValue}"/>.
/// </summary>
public class ResourceNamesCache : IResourceNamesCache
{
private readonly ConcurrentDictionary<string, IList<string>> _cache = new ConcurrentDictionary<string, IList<string>>();
/// <inheritdoc />
public IList<string> GetOrAdd(string name, Func<string, IList<string>> valueFactory)
{
return _cache.GetOrAdd(name, valueFactory);
}
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Localization_MissingManifest" xml:space="preserve">
<value>The manifest '{0}' was not found.</value>
</data>
<data name="Localization_MissingManifest_Parent" xml:space="preserve">
<value>No manifests exist for the current culture.</value>
</data>
</root>

View File

@ -0,0 +1,35 @@
// 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
{
/// <summary>
/// Provides the RootNamespace of an Assembly. The RootNamespace of the assembly is used by Localization to
/// determine the resource name to look for when RootNamespace differs from the AssemblyName.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
public class RootNamespaceAttribute : Attribute
{
/// <summary>
/// Creates a new <see cref="RootNamespaceAttribute"/>.
/// </summary>
/// <param name="rootNamespace">The RootNamespace for this Assembly.</param>
public RootNamespaceAttribute(string rootNamespace)
{
if (string.IsNullOrEmpty(rootNamespace))
{
throw new ArgumentNullException(nameof(rootNamespace));
}
RootNamespace = rootNamespace;
}
/// <summary>
/// The RootNamespace of this Assembly. The RootNamespace of the assembly is used by Localization to
/// determine the resource name to look for when RootNamespace differs from the AssemblyName.
/// </summary>
public string RootNamespace { get; }
}
}

View File

@ -0,0 +1,687 @@
{
"AssemblyIdentity": "Microsoft.Extensions.Localization, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.Extensions.DependencyInjection.LocalizationServiceCollectionExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "AddLocalization",
"Parameters": [
{
"Name": "services",
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
}
],
"ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "AddLocalization",
"Parameters": [
{
"Name": "services",
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
},
{
"Name": "setupAction",
"Type": "System.Action<Microsoft.Extensions.Localization.LocalizationOptions>"
}
],
"ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.IResourceNamesCache",
"Visibility": "Public",
"Kind": "Interface",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "GetOrAdd",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "valueFactory",
"Type": "System.Func<System.String, System.Collections.Generic.IList<System.String>>"
}
],
"ReturnType": "System.Collections.Generic.IList<System.String>",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.LocalizationOptions",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_ResourcesPath",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_ResourcesPath",
"Parameters": [
{
"Name": "value",
"Type": "System.String"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.ResourceLocationAttribute",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "System.Attribute",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_ResourceLocation",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "resourceLocation",
"Type": "System.String"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.Extensions.Localization.IStringLocalizer"
],
"Members": [
{
"Kind": "Method",
"Name": "get_Item",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Item",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "arguments",
"Type": "System.Object[]",
"IsParams": true
}
],
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "WithCulture",
"Parameters": [
{
"Name": "culture",
"Type": "System.Globalization.CultureInfo"
}
],
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetAllStrings",
"Parameters": [
{
"Name": "includeParentCultures",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetAllStrings",
"Parameters": [
{
"Name": "includeParentCultures",
"Type": "System.Boolean"
},
{
"Name": "culture",
"Type": "System.Globalization.CultureInfo"
}
],
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
"Visibility": "Protected",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetStringSafely",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "culture",
"Type": "System.Globalization.CultureInfo"
}
],
"ReturnType": "System.String",
"Visibility": "Protected",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "resourceManager",
"Type": "System.Resources.ResourceManager"
},
{
"Name": "resourceAssembly",
"Type": "System.Reflection.Assembly"
},
{
"Name": "baseName",
"Type": "System.String"
},
{
"Name": "resourceNamesCache",
"Type": "Microsoft.Extensions.Localization.IResourceNamesCache"
},
{
"Name": "logger",
"Type": "Microsoft.Extensions.Logging.ILogger"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "resourceManager",
"Type": "System.Resources.ResourceManager"
},
{
"Name": "resourceAssemblyWrapper",
"Type": "Microsoft.Extensions.Localization.Internal.AssemblyWrapper"
},
{
"Name": "baseName",
"Type": "System.String"
},
{
"Name": "resourceNamesCache",
"Type": "Microsoft.Extensions.Localization.IResourceNamesCache"
},
{
"Name": "logger",
"Type": "Microsoft.Extensions.Logging.ILogger"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "resourceManager",
"Type": "System.Resources.ResourceManager"
},
{
"Name": "resourceStringProvider",
"Type": "Microsoft.Extensions.Localization.Internal.IResourceStringProvider"
},
{
"Name": "baseName",
"Type": "System.String"
},
{
"Name": "resourceNamesCache",
"Type": "Microsoft.Extensions.Localization.IResourceNamesCache"
},
{
"Name": "logger",
"Type": "Microsoft.Extensions.Logging.ILogger"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizerFactory",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.Extensions.Localization.IStringLocalizerFactory"
],
"Members": [
{
"Kind": "Method",
"Name": "GetResourcePrefix",
"Parameters": [
{
"Name": "typeInfo",
"Type": "System.Reflection.TypeInfo"
}
],
"ReturnType": "System.String",
"Virtual": true,
"Visibility": "Protected",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetResourcePrefix",
"Parameters": [
{
"Name": "typeInfo",
"Type": "System.Reflection.TypeInfo"
},
{
"Name": "baseNamespace",
"Type": "System.String"
},
{
"Name": "resourcesRelativePath",
"Type": "System.String"
}
],
"ReturnType": "System.String",
"Virtual": true,
"Visibility": "Protected",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetResourcePrefix",
"Parameters": [
{
"Name": "baseResourceName",
"Type": "System.String"
},
{
"Name": "baseNamespace",
"Type": "System.String"
}
],
"ReturnType": "System.String",
"Virtual": true,
"Visibility": "Protected",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Create",
"Parameters": [
{
"Name": "resourceSource",
"Type": "System.Type"
}
],
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizerFactory",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Create",
"Parameters": [
{
"Name": "baseName",
"Type": "System.String"
},
{
"Name": "location",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizerFactory",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "CreateResourceManagerStringLocalizer",
"Parameters": [
{
"Name": "assembly",
"Type": "System.Reflection.Assembly"
},
{
"Name": "baseName",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer",
"Virtual": true,
"Visibility": "Protected",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetResourcePrefix",
"Parameters": [
{
"Name": "location",
"Type": "System.String"
},
{
"Name": "baseName",
"Type": "System.String"
},
{
"Name": "resourceLocation",
"Type": "System.String"
}
],
"ReturnType": "System.String",
"Virtual": true,
"Visibility": "Protected",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetResourceLocationAttribute",
"Parameters": [
{
"Name": "assembly",
"Type": "System.Reflection.Assembly"
}
],
"ReturnType": "Microsoft.Extensions.Localization.ResourceLocationAttribute",
"Virtual": true,
"Visibility": "Protected",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetRootNamespaceAttribute",
"Parameters": [
{
"Name": "assembly",
"Type": "System.Reflection.Assembly"
}
],
"ReturnType": "Microsoft.Extensions.Localization.RootNamespaceAttribute",
"Virtual": true,
"Visibility": "Protected",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "localizationOptions",
"Type": "Microsoft.Extensions.Options.IOptions<Microsoft.Extensions.Localization.LocalizationOptions>"
},
{
"Name": "loggerFactory",
"Type": "Microsoft.Extensions.Logging.ILoggerFactory"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.ResourceManagerWithCultureStringLocalizer",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Item",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Item",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "arguments",
"Type": "System.Object[]",
"IsParams": true
}
],
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "GetAllStrings",
"Parameters": [
{
"Name": "includeParentCultures",
"Type": "System.Boolean"
}
],
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
"Virtual": true,
"Override": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "resourceManager",
"Type": "System.Resources.ResourceManager"
},
{
"Name": "resourceAssembly",
"Type": "System.Reflection.Assembly"
},
{
"Name": "baseName",
"Type": "System.String"
},
{
"Name": "resourceNamesCache",
"Type": "Microsoft.Extensions.Localization.IResourceNamesCache"
},
{
"Name": "culture",
"Type": "System.Globalization.CultureInfo"
},
{
"Name": "logger",
"Type": "Microsoft.Extensions.Logging.ILogger"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.ResourceNamesCache",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.Extensions.Localization.IResourceNamesCache"
],
"Members": [
{
"Kind": "Method",
"Name": "GetOrAdd",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "valueFactory",
"Type": "System.Func<System.String, System.Collections.Generic.IList<System.String>>"
}
],
"ReturnType": "System.Collections.Generic.IList<System.String>",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Localization.IResourceNamesCache",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Localization.RootNamespaceAttribute",
"Visibility": "Public",
"Kind": "Class",
"BaseType": "System.Attribute",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_RootNamespace",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "rootNamespace",
"Type": "System.String"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -0,0 +1,68 @@
// 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.Linq;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.Extensions.DependencyInjection
{
public class LocalizationServiceCollectionExtensionsTest
{
[Fact]
public void AddLocalization_AddsNeededServices()
{
// Arrange
var collection = new ServiceCollection();
// Act
LocalizationServiceCollectionExtensions.AddLocalizationServices(collection);
// Assert
AssertContainsSingle(collection, typeof(IStringLocalizerFactory), typeof(ResourceManagerStringLocalizerFactory));
AssertContainsSingle(collection, typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
}
[Fact]
public void AddLocalizationWithLocalizationOptions_AddsNeededServices()
{
// Arrange
var collection = new ServiceCollection();
// Act
LocalizationServiceCollectionExtensions.AddLocalizationServices(
collection,
options => options.ResourcesPath = "Resources");
AssertContainsSingle(collection, typeof(IStringLocalizerFactory), typeof(ResourceManagerStringLocalizerFactory));
AssertContainsSingle(collection, typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
}
private void AssertContainsSingle(
IServiceCollection services,
Type serviceType,
Type implementationType)
{
var matches = services
.Where(sd =>
sd.ServiceType == serviceType &&
sd.ImplementationType == implementationType)
.ToArray();
if (matches.Length == 0)
{
Assert.True(
false,
$"Could not find an instance of {implementationType} registered as {serviceType}");
}
else if (matches.Length > 1)
{
Assert.True(
false,
$"Found multiple instances of {implementationType} registered as {serviceType}");
}
}
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.Localization" />
<Reference Include="Microsoft.Extensions.DependencyInjection" />
<Reference Include="Microsoft.Extensions.Logging.Testing" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,296 @@
// 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.IO;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
// This namespace intentionally matches the default assembly namespace.
namespace Microsoft.Extensions.Localization.Tests
{
public class TestResourceManagerStringLocalizerFactory : ResourceManagerStringLocalizerFactory
{
private ResourceLocationAttribute _resourceLocationAttribute;
private RootNamespaceAttribute _rootNamespaceAttribute;
public Assembly Assembly { get; private set; }
public string BaseName { get; private set; }
public TestResourceManagerStringLocalizerFactory(
IOptions<LocalizationOptions> localizationOptions,
ResourceLocationAttribute resourceLocationAttribute,
RootNamespaceAttribute rootNamespaceAttribute,
ILoggerFactory loggerFactory)
: base(localizationOptions, loggerFactory)
{
_resourceLocationAttribute = resourceLocationAttribute;
_rootNamespaceAttribute = rootNamespaceAttribute;
}
protected override ResourceLocationAttribute GetResourceLocationAttribute(Assembly assembly)
{
return _resourceLocationAttribute;
}
protected override RootNamespaceAttribute GetRootNamespaceAttribute(Assembly assembly)
{
return _rootNamespaceAttribute;
}
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 locOptions = new LocalizationOptions();
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var resourceLocationAttribute = new ResourceLocationAttribute(Path.Combine("My", "Resources"));
var loggerFactory = NullLoggerFactory.Instance;
var typeFactory = new TestResourceManagerStringLocalizerFactory(
options.Object,
resourceLocationAttribute,
rootNamespaceAttribute: null,
loggerFactory: loggerFactory);
var stringFactory = new TestResourceManagerStringLocalizerFactory(
options.Object,
resourceLocationAttribute,
rootNamespaceAttribute: null,
loggerFactory: loggerFactory);
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()
{
// Arrange
var locOptions = new LocalizationOptions();
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var loggerFactory = NullLoggerFactory.Instance;
var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory);
// Act
var result1 = factory.Create(typeof(ResourceManagerStringLocalizerFactoryTest));
var result2 = factory.Create(typeof(ResourceManagerStringLocalizerFactoryTest));
// Assert
Assert.Same(result1, result2);
}
[Fact]
public void Create_FromType_ReturnsNewResultForDifferentType()
{
// Arrange
var locOptions = new LocalizationOptions();
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var loggerFactory = NullLoggerFactory.Instance;
var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory);
// Act
var result1 = factory.Create(typeof(ResourceManagerStringLocalizerFactoryTest));
var result2 = factory.Create(typeof(LocalizationOptions));
// Assert
Assert.NotSame(result1, result2);
}
[Fact]
public void Create_ResourceLocationAttribute_RootNamespaceIgnoredWhenNoLocation()
{
// Arrange
var locOptions = new LocalizationOptions();
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var loggerFactory = NullLoggerFactory.Instance;
var resourcePath = Path.Combine("My", "Resources");
var rootNamespace = "MyNamespace";
var rootNamespaceAttribute = new RootNamespaceAttribute(rootNamespace);
var typeFactory = new TestResourceManagerStringLocalizerFactory(
options.Object,
resourceLocationAttribute: null,
rootNamespaceAttribute: rootNamespaceAttribute,
loggerFactory: loggerFactory);
var type = typeof(ResourceManagerStringLocalizerFactoryTest);
// Act
typeFactory.Create(type);
// Assert
Assert.Equal($"Microsoft.Extensions.Localization.Tests.ResourceManagerStringLocalizerFactoryTest", typeFactory.BaseName);
}
[Fact]
public void Create_ResourceLocationAttribute_UsesRootNamespace()
{
// Arrange
var locOptions = new LocalizationOptions();
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var loggerFactory = NullLoggerFactory.Instance;
var resourcePath = Path.Combine("My", "Resources");
var rootNamespace = "MyNamespace";
var resourceLocationAttribute = new ResourceLocationAttribute(resourcePath);
var rootNamespaceAttribute = new RootNamespaceAttribute(rootNamespace);
var typeFactory = new TestResourceManagerStringLocalizerFactory(
options.Object,
resourceLocationAttribute,
rootNamespaceAttribute,
loggerFactory);
var type = typeof(ResourceManagerStringLocalizerFactoryTest);
// Act
typeFactory.Create(type);
// Assert
Assert.Equal($"MyNamespace.My.Resources.ResourceManagerStringLocalizerFactoryTest", typeFactory.BaseName);
}
[Fact]
public void Create_FromType_ResourcesPathDirectorySeperatorToDot()
{
// Arrange
var locOptions = new LocalizationOptions();
locOptions.ResourcesPath = Path.Combine("My", "Resources");
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var loggerFactory = NullLoggerFactory.Instance;
var factory = new TestResourceManagerStringLocalizerFactory(
options.Object,
resourceLocationAttribute: null,
rootNamespaceAttribute: null,
loggerFactory: loggerFactory);
// Act
factory.Create(typeof(ResourceManagerStringLocalizerFactoryTest));
// Assert
Assert.Equal("Microsoft.Extensions.Localization.Tests.My.Resources." + nameof(ResourceManagerStringLocalizerFactoryTest), factory.BaseName);
}
[Fact]
public void Create_FromNameLocation_ReturnsCachedResultForSameNameLocation()
{
// Arrange
var locOptions = new LocalizationOptions();
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var loggerFactory = NullLoggerFactory.Instance;
var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory);
var location = typeof(ResourceManagerStringLocalizer).GetTypeInfo().Assembly.FullName;
// Act
var result1 = factory.Create("baseName", location);
var result2 = factory.Create("baseName", location);
// Assert
Assert.Same(result1, result2);
}
[Fact]
public void Create_FromNameLocation_ReturnsNewResultForDifferentName()
{
// Arrange
var locOptions = new LocalizationOptions();
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var loggerFactory = NullLoggerFactory.Instance;
var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory);
var location = typeof(ResourceManagerStringLocalizer).GetTypeInfo().Assembly.FullName;
// Act
var result1 = factory.Create("baseName1", location);
var result2 = factory.Create("baseName2", location);
// Assert
Assert.NotSame(result1, result2);
}
[Fact]
public void Create_FromNameLocation_ReturnsNewResultForDifferentLocation()
{
// Arrange
var locOptions = new LocalizationOptions();
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var loggerFactory = NullLoggerFactory.Instance;
var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory);
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);
var result2 = factory.Create("baseName", location2);
// Assert
Assert.NotSame(result1, result2);
}
[Fact]
public void Create_FromNameLocation_ResourcesPathDirectorySeparatorToDot()
{
// Arrange
var locOptions = new LocalizationOptions();
locOptions.ResourcesPath = Path.Combine("My", "Resources");
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var loggerFactory = NullLoggerFactory.Instance;
var factory = new TestResourceManagerStringLocalizerFactory(
options.Object,
resourceLocationAttribute: null,
rootNamespaceAttribute: null,
loggerFactory: loggerFactory);
// Act
var result1 = factory.Create("baseName", location: "Microsoft.Extensions.Localization.Tests");
// Assert
Assert.Equal("Microsoft.Extensions.Localization.Tests.My.Resources.baseName", factory.BaseName);
}
[Fact]
public void Create_FromNameLocation_NullLocationThrows()
{
// Arrange
var locOptions = new LocalizationOptions();
var options = new Mock<IOptions<LocalizationOptions>>();
options.Setup(o => o.Value).Returns(locOptions);
var loggerFactory = NullLoggerFactory.Instance;
var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory);
// Act & Assert
Assert.Throws<ArgumentNullException>(() => factory.Create("baseName", location: null));
}
}
}

View File

@ -0,0 +1,299 @@
// 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;
using System.Reflection;
using System.Resources;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Localization.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
namespace Microsoft.Extensions.Localization
{
public class ResourceManagerStringLocalizerTest
{
[Fact]
public void EnumeratorCachesCultureWalkForSameAssembly()
{
// Arrange
var resourceNamesCache = new ResourceNamesCache();
var baseName = "test";
var resourceAssembly = new TestAssemblyWrapper();
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
var resourceStreamManager = new TestResourceStringProvider(
resourceNamesCache,
resourceManager,
resourceAssembly.Assembly,
baseName);
var logger = Logger;
var localizer1 = new ResourceManagerStringLocalizer(resourceManager,
resourceStreamManager,
baseName,
resourceNamesCache,
logger);
var localizer2 = new ResourceManagerStringLocalizer(resourceManager,
resourceStreamManager,
baseName,
resourceNamesCache,
logger);
// Act
for (var i = 0; i < 5; i++)
{
localizer1.GetAllStrings().ToList();
localizer2.GetAllStrings().ToList();
}
// Assert
var expectedCallCount = GetCultureInfoDepth(CultureInfo.CurrentUICulture);
Assert.Equal(expectedCallCount, resourceAssembly.ManifestResourceStreamCallCount);
}
[Fact]
public void EnumeratorCacheIsScopedByAssembly()
{
// Arrange
var resourceNamesCache = new ResourceNamesCache();
var baseName = "test";
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,
resourceStreamManager1,
baseName,
resourceNamesCache,
logger);
var localizer2 = new ResourceManagerStringLocalizer(
resourceManager2,
resourceStreamManager2,
baseName,
resourceNamesCache,
logger);
// Act
localizer1.GetAllStrings().ToList();
localizer2.GetAllStrings().ToList();
// Assert
var expectedCallCount = GetCultureInfoDepth(CultureInfo.CurrentUICulture);
Assert.Equal(expectedCallCount, resourceAssembly1.ManifestResourceStreamCallCount);
Assert.Equal(expectedCallCount, resourceAssembly2.ManifestResourceStreamCallCount);
}
[Fact]
public void GetString_PopulatesSearchedLocationOnLocalizedString()
{
// Arrange
var baseName = "Resources.TestResource";
var resourceNamesCache = new ResourceNamesCache();
var resourceAssembly = new TestAssemblyWrapper();
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceManager, resourceAssembly.Assembly, baseName);
var logger = Logger;
var localizer = new ResourceManagerStringLocalizer(
resourceManager,
resourceStreamManager,
baseName,
resourceNamesCache,
logger);
// Act
var value = localizer["name"];
// Assert
Assert.Equal("Resources.TestResource", value.SearchedLocation);
}
[Fact]
[ReplaceCulture("en-US", "en-US")]
public void GetString_LogsLocationSearched()
{
// Arrange
var baseName = "Resources.TestResource";
var resourceNamesCache = new ResourceNamesCache();
var resourceAssembly = new TestAssemblyWrapper();
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceManager, resourceAssembly.Assembly, baseName);
var logger = Logger;
var localizer = new ResourceManagerStringLocalizer(
resourceManager,
resourceStreamManager,
baseName,
resourceNamesCache,
logger);
// Act
var value = localizer["a key!"];
// Assert
var write = Assert.Single(Sink.Writes);
Assert.Equal("ResourceManagerStringLocalizer searched for 'a key!' in 'Resources.TestResource' with culture 'en-US'.", write.State.ToString());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ResourceManagerStringLocalizer_GetAllStrings_ReturnsExpectedValue(bool includeParentCultures)
{
// Arrange
var baseName = "test";
var resourceNamesCache = new ResourceNamesCache();
var resourceAssembly = new TestAssemblyWrapper();
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceManager, resourceAssembly.Assembly, baseName);
var logger = Logger;
var localizer = new ResourceManagerStringLocalizer(
resourceManager,
resourceStreamManager,
baseName,
resourceNamesCache,
logger);
// Act
// We have to access the result so it evaluates.
var strings = localizer.GetAllStrings(includeParentCultures).ToList();
// Assert
var value = Assert.Single(strings);
Assert.Equal("TestName", value.Value);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ResourceManagerStringLocalizer_GetAllStrings_MissingResourceThrows(bool includeParentCultures)
{
// Arrange
var resourceNamesCache = new ResourceNamesCache();
var baseName = "testington";
var resourceAssembly = new TestAssemblyWrapper();
resourceAssembly.HasResources = false;
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
var logger = Logger;
var localizer = new ResourceManagerWithCultureStringLocalizer(
resourceManager,
resourceAssembly.Assembly,
baseName,
resourceNamesCache,
CultureInfo.CurrentCulture,
logger);
// Act & Assert
var exception = Assert.Throws<MissingManifestResourceException>(() =>
{
// 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()
{
var stream = new MemoryStream();
var resourceWriter = new ResourceWriter(stream);
resourceWriter.AddResource("TestName", "value");
resourceWriter.Generate();
stream.Position = 0;
return stream;
}
private static int GetCultureInfoDepth(CultureInfo culture)
{
var result = 0;
var currentCulture = culture;
while (true)
{
result++;
if (currentCulture == currentCulture.Parent)
{
break;
}
currentCulture = currentCulture.Parent;
}
return result;
}
private TestSink Sink { get; } = new TestSink();
private ILogger Logger => new TestLoggerFactory(Sink, enabled: true).CreateLogger<ResourceManagerStringLocalizer>();
public class TestResourceManager : ResourceManager
{
private AssemblyWrapper _assemblyWrapper;
public TestResourceManager(string baseName, AssemblyWrapper assemblyWrapper)
: base(baseName, assemblyWrapper.Assembly)
{
_assemblyWrapper = assemblyWrapper;
}
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)
{
}
}
public class TestAssemblyWrapper : AssemblyWrapper
{
public TestAssemblyWrapper()
: this(typeof(TestAssemblyWrapper))
{
}
public TestAssemblyWrapper(Type type)
: base(type.GetTypeInfo().Assembly)
{
}
public bool HasResources { get; set; } = true;
public int ManifestResourceStreamCallCount { get; private set; }
public override Stream GetManifestResourceStream(string name)
{
ManifestResourceStreamCallCount++;
return HasResources ? MakeResourceStream() : null;
}
}
}
}