I need to keep my translations in database so users can add, delete, and change them. I hold all of my translations in a table with composite primary key (variableName, culture), where variableName is just a name of some text which can have multiple translations and they correspond to the culture which is a string, like "en-US". So for example I have a variable "submitLogin" which I display on login button and there are three languages in my database: English, German, and Polish. This means I keep three rows in my table for that particular text: ("submitLogin", "en-US", "English translation), ("submitLogin", "de-DE", "German translation") and ("submitLogin", "pl-PL", "Polish translation").
So far my application has been based on a class Resources.cs which contains all translation variables from database, e.g.:
public static string buttonContinueShopping {
get {
return (string) resourceProvider.GetResource("buttonContinueShopping", CultureInfo.CurrentUICulture.Name);
}
}
In views I use these static properties to get my translations like this:
@Resources.buttonContinueShopping
I can create a dynamic type which will behave exactly the same way in views (except not having static properties but I can create an object on every view, that's not the problem - although it doesn't seem nice):
public class Resource : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
ResourceManager rsManager = new ResourceManager();
result = rsManager.GetString(binder.Name);
return true;
}
}
But I have a problem with my models' attributes. So far I've used them like this:
[Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "errorRequired")]
[DataType(DataType.EmailAddress, ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "errorWrongDataType")]
[EmailAddress]
[Display(Name = "nameEmail", ResourceType = typeof(Resources.Resources))]
public string Email { get; set; }
Now I have to get rid of my Resources.cs because they are generated after running a console program which reads all unique values of translation variables from database and creates properties (like the one I showed above). I cannot have this file anymore because users can add new translation variables in runtime.
How do I change as little as possible and make these attributes read error messages, display names etc. from database?
I have three ideas but I don't know how to get them done:
Use custom attributes - I tried it for Required attribute but it just doesn't add any client-side validation nor any error messages in HTML.
Use custom DataAnnotationsModelMetadataProvider - I tried this but it doesn't work after reloading the page - on the first page load all errors exist (particularly this: 'Field Email is required.') but after reloading the page, required error message changes to 'Field is required'. This is what I do:
public class CustomDataAnnotationsProvider: DataAnnotationsModelMetadataProvider { private ResourceManager resourceManager = new ResourceManager(); protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { string key = string.Empty; string localizedValue = string.Empty; foreach (var attr in attributes) { if (attr != null) { if (attr is DisplayAttribute) { key = ((DisplayAttribute)attr).Name; if (!string.IsNullOrEmpty(key) && !key.Contains(" ")) { localizedValue = resourceManager.GetString(key); ((DisplayAttribute)attr).Name = localizedValue; } } else if (attr is ValidationAttribute || attr is RequiredAttribute) { key = ((ValidationAttribute)attr).ErrorMessage; if (!string.IsNullOrEmpty(key) && !key.Contains(" ")) { localizedValue = resourceManager.GetString(key); ((ValidationAttribute)attr).ErrorMessage = localizedValue; } } } } return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);}
Global.asax:
ModelMetadataProviders.Current = new CustomDataAnnotationsProvider();
Model:
[Required(ErrorMessage = "errorRequired")]
[EmailAddress(ErrorMessage = "errorWrongDataType")]
[Display(Name = "nameEmail")]
public string Email { get; set; }
Use reflection (would be the best but I have no idea how to do it). Let's say I leave my attributes like that and remove all properties from my Resources.cs. Now what does RequiredAttribute do? It takes the type given and gets the property given, so e.g. It tries to do this:
Resources.Resources.nameEmail.get
The question is: is it possible to write some reflection code which would take care of 'requests' for non-existing properties (like nameEmail)?