Open chanlito opened 6 years ago
What's so hard about understanding that a project is dead? Certainly your all-caps rage ain't gonna bring it back. npm init
and start working on your own instead.
nice one @lazarljubenovic =) it's a quiet saturday afternoon where i'm at so why not pile on a little...
@mikila85, @picardplaisimond, @olawalejuwonm, etc... we did the wrappers for custom messaging that i mentioned up in this thread over 2 years ago and those have been rock solid without need for change still cranking great for tons of production usage every day, making it very much not dead to us... the customizing is not hard code... if you look at this library as a nice core engine for managing typescript decorators and then customizing to your needs on top of that, it is very useful... even for adding in your own new validation routines that can be called the same as those it comes with, very easy... it's of course your choice where you want to invest your time and it doesn't have to be here if you can find exactly what you need elsewhere by all means, but i just wanted to chime in and maybe provide a little "faith" that this might very well work for you without that much effort if you dig in on the samples provided above and ask questions to folks that are still actively using this lib... i'll help where i can answer a direct question, our needs are pretty simple, e.g. we're not doing multi-language so you'd have to get that kind of help from the other samples posted.
i realized a main thing i wanted to point out is that some library communities can be quiet because they're dead and it's best to take that as a sign to move on, but this one is quiet because it has achieved feature maturity (for our needs at least) which is a completely opposite reason to stick around and learn more.
I've seen the solution you suggested @Beej126. I almost went for it. It seems solid.
But we finally decided to drop this feature. We use this library in our backend with NestJS. I was looking for a way to add translation to error messages. But the default messages are good enough (and it's more for a debug purpose since the user will not see it)
Indeed, since we also validate the forms before sending them to the backend, we can generate the error messages directly on the frontend client (with the translations).
For more complex validations requiring this library, we can at least change the message to return an error code (that we can format on the frontend).
Finally, there's a library called NestJs-I18n that has a function for translating error messages. It's not global, but it's already very good. But we've decided not to use it, since we're now handling all this on the frontend.
On the other hand, I'm still very disappointed that such a simple feature isn't available... but like @Beej126 said, this library reached maturity. So we shouldn't wait for an update to happen tomorrow. It does an excellent job in all other aspects of data validation.
I hope this reflexion will help other people.
Funciono para mi, en express
import i18next from "i18next";
import i18nextBackend from "i18next-fs-backend";
import i18nextMiddleware from "i18next-http-middleware";
i18next
.use(i18nextBackend)
.use(i18nextMiddleware.LanguageDetector)
.init({
fallbackLng: "es",
backend: {
loadPath: _dirname + "/../locales/{{lng}}/traduccion.json",
},
});
const app: Application = express();
app.use(i18nextMiddleware.handle(i18next));
@IsEmail(
{},
{
message: () => {
return i18next.t("is_email");
},
}
)
Nose porque pero si lo pongo asi:
message: i18next.t("is_email")
No funciona
Pero si lo coloco asi, funciona bien
message: () => i18next.t("is_email"),
o que seria lo mismo
message: () =>
{
return i18next.t("is_email")
}
A validation message without the property name will be beneficial. The actual field name may not be the same as the variable name.
We have developed a wrapper that thinly wraps the class-validator to make the default error messages in a specific language. It is not possible to translate the messages according to the client's language like i18next does, but we would be happy if you could use it if you just want to translate the messages into your own language. It is just wrapping the class-validator rules, so it won't break your environment.
This library just had a new release on jan 12.
Any update as regards this issue?
Will it be ever happening?
FYI: I did some tweaks with the package which enables you to pass a transformation function at the time of validation to modify the validation error message. you can write your own implementation of the transformation function (which will be called with the name or type of validation, example: isString, isArray and so on) to handle the message as you want, but you can also pass i18next.t
or req.t t
to the validatorOptions as the transformation function which will pick the validation message from the translation.json based on the language.
Also incase you want to provide any specific message for any specific key in your schema then you can pass a transformKey to the validationOptions
of that specific property and write it's implementation in the translation.json
You can play with the live demo here or see the documentation here I hope that helps 🙌
My workaround. It seems to be working fine. No need any patch.
import { ValidationUtils } from 'class-validator/cjs/validation/ValidationUtils';
export const ErrorMessages = {
arrayContains: 'поле $property должно содержать $constraint1 значений',
arrayMaxSize:
'поле $property должно содержать не более $constraint1 элементов',
arrayMinSize:
'поле $property должно содержать не менее $constraint1 элементов',
arrayNotContains: 'поле $property не должно содержать $constraint1 значений',
arrayNotEmpty: 'поле $property не должно быть пустым',
contains: 'поле $property должно содержать строку $constraint1',
equals: 'поле $property должно быть равно $constraint1',
isAlpha: 'поле $property должно содержать только буквы (a-zA-Z)',
isAlphanumeric: 'поле $property должно содержать только буквы и цифры',
isArray: 'поле $property должно быть массивом',
isAscii: 'поле $property должно содержать только символы ASCII',
isBIC: 'поле $property должно быть BIC или SWIFT кодом',
isBase32: 'поле $property должно быть закодировано в формате base32',
isBase58: 'поле $property должно быть закодировано в формате base58',
isBase64: 'поле $property должно быть закодировано в формате base64',
isBoolean: 'поле $property должно быть логическим значением',
isBooleanString:
'поле $property должно быть строкой, представляющей логическое значение',
isBtcAddress: 'поле $property должно быть адресом BTC',
isCreditCard: 'поле $property должно быть кредитной картой',
isCurrency: 'поле $property должно быть валютой',
isDataURI: 'поле $property должно быть в формате data URI',
isDate: 'поле $property должно быть экземпляром Date',
isDateString:
'поле $property должно быть корректной строкой даты в формате ISO 8601',
isDecimal: 'поле $property не является действительным десятичным числом.',
isDefined: 'поле $property не должно быть null или undefined',
isDivisibleBy: 'поле $property должно быть кратным $constraint1',
isEAN: 'поле $property должно быть EAN (европейским артикулом)',
isEmail: 'поле $property должно быть адресом электронной почты',
isEmpty: 'поле $property должно быть пустым',
isEnum:
'поле $property должно быть одним из следующих значений: $constraint2',
isEthereumAddress: 'поле $property должно быть адресом Ethereum',
isFQDN: 'поле $property должно быть допустимым доменным именем',
isFirebasePushId: 'поле $property должно быть идентификатором Firebase Push',
isFullWidth: 'поле $property должно содержать символы полной ширины',
isHSL: 'поле $property должно быть цветом в формате HSL',
isHalfWidth: 'поле $property должно содержать символы половины ширины',
isHash: 'поле $property должно быть хешем типа $constraint1',
isHexColor: 'поле $property должно быть шестнадцатеричным цветом',
isHexadecimal: 'поле $property должно быть шестнадцатеричным числом',
isIBAN: 'поле $property должно быть IBAN',
isIP: 'поле $property должно быть IP-адресом',
isISBN: 'поле $property должно быть ISBN',
isISIN:
'поле $property должно быть ISIN (идентификатором ценной бумаги/акции)',
isISO31661Alpha2:
'поле $property должно быть допустимым кодом ISO31661 Alpha2',
isISO31661Alpha3:
'поле $property должно быть допустимым кодом ISO31661 Alpha3',
isISO8601:
'поле $property должно быть корректной строкой даты в формате ISO 8601',
isISRC: 'поле $property должно быть ISRC',
isISSN: 'поле $property должно быть ISSN',
isIdentityCard: 'поле $property должно быть номером удостоверения личности',
isIn: 'поле $property должно быть одним из следующих значений: $constraint1',
isInt: 'поле $property должно быть целым числом',
isJSON: 'поле $property должно быть строкой JSON',
isJWT: 'поле $property должно быть строкой JWT',
isLatLong: 'поле $property должно быть строкой широты,долготы',
isLatitude: 'поле $property должно быть строкой или числом широты',
isLocale: 'поле $property должно быть локалью',
isLongitude: 'поле $property должно быть строкой или числом долготы',
isLowercase: 'поле $property должно быть строкой в нижнем регистре',
isMacAddress: 'поле $property должно быть MAC-адресом',
isMagnetURI: 'поле $property должно быть в формате magnet URI',
isMilitaryTime:
'поле $property должно быть корректным представлением времени военного времени в формате HH:MM',
isMimeType: 'поле $property должно быть форматом MIME типа',
isMobilePhone: 'поле $property должно быть номером телефона',
isMongoId: 'поле $property должно быть идентификатором MongoDB',
isMultibyte:
'поле $property должно содержать один или более многобайтовых символов',
isNegative: 'поле $property должно быть отрицательным числом',
isNotEmpty: 'поле $property не должно быть пустым',
isNotEmptyObject: 'поле $property должно быть непустым объектом',
isNotIn:
'поле $property не должно быть одним из следующих значений: $constraint1',
isNumber:
'поле $property должно быть числом, соответствующим указанным ограничениям',
isNumberString: 'поле $property должно быть строкой числа',
isObject: 'поле $property должно быть объектом',
isOctal: 'поле $property должно быть допустимым восьмеричным числом',
isPassportNumber:
'поле $property должно быть действительным номером паспорта',
isPhoneNumber: 'поле $property должно быть действительным номером телефона',
isPort: 'поле $property должно быть портом',
isPositive: 'поле $property должно быть положительным числом',
isPostalCode: 'поле $property должно быть почтовым индексом',
isRFC3339: 'поле $property должно быть датой в формате RFC 3339',
isRgbColor: 'поле $property должно быть цветом RGB',
isSemVer:
'поле $property должно соответствовать спецификации семантического версионирования',
isString: 'поле $property должно быть строкой',
isStrongPassword: 'поле $property недостаточно надёжно',
isSurrogatePair:
'поле $property должно содержать любые символы пары замещения',
isTimeZone: 'поле $property должно быть допустимой временной зоной IANA',
isUUID: 'поле $property должно быть UUID',
isUppercase: 'поле $property должно быть строкой в верхнем регистре',
isUrl: 'поле $property должно быть адресом URL',
isVariableWidth:
'поле $property должно содержать символы полной и половинной ширины',
matches:
'поле $property должно соответствовать регулярному выражению $constraint1',
max: 'поле $property не должно быть больше $constraint1',
maxDate: 'поле $property - это $constraint1',
maxLength:
'поле $property должно быть короче или равно $constraint1 символам',
min: 'поле $property не должно быть меньше $constraint1',
minDate: 'минимальная допустимая дата для $property - это $constraint1',
minLength:
'поле $property должно быть длиннее или равно $constraint1 символам',
notContains: 'поле $property не должно содержать строку $constraint1',
notEquals: 'поле $property не должно быть равно $constraint1',
isISO4217CurrencyCode:
'поле $property должно быть допустимым кодом валюты ISO4217',
isTaxId: 'поле $property должно быть номером налоговой идентификации',
};
app.useGlobalPipes(
new ValidationPipe({
validationError: {
value: true,
},
exceptionFactory(errors) {
const result = [];
errors.forEach((error) => {
const validationMetas =
getMetadataStorage().getTargetValidationMetadatas(
error.target.constructor,
error.target.constructor.name,
true,
false,
);
const validationMeta = validationMetas.find(
(meta) => meta.propertyName === error.property,
);
const validationArguments = {
targetName: error.target.constructor.name,
property: error.property,
value: error.value,
constraints: validationMeta.constraints,
};
const message = ValidationUtils.replaceMessageSpecialTokens(
ErrorMessages[Object.keys(error.constraints)[0]],
validationArguments,
);
result.push(message);
});
return new BadRequestException(result);
},
}),
);
Enhancing @musaevonline suggestion, allowing for a customizable field name in the response message:
Example:
@IsString({ context: { fieldName: 'nome do usuário' } }) <= optional custom field name
userName: string;
Code:
/**
* Arguments being sent to message builders - user can create message either by simply returning a string,
* either by returning a function that accepts MessageArguments and returns a message string built based on these arguments.
*/
export interface ValidationArguments {
/**
* Constraints set by this validation type.
*/
constraints: any[];
/**
* Name of the field that is being validated
*/
fieldName?: string; <= Added this field
/**
* Object that is being validated.
*/
object?: object;
/**
* Name of the object's property being validated.
*/
property: string;
/**
* Name of the target that is being validated.
*/
targetName: string;
/**
* Validating value.
*/
value: any;
}
export class ValidationUtils {
static replaceMessageSpecialTokens(
message: ((args: ValidationArguments) => string) | string,
validationArguments: ValidationArguments,
): string {
let messageString: string;
if (message instanceof Function) {
messageString = (message as (args: ValidationArguments) => string)(validationArguments);
} else if (typeof message === 'string') {
messageString = message;
}
if (messageString && Array.isArray(validationArguments.constraints)) {
validationArguments.constraints.forEach((constraint, index) => {
messageString = messageString.replace(
new RegExp(`\\$constraint${index + 1}`, 'g'),
constraintToString(constraint),
);
});
}
if (
messageString &&
validationArguments?.value &&
['boolean', 'number', 'string'].includes(typeof validationArguments.value)
)
messageString = messageString.replace(/\$value/g, validationArguments.value);
if (messageString) {
messageString = messageString.replace(
/\$property/g,
validationArguments.fieldName || validationArguments.property, <= Added this check
);
}
if (messageString) messageString = messageString.replace(/\$target/g, validationArguments.targetName);
return messageString;
}
}
// main.ts
app.useGlobalPipes( new ValidationPipe({
exceptionFactory(errors): BadRequestException {
const result: string[] = [];
errors.forEach((error) => {
const validationMetas = getMetadataStorage().getTargetValidationMetadatas(
error.target.constructor,
error.target.constructor.name,
true,
false,
);
const validationMeta = validationMetas.find((meta) => meta.propertyName === error.property);
let errorKey = Object.keys(error.constraints)[0];
const errorMessage = error.constraints[errorKey];
// fix isLength ambiguous message
if (errorKey === 'isLength') {
if (errorMessage.includes('be longer')) {
errorKey = 'minLength';
} else if (errorMessage.includes('be shorter')) {
errorKey = 'maxLength';
} else {
Logger.warn(`Mensagem inesperada para a propriedade 'isLength': ${errorMessage}`);
}
}
const validationArguments: ValidationArguments = {
constraints: validationMeta.constraints,
fieldName: error?.contexts?.[errorKey]?.fieldName,
property: error.property,
targetName: error.target.constructor.name,
value: error.value,
};
const messageTemplate = errorMessages[errorKey];
if (!messageTemplate) {
Logger.warn(`Chave de erro não encontrada na lista de Mensagens de Erro: ${errorKey}`);
}
const message = ValidationUtils.replaceMessageSpecialTokens(messageTemplate, validationArguments);
result.push(message);
});
return new BadRequestException(result);
},
validationError: {
value: true,
},
})
)
export const errorMessages = {
arrayContains: 'o campo $property deve conter $constraint1 valores',
arrayMaxSize: 'o campo $property deve conter no máximo $constraint1 elementos',
arrayMinSize: 'o campo $property deve conter pelo menos $constraint1 elementos',
arrayNotContains: 'o campo $property não deve conter $constraint1 valores',
arrayNotEmpty: 'o campo $property não deve estar vazio',
contains: 'o campo $property deve conter a string $constraint1',
equals: 'o campo $property deve ser igual a $constraint1',
isAlpha: 'o campo $property deve conter apenas letras (a-zA-Z)',
isAlphanumeric: 'o campo $property deve conter apenas letras e números',
isArray: 'o campo $property deve ser um array',
isAscii: 'o campo $property deve conter apenas caracteres ASCII',
isBIC: 'o campo $property deve ser um código BIC ou SWIFT',
isBase32: 'o campo $property deve estar codificado no formato base32',
isBase58: 'o campo $property deve estar codificado no formato base58',
isBase64: 'o campo $property deve estar codificado no formato base64',
isBoolean: 'o campo $property deve ser um valor booleano',
isBooleanString: 'o campo $property deve ser uma string que representa um valor booleano',
isBtcAddress: 'o campo $property deve ser um endereço BTC',
isCreditCard: 'o campo $property deve ser um cartão de crédito',
isCurrency: 'o campo $property deve ser uma moeda',
isDataURI: 'o campo $property deve estar no formato data URI',
isDate: 'o campo $property deve ser uma instância de Date',
isDateString: 'o campo $property deve ser uma string de data válida no formato ISO 8601',
isDecimal: 'o campo $property não é um número decimal válido',
isDefined: 'o campo $property não deve ser null ou undefined',
isDivisibleBy: 'o campo $property deve ser divisível por $constraint1',
isEAN: 'o campo $property deve ser um EAN (código de artigo europeu)',
isEmail: 'o campo $property deve ser um endereço de email',
isEmpty: 'o campo $property deve estar vazio',
isEnum: 'o campo $property deve ser um dos seguintes valores: $constraint2',
isEthereumAddress: 'o campo $property deve ser um endereço Ethereum',
isFQDN: 'o campo $property deve ser um nome de domínio válido',
isFirebasePushId: 'o campo $property deve ser um identificador Firebase Push',
isFullWidth: 'o campo $property deve conter caracteres de largura total',
isHSL: 'o campo $property deve ser uma cor no formato HSL',
isHalfWidth: 'o campo $property deve conter caracteres de meia largura',
isHash: 'o campo $property deve ser um hash do tipo $constraint1',
isHexColor: 'o campo $property deve ser uma cor hexadecimal',
isHexadecimal: 'o campo $property deve ser um número hexadecimal',
isIBAN: 'o campo $property deve ser um IBAN',
isIP: 'o campo $property deve ser um endereço IP',
isISBN: 'o campo $property deve ser um ISBN',
isISIN: 'o campo $property deve ser um ISIN (identificador de título/ação)',
isISO4217CurrencyCode: 'o campo $property deve ser um código de moeda ISO4217 válido',
isISO8601: 'o campo $property deve ser uma string de data válida no formato ISO 8601',
isISO31661Alpha2: 'o campo $property deve ser um código ISO31661 Alpha2 válido',
isISO31661Alpha3: 'o campo $property deve ser um código ISO31661 Alpha3 válido',
isISRC: 'o campo $property deve ser um ISRC',
isISSN: 'o campo $property deve ser um ISSN',
isIdentityCard: 'o campo $property deve ser um número de documento de identidade',
isIn: 'o campo $property deve ser um dos seguintes valores: $constraint1',
isInt: 'o campo $property deve ser um número inteiro',
isJSON: 'o campo $property deve ser uma string JSON',
isJWT: 'o campo $property deve ser uma string JWT',
isLatLong: 'o campo $property deve ser uma string de latitude e longitude',
isLatitude: 'o campo $property deve ser uma string ou número de latitude',
isLocale: 'o campo $property deve ser uma localidade',
isLongitude: 'o campo $property deve ser uma string ou número de longitude',
isLowercase: 'o campo $property deve ser uma string em minúsculas',
isMacAddress: 'o campo $property deve ser um endereço MAC',
isMagnetURI: 'o campo $property deve estar no formato magnet URI',
isMilitaryTime: 'o campo $property deve ser uma representação de hora militar válida no formato HH:MM',
isMimeType: 'o campo $property deve ser um tipo MIME válido',
isMobilePhone: 'o campo $property deve ser um número de telefone válido',
isMongoId: 'o campo $property deve ser um identificador MongoDB',
isMultibyte: 'o campo $property deve conter um ou mais caracteres multibyte',
isNegative: 'o campo $property deve ser um número negativo',
isNotEmpty: 'o campo $property não deve estar vazio',
isNotEmptyObject: 'o campo $property deve ser um objeto não vazio',
isNotIn: 'o campo $property não deve ser um dos seguintes valores: $constraint1',
isNumber: 'o campo $property deve ser um número que atenda às restrições especificadas',
isNumberString: 'o campo $property deve ser uma string numérica',
isObject: 'o campo $property deve ser um objeto',
isOctal: 'o campo $property deve ser um número octal válido',
isPassportNumber: 'o campo $property deve ser um número de passaporte válido',
isPhoneNumber: 'o campo $property deve ser um número de telefone válido',
isPort: 'o campo $property deve ser uma porta',
isPositive: 'o campo $property deve ser um número positivo',
isPostalCode: 'o campo $property deve ser um código postal',
isRFC3339: 'o campo $property deve ser uma data no formato RFC 3339',
isRgbColor: 'o campo $property deve ser uma cor RGB',
isSemVer: 'o campo $property deve estar em conformidade com a especificação de versionamento semântico',
isString: 'o campo $property deve ser uma string',
isStrongPassword: 'o campo $property não é seguro o suficiente',
isSurrogatePair: 'o campo $property deve conter qualquer caractere de par substituto',
isTaxId: 'o campo $property deve ser um número de identificação fiscal',
isTimeZone: 'o campo $property deve ser um fuso horário IANA válido',
isUppercase: 'o campo $property deve ser uma string em maiúsculas',
isUrl: 'o campo $property deve ser um URL',
isUuid: 'o campo $property deve ser um UUID',
isVariableWidth: 'o campo $property deve conter caracteres de largura total e meia largura',
matches: 'o campo $property deve corresponder à expressão regular $constraint1',
max: 'o campo $property não deve ser maior que $constraint1',
maxDate: 'a data máxima para o campo $property é $constraint1',
maxLength: 'o campo $property deve ter no máximo $constraint1 caracteres',
min: 'o campo $property não deve ser menor que $constraint1',
minDate: 'a data mínima permitida para o campo $property é $constraint1',
minLength: 'o campo $property deve ter no mínimo $constraint1 caracteres',
notContains: 'o campo $property não deve conter a string $constraint1',
notEquals: 'o campo $property não deve ser igual a $constraint1',
};
Instead of passing custom
message
to each decorator.