Merge pull request #369 from hishamco/useRequestLocalization

Add a builder API for configuring UseRequestLocalization
This commit is contained in:
Artak Mkrtchyan 2018-02-07 09:17:44 -08:00 коммит произвёл GitHub
Родитель 5005d6c94b db4cc14522
Коммит 629ffb7ede
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 381 добавлений и 25 удалений

Просмотреть файл

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@ -24,31 +23,19 @@ namespace LocalizationSample
public void Configure(IApplicationBuilder app, IStringLocalizer<Startup> SR)
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("en-AU"),
new CultureInfo("en-GB"),
new CultureInfo("es-ES"),
new CultureInfo("ja-JP"),
new CultureInfo("fr-FR"),
new CultureInfo("zh"),
new CultureInfo("zh-CN")
};
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
// Optionally create an app-specific provider with just a delegate, e.g. look up user preference from DB.
// Inserting it as position 0 ensures it has priority over any of the default providers.
//options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(async context =>
//{
var supportedCultures = new [] { "en-US", "en-AU", "en-GB", "es-ES", "ja-JP", "fr-FR", "zh", "zh-CN" };
app.UseRequestLocalization(options =>
options
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures)
.SetDefaultCulture(supportedCultures[0])
// Optionally create an app-specific provider with just a delegate, e.g. look up user preference from DB.
// Inserting it as position 0 ensures it has priority over any of the default providers.
//.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(async context =>
//{
//}));
app.UseRequestLocalization(options);
//}));
);
app.Use(async (context, next) =>
{

Просмотреть файл

@ -43,6 +43,7 @@ namespace Microsoft.AspNetCore.Builder
{
throw new ArgumentNullException(nameof(app));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
@ -50,5 +51,72 @@ namespace Microsoft.AspNetCore.Builder
return app.UseMiddleware<RequestLocalizationMiddleware>(Options.Create(options));
}
/// <summary>
/// Adds the <see cref="RequestLocalizationMiddleware"/> to automatically set culture information for
/// requests based on information provided by the client.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="optionsAction"></param>
/// <remarks>
/// This will going to instantiate a new <see cref="RequestLocalizationOptions"/> that doesn't come from the services.
/// </remarks>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseRequestLocalization(
this IApplicationBuilder app,
Action<RequestLocalizationOptions> optionsAction)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (optionsAction == null)
{
throw new ArgumentNullException(nameof(optionsAction));
}
var options = new RequestLocalizationOptions();
optionsAction.Invoke(options);
return app.UseMiddleware<RequestLocalizationMiddleware>(Options.Create(options));
}
/// <summary>
/// Adds the <see cref="RequestLocalizationMiddleware"/> to automatically set culture information for
/// requests based on information provided by the client.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="cultures">The culture names to be added by the application, which is represents both supported cultures and UI cultures.</param>
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
/// <remarks>
/// Note that the first culture is the default culture name.
/// </remarks>
public static IApplicationBuilder UseRequestLocalization(
this IApplicationBuilder app,
params string[] cultures)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (cultures == null)
{
throw new ArgumentNullException(nameof(cultures));
}
if (cultures.Length == 0)
{
throw new ArgumentException(Resources.Exception_CulturesShouldNotBeEmpty);
}
var options = new RequestLocalizationOptions()
.AddSupportedCultures(cultures)
.AddSupportedUICultures(cultures)
.SetDefaultCulture(cultures[0]);
return app.UseMiddleware<RequestLocalizationMiddleware>(Options.Create(options));
}
}
}

37
src/Microsoft.AspNetCore.Localization/Properties/Resources.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,37 @@
// <auto-generated />
namespace Microsoft.AspNetCore.Localization
{
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Localization.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Please provide at least one culture.
/// </summary>
internal static string Exception_CulturesShouldNotBeEmpty
{
get { return GetString("Exception_CulturesShouldNotBeEmpty"); }
}
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;
}
}
}

Просмотреть файл

@ -109,5 +109,52 @@ namespace Microsoft.AspNetCore.Builder
/// </list>
/// </summary>
public IList<IRequestCultureProvider> RequestCultureProviders { get; set; }
/// <summary>
/// Adds the set of the supported cultures by the application.
/// </summary>
/// <param name="cultures">The cultures to be added.</param>
/// <returns>The <see cref="RequestLocalizationOptions"/>.</returns>
public RequestLocalizationOptions AddSupportedCultures(params string[] cultures)
{
var supportedCultures = new List<CultureInfo>();
foreach (var culture in cultures)
{
supportedCultures.Add(new CultureInfo(culture));
}
SupportedCultures = supportedCultures;
return this;
}
/// <summary>
/// Adds the set of the supported UI cultures by the application.
/// </summary>
/// <param name="uiCultures">The UI cultures to be added.</param>
/// <returns>The <see cref="RequestLocalizationOptions"/>.</returns>
public RequestLocalizationOptions AddSupportedUICultures(params string[] uiCultures)
{
var supportedUICultures = new List<CultureInfo>();
foreach (var culture in uiCultures)
{
supportedUICultures.Add(new CultureInfo(culture));
}
SupportedUICultures = supportedUICultures;
return this;
}
/// <summary>
/// Set the default culture which is used by the application when a supported culture could not be determined by
/// one of the configured <see cref="IRequestCultureProvider"/>s.
/// </summary>
/// <param name="defaultCulture">The default culture to be set.</param>
/// <returns>The <see cref="RequestLocalizationOptions"/>.</returns>
public RequestLocalizationOptions SetDefaultCulture(string defaultCulture)
{
DefaultRequestCulture = new RequestCulture(defaultCulture);
return this;
}
}
}

Просмотреть файл

@ -0,0 +1,123 @@
<?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="Exception_CulturesShouldNotBeEmpty" xml:space="preserve">
<value>Please provide at least one culture.</value>
</data>
</root>

Просмотреть файл

@ -0,0 +1,42 @@
// 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 LocalizationWebsite.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace LocalizationWebsite
{
public class StartupBuilderAPIs
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
}
public void Configure(
IApplicationBuilder app,
ILoggerFactory loggerFactory,
IStringLocalizer<Customer> customerStringLocalizer)
{
var supportedCultures = new[] { "en-US", "fr-FR" };
app.UseRequestLocalization(options =>
options
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures)
.SetDefaultCulture("ar-YE")
);
app.Run(async (context) =>
{
var requestCultureFeature = context.Features.Get<IRequestCultureFeature>();
var requestCulture = requestCultureFeature.RequestCulture;
await context.Response.WriteAsync(customerStringLocalizer["Hello"]);
});
}
}
}

Просмотреть файл

@ -77,6 +77,15 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests
"Bonjour from StartupResourcesAtRootFolder Bonjour from Test in root folder Bonjour from Customer in Models folder");
}
[Fact]
public Task Localization_BuilderAPIs()
{
return RunTest(
typeof(StartupBuilderAPIs),
"ar-YE",
"Hello");
}
private async Task RunTest(Type startupType, string culture, string expected)
{
var webHostBuilder = new WebHostBuilder().UseStartup(startupType);

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Xunit;
@ -84,6 +85,48 @@ namespace Microsoft.AspNetCore.Localization
Assert.Collection(options.SupportedUICultures, item => Assert.Equal(explicitCulture, item));
}
[Fact]
public void BuilderAPIs_AddSupportedCultures()
{
// Arrange
var supportedCultures = new[] { "en-US", "ar-YE" };
// Act
var options = new RequestLocalizationOptions()
.AddSupportedCultures(supportedCultures);
// Assert
Assert.Equal(supportedCultures, options.SupportedCultures.Select(c => c.Name));
}
[Fact]
public void BuilderAPIs_AddSupportedUICultures()
{
// Arrange
var supportedUICultures = new[] { "en-US", "ar-YE" };
// Act
var options = new RequestLocalizationOptions()
.AddSupportedUICultures(supportedUICultures);
// Assert
Assert.Equal(supportedUICultures, options.SupportedUICultures.Select(c => c.Name));
}
[Fact]
public void BuilderAPIs_SetDefaultCulture()
{
// Arrange
var defaultCulture = "ar-YE";
// Act
var options = new RequestLocalizationOptions()
.SetDefaultCulture(defaultCulture);
// Assert
Assert.Equal(defaultCulture, options.DefaultRequestCulture.Culture.Name);
}
public void Dispose()
{
CultureInfo.CurrentCulture = _initialCulture;