diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Annotations/CommentValidationAttributes.cs b/src/Orchard.Web/Modules/Orchard.Comments/Annotations/CommentValidationAttributes.cs
deleted file mode 100644
index 551afb931..000000000
--- a/src/Orchard.Web/Modules/Orchard.Comments/Annotations/CommentValidationAttributes.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using Orchard.Localization;
-
-namespace Orchard.Comments.Annotations {
- public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute {
- public RequiredAttribute() {
- T = NullLocalizer.Instance;
- }
-
- public Localizer T { get; set; }
-
- public override string FormatErrorMessage(string name) {
- return T("You must provide a {0} in order to comment.", name).Text;
- }
- }
-
- public class CommentRequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute {
- public CommentRequiredAttribute() {
- T = NullLocalizer.Instance;
- }
-
- public Localizer T { get; set; }
-
- public override string FormatErrorMessage(string name) {
- return T("You must provide a Comment.", name).Text;
- }
- }
-
- public class RegularExpressionAttribute : System.ComponentModel.DataAnnotations.RegularExpressionAttribute {
- public RegularExpressionAttribute(string pattern) : base(pattern) {
- T = NullLocalizer.Instance;
- }
-
- public Localizer T { get; set; }
-
- public override string FormatErrorMessage(string name) {
- return T("The {0} is not valid.", name).Text;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj b/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj
index e7c1147d7..4df3b95c1 100644
--- a/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj
+++ b/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj
@@ -67,7 +67,6 @@
-
diff --git a/src/Orchard.Web/Modules/Orchard.Comments/ViewModels/CommentsCreateViewModel.cs b/src/Orchard.Web/Modules/Orchard.Comments/ViewModels/CommentsCreateViewModel.cs
index 558d85e4c..94408a78a 100644
--- a/src/Orchard.Web/Modules/Orchard.Comments/ViewModels/CommentsCreateViewModel.cs
+++ b/src/Orchard.Web/Modules/Orchard.Comments/ViewModels/CommentsCreateViewModel.cs
@@ -1,21 +1,22 @@
-using System.ComponentModel.DataAnnotations;
-using Orchard.Comments.Annotations;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
namespace Orchard.Comments.ViewModels {
public class CommentsCreateViewModel {
- [Annotations.Required]
+ [Required]
[StringLength(255)]
public string Name { get; set; }
- [Annotations.RegularExpression(@"^[^@\s]+@[^@\s]+$")]
+ [RegularExpression(@"^[^@\s]+@[^@\s]+$")]
[StringLength(255)]
public string Email { get; set; }
[StringLength(245)]
- [Annotations.RegularExpression(@"^(http(s)?://)?([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}[\S]+$")]
+ [DisplayName("Site")]
+ [RegularExpression(@"^(http(s)?://)?([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}[\S]+$")]
public string SiteName { get; set; }
- [CommentRequired]
+ [Required, DisplayName("Comment")]
public string CommentText { get; set; }
public int CommentedOn { get; set; }
diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs
index 66654d908..c781ab137 100644
--- a/src/Orchard/Environment/OrchardStarter.cs
+++ b/src/Orchard/Environment/OrchardStarter.cs
@@ -23,6 +23,7 @@ using Orchard.FileSystems.VirtualPath;
using Orchard.FileSystems.WebSite;
using Orchard.Logging;
using Orchard.Mvc;
+using Orchard.Mvc.DataAnnotations;
using Orchard.Mvc.ViewEngines.Razor;
using Orchard.Mvc.ViewEngines.ThemeAwareness;
using Orchard.Services;
@@ -143,6 +144,10 @@ namespace Orchard.Environment {
//MvcServiceLocator.SetCurrent(hostContainer);
OrchardHostContainerRegistry.RegisterHostContainer(hostContainer);
+ // Register localized data annotations
+ ModelValidatorProviders.Providers.Clear();
+ ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider());
+
return container;
}
diff --git a/src/Orchard/Mvc/DataAnnotations/LocalizedModelValidatorProvider.cs b/src/Orchard/Mvc/DataAnnotations/LocalizedModelValidatorProvider.cs
new file mode 100644
index 000000000..5975da82e
--- /dev/null
+++ b/src/Orchard/Mvc/DataAnnotations/LocalizedModelValidatorProvider.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Web.Mvc;
+using Orchard.Localization;
+
+namespace Orchard.Mvc.DataAnnotations {
+ public class LocalizedModelValidatorProvider : DataAnnotationsModelValidatorProvider {
+ private static readonly Dictionary> _validationAttributes;
+
+ static LocalizedModelValidatorProvider() {
+ _validationAttributes = new Dictionary> {
+ { typeof(RequiredAttribute), (attribute, t) => new LocalizedRequiredAttribute((RequiredAttribute)attribute, t)},
+ { typeof(RangeAttribute), (attribute, t) => new LocalizedRangeAttribute((RangeAttribute)attribute, t)},
+ { typeof(StringLengthAttribute), (attribute, t) => new LocalizedStringLengthAttribute((StringLengthAttribute)attribute, t)},
+ { typeof(RegularExpressionAttribute), (attribute, t) => new LocalizedRegularExpressionAttribute((RegularExpressionAttribute)attribute, t)}
+ };
+ }
+
+ protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable attributes) {
+ var localizedAttributes = new List();
+
+ foreach ( var attribute in attributes ) {
+ Func localizedAttribute;
+
+ // overriden messages have their localization in the scope of the class they are applied to
+ var tContainer = LocalizationUtilities.Resolve(context, metadata.ContainerType.FullName);
+
+ // default translations use the attribute's scope, e.g., System.ComponentModel.DataAnnotations.RequiredAttribute
+ var tProvider = LocalizationUtilities.Resolve(context, attribute.GetType().FullName);
+
+ var validationAttribute = attribute as ValidationAttribute;
+
+ // substitute the attribute to its localized version if available
+ if ( _validationAttributes.TryGetValue(attribute.GetType(), out localizedAttribute) ) {
+ localizedAttributes.Add(localizedAttribute((ValidationAttribute)attribute, tProvider));
+ }
+ else {
+
+ // try to inject the localizer if it's an unkown validation attribute
+ if ( validationAttribute != null ) {
+
+ var propertyInfo = validationAttribute.GetType().GetProperty("T", typeof(Localizer));
+ if ( propertyInfo != null ) {
+ propertyInfo.SetValue(attribute, tProvider, null);
+ }
+ }
+
+ if ( attribute is DisplayNameAttribute ) {
+ metadata.DisplayName = tContainer(metadata.DisplayName).Text;
+ }
+
+ localizedAttributes.Add(attribute);
+ }
+ }
+
+ var result = base.GetValidators(metadata, context, localizedAttributes);
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Mvc/DataAnnotations/LocalizedRangeAttribute.cs b/src/Orchard/Mvc/DataAnnotations/LocalizedRangeAttribute.cs
new file mode 100644
index 000000000..573b58bfe
--- /dev/null
+++ b/src/Orchard/Mvc/DataAnnotations/LocalizedRangeAttribute.cs
@@ -0,0 +1,24 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Runtime.Serialization;
+using Orchard.Localization;
+
+namespace Orchard.Mvc.DataAnnotations {
+ public class LocalizedRangeAttribute : RangeAttribute {
+ public LocalizedRangeAttribute(RangeAttribute attribute, Localizer t)
+ : base(attribute.OperandType, new FormatterConverter().ToString(attribute.Minimum), new FormatterConverter().ToString(attribute.Maximum)) {
+ if ( !String.IsNullOrEmpty(attribute.ErrorMessage) )
+ ErrorMessage = attribute.ErrorMessage;
+
+ T = t;
+ }
+
+ public Localizer T { get; set; }
+
+ public override string FormatErrorMessage(string name) {
+ return String.IsNullOrEmpty(ErrorMessage)
+ ? T("The field {0} must be between {1} and {2}.", name, Minimum, Maximum).Text
+ : T(ErrorMessage, name, Minimum, Maximum).Text;
+ }
+ }
+}
diff --git a/src/Orchard/Mvc/DataAnnotations/LocalizedRequiredAttribute.cs b/src/Orchard/Mvc/DataAnnotations/LocalizedRequiredAttribute.cs
new file mode 100644
index 000000000..72d3258b3
--- /dev/null
+++ b/src/Orchard/Mvc/DataAnnotations/LocalizedRequiredAttribute.cs
@@ -0,0 +1,24 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using Orchard.Localization;
+
+namespace Orchard.Mvc.DataAnnotations {
+ public class LocalizedRequiredAttribute : RequiredAttribute {
+ public LocalizedRequiredAttribute(RequiredAttribute attribute, Localizer t) {
+ AllowEmptyStrings = attribute.AllowEmptyStrings;
+
+ if ( !String.IsNullOrEmpty(attribute.ErrorMessage) )
+ ErrorMessage = attribute.ErrorMessage;
+
+ T = t;
+ }
+
+ public Localizer T { get; set; }
+
+ public override string FormatErrorMessage(string name) {
+ return String.IsNullOrEmpty(ErrorMessage)
+ ? T("The field {0} is required.", name).Text
+ : T(ErrorMessage, name).Text;
+ }
+ }
+}
diff --git a/src/Orchard/Mvc/DataAnnotations/LocalizedReularExpressionAttribute.cs b/src/Orchard/Mvc/DataAnnotations/LocalizedReularExpressionAttribute.cs
new file mode 100644
index 000000000..4ff861822
--- /dev/null
+++ b/src/Orchard/Mvc/DataAnnotations/LocalizedReularExpressionAttribute.cs
@@ -0,0 +1,23 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using Orchard.Localization;
+
+namespace Orchard.Mvc.DataAnnotations {
+ public class LocalizedRegularExpressionAttribute : RegularExpressionAttribute {
+ public LocalizedRegularExpressionAttribute(RegularExpressionAttribute attribute, Localizer t)
+ : base(attribute.Pattern) {
+ if ( !String.IsNullOrEmpty(attribute.ErrorMessage) )
+ ErrorMessage = attribute.ErrorMessage;
+
+ T = t;
+ }
+
+ public Localizer T { get; set; }
+
+ public override string FormatErrorMessage(string name) {
+ return String.IsNullOrEmpty(ErrorMessage)
+ ? T("The field {0} must match the regular expression '{1}'.", name, Pattern).Text
+ : T(ErrorMessage, name, Pattern).Text;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Mvc/DataAnnotations/LocalizedStringMaxLengthAttribute.cs b/src/Orchard/Mvc/DataAnnotations/LocalizedStringMaxLengthAttribute.cs
new file mode 100644
index 000000000..6a5bfbbe1
--- /dev/null
+++ b/src/Orchard/Mvc/DataAnnotations/LocalizedStringMaxLengthAttribute.cs
@@ -0,0 +1,28 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using Orchard.Localization;
+
+namespace Orchard.Mvc.DataAnnotations {
+ public class LocalizedStringLengthAttribute : StringLengthAttribute {
+ public LocalizedStringLengthAttribute(StringLengthAttribute attribute, Localizer t)
+ : base(attribute.MaximumLength) {
+ if ( !String.IsNullOrEmpty(attribute.ErrorMessage) )
+ ErrorMessage = attribute.ErrorMessage;
+
+ MinimumLength = attribute.MinimumLength;
+
+ T = t;
+ }
+
+ public Localizer T { get; set; }
+
+ public override string FormatErrorMessage(string name) {
+ if ( !String.IsNullOrEmpty(ErrorMessage) )
+ return T(ErrorMessage, name, MaximumLength, MinimumLength).Text;
+
+ return MinimumLength > 0
+ ? T("The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.", name, MaximumLength, MinimumLength).Text
+ : T("The field {0} must be a string with a maximum length of {1}.", name, MaximumLength, MinimumLength).Text;
+ }
+ }
+}
diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj
index f33f28740..e96f77bda 100644
--- a/src/Orchard/Orchard.Framework.csproj
+++ b/src/Orchard/Orchard.Framework.csproj
@@ -230,6 +230,11 @@
+
+
+
+
+