From 29f7e4bd6209764a0f92b19e878ea378263c0302 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 16 May 2017 09:12:25 +0300 Subject: [PATCH 1/7] Add a builder API for configuring UseRequestLocalization --- samples/LocalizationSample/Startup.cs | 37 ++++------- .../ApplicationBuilderExtensions.cs | 65 +++++++++++++++++++ .../RequestLocalizationOptions.cs | 48 ++++++++++++++ 3 files changed, 125 insertions(+), 25 deletions(-) diff --git a/samples/LocalizationSample/Startup.cs b/samples/LocalizationSample/Startup.cs index ef6e638..8f1bb0d 100644 --- a/samples/LocalizationSample/Startup.cs +++ b/samples/LocalizationSample/Startup.cs @@ -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 SR) { - var supportedCultures = new List - { - 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) => { diff --git a/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs index 4e55497..245ad74 100644 --- a/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs @@ -43,6 +43,7 @@ namespace Microsoft.AspNetCore.Builder { throw new ArgumentNullException(nameof(app)); } + if (options == null) { throw new ArgumentNullException(nameof(options)); @@ -50,5 +51,69 @@ namespace Microsoft.AspNetCore.Builder return app.UseMiddleware(Options.Create(options)); } + + /// + /// Adds the to automatically set culture information for + /// requests based on information provided by the client. + /// + /// The . + /// + /// The . + public static IApplicationBuilder UseRequestLocalization( + this IApplicationBuilder app, + Action 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(Options.Create(options)); + } + + /// + /// Adds the to automatically set culture information for + /// requests based on information provided by the client. + /// + /// The . + /// The culture names to be added by the application, which is represents both supported cultures and UI cultures. + /// The . + /// + /// Note that the first culture is the default culture name. + /// + 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(nameof(cultures)); + } + + var options = new RequestLocalizationOptions() + .AddSupportedCultures(cultures) + .AddSupportedUICultures(cultures) + .SetDefaultCulture(cultures[0]); + + return app.UseMiddleware(Options.Create(options)); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Localization/RequestLocalizationOptions.cs b/src/Microsoft.AspNetCore.Localization/RequestLocalizationOptions.cs index a3a39b4..ed49ce9 100644 --- a/src/Microsoft.AspNetCore.Localization/RequestLocalizationOptions.cs +++ b/src/Microsoft.AspNetCore.Localization/RequestLocalizationOptions.cs @@ -109,5 +109,53 @@ namespace Microsoft.AspNetCore.Builder /// /// public IList RequestCultureProviders { get; set; } + + + /// + /// Adds the set of the supported cultures by the application. + /// + /// The cultures to be added. + /// The . + public RequestLocalizationOptions AddSupportedCultures(params string[] cultures) + { + var supportedCultures = new List(); + + foreach (var culture in cultures) + { + supportedCultures.Add(new CultureInfo(culture)); + } + + SupportedCultures = supportedCultures; + return this; + } + + /// + /// Adds the set of the supported UI cultures by the application. + /// + /// The UI cultures to be added. + /// The . + public RequestLocalizationOptions AddSupportedUICultures(params string[] uiCultures) + { + var supportedUICultures = new List(); + foreach (var culture in uiCultures) + { + supportedUICultures.Add(new CultureInfo(culture)); + } + + SupportedUICultures = supportedUICultures; + return this; + } + + /// + /// Set the default culture to be used by the application when a supported culture could not be determined by + /// one of the configured s. + /// + /// The default culture to be set. + /// The . + public RequestLocalizationOptions SetDefaultCulture(string defaultCulture) + { + DefaultRequestCulture = new RequestCulture(defaultCulture); + return this; + } } } From 2fe9fb4707634295e730a8ea9b25176fab089172 Mon Sep 17 00:00:00 2001 From: hishamco Date: Sat, 13 Jan 2018 23:34:43 +0300 Subject: [PATCH 2/7] WIP --- .../ApplicationBuilderExtensions.cs | 2 +- .../RequestLocalizationOptions.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs index 245ad74..4727123 100644 --- a/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs @@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Builder if (cultures.Length == 0) { - throw new ArgumentException(nameof(cultures)); + throw new ArgumentException($"The {cultures} cannot be null.", nameof(cultures)); } var options = new RequestLocalizationOptions() diff --git a/src/Microsoft.AspNetCore.Localization/RequestLocalizationOptions.cs b/src/Microsoft.AspNetCore.Localization/RequestLocalizationOptions.cs index ed49ce9..1677636 100644 --- a/src/Microsoft.AspNetCore.Localization/RequestLocalizationOptions.cs +++ b/src/Microsoft.AspNetCore.Localization/RequestLocalizationOptions.cs @@ -110,7 +110,6 @@ namespace Microsoft.AspNetCore.Builder /// public IList RequestCultureProviders { get; set; } - /// /// Adds the set of the supported cultures by the application. /// @@ -147,7 +146,7 @@ namespace Microsoft.AspNetCore.Builder } /// - /// Set the default culture to be used by the application when a supported culture could not be determined by + /// Set the default culture which is used by the application when a supported culture could not be determined by /// one of the configured s. /// /// The default culture to be set. From 36b55fc7ba1fd1b19f8878b87778af73eeaa4213 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 23 Jan 2018 07:53:14 +0300 Subject: [PATCH 3/7] Address feedback --- .../ApplicationBuilderExtensions.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs index 4727123..24f89e3 100644 --- a/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs @@ -58,6 +58,9 @@ namespace Microsoft.AspNetCore.Builder /// /// The . /// + /// + /// This will going to instantiate a new that doesn't come from the services. + /// /// The . public static IApplicationBuilder UseRequestLocalization( this IApplicationBuilder app, @@ -105,7 +108,7 @@ namespace Microsoft.AspNetCore.Builder if (cultures.Length == 0) { - throw new ArgumentException($"The {cultures} cannot be null.", nameof(cultures)); + throw new ArgumentException("Please provide at least one culture."); } var options = new RequestLocalizationOptions() From 1f00f0a95119838f6d16a9916c8afccefdef0171 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 23 Jan 2018 08:31:58 +0300 Subject: [PATCH 4/7] Use Resources.resx --- .../ApplicationBuilderExtensions.cs | 2 +- .../Properties/Resources.Designer.cs | 37 ++++++ .../Resources.resx | 123 ++++++++++++++++++ 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.AspNetCore.Localization/Properties/Resources.Designer.cs create mode 100644 src/Microsoft.AspNetCore.Localization/Resources.resx diff --git a/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs index 24f89e3..9f2a0da 100644 --- a/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Localization/ApplicationBuilderExtensions.cs @@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.Builder if (cultures.Length == 0) { - throw new ArgumentException("Please provide at least one culture."); + throw new ArgumentException(Resources.Exception_CulturesShouldNotBeEmpty); } var options = new RequestLocalizationOptions() diff --git a/src/Microsoft.AspNetCore.Localization/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Localization/Properties/Resources.Designer.cs new file mode 100644 index 0000000..581e3cd --- /dev/null +++ b/src/Microsoft.AspNetCore.Localization/Properties/Resources.Designer.cs @@ -0,0 +1,37 @@ +// +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); + + /// + /// Please provide at least one culture. + /// + 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; + } + } +} diff --git a/src/Microsoft.AspNetCore.Localization/Resources.resx b/src/Microsoft.AspNetCore.Localization/Resources.resx new file mode 100644 index 0000000..17ef00c --- /dev/null +++ b/src/Microsoft.AspNetCore.Localization/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Please provide at least one culture. + + \ No newline at end of file From 333092ad5b36b5a5b1d4d647677f60218a0966b3 Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 23 Jan 2018 14:16:18 +0300 Subject: [PATCH 5/7] Add unit tests --- .../RequestLocalizationOptionsTest.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/Microsoft.AspNetCore.Localization.Tests/RequestLocalizationOptionsTest.cs b/test/Microsoft.AspNetCore.Localization.Tests/RequestLocalizationOptionsTest.cs index 12c6f0a..7e6835c 100644 --- a/test/Microsoft.AspNetCore.Localization.Tests/RequestLocalizationOptionsTest.cs +++ b/test/Microsoft.AspNetCore.Localization.Tests/RequestLocalizationOptionsTest.cs @@ -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.Collection(options.SupportedCultures, item => Assert.Contains(item.Name, supportedCultures)); + } + + [Fact] + public void BuilderAPIs_AddSupportedUICultures() + { + // Arrange + var supportedUICultures = new[] { "en-US", "ar-YE" }; + + // Act + var options = new RequestLocalizationOptions() + .AddSupportedCultures(supportedUICultures); + + // Assert + Assert.Collection(options.SupportedUICultures, item => Assert.Contains(item.Name, supportedUICultures)); + } + + [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; From 017bb4dd4f0baa918cd570af167e7e3e30b3ab8a Mon Sep 17 00:00:00 2001 From: hishamco Date: Tue, 23 Jan 2018 23:51:15 +0300 Subject: [PATCH 6/7] Fix tests --- .../RequestLocalizationOptionsTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Microsoft.AspNetCore.Localization.Tests/RequestLocalizationOptionsTest.cs b/test/Microsoft.AspNetCore.Localization.Tests/RequestLocalizationOptionsTest.cs index 7e6835c..c33a673 100644 --- a/test/Microsoft.AspNetCore.Localization.Tests/RequestLocalizationOptionsTest.cs +++ b/test/Microsoft.AspNetCore.Localization.Tests/RequestLocalizationOptionsTest.cs @@ -96,7 +96,7 @@ namespace Microsoft.AspNetCore.Localization .AddSupportedCultures(supportedCultures); // Assert - Assert.Collection(options.SupportedCultures, item => Assert.Contains(item.Name, supportedCultures)); + Assert.Equal(supportedCultures, options.SupportedCultures.Select(c => c.Name)); } [Fact] @@ -107,10 +107,10 @@ namespace Microsoft.AspNetCore.Localization // Act var options = new RequestLocalizationOptions() - .AddSupportedCultures(supportedUICultures); + .AddSupportedUICultures(supportedUICultures); // Assert - Assert.Collection(options.SupportedUICultures, item => Assert.Contains(item.Name, supportedUICultures)); + Assert.Equal(supportedUICultures, options.SupportedUICultures.Select(c => c.Name)); } [Fact] From db4cc145224f4415f3e1de03d9b993b96ef90888 Mon Sep 17 00:00:00 2001 From: hishamco Date: Wed, 24 Jan 2018 00:22:19 +0300 Subject: [PATCH 7/7] Add functional test --- .../LocalizationWebsite/StartupBuilderAPIs.cs | 42 +++++++++++++++++++ .../LocalizationTest.cs | 9 ++++ 2 files changed, 51 insertions(+) create mode 100644 test/LocalizationWebsite/StartupBuilderAPIs.cs diff --git a/test/LocalizationWebsite/StartupBuilderAPIs.cs b/test/LocalizationWebsite/StartupBuilderAPIs.cs new file mode 100644 index 0000000..4dd4ec4 --- /dev/null +++ b/test/LocalizationWebsite/StartupBuilderAPIs.cs @@ -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 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(); + var requestCulture = requestCultureFeature.RequestCulture; + await context.Response.WriteAsync(customerStringLocalizer["Hello"]); + }); + } + } +} diff --git a/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs b/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs index f0a0a94..5f09115 100644 --- a/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs +++ b/test/Microsoft.AspNetCore.Localization.FunctionalTests/LocalizationTest.cs @@ -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);