ajv-i18n
ajv-i18n copied to clipboard
Make i18n a real AJV plugin
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 🤓
Actually I just developed it in my fork: https://github.com/ggondim/ajv-i18n/commit/aaf14588b2f935d7a97657cf087b390579090bb7
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;
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.
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)