validate.js icon indicating copy to clipboard operation
validate.js copied to clipboard

Internationalisation (i18n, multi-language support)

Open jakubrohleder opened this issue 9 years ago • 15 comments

First of all I real like the library approach that makes it useable for multiple frameworks. The one option I real miss is some kind multi-language support. Have you thought about adding these feature?

I think the simples and sufficient fix would be to add option that turns messages into codes (like 'VALIDATEJS.ERROR.REQUIRED'). So people can handle translation on their own. What do you think about such improvement?

Ps. sorry for weird title but I want the topic to be easy to find for others.

jakubrohleder avatar Oct 01 '15 19:10 jakubrohleder

i18n is definitely something that is lacking in this library. What you describe is already possible actually.

All included validators support overwriting the default options. So you could do this:

validate.validators.presence.options = {
  message: "VALIDATEJS.ERROR.REQUIRED"
};
validate.options = {fullMessages: false};
validate({}, {name: {presence: true}});
// => {"name": ["VALIDATEJS.ERROR.REQUIRED"]}

But if you have a concrete suggestion I'd be happy to consider it!

ansman avatar Oct 05 '15 17:10 ansman

Thanks for this @ansman

jsenecal avatar Oct 05 '15 18:10 jsenecal

@ansman Thanks for the answer and temporary fix.

I'll take a closer look at the source code and hopefully propose more specific solution along with a PR.

jakubrohleder avatar Oct 05 '15 19:10 jakubrohleder

@jakubrohleder I would recommend that you describe your solution before implementing it in case it is in another direction than the library is heading.

This could potentially be added in a separate file that could be included after validate.js which would set the default messages when included validate-i18n.js for example, maybe even as a separate library with official support.

ansman avatar Oct 05 '15 19:10 ansman

@ansman I think that library that modifies another library is too hackish and can cause potential problems in the future. In my opinion the best solution would be something that is built in, yet optional.

I'll describe my idea here before making a PR.

jakubrohleder avatar Oct 05 '15 20:10 jakubrohleder

I fail to see the need for validate.js to do anything around this?

// ./i18n/en.js

export default {
  'UsernameRequired': 'The username is required'
}
// ./i18n/jp.js

export default {
  'UsernameRequired': 'ユーザ名が必要です'
}
import i18n from 'mythical-i18n-library';
import en from './i18n/en';
import jp from './i18n/jp';

let {translate} = i18n({lang: 'en', locales: {en, jp});

let constraints = {
  username: {
    presence: {
      message: translate('UsernameRequired')
    }
  }
};

validate({}, constraints);

airtonix avatar Feb 14 '16 04:02 airtonix

@airtonix Technically validate.js already supports this but some things could be nicer.

The whole idea of prefixing the message with the attribute name needs to be rethought if translations should feel like a first class citizen. For example I've been playing around with the idea that instead of always prefixing that the message will have to contain a marker (such as %{attribute} for lowercase and %{Attribute} for upper case.). But this will still require the attribute to be translated before inserting.

It's not high priority but it's definitely something that needs to be looked at before 1.0

ansman avatar Feb 18 '16 15:02 ansman

@ansman I still feel like translation is really not the scope of validate.js.

It is sufficiently designed to allow a project integrator to use other libraries dedicated to i18n.

airtonix avatar Aug 10 '16 08:08 airtonix

@airtonix Exactly, validate.js shouldn't support i18n in itself but it should make it easy to implement.

ansman avatar Aug 15 '16 18:08 ansman

@ansman @airtonix how one should do it? is there a proper hook points? I've looked at documentation and found validator.prettify, but seems like it also prettifies values. What libs do u recommend to use for i18n? Btw, idea with %{attribute} looks promising :).

mikegolod avatar May 26 '17 21:05 mikegolod

@ansman That %{attribute} / %{Attribute} would be extremely helpful for getting started on translations. Sending %{attribute} can't be blank" to a translator is a lot more approachable than trying to send English fragments like can't be blank to them.

vdh avatar Sep 06 '17 08:09 vdh

Any updates with this? I did the workaround proposed for @ansman in https://github.com/ansman/validate.js/issues/74#issuecomment-145609665, but for validators that involves variables as "is too short (minimum is 8 characters)" is too ugly to translate a general message

joelmora avatar Jun 22 '18 17:06 joelmora

I solved it in my solution like Constraint:

{
  title: {
    presence: { allowEmpty: false, message: 'VALIDATION.REQUIRED' },
    length: {
      minimum: 5,
      message: { message: 'VALIDATION.MINLENGTH', value: 5 }
    }
  }

English translation:

"MINLENGTH": "Must provide at least {{value}} characters"

and calling the i18n framework like

let result = ...; // validate object, get field where the message should be printed
translate(result.message, { value: result.value});

Very simple task tbh. You could even do something like

Constraint:

{
  title: {
    presence: { allowEmpty: false, message: 'VALIDATION.REQUIRED' },
    length: {
      minimum: 5,
      maximum: 30,
      message: { message: 'VALIDATION.LENGTH_BETWEEN', value: [5, 30] }
    }
  }

English translation:

"LENGTH_BETWEEN": "You must provide betweeen {{from}} and {{to}} characters"

Calling

let result = ...; // validate object, get field where the message should be printed
translate(result.message, { from: result.value[0], to: result.value[1]});

...and so on.

I also think there's nothing more about i18n that validate.js needs to support, as it's very environment specific.

marcelcremer avatar Dec 09 '18 13:12 marcelcremer

I think a new error format like validate.options = {format: "code"} where the message returned would be codes (ex: { message: 'VALIDATION.LENGTH_BETWEEN', value: [5, 30] }) instead of English messages could help.

dalssoft avatar Oct 02 '19 22:10 dalssoft

import Validator from "validate.js";
import I18n from "react-native-i18n";
/**
 * need to add pronis if async is required
 * need to add format and parse method for date and date time
 */
export default class ValidatorJS {
  constructor(translateFn = I18n.t.bind(I18n)) {
    this.translate = translateFn;
    this.Validator = Validator;
    this.Validator.formatters.custom = this.overloadFormatFunc;
    this.Validator.convertErrorMessages = this.convertErrorMessages;
    this.overloadErrorMessages();
  }
  /** adapters */
  bindRequiredToPresence = (constraints) =>
    Object.entries(constraints).reduce(
      (acc, [key, { required, ...ObjVal }]) => {
        acc[key] = { presence: required, ...ObjVal };
        return acc;
      },
      {}
    );
  /** binds default messages */
  overloadErrorMessages = () => {
    this.Validator.validators.date.options = {
      message: "DATE_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.datetime.options = {
      message: "DATETIME_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.email.options = {
      message: "EMAIL_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.equality.options = {
      message: "EQUALITY_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.exclusion.options = {
      message: "EXCLUSION_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.format.options = {
      message: "FORMAT_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.inclusion.options = {
      message: "INCLUSION_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.length.options = {
      message: "LENGTH_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.numericality.options = {
      message: "NUMERICALITY_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.presence.options = {
      message: "PRESSENCE_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.url.options = {
      message: "URL_VALIDATOR_ERROR_MSG",
    };
    this.Validator.validators.type.messages = {
      array: "ARRAY_VALIDATOR_ERROR_MSG",
      boolean: "BOOLEAN_VALIDATOR_ERROR_MSG",
      date: "DATE_VALIDATOR_ERROR_MSG",
      integer: "INTEGER_VALIDATOR_ERROR_MSG",
      number: "NUMBER_VALIDATOR_ERROR_MSG",
      object: "OBJECT_VALIDATOR_ERROR_MSG",
      string: "STRING_VALIDATOR_ERROR_MSG",
    };
  };

  convertErrorMessages = (errors, options) => {
    options = options || {};
    var ret = [];
    errors.forEach((errorInfo) => {
      var error = this.Validator.result(
        errorInfo.error,
        errorInfo.value,
        errorInfo.attribute,
        errorInfo.options,
        errorInfo.attributes,
        errorInfo.globalOptions
      );

      if (!this.Validator.isString(error)) {
        ret.push(errorInfo);
        return;
      }

      if (error[0] === "^") {
        error = error.slice(1);
      } else if (options.fullMessages !== false) {
        error = error;
      }
      error = error.replace(/\\\^/g, "^");
      error = this.Validator.format(error, {
        value: this.Validator.stringifyValue(errorInfo.value, options),
      });
      ret.push(this.Validator.extend({}, errorInfo, { error: error }));
    });
    return ret;
  };
  /** uses translate with formatfunc */
  overloadFormatFunc = (errors) =>
    errors.reduce((acc, { validator, attribute, value, error }) => {
      acc[attribute] = this.translate(error, {
        validator,
        attribute,
        value,
        prettified: this.Validator.capitalize(
          this.Validator.prettify(attribute)
        ),
      });
      return acc;
    }, {});
  /** overrides */
  validate = (data, constraints, options = { format: "custom" }) => {
    // console.log("validatingData:", data);
    // console.log("validatingconstraints:", constraints);
    return this.Validator(
      data,
      this.bindRequiredToPresence(constraints),
      options
    );
    // console.log("validatedRes:", res);
    // return res;
  };
}
// usage
export default class ValidatorAdapter {
    constructor(formCtx,validator=ValidatorJS,translate){
        this.validator = new validator(translate);
        this.formCtx = formCtx;
    }
    validate = () => this.validator.validate({...this.formCtx.state.values},{...this.formCtx.elements});
}
FORM extends React.Component{
constructor(props) {
    super(props);
    this.state = {values: {}, options: {}, errors: {}, isVisible: false};
    this.validator = new ValidatorAdapter(this,props.validator, props.translate);
  }.......
_handleSubmitAndValidate = (key="_handleSubmit")=>(e)=>{
    let {values,errors} = this.state;
    let valid = this.validator.validate();
    if(!valid){
      /** this means no error at all */
      this.setState({errors:{}})
      this[key](e,values,errors);
    }else{
      errors = {...errors, ...valid}
      this.setState({errors})
    }
  }
.....
}

azatTemirbek avatar Apr 13 '20 11:04 azatTemirbek