From d8a523a07f76f722b00a2a95f74668b9fc93e6a4 Mon Sep 17 00:00:00 2001 From: Kirthi Krishnamraju Date: Mon, 1 Jun 2015 16:15:38 -0700 Subject: [PATCH] Added Mvc localization project --- Mvc.NoFun.sln | 32 +- Mvc.sln | 30 ++ .../DefaultAssemblyProvider.cs | 1 + .../HtmlLocalizer.cs | 191 ++++++++++++ .../HtmlLocalizerFactory.cs | 53 ++++ .../HtmlLocalizerOfT.cs | 60 ++++ .../IHtmlLocalizer.cs | 38 +++ .../IHtmlLocalizerFactory.cs | 30 ++ .../IHtmlLocalizerOfT.cs | 13 + .../IViewLocalizer.cs | 12 + .../LocalizedHtmlString.cs | 51 ++++ .../Microsoft.AspNet.Mvc.Localization.xproj | 18 ++ ...LocalizationServiceCollectionExtensions.cs | 52 ++++ .../Properties/Resources.Designer.cs | 62 ++++ .../ViewLocalizer.cs | 76 +++++ .../project.json | 31 ++ .../MvcServiceCollectionExtensions.cs | 28 -- src/Microsoft.AspNet.Mvc/project.json | 1 + .../LocalizationTest.cs | 100 +++++++ .../HtmlLocalizerOfTTest.cs | 63 ++++ .../HtmlLocalizerTest.cs | 141 +++++++++ ...crosoft.AspNet.Mvc.Localization.Test.xproj | 20 ++ ...lizationServiceCollectionExtensionsTest.cs | 274 ++++++++++++++++++ .../ViewLocalizerTest.cs | 80 +++++ .../project.json | 24 ++ .../Controllers/HomeController.cs | 15 +- .../Resources/HomeController.fr.resx | 129 +++++++++ .../Views.Home.Locpage.cshtml.en-GB.resx | 132 +++++++++ .../Views.Home.Locpage.cshtml.fr.resx | 132 +++++++++ ....Shared._LocalizationLayout.cshtml.fr.resx | 141 +++++++++ test/WebSites/LocalizationWebSite/Startup.cs | 5 + .../TestStringLocalizer.cs | 96 ++++++ .../TestStringLocalizerFactory.cs | 52 ++++ .../Views/Home/Locpage.cshtml | 7 + .../Views/Shared/_LocalizationLayout.cshtml | 1 + .../Views/_ViewImports.cshtml | 5 + .../WebSites/LocalizationWebSite/project.json | 24 +- test/WebSites/RazorWebSite/Startup.cs | 1 - test/WebSites/RazorWebSite/project.json | 1 + 39 files changed, 2178 insertions(+), 44 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizer.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizerFactory.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizerOfT.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizer.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizerFactory.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizerOfT.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/IViewLocalizer.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/LocalizedHtmlString.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/Microsoft.AspNet.Mvc.Localization.xproj create mode 100644 src/Microsoft.AspNet.Mvc.Localization/MvcLocalizationServiceCollectionExtensions.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/Properties/Resources.Designer.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/ViewLocalizer.cs create mode 100644 src/Microsoft.AspNet.Mvc.Localization/project.json create mode 100644 test/Microsoft.AspNet.Mvc.Localization.Test/HtmlLocalizerOfTTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Localization.Test/HtmlLocalizerTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Localization.Test/Microsoft.AspNet.Mvc.Localization.Test.xproj create mode 100644 test/Microsoft.AspNet.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Localization.Test/ViewLocalizerTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Localization.Test/project.json create mode 100644 test/WebSites/LocalizationWebSite/Resources/HomeController.fr.resx create mode 100644 test/WebSites/LocalizationWebSite/Resources/Views.Home.Locpage.cshtml.en-GB.resx create mode 100644 test/WebSites/LocalizationWebSite/Resources/Views.Home.Locpage.cshtml.fr.resx create mode 100644 test/WebSites/LocalizationWebSite/Resources/Views.Shared._LocalizationLayout.cshtml.fr.resx create mode 100644 test/WebSites/LocalizationWebSite/TestStringLocalizer.cs create mode 100644 test/WebSites/LocalizationWebSite/TestStringLocalizerFactory.cs create mode 100644 test/WebSites/LocalizationWebSite/Views/Home/Locpage.cshtml create mode 100644 test/WebSites/LocalizationWebSite/Views/Shared/_LocalizationLayout.cshtml create mode 100644 test/WebSites/LocalizationWebSite/Views/_ViewImports.cshtml diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 965e414066..f1df7292ed 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -84,6 +84,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Cors.T EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.DataAnnotations.Test", "test\Microsoft.AspNet.Mvc.DataAnnotations.Test\Microsoft.AspNet.Mvc.DataAnnotations.Test.xproj", "{827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Localization", "src\Microsoft.AspNet.Mvc.Localization\Microsoft.AspNet.Mvc.Localization.xproj", "{50893B10-5735-4F35-9995-F81DA3F0189E}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Localization.Test", "test\Microsoft.AspNet.Mvc.Localization.Test\Microsoft.AspNet.Mvc.Localization.Test.xproj", "{8FC726B5-E766-44E0-8B38-1313B6D8D9A7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -371,7 +375,7 @@ Global {F21E225B-190B-4DAA-8B0A-05986D231F56}.Release|Mixed Platforms.Build.0 = Release|Any CPU {F21E225B-190B-4DAA-8B0A-05986D231F56}.Release|x86.ActiveCfg = Release|Any CPU {F21E225B-190B-4DAA-8B0A-05986D231F56}.Release|x86.Build.0 = Release|Any CPU - {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Any CPU.Build.0 = Debug|Any CPU {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU @@ -491,6 +495,30 @@ Global {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|Mixed Platforms.Build.0 = Release|Any CPU {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|x86.ActiveCfg = Release|Any CPU {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|x86.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|x86.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|x86.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Any CPU.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|x86.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|x86.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|x86.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Any CPU.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|x86.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -531,5 +559,7 @@ Global {2DD786CA-7AF7-437A-B499-801A589B9A1C} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {50893B10-5735-4F35-9995-F81DA3F0189E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} EndGlobalSection EndGlobal diff --git a/Mvc.sln b/Mvc.sln index e2ac80d036..276f95a909 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -184,6 +184,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.DataAn EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.ViewFeatures.Test", "test\Microsoft.AspNet.Mvc.ViewFeatures.Test\Microsoft.AspNet.Mvc.ViewFeatures.Test.xproj", "{60873DFA-97B9-419E-BAA3-940FC9B07085}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Localization", "src\Microsoft.AspNet.Mvc.Localization\Microsoft.AspNet.Mvc.Localization.xproj", "{50893B10-5735-4F35-9995-F81DA3F0189E}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Localization.Test", "test\Microsoft.AspNet.Mvc.Localization.Test\Microsoft.AspNet.Mvc.Localization.Test.xproj", "{8FC726B5-E766-44E0-8B38-1313B6D8D9A7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1126,6 +1130,30 @@ Global {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|Mixed Platforms.Build.0 = Release|Any CPU {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|x86.ActiveCfg = Release|Any CPU {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|x86.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|x86.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|x86.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Any CPU.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|x86.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|x86.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|x86.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Any CPU.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|x86.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1216,5 +1244,7 @@ Global {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {60873DFA-97B9-419E-BAA3-940FC9B07085} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {50893B10-5735-4F35-9995-F81DA3F0189E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs index 3f47123d3a..3320830bf0 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs @@ -37,6 +37,7 @@ namespace Microsoft.AspNet.Mvc "Microsoft.AspNet.Mvc.DataAnnotations", "Microsoft.AspNet.Mvc.Formatters.Json", "Microsoft.AspNet.Mvc.Formatters.Xml", + "Microsoft.AspNet.Mvc.Localization", "Microsoft.AspNet.Mvc.Razor", "Microsoft.AspNet.Mvc.Razor.Host", "Microsoft.AspNet.Mvc.TagHelpers", diff --git a/src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizer.cs b/src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizer.cs new file mode 100644 index 0000000000..6f2d356031 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizer.cs @@ -0,0 +1,191 @@ +// 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; +using System.Globalization; +using System.Text; +using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.Framework.Internal; +using Microsoft.Framework.Localization; +using Microsoft.Framework.WebEncoders; + + +namespace Microsoft.AspNet.Mvc.Localization +{ + /// + /// An that uses the to provide localized HTML content. + /// This service just encodes the arguments but not the resource string. + /// + public class HtmlLocalizer : IHtmlLocalizer + { + private IStringLocalizer _localizer; + private readonly IHtmlEncoder _encoder; + + /// + /// Creates a new . + /// + /// The to read strings from. + /// The . + public HtmlLocalizer([NotNull] IStringLocalizer localizer, [NotNull] IHtmlEncoder encoder) + { + _localizer = localizer; + _encoder = encoder; + } + + /// + public virtual LocalizedString this[[NotNull] string key] => _localizer[key]; + + /// + public virtual LocalizedString this[[NotNull] string key, params object[] arguments] => + _localizer[key, arguments]; + + /// + /// Creates a new for a specific . + /// + /// The to use. + /// A culture-specific . + public virtual IHtmlLocalizer WithCulture([NotNull] CultureInfo culture) => + new HtmlLocalizer(_localizer.WithCulture(culture), _encoder); + + /// + /// Creates a new for a specific . + /// + /// The to use. + /// A culture-specific . + IStringLocalizer IStringLocalizer.WithCulture([NotNull] CultureInfo culture) => WithCulture(culture); + + /// + public virtual LocalizedString GetString([NotNull] string key) => _localizer.GetString(key); + + /// + public virtual LocalizedString GetString([NotNull] string key, params object[] arguments) => + _localizer.GetString(key, arguments); + + /// + public virtual IEnumerable GetAllStrings(bool includeAncestorCultures) => + _localizer.GetAllStrings(includeAncestorCultures); + + /// + public virtual LocalizedHtmlString Html([NotNull] string key) => ToHtmlString(_localizer.GetString(key)); + + /// + public virtual LocalizedHtmlString Html([NotNull] string key, params object[] arguments) + { + var stringValue = _localizer[key].Value; + + return ToHtmlString(new LocalizedString(key, EncodeArguments(stringValue, arguments))); + } + + /// + /// Creates a new for a . + /// + /// The . + protected virtual LocalizedHtmlString ToHtmlString(LocalizedString result) => + new LocalizedHtmlString(result.Name, result.Value, result.ResourceNotFound); + + /// + /// Encodes the arguments based on the object type. + /// + /// The resourceString whose arguments need to be encoded. + /// The array of objects to encode. + /// The string with encoded arguments. + protected virtual string EncodeArguments([NotNull] string resourceString, [NotNull] object[] arguments) + { + var position = 0; + var length = resourceString.Length; + char currentCharacter; + StringBuilder tokenBuffer = null; + var outputBuffer = new StringBuilder(); + var isToken = false; + + while (position < length) + { + currentCharacter = resourceString[position]; + + position++; + if (currentCharacter == '}') + { + if (position < length && resourceString[position] == '}') // Treat as escape character for }} + { + if (isToken) + { + tokenBuffer.Append("}}"); + } + else + { + outputBuffer.Append("}"); + } + + position++; + } + else + { + AppendToBuffer(isToken, '}', tokenBuffer, outputBuffer); + + if (position == length) + { + break; + } + AppendToOutputBuffer(arguments, tokenBuffer, outputBuffer); + + isToken = false; + tokenBuffer = null; + } + } + else if (currentCharacter == '{') + { + if (position < length && resourceString[position] == '{') // Treat as escape character for {{ + { + if (isToken) + { + tokenBuffer.Append("{{"); + } + else + { + outputBuffer.Append("{"); + } + position++; + } + else + { + tokenBuffer = new StringBuilder(); + tokenBuffer.Append("{"); + isToken = true; + } + } + else + { + AppendToBuffer(isToken, currentCharacter, tokenBuffer, outputBuffer); + } + } + AppendToOutputBuffer(arguments, tokenBuffer, outputBuffer); + + return outputBuffer.ToString(); + } + + private void AppendToBuffer( + bool isToken, + char value, + StringBuilder tokenBuffer, + StringBuilder outputBuffer) + { + if (isToken) + { + tokenBuffer.Append(value); + } + else + { + outputBuffer.Append(value); + } + } + + private void AppendToOutputBuffer(object[] arguments, StringBuilder tokenBuffer, StringBuilder outputBuffer) + { + if (tokenBuffer != null && tokenBuffer.Length > 0) + { + outputBuffer.Append(_encoder.HtmlEncode(string.Format(tokenBuffer.ToString(), arguments))); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizerFactory.cs b/src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizerFactory.cs new file mode 100644 index 0000000000..1193bd8a49 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizerFactory.cs @@ -0,0 +1,53 @@ +// 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.Framework.Internal; +using Microsoft.Framework.Localization; +using Microsoft.Framework.WebEncoders; + +namespace Microsoft.AspNet.Mvc.Localization +{ + /// + /// An that creates instances of . + /// + public class HtmlLocalizerFactory : IHtmlLocalizerFactory + { + private readonly IStringLocalizerFactory _factory; + private readonly IHtmlEncoder _encoder; + + /// + /// Creates a new . + /// + /// The . + /// The . + public HtmlLocalizerFactory([NotNull] IStringLocalizerFactory localizerFactory, [NotNull] IHtmlEncoder encoder) + { + _factory = localizerFactory; + _encoder = encoder; + } + + /// + /// Creates an using the and + /// of the specified . + /// + /// The . + /// The . + public virtual IHtmlLocalizer Create(Type resourceSource) + { + return new HtmlLocalizer(_factory.Create(resourceSource), _encoder); + } + + /// + /// Creates an . + /// + /// The base name of the resource to load strings from. + /// The location to load resources from. + /// The . + public virtual IHtmlLocalizer Create(string baseName, string location) + { + var localizer = _factory.Create(baseName, location); + return new HtmlLocalizer(localizer, _encoder); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizerOfT.cs b/src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizerOfT.cs new file mode 100644 index 0000000000..682e0a8405 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/HtmlLocalizerOfT.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Globalization; +using Microsoft.Framework.Internal; +using Microsoft.Framework.Localization; + +namespace Microsoft.AspNet.Mvc.Localization +{ + /// + /// This is an that provides localized HTML content. + /// + /// The to scope the resource names. + public class HtmlLocalizer : IHtmlLocalizer + { + private readonly IHtmlLocalizer _localizer; + + /// + /// Creates a new . + /// + /// The . + public HtmlLocalizer(IHtmlLocalizerFactory factory) + { + _localizer = factory.Create(typeof(TResource)); + } + + /// + public virtual LocalizedString this[[NotNull] string key] => _localizer[key]; + + /// + public virtual LocalizedString this[[NotNull] string key, params object[] arguments] => + _localizer[key, arguments]; + + /// + public virtual IHtmlLocalizer WithCulture([NotNull] CultureInfo culture) => _localizer.WithCulture(culture); + + /// + IStringLocalizer IStringLocalizer.WithCulture([NotNull] CultureInfo culture) => + _localizer.WithCulture(culture); + + /// + public virtual LocalizedString GetString([NotNull] string key) => _localizer.GetString(key); + + /// + public virtual LocalizedString GetString([NotNull] string key, params object[] arguments) => + _localizer.GetString(key, arguments); + + /// + public virtual LocalizedHtmlString Html([NotNull] string key) => _localizer.Html(key); + + /// + public virtual LocalizedHtmlString Html([NotNull] string key, params object[] arguments) => + _localizer.Html(key, arguments); + + /// + public virtual IEnumerable GetAllStrings(bool includeAncestorCultures) => + _localizer.GetAllStrings(includeAncestorCultures); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizer.cs b/src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizer.cs new file mode 100644 index 0000000000..bda07a7ec5 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizer.cs @@ -0,0 +1,38 @@ +// 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 Microsoft.Framework.Internal; +using Microsoft.Framework.Localization; + +namespace Microsoft.AspNet.Mvc.Localization +{ + /// + /// This service does not HTML encode the resource string. It HTML encodes all arguments that are formatted in + /// the resource string. + /// + public interface IHtmlLocalizer : IStringLocalizer + { + /// + /// Creates a new for a specific . + /// + /// The to use. + /// A culture-specific . + new IHtmlLocalizer WithCulture([NotNull] CultureInfo culture); + + /// + /// Gets the resource for a specific key. + /// + /// The key to use. + /// The resource. + LocalizedHtmlString Html([NotNull] string key); + + /// + /// Gets the resource for a specific key. + /// + /// The key to use. + /// The values to format the string with. + /// The resource. + LocalizedHtmlString Html([NotNull] string key, params object[] arguments); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizerFactory.cs b/src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizerFactory.cs new file mode 100644 index 0000000000..c68889a485 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizerFactory.cs @@ -0,0 +1,30 @@ +// 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.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.Localization +{ + /// + /// A factory that creates instances. + /// + public interface IHtmlLocalizerFactory + { + /// + /// Creates an using the and + /// of the specified . + /// + /// The . + /// The . + IHtmlLocalizer Create([NotNull] Type resourceSource); + + /// + /// Creates an . + /// + /// The base name of the resource to load strings from. + /// The location to load resources from. + /// The . + IHtmlLocalizer Create([NotNull] string baseName, [NotNull] string location); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizerOfT.cs b/src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizerOfT.cs new file mode 100644 index 0000000000..4df8e73fe5 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/IHtmlLocalizerOfT.cs @@ -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. + +namespace Microsoft.AspNet.Mvc.Localization +{ + /// + /// An that provides localized HTML content. + /// + /// The to scope the resource names. + public interface IHtmlLocalizer : IHtmlLocalizer + { + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Localization/IViewLocalizer.cs b/src/Microsoft.AspNet.Mvc.Localization/IViewLocalizer.cs new file mode 100644 index 0000000000..8204e3205a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/IViewLocalizer.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Mvc.Localization +{ + /// + /// A service that provides localized strings for views. + /// + public interface IViewLocalizer : IHtmlLocalizer + { + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Localization/LocalizedHtmlString.cs b/src/Microsoft.AspNet.Mvc.Localization/LocalizedHtmlString.cs new file mode 100644 index 0000000000..d6842a83d0 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/LocalizedHtmlString.cs @@ -0,0 +1,51 @@ +// 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.Mvc.Rendering; + +namespace Microsoft.AspNet.Mvc.Localization +{ + /// + /// An with localized content. + /// + public class LocalizedHtmlString : HtmlString + { + /// + /// Creates an instance of . + /// + /// The name of the string resource. + /// The string resource. + public LocalizedHtmlString(string key, string value) + : this(key, value, isResourceNotFound: false) + { + } + + /// + /// Creates an instance of . + /// + /// The name of the string resource. + /// The string resource. + /// A flag that indicates if the resource is not found. + public LocalizedHtmlString(string key, string value, bool isResourceNotFound) + : base(value) + { + Key = key; + IsResourceNotFound = isResourceNotFound; + } + + /// + /// The name of the string resource. + /// + public string Key { get; } + + /// + /// The string resource. + /// + public string Value => ToString(); + + /// + /// Gets a flag that indicates if the resource is not found. + /// + public bool IsResourceNotFound { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Localization/Microsoft.AspNet.Mvc.Localization.xproj b/src/Microsoft.AspNet.Mvc.Localization/Microsoft.AspNet.Mvc.Localization.xproj new file mode 100644 index 0000000000..dcd368ce82 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/Microsoft.AspNet.Mvc.Localization.xproj @@ -0,0 +1,18 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 50893b10-5735-4f35-9995-f81da3f0189e + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + diff --git a/src/Microsoft.AspNet.Mvc.Localization/MvcLocalizationServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc.Localization/MvcLocalizationServiceCollectionExtensions.cs new file mode 100644 index 0000000000..0583e09188 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/MvcLocalizationServiceCollectionExtensions.cs @@ -0,0 +1,52 @@ +// 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.Linq; +using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Mvc.Localization; +using Microsoft.AspNet.Mvc.Razor; +using Microsoft.Framework.Internal; +using Microsoft.Framework.WebEncoders; + +namespace Microsoft.Framework.DependencyInjection +{ + public static class MvcLocalizationServiceCollectionExtensions + { + /// + /// Adds Mvc localization to the application. + /// + /// The . + /// The . + public static IServiceCollection AddMvcLocalization([NotNull] this IServiceCollection services) + { + return AddMvcLocalization(services, LanguageViewLocationExpanderFormat.Suffix); + } + + /// + /// Adds Mvc localization to the application. + /// + /// The . + /// The view format for localized views. + /// The . + public static IServiceCollection AddMvcLocalization( + [NotNull] this IServiceCollection services, + LanguageViewLocationExpanderFormat format) + { + services.Configure( + options => + { + options.ViewLocationExpanders.Add(new LanguageViewLocationExpander(format)); + }, + DefaultOrder.DefaultFrameworkSortOrder); + + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Transient(typeof(IHtmlLocalizer<>), typeof(HtmlLocalizer<>))); + services.TryAdd(ServiceDescriptor.Transient()); + if (!services.Any(sd => sd.ServiceType == typeof(IHtmlEncoder))) + { + services.TryAdd(ServiceDescriptor.Instance(HtmlEncoder.Default)); + } + return services.AddLocalization(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Localization/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Localization/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..875f4c8249 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +// +namespace Microsoft.AspNet.Mvc.Localization +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNet.Mvc.Localization.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Must call CreateStringLocalizer method before using this property. + /// + internal static string NullStringLocalizer + { + get { return GetString("NullStringLocalizer"); } + } + + /// + /// Must call CreateStringLocalizer method before using this property. + /// + internal static string FormatNullStringLocalizer() + { + return GetString("NullStringLocalizer"); + } + + /// + /// IStringLocalizerFactory is null. Must call other constructor overload to use this property. + /// + internal static string NullStringLocalizerFactory + { + get { return GetString("NullStringLocalizerFactory"); } + } + + /// + /// IStringLocalizerFactory is null. Must call other constructor overload to use this property. + /// + internal static string FormatNullStringLocalizerFactory() + { + return GetString("NullStringLocalizerFactory"); + } + + 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.AspNet.Mvc.Localization/ViewLocalizer.cs b/src/Microsoft.AspNet.Mvc.Localization/ViewLocalizer.cs new file mode 100644 index 0000000000..7d94c25dcc --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/ViewLocalizer.cs @@ -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.Globalization; +using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.Framework.Internal; +using Microsoft.Framework.Localization; +using Microsoft.Framework.Runtime; + +namespace Microsoft.AspNet.Mvc.Localization +{ + /// + /// A that provides localized strings for views. + /// + public class ViewLocalizer : IViewLocalizer, ICanHasViewContext + { + private readonly IHtmlLocalizerFactory _localizerFactory; + private readonly string _applicationName; + private IHtmlLocalizer _localizer; + + /// + /// Creates a new . + /// + /// The . + /// The . + public ViewLocalizer( + [NotNull] IHtmlLocalizerFactory localizerFactory, + [NotNull] IApplicationEnvironment applicationEnvironment) + { + _applicationName = applicationEnvironment.ApplicationName; + _localizerFactory = localizerFactory; + } + + /// + public LocalizedString this[[NotNull] string name] => _localizer[name]; + + /// + public LocalizedString this[[NotNull] string name, params object[] arguments] => _localizer[name, arguments]; + + /// + public LocalizedString GetString([NotNull] string name) => _localizer.GetString(name); + + /// + public LocalizedString GetString([NotNull] string name, params object[] values) => + _localizer.GetString(name, values); + + /// + public LocalizedHtmlString Html([NotNull] string key) => _localizer.Html(key); + + /// + public LocalizedHtmlString Html([NotNull] string key, params object[] arguments) => + _localizer.Html(key, arguments); + + /// + public IStringLocalizer WithCulture([NotNull] CultureInfo culture) => _localizer.WithCulture(culture); + + /// + IHtmlLocalizer IHtmlLocalizer.WithCulture([NotNull] CultureInfo culture) => _localizer.WithCulture(culture); + + public void Contextualize(ViewContext viewContext) + { + var baseName = viewContext.View.Path.Replace('/', '.').Replace('\\', '.'); + if (baseName.StartsWith(".")) + { + baseName = baseName.Substring(1); + } + baseName = _applicationName + "." + baseName; + _localizer = _localizerFactory.Create(baseName, _applicationName); + } + + /// + public IEnumerable GetAllStrings(bool includeAncestorCultures) => + _localizer.GetAllStrings(includeAncestorCultures); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Localization/project.json b/src/Microsoft.AspNet.Mvc.Localization/project.json new file mode 100644 index 0000000000..434e679f73 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Localization/project.json @@ -0,0 +1,31 @@ +{ + "description": "Features that enable globalization & localization of MVC applications.", + "version": "6.0.0-*", + "compilationOptions": { + "warningsAsErrors": false + }, + "dependencies": { + "Microsoft.AspNet.Localization": "1.0.0-*", + "Microsoft.AspNet.Mvc.Razor": "6.0.0-*", + "Microsoft.Framework.DependencyInjection": "1.0.0-*", + "Microsoft.Framework.Localization": "1.0.0-*", + "Microsoft.Framework.NotNullAttribute.Sources": { "version": "1.0.0-*", "type": "build" }, + "Microsoft.Framework.PropertyHelper.Sources": { "version": "1.0.0-*", "type": "build" } + }, + "frameworks": { + "dnx451": {}, + "dnxcore50": {} + }, + "exclude": [ + "wwwroot", + "node_modules", + "bower_components" + ], + "packExclude": [ + "node_modules", + "bower_components", + "**.kproj", + "**.user", + "**.vspscc" + ] +} diff --git a/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs index 8381251f0e..d35ff24d27 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs @@ -100,33 +100,5 @@ namespace Microsoft.Framework.DependencyInjection return WithControllersAsServices(services, controllerTypes.Select(type => type.AsType())); } - - /// - /// Adds Mvc localization to the application. - /// - /// The . - /// The . - public static IServiceCollection AddMvcLocalization([NotNull] this IServiceCollection services) - { - return AddMvcLocalization(services, LanguageViewLocationExpanderFormat.Suffix); - } - - /// - /// Adds Mvc localization to the application. - /// - /// The . - /// The view format for localized views. - /// The . - public static IServiceCollection AddMvcLocalization( - [NotNull] this IServiceCollection services, - LanguageViewLocationExpanderFormat format) - { - services.ConfigureRazorViewEngine(options => - { - options.ViewLocationExpanders.Add(new LanguageViewLocationExpander(format)); - }); - - return services; - } } } diff --git a/src/Microsoft.AspNet.Mvc/project.json b/src/Microsoft.AspNet.Mvc/project.json index a90afbe8d3..2de3cd642f 100644 --- a/src/Microsoft.AspNet.Mvc/project.json +++ b/src/Microsoft.AspNet.Mvc/project.json @@ -13,6 +13,7 @@ "Microsoft.AspNet.Mvc.Cors": "6.0.0-*", "Microsoft.AspNet.Mvc.DataAnnotations": "6.0.0-*", "Microsoft.AspNet.Mvc.Formatters.Json": "6.0.0-*", + "Microsoft.AspNet.Mvc.Localization": "6.0.0-*", "Microsoft.AspNet.Mvc.Razor": "6.0.0-*", "Microsoft.AspNet.Mvc.ViewFeatures": "6.0.0-*", "Microsoft.Framework.Caching.Memory": "1.0.0-*", diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/LocalizationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/LocalizationTest.cs index c15576f728..68f2388476 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/LocalizationTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/LocalizationTest.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.IO; using System.Reflection; +using System.Resources; using System.Threading.Tasks; +using System.Xml.Linq; using LocalizationWebSite; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Testing; @@ -75,5 +78,102 @@ mypartial // Assert Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true); } + + public static IEnumerable LocalizationResourceData + { + get + { + // Dnx does not support reading resources yet. Coreclr return null value while trying to read resources. + // https://github.com/aspnet/Mvc/issues/2747 +#if DNX451 + var expected1 = +@"Hello there!! +Learn More +Hi John ! You are in 2015 year and today is Thursday"; + + yield return new[] {"en-GB", expected1 }; + + var expected2 = +@"Bonjour! +apprendre Encore Plus +Salut John ! Vous êtes en 2015 an aujourd'hui est Thursday"; + yield return new[] { "fr", expected2 }; +#else + var expectedCoreClr = +@"Hello there!! +Learn More +Hi"; + yield return new[] {"en-GB", expectedCoreClr }; + yield return new[] {"fr", expectedCoreClr }; +#endif + + } + } + + [Theory] + [MemberData(nameof(LocalizationResourceData))] + public async Task Localization_Resources_ReturnExpectedValues(string value, string expected) + { + // Arrange + var server = TestHelper.CreateServer(_app, SiteName, _configureServices); + var client = server.CreateClient(); + var cultureCookie = "c=" + value + "|uic=" + value; + client.DefaultRequestHeaders.Add( + "Cookie", + new CookieHeaderValue("ASPNET_CULTURE", cultureCookie).ToString()); + + if (!value.StartsWith("en")) + { + // Manually generating .resources file since we don't autogenerate .resources file yet. + WriteResourceFile("HomeController." + value + ".resx"); + WriteResourceFile("Views.Shared._LocalizationLayout.cshtml." + value + ".resx"); + } + WriteResourceFile("Views.Home.Locpage.cshtml." + value + ".resx"); + + // Act + var body = await client.GetStringAsync("http://localhost/Home/Locpage"); + + // Assert + Assert.Equal(expected, body.Trim()); + } + + private void WriteResourceFile(string resxFileName) + { + var resxFilePath = Path.Combine("..", "WebSites", SiteName, "Resources"); + var resxFullFileName = Path.Combine(resxFilePath, resxFileName); + if (File.Exists(resxFullFileName)) + { + using (var fs = File.OpenRead(resxFullFileName)) + { + var document = XDocument.Load(fs); + + var binDirPath = Path.Combine(resxFilePath, "bin"); + if (!Directory.Exists(binDirPath)) + { + Directory.CreateDirectory(binDirPath); + } + + // Put in "bin" sub-folder of resx file + var targetPath = Path.Combine( + binDirPath, + Path.ChangeExtension(resxFileName, ".resources")); + + using (var targetStream = File.Create(targetPath)) + { + var rw = new ResourceWriter(targetStream); + + foreach (var e in document.Root.Elements("data")) + { + var name = e.Attribute("name").Value; + var value = e.Element("value").Value; + + rw.AddResource(name, value); + } + + rw.Generate(); + } + } + } + } } } diff --git a/test/Microsoft.AspNet.Mvc.Localization.Test/HtmlLocalizerOfTTest.cs b/test/Microsoft.AspNet.Mvc.Localization.Test/HtmlLocalizerOfTTest.cs new file mode 100644 index 0000000000..1277c47c11 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Localization.Test/HtmlLocalizerOfTTest.cs @@ -0,0 +1,63 @@ +// 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 Microsoft.AspNet.Mvc.Rendering; +using Microsoft.Framework.Localization; +using Microsoft.Framework.Runtime; +using Microsoft.Framework.WebEncoders.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Localization.Test +{ + public class HtmlLocalizerOfTTest + { + [Fact] + public void HtmlLocalizerOfTTest_UseIndexer_ReturnsLocalizedString() + { + // Arrange + var localizedString = new LocalizedString("Hello", "Bonjour"); + + var htmlLocalizer = new Mock(); + htmlLocalizer.Setup(h => h["Hello"]).Returns(localizedString); + + var htmlLocalizerFactory = new Mock(); + htmlLocalizerFactory.Setup(h => h.Create(typeof(TestClass))) + .Returns(htmlLocalizer.Object); + + var htmlLocalizerOfT = new HtmlLocalizer(htmlLocalizerFactory.Object); + + // Act + var actualLocalizedString = htmlLocalizerOfT["Hello"]; + + // Assert + Assert.Equal(localizedString, actualLocalizedString); + } + + [Fact] + public void HtmlLocalizerOfTTest_UseIndexerWithArguments_ReturnsLocalizedString() + { + // Arrange + var applicationEnvironment = new Mock(); + applicationEnvironment.Setup(a => a.ApplicationName).Returns("TestApplication"); + + var localizedString = new LocalizedString("Hello", "Bonjour test"); + + var htmlLocalizer = new Mock(); + htmlLocalizer.Setup(h => h["Hello", "test"]).Returns(localizedString); + + var htmlLocalizerFactory = new Mock(); + htmlLocalizerFactory.Setup(h => h.Create(typeof(TestClass))) + .Returns(htmlLocalizer.Object); + + var htmlLocalizerOfT = new HtmlLocalizer(htmlLocalizerFactory.Object); + + // Act + var actualLocalizedString = htmlLocalizerOfT["Hello", "test"]; + + // Assert + Assert.Equal(localizedString, actualLocalizedString); + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Localization.Test/HtmlLocalizerTest.cs b/test/Microsoft.AspNet.Mvc.Localization.Test/HtmlLocalizerTest.cs new file mode 100644 index 0000000000..b0780fcbc1 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Localization.Test/HtmlLocalizerTest.cs @@ -0,0 +1,141 @@ +// 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 Microsoft.AspNet.Testing; +using Microsoft.Framework.Localization; +using Microsoft.Framework.WebEncoders.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Localization.Test +{ + public class HtmlLocalizerTest + { + [Fact] + public void HtmlLocalizer_UseIndexer_ReturnsLocalizedString() + { + // Arrange + var localizedString = new LocalizedString("Hello", "Bonjour"); + var stringLocalizer = new Mock(); + stringLocalizer.Setup(s => s["Hello"]).Returns(localizedString); + + var htmlLocalizer = new HtmlLocalizer(stringLocalizer.Object, new CommonTestEncoder()); + + // Act + var actualLocalizedString = htmlLocalizer["Hello"]; + + // Assert + Assert.Equal(localizedString, actualLocalizedString); + } + + [Fact] + public void HtmlLocalizer_UseIndexerWithArguments_ReturnsLocalizedString() + { + // Arrange + var localizedString = new LocalizedString("Hello", "Bonjour test"); + + var stringLocalizer = new Mock(); + stringLocalizer.Setup(s => s["Hello", "test"]).Returns(localizedString); + + var htmlLocalizer = new HtmlLocalizer(stringLocalizer.Object, new CommonTestEncoder()); + + // Act + var actualLocalizedString = htmlLocalizer["Hello", "test"]; + + // Assert + Assert.Equal(localizedString, actualLocalizedString); + } + + public static IEnumerable HtmlData + { + get + { + yield return new object[] { "Bonjour {0} {{{{ }}", new object[] { "test" }, "Bonjour HtmlEncode[[test]] {{ }" }; + yield return new object[] { "Bonjour {{0}}", new object[] { "{0}" }, "Bonjour {0}" }; + yield return new object[] { "Bonjour {0:x}", new object[] { 10 }, "Bonjour HtmlEncode[[a]]" }; + yield return new object[] { "Bonjour {0:x}}}", new object[] { 10 }, "Bonjour HtmlEncode[[x}]]" }; + yield return new object[] { "Bonjour {{0:x}}", new object[] { 10 }, "Bonjour {0:x}" }; + yield return new object[] { "{{ Bonjour {{{0:x}}}", new object[] { 10 }, "{ Bonjour {HtmlEncode[[x}]]" }; + yield return new object[] { "}} Bonjour {{{0:x}}}", new object[] { 10 }, "} Bonjour {HtmlEncode[[x}]]" }; + yield return new object[] { "}} Bonjour", new object[] { }, "} Bonjour" }; + yield return new object[] { "{{ {0} }}", new object[] { 10 }, "{ HtmlEncode[[10]] }" }; + yield return new object[] { + "Bonjour {{{0:x}}} {1:yyyy}", + new object[] { 10, new DateTime(2015, 10, 10) }, + "Bonjour {HtmlEncode[[x}]] HtmlEncode[[2015]]" + }; + yield return new object[] { + "Bonjour {{{0:x}}} Bienvenue {{1:yyyy}}", + new object[] { 10, new DateTime(2015, 10, 10) }, + "Bonjour {HtmlEncode[[x}]] Bienvenue {1:yyyy}" + }; + yield return new object[] { + "Bonjour {0,6} Bienvenue {{1:yyyy}}", + new object[] { 10, new DateTime(2015, 10, 10) }, + "Bonjour HtmlEncode[[ 10]] Bienvenue {1:yyyy}" + }; + if (!TestPlatformHelper.IsMono) + { + // Mono doesn't deal well with custom format strings, even valid ones + yield return new object[] { "{0:{{000}}}", new object[] { 10 }, "HtmlEncode[[{010}]]" }; + yield return new object[] { + "Bonjour {0:'{{characters that should be escaped}}b'###'b'}", + new object[] { 10 }, + "Bonjour HtmlEncode[[{characters that should be escaped}b10b]]" + }; + } + } + } + + [Theory] + [MemberData(nameof(HtmlData))] + public void HtmlLocalizer_HtmlWithArguments_ReturnsLocalizedHtml( + string format, + object[] arguments, + string expectedText) + { + // Arrange + var localizedString = new LocalizedString("Hello", format); + + var stringLocalizer = new Mock(); + stringLocalizer.Setup(s => s["Hello"]).Returns(localizedString); + + var htmlLocalizer = new HtmlLocalizer(stringLocalizer.Object, new CommonTestEncoder()); + + // Act + var localizedHtmlString = htmlLocalizer.Html("Hello", arguments); + + // Assert + Assert.NotNull(localizedHtmlString); + Assert.Equal(expectedText, localizedHtmlString.Value); + } + + [Theory] + [InlineData("{")] + [InlineData("{0")] + public void HtmlLocalizer_HtmlWithInvalidResourcestring_ThrowsException(string format) + { + // Arrange + var localizedString = new LocalizedString("Hello", format); + + var stringLocalizer = new Mock(); + stringLocalizer.Setup(s => s["Hello"]).Returns(localizedString); + + var htmlLocalizer = new HtmlLocalizer(stringLocalizer.Object, new CommonTestEncoder()); + + // Act + var exception = Assert.Throws(() => htmlLocalizer.Html("Hello", new object[] { })); + + // Assert + Assert.NotNull(exception); + Assert.Equal("Input string was not in a correct format.", exception.Message); + } + } + + public class TestClass + { + + } +} diff --git a/test/Microsoft.AspNet.Mvc.Localization.Test/Microsoft.AspNet.Mvc.Localization.Test.xproj b/test/Microsoft.AspNet.Mvc.Localization.Test/Microsoft.AspNet.Mvc.Localization.Test.xproj new file mode 100644 index 0000000000..7d2202926a --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Localization.Test/Microsoft.AspNet.Mvc.Localization.Test.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 8fc726b5-e766-44e0-8b38-1313b6d8d9a7 + Microsoft.AspNet.Mvc.Localization.Test + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + diff --git a/test/Microsoft.AspNet.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs new file mode 100644 index 0000000000..a91d1bf770 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs @@ -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.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.AspNet.Mvc.Razor; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Localization; +using Microsoft.Framework.OptionsModel; +using Microsoft.Framework.WebEncoders; +using Microsoft.Framework.WebEncoders.Testing; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Localization.Test +{ + public class MvcLocalizationServiceCollectionExtensionsTest + { + [Fact] + public void AddMvcLocalization_AddsNeededServices() + { + // Arrange + var collection = new ServiceCollection(); + + // Act + MvcLocalizationServiceCollectionExtensions.AddMvcLocalization(collection); + + // Assert + var services = collection.ToList(); + Assert.Equal(7, services.Count); + + Assert.Equal(typeof(IConfigureOptions), services[0].ServiceType); + Assert.Equal(ServiceLifetime.Singleton, services[0].Lifetime); + + Assert.Equal(typeof(IHtmlLocalizerFactory), services[1].ServiceType); + Assert.Equal(typeof(HtmlLocalizerFactory), services[1].ImplementationType); + Assert.Equal(ServiceLifetime.Singleton, services[1].Lifetime); + + Assert.Equal(typeof(IHtmlLocalizer<>), services[2].ServiceType); + Assert.Equal(typeof(HtmlLocalizer<>), services[2].ImplementationType); + Assert.Equal(ServiceLifetime.Transient, services[2].Lifetime); + + Assert.Equal(typeof(IViewLocalizer), services[3].ServiceType); + Assert.Equal(typeof(ViewLocalizer), services[3].ImplementationType); + Assert.Equal(ServiceLifetime.Transient, services[3].Lifetime); + + Assert.Equal(typeof(IHtmlEncoder), services[4].ServiceType); + Assert.Equal(ServiceLifetime.Singleton, services[4].Lifetime); + + Assert.Equal(typeof(IStringLocalizerFactory), services[5].ServiceType); + Assert.Equal(typeof(ResourceManagerStringLocalizerFactory), services[5].ImplementationType); + Assert.Equal(ServiceLifetime.Singleton, services[5].Lifetime); + + Assert.Equal(typeof(IStringLocalizer<>), services[6].ServiceType); + Assert.Equal(typeof(StringLocalizer<>), services[6].ImplementationType); + Assert.Equal(ServiceLifetime.Transient, services[6].Lifetime); + } + + [Fact] + public void AddCustomLocalizers_BeforeMvcLocalization_AddsNeededServices() + { + // Arrange + var collection = new ServiceCollection(); + + // Act + collection.Add(ServiceDescriptor.Singleton(typeof(IHtmlLocalizerFactory), typeof(TestHtmlLocalizerFactory))); + collection.Add(ServiceDescriptor.Transient(typeof(IHtmlLocalizer<>), typeof(TestHtmlLocalizer<>))); + collection.Add(ServiceDescriptor.Transient(typeof(IViewLocalizer), typeof(TestViewLocalizer))); + collection.Add(ServiceDescriptor.Instance(typeof(IHtmlEncoder), typeof(CommonTestEncoder))); + + MvcLocalizationServiceCollectionExtensions.AddMvcLocalization(collection); + + // Assert + var services = collection.ToList(); + Assert.Equal(7, services.Count); + + Assert.Equal(typeof(IHtmlLocalizerFactory), services[0].ServiceType); + Assert.Equal(typeof(TestHtmlLocalizerFactory), services[0].ImplementationType); + Assert.Equal(ServiceLifetime.Singleton, services[0].Lifetime); + + Assert.Equal(typeof(IHtmlLocalizer<>), services[1].ServiceType); + Assert.Equal(typeof(TestHtmlLocalizer<>), services[1].ImplementationType); + Assert.Equal(ServiceLifetime.Transient, services[1].Lifetime); + + Assert.Equal(typeof(IViewLocalizer), services[2].ServiceType); + Assert.Equal(typeof(TestViewLocalizer), services[2].ImplementationType); + Assert.Equal(ServiceLifetime.Transient, services[2].Lifetime); + + Assert.Equal(typeof(IHtmlEncoder), services[3].ServiceType); + Assert.Equal(typeof(CommonTestEncoder), services[3].ImplementationInstance); + Assert.Equal(ServiceLifetime.Singleton, services[3].Lifetime); + } + + [Fact] + public void AddCustomLocalizers_AfterMvcLocalization_AddsNeededServices() + { + // Arrange + var collection = new ServiceCollection(); + + collection.Configure(options => + { + options.ViewLocationExpanders.Add(new CustomPartialDirectoryViewLocationExpander()); + }); + + // Act + MvcLocalizationServiceCollectionExtensions.AddMvcLocalization(collection); + + collection.Add(ServiceDescriptor.Transient(typeof(IHtmlLocalizer<>), typeof(TestHtmlLocalizer<>))); + collection.Add(ServiceDescriptor.Transient(typeof(IHtmlLocalizer), typeof(TestViewLocalizer))); + collection.Add(ServiceDescriptor.Instance(typeof(IHtmlEncoder), typeof(CommonTestEncoder))); + + // Assert + var services = collection.ToList(); + Assert.Equal(11, services.Count); + + Assert.Equal(typeof(IConfigureOptions), services[0].ServiceType); + Assert.Equal(ServiceLifetime.Singleton, services[0].Lifetime); + Assert.Equal(0, ((IConfigureOptions)services[0].ImplementationInstance).Order); + + Assert.Equal(typeof(IConfigureOptions), services[1].ServiceType); + Assert.Equal(ServiceLifetime.Singleton, services[1].Lifetime); + Assert.Equal(-1000, ((IConfigureOptions)services[1].ImplementationInstance).Order); + + Assert.Equal(typeof(IHtmlLocalizerFactory), services[2].ServiceType); + Assert.Equal(typeof(HtmlLocalizerFactory), services[2].ImplementationType); + Assert.Equal(ServiceLifetime.Singleton, services[2].Lifetime); + + Assert.Equal(typeof(IHtmlLocalizer<>), services[3].ServiceType); + Assert.Equal(typeof(HtmlLocalizer<>), services[3].ImplementationType); + Assert.Equal(ServiceLifetime.Transient, services[3].Lifetime); + + Assert.Equal(typeof(IViewLocalizer), services[4].ServiceType); + Assert.Equal(typeof(ViewLocalizer), services[4].ImplementationType); + Assert.Equal(ServiceLifetime.Transient, services[4].Lifetime); + + Assert.Equal(typeof(IHtmlEncoder), services[5].ServiceType); + Assert.Equal(ServiceLifetime.Singleton, services[5].Lifetime); + + Assert.Equal(typeof(IStringLocalizerFactory), services[6].ServiceType); + Assert.Equal(typeof(ResourceManagerStringLocalizerFactory), services[6].ImplementationType); + Assert.Equal(ServiceLifetime.Singleton, services[6].Lifetime); + + Assert.Equal(typeof(IStringLocalizer<>), services[7].ServiceType); + Assert.Equal(typeof(StringLocalizer<>), services[7].ImplementationType); + Assert.Equal(ServiceLifetime.Transient, services[7].Lifetime); + + Assert.Equal(typeof(IHtmlLocalizer<>), services[8].ServiceType); + Assert.Equal(typeof(TestHtmlLocalizer<>), services[8].ImplementationType); + Assert.Equal(ServiceLifetime.Transient, services[8].Lifetime); + + Assert.Equal(typeof(IHtmlLocalizer), services[9].ServiceType); + Assert.Equal(typeof(TestViewLocalizer), services[9].ImplementationType); + Assert.Equal(ServiceLifetime.Transient, services[9].Lifetime); + + Assert.Equal(typeof(IHtmlEncoder), services[10].ServiceType); + Assert.Equal(typeof(CommonTestEncoder), services[10].ImplementationInstance); + Assert.Equal(ServiceLifetime.Singleton, services[10].Lifetime); + } + } + + public class TestViewLocalizer : IViewLocalizer + { + public LocalizedString this[string name] + { + get + { + throw new NotImplementedException(); + } + } + + public LocalizedString this[string name, params object[] arguments] + { + get + { + throw new NotImplementedException(); + } + } + + public IEnumerable GetAllStrings(bool includeAncestorCultures) + { + throw new NotImplementedException(); + } + + public LocalizedHtmlString Html(string key) + { + throw new NotImplementedException(); + } + + public LocalizedHtmlString Html(string key, params object[] arguments) + { + throw new NotImplementedException(); + } + + public IHtmlLocalizer WithCulture(CultureInfo culture) + { + throw new NotImplementedException(); + } + + IStringLocalizer IStringLocalizer.WithCulture(CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + public class TestHtmlLocalizer : IHtmlLocalizer + { + public LocalizedString this[string name] + { + get + { + throw new NotImplementedException(); + } + } + + public LocalizedString this[string name, params object[] arguments] + { + get + { + throw new NotImplementedException(); + } + } + + public IEnumerable GetAllStrings(bool includeAncestorCultures) + { + throw new NotImplementedException(); + } + + public LocalizedHtmlString Html(string key) + { + throw new NotImplementedException(); + } + + public LocalizedHtmlString Html(string key, params object[] arguments) + { + throw new NotImplementedException(); + } + + public IHtmlLocalizer WithCulture(CultureInfo culture) + { + throw new NotImplementedException(); + } + + IStringLocalizer IStringLocalizer.WithCulture(CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + public class TestHtmlLocalizerFactory : IHtmlLocalizerFactory + { + public IHtmlLocalizer Create(Type resourceSource) + { + throw new NotImplementedException(); + } + + public IHtmlLocalizer Create(string baseName, string location) + { + throw new NotImplementedException(); + } + } + + public class CustomPartialDirectoryViewLocationExpander : IViewLocationExpander + { + public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) + { + throw new NotImplementedException(); + } + + public void PopulateValues(ViewLocationExpanderContext context) + { + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Localization.Test/ViewLocalizerTest.cs b/test/Microsoft.AspNet.Mvc.Localization.Test/ViewLocalizerTest.cs new file mode 100644 index 0000000000..b2a749a2f8 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Localization.Test/ViewLocalizerTest.cs @@ -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.Globalization; +using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.Framework.Localization; +using Microsoft.Framework.Runtime; +using Microsoft.Framework.WebEncoders.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Localization.Test +{ + public class ViewLocalizerTest + { + [Fact] + public void ViewLocalizer_UseIndexer_ReturnsLocalizedString() + { + // Arrange + var applicationEnvironment = new Mock(); + applicationEnvironment.Setup(a => a.ApplicationName).Returns("TestApplication"); + + var localizedString = new LocalizedString("Hello", "Bonjour"); + + var htmlLocalizer = new Mock(); + htmlLocalizer.Setup(h => h["Hello"]).Returns(localizedString); + + var htmlLocalizerFactory = new Mock(); + htmlLocalizerFactory.Setup(h => h.Create("TestApplication.example", "TestApplication")) + .Returns(htmlLocalizer.Object); + + var viewLocalizer = new ViewLocalizer(htmlLocalizerFactory.Object, applicationEnvironment.Object); + + var view = new Mock(); + view.Setup(v => v.Path).Returns("example"); + var viewContext = new ViewContext(); + viewContext.View = view.Object; + + viewLocalizer.Contextualize(viewContext); + + // Act + var actualLocalizedString = viewLocalizer["Hello"]; + + // Assert + Assert.Equal(localizedString, actualLocalizedString); + } + + [Fact] + public void ViewLocalizer_UseIndexerWithArguments_ReturnsLocalizedString() + { + // Arrange + var applicationEnvironment = new Mock(); + applicationEnvironment.Setup(a => a.ApplicationName).Returns("TestApplication"); + + var localizedString = new LocalizedString("Hello", "Bonjour test"); + + var htmlLocalizer = new Mock(); + htmlLocalizer.Setup(h => h["Hello", "test"]).Returns(localizedString); + + var htmlLocalizerFactory = new Mock(); + htmlLocalizerFactory.Setup( + h => h.Create("TestApplication.example", "TestApplication")).Returns(htmlLocalizer.Object); + + var viewLocalizer = new ViewLocalizer(htmlLocalizerFactory.Object, applicationEnvironment.Object); + + var view = new Mock(); + view.Setup(v => v.Path).Returns("example"); + var viewContext = new ViewContext(); + viewContext.View = view.Object; + + viewLocalizer.Contextualize(viewContext); + + // Act + var actualLocalizedString = viewLocalizer["Hello", "test"]; + + // Assert + Assert.Equal(localizedString, actualLocalizedString); + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Localization.Test/project.json b/test/Microsoft.AspNet.Mvc.Localization.Test/project.json new file mode 100644 index 0000000000..8f4c30de24 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Localization.Test/project.json @@ -0,0 +1,24 @@ +{ + "compilationOptions": { + "warningsAsErrors": "true" + }, + "dependencies": { + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.Localization": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestCommon": { "version": "6.0.0-*", "type": "build" }, + "Microsoft.AspNet.Testing": "1.0.0-*", + "Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-*", + "Microsoft.Framework.WebEncoders.Testing": "1.0.0-*", + "xunit.runner.aspnet": "2.0.0-aspnet-*" + }, + "commands": { + "test": "xunit.runner.aspnet" + }, + "frameworks": { + "dnx451": { + "dependencies": { + "Moq": "4.2.1312.1622" + } + } + } +} diff --git a/test/WebSites/LocalizationWebSite/Controllers/HomeController.cs b/test/WebSites/LocalizationWebSite/Controllers/HomeController.cs index 400f382eb8..fdc25a2171 100644 --- a/test/WebSites/LocalizationWebSite/Controllers/HomeController.cs +++ b/test/WebSites/LocalizationWebSite/Controllers/HomeController.cs @@ -2,15 +2,28 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Mvc.Localization; namespace LocalizationWebSite.Controllers { public class HomeController : Controller { - // GET: // + private readonly IHtmlLocalizer _localizer; + + public HomeController(IHtmlLocalizer localizer) + { + _localizer = localizer; + } + public IActionResult Index() { return View(); } + + public IActionResult Locpage() + { + ViewData["Message"] = _localizer["Learn More"]; + return View(); + } } } diff --git a/test/WebSites/LocalizationWebSite/Resources/HomeController.fr.resx b/test/WebSites/LocalizationWebSite/Resources/HomeController.fr.resx new file mode 100644 index 0000000000..70dd8d4706 --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Resources/HomeController.fr.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bonjour! + + + Apprenez à créer des applications ASP.NET qui peuvent fonctionner ne importe où. + + + apprendre Encore Plus + + \ No newline at end of file diff --git a/test/WebSites/LocalizationWebSite/Resources/Views.Home.Locpage.cshtml.en-GB.resx b/test/WebSites/LocalizationWebSite/Resources/Views.Home.Locpage.cshtml.en-GB.resx new file mode 100644 index 0000000000..e41ff10213 --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Resources/Views.Home.Locpage.cshtml.en-GB.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Hello there!! + + + Learn how to build ASP.NET apps that can run anywhere. + + + Learn More + + + Hi {0,-10}! You are in {1:yyyy} year and today is {2} + + \ No newline at end of file diff --git a/test/WebSites/LocalizationWebSite/Resources/Views.Home.Locpage.cshtml.fr.resx b/test/WebSites/LocalizationWebSite/Resources/Views.Home.Locpage.cshtml.fr.resx new file mode 100644 index 0000000000..1ae6682e18 --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Resources/Views.Home.Locpage.cshtml.fr.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bonjour! + + + Apprenez à créer des applications ASP.NET qui peuvent fonctionner ne importe où. + + + apprendre Encore Plus + + + Salut {0,-10}! Vous êtes en {1:yyyy} an aujourd'hui est {2} + + \ No newline at end of file diff --git a/test/WebSites/LocalizationWebSite/Resources/Views.Shared._LocalizationLayout.cshtml.fr.resx b/test/WebSites/LocalizationWebSite/Resources/Views.Shared._LocalizationLayout.cshtml.fr.resx new file mode 100644 index 0000000000..fa0e1a299e --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Resources/Views.Shared._LocalizationLayout.cshtml.fr.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + sur + + + Nom de l'application + + + communiquer + + + maison + + + Mon application ASP.NET + + + Ressources pour ce point de vue + + + Aujourd'hui, ce est: {0} + + \ No newline at end of file diff --git a/test/WebSites/LocalizationWebSite/Startup.cs b/test/WebSites/LocalizationWebSite/Startup.cs index b3abd50242..28cbc2a11f 100644 --- a/test/WebSites/LocalizationWebSite/Startup.cs +++ b/test/WebSites/LocalizationWebSite/Startup.cs @@ -3,6 +3,7 @@ using Microsoft.AspNet.Builder; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Localization; namespace LocalizationWebSite { @@ -14,6 +15,10 @@ namespace LocalizationWebSite // Add MVC services to the services container services.AddMvc(); services.AddMvcLocalization(); + + // Adding TestStringLocalizerFactory since ResourceStringLocalizerFactory uses ResourceManager. DNX does + // not support getting non-enu resources from ResourceManager yet. + services.AddSingleton(); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/LocalizationWebSite/TestStringLocalizer.cs b/test/WebSites/LocalizationWebSite/TestStringLocalizer.cs new file mode 100644 index 0000000000..be3d4a2fbd --- /dev/null +++ b/test/WebSites/LocalizationWebSite/TestStringLocalizer.cs @@ -0,0 +1,96 @@ +// 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.IO; +using System.Resources; +using Microsoft.Framework.Localization; +using Microsoft.Framework.Localization.Internal; + +namespace LocalizationWebSite +{ + public class TestStringLocalizer : IStringLocalizer + { + private readonly IResourceNamesCache _resourceNamesCache; + private ResourceManager _resourceManager; + private readonly AssemblyWrapper _resourceAssemblyWrapper; + private readonly string _resourceBaseName; + private string _applicationBasePath; + + public TestStringLocalizer(ResourceManager resourceManager, + AssemblyWrapper resourceAssembly, + string baseName, + IResourceNamesCache resourceNamesCache, + string applicationBasePath) + { + _resourceAssemblyWrapper = resourceAssembly; + _resourceManager = resourceManager; + _resourceBaseName = baseName; + _resourceNamesCache = resourceNamesCache; + _applicationBasePath = applicationBasePath; + } + + public virtual LocalizedString this[string name] + { + get + { + var value = GetStringSafely(name, null); + return new LocalizedString(name, value ?? name, resourceNotFound: value == null); + } + } + + public virtual LocalizedString this[string name, params object[] arguments] + { + get + { + var format = GetStringSafely(name, null); + var value = string.Format(format ?? name, arguments); + return new LocalizedString(name, value, resourceNotFound: format == null); + } + } + + public IStringLocalizer WithCulture(CultureInfo culture) + { + return new TestStringLocalizer(_resourceManager, + _resourceAssemblyWrapper, + _resourceBaseName, + _resourceNamesCache, + _applicationBasePath); + } + + public virtual IEnumerable GetAllStrings(bool includeAncestorCultures) => + GetAllStrings(includeAncestorCultures, CultureInfo.CurrentUICulture); + + protected IEnumerable GetAllStrings(bool includeAncestorCultures, CultureInfo culture) + { + throw new NotImplementedException(); + } + + protected string GetStringSafely(string name, CultureInfo culture) + { + var resourceValue = string.Empty; +#if DNX451 + var cultureName = (culture ?? CultureInfo.CurrentUICulture).Name; + var resourceFile = _resourceManager.BaseName.Substring(_resourceManager.BaseName.IndexOf('.') + 1) + "." + cultureName; + var filePath = Path.Combine(_applicationBasePath, "Resources", "bin"); + + if (File.Exists(Path.Combine(filePath, resourceFile + ".resources"))) + { + _resourceManager = ResourceManager.CreateFileBasedResourceManager(resourceFile, filePath, null); + } +#endif + try + { + // retrieve the value of the specified key + resourceValue = _resourceManager.GetString(name); + } + catch (MissingManifestResourceException) + { + return name; + } + return resourceValue; + } + } +} diff --git a/test/WebSites/LocalizationWebSite/TestStringLocalizerFactory.cs b/test/WebSites/LocalizationWebSite/TestStringLocalizerFactory.cs new file mode 100644 index 0000000000..19fb7b9b28 --- /dev/null +++ b/test/WebSites/LocalizationWebSite/TestStringLocalizerFactory.cs @@ -0,0 +1,52 @@ +// 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.Reflection; +using System.Resources; +using Microsoft.Framework.Localization; +using Microsoft.Framework.Localization.Internal; +using Microsoft.Framework.Runtime; + +namespace LocalizationWebSite +{ + public class TestStringLocalizerFactory : IStringLocalizerFactory + { + private readonly IApplicationEnvironment _applicationEnvironment; + private readonly IResourceNamesCache _resourceNamesCache = new ResourceNamesCache(); + + public TestStringLocalizerFactory(IApplicationEnvironment applicationEnvironment) + { + _applicationEnvironment = applicationEnvironment; + } + + public IStringLocalizer Create(Type resourceSource) + { + var typeInfo = resourceSource.GetTypeInfo(); + var assembly = typeInfo.Assembly; + var baseName = typeInfo.FullName; + return new TestStringLocalizer( + new ResourceManager(resourceSource), + new AssemblyWrapper(assembly), + baseName, + _resourceNamesCache, + _applicationEnvironment.ApplicationBasePath); + } + + public IStringLocalizer Create(string baseName, string location) + { + if (string.IsNullOrEmpty(location)) + { + location = _applicationEnvironment.ApplicationName; + } + var assembly = Assembly.Load(new AssemblyName(location)); + + return new TestStringLocalizer( + new ResourceManager(baseName, assembly), + new AssemblyWrapper(assembly), + baseName, + _resourceNamesCache, + _applicationEnvironment.ApplicationBasePath); + } + } +} diff --git a/test/WebSites/LocalizationWebSite/Views/Home/Locpage.cshtml b/test/WebSites/LocalizationWebSite/Views/Home/Locpage.cshtml new file mode 100644 index 0000000000..de2a3a134d --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Views/Home/Locpage.cshtml @@ -0,0 +1,7 @@ +@{ + Layout = "/Views/Shared/_LocalizationLayout.cshtml"; + var date = new DateTime(2015,6,25); +} +@LocString["Hello there!!"] +@ViewBag.Message +@LocString.Html("Hi", "John", @date , @date.DayOfWeek) diff --git a/test/WebSites/LocalizationWebSite/Views/Shared/_LocalizationLayout.cshtml b/test/WebSites/LocalizationWebSite/Views/Shared/_LocalizationLayout.cshtml new file mode 100644 index 0000000000..d2f231de2f --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Views/Shared/_LocalizationLayout.cshtml @@ -0,0 +1 @@ +@RenderBody() \ No newline at end of file diff --git a/test/WebSites/LocalizationWebSite/Views/_ViewImports.cshtml b/test/WebSites/LocalizationWebSite/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..5ad826fca6 --- /dev/null +++ b/test/WebSites/LocalizationWebSite/Views/_ViewImports.cshtml @@ -0,0 +1,5 @@ +@using LocalizationWebSite +@using Microsoft.Framework.Localization +@using Microsoft.AspNet.Mvc.Localization + +@inject IViewLocalizer LocString \ No newline at end of file diff --git a/test/WebSites/LocalizationWebSite/project.json b/test/WebSites/LocalizationWebSite/project.json index ce5949d698..1e1ea36565 100644 --- a/test/WebSites/LocalizationWebSite/project.json +++ b/test/WebSites/LocalizationWebSite/project.json @@ -1,17 +1,15 @@ { - "commands": { - "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001", - "kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000" - }, - "dependencies": { - "Kestrel": "1.0.0-*", - "Microsoft.AspNet.Localization": "1.0.0-*", - "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", - "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Server.WebListener": "1.0.0-*", - "Microsoft.AspNet.StaticFiles": "1.0.0-*" - }, + "commands": { + "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001", + "kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000" + }, + "dependencies": { + "Kestrel": "1.0.0-*", + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*" + }, "frameworks": { "dnx451": { }, "dnxcore50": { } diff --git a/test/WebSites/RazorWebSite/Startup.cs b/test/WebSites/RazorWebSite/Startup.cs index 582e35c42c..ddae6d73a1 100644 --- a/test/WebSites/RazorWebSite/Startup.cs +++ b/test/WebSites/RazorWebSite/Startup.cs @@ -3,7 +3,6 @@ using System.Globalization; using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Localization; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Razor; using Microsoft.Framework.DependencyInjection; diff --git a/test/WebSites/RazorWebSite/project.json b/test/WebSites/RazorWebSite/project.json index dfbcb45596..3b0ab5bfe9 100644 --- a/test/WebSites/RazorWebSite/project.json +++ b/test/WebSites/RazorWebSite/project.json @@ -7,6 +7,7 @@ "Kestrel": "1.0.0-*", "Microsoft.AspNet.Localization": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.Localization": "6.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Server.IIS": "1.0.0-*", "Microsoft.AspNet.Server.WebListener": "1.0.0-*",