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