Open solnic opened 13 years ago
Yes! I’m suffering because of that too. Let’s work on this man, I’ll start by looking at your fork. It’s already too late to push this into 1.1 release, but we can add that to 1.1.1!
by Piotr Solnica (solnic)
I’ve quickly looked at your forks, all looks great. I will try to use it in my app as a test drive. If it works fine then why not merging your work in now? Are there any public/semipublic API changes?
by Piotr Solnica (solnic)
I don’t know too much about localization, so I’ll let people who know more speak up about specifics. One idea I had, playing off gix’s original, is to instead of returning error messages like we do now, we setup objects for each type of error that have enough information in their type, attributes to reconstruct the error message, but when stringified default to the current error messages we have now.
This would allow us to keep a lot of the same API we have now, but we’d have rich objects for each error message that could be built on top of.
I’d probably want some sort of base class for the behavior, one subclass for each distinct error message, and one for custom validators. We can switch on the object type for different behavior for each object, rather than inspecting a Symbol attribute (which is just simulated polymorphism, a code smell).
by Dan Kubb (dkubb)
That’s essentially what my idea and branch do, except there is a single class instead of multiple ones, and context is provided via a hash. It would be easy to change that if it’s preferred that way. Actually thinking about it I prefer that idea myself instead of having a symbol for each error.
Regarding backwards compatibility, this mostly affects the ValidationErrors collection (methods like #on, #each or #[] returning error objects instead of strings). Giving error objects #to_str and #to_s methods returning the formatted error message works as long as no string methods are called on it. But since there is no real overlap in methods between the error class and strings method_missing could take care of it and delegate it to the message returned by #to_s (and give a deprecation warning).
by gix
This feature would help me out
by Xavier Shay
I pushed a second version implementing a cleaner way of transforming errors (see https://github.com/gix/dm-validations/commit/7145db7c8d8c556a2c787a5282b4750c82818fa0#diff-0).
Some unresolved things:
How should custom messages be handled with multiple validation contexts? The specs use a message hash with the context symbol as key, but the whole hash is passed in as "error message" which seems weird to me because I have to remember the context to retrieve the appropriate message.
by gix
So it is really true that error messages can not be translated in a uniform way? I am wondering if there are any best practices or proposed workarounds for 1.2?
Maybe I am missing something and this is implemented in the 1.3 branch already?
Yes it'll be possible to add translations in 1.3 (current master)
That's great news. Thanks solnic
@solnic How we can do that ? There are some example ?
I believe @emmanuel should know. He's done most of the work.
@shingara — unfortunately there are no real docs. Take a look at: https://github.com/datamapper/dm-validations/blob/master/lib/data_mapper/validation/message_transformer.rb#L90-106 for an example of a simple DataMapper::Validation::MessageTransformer
subclass which looks up violation error messages from I18n
with keys like errors.absent
. If you want something like ActiveRecord's style of keys (see here for more info: https://github.com/rails/rails/blob/master/activemodel/lib/active_model/errors.rb#L297-352), you would need to define a new MessageTransformer
subclass with a custom #transform
method, like:
class BetterI18n < DataMapper::Validation::MessageTransformer
def transform(violation)
raise ArgumentError, "+violation+ must be specified" if violation.nil?
violation_type = violation.type
resource = violation.resource
model_name = resource.model.model_name
attribute_name = violation.attribute_name
i18n_scope = self.i18n_scope(resource, model_name, attribute_name)
options = {
:model => transform_model_name(model_name),
:attribute => transform_attribute_name(model_name, attribute_name),
:value => resource.validation_property_value(attribute_name)
}.merge(violation.info)
::I18n.translate("#{i18n_scope}.#{violation_type}", options)
end
def i18n_scope(resource, model_name, attribute_name)
'datamapper.errors'
end
def transform_model_name(model_name)
::I18n.translate("models.#{model_name}")
end
def transform_attribute_name(model_name, attribute_name)
::I18n.translate("models.#{model_name}.attributes.#{attribute_name}")
end
end # class BetterI18n
And then set your transformer as the default like so:
DataMapper::Validation::Violation.default_transformer = BetterI18n.new
To clarify the intent of this code: if you follow the link and check out the #generate_message
method from ActiveModel::Validations
, you'll see a potentially long list of defaults that is built up for every violation message lookup.
This interface (MessageTransformer#transform
) allows you to specify exactly the strategy you want for violation message lookups, and it can do that, and only that (as opposed to supporting many different strategies by always looking everywhere).
@emmanuel Thx for the explanation, this works perfectly for me! In case anyone else needs it, here's the errors.en.yml file I use. I haven't yet tested all interpolations but I went through dm-validation's rules and took the keys from there.
en:
datamapper:
errors:
absent: "%{attribute} must be absent"
inclusion: "%{attribute} must be one of %{set}"
invalid: "%{attribute} has an invalid format"
confirmation: "%{attribute} does not match the confirmation"
accepted: "%{attribute} is not accepted"
nil: "%{attribute} must not be nil"
blank: "%{attribute} must not be blank"
length_between: "%{attribute} must be between %{min} and %{max} characters long"
too_long: "%{attribute} must be at most %{maximum} characters long"
too_short: "%{attribute} must be at least %{minimum} characters long"
wrong_length: "%{attribute} must be %{expected} characters long"
taken: "%{attribute} is already taken"
not_a_number: "%{attribute} must be a number"
not_an_integer: "%{attribute} must be an integer"
greater_than: "%{attribute} must be greater than %{minimum}"
greater_than_or_equal_to: "%{attribute} must be greater than or equal to %{minimum}"
equal_to: "%{attribute} must be equal to %{expected}"
not_equal_to: "%{attribute} must not be equal to %{not_expected}"
less_than: "%{attribute} must be less than %{maximum}"
less_than_or_equal_to: "%{attribute} must be less than or equal to %{maximum}"
value_between: "%{attribute} must be between %{minimum} and %{maximum}"
primitive: "%{attribute} must be of type %{primitive}"
@snusnu — this is great!
Someday I'll figure out how to set up dm-validations to work out-of-the-box with i18n (including putting your yml in 18n's load path) while still keeping the i18n dependency optional.
fwiw, here are my german translations for the above:
datamapper:
errors:
absent: "%{attribute} darf nicht vorhanden sein"
inclusion: "%{attribute} muss einer der folgenden Werte sein: %{set}"
invalid: "%{attribute} hat ein ungültiges Format"
confirmation: "%{attribute} stimmt mit der Bestätigung nicht überein"
accepted: "%{attribute} wurde nicht akzeptiert"
nil: "%{attribute} muss vorhanden sein"
blank: "%{attribute} muss ausgefüllt sein"
length_between: "%{attribute} muss zwischen %{min} und %{max} Zeichen lang sein"
too_long: "%{attribute} darf maximal %{maximum} Zeichen lang sein"
too_short: "%{attribute} muss mindestens %{minimum} Zeichen enthalten"
wrong_length: "%{attribute} muss genau %{expected} Zeichen enthalten"
taken: "%{attribute} wird bereits verwendet"
not_a_number: "%{attribute} muss eine Zahl sein"
not_an_integer: "%{attribute} muss eine Ganzzahl sein"
greater_than: "%{attribute} muss größer als %{minimum} sein"
greater_than_or_equal_to: "%{attribute} muss größer oder gleich %{minimum} sein"
equal_to: "%{attribute} muss den Wert %{expected} haben"
not_equal_to: "%{attribute} darf nicht den Wert %{not_expected} haben"
less_than: "%{attribute} muss einen Wert kleiner als %{maximum} haben"
less_than_or_equal_to: "%{attribute} muss einen Wert kleiner oder gleich %{maximum} haben"
value_between: "%{attribute} muss einen Wert zwischen %{minimum} und %{maximum} haben"
primitive: "%{attribute} muss vom Typ %{primitive} sein"
Hi, I tried the custom MessageTransformer and the DefaultI18n that comes with the new version of the DataMapper but for some reason "model_name" it's now available and I get "undefined method `model_name' for".
Changing model_name to storage_name solves the problem, I'm really new to DataMapper maybe I'm doing something wrong?
Hi, I am using ruby 2.0.0, sinatra 1.4.4 and Datamapper 1.2.0. I can´t get datamapper working with DefaultI18n.new. I tried to add this line: DataMapper::Validation::Violation.default_transformer = DataMapper::Validation::MessageTransformer::DefaultI18n.new Inside the configure block of my sinatra app.
Any help would be great :)
I want to revise the issue of localization again because it has been one of the pain points for me in using datamapper; and the last (and only?) ticket I could find on this issue is several years old and closed.
dm-validations allows changing the error messages, but that's of little use without the ability to translate model and field names. It also doesn't integrate nicely e.g. into Rails.
There are different ways to achieve this, but I think it would be good if dm-validations would be language-agnostic. For example instead of generating an error message immediately when adding an error, add information about the violation: the resource, field name, failed validation and validation-specific context values (user object, :password, :too_short, :minimum => 6).
With such an approach it would be easy to put different ways of translation on top of it, e.g.
Custom messages can be added as additional context information (my fork of dm-validations does that), though with the suggested localizability these would maybe become a bit superfluous.
Created by gix - 2011-01-29 20:03:11 UTC
Original Lighthouse ticket: http://datamapper.lighthouseapp.com/projects/20609/tickets/1477