Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make i18n a real AJV plugin #153

Open
ggondim opened this issue Jul 5, 2020 · 3 comments
Open

Make i18n a real AJV plugin #153

ggondim opened this issue Jul 5, 2020 · 3 comments

Comments

@ggondim
Copy link
Contributor

ggondim commented Jul 5, 2020

Implementation proposal

Considering ajv-i18n's localization function already mutates the errors object:

errorsText()

When using ajv-i18 as a plugin, the ajv.errorsText() function should return the localized error messages.

Currently we need to hack the errors object and then call errorsText().

I propose expose a initialization function in ajv-i18n, just like other plugins, so this function could replace the errorsText() by its localized method.

// @param {Ajv} ajv An Ajv instance
function initialize(ajv) {

  ajv._errorsText = ajv.errorsText;

  // intercepts errorsText function
  ajv.errorsText = (errors) => {
    localize(errors);
    return ajv._errorsText(errors);
  }

}

So, this initialization could be used like other plugins (ajv-keywords example):

require('ajv-i18n')(ajv);

Here is a working Run-Kit running this example.

errors

If the localize method already converts all errors messages in validate.errors , the plugin could also replace errors by a getter/setter that does this conversion:

function initialize(ajv, locale) {

  ajv._compile = ajv.compile;

  // intercepts compile function
  ajv.compile = schema => {
    const validate = ajv._compile(schema);
    Object.defineProperty(validate, 'errors', makeLocalizedErrors(ajv));
    return validate;
  }

}

function makeLocalizedErrors(ajv) {
  return {
    get: () => {
      return ajv._errors;
    },
    set: (errors) => {
      ajv._errors = localize(errors);
    },
    configurable: true
  };
}

Considerations about the existent implementation

What would happen to the current localize function in module.exports?

To work like other AJV's plugins, the current module.exports needs to be changed to the proposed initialization function, that would lead to a breaking change (major version).

But the current localization function exported in localize.jst could be exported as a property of the initialization function, just like ajv-keywords does with the get() function.

module.exports = initialize;

function initialize(ajv) {
  // ... initialize function as defined above
}

initialize.localize = localize;

function localize(errors) {
   // current exported function in module.exports
}

If you don't want to make a breaking change, at least approve the inverse

The inverse could be made, so this plugin injection could be:

module.exports = localize;

function localize(errors) {
   // current exported function in module.exports
}

function initialize(ajv) {
  // ... initialize function as defined above
}

localize.localize = initialize;

... used as:

const { localize } = require('ajv-i18n');
localize(ajv);

When can I make a pull request?

If this implementation design is approved, I start to code it and in a few hours I make a pull request.

Looking forward to a special reply from the most active member, @epoberezkin 🤓

ggondim added a commit to ggondim/ajv-i18n that referenced this issue Jul 5, 2020
@ggondim
Copy link
Contributor Author

ggondim commented Jul 5, 2020

Actually I just developed it in my fork: ggondim@aaf1458

Example of a localize file (en/index.js) built with npm run build:

'use strict';

function localize_en(errors) {
  // omitted for legibility  
};

function makeLocalizedErrors(ajv) {
  return {
    get: function() {
      return ajv._errors;
    },
    set: function(errors) {
      ajv._errors = localize_en(errors);
    },
    configurable: true
  };
}

function initialize(ajv) {
  ajv._compile = ajv.compile;
  ajv.compile = schema => {
    const validate = ajv._compile(schema);
    Object.defineProperty(validate, 'errors', makeLocalizedErrors(ajv));
    return validate;
  }
}
initialize.localize = localize_en;
module.exports = initialize;

ggondim added a commit to ggondim/ajv-i18n that referenced this issue Jul 5, 2020
@epoberezkin
Copy link
Member

Thank you. It is probably better to do in ajv v7-beta that is just released - this implementation will have to be rewritten probably anyway.

@gabormagyar
Copy link

For anyone else finding this in the future here's an up-to-date workaround to this problem.

I'm using ajv as part of a jsonforms.io form, so I don't have easy access to the error objects. Alternatively I would have had to add localize calls to each individual form cell renderer, which isn't very clean.

Here I overwrite the ajv.compile function with a wrapper into which I can inject the localization call.

  const ajv = createAjv({messages: false})
  const compileFunction = ajv.compile
  ajv.compile = Object.assign((...compileProps: Parameters<typeof compileFunction>) => {
    const validateFunction = compileFunction.apply(ajv, compileProps)

    const newValidateFunction = Object.assign((...innerProps: Parameters<typeof validateFunction>) => {
      const validationResult = validateFunction.apply(compileFunction, innerProps)
      // @ts-expect-error `ErrorObject` is incorrect type in ajv-i18n: https://github.com/ajv-validator/ajv-i18n/pull/205
      localize(validateFunction.errors)
      Object.assign(newValidateFunction, validateFunction)
      return validationResult
    }, validateFunction)

    return newValidateFunction
  }, compileFunction)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants