Bunch of changes:

- Implemented pluggable strategies for determining request culture
- Added NotNull on public APIs
- Added support for a default request culture
- Added options class for configuring the middleware
- Improved the query string logic to support separate formatting & language cultures
- Implemented the logic for accept-language header
- Added more doc comments
This commit is contained in:
damianedwards 2015-05-06 16:50:43 -07:00
parent 5795d331bc
commit b11f7d51c3
21 changed files with 396 additions and 64 deletions

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.Globalization;
using System.Linq;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Localization.Internal;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Localization
{
/// <summary>
/// Determines the culture information for a request via the value of the Accept-Language header.
/// </summary>
/// <remarks>
///
/// </remarks>
public class AcceptLanguageHeaderRequestCultureStrategy : IRequestCultureStrategy
{
/// <summary>
/// The maximum number of values in the Accept-Language header to attempt to create a <see cref="CultureInfo"/>
/// from for the current request.
/// Defaults to <c>3</c>.
/// </summary>
public int MaximumAcceptLanguageHeaderValuesToTry { get; set; } = 3;
/// <inheritdoc />
public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext)
{
var acceptLanguageHeader = httpContext.Request.GetTypedHeaders().AcceptLanguage;
if (acceptLanguageHeader == null || acceptLanguageHeader.Count == 0)
{
return null;
}
var languages = acceptLanguageHeader.AsEnumerable();
if (MaximumAcceptLanguageHeaderValuesToTry > 0)
{
// We take only the first configured number of languages from the header and then order those that we
// attempt to parse as a CultureInfo to mitigate potentially spinning CPU on lots of parse attempts.
languages = languages.Take(MaximumAcceptLanguageHeaderValuesToTry);
}
var orderedLanguages = languages.OrderByDescending(h => h, StringWithQualityHeaderValueComparer.QualityComparer)
.ToList();
foreach (var language in orderedLanguages)
{
// Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in
// the CultureInfo ctor
if (language.Value != null)
{
try
{
return new RequestCulture(CultureUtilities.GetCultureFromName(language.Value));
}
catch (CultureNotFoundException) { }
}
}
return null;
}
}
}

View File

@ -0,0 +1,18 @@
// 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.AspNet.Http;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Localization
{
public class CookieRequestCultureStrategy : IRequestCultureStrategy
{
public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext)
{
// TODO
return null;
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Localization
{
public class CustomRequestCultureStrategy : IRequestCultureStrategy
{
private readonly Func<HttpContext, RequestCulture> _strategy;
public CustomRequestCultureStrategy([NotNull] Func<HttpContext, RequestCulture> strategy)
{
_strategy = strategy;
}
public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext)
{
return _strategy(httpContext);
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Localization; using Microsoft.AspNet.Localization;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Builder namespace Microsoft.AspNet.Builder
{ {
@ -12,13 +13,29 @@ namespace Microsoft.AspNet.Builder
{ {
/// <summary> /// <summary>
/// Adds the <see cref="RequestLocalizationMiddleware"/> to automatically set culture information for /// Adds the <see cref="RequestLocalizationMiddleware"/> to automatically set culture information for
/// requests based on information provided by the client. /// requests based on information provided by the client using the default options.
/// </summary> /// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/>.</param> /// <param name="builder">The <see cref="IApplicationBuilder"/>.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns> /// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseRequestLocalization(this IApplicationBuilder builder) public static IApplicationBuilder UseRequestLocalization([NotNull] this IApplicationBuilder builder)
{ {
return builder.UseMiddleware<RequestLocalizationMiddleware>(); var options = new RequestLocalizationMiddlewareOptions();
return UseRequestLocalization(builder, options);
}
/// <summary>
/// Adds the <see cref="RequestLocalizationMiddleware"/> to automatically set culture information for
/// requests based on information provided by the client.
/// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="options">The options to configure the middleware with.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseRequestLocalization(
[NotNull] this IApplicationBuilder builder,
[NotNull] RequestLocalizationMiddlewareOptions options)
{
return builder.UseMiddleware<RequestLocalizationMiddleware>(options);
} }
} }
} }

View File

@ -12,5 +12,12 @@ namespace Microsoft.AspNet.Localization
/// The <see cref="Localization.RequestCulture"/> of the request. /// The <see cref="Localization.RequestCulture"/> of the request.
/// </summary> /// </summary>
RequestCulture RequestCulture { get; } RequestCulture RequestCulture { get; }
/// <summary>
/// The <see cref="IRequestCultureStrategy"/> that determined the request's culture information.
/// If the value is <c>null</c> then no strategy was used and the request's culture was set to the value of
/// <see cref="RequestLocalizationMiddlewareOptions.DefaultRequestCulture"/>.
/// </summary>
IRequestCultureStrategy Strategy { get; }
} }
} }

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Localization
{
public interface IRequestCultureStrategy
{
RequestCulture DetermineRequestCulture(HttpContext httpContext);
}
}

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.Globalization;
namespace Microsoft.AspNet.Localization.Internal
{
public static class CultureUtilities
{
public static CultureInfo GetCultureFromName(string cultureName)
{
// Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in
// the CultureInfo ctor
if (cultureName == null)
{
return null;
}
try
{
return new CultureInfo(cultureName);
}
catch (CultureNotFoundException)
{
return null;
}
}
}
}

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 Microsoft.AspNet.Http;
using Microsoft.AspNet.Localization.Internal;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Localization
{
/// <summary>
/// Determines the culture information for a request via values in the query string.
/// </summary>
public class QueryStringRequestCultureStrategy : IRequestCultureStrategy
{
/// <summary>
/// The key that contains the culture name.
/// Defaults to "culture".
/// </summary>
public string QueryStringKey { get; set; } = "culture";
/// <summary>
/// The key that contains the UI culture name. If not specified or no value is found,
/// <see cref="QueryStringKey"/> will be used.
/// Defaults to "ui-culture".
/// </summary>
public string UIQueryStringKey { get; set; } = "ui-culture";
/// <inheritdoc />
public RequestCulture DetermineRequestCulture([NotNull] HttpContext httpContext)
{
var request = httpContext.Request;
if (!request.QueryString.HasValue)
{
return null;
}
string queryCulture = null;
string queryUICulture = null;
if (!string.IsNullOrWhiteSpace(QueryStringKey))
{
queryCulture = request.Query[QueryStringKey];
}
if (!string.IsNullOrWhiteSpace(UIQueryStringKey))
{
queryUICulture = request.Query[UIQueryStringKey];
}
if (queryCulture == null && queryUICulture == null)
{
// No values specified for either so no match
return null;
}
if (queryCulture != null && queryUICulture == null)
{
// Value for culture but not for UI culture so default to culture value for both
queryUICulture = queryCulture;
}
return new RequestCulture(
CultureUtilities.GetCultureFromName(queryCulture),
CultureUtilities.GetCultureFromName(queryUICulture));
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Globalization; using System.Globalization;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Localization namespace Microsoft.AspNet.Localization
{ {
@ -15,7 +16,7 @@ namespace Microsoft.AspNet.Localization
/// properties set to the same <see cref="CultureInfo"/> value. /// properties set to the same <see cref="CultureInfo"/> value.
/// </summary> /// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> for the request.</param> /// <param name="culture">The <see cref="CultureInfo"/> for the request.</param>
public RequestCulture(CultureInfo culture) public RequestCulture([NotNull] CultureInfo culture)
: this (culture, culture) : this (culture, culture)
{ {
@ -27,7 +28,7 @@ namespace Microsoft.AspNet.Localization
/// </summary> /// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> for the request to be used for formatting.</param> /// <param name="culture">The <see cref="CultureInfo"/> for the request to be used for formatting.</param>
/// <param name="uiCulture">The <see cref="CultureInfo"/> for the request to be used for text, i.e. language.</param> /// <param name="uiCulture">The <see cref="CultureInfo"/> for the request to be used for text, i.e. language.</param>
public RequestCulture(CultureInfo culture, CultureInfo uiCulture) public RequestCulture([NotNull] CultureInfo culture, [NotNull] CultureInfo uiCulture)
{ {
Culture = culture; Culture = culture;
UICulture = uiCulture; UICulture = uiCulture;

View File

@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Localization namespace Microsoft.AspNet.Localization
{ {
/// <summary> /// <summary>
@ -12,12 +14,16 @@ namespace Microsoft.AspNet.Localization
/// Creates a new <see cref="RequestCultureFeature"/> with the specified <see cref="Localization.RequestCulture"/>. /// Creates a new <see cref="RequestCultureFeature"/> with the specified <see cref="Localization.RequestCulture"/>.
/// </summary> /// </summary>
/// <param name="requestCulture">The <see cref="Localization.RequestCulture"/>.</param> /// <param name="requestCulture">The <see cref="Localization.RequestCulture"/>.</param>
public RequestCultureFeature(RequestCulture requestCulture) public RequestCultureFeature([NotNull] RequestCulture requestCulture, IRequestCultureStrategy strategy)
{ {
RequestCulture = requestCulture; RequestCulture = requestCulture;
Strategy = strategy;
} }
/// <inheritdoc /> /// <inheritdoc />
public RequestCulture RequestCulture { get; } public RequestCulture RequestCulture { get; }
/// <inheritdoc />
public IRequestCultureStrategy Strategy { get; }
} }
} }

View File

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Localization namespace Microsoft.AspNet.Localization
{ {
@ -16,14 +17,17 @@ namespace Microsoft.AspNet.Localization
public class RequestLocalizationMiddleware public class RequestLocalizationMiddleware
{ {
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly RequestLocalizationMiddlewareOptions _options;
/// <summary> /// <summary>
/// Creates a new <see cref="RequestLocalizationMiddleware"/>. /// Creates a new <see cref="RequestLocalizationMiddleware"/>.
/// </summary> /// </summary>
/// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param> /// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
public RequestLocalizationMiddleware(RequestDelegate next) /// <param name="options"></param>
public RequestLocalizationMiddleware([NotNull] RequestDelegate next, [NotNull] RequestLocalizationMiddlewareOptions options)
{ {
_next = next; _next = next;
_options = options;
} }
/// <summary> /// <summary>
@ -31,42 +35,35 @@ namespace Microsoft.AspNet.Localization
/// </summary> /// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param> /// <param name="context">The <see cref="HttpContext"/>.</param>
/// <returns>A <see cref="Task"/> that completes when the middleware has completed processing.</returns> /// <returns>A <see cref="Task"/> that completes when the middleware has completed processing.</returns>
public async Task Invoke(HttpContext context) public async Task Invoke([NotNull] HttpContext context)
{ {
// TODO: Make this read from Accept-Language header, cookie, app-provided delegate, etc. var requestCulture = _options.DefaultRequestCulture ??
if (context.Request.QueryString.HasValue) new RequestCulture(CultureInfo.DefaultThreadCurrentCulture, CultureInfo.DefaultThreadCurrentUICulture);
IRequestCultureStrategy winningStrategy = null;
if (_options.RequestCultureStrategies != null)
{ {
var queryCulture = context.Request.Query["culture"]; foreach (var strategy in _options.RequestCultureStrategies)
if (!string.IsNullOrEmpty(queryCulture))
{ {
var requestCulture = new RequestCulture(new CultureInfo(queryCulture)); var result = strategy.DetermineRequestCulture(context);
if (result != null)
context.SetFeature<IRequestCultureFeature>(new RequestCultureFeature(requestCulture)); {
requestCulture = result;
var originalCulture = CultureInfo.CurrentCulture; winningStrategy = strategy;
var originalUICulture = CultureInfo.CurrentUICulture; break;
}
SetCurrentCulture(requestCulture);
await _next(context);
return;
} }
} }
else
{ context.SetFeature<IRequestCultureFeature>(new RequestCultureFeature(requestCulture, winningStrategy));
// NOTE: The below doesn't seem to be needed anymore now that DNX is correctly managing culture across
// async calls but we'll need to verify properly. SetCurrentThreadCulture(requestCulture);
// Forcibly set thread to en-US as sometimes previous threads have wrong culture across async calls,
// see note above.
//var defaultRequestCulture = new RequestCulture(new CultureInfo("en-US"));
//SetCurrentCulture(defaultRequestCulture);
}
await _next(context); await _next(context);
} }
private void SetCurrentCulture(RequestCulture requestCulture) private static void SetCurrentThreadCulture(RequestCulture requestCulture)
{ {
#if DNX451 #if DNX451
Thread.CurrentThread.CurrentCulture = requestCulture.Culture; Thread.CurrentThread.CurrentCulture = requestCulture.Culture;

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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace Microsoft.AspNet.Localization
{
/// <summary>
/// Specifies options for the <see cref="RequestLocalizationMiddleware"/>.
/// </summary>
public class RequestLocalizationMiddlewareOptions
{
/// <summary>
/// Creates a new <see cref="RequestLocalizationMiddlewareOptions"/> with default values.
/// </summary>
public RequestLocalizationMiddlewareOptions()
{
DefaultRequestCulture = new RequestCulture(CultureInfo.DefaultThreadCurrentCulture, CultureInfo.DefaultThreadCurrentUICulture);
RequestCultureStrategies = new List<IRequestCultureStrategy>
{
new QueryStringRequestCultureStrategy(),
new CookieRequestCultureStrategy(),
new AcceptLanguageHeaderRequestCultureStrategy { MaximumAcceptLanguageHeaderValuesToTry = MaximumAcceptLanguageHeaderValuesToTry }
};
}
/// <summary>
/// The maximum number of values in the Accept-Language header to attempt to create a <see cref="CultureInfo"/>
/// from for the current request.
/// Defaults to <c>3</c>.
/// </summary>
public int MaximumAcceptLanguageHeaderValuesToTry { get; set; } = 3;
/// <summary>
/// The default <see cref="RequestCulture"/> to use. This value will be used if none of the configured
/// <see cref="IRequestCultureStrategy"/> options result in a non-<c>null</c> result.
/// Defaults to <see cref="RequestCulture.Culture"/> set to <see cref="CultureInfo.DefaultThreadCurrentCulture"/>
/// and <see cref="RequestCulture.UICulture"/> set to <see cref="CultureInfo.DefaultThreadCurrentUICulture"/>.
/// </summary>
public RequestCulture DefaultRequestCulture { get; set; }
/// <summary>
/// The cultures supported by the application. If this value is non-<c>null</c>, the
/// <see cref="RequestLocalizationMiddleware"/> will only set the current request culture to an entry in this
/// list. A value of <c>null</c> means all cultures are supported.
/// Defaults to <c>null</c>.
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Improves usability")]
public IList<CultureInfo> SupportedCultures { get; set; }
/// <summary>
/// The UI cultures supported by the application. If this value is non-<c>null</c>, the
/// <see cref="RequestLocalizationMiddleware"/> will only set the current request culture to an entry in this
/// list. A value of <c>null</c> means all cultures are supported.
/// Defaults to <c>null</c>.
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Improves usability")]
public IList<CultureInfo> SupportedUICultures { get; set; }
/// <summary>
/// An ordered list of strategies used to determine a request's culture information. The first strategy that
/// returns a non-<c>null</c> result for a given request will be used.
/// Defaults to the following:
/// <list type="number">
/// <item><description><see cref="QueryStringRequestCultureStrategy"/></description></item>
/// <item><description><see cref="CookieRequestCultureStrategy"/></description></item>
/// <item><description><see cref="AcceptLanguageHeaderRequestCultureStrategy"/></description></item>
/// </list>
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Improves usability")]
public IList<IRequestCultureStrategy> RequestCultureStrategies { get; set; }
}
}

View File

@ -4,6 +4,7 @@
"dependencies": { "dependencies": {
"Microsoft.Framework.Localization.Abstractions": "1.0.0-*", "Microsoft.Framework.Localization.Abstractions": "1.0.0-*",
"Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" },
"Microsoft.AspNet.Http.Extensions": "1.0.0-*" "Microsoft.AspNet.Http.Extensions": "1.0.0-*"
}, },
@ -13,6 +14,7 @@
"dependencies": { "dependencies": {
"System.Collections": "4.0.10-*", "System.Collections": "4.0.10-*",
"System.Linq": "4.0.0-*", "System.Linq": "4.0.0-*",
"System.Globalization": "4.0.10-*",
"System.Threading": "4.0.10-*", "System.Threading": "4.0.10-*",
"Microsoft.CSharp": "4.0.0-*" "Microsoft.CSharp": "4.0.0-*"
} }

View File

@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.Internal;
namespace Microsoft.Framework.Localization namespace Microsoft.Framework.Localization
{ {
/// <summary> /// <summary>
@ -13,7 +15,7 @@ namespace Microsoft.Framework.Localization
/// </summary> /// </summary>
/// <param name="name">The name of the string in the resource it was loaded from.</param> /// <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="value">The actual string.</param>
public LocalizedString(string name, string value) public LocalizedString([NotNull] string name, [NotNull] string value)
: this(name, value, resourceNotFound: false) : this(name, value, resourceNotFound: false)
{ {
@ -25,7 +27,7 @@ namespace Microsoft.Framework.Localization
/// <param name="name">The name of the string in the resource it was loaded from.</param> /// <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="value">The actual string.</param>
/// <param name="resourceNotFound">Whether the string was found in a resource. Set this to <c>false</c> to indicate an alternate string value was used.</param> /// <param name="resourceNotFound">Whether the string was found in a resource. Set this to <c>false</c> to indicate an alternate string value was used.</param>
public LocalizedString(string name, string value, bool resourceNotFound) public LocalizedString([NotNull] string name, [NotNull] string value, bool resourceNotFound)
{ {
Name = name; Name = name;
Value = value; Value = value;

View File

@ -4,6 +4,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Microsoft.Framework.Internal;
namespace Microsoft.Framework.Localization namespace Microsoft.Framework.Localization
{ {
@ -19,7 +20,7 @@ namespace Microsoft.Framework.Localization
/// Creates a new <see cref="StringLocalizer{TResourceSource}"/>. /// Creates a new <see cref="StringLocalizer{TResourceSource}"/>.
/// </summary> /// </summary>
/// <param name="factory">The <see cref="IStringLocalizerFactory"/> to use.</param> /// <param name="factory">The <see cref="IStringLocalizerFactory"/> to use.</param>
public StringLocalizer(IStringLocalizerFactory factory) public StringLocalizer([NotNull] IStringLocalizerFactory factory)
{ {
_localizer = factory.Create(typeof(TResourceSource)); _localizer = factory.Create(typeof(TResourceSource));
} }
@ -28,16 +29,17 @@ namespace Microsoft.Framework.Localization
public virtual IStringLocalizer WithCulture(CultureInfo culture) => _localizer.WithCulture(culture); public virtual IStringLocalizer WithCulture(CultureInfo culture) => _localizer.WithCulture(culture);
/// <inheritdoc /> /// <inheritdoc />
public virtual LocalizedString this[string key] => _localizer[key]; public virtual LocalizedString this[[NotNull] string key] => _localizer[key];
/// <inheritdoc /> /// <inheritdoc />
public virtual LocalizedString this[string key, params object[] arguments] => _localizer[key, arguments]; public virtual LocalizedString this[[NotNull] string key, params object[] arguments] =>
_localizer[key, arguments];
/// <inheritdoc /> /// <inheritdoc />
public virtual LocalizedString GetString(string key) => _localizer.GetString(key); public virtual LocalizedString GetString([NotNull] string key) => _localizer.GetString(key);
/// <inheritdoc /> /// <inheritdoc />
public virtual LocalizedString GetString(string key, params object[] arguments) => public virtual LocalizedString GetString([NotNull] string key, params object[] arguments) =>
_localizer.GetString(key, arguments); _localizer.GetString(key, arguments);
/// <inheritdoc /> /// <inheritdoc />

View File

@ -3,7 +3,7 @@
"description": "Abstractions of application localization services.", "description": "Abstractions of application localization services.",
"dependencies": { "dependencies": {
"Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" }
}, },
"frameworks": { "frameworks": {

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.Internal;
using Microsoft.Framework.Localization; using Microsoft.Framework.Localization;
namespace Microsoft.Framework.DependencyInjection namespace Microsoft.Framework.DependencyInjection
@ -15,7 +16,7 @@ namespace Microsoft.Framework.DependencyInjection
/// </summary> /// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns> /// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddLocalization(this IServiceCollection services) public static IServiceCollection AddLocalization([NotNull] this IServiceCollection services)
{ {
services.TryAdd(new ServiceDescriptor( services.TryAdd(new ServiceDescriptor(
typeof(IStringLocalizerFactory), typeof(IStringLocalizerFactory),

View File

@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Reflection; using System.Reflection;
using System.Resources; using System.Resources;
using Microsoft.Framework.Internal;
namespace Microsoft.Framework.Localization namespace Microsoft.Framework.Localization
{ {
@ -27,9 +28,9 @@ namespace Microsoft.Framework.Localization
/// <param name="resourceAssembly">The <see cref="Assembly"/> that contains the strings as embedded resources.</param> /// <param name="resourceAssembly">The <see cref="Assembly"/> that contains the strings as embedded resources.</param>
/// <param name="baseName">The base name of the embedded resource in the <see cref="Assembly"/> that contains the strings.</param> /// <param name="baseName">The base name of the embedded resource in the <see cref="Assembly"/> that contains the strings.</param>
public ResourceManagerStringLocalizer( public ResourceManagerStringLocalizer(
ResourceManager resourceManager, [NotNull] ResourceManager resourceManager,
Assembly resourceAssembly, [NotNull] Assembly resourceAssembly,
string baseName) [NotNull] string baseName)
{ {
ResourceManager = resourceManager; ResourceManager = resourceManager;
ResourceAssembly = resourceAssembly; ResourceAssembly = resourceAssembly;
@ -52,20 +53,20 @@ namespace Microsoft.Framework.Localization
protected string ResourceBaseName { get; } protected string ResourceBaseName { get; }
/// <inheritdoc /> /// <inheritdoc />
public virtual LocalizedString this[string name] => GetString(name); public virtual LocalizedString this[[NotNull] string name] => GetString(name);
/// <inheritdoc /> /// <inheritdoc />
public virtual LocalizedString this[string name, params object[] arguments] => GetString(name, arguments); public virtual LocalizedString this[[NotNull] string name, params object[] arguments] => GetString(name, arguments);
/// <inheritdoc /> /// <inheritdoc />
public virtual LocalizedString GetString(string name) public virtual LocalizedString GetString([NotNull] string name)
{ {
var value = GetStringSafely(name, null); var value = GetStringSafely(name, null);
return new LocalizedString(name, value ?? name, resourceNotFound: value == null); return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
} }
/// <inheritdoc /> /// <inheritdoc />
public virtual LocalizedString GetString(string name, params object[] arguments) public virtual LocalizedString GetString([NotNull] string name, params object[] arguments)
{ {
var format = GetStringSafely(name, null); var format = GetStringSafely(name, null);
var value = string.Format(format ?? name, arguments); var value = string.Format(format ?? name, arguments);
@ -95,7 +96,7 @@ namespace Microsoft.Framework.Localization
/// <param name="name">The name of the string resource.</param> /// <param name="name">The name of the string resource.</param>
/// <param name="culture">The <see cref="CultureInfo"/> to get the string for.</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> /// <returns>The resource string, or <c>null</c> if none was found.</returns>
protected string GetStringSafely(string name, CultureInfo culture) protected string GetStringSafely([NotNull] string name, [NotNull] CultureInfo culture)
{ {
var cacheKey = new MissingManifestCacheKey(name, culture); var cacheKey = new MissingManifestCacheKey(name, culture);
if (_missingManifestCache.ContainsKey(cacheKey)) if (_missingManifestCache.ContainsKey(cacheKey))
@ -133,7 +134,7 @@ namespace Microsoft.Framework.Localization
/// </summary> /// </summary>
/// <param name="culture">The <see cref="CultureInfo"/> to get strings for.</param> /// <param name="culture">The <see cref="CultureInfo"/> to get strings for.</param>
/// <returns>The <see cref="IEnumerator{LocalizedString}"/>.</returns> /// <returns>The <see cref="IEnumerator{LocalizedString}"/>.</returns>
protected IEnumerator<LocalizedString> GetEnumerator(CultureInfo culture) protected IEnumerator<LocalizedString> GetEnumerator([NotNull] CultureInfo culture)
{ {
// TODO: I'm sure something here should be cached, probably the whole result // TODO: I'm sure something here should be cached, probably the whole result
var resourceNames = GetResourceNamesFromCultureHierarchy(culture); var resourceNames = GetResourceNamesFromCultureHierarchy(culture);

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Resources; using System.Resources;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Runtime; using Microsoft.Framework.Runtime;
namespace Microsoft.Framework.Localization namespace Microsoft.Framework.Localization
@ -19,7 +20,7 @@ namespace Microsoft.Framework.Localization
/// Creates a new <see cref="ResourceManagerStringLocalizer"/>. /// Creates a new <see cref="ResourceManagerStringLocalizer"/>.
/// </summary> /// </summary>
/// <param name="applicationEnvironment"></param> /// <param name="applicationEnvironment"></param>
public ResourceManagerStringLocalizerFactory(IApplicationEnvironment applicationEnvironment) public ResourceManagerStringLocalizerFactory([NotNull] IApplicationEnvironment applicationEnvironment)
{ {
_applicationEnvironment = applicationEnvironment; _applicationEnvironment = applicationEnvironment;
} }
@ -30,7 +31,7 @@ namespace Microsoft.Framework.Localization
/// </summary> /// </summary>
/// <param name="resourceSource">The <see cref="Type"/>.</param> /// <param name="resourceSource">The <see cref="Type"/>.</param>
/// <returns>The <see cref="ResourceManagerStringLocalizer"/>.</returns> /// <returns>The <see cref="ResourceManagerStringLocalizer"/>.</returns>
public IStringLocalizer Create(Type resourceSource) public IStringLocalizer Create([NotNull] Type resourceSource)
{ {
var typeInfo = resourceSource.GetTypeInfo(); var typeInfo = resourceSource.GetTypeInfo();
var assembly = typeInfo.Assembly; var assembly = typeInfo.Assembly;
@ -44,7 +45,7 @@ namespace Microsoft.Framework.Localization
/// <param name="baseName">The base name of the resource to load strings from.</param> /// <param name="baseName">The base name of the resource to load strings from.</param>
/// <param name="location">The location to load resources from.</param> /// <param name="location">The location to load resources from.</param>
/// <returns>The <see cref="ResourceManagerStringLocalizer"/>.</returns> /// <returns>The <see cref="ResourceManagerStringLocalizer"/>.</returns>
public IStringLocalizer Create(string baseName, string location) public IStringLocalizer Create([NotNull] string baseName, [NotNull] string location)
{ {
var assembly = Assembly.Load(new AssemblyName(location ?? _applicationEnvironment.ApplicationName)); var assembly = Assembly.Load(new AssemblyName(location ?? _applicationEnvironment.ApplicationName));

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Reflection; using System.Reflection;
using System.Resources; using System.Resources;
using Microsoft.Framework.Internal;
namespace Microsoft.Framework.Localization namespace Microsoft.Framework.Localization
{ {
@ -25,30 +26,30 @@ namespace Microsoft.Framework.Localization
/// <param name="baseName">The base name of the embedded resource in the <see cref="Assembly"/> that contains the strings.</param> /// <param name="baseName">The base name of the embedded resource in the <see cref="Assembly"/> that contains the strings.</param>
/// <param name="culture">The specific <see cref="CultureInfo"/> to use.</param> /// <param name="culture">The specific <see cref="CultureInfo"/> to use.</param>
public ResourceManagerWithCultureStringLocalizer( public ResourceManagerWithCultureStringLocalizer(
ResourceManager resourceManager, [NotNull] ResourceManager resourceManager,
Assembly assembly, [NotNull] Assembly assembly,
string baseName, [NotNull] string baseName,
CultureInfo culture) [NotNull] CultureInfo culture)
: base(resourceManager, assembly, baseName) : base(resourceManager, assembly, baseName)
{ {
_culture = culture; _culture = culture;
} }
/// <inheritdoc /> /// <inheritdoc />
public override LocalizedString this[string name] => GetString(name); public override LocalizedString this[[NotNull] string name] => GetString(name);
/// <inheritdoc /> /// <inheritdoc />
public override LocalizedString this[string name, params object[] arguments] => GetString(name, arguments); public override LocalizedString this[[NotNull] string name, params object[] arguments] => GetString(name, arguments);
/// <inheritdoc /> /// <inheritdoc />
public override LocalizedString GetString(string name) public override LocalizedString GetString([NotNull] string name)
{ {
var value = GetStringSafely(name, _culture); var value = GetStringSafely(name, _culture);
return new LocalizedString(name, value ?? name); return new LocalizedString(name, value ?? name);
} }
/// <inheritdoc /> /// <inheritdoc />
public override LocalizedString GetString(string name, params object[] arguments) public override LocalizedString GetString([NotNull] string name, params object[] arguments)
{ {
var format = GetStringSafely(name, _culture); var format = GetStringSafely(name, _culture);
var value = string.Format(_culture, format ?? name, arguments); var value = string.Format(_culture, format ?? name, arguments);

View File

@ -5,6 +5,7 @@
"dependencies": { "dependencies": {
"Microsoft.Framework.DependencyInjection.Abstractions": "1.0.0-*", "Microsoft.Framework.DependencyInjection.Abstractions": "1.0.0-*",
"Microsoft.Framework.Localization.Abstractions": "1.0.0-*", "Microsoft.Framework.Localization.Abstractions": "1.0.0-*",
"Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" },
"Microsoft.Framework.Runtime.Abstractions": "1.0.0-*", "Microsoft.Framework.Runtime.Abstractions": "1.0.0-*",
"System.Resources.ReaderWriter": "4.0.0-*" "System.Resources.ReaderWriter": "4.0.0-*"
}, },